installAddonRecursive installs an addon and its dependencies recursively
(app *DdevApp, addonName string, verbose bool)
| 1158 | |
| 1159 | // installAddonRecursive installs an addon and its dependencies recursively |
| 1160 | func installAddonRecursive(app *DdevApp, addonName string, verbose bool) error { |
| 1161 | // Normalize the addon identifier for consistent circular dependency detection |
| 1162 | normalizedName := NormalizeAddonIdentifier(addonName) |
| 1163 | |
| 1164 | // Check for circular dependencies using normalized name |
| 1165 | if installStackMap[normalizedName] { |
| 1166 | return fmt.Errorf("circular dependency detected: %s", |
| 1167 | strings.Join(append(installStack, addonName), " -> ")) |
| 1168 | } |
| 1169 | |
| 1170 | installStack = append(installStack, addonName) |
| 1171 | installStackMap[normalizedName] = true |
| 1172 | defer func() { |
| 1173 | // Clean up both slice and map using normalized name |
| 1174 | installStack = installStack[:len(installStack)-1] |
| 1175 | delete(installStackMap, normalizedName) |
| 1176 | }() |
| 1177 | |
| 1178 | // Handle different dependency formats (same as ddev add-on get) |
| 1179 | parts := strings.Split(addonName, "/") |
| 1180 | extractedDir := "" |
| 1181 | tarballURL := "" |
| 1182 | var cleanup func() |
| 1183 | |
| 1184 | switch { |
| 1185 | // Local directory path (check this first before GitHub parsing) |
| 1186 | case fileutil.IsDirectory(addonName): |
| 1187 | extractedDir = addonName |
| 1188 | if verbose { |
| 1189 | util.Success("Installing from local directory: %s", addonName) |
| 1190 | } |
| 1191 | |
| 1192 | // Local tarball file |
| 1193 | case fileutil.FileExists(addonName) && (strings.HasSuffix(filepath.Base(addonName), "tar.gz") || strings.HasSuffix(filepath.Base(addonName), "tar") || strings.HasSuffix(filepath.Base(addonName), "tgz")): |
| 1194 | var err error |
| 1195 | extractedDir, cleanup, err = archive.ExtractTarballWithCleanup(addonName, true) |
| 1196 | if err != nil { |
| 1197 | return fmt.Errorf("unable to extract %s: %v", addonName, err) |
| 1198 | } |
| 1199 | defer cleanup() |
| 1200 | |
| 1201 | // URL to tarball (including GitHub tarball URLs) |
| 1202 | case strings.HasPrefix(addonName, "http://") || strings.HasPrefix(addonName, "https://"): |
| 1203 | tarballURL = addonName |
| 1204 | var err error |
| 1205 | extractedDir, cleanup, err = archive.DownloadAndExtractTarball(tarballURL, true) |
| 1206 | if err != nil { |
| 1207 | return fmt.Errorf("unable to download %v: %v", addonName, err) |
| 1208 | } |
| 1209 | defer cleanup() |
| 1210 | |
| 1211 | // GitHub owner/repo format (check this last, and exclude paths) |
| 1212 | case len(parts) == 2 && !strings.Contains(addonName, ".") && !strings.HasPrefix(addonName, "."): |
| 1213 | return InstallAddonFromGitHub(app, addonName, "", verbose) |
| 1214 | |
| 1215 | default: |
| 1216 | return fmt.Errorf("unsupported dependency format: %s (must be owner/repo, /path/to/addon, or https://...)", addonName) |
| 1217 | } |
no test coverage detected