open-nomad/drivers/docker/utils.go
2018-11-19 22:59:16 -05:00

191 lines
5.2 KiB
Go

package docker
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/distribution/reference"
"github.com/docker/docker/registry"
docker "github.com/fsouza/go-dockerclient"
)
func parseDockerImage(image string) (repo, tag string) {
repo, tag = docker.ParseRepositoryTag(image)
if tag != "" {
return repo, tag
}
if i := strings.IndexRune(image, '@'); i > -1 { // Has digest (@sha256:...)
// when pulling images with a digest, the repository contains the sha hash, and the tag is empty
// see: https://github.com/fsouza/go-dockerclient/blob/master/image_test.go#L471
repo = image
} else {
tag = "latest"
}
return repo, tag
}
func dockerImageRef(repo string, tag string) string {
if tag == "" {
return repo
}
return fmt.Sprintf("%s:%s", repo, tag)
}
// loadDockerConfig loads the docker config at the specified path, returning an
// error if it couldn't be read.
func loadDockerConfig(file string) (*configfile.ConfigFile, error) {
f, err := os.Open(file)
if err != nil {
return nil, fmt.Errorf("Failed to open auth config file: %v, error: %v", file, err)
}
defer f.Close()
cfile := new(configfile.ConfigFile)
if err = cfile.LoadFromReader(f); err != nil {
return nil, fmt.Errorf("Failed to parse auth config file: %v", err)
}
return cfile, nil
}
// parseRepositoryInfo takes a repo and returns the Docker RepositoryInfo. This
// is useful for interacting with a Docker config object.
func parseRepositoryInfo(repo string) (*registry.RepositoryInfo, error) {
name, err := reference.ParseNormalizedNamed(repo)
if err != nil {
return nil, fmt.Errorf("Failed to parse named repo %q: %v", repo, err)
}
repoInfo, err := registry.ParseRepositoryInfo(name)
if err != nil {
return nil, fmt.Errorf("Failed to parse repository: %v", err)
}
return repoInfo, nil
}
// firstValidAuth tries a list of auth backends, returning first error or AuthConfiguration
func firstValidAuth(repo string, backends []authBackend) (*docker.AuthConfiguration, error) {
for _, backend := range backends {
auth, err := backend(repo)
if auth != nil || err != nil {
return auth, err
}
}
return nil, nil
}
// authFromTaskConfig generates an authBackend for any auth given in the task-configuration
func authFromTaskConfig(driverConfig *TaskConfig) authBackend {
return func(string) (*docker.AuthConfiguration, error) {
if len(driverConfig.Auth.Email) == 0 {
return nil, nil
}
return &docker.AuthConfiguration{
Username: driverConfig.Auth.Username,
Password: driverConfig.Auth.Password,
Email: driverConfig.Auth.Email,
ServerAddress: driverConfig.Auth.ServerAddr,
}, nil
}
}
// authFromDockerConfig generate an authBackend for a dockercfg-compatible file.
// The authBacken can either be from explicit auth definitions or via credential
// helpers
func authFromDockerConfig(file string) authBackend {
return func(repo string) (*docker.AuthConfiguration, error) {
if file == "" {
return nil, nil
}
repoInfo, err := parseRepositoryInfo(repo)
if err != nil {
return nil, err
}
cfile, err := loadDockerConfig(file)
if err != nil {
return nil, err
}
return firstValidAuth(repo, []authBackend{
func(string) (*docker.AuthConfiguration, error) {
dockerAuthConfig := registry.ResolveAuthConfig(cfile.AuthConfigs, repoInfo.Index)
auth := &docker.AuthConfiguration{
Username: dockerAuthConfig.Username,
Password: dockerAuthConfig.Password,
Email: dockerAuthConfig.Email,
ServerAddress: dockerAuthConfig.ServerAddress,
}
if authIsEmpty(auth) {
return nil, nil
}
return auth, nil
},
authFromHelper(cfile.CredentialHelpers[registry.GetAuthConfigKey(repoInfo.Index)]),
authFromHelper(cfile.CredentialsStore),
})
}
}
// authFromHelper generates an authBackend for a docker-credentials-helper;
// A script taking the requested domain on input, outputting JSON with
// "Username" and "Secret"
func authFromHelper(helperName string) authBackend {
return func(repo string) (*docker.AuthConfiguration, error) {
if helperName == "" {
return nil, nil
}
helper := dockerAuthHelperPrefix + helperName
cmd := exec.Command(helper, "get")
repoInfo, err := parseRepositoryInfo(repo)
if err != nil {
return nil, err
}
// Ensure that the HTTPs prefix exists
repoAddr := fmt.Sprintf("https://%s", repoInfo.Index.Name)
cmd.Stdin = strings.NewReader(repoAddr)
output, err := cmd.Output()
if err != nil {
switch err.(type) {
default:
return nil, err
case *exec.ExitError:
return nil, fmt.Errorf("%s with input %q failed with stderr: %s", helper, repo, output)
}
}
var response map[string]string
if err := json.Unmarshal(output, &response); err != nil {
return nil, err
}
auth := &docker.AuthConfiguration{
Username: response["Username"],
Password: response["Secret"],
}
if authIsEmpty(auth) {
return nil, nil
}
return auth, nil
}
}
// authIsEmpty returns if auth is nil or an empty structure
func authIsEmpty(auth *docker.AuthConfiguration) bool {
if auth == nil {
return false
}
return auth.Username == "" &&
auth.Password == "" &&
auth.Email == "" &&
auth.ServerAddress == ""
}