mirror of https://github.com/docker/cli.git
Merge pull request #528 from andrewhsu/ven
vndr github.com/docker/docker to 84144a8 to fix stuff
This commit is contained in:
commit
f697de32b9
|
@ -4,7 +4,7 @@ github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
|
||||||
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
|
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
|
||||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
|
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
|
||||||
github.com/docker/docker ea220e70a13963da544645376cd9331021eec6b4
|
github.com/docker/docker 84144a8c66c1bb2af8fa997288f51ef2719971b4
|
||||||
github.com/docker/docker-credential-helpers v0.5.1
|
github.com/docker/docker-credential-helpers v0.5.1
|
||||||
|
|
||||||
# the docker/go package contains a customized version of canonical/json
|
# the docker/go package contains a customized version of canonical/json
|
||||||
|
|
|
@ -181,7 +181,7 @@ type ImageBuildOptions struct {
|
||||||
SessionID string
|
SessionID string
|
||||||
|
|
||||||
// TODO @jhowardmsft LCOW Support: This will require extending to include
|
// TODO @jhowardmsft LCOW Support: This will require extending to include
|
||||||
// `Platform string`, but is ommited for now as it's hard-coded temporarily
|
// `Platform string`, but is omitted for now as it's hard-coded temporarily
|
||||||
// to avoid API changes.
|
// to avoid API changes.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Package filters provides helper function to parse and handle command line
|
/*Package filters provides tools for encoding a mapping of keys to a set of
|
||||||
// filter, used for example in docker ps or docker images commands.
|
multiple values.
|
||||||
|
*/
|
||||||
package filters
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -11,27 +12,34 @@ import (
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Args stores filter arguments as map key:{map key: bool}.
|
// Args stores a mapping of keys to a set of multiple values.
|
||||||
// It contains an aggregation of the map of arguments (which are in the form
|
|
||||||
// of -f 'key=value') based on the key, and stores values for the same key
|
|
||||||
// in a map with string keys and boolean values.
|
|
||||||
// e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu'
|
|
||||||
// the args will be {"image.name":{"ubuntu":true},"label":{"label1=1":true,"label2=2":true}}
|
|
||||||
type Args struct {
|
type Args struct {
|
||||||
fields map[string]map[string]bool
|
fields map[string]map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewArgs initializes a new Args struct.
|
// KeyValuePair are used to initialize a new Args
|
||||||
func NewArgs() Args {
|
type KeyValuePair struct {
|
||||||
return Args{fields: map[string]map[string]bool{}}
|
Key string
|
||||||
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFlag parses the argument to the filter flag. Like
|
// Arg creates a new KeyValuePair for initializing Args
|
||||||
|
func Arg(key, value string) KeyValuePair {
|
||||||
|
return KeyValuePair{Key: key, Value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArgs returns a new Args populated with the initial args
|
||||||
|
func NewArgs(initialArgs ...KeyValuePair) Args {
|
||||||
|
args := Args{fields: map[string]map[string]bool{}}
|
||||||
|
for _, arg := range initialArgs {
|
||||||
|
args.Add(arg.Key, arg.Value)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFlag parses a key=value string and adds it to an Args.
|
||||||
//
|
//
|
||||||
// `docker ps -f 'created=today' -f 'image.name=ubuntu*'`
|
// Deprecated: Use Args.Add()
|
||||||
//
|
|
||||||
// If prev map is provided, then it is appended to, and returned. By default a new
|
|
||||||
// map is created.
|
|
||||||
func ParseFlag(arg string, prev Args) (Args, error) {
|
func ParseFlag(arg string, prev Args) (Args, error) {
|
||||||
filters := prev
|
filters := prev
|
||||||
if len(arg) == 0 {
|
if len(arg) == 0 {
|
||||||
|
@ -52,74 +60,95 @@ func ParseFlag(arg string, prev Args) (Args, error) {
|
||||||
return filters, nil
|
return filters, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrBadFormat is an error returned in case of bad format for a filter.
|
// ErrBadFormat is an error returned when a filter is not in the form key=value
|
||||||
|
//
|
||||||
|
// Deprecated: this error will be removed in a future version
|
||||||
var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
|
var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
|
||||||
|
|
||||||
// ToParam packs the Args into a string for easy transport from client to server.
|
// ToParam encodes the Args as args JSON encoded string
|
||||||
|
//
|
||||||
|
// Deprecated: use ToJSON
|
||||||
func ToParam(a Args) (string, error) {
|
func ToParam(a Args) (string, error) {
|
||||||
// this way we don't URL encode {}, just empty space
|
return ToJSON(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns a JSON byte representation of the Args
|
||||||
|
func (args Args) MarshalJSON() ([]byte, error) {
|
||||||
|
if len(args.fields) == 0 {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
return json.Marshal(args.fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJSON returns the Args as a JSON encoded string
|
||||||
|
func ToJSON(a Args) (string, error) {
|
||||||
if a.Len() == 0 {
|
if a.Len() == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
buf, err := json.Marshal(a)
|
||||||
buf, err := json.Marshal(a.fields)
|
return string(buf), err
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(buf), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToParamWithVersion packs the Args into a string for easy transport from client to server.
|
// ToParamWithVersion encodes Args as a JSON string. If version is less than 1.22
|
||||||
// The generated string will depend on the specified version (corresponding to the API version).
|
// then the encoded format will use an older legacy format where the values are a
|
||||||
|
// list of strings, instead of a set.
|
||||||
|
//
|
||||||
|
// Deprecated: Use ToJSON
|
||||||
func ToParamWithVersion(version string, a Args) (string, error) {
|
func ToParamWithVersion(version string, a Args) (string, error) {
|
||||||
// this way we don't URL encode {}, just empty space
|
|
||||||
if a.Len() == 0 {
|
if a.Len() == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// for daemons older than v1.10, filter must be of the form map[string][]string
|
|
||||||
var buf []byte
|
|
||||||
var err error
|
|
||||||
if version != "" && versions.LessThan(version, "1.22") {
|
if version != "" && versions.LessThan(version, "1.22") {
|
||||||
buf, err = json.Marshal(convertArgsToSlice(a.fields))
|
buf, err := json.Marshal(convertArgsToSlice(a.fields))
|
||||||
} else {
|
return string(buf), err
|
||||||
buf, err = json.Marshal(a.fields)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(buf), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromParam unpacks the filter Args.
|
return ToJSON(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromParam decodes a JSON encoded string into Args
|
||||||
|
//
|
||||||
|
// Deprecated: use FromJSON
|
||||||
func FromParam(p string) (Args, error) {
|
func FromParam(p string) (Args, error) {
|
||||||
if len(p) == 0 {
|
return FromJSON(p)
|
||||||
return NewArgs(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r := strings.NewReader(p)
|
// FromJSON decodes a JSON encoded string into Args
|
||||||
d := json.NewDecoder(r)
|
func FromJSON(p string) (Args, error) {
|
||||||
|
args := NewArgs()
|
||||||
|
|
||||||
m := map[string]map[string]bool{}
|
if p == "" {
|
||||||
if err := d.Decode(&m); err != nil {
|
return args, nil
|
||||||
r.Seek(0, 0)
|
}
|
||||||
|
|
||||||
// Allow parsing old arguments in slice format.
|
raw := []byte(p)
|
||||||
// Because other libraries might be sending them in this format.
|
err := json.Unmarshal(raw, &args)
|
||||||
|
if err == nil {
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to parsing arguments in the legacy slice format
|
||||||
deprecated := map[string][]string{}
|
deprecated := map[string][]string{}
|
||||||
if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil {
|
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
||||||
m = deprecatedArgs(deprecated)
|
return args, err
|
||||||
} else {
|
|
||||||
return NewArgs(), err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Args{m}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the list of values associates with a field.
|
args.fields = deprecatedArgs(deprecated)
|
||||||
// It returns a slice of strings to keep backwards compatibility with old code.
|
return args, nil
|
||||||
func (filters Args) Get(field string) []string {
|
}
|
||||||
values := filters.fields[field]
|
|
||||||
|
// UnmarshalJSON populates the Args from JSON encode bytes
|
||||||
|
func (args Args) UnmarshalJSON(raw []byte) error {
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal(raw, &args.fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the list of values associated with the key
|
||||||
|
func (args Args) Get(key string) []string {
|
||||||
|
values := args.fields[key]
|
||||||
if values == nil {
|
if values == nil {
|
||||||
return make([]string, 0)
|
return make([]string, 0)
|
||||||
}
|
}
|
||||||
|
@ -130,37 +159,34 @@ func (filters Args) Get(field string) []string {
|
||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a new value to a filter field.
|
// Add a new value to the set of values
|
||||||
func (filters Args) Add(name, value string) {
|
func (args Args) Add(key, value string) {
|
||||||
if _, ok := filters.fields[name]; ok {
|
if _, ok := args.fields[key]; ok {
|
||||||
filters.fields[name][value] = true
|
args.fields[key][value] = true
|
||||||
} else {
|
} else {
|
||||||
filters.fields[name] = map[string]bool{value: true}
|
args.fields[key] = map[string]bool{value: true}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Del removes a value from a filter field.
|
// Del removes a value from the set
|
||||||
func (filters Args) Del(name, value string) {
|
func (args Args) Del(key, value string) {
|
||||||
if _, ok := filters.fields[name]; ok {
|
if _, ok := args.fields[key]; ok {
|
||||||
delete(filters.fields[name], value)
|
delete(args.fields[key], value)
|
||||||
if len(filters.fields[name]) == 0 {
|
if len(args.fields[key]) == 0 {
|
||||||
delete(filters.fields, name)
|
delete(args.fields, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the number of fields in the arguments.
|
// Len returns the number of keys in the mapping
|
||||||
func (filters Args) Len() int {
|
func (args Args) Len() int {
|
||||||
return len(filters.fields)
|
return len(args.fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchKVList returns true if the values for the specified field matches the ones
|
// MatchKVList returns true if all the pairs in sources exist as key=value
|
||||||
// from the sources.
|
// pairs in the mapping at key, or if there are no values at key.
|
||||||
// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
|
func (args Args) MatchKVList(key string, sources map[string]string) bool {
|
||||||
// field is 'label' and sources are {'label1': '1', 'label2': '2'}
|
fieldValues := args.fields[key]
|
||||||
// it returns true.
|
|
||||||
func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
|
||||||
fieldValues := filters.fields[field]
|
|
||||||
|
|
||||||
//do not filter if there is no filter set or cannot determine filter
|
//do not filter if there is no filter set or cannot determine filter
|
||||||
if len(fieldValues) == 0 {
|
if len(fieldValues) == 0 {
|
||||||
|
@ -171,8 +197,8 @@ func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for name2match := range fieldValues {
|
for value := range fieldValues {
|
||||||
testKV := strings.SplitN(name2match, "=", 2)
|
testKV := strings.SplitN(value, "=", 2)
|
||||||
|
|
||||||
v, ok := sources[testKV[0]]
|
v, ok := sources[testKV[0]]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -186,16 +212,13 @@ func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true if the values for the specified field matches the source string
|
// Match returns true if any of the values at key match the source string
|
||||||
// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
|
func (args Args) Match(field, source string) bool {
|
||||||
// field is 'image.name' and source is 'ubuntu'
|
if args.ExactMatch(field, source) {
|
||||||
// it returns true.
|
|
||||||
func (filters Args) Match(field, source string) bool {
|
|
||||||
if filters.ExactMatch(field, source) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldValues := filters.fields[field]
|
fieldValues := args.fields[field]
|
||||||
for name2match := range fieldValues {
|
for name2match := range fieldValues {
|
||||||
match, err := regexp.MatchString(name2match, source)
|
match, err := regexp.MatchString(name2match, source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -208,9 +231,9 @@ func (filters Args) Match(field, source string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExactMatch returns true if the source matches exactly one of the filters.
|
// ExactMatch returns true if the source matches exactly one of the values.
|
||||||
func (filters Args) ExactMatch(field, source string) bool {
|
func (args Args) ExactMatch(key, source string) bool {
|
||||||
fieldValues, ok := filters.fields[field]
|
fieldValues, ok := args.fields[key]
|
||||||
//do not filter if there is no filter set or cannot determine filter
|
//do not filter if there is no filter set or cannot determine filter
|
||||||
if !ok || len(fieldValues) == 0 {
|
if !ok || len(fieldValues) == 0 {
|
||||||
return true
|
return true
|
||||||
|
@ -220,14 +243,15 @@ func (filters Args) ExactMatch(field, source string) bool {
|
||||||
return fieldValues[source]
|
return fieldValues[source]
|
||||||
}
|
}
|
||||||
|
|
||||||
// UniqueExactMatch returns true if there is only one filter and the source matches exactly this one.
|
// UniqueExactMatch returns true if there is only one value and the source
|
||||||
func (filters Args) UniqueExactMatch(field, source string) bool {
|
// matches exactly the value.
|
||||||
fieldValues := filters.fields[field]
|
func (args Args) UniqueExactMatch(key, source string) bool {
|
||||||
|
fieldValues := args.fields[key]
|
||||||
//do not filter if there is no filter set or cannot determine filter
|
//do not filter if there is no filter set or cannot determine filter
|
||||||
if len(fieldValues) == 0 {
|
if len(fieldValues) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if len(filters.fields[field]) != 1 {
|
if len(args.fields[key]) != 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,14 +259,14 @@ func (filters Args) UniqueExactMatch(field, source string) bool {
|
||||||
return fieldValues[source]
|
return fieldValues[source]
|
||||||
}
|
}
|
||||||
|
|
||||||
// FuzzyMatch returns true if the source matches exactly one of the filters,
|
// FuzzyMatch returns true if the source matches exactly one value, or the
|
||||||
// or the source has one of the filters as a prefix.
|
// source has one of the values as a prefix.
|
||||||
func (filters Args) FuzzyMatch(field, source string) bool {
|
func (args Args) FuzzyMatch(key, source string) bool {
|
||||||
if filters.ExactMatch(field, source) {
|
if args.ExactMatch(key, source) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldValues := filters.fields[field]
|
fieldValues := args.fields[key]
|
||||||
for prefix := range fieldValues {
|
for prefix := range fieldValues {
|
||||||
if strings.HasPrefix(source, prefix) {
|
if strings.HasPrefix(source, prefix) {
|
||||||
return true
|
return true
|
||||||
|
@ -251,9 +275,17 @@ func (filters Args) FuzzyMatch(field, source string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include returns true if the name of the field to filter is in the filters.
|
// Include returns true if the key exists in the mapping
|
||||||
func (filters Args) Include(field string) bool {
|
//
|
||||||
_, ok := filters.fields[field]
|
// Deprecated: use Contains
|
||||||
|
func (args Args) Include(field string) bool {
|
||||||
|
_, ok := args.fields[field]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains returns true if the key exists in the mapping
|
||||||
|
func (args Args) Contains(field string) bool {
|
||||||
|
_, ok := args.fields[field]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,10 +297,10 @@ func (e invalidFilter) Error() string {
|
||||||
|
|
||||||
func (invalidFilter) InvalidParameter() {}
|
func (invalidFilter) InvalidParameter() {}
|
||||||
|
|
||||||
// Validate ensures that all the fields in the filter are valid.
|
// Validate compared the set of accepted keys against the keys in the mapping.
|
||||||
// It returns an error as soon as it finds an invalid field.
|
// An error is returned if any mapping keys are not in the accepted set.
|
||||||
func (filters Args) Validate(accepted map[string]bool) error {
|
func (args Args) Validate(accepted map[string]bool) error {
|
||||||
for name := range filters.fields {
|
for name := range args.fields {
|
||||||
if !accepted[name] {
|
if !accepted[name] {
|
||||||
return invalidFilter(name)
|
return invalidFilter(name)
|
||||||
}
|
}
|
||||||
|
@ -276,13 +308,14 @@ func (filters Args) Validate(accepted map[string]bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WalkValues iterates over the list of filtered values for a field.
|
// WalkValues iterates over the list of values for a key in the mapping and calls
|
||||||
// It stops the iteration if it finds an error and it returns that error.
|
// op() for each value. If op returns an error the iteration stops and the
|
||||||
func (filters Args) WalkValues(field string, op func(value string) error) error {
|
// error is returned.
|
||||||
if _, ok := filters.fields[field]; !ok {
|
func (args Args) WalkValues(field string, op func(value string) error) error {
|
||||||
|
if _, ok := args.fields[field]; !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for v := range filters.fields[field] {
|
for v := range args.fields[field] {
|
||||||
if err := op(v); err != nil {
|
if err := op(v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Package client is a Go client for the Docker Engine API.
|
Package client is a Go client for the Docker Engine API.
|
||||||
|
|
||||||
The "docker" command uses this package to communicate with the daemon. It can also
|
|
||||||
be used by your own Go applications to do anything the command-line interface does
|
|
||||||
- running containers, pulling images, managing swarms, etc.
|
|
||||||
|
|
||||||
For more information about the Engine API, see the documentation:
|
For more information about the Engine API, see the documentation:
|
||||||
https://docs.docker.com/engine/reference/api/
|
https://docs.docker.com/engine/reference/api/
|
||||||
|
|
||||||
|
@ -160,7 +156,7 @@ func NewEnvClient() (*Client, error) {
|
||||||
// highly recommended that you set a version or your client may break if the
|
// highly recommended that you set a version or your client may break if the
|
||||||
// server is upgraded.
|
// server is upgraded.
|
||||||
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
|
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
|
||||||
proto, addr, basePath, err := ParseHost(host)
|
hostURL, err := ParseHostURL(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -171,7 +167,7 @@ func NewClient(host string, version string, client *http.Client, httpHeaders map
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
transport := new(http.Transport)
|
transport := new(http.Transport)
|
||||||
sockets.ConfigureTransport(transport, proto, addr)
|
sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
|
||||||
client = &http.Client{
|
client = &http.Client{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
CheckRedirect: CheckRedirect,
|
CheckRedirect: CheckRedirect,
|
||||||
|
@ -189,28 +185,24 @@ func NewClient(host string, version string, client *http.Client, httpHeaders map
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: store URL instead of proto/addr/basePath
|
||||||
return &Client{
|
return &Client{
|
||||||
scheme: scheme,
|
scheme: scheme,
|
||||||
host: host,
|
host: host,
|
||||||
proto: proto,
|
proto: hostURL.Scheme,
|
||||||
addr: addr,
|
addr: hostURL.Host,
|
||||||
basePath: basePath,
|
basePath: hostURL.Path,
|
||||||
client: client,
|
client: client,
|
||||||
version: version,
|
version: version,
|
||||||
customHTTPHeaders: httpHeaders,
|
customHTTPHeaders: httpHeaders,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close ensures that transport.Client is closed
|
// Close the transport used by the client
|
||||||
// especially needed while using NewClient with *http.Client = nil
|
|
||||||
// for example
|
|
||||||
// client.NewClient("unix:///var/run/docker.sock", nil, "v1.18", map[string]string{"User-Agent": "engine-api-cli-1.0"})
|
|
||||||
func (cli *Client) Close() error {
|
func (cli *Client) Close() error {
|
||||||
|
|
||||||
if t, ok := cli.client.Transport.(*http.Transport); ok {
|
if t, ok := cli.client.Transport.(*http.Transport); ok {
|
||||||
t.CloseIdleConnections()
|
t.CloseIdleConnections()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,37 +212,27 @@ func (cli *Client) getAPIPath(p string, query url.Values) string {
|
||||||
var apiPath string
|
var apiPath string
|
||||||
if cli.version != "" {
|
if cli.version != "" {
|
||||||
v := strings.TrimPrefix(cli.version, "v")
|
v := strings.TrimPrefix(cli.version, "v")
|
||||||
apiPath = path.Join(cli.basePath, "/v"+v+p)
|
apiPath = path.Join(cli.basePath, "/v"+v, p)
|
||||||
} else {
|
} else {
|
||||||
apiPath = path.Join(cli.basePath, p)
|
apiPath = path.Join(cli.basePath, p)
|
||||||
}
|
}
|
||||||
|
return (&url.URL{Path: apiPath, RawQuery: query.Encode()}).String()
|
||||||
u := &url.URL{
|
|
||||||
Path: apiPath,
|
|
||||||
}
|
|
||||||
if len(query) > 0 {
|
|
||||||
u.RawQuery = query.Encode()
|
|
||||||
}
|
|
||||||
return u.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientVersion returns the version string associated with this
|
// ClientVersion returns the API version used by this client.
|
||||||
// instance of the Client. Note that this value can be changed
|
|
||||||
// via the DOCKER_API_VERSION env var.
|
|
||||||
// This operation doesn't acquire a mutex.
|
|
||||||
func (cli *Client) ClientVersion() string {
|
func (cli *Client) ClientVersion() string {
|
||||||
return cli.version
|
return cli.version
|
||||||
}
|
}
|
||||||
|
|
||||||
// NegotiateAPIVersion updates the version string associated with this
|
// NegotiateAPIVersion queries the API and updates the version to match the
|
||||||
// instance of the Client to match the latest version the server supports
|
// API version. Any errors are silently ignored.
|
||||||
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
|
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
|
||||||
ping, _ := cli.Ping(ctx)
|
ping, _ := cli.Ping(ctx)
|
||||||
cli.NegotiateAPIVersionPing(ping)
|
cli.NegotiateAPIVersionPing(ping)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NegotiateAPIVersionPing updates the version string associated with this
|
// NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
|
||||||
// instance of the Client to match the latest version the server supports
|
// if the ping version is less than the default version.
|
||||||
func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
|
func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
|
||||||
if cli.manualOverride {
|
if cli.manualOverride {
|
||||||
return
|
return
|
||||||
|
@ -272,17 +254,28 @@ func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DaemonHost returns the host associated with this instance of the Client.
|
// DaemonHost returns the host address used by the client
|
||||||
// This operation doesn't acquire a mutex.
|
|
||||||
func (cli *Client) DaemonHost() string {
|
func (cli *Client) DaemonHost() string {
|
||||||
return cli.host
|
return cli.host
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseHost verifies that the given host strings is valid.
|
// ParseHost parses a url string, validates the strings is a host url, and returns
|
||||||
|
// the parsed host as: protocol, address, and base path
|
||||||
|
// Deprecated: use ParseHostURL
|
||||||
func ParseHost(host string) (string, string, string, error) {
|
func ParseHost(host string) (string, string, string, error) {
|
||||||
|
hostURL, err := ParseHostURL(host)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
return hostURL.Scheme, hostURL.Host, hostURL.Path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseHostURL parses a url string, validates the string is a host url, and
|
||||||
|
// returns the parsed URL
|
||||||
|
func ParseHostURL(host string) (*url.URL, error) {
|
||||||
protoAddrParts := strings.SplitN(host, "://", 2)
|
protoAddrParts := strings.SplitN(host, "://", 2)
|
||||||
if len(protoAddrParts) == 1 {
|
if len(protoAddrParts) == 1 {
|
||||||
return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
|
return nil, fmt.Errorf("unable to parse docker host `%s`", host)
|
||||||
}
|
}
|
||||||
|
|
||||||
var basePath string
|
var basePath string
|
||||||
|
@ -290,16 +283,19 @@ func ParseHost(host string) (string, string, string, error) {
|
||||||
if proto == "tcp" {
|
if proto == "tcp" {
|
||||||
parsed, err := url.Parse("tcp://" + addr)
|
parsed, err := url.Parse("tcp://" + addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
addr = parsed.Host
|
addr = parsed.Host
|
||||||
basePath = parsed.Path
|
basePath = parsed.Path
|
||||||
}
|
}
|
||||||
return proto, addr, basePath, nil
|
return &url.URL{
|
||||||
|
Scheme: proto,
|
||||||
|
Host: addr,
|
||||||
|
Path: basePath,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomHTTPHeaders returns the custom http headers associated with this
|
// CustomHTTPHeaders returns the custom http headers stored by the client.
|
||||||
// instance of the Client. This operation doesn't acquire a mutex.
|
|
||||||
func (cli *Client) CustomHTTPHeaders() map[string]string {
|
func (cli *Client) CustomHTTPHeaders() map[string]string {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
for k, v := range cli.customHTTPHeaders {
|
for k, v := range cli.customHTTPHeaders {
|
||||||
|
@ -308,8 +304,7 @@ func (cli *Client) CustomHTTPHeaders() map[string]string {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCustomHTTPHeaders updates the custom http headers associated with this
|
// SetCustomHTTPHeaders that will be set on every HTTP request made by the client.
|
||||||
// instance of the Client. This operation doesn't acquire a mutex.
|
|
||||||
func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
|
func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
|
||||||
cli.customHTTPHeaders = headers
|
cli.customHTTPHeaders = headers
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,8 @@ type notFound interface {
|
||||||
NotFound() bool // Is the error a NotFound error
|
NotFound() bool // Is the error a NotFound error
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsErrNotFound returns true if the error is caused with an
|
// IsErrNotFound returns true if the error is a NotFound error, which is returned
|
||||||
// object (image, container, network, volume, …) is not found in the docker host.
|
// by the API when some object is not found.
|
||||||
func IsErrNotFound(err error) bool {
|
func IsErrNotFound(err error) bool {
|
||||||
te, ok := err.(notFound)
|
te, ok := err.(notFound)
|
||||||
return ok && te.NotFound()
|
return ok && te.NotFound()
|
||||||
|
@ -60,6 +60,8 @@ func (e imageNotFoundError) Error() string {
|
||||||
|
|
||||||
// IsErrImageNotFound returns true if the error is caused
|
// IsErrImageNotFound returns true if the error is caused
|
||||||
// when an image is not found in the docker host.
|
// when an image is not found in the docker host.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrImageNotFound(err error) bool {
|
func IsErrImageNotFound(err error) bool {
|
||||||
return IsErrNotFound(err)
|
return IsErrNotFound(err)
|
||||||
}
|
}
|
||||||
|
@ -81,6 +83,8 @@ func (e containerNotFoundError) Error() string {
|
||||||
|
|
||||||
// IsErrContainerNotFound returns true if the error is caused
|
// IsErrContainerNotFound returns true if the error is caused
|
||||||
// when a container is not found in the docker host.
|
// when a container is not found in the docker host.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrContainerNotFound(err error) bool {
|
func IsErrContainerNotFound(err error) bool {
|
||||||
return IsErrNotFound(err)
|
return IsErrNotFound(err)
|
||||||
}
|
}
|
||||||
|
@ -102,6 +106,8 @@ func (e networkNotFoundError) Error() string {
|
||||||
|
|
||||||
// IsErrNetworkNotFound returns true if the error is caused
|
// IsErrNetworkNotFound returns true if the error is caused
|
||||||
// when a network is not found in the docker host.
|
// when a network is not found in the docker host.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrNetworkNotFound(err error) bool {
|
func IsErrNetworkNotFound(err error) bool {
|
||||||
return IsErrNotFound(err)
|
return IsErrNotFound(err)
|
||||||
}
|
}
|
||||||
|
@ -123,6 +129,8 @@ func (e volumeNotFoundError) Error() string {
|
||||||
|
|
||||||
// IsErrVolumeNotFound returns true if the error is caused
|
// IsErrVolumeNotFound returns true if the error is caused
|
||||||
// when a volume is not found in the docker host.
|
// when a volume is not found in the docker host.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrVolumeNotFound(err error) bool {
|
func IsErrVolumeNotFound(err error) bool {
|
||||||
return IsErrNotFound(err)
|
return IsErrNotFound(err)
|
||||||
}
|
}
|
||||||
|
@ -161,6 +169,8 @@ func (e nodeNotFoundError) NotFound() bool {
|
||||||
|
|
||||||
// IsErrNodeNotFound returns true if the error is caused
|
// IsErrNodeNotFound returns true if the error is caused
|
||||||
// when a node is not found.
|
// when a node is not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrNodeNotFound(err error) bool {
|
func IsErrNodeNotFound(err error) bool {
|
||||||
_, ok := err.(nodeNotFoundError)
|
_, ok := err.(nodeNotFoundError)
|
||||||
return ok
|
return ok
|
||||||
|
@ -183,6 +193,8 @@ func (e serviceNotFoundError) NotFound() bool {
|
||||||
|
|
||||||
// IsErrServiceNotFound returns true if the error is caused
|
// IsErrServiceNotFound returns true if the error is caused
|
||||||
// when a service is not found.
|
// when a service is not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrServiceNotFound(err error) bool {
|
func IsErrServiceNotFound(err error) bool {
|
||||||
_, ok := err.(serviceNotFoundError)
|
_, ok := err.(serviceNotFoundError)
|
||||||
return ok
|
return ok
|
||||||
|
@ -205,6 +217,8 @@ func (e taskNotFoundError) NotFound() bool {
|
||||||
|
|
||||||
// IsErrTaskNotFound returns true if the error is caused
|
// IsErrTaskNotFound returns true if the error is caused
|
||||||
// when a task is not found.
|
// when a task is not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrTaskNotFound(err error) bool {
|
func IsErrTaskNotFound(err error) bool {
|
||||||
_, ok := err.(taskNotFoundError)
|
_, ok := err.(taskNotFoundError)
|
||||||
return ok
|
return ok
|
||||||
|
@ -251,6 +265,8 @@ func (e secretNotFoundError) NotFound() bool {
|
||||||
|
|
||||||
// IsErrSecretNotFound returns true if the error is caused
|
// IsErrSecretNotFound returns true if the error is caused
|
||||||
// when a secret is not found.
|
// when a secret is not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrSecretNotFound(err error) bool {
|
func IsErrSecretNotFound(err error) bool {
|
||||||
_, ok := err.(secretNotFoundError)
|
_, ok := err.(secretNotFoundError)
|
||||||
return ok
|
return ok
|
||||||
|
@ -273,6 +289,8 @@ func (e configNotFoundError) NotFound() bool {
|
||||||
|
|
||||||
// IsErrConfigNotFound returns true if the error is caused
|
// IsErrConfigNotFound returns true if the error is caused
|
||||||
// when a config is not found.
|
// when a config is not found.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrConfigNotFound(err error) bool {
|
func IsErrConfigNotFound(err error) bool {
|
||||||
_, ok := err.(configNotFoundError)
|
_, ok := err.(configNotFoundError)
|
||||||
return ok
|
return ok
|
||||||
|
@ -295,6 +313,8 @@ func (e pluginNotFoundError) Error() string {
|
||||||
|
|
||||||
// IsErrPluginNotFound returns true if the error is caused
|
// IsErrPluginNotFound returns true if the error is caused
|
||||||
// when a plugin is not found in the docker host.
|
// when a plugin is not found in the docker host.
|
||||||
|
//
|
||||||
|
// Deprecated: Use IsErrNotFound
|
||||||
func IsErrPluginNotFound(err error) bool {
|
func IsErrPluginNotFound(err error) bool {
|
||||||
return IsErrNotFound(err)
|
return IsErrNotFound(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
// parse_logs.go contains utility helpers for getting information out of docker
|
|
||||||
// log lines. really, it only contains ParseDetails right now. maybe in the
|
|
||||||
// future there will be some desire to parse log messages back into a struct?
|
|
||||||
// that would go here if we did
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseLogDetails takes a details string of key value pairs in the form
|
|
||||||
// "k=v,l=w", where the keys and values are url query escaped, and each pair
|
|
||||||
// is separated by a comma, returns a map. returns an error if the details
|
|
||||||
// string is not in a valid format
|
|
||||||
// the exact form of details encoding is implemented in
|
|
||||||
// api/server/httputils/write_log_stream.go
|
|
||||||
func ParseLogDetails(details string) (map[string]string, error) {
|
|
||||||
pairs := strings.Split(details, ",")
|
|
||||||
detailsMap := make(map[string]string, len(pairs))
|
|
||||||
for _, pair := range pairs {
|
|
||||||
p := strings.SplitN(pair, "=", 2)
|
|
||||||
// if there is no equals sign, we will only get 1 part back
|
|
||||||
if len(p) != 2 {
|
|
||||||
return nil, errors.New("invalid details format")
|
|
||||||
}
|
|
||||||
k, err := url.QueryUnescape(p[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
v, err := url.QueryUnescape(p[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
detailsMap[k] = v
|
|
||||||
}
|
|
||||||
return detailsMap, nil
|
|
||||||
}
|
|
|
@ -88,13 +88,12 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
|
||||||
|
|
||||||
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
|
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
|
||||||
distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth)
|
distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth)
|
||||||
imageWithDigest := image
|
|
||||||
var platforms []swarm.Platform
|
var platforms []swarm.Platform
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
imageWithDigest = imageWithDigestString(image, distributionInspect.Descriptor.Digest)
|
imageWithDigest := imageWithDigestString(image, distributionInspect.Descriptor.Digest)
|
||||||
|
|
||||||
if len(distributionInspect.Platforms) > 0 {
|
if len(distributionInspect.Platforms) > 0 {
|
||||||
platforms = make([]swarm.Platform, 0, len(distributionInspect.Platforms))
|
platforms = make([]swarm.Platform, 0, len(distributionInspect.Platforms))
|
||||||
|
@ -105,12 +104,12 @@ func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, ima
|
||||||
// something like "armv7l" (includes the variant), which causes arm images
|
// something like "armv7l" (includes the variant), which causes arm images
|
||||||
// to stop working with swarm mode. This patch removes the architecture
|
// to stop working with swarm mode. This patch removes the architecture
|
||||||
// constraint for arm images to ensure tasks get scheduled.
|
// constraint for arm images to ensure tasks get scheduled.
|
||||||
arch := strings.ToLower(p.Architecture)
|
arch := p.Architecture
|
||||||
if arch == "arm" {
|
if strings.ToLower(arch) == "arm" {
|
||||||
arch = ""
|
arch = ""
|
||||||
}
|
}
|
||||||
platforms = append(platforms, swarm.Platform{
|
platforms = append(platforms, swarm.Platform{
|
||||||
Architecture: p.Architecture,
|
Architecture: arch,
|
||||||
OS: p.OS,
|
OS: p.OS,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -18,8 +19,15 @@ func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (types.Vo
|
||||||
|
|
||||||
// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
|
// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
|
||||||
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) {
|
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) {
|
||||||
|
// The empty ID needs to be handled here because with an empty ID the
|
||||||
|
// request url will not contain a trailing / which calls the volume list API
|
||||||
|
// instead of volume inspect
|
||||||
|
if volumeID == "" {
|
||||||
|
return types.Volume{}, nil, volumeNotFoundError{volumeID}
|
||||||
|
}
|
||||||
|
|
||||||
var volume types.Volume
|
var volume types.Volume
|
||||||
resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
|
resp, err := cli.get(ctx, path.Join("/volumes", volumeID), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp.statusCode == http.StatusNotFound {
|
if resp.statusCode == http.StatusNotFound {
|
||||||
return volume, nil, volumeNotFoundError{volumeID}
|
return volume, nil, volumeNotFoundError{volumeID}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# the following lines are in sorted order, FYI
|
# the following lines are in sorted order, FYI
|
||||||
github.com/Azure/go-ansiterm 19f72df4d05d31cbe1c56bfc8045c96babff6c7e
|
github.com/Azure/go-ansiterm 19f72df4d05d31cbe1c56bfc8045c96babff6c7e
|
||||||
github.com/Microsoft/hcsshim v0.6.3
|
github.com/Microsoft/hcsshim v0.6.4
|
||||||
github.com/Microsoft/go-winio v0.4.5
|
github.com/Microsoft/go-winio v0.4.5
|
||||||
github.com/moby/buildkit da2b9dc7dab99e824b2b1067ad7d0523e32dd2d9 https://github.com/dmcgowan/buildkit.git
|
github.com/moby/buildkit da2b9dc7dab99e824b2b1067ad7d0523e32dd2d9 https://github.com/dmcgowan/buildkit.git
|
||||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
|
@ -8,7 +8,7 @@ github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||||
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
|
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
|
||||||
github.com/gorilla/context v1.1
|
github.com/gorilla/context v1.1
|
||||||
github.com/gorilla/mux v1.1
|
github.com/gorilla/mux v1.1
|
||||||
github.com/Microsoft/opengcs v0.3.2
|
github.com/Microsoft/opengcs v0.3.3
|
||||||
github.com/kr/pty 5cf931ef8f
|
github.com/kr/pty 5cf931ef8f
|
||||||
github.com/mattn/go-shellwords v1.0.3
|
github.com/mattn/go-shellwords v1.0.3
|
||||||
github.com/sirupsen/logrus v1.0.1
|
github.com/sirupsen/logrus v1.0.1
|
||||||
|
@ -63,7 +63,7 @@ github.com/pborman/uuid v1.0
|
||||||
google.golang.org/grpc v1.3.0
|
google.golang.org/grpc v1.3.0
|
||||||
|
|
||||||
# When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly
|
# When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly
|
||||||
github.com/opencontainers/runc 3f2f8b84a77f73d38244dd690525642a72156c64
|
github.com/opencontainers/runc 1c81e2a794c6e26a4c650142ae8893c47f619764
|
||||||
github.com/opencontainers/image-spec 372ad780f63454fbbbbcc7cf80e5b90245c13e13
|
github.com/opencontainers/image-spec 372ad780f63454fbbbbcc7cf80e5b90245c13e13
|
||||||
github.com/opencontainers/runtime-spec v1.0.0
|
github.com/opencontainers/runtime-spec v1.0.0
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue