1
0
Fork 0

chore: update docker and k8s

This commit is contained in:
Ludovic Fernandez 2019-08-05 18:24:03 +02:00 committed by Traefiker Bot
parent 2b5c7f9e91
commit c2d440a914
1283 changed files with 67741 additions and 27918 deletions

View file

@ -41,13 +41,19 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
if err != nil {
return err
}
pm, err := fileutils.NewPatternMatcher(excludes)
if err != nil {
return err
}
return filepath.Walk(contextRoot, func(filePath string, f os.FileInfo, err error) error {
if err != nil {
if os.IsPermission(err) {
return errors.Errorf("can't stat '%s'", filePath)
}
if os.IsNotExist(err) {
return nil
return errors.Errorf("file ('%s') not found or excluded by .dockerignore", filePath)
}
return err
}
@ -55,7 +61,7 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
// skip this directory/file if it's not in the path, it won't get added to the context
if relFilePath, err := filepath.Rel(contextRoot, filePath); err != nil {
return err
} else if skip, err := fileutils.Matches(relFilePath, excludes); err != nil {
} else if skip, err := filepathMatches(pm, relFilePath); err != nil {
return err
} else if skip {
if f.IsDir() {
@ -81,59 +87,91 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
})
}
func filepathMatches(matcher *fileutils.PatternMatcher, file string) (bool, error) {
file = filepath.Clean(file)
if file == "." {
// Don't let them exclude everything, kind of silly.
return false, nil
}
return matcher.Matches(file)
}
// DetectArchiveReader detects whether the input stream is an archive or a
// Dockerfile and returns a buffered version of input, safe to consume in lieu
// of input. If an archive is detected, isArchive is set to true, and to false
// otherwise, in which case it is safe to assume input represents the contents
// of a Dockerfile.
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool, err error) {
buf := bufio.NewReader(input)
magic, err := buf.Peek(archiveHeaderSize * 2)
if err != nil && err != io.EOF {
return nil, false, errors.Errorf("failed to peek context header from STDIN: %v", err)
}
return ioutils.NewReadCloserWrapper(buf, func() error { return input.Close() }), IsArchive(magic), nil
}
// WriteTempDockerfile writes a Dockerfile stream to a temporary file with a
// name specified by DefaultDockerfileName and returns the path to the
// temporary directory containing the Dockerfile.
func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
// err is a named return value, due to the defer call below.
dockerfileDir, err = ioutil.TempDir("", "docker-build-tempdockerfile-")
if err != nil {
return "", errors.Errorf("unable to create temporary context directory: %v", err)
}
defer func() {
if err != nil {
os.RemoveAll(dockerfileDir)
}
}()
f, err := os.Create(filepath.Join(dockerfileDir, DefaultDockerfileName))
if err != nil {
return "", err
}
defer f.Close()
if _, err := io.Copy(f, rc); err != nil {
return "", err
}
return dockerfileDir, rc.Close()
}
// GetContextFromReader will read the contents of the given reader as either a
// Dockerfile or tar archive. Returns a tar archive used as a context and a
// path to the Dockerfile inside the tar.
func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
buf := bufio.NewReader(r)
magic, err := buf.Peek(archiveHeaderSize)
if err != nil && err != io.EOF {
return nil, "", errors.Errorf("failed to peek context header from STDIN: %v", err)
func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
rc, isArchive, err := DetectArchiveReader(rc)
if err != nil {
return nil, "", err
}
if IsArchive(magic) {
return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
if isArchive {
return rc, dockerfileName, nil
}
// Input should be read as a Dockerfile.
if dockerfileName == "-" {
return nil, "", errors.New("build context is not an archive")
}
// Input should be read as a Dockerfile.
tmpDir, err := ioutil.TempDir("", "docker-build-context-")
if err != nil {
return nil, "", errors.Errorf("unable to create temporary context directory: %v", err)
}
f, err := os.Create(filepath.Join(tmpDir, DefaultDockerfileName))
dockerfileDir, err := WriteTempDockerfile(rc)
if err != nil {
return nil, "", err
}
_, err = io.Copy(f, buf)
if err != nil {
f.Close()
return nil, "", err
}
if err := f.Close(); err != nil {
return nil, "", err
}
if err := r.Close(); err != nil {
return nil, "", err
}
tar, err := archive.Tar(tmpDir, archive.Uncompressed)
tar, err := archive.Tar(dockerfileDir, archive.Uncompressed)
if err != nil {
return nil, "", err
}
return ioutils.NewReadCloserWrapper(tar, func() error {
err := tar.Close()
os.RemoveAll(tmpDir)
os.RemoveAll(dockerfileDir)
return err
}), DefaultDockerfileName, nil
}
// IsArchive checks for the magic bytes of a tar or any supported compression
@ -167,6 +205,10 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error)
return "", "", err
}
relDockerfile, err := getDockerfileRelPath(absContextDir, dockerfileName)
if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
return "", "", errors.Errorf("the Dockerfile (%s) must be within the build context", dockerfileName)
}
return absContextDir, relDockerfile, err
}
@ -318,10 +360,6 @@ func getDockerfileRelPath(absContextDir, givenDockerfile string) (string, error)
return "", errors.Errorf("unable to get relative Dockerfile path: %v", err)
}
if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
return "", errors.Errorf("the Dockerfile (%s) must be within the build context", givenDockerfile)
}
return relDockerfile, nil
}

View file

@ -5,10 +5,11 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/docker/api/types"
"github.com/docker/cli/cli/config/types"
"github.com/docker/docker/pkg/homedir"
"github.com/pkg/errors"
)
@ -18,6 +19,7 @@ const (
ConfigFileName = "config.json"
configFileDir = ".docker"
oldConfigfile = ".dockercfg"
contextsDir = "contexts"
)
var (
@ -35,9 +37,23 @@ func Dir() string {
return configDir
}
// ContextStoreDir returns the directory the docker contexts are stored in
func ContextStoreDir() string {
return filepath.Join(Dir(), contextsDir)
}
// SetDir sets the directory the configuration file is stored in
func SetDir(dir string) {
configDir = dir
configDir = filepath.Clean(dir)
}
// Path returns the path to a file relative to the config dir
func Path(p ...string) (string, error) {
path := filepath.Join(append([]string{Dir()}, p...)...)
if !strings.HasPrefix(path, Dir()+string(filepath.Separator)) {
return "", errors.Errorf("path %q is outside of root config directory %q", path, Dir())
}
return path, nil
}
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
@ -75,18 +91,18 @@ func Load(configDir string) (*configfile.ConfigFile, error) {
if _, err := os.Stat(filename); err == nil {
file, err := os.Open(filename)
if err != nil {
return configFile, errors.Errorf("%s - %v", filename, err)
return configFile, errors.Wrap(err, filename)
}
defer file.Close()
err = configFile.LoadFromReader(file)
if err != nil {
err = errors.Errorf("%s - %v", filename, err)
err = errors.Wrap(err, filename)
}
return configFile, err
} else if !os.IsNotExist(err) {
// if file is there but we can't stat it for any reason other
// than it doesn't exist then stop
return configFile, errors.Errorf("%s - %v", filename, err)
return configFile, errors.Wrap(err, filename)
}
// Can't find latest config file so check for the old one
@ -96,12 +112,12 @@ func Load(configDir string) (*configfile.ConfigFile, error) {
}
file, err := os.Open(confFile)
if err != nil {
return configFile, errors.Errorf("%s - %v", confFile, err)
return configFile, errors.Wrap(err, filename)
}
defer file.Close()
err = configFile.LegacyLoadFromReader(file)
if err != nil {
return configFile, errors.Errorf("%s - %v", confFile, err)
return configFile, errors.Wrap(err, filename)
}
return configFile, nil
}

View file

@ -3,6 +3,7 @@ package configfile
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
@ -10,8 +11,7 @@ import (
"strings"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/cli/cli/config/types"
"github.com/pkg/errors"
)
@ -19,31 +19,38 @@ const (
// This constant is only used for really old config files when the
// URL wasn't saved as part of the config file and it was just
// assumed to be this value.
defaultIndexserver = "https://index.docker.io/v1/"
defaultIndexServer = "https://index.docker.io/v1/"
)
// ConfigFile ~/.docker/config.json file info
type ConfigFile struct {
AuthConfigs map[string]types.AuthConfig `json:"auths"`
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
PsFormat string `json:"psFormat,omitempty"`
ImagesFormat string `json:"imagesFormat,omitempty"`
NetworksFormat string `json:"networksFormat,omitempty"`
PluginsFormat string `json:"pluginsFormat,omitempty"`
VolumesFormat string `json:"volumesFormat,omitempty"`
StatsFormat string `json:"statsFormat,omitempty"`
DetachKeys string `json:"detachKeys,omitempty"`
CredentialsStore string `json:"credsStore,omitempty"`
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
Filename string `json:"-"` // Note: for internal use only
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
ServicesFormat string `json:"servicesFormat,omitempty"`
TasksFormat string `json:"tasksFormat,omitempty"`
SecretFormat string `json:"secretFormat,omitempty"`
ConfigFormat string `json:"configFormat,omitempty"`
NodesFormat string `json:"nodesFormat,omitempty"`
PruneFilters []string `json:"pruneFilters,omitempty"`
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
AuthConfigs map[string]types.AuthConfig `json:"auths"`
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
PsFormat string `json:"psFormat,omitempty"`
ImagesFormat string `json:"imagesFormat,omitempty"`
NetworksFormat string `json:"networksFormat,omitempty"`
PluginsFormat string `json:"pluginsFormat,omitempty"`
VolumesFormat string `json:"volumesFormat,omitempty"`
StatsFormat string `json:"statsFormat,omitempty"`
DetachKeys string `json:"detachKeys,omitempty"`
CredentialsStore string `json:"credsStore,omitempty"`
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
Filename string `json:"-"` // Note: for internal use only
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
ServicesFormat string `json:"servicesFormat,omitempty"`
TasksFormat string `json:"tasksFormat,omitempty"`
SecretFormat string `json:"secretFormat,omitempty"`
ConfigFormat string `json:"configFormat,omitempty"`
NodesFormat string `json:"nodesFormat,omitempty"`
PruneFilters []string `json:"pruneFilters,omitempty"`
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
Experimental string `json:"experimental,omitempty"`
StackOrchestrator string `json:"stackOrchestrator,omitempty"`
Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"`
CurrentContext string `json:"currentContext,omitempty"`
CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"`
Plugins map[string]map[string]string `json:"plugins,omitempty"`
Aliases map[string]string `json:"aliases,omitempty"`
}
// ProxyConfig contains proxy configuration settings
@ -54,12 +61,19 @@ type ProxyConfig struct {
FTPProxy string `json:"ftpProxy,omitempty"`
}
// KubernetesConfig contains Kubernetes orchestrator settings
type KubernetesConfig struct {
AllNamespaces string `json:"allNamespaces,omitempty"`
}
// New initializes an empty configuration file for the given filename 'fn'
func New(fn string) *ConfigFile {
return &ConfigFile{
AuthConfigs: make(map[string]types.AuthConfig),
HTTPHeaders: make(map[string]string),
Filename: fn,
Plugins: make(map[string]map[string]string),
Aliases: make(map[string]string),
}
}
@ -85,8 +99,8 @@ func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
if err != nil {
return err
}
authConfig.ServerAddress = defaultIndexserver
configFile.AuthConfigs[defaultIndexserver] = authConfig
authConfig.ServerAddress = defaultIndexServer
configFile.AuthConfigs[defaultIndexServer] = authConfig
} else {
for k, authConfig := range configFile.AuthConfigs {
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
@ -117,7 +131,7 @@ func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
ac.ServerAddress = addr
configFile.AuthConfigs[addr] = ac
}
return nil
return checkKubernetesConfiguration(configFile.Kubernetes)
}
// ContainsAuth returns whether there is authentication configured
@ -166,20 +180,26 @@ func (configFile *ConfigFile) Save() error {
return errors.Errorf("Can't save config with empty filename")
}
if err := os.MkdirAll(filepath.Dir(configFile.Filename), 0700); err != nil {
dir := filepath.Dir(configFile.Filename)
if err := os.MkdirAll(dir, 0700); err != nil {
return err
}
f, err := os.OpenFile(configFile.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
temp, err := ioutil.TempFile(dir, filepath.Base(configFile.Filename))
if err != nil {
return err
}
defer f.Close()
return configFile.SaveToWriter(f)
err = configFile.SaveToWriter(temp)
temp.Close()
if err != nil {
os.Remove(temp.Name())
return err
}
return os.Rename(temp.Name(), configFile.Filename)
}
// ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and
// then checking this against any environment variables provided to the container
func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts []string) map[string]*string {
func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts map[string]*string) map[string]*string {
var cfgKey string
if _, ok := configFile.Proxies[host]; !ok {
@ -195,7 +215,10 @@ func (configFile *ConfigFile) ParseProxyConfig(host string, runOpts []string) ma
"NO_PROXY": &config.NoProxy,
"FTP_PROXY": &config.FTPProxy,
}
m := opts.ConvertKVStringsToMapWithNil(runOpts)
m := runOpts
if m == nil {
m = make(map[string]*string)
}
for k := range permitted {
if *permitted[k] == "" {
continue
@ -249,24 +272,29 @@ func decodeAuth(authStr string) (string, string, error) {
// GetCredentialsStore returns a new credentials store from the settings in the
// configuration file
func (configFile *ConfigFile) GetCredentialsStore(serverAddress string) credentials.Store {
if helper := getConfiguredCredentialStore(configFile, serverAddress); helper != "" {
return credentials.NewNativeStore(configFile, helper)
func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" {
return newNativeStore(configFile, helper)
}
return credentials.NewFileStore(configFile)
}
// var for unit testing.
var newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentials.Store {
return credentials.NewNativeStore(configFile, helperSuffix)
}
// GetAuthConfig for a repository from the credential store
func (configFile *ConfigFile) GetAuthConfig(serverAddress string) (types.AuthConfig, error) {
return configFile.GetCredentialsStore(serverAddress).Get(serverAddress)
func (configFile *ConfigFile) GetAuthConfig(registryHostname string) (types.AuthConfig, error) {
return configFile.GetCredentialsStore(registryHostname).Get(registryHostname)
}
// getConfiguredCredentialStore returns the credential helper configured for the
// given registry, the default credsStore, or the empty string if neither are
// configured.
func getConfiguredCredentialStore(c *ConfigFile, serverAddress string) string {
if c.CredentialHelpers != nil && serverAddress != "" {
if helper, exists := c.CredentialHelpers[serverAddress]; exists {
func getConfiguredCredentialStore(c *ConfigFile, registryHostname string) string {
if c.CredentialHelpers != nil && registryHostname != "" {
if helper, exists := c.CredentialHelpers[registryHostname]; exists {
return helper
}
}
@ -283,19 +311,75 @@ func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig,
}
}
for registry := range configFile.CredentialHelpers {
helper := configFile.GetCredentialsStore(registry)
newAuths, err := helper.GetAll()
if err != nil {
return nil, err
}
addAll(newAuths)
}
defaultStore := configFile.GetCredentialsStore("")
newAuths, err := defaultStore.GetAll()
if err != nil {
return nil, err
}
addAll(newAuths)
// Auth configs from a registry-specific helper should override those from the default store.
for registryHostname := range configFile.CredentialHelpers {
newAuth, err := configFile.GetAuthConfig(registryHostname)
if err != nil {
return nil, err
}
auths[registryHostname] = newAuth
}
return auths, nil
}
// GetFilename returns the file name that this config file is based on.
func (configFile *ConfigFile) GetFilename() string {
return configFile.Filename
}
// PluginConfig retrieves the requested option for the given plugin.
func (configFile *ConfigFile) PluginConfig(pluginname, option string) (string, bool) {
if configFile.Plugins == nil {
return "", false
}
pluginConfig, ok := configFile.Plugins[pluginname]
if !ok {
return "", false
}
value, ok := pluginConfig[option]
return value, ok
}
// SetPluginConfig sets the option to the given value for the given
// plugin. Passing a value of "" will remove the option. If removing
// the final config item for a given plugin then also cleans up the
// overall plugin entry.
func (configFile *ConfigFile) SetPluginConfig(pluginname, option, value string) {
if configFile.Plugins == nil {
configFile.Plugins = make(map[string]map[string]string)
}
pluginConfig, ok := configFile.Plugins[pluginname]
if !ok {
pluginConfig = make(map[string]string)
configFile.Plugins[pluginname] = pluginConfig
}
if value != "" {
pluginConfig[option] = value
} else {
delete(pluginConfig, option)
}
if len(pluginConfig) == 0 {
delete(configFile.Plugins, pluginname)
}
}
func checkKubernetesConfiguration(kubeConfig *KubernetesConfig) error {
if kubeConfig == nil {
return nil
}
switch kubeConfig.AllNamespaces {
case "":
case "enabled":
case "disabled":
default:
return fmt.Errorf("invalid 'kubernetes.allNamespaces' value, should be 'enabled' or 'disabled': %s", kubeConfig.AllNamespaces)
}
return nil
}

View file

@ -1,7 +1,7 @@
package credentials
import (
"github.com/docker/docker/api/types"
"github.com/docker/cli/cli/config/types"
)
// Store is the interface that any credentials store must implement.

View file

@ -1,11 +1,11 @@
package credentials
import (
"github.com/docker/docker-credential-helpers/pass"
"os/exec"
)
func defaultCredentialsStore() string {
if pass.PassInitialized {
if _, err := exec.LookPath("pass"); err == nil {
return "pass"
}

View file

@ -2,4 +2,6 @@
package credentials
const defaultCredentialsStore = ""
func defaultCredentialsStore() string {
return ""
}

View file

@ -1,13 +1,15 @@
package credentials
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/registry"
"strings"
"github.com/docker/cli/cli/config/types"
)
type store interface {
Save() error
GetAuthConfigs() map[string]types.AuthConfig
GetFilename() string
}
// fileStore implements a credentials store using
@ -34,7 +36,7 @@ func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
// Maybe they have a legacy config file, we will iterate the keys converting
// them to the new format and testing
for r, ac := range c.file.GetAuthConfigs() {
if serverAddress == registry.ConvertToHostname(r) {
if serverAddress == ConvertToHostname(r) {
return ac, nil
}
}
@ -53,3 +55,27 @@ func (c *fileStore) Store(authConfig types.AuthConfig) error {
c.file.GetAuthConfigs()[authConfig.ServerAddress] = authConfig
return c.file.Save()
}
func (c *fileStore) GetFilename() string {
return c.file.GetFilename()
}
func (c *fileStore) IsFileStore() bool {
return true
}
// ConvertToHostname converts a registry url which has http|https prepended
// to just an hostname.
// Copied from github.com/docker/docker/registry.ConvertToHostname to reduce dependencies.
func ConvertToHostname(url string) string {
stripped := url
if strings.HasPrefix(url, "http://") {
stripped = strings.TrimPrefix(url, "http://")
} else if strings.HasPrefix(url, "https://") {
stripped = strings.TrimPrefix(url, "https://")
}
nameParts := strings.SplitN(stripped, "/", 2)
return nameParts[0]
}

View file

@ -1,9 +1,9 @@
package credentials
import (
"github.com/docker/cli/cli/config/types"
"github.com/docker/docker-credential-helpers/client"
"github.com/docker/docker-credential-helpers/credentials"
"github.com/docker/docker/api/types"
)
const (

View file

@ -0,0 +1,22 @@
package types
// AuthConfig contains authorization information for connecting to a Registry
type AuthConfig struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Auth string `json:"auth,omitempty"`
// Email is an optional value associated with the username.
// This field is deprecated and will be removed in a later
// version of docker.
Email string `json:"email,omitempty"`
ServerAddress string `json:"serveraddress,omitempty"`
// IdentityToken is used to authenticate the user and get
// an access token for the registry.
IdentityToken string `json:"identitytoken,omitempty"`
// RegistryToken is a bearer token to be sent to a registry
RegistryToken string `json:"registrytoken,omitempty"`
}

View file

@ -1,13 +1,7 @@
package opts
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
)
// ParseEnvFile reads a file with environment variables enumerated by lines
@ -24,58 +18,5 @@ import (
// environment variables, that's why we just strip leading whitespace and
// nothing more.
func ParseEnvFile(filename string) ([]string, error) {
fh, err := os.Open(filename)
if err != nil {
return []string{}, err
}
defer fh.Close()
lines := []string{}
scanner := bufio.NewScanner(fh)
currentLine := 0
utf8bom := []byte{0xEF, 0xBB, 0xBF}
for scanner.Scan() {
scannedBytes := scanner.Bytes()
if !utf8.Valid(scannedBytes) {
return []string{}, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v", filename, currentLine+1, scannedBytes)
}
// We trim UTF8 BOM
if currentLine == 0 {
scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom)
}
// trim the line from all leading whitespace first
line := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace)
currentLine++
// line is not empty, and not starting with '#'
if len(line) > 0 && !strings.HasPrefix(line, "#") {
data := strings.SplitN(line, "=", 2)
// trim the front of a variable, but nothing else
variable := strings.TrimLeft(data[0], whiteSpaces)
if strings.ContainsAny(variable, whiteSpaces) {
return []string{}, ErrBadEnvVariable{fmt.Sprintf("variable '%s' has white spaces", variable)}
}
if len(data) > 1 {
// pass the value through, no trimming
lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
} else {
// if only a pass-through variable is given, clean it up.
lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line)))
}
}
}
return lines, scanner.Err()
}
var whiteSpaces = " \t"
// ErrBadEnvVariable typed error for bad environment variable
type ErrBadEnvVariable struct {
msg string
}
func (e ErrBadEnvVariable) Error() string {
return fmt.Sprintf("poorly formatted environment: %s", e.msg)
return parseKeyValueFile(filename, os.LookupEnv)
}

77
vendor/github.com/docker/cli/opts/file.go generated vendored Normal file
View file

@ -0,0 +1,77 @@
package opts
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
)
const whiteSpaces = " \t"
// ErrBadKey typed error for bad environment variable
type ErrBadKey struct {
msg string
}
func (e ErrBadKey) Error() string {
return fmt.Sprintf("poorly formatted environment: %s", e.msg)
}
func parseKeyValueFile(filename string, emptyFn func(string) (string, bool)) ([]string, error) {
fh, err := os.Open(filename)
if err != nil {
return []string{}, err
}
defer fh.Close()
lines := []string{}
scanner := bufio.NewScanner(fh)
currentLine := 0
utf8bom := []byte{0xEF, 0xBB, 0xBF}
for scanner.Scan() {
scannedBytes := scanner.Bytes()
if !utf8.Valid(scannedBytes) {
return []string{}, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v", filename, currentLine+1, scannedBytes)
}
// We trim UTF8 BOM
if currentLine == 0 {
scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom)
}
// trim the line from all leading whitespace first
line := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace)
currentLine++
// line is not empty, and not starting with '#'
if len(line) > 0 && !strings.HasPrefix(line, "#") {
data := strings.SplitN(line, "=", 2)
// trim the front of a variable, but nothing else
variable := strings.TrimLeft(data[0], whiteSpaces)
if strings.ContainsAny(variable, whiteSpaces) {
return []string{}, ErrBadKey{fmt.Sprintf("variable '%s' contains whitespaces", variable)}
}
if len(variable) == 0 {
return []string{}, ErrBadKey{fmt.Sprintf("no variable name on line '%s'", line)}
}
if len(data) > 1 {
// pass the value through, no trimming
lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
} else {
var value string
var present bool
if emptyFn != nil {
value, present = emptyFn(line)
}
if present {
// if only a pass-through variable is given, clean it up.
lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), value))
}
}
}
}
return lines, scanner.Err()
}

112
vendor/github.com/docker/cli/opts/gpus.go generated vendored Normal file
View file

@ -0,0 +1,112 @@
package opts
import (
"encoding/csv"
"fmt"
"strconv"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
)
// GpuOpts is a Value type for parsing mounts
type GpuOpts struct {
values []container.DeviceRequest
}
func parseCount(s string) (int, error) {
if s == "all" {
return -1, nil
}
i, err := strconv.Atoi(s)
return i, errors.Wrap(err, "count must be an integer")
}
// Set a new mount value
// nolint: gocyclo
func (o *GpuOpts) Set(value string) error {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return err
}
req := container.DeviceRequest{}
seen := map[string]struct{}{}
// Set writable as the default
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
key := parts[0]
if _, ok := seen[key]; ok {
return fmt.Errorf("gpu request key '%s' can be specified only once", key)
}
seen[key] = struct{}{}
if len(parts) == 1 {
seen["count"] = struct{}{}
req.Count, err = parseCount(key)
if err != nil {
return err
}
continue
}
value := parts[1]
switch key {
case "driver":
req.Driver = value
case "count":
req.Count, err = parseCount(value)
if err != nil {
return err
}
case "device":
req.DeviceIDs = strings.Split(value, ",")
case "capabilities":
req.Capabilities = [][]string{append(strings.Split(value, ","), "gpu")}
case "options":
r := csv.NewReader(strings.NewReader(value))
optFields, err := r.Read()
if err != nil {
return errors.Wrap(err, "failed to read gpu options")
}
req.Options = ConvertKVStringsToMap(optFields)
default:
return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
}
}
if _, ok := seen["count"]; !ok && req.DeviceIDs == nil {
req.Count = 1
}
if req.Options == nil {
req.Options = make(map[string]string)
}
if req.Capabilities == nil {
req.Capabilities = [][]string{{"gpu"}}
}
o.values = append(o.values, req)
return nil
}
// Type returns the type of this option
func (o *GpuOpts) Type() string {
return "gpu-request"
}
// String returns a string repr of this option
func (o *GpuOpts) String() string {
gpus := []string{}
for _, gpu := range o.values {
gpus = append(gpus, fmt.Sprintf("%v", gpu))
}
return strings.Join(gpus, ", ")
}
// Value returns the mounts
func (o *GpuOpts) Value() []container.DeviceRequest {
return o.values
}

View file

@ -77,6 +77,8 @@ func parseDockerDaemonHost(addr string) (string, error) {
return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe)
case "fd":
return addr, nil
case "ssh":
return addr, nil
default:
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}

View file

@ -76,6 +76,9 @@ func (m *MountOpt) Set(value string) error {
case "volume-nocopy":
volumeOptions().NoCopy = true
continue
case "bind-nonrecursive":
bindOptions().NonRecursive = true
continue
}
}
@ -100,6 +103,11 @@ func (m *MountOpt) Set(value string) error {
mount.Consistency = mounttypes.Consistency(strings.ToLower(value))
case "bind-propagation":
bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value))
case "bind-nonrecursive":
bindOptions().NonRecursive, err = strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", key, value)
}
case "volume-nocopy":
volumeOptions().NoCopy, err = strconv.ParseBool(value)
if err != nil {

View file

@ -15,9 +15,13 @@ const (
// NetworkAttachmentOpts represents the network options for endpoint creation
type NetworkAttachmentOpts struct {
Target string
Aliases []string
DriverOpts map[string]string
Target string
Aliases []string
DriverOpts map[string]string
Links []string // TODO add support for links in the csv notation of `--network`
IPv4Address string // TODO add support for IPv4-address in the csv notation of `--network`
IPv6Address string // TODO add support for IPv6-address in the csv notation of `--network`
LinkLocalIPs []string // TODO add support for LinkLocalIPs in the csv notation of `--network` ?
}
// NetworkOpt represents a network config in swarm mode.
@ -95,6 +99,16 @@ func (n *NetworkOpt) String() string {
return ""
}
// NetworkMode return the network mode for the network option
func (n *NetworkOpt) NetworkMode() string {
networkIDOrName := "default"
netOptVal := n.Value()
if len(netOptVal) > 0 {
networkIDOrName = netOptVal[0].Target
}
return networkIDOrName
}
func parseDriverOpt(driverOpt string) (string, string, error) {
parts := strings.SplitN(driverOpt, "=", 2)
if len(parts) != 2 {

View file

@ -10,6 +10,7 @@ import (
"github.com/docker/docker/api/types/filters"
units "github.com/docker/go-units"
"github.com/pkg/errors"
)
var (
@ -266,10 +267,24 @@ func validateDomain(val string) (string, error) {
}
// ValidateLabel validates that the specified string is a valid label, and returns it.
// Labels are in the form on key=value.
//
// Labels are in the form of key=value; key must be a non-empty string, and not
// contain whitespaces. A value is optional (defaults to an empty string if omitted).
//
// Leading whitespace is removed during validation but values are kept as-is
// otherwise, so any string value is accepted for both, which includes whitespace
// (for values) and quotes (surrounding, or embedded in key or value).
//
// TODO discuss if quotes (and other special characters) should be valid or invalid for keys
// TODO discuss if leading/trailing whitespace in keys should be preserved (and valid)
func ValidateLabel(val string) (string, error) {
if strings.Count(val, "=") < 1 {
return "", fmt.Errorf("bad attribute format: %s", val)
arr := strings.SplitN(val, "=", 2)
key := strings.TrimLeft(arr[0], whiteSpaces)
if key == "" {
return "", fmt.Errorf("invalid label '%s': empty name", val)
}
if strings.ContainsAny(key, whiteSpaces) {
return "", fmt.Errorf("label '%s' contains whitespaces", key)
}
return val, nil
}
@ -306,6 +321,17 @@ func ValidateSysctl(val string) (string, error) {
return "", fmt.Errorf("sysctl '%s' is not whitelisted", val)
}
// ValidateProgressOutput errors out if an invalid value is passed to --progress
func ValidateProgressOutput(val string) error {
valid := []string{"auto", "plain", "tty"}
for _, s := range valid {
if s == val {
return nil
}
}
return fmt.Errorf("invalid value %q passed to --progress, valid values are: %s", val, strings.Join(valid, ", "))
}
// FilterOpt is a flag type for validating filters
type FilterOpt struct {
filter filters.Args
@ -317,7 +343,7 @@ func NewFilterOpt() FilterOpt {
}
func (o *FilterOpt) String() string {
repr, err := filters.ToParam(o.filter)
repr, err := filters.ToJSON(o.filter)
if err != nil {
return "invalid filters"
}
@ -326,9 +352,18 @@ func (o *FilterOpt) String() string {
// Set sets the value of the opt by parsing the command line value
func (o *FilterOpt) Set(value string) error {
var err error
o.filter, err = filters.ParseFlag(value, o.filter)
return err
if value == "" {
return nil
}
if !strings.Contains(value, "=") {
return errors.New("bad format of filter (expected name=value)")
}
f := strings.SplitN(value, "=", 2)
name := strings.ToLower(strings.TrimSpace(f[0]))
value = strings.TrimSpace(f[1])
o.filter.Add(name, value)
return nil
}
// Type returns the option type

View file

@ -2,6 +2,7 @@ package opts
import (
"fmt"
"os"
"strconv"
"strings"
@ -11,18 +12,29 @@ import (
// ReadKVStrings reads a file of line terminated key=value pairs, and overrides any keys
// present in the file with additional pairs specified in the override parameter
func ReadKVStrings(files []string, override []string) ([]string, error) {
envVariables := []string{}
return readKVStrings(files, override, nil)
}
// ReadKVEnvStrings reads a file of line terminated key=value pairs, and overrides any keys
// present in the file with additional pairs specified in the override parameter.
// If a key has no value, it will get the value from the environment.
func ReadKVEnvStrings(files []string, override []string) ([]string, error) {
return readKVStrings(files, override, os.LookupEnv)
}
func readKVStrings(files []string, override []string, emptyFn func(string) (string, bool)) ([]string, error) {
variables := []string{}
for _, ef := range files {
parsedVars, err := ParseEnvFile(ef)
parsedVars, err := parseKeyValueFile(ef, emptyFn)
if err != nil {
return nil, err
}
envVariables = append(envVariables, parsedVars...)
variables = append(variables, parsedVars...)
}
// parse the '-e' and '--env' after, to allow override
envVariables = append(envVariables, override...)
variables = append(variables, override...)
return envVariables, nil
return variables, nil
}
// ConvertKVStringsToMap converts ["key=value"] to {"key":"value"}

View file

@ -9,6 +9,7 @@ import (
"github.com/docker/docker/api/types/swarm"
"github.com/docker/go-connections/nat"
"github.com/sirupsen/logrus"
)
const (
@ -49,7 +50,7 @@ func (p *PortOpt) Set(value string) error {
switch key {
case portOptProtocol:
if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) {
if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) && value != string(swarm.PortConfigProtocolSCTP) {
return fmt.Errorf("invalid protocol value %s", value)
}
@ -147,17 +148,25 @@ func ConvertPortToPortConfig(
ports := []swarm.PortConfig{}
for _, binding := range portBindings[port] {
hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16)
if binding.HostIP != "" && binding.HostIP != "0.0.0.0" {
logrus.Warnf("ignoring IP-address (%s:%s:%s) service will listen on '0.0.0.0'", binding.HostIP, binding.HostPort, port)
}
startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort)
if err != nil && binding.HostPort != "" {
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
}
ports = append(ports, swarm.PortConfig{
//TODO Name: ?
Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
TargetPort: uint32(port.Int()),
PublishedPort: uint32(hostPort),
PublishMode: swarm.PortConfigPublishModeIngress,
})
for i := startHostPort; i <= endHostPort; i++ {
ports = append(ports, swarm.PortConfig{
//TODO Name: ?
Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
TargetPort: uint32(port.Int()),
PublishedPort: uint32(i),
PublishMode: swarm.PortConfigPublishModeIngress,
})
}
}
return ports, nil
}