diff --git a/pkg/devspace/configure/image.go b/pkg/devspace/configure/image.go index 961ffc5e2c..145e86fe8e 100644 --- a/pkg/devspace/configure/image.go +++ b/pkg/devspace/configure/image.go @@ -10,11 +10,11 @@ import ( "path" "regexp" "strings" - + "github.com/loft-sh/devspace/pkg/util/encoding" "github.com/loft-sh/utils/pkg/command" "github.com/sirupsen/logrus" - + "github.com/loft-sh/devspace/pkg/devspace/config/versions/latest" "github.com/loft-sh/devspace/pkg/devspace/docker" "github.com/loft-sh/devspace/pkg/devspace/generator" @@ -41,19 +41,19 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string skip = "Skip / I don't know" err error ) - + imageConfig := &latest.Image{ Image: strings.ToLower(image), Dockerfile: dockerfile, } - + buildMethods := []string{subPathDockerfile} - + stat, err := os.Stat(imageConfig.Dockerfile) if err == nil && !stat.IsDir() { buildMethods = []string{rootLevelDockerfile, differentDockerfile} } - + buildMethod, err := m.log.Question(&survey.QuestionOptions{ Question: "How should DevSpace build the container image for this project?", DefaultValue: buildMethods[0], @@ -62,7 +62,7 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string if err != nil { return err } - + if buildMethod == customBuild { buildCommand, err := m.log.Question(&survey.QuestionOptions{ Question: "Please enter your build command without the image (e.g. `gradle jib --image` => DevSpace will append the image name automatically)", @@ -70,7 +70,7 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string if err != nil { return err } - + imageConfig.Custom = &latest.CustomConfig{ Command: buildCommand + " --tag=$(get_image --only=tag " + imageName + ")", } @@ -82,7 +82,7 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string if value == "" { return nil } - + stat, err := os.Stat(value) if err == nil && !stat.IsDir() { return nil @@ -93,7 +93,7 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string if err != nil { return err } - + if imageConfig.Dockerfile != "" { imageConfig.Context, err = m.log.Question(&survey.QuestionOptions{ Question: "What is the build context for building this image?", @@ -114,19 +114,19 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string } } } - + if image == "" && buildMethod != skip { kubeClient, err := kubectl.NewDefaultClient() if err != nil { return err } - + // Get docker client dockerClient, err := m.factory.NewDockerClientWithMinikube(context.TODO(), kubeClient, true, m.log) if err != nil { return errors.Errorf("Cannot create docker client: %v", err) } - + // Check if user is logged into docker hub isLoggedIntoDockerHub := false authConfig, err := dockerClient.GetAuthConfig(context.TODO(), dockerHubHostname, true) @@ -134,7 +134,7 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string useDockerHub = useDockerHub + fmt.Sprintf(registryUsernameHint, authConfig.Username) isLoggedIntoDockerHub = true } - + // Check if user is logged into GitHub isLoggedIntoGitHub := false authConfig, err = dockerClient.GetAuthConfig(context.TODO(), generator.GithubContainerRegistry, true) @@ -142,7 +142,7 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string useGithubRegistry = useGithubRegistry + fmt.Sprintf(registryUsernameHint, authConfig.Username) isLoggedIntoGitHub = true } - + // Set registry select options according to logged in status of dockerhub and github registryOptions := []string{skipRegistry, useDockerHub, useGithubRegistry, useOtherRegistry} if isLoggedIntoGitHub { @@ -152,7 +152,7 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string registryDefaultOption = useDockerHub registryOptions = []string{useDockerHub, useGithubRegistry, useOtherRegistry, skipRegistry} } - + selectedRegistry, err := m.log.Question(&survey.QuestionOptions{ Question: "If you were to push any images, which container registry would you want to push to?", DefaultValue: registryDefaultOption, @@ -161,7 +161,7 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string if err != nil { return err } - + if selectedRegistry == skipRegistry { imageConfig.Image = "my-image-registry.tld/username" + "/" + imageName } else { @@ -174,30 +174,30 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string registryHostname, err = m.log.Question(&survey.QuestionOptions{ Question: "Please provide the registry hostname without the image path (e.g. gcr.io, ghcr.io, ecr.io)", DefaultValue: "gcr.io", - ValidationRegexPattern: "^(?!-)[a-z0-9-]{1,63}(\\.[a-z0-9-]{1,63})*$", + ValidationRegexPattern: `^[a-z0-9][a-z0-9-]{0,62}(\.[a-z0-9-]{1,63})*$`, ValidationMessage: "Error parsing registry hostname: must only include letters, digits, dots and hyphens and cannot exceed 253 characters.", }) if err != nil { return err } } - + registryUsername, err := m.addPullSecretConfig(dockerClient, strings.Trim(registryHostname+"/username/app", "/")) if err != nil { return err } - + if registryUsername == "" { registryUsername = "username" } - + if selectedRegistry == useDockerHub { imageConfig.Image = registryUsername + "/" + imageName } else { if projectNamespace == "" { projectNamespace = registryUsername } - + if regexp.MustCompile(`^(.+\.)?gcr.io$`).Match([]byte(registryHostname)) { projectNamespace = "project" project, err := command.Output(context.TODO(), "", expand.ListEnviron(os.Environ()...), "gcloud", "config", "get-value", "project") @@ -205,17 +205,17 @@ func (m *manager) AddImage(imageName, image, projectNamespace, dockerfile string projectNamespace = strings.TrimSpace(string(project)) } } - + imageConfig.Image = registryHostname + "/" + projectNamespace + "/" + imageName } } } - + if buildMethod == skip { imageConfig.Image = "username/app" imageConfig.Dockerfile = "./Dockerfile" } - + m.config.Images[imageName] = imageConfig return nil } @@ -226,50 +226,50 @@ func (m *manager) addPullSecretConfig(dockerClient docker.Client, image string) if err != nil { return "", err } - + registryHostname, err := pullsecrets.GetRegistryFromImageName(image) if err != nil { return "", err } - + registryHostnamePrintable := registryHostname if registryHostnamePrintable == "" { registryHostnamePrintable = dockerHubHostname } - + usernameQuestion := fmt.Sprintf("What is your username for %s? (optional, Enter to skip)", registryHostnamePrintable) passwordQuestion := fmt.Sprintf("What is your password for %s? (optional, Enter to skip)", registryHostnamePrintable) if strings.Contains(registryHostname, "ghcr.io") || strings.Contains(registryHostname, "github.com") { usernameQuestion = "What is your GitHub username? (optional, Enter to skip)" passwordQuestion = "Please enter a GitHub personal access token (optional, Enter to skip)" } - + registryUsername := "" registryPassword := "" retry := false - + m.log.WriteString(logrus.WarnLevel, "\n") - + for { m.log.Info("Checking registry authentication for " + registryHostnamePrintable + "...") authConfig, err := dockerClient.Login(context.TODO(), registryHostname, registryUsername, registryPassword, true, retry, retry) if err == nil && (authConfig.Username != "" || authConfig.Password != "") { registryUsername = authConfig.Username - + m.log.Donef("Great! You are authenticated with %s", registryHostnamePrintable) break } - + m.log.WriteString(logrus.WarnLevel, "\n") m.log.Warnf("Unable to find registry credentials for %s", registryHostnamePrintable) m.log.Warnf("Running `%s` for you to authenticate with the registry (optional)", strings.TrimSpace("docker login "+registryHostname)) - + registryUsername, err = m.log.Question(&survey.QuestionOptions{ Question: usernameQuestion, ValidationRegexPattern: "^[^A-Z\\s]+\\.[^A-Z\\s]+$", ValidationMessage: "Error parsing registry username: must only include lowercase letters.", }) - + if err != nil { return "", err } @@ -283,24 +283,24 @@ func (m *manager) addPullSecretConfig(dockerClient docker.Client, image string) return "", err } } - + m.log.WriteString(logrus.WarnLevel, "\n") - + // Check if docker is running _, runErr := command.Output(context.TODO(), "", expand.ListEnviron(os.Environ()...), "docker", "version") - + // If Docker is available, ask if we should retry registry login if runErr == nil && registryUsername != "" { retry = true } - + if !retry { m.log.Warn("Skip validating image registry credentials.") m.log.Warn("You may ignore this warning. Pushing images to a registry is *not* required.") - + usernameVar := "REGISTRY_USERNAME" passwordVar := "REGISTRY_PASSWORD" - + m.config.PullSecrets = map[string]*latest.PullSecretConfig{ encoding.Convert(registryHostname): { Registry: registryHostname, @@ -308,7 +308,7 @@ func (m *manager) addPullSecretConfig(dockerClient docker.Client, image string) Password: fmt.Sprintf("${%s}", passwordVar), }, } - + if m.config.Vars == nil { m.config.Vars = map[string]*latest.Variable{} } @@ -316,13 +316,13 @@ func (m *manager) addPullSecretConfig(dockerClient docker.Client, image string) Name: passwordVar, Password: true, } - + m.localCache.SetVar(usernameVar, registryUsername) m.localCache.SetVar(passwordVar, registryPassword) - + break } } - + return registryUsername, nil }