github.com/ilhicas/nomad@v0.11.8/drivers/docker/utils.go (about)

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