docker.go 4.9 KB

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