docker.go 5.3 KB

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