diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go index cfb3b7c875..dad08826e2 100644 --- a/src/cmd/initialize.go +++ b/src/cmd/initialize.go @@ -6,6 +6,8 @@ import ( "path" "path/filepath" + "github.com/AlecAivazis/survey/v2" + "github.com/Masterminds/semver/v3" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/internal/message" "github.com/defenseunicorns/zarf/src/internal/packager" @@ -39,16 +41,58 @@ var initCmd = &cobra.Command{ initPackageName := fmt.Sprintf("zarf-init-%s.tar.zst", config.GetArch()) config.DeployOptions.PackagePath = initPackageName - // Use an init-package in the executable directory if none exist in current working directory + // Try to use an init-package in the executable directory if none exist in current working directory if utils.InvalidPath(config.DeployOptions.PackagePath) { executablePath, err := utils.GetFinalExecutablePath() if err != nil { - message.Fatal(err, "Unable to get the directory where the zarf cli executable lives") + message.Error(err, "Unable to get the directory where the zarf cli is located.") } executableDir := path.Dir(executablePath) config.DeployOptions.PackagePath = filepath.Join(executableDir, initPackageName) + + // If the init-package doesn't exist in the executable directory, suggest trying to download + if utils.InvalidPath(config.DeployOptions.PackagePath) { + + if config.DeployOptions.Confirm { + message.Fatalf(nil, "This command requires a zarf-init package, but one was not found on the local system.") + } + + // If no CLI version exists (should only occur in dev or CI), try to get the latest release tag from Githhub + if _, err := semver.StrictNewVersion(config.CLIVersion); err != nil { + config.CLIVersion, err = utils.GetLatestReleaseTag(config.GithubProject) + if err != nil { + message.Fatal(err, "No CLI version found and unable to get the latest release tag for the zarf cli.") + } + } + + var confirmDownload bool + url := fmt.Sprintf("https://github.com/%s/releases/download/%s/%s", config.GithubProject, config.CLIVersion, initPackageName) + + // Give the user the choice to download the init-package and note that this does require an internet connection + message.Question(fmt.Sprintf("It seems the init package could not be found locally, but can be downloaded from %s", url)) + + message.Note("Note: This will require an internet connection.") + + // Prompt the user if --confirm not specified + if !confirmDownload { + prompt := &survey.Confirm{ + Message: "Do you want to download this init package?", + } + _ = survey.AskOne(prompt, &confirmDownload) + } + + // If the user wants to download the init-package, download it + if confirmDownload { + utils.DownloadToFile(url, config.DeployOptions.PackagePath, "") + } else { + // Otherwise, exit and tell the user to manually download the init-package + message.Warn("You must download the init package manually and place it in the current working directory") + os.Exit(0) + } + } } + // Run everything packager.Deploy() }, diff --git a/src/config/config.go b/src/config/config.go index c295b8042e..9010029ff7 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -18,6 +18,7 @@ import ( ) const ( + GithubProject = "defenseunicorns/zarf" IPV4Localhost = "127.0.0.1" PackagePrefix = "zarf-package" diff --git a/src/internal/utils/github.go b/src/internal/utils/github.go new file mode 100644 index 0000000000..04877f52e9 --- /dev/null +++ b/src/internal/utils/github.go @@ -0,0 +1,34 @@ +package utils + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/defenseunicorns/zarf/src/internal/message" +) + +// GetLatestReleaseTag returns the latest release tag for the given github project +func GetLatestReleaseTag(project string) (string, error) { + message.Debugf("utils.GetLatestReleaseTag(%s)", project) + + // We only need the tag from the JSON response + // See https://github.com/google/go-github/blob/v45.1.0/github/repos_releases.go#L21 for complete reference + ghRelease := struct { + TagName string `json:"tag_name"` + }{} + + // Get the latest release from the GitHub API for the given project + resp, err := http.Get(fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", project)) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // Decode the JSON response into the ghRelease struct + if err := json.NewDecoder(resp.Body).Decode(&ghRelease); err != nil { + return "", err + } + + return ghRelease.TagName, nil +}