2
0

docker.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package config
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "fmt"
  6. "io/ioutil"
  7. "net/url"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "strings"
  12. "github.com/docker/cli/cli/config/configfile"
  13. "github.com/docker/cli/cli/config/types"
  14. "github.com/fatih/color"
  15. api "github.com/porter-dev/porter/api/client"
  16. "github.com/porter-dev/porter/cli/cmd/github"
  17. )
  18. // SetDockerConfig sets up the docker config.json
  19. func SetDockerConfig(ctx context.Context, client api.Client, pID uint) error {
  20. // get all registries that should be added
  21. regToAdd := make([]string, 0)
  22. // get the list of namespaces
  23. resp, err := client.ListRegistries(
  24. ctx,
  25. pID,
  26. )
  27. if err != nil {
  28. return err
  29. }
  30. registries := *resp
  31. for _, registry := range registries {
  32. if registry.URL != "" {
  33. rURL := registry.URL
  34. if !strings.Contains(rURL, "http") {
  35. rURL = "http://" + rURL
  36. }
  37. // strip the protocol
  38. regURL, err := url.Parse(rURL)
  39. if err != nil {
  40. continue
  41. }
  42. regToAdd = append(regToAdd, regURL.Host)
  43. }
  44. }
  45. // create a docker dir if it does not exist
  46. dockerDir := filepath.Join(home, ".docker")
  47. if _, err := os.Stat(dockerDir); os.IsNotExist(err) {
  48. err = os.Mkdir(dockerDir, 0o700)
  49. if err != nil {
  50. return err
  51. }
  52. }
  53. dockerConfigFile := filepath.Join(home, ".docker", "config.json")
  54. // determine if configfile exists
  55. if _, err := os.Stat(dockerConfigFile); os.IsNotExist(err) {
  56. // if it does not exist, create it
  57. err := ioutil.WriteFile(dockerConfigFile, []byte("{}"), 0o700)
  58. if err != nil {
  59. return err
  60. }
  61. }
  62. // read the file bytes
  63. // // TODO: STEFAN - figure out why we are parsing the ~/.docker/config.json into the CLI config. Are we using the variables somewhere?
  64. // configBytes, err := ioutil.ReadFile(dockerConfigFile)
  65. // if err != nil {
  66. // return err
  67. // }
  68. // check if the docker credential helper exists
  69. if !commandExists("docker-credential-porter") {
  70. err := downloadCredMatchingRelease(ctx)
  71. if err != nil {
  72. color.New(color.FgRed).Println("Failed to download credential helper binary:", err.Error())
  73. os.Exit(1)
  74. }
  75. }
  76. // otherwise, check the version flag of the binary
  77. cmdVersionCred := exec.Command("docker-credential-porter", "--version")
  78. writer := &VersionWriter{}
  79. cmdVersionCred.Stdout = writer
  80. err = cmdVersionCred.Run()
  81. if err != nil || writer.Version != Version {
  82. err := downloadCredMatchingRelease(ctx)
  83. if err != nil {
  84. color.New(color.FgRed).Println("Failed to download credential helper binary:", err.Error())
  85. os.Exit(1)
  86. }
  87. }
  88. configFile := &configfile.ConfigFile{
  89. Filename: dockerConfigFile,
  90. }
  91. // // TODO: STEFAN - figure out why we are parsing the ~/.docker/config.json into the CLI config. Are we using the variables somewhere?
  92. // err = json.Unmarshal(configBytes, GetCLIConfig())
  93. // if err != nil {
  94. // return err
  95. // }
  96. if configFile.CredentialHelpers == nil {
  97. configFile.CredentialHelpers = make(map[string]string)
  98. }
  99. if configFile.AuthConfigs == nil {
  100. configFile.AuthConfigs = make(map[string]types.AuthConfig)
  101. }
  102. for _, regURL := range regToAdd {
  103. // if this is a dockerhub registry, see if an auth config has already been generated
  104. // for index.docker.io
  105. if strings.Contains(regURL, "index.docker.io") {
  106. isAuthenticated := false
  107. for key := range configFile.AuthConfigs {
  108. if key == "https://index.docker.io/v1/" {
  109. isAuthenticated = true
  110. }
  111. }
  112. if !isAuthenticated {
  113. // get a dockerhub token from the Porter API
  114. tokenResp, err := client.GetDockerhubAuthorizationToken(ctx, pID)
  115. if err != nil {
  116. return err
  117. }
  118. decodedToken, err := base64.StdEncoding.DecodeString(tokenResp.Token)
  119. if err != nil {
  120. return fmt.Errorf("Invalid token: %v", err)
  121. }
  122. parts := strings.SplitN(string(decodedToken), ":", 2)
  123. if len(parts) < 2 {
  124. return fmt.Errorf("Invalid token: expected two parts, got %d", len(parts))
  125. }
  126. configFile.AuthConfigs["https://index.docker.io/v1/"] = types.AuthConfig{
  127. Auth: tokenResp.Token,
  128. Username: parts[0],
  129. Password: parts[1],
  130. }
  131. // since we're using token-based auth, unset the credstore
  132. configFile.CredentialsStore = ""
  133. }
  134. } else {
  135. configFile.CredentialHelpers[regURL] = "porter"
  136. }
  137. }
  138. return configFile.Save()
  139. }
  140. func commandExists(cmd string) bool {
  141. _, err := exec.LookPath(cmd)
  142. return err == nil
  143. }
  144. func downloadCredMatchingRelease(ctx context.Context) error {
  145. // download the porter cred helper
  146. z := &github.ZIPReleaseGetter{
  147. AssetName: "docker-credential-porter",
  148. AssetFolderDest: "/usr/local/bin",
  149. ZipFolderDest: filepath.Join(home, ".porter"),
  150. ZipName: "docker-credential-porter_latest.zip",
  151. EntityID: "porter-dev",
  152. RepoName: "porter-archive",
  153. IsPlatformDependent: true,
  154. Downloader: &github.ZIPDownloader{
  155. ZipFolderDest: filepath.Join(home, ".porter"),
  156. AssetFolderDest: "/usr/local/bin",
  157. ZipName: "docker-credential-porter_latest.zip",
  158. },
  159. }
  160. return z.GetRelease(ctx, Version)
  161. }