github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/docker_context.go (about) 1 package client 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 10 "github.com/pkg/errors" 11 12 "github.com/opencontainers/go-digest" 13 14 "github.com/buildpacks/pack/pkg/logging" 15 ) 16 17 const ( 18 dockerHostEnvVar = "DOCKER_HOST" 19 dockerConfigEnvVar = "DOCKER_CONFIG" 20 defaultDockerRootConfigDir = ".docker" 21 defaultDockerConfigFileName = "config.json" 22 23 dockerContextDirName = "contexts" 24 dockerContextMetaDirName = "meta" 25 dockerContextMetaFileName = "meta.json" 26 dockerContextEndpoint = "docker" 27 defaultDockerContext = "default" 28 ) 29 30 type configFile struct { 31 CurrentContext string `json:"currentContext,omitempty"` 32 } 33 34 type endpoint struct { 35 Host string `json:",omitempty"` 36 } 37 38 /* 39 Example Docker context file 40 { 41 "Name": "desktop-linux", 42 "dockerConfigMetadata": { 43 "Description": "Docker Desktop" 44 }, 45 "Endpoints": { 46 "docker": { 47 "Host": "unix:///Users/jbustamante/.docker/run/docker.sock", 48 "SkipTLSVerify": false 49 } 50 } 51 } 52 */ 53 type dockerConfigMetadata struct { 54 Name string `json:",omitempty"` 55 Endpoints map[string]endpoint `json:"endpoints,omitempty"` 56 } 57 58 func ProcessDockerContext(logger logging.Logger) error { 59 dockerHost := os.Getenv(dockerHostEnvVar) 60 if dockerHost == "" { 61 dockerConfigDir, err := configDir() 62 if err != nil { 63 return err 64 } 65 66 logger.Debugf("looking for docker configuration file at: %s", dockerConfigDir) 67 configuration, err := readConfigFile(dockerConfigDir) 68 if err != nil { 69 return errors.Wrapf(err, "reading configuration file at '%s'", dockerConfigDir) 70 } 71 72 if skip(configuration) { 73 logger.Debug("docker context is default or empty, skipping it") 74 return nil 75 } 76 77 configMetaData, err := readConfigMetadata(dockerConfigDir, configuration.CurrentContext) 78 if err != nil { 79 return errors.Wrapf(err, "reading metadata for current context '%s' at '%s'", configuration.CurrentContext, dockerConfigDir) 80 } 81 82 if dockerEndpoint, ok := configMetaData.Endpoints[dockerContextEndpoint]; ok { 83 os.Setenv(dockerHostEnvVar, dockerEndpoint.Host) 84 logger.Debugf("using docker context '%s' with endpoint = '%s'", configuration.CurrentContext, dockerEndpoint.Host) 85 } else { 86 logger.Warnf("docker endpoint doesn't exist for context '%s'", configuration.CurrentContext) 87 } 88 } else { 89 logger.Debugf("'%s=%s' environment variable is being used", dockerHostEnvVar, dockerHost) 90 } 91 return nil 92 } 93 94 func configDir() (string, error) { 95 dir := os.Getenv(dockerConfigEnvVar) 96 if dir == "" { 97 home, err := os.UserHomeDir() 98 if err != nil { 99 return "", errors.Wrap(err, "determining user home directory") 100 } 101 dir = filepath.Join(home, defaultDockerRootConfigDir) 102 } 103 return dir, nil 104 } 105 106 func readConfigFile(configDir string) (*configFile, error) { 107 filename := filepath.Join(configDir, defaultDockerConfigFileName) 108 config := &configFile{} 109 file, err := os.Open(filename) 110 if err != nil { 111 if os.IsNotExist(err) { 112 return &configFile{}, nil 113 } 114 return &configFile{}, err 115 } 116 defer file.Close() 117 if err := json.NewDecoder(file).Decode(config); err != nil && !errors.Is(err, io.EOF) { 118 return &configFile{}, err 119 } 120 return config, nil 121 } 122 123 func readConfigMetadata(configDir string, context string) (dockerConfigMetadata, error) { 124 dockerContextDir := filepath.Join(configDir, dockerContextDirName) 125 metaFileName := filepath.Join(dockerContextDir, dockerContextMetaDirName, digest.FromString(context).Encoded(), dockerContextMetaFileName) 126 bytes, err := os.ReadFile(metaFileName) 127 if err != nil { 128 if errors.Is(err, os.ErrNotExist) { 129 return dockerConfigMetadata{}, fmt.Errorf("docker context '%s' not found", context) 130 } 131 return dockerConfigMetadata{}, err 132 } 133 var meta dockerConfigMetadata 134 if err := json.Unmarshal(bytes, &meta); err != nil { 135 return dockerConfigMetadata{}, fmt.Errorf("parsing %s: %v", metaFileName, err) 136 } 137 if meta.Name != context { 138 return dockerConfigMetadata{}, fmt.Errorf("context '%s' doesn't match metadata name '%s' at '%s'", context, meta.Name, metaFileName) 139 } 140 141 return meta, nil 142 } 143 144 func skip(configuration *configFile) bool { 145 return configuration == nil || configuration.CurrentContext == defaultDockerContext || configuration.CurrentContext == "" 146 }