package config import ( "context" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net/url" "os" "os/exec" "path/filepath" "strings" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/types" "github.com/fatih/color" api "github.com/porter-dev/porter/api/client" "github.com/porter-dev/porter/cli/cmd/github" ) func SetDockerConfig(client *api.Client) error { pID := GetCLIConfig().Project // get all registries that should be added regToAdd := make([]string, 0) // get the list of namespaces resp, err := client.ListRegistries( context.Background(), pID, ) if err != nil { return err } registries := *resp for _, registry := range registries { if registry.URL != "" { rURL := registry.URL if !strings.Contains(rURL, "http") { rURL = "http://" + rURL } // strip the protocol regURL, err := url.Parse(rURL) if err != nil { continue } regToAdd = append(regToAdd, regURL.Host) } } // create a docker dir if it does not exist dockerDir := filepath.Join(home, ".docker") if _, err := os.Stat(dockerDir); os.IsNotExist(err) { err = os.Mkdir(dockerDir, 0700) if err != nil { return err } } dockerConfigFile := filepath.Join(home, ".docker", "config.json") // determine if configfile exists if _, err := os.Stat(dockerConfigFile); os.IsNotExist(err) { // if it does not exist, create it err := ioutil.WriteFile(dockerConfigFile, []byte("{}"), 0700) if err != nil { return err } } // read the file bytes configBytes, err := ioutil.ReadFile(dockerConfigFile) if err != nil { return err } // check if the docker credential helper exists if !commandExists("docker-credential-porter") { err := downloadCredMatchingRelease() if err != nil { color.New(color.FgRed).Println("Failed to download credential helper binary:", err.Error()) os.Exit(1) } } // otherwise, check the version flag of the binary cmdVersionCred := exec.Command("docker-credential-porter", "--version") writer := &VersionWriter{} cmdVersionCred.Stdout = writer err = cmdVersionCred.Run() if err != nil || writer.Version != Version { err := downloadCredMatchingRelease() if err != nil { color.New(color.FgRed).Println("Failed to download credential helper binary:", err.Error()) os.Exit(1) } } configFile := &configfile.ConfigFile{ Filename: dockerConfigFile, } err = json.Unmarshal(configBytes, GetCLIConfig()) if err != nil { return err } if configFile.CredentialHelpers == nil { configFile.CredentialHelpers = make(map[string]string) } if configFile.AuthConfigs == nil { configFile.AuthConfigs = make(map[string]types.AuthConfig) } for _, regURL := range regToAdd { // if this is a dockerhub registry, see if an auth config has already been generated // for index.docker.io if strings.Contains(regURL, "index.docker.io") { isAuthenticated := false for key := range configFile.AuthConfigs { if key == "https://index.docker.io/v1/" { isAuthenticated = true } } if !isAuthenticated { // get a dockerhub token from the Porter API tokenResp, err := client.GetDockerhubAuthorizationToken(context.Background(), GetCLIConfig().Project) if err != nil { return err } decodedToken, err := base64.StdEncoding.DecodeString(tokenResp.Token) if err != nil { return fmt.Errorf("Invalid token: %v", err) } parts := strings.SplitN(string(decodedToken), ":", 2) if len(parts) < 2 { return fmt.Errorf("Invalid token: expected two parts, got %d", len(parts)) } configFile.AuthConfigs["https://index.docker.io/v1/"] = types.AuthConfig{ Auth: tokenResp.Token, Username: parts[0], Password: parts[1], } // since we're using token-based auth, unset the credstore configFile.CredentialsStore = "" } } else { configFile.CredentialHelpers[regURL] = "porter" } } return configFile.Save() } func commandExists(cmd string) bool { _, err := exec.LookPath(cmd) return err == nil } func downloadCredMatchingRelease() error { // download the porter cred helper z := &github.ZIPReleaseGetter{ AssetName: "docker-credential-porter", AssetFolderDest: "/usr/local/bin", ZipFolderDest: filepath.Join(home, ".porter"), ZipName: "docker-credential-porter_latest.zip", EntityID: "porter-dev", RepoName: "porter", IsPlatformDependent: true, Downloader: &github.ZIPDownloader{ ZipFolderDest: filepath.Join(home, ".porter"), AssetFolderDest: "/usr/local/bin", ZipName: "docker-credential-porter_latest.zip", }, } return z.GetRelease(Version) }