github.com/bigcommerce/nomad@v0.9.3-bc/drivers/docker/utils.go (about) 1 package docker 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 12 "github.com/docker/cli/cli/config/configfile" 13 "github.com/docker/distribution/reference" 14 "github.com/docker/docker/registry" 15 "github.com/docker/docker/volume/mounts" 16 docker "github.com/fsouza/go-dockerclient" 17 ) 18 19 func parseDockerImage(image string) (repo, tag string) { 20 repo, tag = docker.ParseRepositoryTag(image) 21 if tag != "" { 22 return repo, tag 23 } 24 if i := strings.IndexRune(image, '@'); i > -1 { // Has digest (@sha256:...) 25 // when pulling images with a digest, the repository contains the sha hash, and the tag is empty 26 // see: https://github.com/fsouza/go-dockerclient/blob/master/image_test.go#L471 27 repo = image 28 } else { 29 tag = "latest" 30 } 31 return repo, tag 32 } 33 34 func dockerImageRef(repo string, tag string) string { 35 if tag == "" { 36 return repo 37 } 38 return fmt.Sprintf("%s:%s", repo, tag) 39 } 40 41 // loadDockerConfig loads the docker config at the specified path, returning an 42 // error if it couldn't be read. 43 func loadDockerConfig(file string) (*configfile.ConfigFile, error) { 44 f, err := os.Open(file) 45 if err != nil { 46 return nil, fmt.Errorf("Failed to open auth config file: %v, error: %v", file, err) 47 } 48 defer f.Close() 49 50 cfile := new(configfile.ConfigFile) 51 if err = cfile.LoadFromReader(f); err != nil { 52 return nil, fmt.Errorf("Failed to parse auth config file: %v", err) 53 } 54 return cfile, nil 55 } 56 57 // parseRepositoryInfo takes a repo and returns the Docker RepositoryInfo. This 58 // is useful for interacting with a Docker config object. 59 func parseRepositoryInfo(repo string) (*registry.RepositoryInfo, error) { 60 name, err := reference.ParseNormalizedNamed(repo) 61 if err != nil { 62 return nil, fmt.Errorf("Failed to parse named repo %q: %v", repo, err) 63 } 64 65 repoInfo, err := registry.ParseRepositoryInfo(name) 66 if err != nil { 67 return nil, fmt.Errorf("Failed to parse repository: %v", err) 68 } 69 70 return repoInfo, nil 71 } 72 73 // firstValidAuth tries a list of auth backends, returning first error or AuthConfiguration 74 func firstValidAuth(repo string, backends []authBackend) (*docker.AuthConfiguration, error) { 75 for _, backend := range backends { 76 auth, err := backend(repo) 77 if auth != nil || err != nil { 78 return auth, err 79 } 80 } 81 return nil, nil 82 } 83 84 // authFromTaskConfig generates an authBackend for any auth given in the task-configuration 85 func authFromTaskConfig(driverConfig *TaskConfig) authBackend { 86 return func(string) (*docker.AuthConfiguration, error) { 87 // If all auth fields are empty, return 88 if len(driverConfig.Auth.Username) == 0 && len(driverConfig.Auth.Password) == 0 && len(driverConfig.Auth.Email) == 0 && len(driverConfig.Auth.ServerAddr) == 0 { 89 return nil, nil 90 } 91 return &docker.AuthConfiguration{ 92 Username: driverConfig.Auth.Username, 93 Password: driverConfig.Auth.Password, 94 Email: driverConfig.Auth.Email, 95 ServerAddress: driverConfig.Auth.ServerAddr, 96 }, nil 97 } 98 } 99 100 // authFromDockerConfig generate an authBackend for a dockercfg-compatible file. 101 // The authBacken can either be from explicit auth definitions or via credential 102 // helpers 103 func authFromDockerConfig(file string) authBackend { 104 return func(repo string) (*docker.AuthConfiguration, error) { 105 if file == "" { 106 return nil, nil 107 } 108 repoInfo, err := parseRepositoryInfo(repo) 109 if err != nil { 110 return nil, err 111 } 112 113 cfile, err := loadDockerConfig(file) 114 if err != nil { 115 return nil, err 116 } 117 118 return firstValidAuth(repo, []authBackend{ 119 func(string) (*docker.AuthConfiguration, error) { 120 dockerAuthConfig := registry.ResolveAuthConfig(cfile.AuthConfigs, repoInfo.Index) 121 auth := &docker.AuthConfiguration{ 122 Username: dockerAuthConfig.Username, 123 Password: dockerAuthConfig.Password, 124 Email: dockerAuthConfig.Email, 125 ServerAddress: dockerAuthConfig.ServerAddress, 126 } 127 if authIsEmpty(auth) { 128 return nil, nil 129 } 130 return auth, nil 131 }, 132 authFromHelper(cfile.CredentialHelpers[registry.GetAuthConfigKey(repoInfo.Index)]), 133 authFromHelper(cfile.CredentialsStore), 134 }) 135 } 136 } 137 138 // authFromHelper generates an authBackend for a docker-credentials-helper; 139 // A script taking the requested domain on input, outputting JSON with 140 // "Username" and "Secret" 141 func authFromHelper(helperName string) authBackend { 142 return func(repo string) (*docker.AuthConfiguration, error) { 143 if helperName == "" { 144 return nil, nil 145 } 146 helper := dockerAuthHelperPrefix + helperName 147 cmd := exec.Command(helper, "get") 148 149 repoInfo, err := parseRepositoryInfo(repo) 150 if err != nil { 151 return nil, err 152 } 153 154 // Ensure that the HTTPs prefix exists 155 repoAddr := fmt.Sprintf("https://%s", repoInfo.Index.Name) 156 157 cmd.Stdin = strings.NewReader(repoAddr) 158 output, err := cmd.Output() 159 if err != nil { 160 switch err.(type) { 161 default: 162 return nil, err 163 case *exec.ExitError: 164 return nil, fmt.Errorf("%s with input %q failed with stderr: %s", helper, repo, output) 165 } 166 } 167 168 var response map[string]string 169 if err := json.Unmarshal(output, &response); err != nil { 170 return nil, err 171 } 172 173 auth := &docker.AuthConfiguration{ 174 Username: response["Username"], 175 Password: response["Secret"], 176 } 177 178 if authIsEmpty(auth) { 179 return nil, nil 180 } 181 return auth, nil 182 } 183 } 184 185 // authIsEmpty returns if auth is nil or an empty structure 186 func authIsEmpty(auth *docker.AuthConfiguration) bool { 187 if auth == nil { 188 return false 189 } 190 return auth.Username == "" && 191 auth.Password == "" && 192 auth.Email == "" && 193 auth.ServerAddress == "" 194 } 195 196 func validateCgroupPermission(s string) bool { 197 for _, c := range s { 198 switch c { 199 case 'r', 'w', 'm': 200 default: 201 return false 202 } 203 } 204 205 return true 206 } 207 208 // expandPath returns the absolute path of dir, relative to base if dir is relative path. 209 // base is expected to be an absolute path 210 func expandPath(base, dir string) string { 211 if filepath.IsAbs(dir) { 212 return filepath.Clean(dir) 213 } 214 215 return filepath.Clean(filepath.Join(base, dir)) 216 } 217 218 // isParentPath returns true if path is a child or a descendant of parent path. 219 // Both inputs need to be absolute paths. 220 func isParentPath(parent, path string) bool { 221 rel, err := filepath.Rel(parent, path) 222 return err == nil && !strings.HasPrefix(rel, "..") 223 } 224 225 func parseVolumeSpec(volBind, os string) (hostPath string, containerPath string, mode string, err error) { 226 if os == "windows" { 227 return parseVolumeSpecWindows(volBind) 228 } 229 return parseVolumeSpecLinux(volBind) 230 } 231 232 func parseVolumeSpecWindows(volBind string) (hostPath string, containerPath string, mode string, err error) { 233 parser := mounts.NewParser("windows") 234 m, err := parser.ParseMountRaw(volBind, "") 235 if err != nil { 236 return "", "", "", err 237 } 238 239 src := m.Source 240 if src == "" && strings.Contains(volBind, m.Name) { 241 src = m.Name 242 } 243 244 if src == "" { 245 return "", "", "", errors.New("missing host path") 246 } 247 248 if m.Destination == "" { 249 return "", "", "", errors.New("container path is empty") 250 } 251 252 return src, m.Destination, m.Mode, nil 253 } 254 255 func parseVolumeSpecLinux(volBind string) (hostPath string, containerPath string, mode string, err error) { 256 // using internal parser to preserve old parsing behavior. Docker 257 // parser has additional validators (e.g. mode validity) and accepts invalid output (per Nomad), 258 // e.g. single path entry to be treated as a container path entry with an auto-generated host-path. 259 // 260 // Reconsider updating to use Docker parser when ready to make incompatible changes. 261 parts := strings.Split(volBind, ":") 262 if len(parts) < 2 { 263 return "", "", "", fmt.Errorf("not <src>:<destination> format") 264 } 265 266 m := "" 267 if len(parts) > 2 { 268 m = parts[2] 269 } 270 271 return parts[0], parts[1], m, nil 272 }