Merge component 'cli' from git@github.com:docker/cli master
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/spf13/cobra"
|
||||
@ -58,6 +59,9 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.Var(&opts.hosts, flagHost, "Set one or more custom host-to-IP mappings (host:ip)")
|
||||
flags.SetAnnotation(flagHost, "version", []string{"1.25"})
|
||||
|
||||
flags.Var(cliopts.NewListOptsRef(&opts.resources.resGenericResources, ValidateSingleGenericResource), "generic-resource", "User defined resources")
|
||||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
|
||||
|
||||
flags.SetInterspersed(false)
|
||||
return cmd
|
||||
}
|
||||
|
||||
105
components/cli/cli/command/service/generic_resource_opts.go
Normal file
105
components/cli/cli/command/service/generic_resource_opts.go
Normal file
@ -0,0 +1,105 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
swarmapi "github.com/docker/swarmkit/api"
|
||||
"github.com/docker/swarmkit/api/genericresource"
|
||||
)
|
||||
|
||||
// GenericResource is a concept that a user can use to advertise user-defined
|
||||
// resources on a node and thus better place services based on these resources.
|
||||
// E.g: NVIDIA GPUs, Intel FPGAs, ...
|
||||
// See https://github.com/docker/swarmkit/blob/master/design/generic_resources.md
|
||||
|
||||
// ValidateSingleGenericResource validates that a single entry in the
|
||||
// generic resource list is valid.
|
||||
// i.e 'GPU=UID1' is valid however 'GPU:UID1' or 'UID1' isn't
|
||||
func ValidateSingleGenericResource(val string) (string, error) {
|
||||
if strings.Count(val, "=") < 1 {
|
||||
return "", fmt.Errorf("invalid generic-resource format `%s` expected `name=value`", val)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ParseGenericResources parses an array of Generic resourceResources
|
||||
// Requesting Named Generic Resources for a service is not supported this
|
||||
// is filtered here.
|
||||
func ParseGenericResources(value []string) ([]swarm.GenericResource, error) {
|
||||
if len(value) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
resources, err := genericresource.Parse(value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid generic resource specification")
|
||||
}
|
||||
|
||||
swarmResources := genericResourcesFromGRPC(resources)
|
||||
for _, res := range swarmResources {
|
||||
if res.NamedResourceSpec != nil {
|
||||
return nil, fmt.Errorf("invalid generic-resource request `%s=%s`, Named Generic Resources is not supported for service create or update", res.NamedResourceSpec.Kind, res.NamedResourceSpec.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return swarmResources, nil
|
||||
}
|
||||
|
||||
// genericResourcesFromGRPC converts a GRPC GenericResource to a GenericResource
|
||||
func genericResourcesFromGRPC(genericRes []*swarmapi.GenericResource) []swarm.GenericResource {
|
||||
var generic []swarm.GenericResource
|
||||
for _, res := range genericRes {
|
||||
var current swarm.GenericResource
|
||||
|
||||
switch r := res.Resource.(type) {
|
||||
case *swarmapi.GenericResource_DiscreteResourceSpec:
|
||||
current.DiscreteResourceSpec = &swarm.DiscreteGenericResource{
|
||||
Kind: r.DiscreteResourceSpec.Kind,
|
||||
Value: r.DiscreteResourceSpec.Value,
|
||||
}
|
||||
case *swarmapi.GenericResource_NamedResourceSpec:
|
||||
current.NamedResourceSpec = &swarm.NamedGenericResource{
|
||||
Kind: r.NamedResourceSpec.Kind,
|
||||
Value: r.NamedResourceSpec.Value,
|
||||
}
|
||||
}
|
||||
|
||||
generic = append(generic, current)
|
||||
}
|
||||
|
||||
return generic
|
||||
}
|
||||
|
||||
func buildGenericResourceMap(genericRes []swarm.GenericResource) (map[string]swarm.GenericResource, error) {
|
||||
m := make(map[string]swarm.GenericResource)
|
||||
|
||||
for _, res := range genericRes {
|
||||
if res.DiscreteResourceSpec == nil {
|
||||
return nil, fmt.Errorf("invalid generic-resource `%+v` for service task", res)
|
||||
}
|
||||
|
||||
_, ok := m[res.DiscreteResourceSpec.Kind]
|
||||
if ok {
|
||||
return nil, fmt.Errorf("duplicate generic-resource `%+v` for service task", res.DiscreteResourceSpec.Kind)
|
||||
}
|
||||
|
||||
m[res.DiscreteResourceSpec.Kind] = res
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func buildGenericResourceList(genericRes map[string]swarm.GenericResource) []swarm.GenericResource {
|
||||
var l []swarm.GenericResource
|
||||
|
||||
for _, res := range genericRes {
|
||||
l = append(l, res)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateSingleGenericResource(t *testing.T) {
|
||||
incorrect := []string{"foo", "fooo-bar"}
|
||||
correct := []string{"foo=bar", "bar=1", "foo=barbar"}
|
||||
|
||||
for _, v := range incorrect {
|
||||
_, err := ValidateSingleGenericResource(v)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
for _, v := range correct {
|
||||
_, err := ValidateSingleGenericResource(v)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
@ -222,23 +222,30 @@ func (opts updateOptions) rollbackConfig(flags *pflag.FlagSet) *swarm.UpdateConf
|
||||
}
|
||||
|
||||
type resourceOptions struct {
|
||||
limitCPU opts.NanoCPUs
|
||||
limitMemBytes opts.MemBytes
|
||||
resCPU opts.NanoCPUs
|
||||
resMemBytes opts.MemBytes
|
||||
limitCPU opts.NanoCPUs
|
||||
limitMemBytes opts.MemBytes
|
||||
resCPU opts.NanoCPUs
|
||||
resMemBytes opts.MemBytes
|
||||
resGenericResources []string
|
||||
}
|
||||
|
||||
func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
|
||||
func (r *resourceOptions) ToResourceRequirements() (*swarm.ResourceRequirements, error) {
|
||||
generic, err := ParseGenericResources(r.resGenericResources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &swarm.ResourceRequirements{
|
||||
Limits: &swarm.Resources{
|
||||
NanoCPUs: r.limitCPU.Value(),
|
||||
MemoryBytes: r.limitMemBytes.Value(),
|
||||
},
|
||||
Reservations: &swarm.Resources{
|
||||
NanoCPUs: r.resCPU.Value(),
|
||||
MemoryBytes: r.resMemBytes.Value(),
|
||||
NanoCPUs: r.resCPU.Value(),
|
||||
MemoryBytes: r.resMemBytes.Value(),
|
||||
GenericResources: generic,
|
||||
},
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
type restartPolicyOptions struct {
|
||||
@ -588,6 +595,11 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
|
||||
return service, err
|
||||
}
|
||||
|
||||
resources, err := options.resources.ToResourceRequirements()
|
||||
if err != nil {
|
||||
return service, err
|
||||
}
|
||||
|
||||
service = swarm.ServiceSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: options.name,
|
||||
@ -619,7 +631,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
|
||||
Isolation: container.Isolation(options.isolation),
|
||||
},
|
||||
Networks: networks,
|
||||
Resources: options.resources.ToResourceRequirements(),
|
||||
Resources: resources,
|
||||
RestartPolicy: options.restartPolicy.ToRestartPolicy(flags),
|
||||
Placement: &swarm.Placement{
|
||||
Constraints: options.constraints.GetAll(),
|
||||
@ -818,6 +830,8 @@ const (
|
||||
flagEnvFile = "env-file"
|
||||
flagEnvRemove = "env-rm"
|
||||
flagEnvAdd = "env-add"
|
||||
flagGenericResourcesRemove = "generic-resource-rm"
|
||||
flagGenericResourcesAdd = "generic-resource-add"
|
||||
flagGroup = "group"
|
||||
flagGroupAdd = "group-add"
|
||||
flagGroupRemove = "group-rm"
|
||||
|
||||
@ -85,3 +85,41 @@ func TestHealthCheckOptionsToHealthConfigConflict(t *testing.T) {
|
||||
_, err := opt.toHealthConfig()
|
||||
assert.EqualError(t, err, "--no-healthcheck conflicts with --health-* options")
|
||||
}
|
||||
|
||||
func TestResourceOptionsToResourceRequirements(t *testing.T) {
|
||||
incorrectOptions := []resourceOptions{
|
||||
{
|
||||
resGenericResources: []string{"foo=bar", "foo=1"},
|
||||
},
|
||||
{
|
||||
resGenericResources: []string{"foo=bar", "foo=baz"},
|
||||
},
|
||||
{
|
||||
resGenericResources: []string{"foo=bar"},
|
||||
},
|
||||
{
|
||||
resGenericResources: []string{"foo=1", "foo=2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range incorrectOptions {
|
||||
_, err := opt.ToResourceRequirements()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
correctOptions := []resourceOptions{
|
||||
{
|
||||
resGenericResources: []string{"foo=1"},
|
||||
},
|
||||
{
|
||||
resGenericResources: []string{"foo=1", "bar=2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range correctOptions {
|
||||
r, err := opt.ToResourceRequirements()
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, r.Reservations.GenericResources, len(opt.resGenericResources))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -95,6 +95,12 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.Var(&options.hosts, flagHostAdd, "Add a custom host-to-IP mapping (host:ip)")
|
||||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
|
||||
|
||||
// Add needs parsing, Remove only needs the key
|
||||
flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource")
|
||||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
|
||||
flags.Var(newListOptsVarWithValidator(ValidateSingleGenericResource), flagGenericResourcesAdd, "Add a Generic resource")
|
||||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -102,6 +108,10 @@ func newListOptsVar() *opts.ListOpts {
|
||||
return opts.NewListOptsRef(&[]string{}, nil)
|
||||
}
|
||||
|
||||
func newListOptsVarWithValidator(validator opts.ValidatorFctType) *opts.ListOpts {
|
||||
return opts.NewListOptsRef(&[]string{}, validator)
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
|
||||
apiClient := dockerCli.Client()
|
||||
@ -314,6 +324,14 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
|
||||
updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
|
||||
}
|
||||
|
||||
if err := addGenericResources(flags, task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := removeGenericResources(flags, task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod)
|
||||
|
||||
if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
|
||||
@ -470,6 +488,72 @@ func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func addGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
|
||||
if !flags.Changed(flagGenericResourcesAdd) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if spec.Resources == nil {
|
||||
spec.Resources = &swarm.ResourceRequirements{}
|
||||
}
|
||||
|
||||
if spec.Resources.Reservations == nil {
|
||||
spec.Resources.Reservations = &swarm.Resources{}
|
||||
}
|
||||
|
||||
values := flags.Lookup(flagGenericResourcesAdd).Value.(*opts.ListOpts).GetAll()
|
||||
generic, err := ParseGenericResources(values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, toAddRes := range generic {
|
||||
m[toAddRes.DiscreteResourceSpec.Kind] = toAddRes
|
||||
}
|
||||
|
||||
spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
|
||||
// Can only be Discrete Resources
|
||||
if !flags.Changed(flagGenericResourcesRemove) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if spec.Resources == nil {
|
||||
spec.Resources = &swarm.ResourceRequirements{}
|
||||
}
|
||||
|
||||
if spec.Resources.Reservations == nil {
|
||||
spec.Resources.Reservations = &swarm.Resources{}
|
||||
}
|
||||
|
||||
values := flags.Lookup(flagGenericResourcesRemove).Value.(*opts.ListOpts).GetAll()
|
||||
|
||||
m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, toRemoveRes := range values {
|
||||
if _, ok := m[toRemoveRes]; !ok {
|
||||
return fmt.Errorf("could not find generic-resource `%s` to remove it", toRemoveRes)
|
||||
}
|
||||
|
||||
delete(m, toRemoveRes)
|
||||
}
|
||||
|
||||
spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePlacementConstraints(flags *pflag.FlagSet, placement *swarm.Placement) {
|
||||
if flags.Changed(flagConstraintAdd) {
|
||||
values := flags.Lookup(flagConstraintAdd).Value.(*opts.ListOpts).GetAll()
|
||||
|
||||
@ -547,3 +547,42 @@ func TestUpdateIsolationInvalid(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, container.Isolation("test"), spec.TaskTemplate.ContainerSpec.Isolation)
|
||||
}
|
||||
|
||||
func TestAddGenericResources(t *testing.T) {
|
||||
task := &swarm.TaskSpec{}
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
|
||||
assert.Nil(t, addGenericResources(flags, task))
|
||||
|
||||
flags.Set(flagGenericResourcesAdd, "foo=1")
|
||||
assert.NoError(t, addGenericResources(flags, task))
|
||||
assert.Len(t, task.Resources.Reservations.GenericResources, 1)
|
||||
|
||||
// Checks that foo isn't added a 2nd time
|
||||
flags = newUpdateCommand(nil).Flags()
|
||||
flags.Set(flagGenericResourcesAdd, "bar=1")
|
||||
assert.NoError(t, addGenericResources(flags, task))
|
||||
assert.Len(t, task.Resources.Reservations.GenericResources, 2)
|
||||
}
|
||||
|
||||
func TestRemoveGenericResources(t *testing.T) {
|
||||
task := &swarm.TaskSpec{}
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
|
||||
assert.Nil(t, removeGenericResources(flags, task))
|
||||
|
||||
flags.Set(flagGenericResourcesRemove, "foo")
|
||||
assert.Error(t, removeGenericResources(flags, task))
|
||||
|
||||
flags = newUpdateCommand(nil).Flags()
|
||||
flags.Set(flagGenericResourcesAdd, "foo=1")
|
||||
addGenericResources(flags, task)
|
||||
flags = newUpdateCommand(nil).Flags()
|
||||
flags.Set(flagGenericResourcesAdd, "bar=1")
|
||||
addGenericResources(flags, task)
|
||||
|
||||
flags = newUpdateCommand(nil).Flags()
|
||||
flags.Set(flagGenericResourcesRemove, "foo")
|
||||
assert.NoError(t, removeGenericResources(flags, task))
|
||||
assert.Len(t, task.Resources.Reservations.GenericResources, 1)
|
||||
}
|
||||
|
||||
@ -192,7 +192,24 @@ func (e EmptyTargetsNotaryRepository) GetAllTargetMetadataByName(name string) ([
|
||||
}
|
||||
|
||||
func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
|
||||
return []client.RoleWithSignatures{}, nil
|
||||
rootRole := data.Role{
|
||||
RootRole: data.RootRole{
|
||||
KeyIDs: []string{"rootID"},
|
||||
Threshold: 1,
|
||||
},
|
||||
Name: data.CanonicalRootRole,
|
||||
}
|
||||
|
||||
targetsRole := data.Role{
|
||||
RootRole: data.RootRole{
|
||||
KeyIDs: []string{"targetsID"},
|
||||
Threshold: 1,
|
||||
},
|
||||
Name: data.CanonicalTargetsRole,
|
||||
}
|
||||
return []client.RoleWithSignatures{
|
||||
{Role: rootRole},
|
||||
{Role: targetsRole}}, nil
|
||||
}
|
||||
|
||||
func (e EmptyTargetsNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
|
||||
|
||||
@ -20,6 +20,7 @@ func NewTrustCommand(dockerCli command.Cli) *cobra.Command {
|
||||
newSignCommand(dockerCli),
|
||||
newTrustKeyCommand(dockerCli),
|
||||
newTrustSignerCommand(dockerCli),
|
||||
newInspectCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
167
components/cli/cli/command/trust/common.go
Normal file
167
components/cli/cli/command/trust/common.go
Normal file
@ -0,0 +1,167 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/image"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/theupdateframework/notary"
|
||||
"github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
)
|
||||
|
||||
// trustTagKey represents a unique signed tag and hex-encoded hash pair
|
||||
type trustTagKey struct {
|
||||
SignedTag string
|
||||
Digest string
|
||||
}
|
||||
|
||||
// trustTagRow encodes all human-consumable information for a signed tag, including signers
|
||||
type trustTagRow struct {
|
||||
trustTagKey
|
||||
Signers []string
|
||||
}
|
||||
|
||||
type trustTagRowList []trustTagRow
|
||||
|
||||
func (tagComparator trustTagRowList) Len() int {
|
||||
return len(tagComparator)
|
||||
}
|
||||
|
||||
func (tagComparator trustTagRowList) Less(i, j int) bool {
|
||||
return tagComparator[i].SignedTag < tagComparator[j].SignedTag
|
||||
}
|
||||
|
||||
func (tagComparator trustTagRowList) Swap(i, j int) {
|
||||
tagComparator[i], tagComparator[j] = tagComparator[j], tagComparator[i]
|
||||
}
|
||||
|
||||
// trustRepo represents consumable information about a trusted repository
|
||||
type trustRepo struct {
|
||||
Name string
|
||||
SignedTags trustTagRowList
|
||||
Signers []trustSigner
|
||||
AdminstrativeKeys []trustSigner
|
||||
}
|
||||
|
||||
// trustSigner represents a trusted signer in a trusted repository
|
||||
// a signer is defined by a name and list of trustKeys
|
||||
type trustSigner struct {
|
||||
Name string `json:",omitempty"`
|
||||
Keys []trustKey `json:",omitempty"`
|
||||
}
|
||||
|
||||
// trustKey contains information about trusted keys
|
||||
type trustKey struct {
|
||||
ID string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// lookupTrustInfo returns processed signature and role information about a notary repository.
|
||||
// This information is to be pretty printed or serialized into a machine-readable format.
|
||||
func lookupTrustInfo(cli command.Cli, remote string) (trustTagRowList, []client.RoleWithSignatures, []data.Role, error) {
|
||||
ctx := context.Background()
|
||||
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote)
|
||||
if err != nil {
|
||||
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, err
|
||||
}
|
||||
tag := imgRefAndAuth.Tag()
|
||||
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
|
||||
if err != nil {
|
||||
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
|
||||
}
|
||||
|
||||
if err = clearChangeList(notaryRepo); err != nil {
|
||||
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, err
|
||||
}
|
||||
defer clearChangeList(notaryRepo)
|
||||
|
||||
// Retrieve all released signatures, match them, and pretty print them
|
||||
allSignedTargets, err := notaryRepo.GetAllTargetMetadataByName(tag)
|
||||
if err != nil {
|
||||
logrus.Debug(trust.NotaryError(remote, err))
|
||||
// print an empty table if we don't have signed targets, but have an initialized notary repo
|
||||
if _, ok := err.(client.ErrNoSuchTarget); !ok {
|
||||
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signatures or cannot access %s", remote)
|
||||
}
|
||||
}
|
||||
signatureRows := matchReleasedSignatures(allSignedTargets)
|
||||
|
||||
// get the administrative roles
|
||||
adminRolesWithSigs, err := notaryRepo.ListRoles()
|
||||
if err != nil {
|
||||
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signers for %s", remote)
|
||||
}
|
||||
|
||||
// get delegation roles with the canonical key IDs
|
||||
delegationRoles, err := notaryRepo.GetDelegationRoles()
|
||||
if err != nil {
|
||||
logrus.Debugf("no delegation roles found, or error fetching them for %s: %v", remote, err)
|
||||
}
|
||||
|
||||
return signatureRows, adminRolesWithSigs, delegationRoles, nil
|
||||
}
|
||||
|
||||
func formatAdminRole(roleWithSigs client.RoleWithSignatures) string {
|
||||
adminKeyList := roleWithSigs.KeyIDs
|
||||
sort.Strings(adminKeyList)
|
||||
|
||||
var role string
|
||||
switch roleWithSigs.Name {
|
||||
case data.CanonicalTargetsRole:
|
||||
role = "Repository Key"
|
||||
case data.CanonicalRootRole:
|
||||
role = "Root Key"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s:\t%s\n", role, strings.Join(adminKeyList, ", "))
|
||||
}
|
||||
|
||||
func getDelegationRoleToKeyMap(rawDelegationRoles []data.Role) map[string][]string {
|
||||
signerRoleToKeyIDs := make(map[string][]string)
|
||||
for _, delRole := range rawDelegationRoles {
|
||||
switch delRole.Name {
|
||||
case trust.ReleasesRole, data.CanonicalRootRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole, data.CanonicalTimestampRole:
|
||||
continue
|
||||
default:
|
||||
signerRoleToKeyIDs[notaryRoleToSigner(delRole.Name)] = delRole.KeyIDs
|
||||
}
|
||||
}
|
||||
return signerRoleToKeyIDs
|
||||
}
|
||||
|
||||
// aggregate all signers for a "released" hash+tagname pair. To be "released," the tag must have been
|
||||
// signed into the "targets" or "targets/releases" role. Output is sorted by tag name
|
||||
func matchReleasedSignatures(allTargets []client.TargetSignedStruct) trustTagRowList {
|
||||
signatureRows := trustTagRowList{}
|
||||
// do a first pass to get filter on tags signed into "targets" or "targets/releases"
|
||||
releasedTargetRows := map[trustTagKey][]string{}
|
||||
for _, tgt := range allTargets {
|
||||
if isReleasedTarget(tgt.Role.Name) {
|
||||
releasedKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
|
||||
releasedTargetRows[releasedKey] = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// now fill out all signers on released keys
|
||||
for _, tgt := range allTargets {
|
||||
targetKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
|
||||
// only considered released targets
|
||||
if _, ok := releasedTargetRows[targetKey]; ok && !isReleasedTarget(tgt.Role.Name) {
|
||||
releasedTargetRows[targetKey] = append(releasedTargetRows[targetKey], notaryRoleToSigner(tgt.Role.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// compile the final output as a sorted slice
|
||||
for targetKey, signers := range releasedTargetRows {
|
||||
signatureRows = append(signatureRows, trustTagRow{targetKey, signers})
|
||||
}
|
||||
sort.Sort(signatureRows)
|
||||
return signatureRows
|
||||
}
|
||||
83
components/cli/cli/command/trust/inspect.go
Normal file
83
components/cli/cli/command/trust/inspect.go
Normal file
@ -0,0 +1,83 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
)
|
||||
|
||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect IMAGE[:TAG] [IMAGE[:TAG]...]",
|
||||
Short: "Return low-level information about keys and signatures",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runInspect(dockerCli, args)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, remotes []string) error {
|
||||
getRefFunc := func(ref string) (interface{}, []byte, error) {
|
||||
i, err := getRepoTrustInfo(dockerCli, ref)
|
||||
return nil, i, err
|
||||
}
|
||||
return inspect.Inspect(dockerCli.Out(), remotes, "", getRefFunc)
|
||||
}
|
||||
|
||||
func getRepoTrustInfo(cli command.Cli, remote string) ([]byte, error) {
|
||||
signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(cli, remote)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
// process the signatures to include repo admin if signed by the base targets role
|
||||
for idx, sig := range signatureRows {
|
||||
if len(sig.Signers) == 0 {
|
||||
signatureRows[idx].Signers = append(sig.Signers, releasedRoleName)
|
||||
}
|
||||
}
|
||||
|
||||
signerList, adminList := []trustSigner{}, []trustSigner{}
|
||||
|
||||
signerRoleToKeyIDs := getDelegationRoleToKeyMap(delegationRoles)
|
||||
|
||||
for signerName, signerKeys := range signerRoleToKeyIDs {
|
||||
signerKeyList := []trustKey{}
|
||||
for _, keyID := range signerKeys {
|
||||
signerKeyList = append(signerKeyList, trustKey{ID: keyID})
|
||||
}
|
||||
signerList = append(signerList, trustSigner{signerName, signerKeyList})
|
||||
}
|
||||
sort.Slice(signerList, func(i, j int) bool { return signerList[i].Name > signerList[j].Name })
|
||||
|
||||
for _, adminRole := range adminRolesWithSigs {
|
||||
switch adminRole.Name {
|
||||
case data.CanonicalRootRole:
|
||||
rootKeys := []trustKey{}
|
||||
for _, keyID := range adminRole.KeyIDs {
|
||||
rootKeys = append(rootKeys, trustKey{ID: keyID})
|
||||
}
|
||||
adminList = append(adminList, trustSigner{"Root", rootKeys})
|
||||
case data.CanonicalTargetsRole:
|
||||
targetKeys := []trustKey{}
|
||||
for _, keyID := range adminRole.KeyIDs {
|
||||
targetKeys = append(targetKeys, trustKey{ID: keyID})
|
||||
}
|
||||
adminList = append(adminList, trustSigner{"Repository", targetKeys})
|
||||
}
|
||||
}
|
||||
sort.Slice(adminList, func(i, j int) bool { return adminList[i].Name > adminList[j].Name })
|
||||
|
||||
return json.Marshal(trustRepo{
|
||||
Name: remote,
|
||||
SignedTags: signatureRows,
|
||||
Signers: signerList,
|
||||
AdminstrativeKeys: adminList,
|
||||
})
|
||||
}
|
||||
135
components/cli/cli/command/trust/inspect_test.go
Normal file
135
components/cli/cli/command/trust/inspect_test.go
Normal file
@ -0,0 +1,135 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/gotestyourself/gotestyourself/golden"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTrustInspectCommandErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "not-enough-args",
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
name: "too-many-args",
|
||||
args: []string{"remote1", "remote2"},
|
||||
expectedError: "requires exactly 1 argument",
|
||||
},
|
||||
{
|
||||
name: "sha-reference",
|
||||
args: []string{"870d292919d01a0af7e7f056271dc78792c05f55f49b9b9012b6d89725bd9abd"},
|
||||
expectedError: "invalid repository name",
|
||||
},
|
||||
{
|
||||
name: "invalid-img-reference",
|
||||
args: []string{"ALPINE"},
|
||||
expectedError: "invalid reference format",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
cmd := newViewCommand(
|
||||
test.NewFakeCli(&fakeClient{}))
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandOfflineErrors(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getOfflineNotaryRepository)
|
||||
cmd := newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"nonexistent-reg-name.io/image"})
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image")
|
||||
|
||||
cli = test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getOfflineNotaryRepository)
|
||||
cmd = newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"})
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandUninitializedErrors(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getUninitializedNotaryRepository)
|
||||
cmd := newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"reg/unsigned-img"})
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img")
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-uninitialized.golden")
|
||||
|
||||
cli = test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getUninitializedNotaryRepository)
|
||||
cmd = newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"reg/unsigned-img:tag"})
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img:tag")
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-uninitialized.golden")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandEmptyNotaryRepo(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getEmptyTargetsNotaryRepository)
|
||||
cmd := newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"reg/img:unsigned-tag"})
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
assert.NoError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-empty-repo.golden")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository)
|
||||
cmd := newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"signed-repo"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-full-repo-no-signers.golden")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository)
|
||||
cmd := newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"signed-repo:green"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-one-tag-no-signers.golden")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getLoadedNotaryRepository)
|
||||
cmd := newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"signed-repo"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-full-repo-with-signers.golden")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandMultipleFullReposWithSigners(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getLoadedNotaryRepository)
|
||||
cmd := newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"signed-repo", "signed-repo"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-multiple-repos-with-signers.golden")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getLoadedNotaryRepository)
|
||||
cmd := newInspectCommand(cli)
|
||||
cmd.SetArgs([]string{"signed-repo:unsigned"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-unsigned-tag-with-signers.golden")
|
||||
}
|
||||
@ -176,7 +176,7 @@ func getExistingSignatureInfoForReleasedTag(notaryRepo client.Repository, tag st
|
||||
func prettyPrintExistingSignatureInfo(out io.Writer, existingSigInfo trustTagRow) {
|
||||
sort.Strings(existingSigInfo.Signers)
|
||||
joinedSigners := strings.Join(existingSigInfo.Signers, ", ")
|
||||
fmt.Fprintf(out, "Existing signatures for tag %s digest %s from:\n%s\n", existingSigInfo.TagName, existingSigInfo.HashHex, joinedSigners)
|
||||
fmt.Fprintf(out, "Existing signatures for tag %s digest %s from:\n%s\n", existingSigInfo.SignedTag, existingSigInfo.Digest, joinedSigners)
|
||||
}
|
||||
|
||||
func initNotaryRepoWithSigners(notaryRepo client.Repository, newSigner data.RoleName) error {
|
||||
|
||||
25
components/cli/cli/command/trust/testdata/trust-inspect-empty-repo.golden
vendored
Normal file
25
components/cli/cli/command/trust/testdata/trust-inspect-empty-repo.golden
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
[
|
||||
{
|
||||
"Name": "reg/img:unsigned-tag",
|
||||
"SignedTags": [],
|
||||
"Signers": [],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "rootID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "targetsID"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -1,6 +1,33 @@
|
||||
SIGNED TAG DIGEST SIGNERS
|
||||
green 677265656e2d646967657374 (Repo Admin)
|
||||
|
||||
Administrative keys for signed-repo:
|
||||
Repository Key: targetsID
|
||||
Root Key: rootID
|
||||
[
|
||||
{
|
||||
"Name": "signed-repo",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "green",
|
||||
"Digest": "677265656e2d646967657374",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "rootID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "targetsID"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@ -1,14 +1,65 @@
|
||||
SIGNED TAG DIGEST SIGNERS
|
||||
blue 626c75652d646967657374 alice
|
||||
green 677265656e2d646967657374 (Repo Admin)
|
||||
red 7265642d646967657374 alice, bob
|
||||
|
||||
List of signers and their keys for signed-repo:
|
||||
|
||||
SIGNER KEYS
|
||||
alice A
|
||||
bob B
|
||||
|
||||
Administrative keys for signed-repo:
|
||||
Repository Key: targetsID
|
||||
Root Key: rootID
|
||||
[
|
||||
{
|
||||
"Name": "signed-repo",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "blue",
|
||||
"Digest": "626c75652d646967657374",
|
||||
"Signers": [
|
||||
"alice"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "green",
|
||||
"Digest": "677265656e2d646967657374",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "red",
|
||||
"Digest": "7265642d646967657374",
|
||||
"Signers": [
|
||||
"alice",
|
||||
"bob"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [
|
||||
{
|
||||
"Name": "bob",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "B"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "alice",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "A"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "rootID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "targetsID"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
128
components/cli/cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden
vendored
Normal file
128
components/cli/cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
[
|
||||
{
|
||||
"Name": "signed-repo",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "blue",
|
||||
"Digest": "626c75652d646967657374",
|
||||
"Signers": [
|
||||
"alice"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "green",
|
||||
"Digest": "677265656e2d646967657374",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "red",
|
||||
"Digest": "7265642d646967657374",
|
||||
"Signers": [
|
||||
"alice",
|
||||
"bob"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [
|
||||
{
|
||||
"Name": "bob",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "B"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "alice",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "A"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "rootID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "targetsID"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "signed-repo",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "blue",
|
||||
"Digest": "626c75652d646967657374",
|
||||
"Signers": [
|
||||
"alice"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "green",
|
||||
"Digest": "677265656e2d646967657374",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "red",
|
||||
"Digest": "7265642d646967657374",
|
||||
"Signers": [
|
||||
"alice",
|
||||
"bob"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [
|
||||
{
|
||||
"Name": "bob",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "B"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "alice",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "A"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "rootID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "targetsID"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -1,6 +1,33 @@
|
||||
SIGNED TAG DIGEST SIGNERS
|
||||
green 677265656e2d646967657374 (Repo Admin)
|
||||
|
||||
Administrative keys for signed-repo:
|
||||
Repository Key: targetsID
|
||||
Root Key: rootID
|
||||
[
|
||||
{
|
||||
"Name": "signed-repo:green",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "green",
|
||||
"Digest": "677265656e2d646967657374",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "rootID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "targetsID"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
1
components/cli/cli/command/trust/testdata/trust-inspect-uninitialized.golden
vendored
Normal file
1
components/cli/cli/command/trust/testdata/trust-inspect-uninitialized.golden
vendored
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
@ -1,13 +1,42 @@
|
||||
|
||||
No signatures for signed-repo:unsigned
|
||||
|
||||
|
||||
List of signers and their keys for signed-repo:
|
||||
|
||||
SIGNER KEYS
|
||||
alice A
|
||||
bob B
|
||||
|
||||
Administrative keys for signed-repo:
|
||||
Repository Key: targetsID
|
||||
Root Key: rootID
|
||||
[
|
||||
{
|
||||
"Name": "signed-repo:unsigned",
|
||||
"SignedTags": [],
|
||||
"Signers": [
|
||||
{
|
||||
"Name": "bob",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "B"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "alice",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "A"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "rootID"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "targetsID"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
6
components/cli/cli/command/trust/testdata/trust-view-full-repo-no-signers.golden
vendored
Normal file
6
components/cli/cli/command/trust/testdata/trust-view-full-repo-no-signers.golden
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
SIGNED TAG DIGEST SIGNERS
|
||||
green 677265656e2d646967657374 (Repo Admin)
|
||||
|
||||
Administrative keys for signed-repo:
|
||||
Repository Key: targetsID
|
||||
Root Key: rootID
|
||||
14
components/cli/cli/command/trust/testdata/trust-view-full-repo-with-signers.golden
vendored
Normal file
14
components/cli/cli/command/trust/testdata/trust-view-full-repo-with-signers.golden
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
SIGNED TAG DIGEST SIGNERS
|
||||
blue 626c75652d646967657374 alice
|
||||
green 677265656e2d646967657374 (Repo Admin)
|
||||
red 7265642d646967657374 alice, bob
|
||||
|
||||
List of signers and their keys for signed-repo:
|
||||
|
||||
SIGNER KEYS
|
||||
alice A
|
||||
bob B
|
||||
|
||||
Administrative keys for signed-repo:
|
||||
Repository Key: targetsID
|
||||
Root Key: rootID
|
||||
6
components/cli/cli/command/trust/testdata/trust-view-one-tag-no-signers.golden
vendored
Normal file
6
components/cli/cli/command/trust/testdata/trust-view-one-tag-no-signers.golden
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
SIGNED TAG DIGEST SIGNERS
|
||||
green 677265656e2d646967657374 (Repo Admin)
|
||||
|
||||
Administrative keys for signed-repo:
|
||||
Repository Key: targetsID
|
||||
Root Key: rootID
|
||||
13
components/cli/cli/command/trust/testdata/trust-view-unsigned-tag-with-signers.golden
vendored
Normal file
13
components/cli/cli/command/trust/testdata/trust-view-unsigned-tag-with-signers.golden
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
No signatures for signed-repo:unsigned
|
||||
|
||||
|
||||
List of signers and their keys for signed-repo:
|
||||
|
||||
SIGNER KEYS
|
||||
alice A
|
||||
bob B
|
||||
|
||||
Administrative keys for signed-repo:
|
||||
Repository Key: targetsID
|
||||
Root Key: rootID
|
||||
@ -1,8 +1,6 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
@ -11,80 +9,28 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/image"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/theupdateframework/notary"
|
||||
"github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
)
|
||||
|
||||
// trustTagKey represents a unique signed tag and hex-encoded hash pair
|
||||
type trustTagKey struct {
|
||||
TagName string
|
||||
HashHex string
|
||||
}
|
||||
|
||||
// trustTagRow encodes all human-consumable information for a signed tag, including signers
|
||||
type trustTagRow struct {
|
||||
trustTagKey
|
||||
Signers []string
|
||||
}
|
||||
|
||||
type trustTagRowList []trustTagRow
|
||||
|
||||
func (tagComparator trustTagRowList) Len() int {
|
||||
return len(tagComparator)
|
||||
}
|
||||
|
||||
func (tagComparator trustTagRowList) Less(i, j int) bool {
|
||||
return tagComparator[i].TagName < tagComparator[j].TagName
|
||||
}
|
||||
|
||||
func (tagComparator trustTagRowList) Swap(i, j int) {
|
||||
tagComparator[i], tagComparator[j] = tagComparator[j], tagComparator[i]
|
||||
}
|
||||
|
||||
func newViewCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "view IMAGE[:TAG]",
|
||||
Short: "Display detailed information about keys and signatures",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return lookupTrustInfo(dockerCli, args[0])
|
||||
return viewTrustInfo(dockerCli, args[0])
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func lookupTrustInfo(cli command.Cli, remote string) error {
|
||||
ctx := context.Background()
|
||||
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote)
|
||||
func viewTrustInfo(cli command.Cli, remote string) error {
|
||||
signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(cli, remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tag := imgRefAndAuth.Tag()
|
||||
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
|
||||
if err != nil {
|
||||
return trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
|
||||
}
|
||||
|
||||
if err = clearChangeList(notaryRepo); err != nil {
|
||||
return err
|
||||
}
|
||||
defer clearChangeList(notaryRepo)
|
||||
|
||||
// Retrieve all released signatures, match them, and pretty print them
|
||||
allSignedTargets, err := notaryRepo.GetAllTargetMetadataByName(tag)
|
||||
if err != nil {
|
||||
logrus.Debug(trust.NotaryError(imgRefAndAuth.Reference().Name(), err))
|
||||
// print an empty table if we don't have signed targets, but have an initialized notary repo
|
||||
if _, ok := err.(client.ErrNoSuchTarget); !ok {
|
||||
return fmt.Errorf("No signatures or cannot access %s", remote)
|
||||
}
|
||||
}
|
||||
signatureRows := matchReleasedSignatures(allSignedTargets)
|
||||
if len(signatureRows) > 0 {
|
||||
if err := printSignatures(cli.Out(), signatureRows); err != nil {
|
||||
return err
|
||||
@ -92,18 +38,6 @@ func lookupTrustInfo(cli command.Cli, remote string) error {
|
||||
} else {
|
||||
fmt.Fprintf(cli.Out(), "\nNo signatures for %s\n\n", remote)
|
||||
}
|
||||
|
||||
// get the administrative roles
|
||||
adminRolesWithSigs, err := notaryRepo.ListRoles()
|
||||
if err != nil {
|
||||
return fmt.Errorf("No signers for %s", remote)
|
||||
}
|
||||
|
||||
// get delegation roles with the canonical key IDs
|
||||
delegationRoles, err := notaryRepo.GetDelegationRoles()
|
||||
if err != nil {
|
||||
logrus.Debugf("no delegation roles found, or error fetching them for %s: %v", remote, err)
|
||||
}
|
||||
signerRoleToKeyIDs := getDelegationRoleToKeyMap(delegationRoles)
|
||||
|
||||
// If we do not have additional signers, do not display
|
||||
@ -117,7 +51,6 @@ func lookupTrustInfo(cli command.Cli, remote string) error {
|
||||
// This will always have the root and targets information
|
||||
fmt.Fprintf(cli.Out(), "\nAdministrative keys for %s:\n", strings.Split(remote, ":")[0])
|
||||
printSortedAdminKeys(cli.Out(), adminRolesWithSigs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -128,65 +61,6 @@ func printSortedAdminKeys(out io.Writer, adminRoles []client.RoleWithSignatures)
|
||||
}
|
||||
}
|
||||
|
||||
func formatAdminRole(roleWithSigs client.RoleWithSignatures) string {
|
||||
adminKeyList := roleWithSigs.KeyIDs
|
||||
sort.Strings(adminKeyList)
|
||||
|
||||
var role string
|
||||
switch roleWithSigs.Name {
|
||||
case data.CanonicalTargetsRole:
|
||||
role = "Repository Key"
|
||||
case data.CanonicalRootRole:
|
||||
role = "Root Key"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s:\t%s\n", role, strings.Join(adminKeyList, ", "))
|
||||
}
|
||||
|
||||
func getDelegationRoleToKeyMap(rawDelegationRoles []data.Role) map[string][]string {
|
||||
signerRoleToKeyIDs := make(map[string][]string)
|
||||
for _, delRole := range rawDelegationRoles {
|
||||
switch delRole.Name {
|
||||
case trust.ReleasesRole, data.CanonicalRootRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole, data.CanonicalTimestampRole:
|
||||
continue
|
||||
default:
|
||||
signerRoleToKeyIDs[notaryRoleToSigner(delRole.Name)] = delRole.KeyIDs
|
||||
}
|
||||
}
|
||||
return signerRoleToKeyIDs
|
||||
}
|
||||
|
||||
// aggregate all signers for a "released" hash+tagname pair. To be "released," the tag must have been
|
||||
// signed into the "targets" or "targets/releases" role. Output is sorted by tag name
|
||||
func matchReleasedSignatures(allTargets []client.TargetSignedStruct) trustTagRowList {
|
||||
signatureRows := trustTagRowList{}
|
||||
// do a first pass to get filter on tags signed into "targets" or "targets/releases"
|
||||
releasedTargetRows := map[trustTagKey][]string{}
|
||||
for _, tgt := range allTargets {
|
||||
if isReleasedTarget(tgt.Role.Name) {
|
||||
releasedKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
|
||||
releasedTargetRows[releasedKey] = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// now fill out all signers on released keys
|
||||
for _, tgt := range allTargets {
|
||||
targetKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
|
||||
// only considered released targets
|
||||
if _, ok := releasedTargetRows[targetKey]; ok && !isReleasedTarget(tgt.Role.Name) {
|
||||
releasedTargetRows[targetKey] = append(releasedTargetRows[targetKey], notaryRoleToSigner(tgt.Role.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// compile the final output as a sorted slice
|
||||
for targetKey, signers := range releasedTargetRows {
|
||||
signatureRows = append(signatureRows, trustTagRow{targetKey, signers})
|
||||
}
|
||||
sort.Sort(signatureRows)
|
||||
return signatureRows
|
||||
}
|
||||
|
||||
// pretty print with ordered rows
|
||||
func printSignatures(out io.Writer, signatureRows trustTagRowList) error {
|
||||
trustTagCtx := formatter.Context{
|
||||
@ -201,8 +75,8 @@ func printSignatures(out io.Writer, signatureRows trustTagRowList) error {
|
||||
formattedSigners = append(formattedSigners, fmt.Sprintf("(%s)", releasedRoleName))
|
||||
}
|
||||
formattedTags = append(formattedTags, formatter.SignedTagInfo{
|
||||
Name: sigRow.TagName,
|
||||
Digest: sigRow.HashHex,
|
||||
Name: sigRow.SignedTag,
|
||||
Digest: sigRow.Digest,
|
||||
Signers: formattedSigners,
|
||||
})
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ type fakeClient struct {
|
||||
dockerClient.Client
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandErrors(t *testing.T) {
|
||||
func TestTrustViewCommandErrors(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
@ -55,7 +55,7 @@ func TestTrustInspectCommandErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandOfflineErrors(t *testing.T) {
|
||||
func TestTrustViewCommandOfflineErrors(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getOfflineNotaryRepository)
|
||||
cmd := newViewCommand(cli)
|
||||
@ -71,7 +71,7 @@ func TestTrustInspectCommandOfflineErrors(t *testing.T) {
|
||||
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandUninitializedErrors(t *testing.T) {
|
||||
func TestTrustViewCommandUninitializedErrors(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getUninitializedNotaryRepository)
|
||||
cmd := newViewCommand(cli)
|
||||
@ -87,7 +87,7 @@ func TestTrustInspectCommandUninitializedErrors(t *testing.T) {
|
||||
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img:tag")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandEmptyNotaryRepoErrors(t *testing.T) {
|
||||
func TestTrustViewCommandEmptyNotaryRepoErrors(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getEmptyTargetsNotaryRepository)
|
||||
cmd := newViewCommand(cli)
|
||||
@ -107,44 +107,44 @@ func TestTrustInspectCommandEmptyNotaryRepoErrors(t *testing.T) {
|
||||
assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for reg/img:")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) {
|
||||
func TestTrustViewCommandFullRepoWithoutSigners(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository)
|
||||
cmd := newViewCommand(cli)
|
||||
cmd.SetArgs([]string{"signed-repo"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-full-repo-no-signers.golden")
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-view-full-repo-no-signers.golden")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) {
|
||||
func TestTrustViewCommandOneTagWithoutSigners(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository)
|
||||
cmd := newViewCommand(cli)
|
||||
cmd.SetArgs([]string{"signed-repo:green"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-one-tag-no-signers.golden")
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-view-one-tag-no-signers.golden")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) {
|
||||
func TestTrustViewCommandFullRepoWithSigners(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getLoadedNotaryRepository)
|
||||
cmd := newViewCommand(cli)
|
||||
cmd.SetArgs([]string{"signed-repo"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-full-repo-with-signers.golden")
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-view-full-repo-with-signers.golden")
|
||||
}
|
||||
|
||||
func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) {
|
||||
func TestTrustViewCommandUnsignedTagInSignedRepo(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cli.SetNotaryClient(getLoadedNotaryRepository)
|
||||
cmd := newViewCommand(cli)
|
||||
cmd.SetArgs([]string{"signed-repo:unsigned"})
|
||||
assert.NoError(t, cmd.Execute())
|
||||
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-unsigned-tag-with-signers.golden")
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-view-unsigned-tag-with-signers.golden")
|
||||
}
|
||||
|
||||
func TestNotaryRoleToSigner(t *testing.T) {
|
||||
@ -224,8 +224,8 @@ func TestMatchOneReleasedSingleSignature(t *testing.T) {
|
||||
outputRow := matchedSigRows[0]
|
||||
// Empty signers because "targets/releases" doesn't show up
|
||||
assert.Empty(t, outputRow.Signers)
|
||||
assert.Equal(t, releasedTgt.Name, outputRow.TagName)
|
||||
assert.Equal(t, hex.EncodeToString(releasedTgt.Hashes[notary.SHA256]), outputRow.HashHex)
|
||||
assert.Equal(t, releasedTgt.Name, outputRow.SignedTag)
|
||||
assert.Equal(t, hex.EncodeToString(releasedTgt.Hashes[notary.SHA256]), outputRow.Digest)
|
||||
}
|
||||
|
||||
func TestMatchOneReleasedMultiSignature(t *testing.T) {
|
||||
@ -249,8 +249,8 @@ func TestMatchOneReleasedMultiSignature(t *testing.T) {
|
||||
outputRow := matchedSigRows[0]
|
||||
// We should have three signers
|
||||
assert.Equal(t, outputRow.Signers, []string{"a", "b", "c"})
|
||||
assert.Equal(t, releasedTgt.Name, outputRow.TagName)
|
||||
assert.Equal(t, hex.EncodeToString(releasedTgt.Hashes[notary.SHA256]), outputRow.HashHex)
|
||||
assert.Equal(t, releasedTgt.Name, outputRow.SignedTag)
|
||||
assert.Equal(t, hex.EncodeToString(releasedTgt.Hashes[notary.SHA256]), outputRow.Digest)
|
||||
}
|
||||
|
||||
func TestMatchMultiReleasedMultiSignature(t *testing.T) {
|
||||
@ -288,18 +288,18 @@ func TestMatchMultiReleasedMultiSignature(t *testing.T) {
|
||||
// note that the output is sorted by tag name, so we can reliably index to validate data:
|
||||
outputTargetA := matchedSigRows[0]
|
||||
assert.Equal(t, outputTargetA.Signers, []string{"a"})
|
||||
assert.Equal(t, targetA.Name, outputTargetA.TagName)
|
||||
assert.Equal(t, hex.EncodeToString(targetA.Hashes[notary.SHA256]), outputTargetA.HashHex)
|
||||
assert.Equal(t, targetA.Name, outputTargetA.SignedTag)
|
||||
assert.Equal(t, hex.EncodeToString(targetA.Hashes[notary.SHA256]), outputTargetA.Digest)
|
||||
|
||||
outputTargetB := matchedSigRows[1]
|
||||
assert.Equal(t, outputTargetB.Signers, []string{"a", "b"})
|
||||
assert.Equal(t, targetB.Name, outputTargetB.TagName)
|
||||
assert.Equal(t, hex.EncodeToString(targetB.Hashes[notary.SHA256]), outputTargetB.HashHex)
|
||||
assert.Equal(t, targetB.Name, outputTargetB.SignedTag)
|
||||
assert.Equal(t, hex.EncodeToString(targetB.Hashes[notary.SHA256]), outputTargetB.Digest)
|
||||
|
||||
outputTargetC := matchedSigRows[2]
|
||||
assert.Equal(t, outputTargetC.Signers, []string{"a", "b", "c"})
|
||||
assert.Equal(t, targetC.Name, outputTargetC.TagName)
|
||||
assert.Equal(t, hex.EncodeToString(targetC.Hashes[notary.SHA256]), outputTargetC.HashHex)
|
||||
assert.Equal(t, targetC.Name, outputTargetC.SignedTag)
|
||||
assert.Equal(t, hex.EncodeToString(targetC.Hashes[notary.SHA256]), outputTargetC.Digest)
|
||||
}
|
||||
|
||||
func TestMatchReleasedSignatureFromTargets(t *testing.T) {
|
||||
@ -313,8 +313,8 @@ func TestMatchReleasedSignatureFromTargets(t *testing.T) {
|
||||
outputRow := matchedSigRows[0]
|
||||
// Empty signers because "targets" doesn't show up
|
||||
assert.Empty(t, outputRow.Signers)
|
||||
assert.Equal(t, releasedTgt.Name, outputRow.TagName)
|
||||
assert.Equal(t, hex.EncodeToString(releasedTgt.Hashes[notary.SHA256]), outputRow.HashHex)
|
||||
assert.Equal(t, releasedTgt.Name, outputRow.SignedTag)
|
||||
assert.Equal(t, hex.EncodeToString(releasedTgt.Hashes[notary.SHA256]), outputRow.Digest)
|
||||
}
|
||||
|
||||
func TestGetSignerRolesWithKeyIDs(t *testing.T) {
|
||||
|
||||
@ -510,9 +510,25 @@ func convertResources(source composetypes.Resources) (*swarm.ResourceRequirement
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var generic []swarm.GenericResource
|
||||
for _, res := range source.Reservations.GenericResources {
|
||||
var r swarm.GenericResource
|
||||
|
||||
if res.DiscreteResourceSpec != nil {
|
||||
r.DiscreteResourceSpec = &swarm.DiscreteGenericResource{
|
||||
Kind: res.DiscreteResourceSpec.Kind,
|
||||
Value: res.DiscreteResourceSpec.Value,
|
||||
}
|
||||
}
|
||||
|
||||
generic = append(generic, r)
|
||||
}
|
||||
|
||||
resources.Reservations = &swarm.Resources{
|
||||
NanoCPUs: cpus,
|
||||
MemoryBytes: int64(source.Reservations.MemoryBytes),
|
||||
NanoCPUs: cpus,
|
||||
MemoryBytes: int64(source.Reservations.MemoryBytes),
|
||||
GenericResources: generic,
|
||||
}
|
||||
}
|
||||
return resources, nil
|
||||
|
||||
@ -8,11 +8,14 @@ import (
|
||||
"time"
|
||||
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestConvertRestartPolicyFromNone(t *testing.T) {
|
||||
@ -433,3 +436,121 @@ func TestServiceConvertsIsolation(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, container.IsolationHyperV, result.TaskTemplate.ContainerSpec.Isolation)
|
||||
}
|
||||
|
||||
func TestConvertServiceSecrets(t *testing.T) {
|
||||
namespace := Namespace{name: "foo"}
|
||||
secrets := []composetypes.ServiceSecretConfig{
|
||||
{Source: "foo_secret"},
|
||||
{Source: "bar_secret"},
|
||||
}
|
||||
secretSpecs := map[string]composetypes.SecretConfig{
|
||||
"foo_secret": {
|
||||
Name: "foo_secret",
|
||||
},
|
||||
"bar_secret": {
|
||||
Name: "bar_secret",
|
||||
},
|
||||
}
|
||||
client := &fakeClient{
|
||||
secretListFunc: func(opts types.SecretListOptions) ([]swarm.Secret, error) {
|
||||
assert.Contains(t, opts.Filters.Get("name"), "foo_secret")
|
||||
assert.Contains(t, opts.Filters.Get("name"), "bar_secret")
|
||||
return []swarm.Secret{
|
||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo_secret"}}},
|
||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "bar_secret"}}},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
refs, err := convertServiceSecrets(client, namespace, secrets, secretSpecs)
|
||||
require.NoError(t, err)
|
||||
expected := []*swarm.SecretReference{
|
||||
{
|
||||
SecretName: "bar_secret",
|
||||
File: &swarm.SecretReferenceFileTarget{
|
||||
Name: "bar_secret",
|
||||
UID: "0",
|
||||
GID: "0",
|
||||
Mode: 0444,
|
||||
},
|
||||
},
|
||||
{
|
||||
SecretName: "foo_secret",
|
||||
File: &swarm.SecretReferenceFileTarget{
|
||||
Name: "foo_secret",
|
||||
UID: "0",
|
||||
GID: "0",
|
||||
Mode: 0444,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(t, expected, refs)
|
||||
}
|
||||
|
||||
func TestConvertServiceConfigs(t *testing.T) {
|
||||
namespace := Namespace{name: "foo"}
|
||||
configs := []composetypes.ServiceConfigObjConfig{
|
||||
{Source: "foo_config"},
|
||||
{Source: "bar_config"},
|
||||
}
|
||||
configSpecs := map[string]composetypes.ConfigObjConfig{
|
||||
"foo_config": {
|
||||
Name: "foo_config",
|
||||
},
|
||||
"bar_config": {
|
||||
Name: "bar_config",
|
||||
},
|
||||
}
|
||||
client := &fakeClient{
|
||||
configListFunc: func(opts types.ConfigListOptions) ([]swarm.Config, error) {
|
||||
assert.Contains(t, opts.Filters.Get("name"), "foo_config")
|
||||
assert.Contains(t, opts.Filters.Get("name"), "bar_config")
|
||||
return []swarm.Config{
|
||||
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "foo_config"}}},
|
||||
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "bar_config"}}},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
refs, err := convertServiceConfigObjs(client, namespace, configs, configSpecs)
|
||||
require.NoError(t, err)
|
||||
expected := []*swarm.ConfigReference{
|
||||
{
|
||||
ConfigName: "bar_config",
|
||||
File: &swarm.ConfigReferenceFileTarget{
|
||||
Name: "bar_config",
|
||||
UID: "0",
|
||||
GID: "0",
|
||||
Mode: 0444,
|
||||
},
|
||||
},
|
||||
{
|
||||
ConfigName: "foo_config",
|
||||
File: &swarm.ConfigReferenceFileTarget{
|
||||
Name: "foo_config",
|
||||
UID: "0",
|
||||
GID: "0",
|
||||
Mode: 0444,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(t, expected, refs)
|
||||
}
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
secretListFunc func(types.SecretListOptions) ([]swarm.Secret, error)
|
||||
configListFunc func(types.ConfigListOptions) ([]swarm.Config, error)
|
||||
}
|
||||
|
||||
func (c *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
|
||||
if c.secretListFunc != nil {
|
||||
return c.secretListFunc(options)
|
||||
}
|
||||
return []swarm.Secret{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
|
||||
if c.configListFunc != nil {
|
||||
return c.configListFunc(options)
|
||||
}
|
||||
return []swarm.Config{}, nil
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
version: "3.4"
|
||||
version: "3.5"
|
||||
|
||||
services:
|
||||
foo:
|
||||
@ -16,7 +16,6 @@ services:
|
||||
labels: [FOO=BAR]
|
||||
|
||||
|
||||
|
||||
cap_add:
|
||||
- ALL
|
||||
|
||||
@ -54,6 +53,13 @@ services:
|
||||
reservations:
|
||||
cpus: '0.0001'
|
||||
memory: 20M
|
||||
generic_resources:
|
||||
- discrete_resource_spec:
|
||||
kind: 'gpu'
|
||||
value: 2
|
||||
- discrete_resource_spec:
|
||||
kind: 'ssd'
|
||||
value: 1
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
delay: 5s
|
||||
|
||||
@ -879,6 +879,20 @@ func TestFullExample(t *testing.T) {
|
||||
Reservations: &types.Resource{
|
||||
NanoCPUs: "0.0001",
|
||||
MemoryBytes: 20 * 1024 * 1024,
|
||||
GenericResources: []types.GenericResource{
|
||||
{
|
||||
DiscreteResourceSpec: &types.DiscreteGenericResource{
|
||||
Kind: "gpu",
|
||||
Value: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
DiscreteResourceSpec: &types.DiscreteGenericResource{
|
||||
Kind: "ssd",
|
||||
Value: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RestartPolicy: &types.RestartPolicy{
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -354,8 +354,23 @@
|
||||
"resources": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limits": {"$ref": "#/definitions/resource"},
|
||||
"reservations": {"$ref": "#/definitions/resource"}
|
||||
"limits": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cpus": {"type": "string"},
|
||||
"memory": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"reservations": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cpus": {"type": "string"},
|
||||
"memory": {"type": "string"},
|
||||
"generic_resources": {"$ref": "#/definitions/generic_resources"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
@ -390,14 +405,23 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"resource": {
|
||||
"id": "#/definitions/resource",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cpus": {"type": "string"},
|
||||
"memory": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
"generic_resources": {
|
||||
"id": "#/definitions/generic_resources",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"discrete_resource_spec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {"type": "string"},
|
||||
"value": {"type": "number"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
|
||||
"network": {
|
||||
|
||||
@ -217,8 +217,24 @@ type Resources struct {
|
||||
// Resource is a resource to be limited or reserved
|
||||
type Resource struct {
|
||||
// TODO: types to convert from units and ratios
|
||||
NanoCPUs string `mapstructure:"cpus"`
|
||||
MemoryBytes UnitBytes `mapstructure:"memory"`
|
||||
NanoCPUs string `mapstructure:"cpus"`
|
||||
MemoryBytes UnitBytes `mapstructure:"memory"`
|
||||
GenericResources []GenericResource `mapstructure:"generic_resources"`
|
||||
}
|
||||
|
||||
// GenericResource represents a "user defined" resource which can
|
||||
// only be an integer (e.g: SSD=3) for a service
|
||||
type GenericResource struct {
|
||||
DiscreteResourceSpec *DiscreteGenericResource `mapstructure:"discrete_resource_spec"`
|
||||
}
|
||||
|
||||
// DiscreteGenericResource represents a "user defined" resource which is defined
|
||||
// as an integer
|
||||
// "Kind" is used to describe the Kind of a resource (e.g: "GPU", "FPGA", "SSD", ...)
|
||||
// Value is used to count the resource (SSD=5, HDD=3, ...)
|
||||
type DiscreteGenericResource struct {
|
||||
Kind string
|
||||
Value int64
|
||||
}
|
||||
|
||||
// UnitBytes is the bytes type
|
||||
|
||||
@ -833,11 +833,11 @@ __docker_complete_log_options() {
|
||||
local common_options2="env env-regex labels"
|
||||
|
||||
# awslogs does not implement the $common_options2.
|
||||
local awslogs_options="$common_options1 awslogs-create-group awslogs-datetime-format awslogs-group awslogs-multiline-pattern awslogs-region awslogs-stream tag"
|
||||
local awslogs_options="$common_options1 awslogs-create-group awslogs-credentials-endpoint awslogs-datetime-format awslogs-group awslogs-multiline-pattern awslogs-region awslogs-stream tag"
|
||||
|
||||
local fluentd_options="$common_options1 $common_options2 fluentd-address fluentd-async-connect fluentd-buffer-limit fluentd-retry-wait fluentd-max-retries tag"
|
||||
local gcplogs_options="$common_options1 $common_options2 gcp-log-cmd gcp-meta-id gcp-meta-name gcp-meta-zone gcp-project"
|
||||
local gelf_options="$common_options1 $common_options2 gelf-address gelf-compression-level gelf-compression-type tag"
|
||||
local gelf_options="$common_options1 $common_options2 gelf-address gelf-compression-level gelf-compression-type gelf-tcp-max-reconnect gelf-tcp-reconnect-delay tag"
|
||||
local journald_options="$common_options1 $common_options2 tag"
|
||||
local json_file_options="$common_options1 $common_options2 max-file max-size"
|
||||
local logentries_options="$common_options1 $common_options2 logentries-token tag"
|
||||
@ -892,12 +892,17 @@ __docker_complete_log_driver_options() {
|
||||
COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
|
||||
return
|
||||
;;
|
||||
awslogs-credentials-endpoint)
|
||||
COMPREPLY=( $( compgen -W "/" -- "${cur##*=}" ) )
|
||||
__docker_nospace
|
||||
return
|
||||
;;
|
||||
fluentd-async-connect)
|
||||
COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
|
||||
return
|
||||
;;
|
||||
gelf-address)
|
||||
COMPREPLY=( $( compgen -W "udp" -S "://" -- "${cur##*=}" ) )
|
||||
COMPREPLY=( $( compgen -W "tcp udp" -S "://" -- "${cur##*=}" ) )
|
||||
__docker_nospace
|
||||
return
|
||||
;;
|
||||
@ -1487,17 +1492,17 @@ _docker_container_kill() {
|
||||
|
||||
_docker_container_logs() {
|
||||
case "$prev" in
|
||||
--since|--tail)
|
||||
--since|--tail|--until)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--details --follow -f --help --since --tail --timestamps -t" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--details --follow -f --help --since --tail --timestamps -t --until" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '--since|--tail')
|
||||
local counter=$(__docker_pos_first_nonflag '--since|--tail|--until')
|
||||
if [ "$cword" -eq "$counter" ]; then
|
||||
__docker_complete_containers_all
|
||||
fi
|
||||
@ -2467,7 +2472,10 @@ _docker_image_build() {
|
||||
--quiet -q
|
||||
--rm
|
||||
"
|
||||
__docker_daemon_is_experimental && boolean_options+="--squash"
|
||||
__docker_daemon_is_experimental && boolean_options+="
|
||||
--squash
|
||||
--stream
|
||||
"
|
||||
|
||||
local all_options="$options_with_args $boolean_options"
|
||||
|
||||
@ -4706,7 +4714,7 @@ _docker_trust_revoke() {
|
||||
_docker_trust_sign() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--help --local" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag)
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
FROM dockercore/golang-cross@sha256:25ff84377e9d7f40639c33cc374166a3b0f1829b8462cf7001d742a846de2687
|
||||
FROM dockercore/golang-cross@sha256:2e843a0e4d82b6bab34d2cb7abe26d1a6cda23226ecc3869100c8db553603f9b
|
||||
ENV DISABLE_WARN_OUTSIDE_CONTAINER=1
|
||||
WORKDIR /go/src/github.com/docker/cli
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
|
||||
FROM golang:1.8.5-alpine3.6
|
||||
FROM golang:1.9.2-alpine3.6
|
||||
|
||||
RUN apk add -U git make bash coreutils ca-certificates
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM golang:1.8.5-alpine3.6
|
||||
FROM golang:1.9.2-alpine3.6
|
||||
|
||||
RUN apk add -U git
|
||||
|
||||
|
||||
@ -823,16 +823,23 @@ change them using `docker run --env <key>=<value>`.
|
||||
|
||||
ADD has two forms:
|
||||
|
||||
- `ADD <src>... <dest>`
|
||||
- `ADD ["<src>",... "<dest>"]` (this form is required for paths containing
|
||||
- `ADD [--chown=<user>:<group>] <src>... <dest>`
|
||||
- `ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]` (this form is required for paths containing
|
||||
whitespace)
|
||||
|
||||
> **Note**:
|
||||
> The `--chown` feature is only supported on Dockerfiles used to build Linux containers,
|
||||
> and will not work on Windows containers. Since user and group ownership concepts do
|
||||
> not translate between Linux and Windows, the use of `/etc/passwd` and `/etc/group` for
|
||||
> translating user and group names to IDs restricts this feature to only be viable for
|
||||
> for Linux OS-based containers.
|
||||
|
||||
The `ADD` instruction copies new files, directories or remote file URLs from `<src>`
|
||||
and adds them to the filesystem of the image at the path `<dest>`.
|
||||
|
||||
Multiple `<src>` resource may be specified but if they are files or
|
||||
directories then they must be relative to the source directory that is
|
||||
being built (the context of the build).
|
||||
Multiple `<src>` resources may be specified but if they are files or
|
||||
directories, their paths are interpreted as relative to the source of
|
||||
the context of the build.
|
||||
|
||||
Each `<src>` may contain wildcards and matching will be done using Go's
|
||||
[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules. For example:
|
||||
@ -854,7 +861,26 @@ named `arr[0].txt`, use the following;
|
||||
ADD arr[[]0].txt /mydir/ # copy a file named "arr[0].txt" to /mydir/
|
||||
|
||||
|
||||
All new files and directories are created with a UID and GID of 0.
|
||||
All new files and directories are created with a UID and GID of 0, unless the
|
||||
optional `--chown` flag specifies a given username, groupname, or UID/GID
|
||||
combination to request specific ownership of the content added. The
|
||||
format of the `--chown` flag allows for either username and groupname strings
|
||||
or direct integer UID and GID in any combination. Providing a username without
|
||||
groupname or a UID without GID will use the same numeric UID as the GID. If a
|
||||
username or groupname is provided, the container's root filesystem
|
||||
`/etc/passwd` and `/etc/group` files will be used to perform the translation
|
||||
from name to integer UID or GID respectively. The following examples show
|
||||
valid definitions for the `--chown` flag:
|
||||
|
||||
ADD --chown=55:mygroup files* /somedir/
|
||||
ADD --chown=bin files* /somedir/
|
||||
ADD --chown=1 files* /somedir/
|
||||
ADD --chown=10:11 files* /somedir/
|
||||
|
||||
If the container root filesystem does not contain either `/etc/passwd` or
|
||||
`/etc/group` files and either user or group names are used in the `--chown`
|
||||
flag, the build will fail on the `ADD` operation. Using numeric IDs requires
|
||||
no lookup and will not depend on container root filesystem content.
|
||||
|
||||
In the case where `<src>` is a remote file URL, the destination will
|
||||
have permissions of 600. If the remote file being retrieved has an HTTP
|
||||
@ -944,15 +970,23 @@ guide](https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practi
|
||||
|
||||
COPY has two forms:
|
||||
|
||||
- `COPY <src>... <dest>`
|
||||
- `COPY ["<src>",... "<dest>"]` (this form is required for paths containing
|
||||
- `COPY [--chown=<user>:<group>] <src>... <dest>`
|
||||
- `COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]` (this form is required for paths containing
|
||||
whitespace)
|
||||
|
||||
> **Note**:
|
||||
> The `--chown` feature is only supported on Dockerfiles used to build Linux containers,
|
||||
> and will not work on Windows containers. Since user and group ownership concepts do
|
||||
> not translate between Linux and Windows, the use of `/etc/passwd` and `/etc/group` for
|
||||
> translating user and group names to IDs restricts this feature to only be viable for
|
||||
> for Linux OS-based containers.
|
||||
|
||||
The `COPY` instruction copies new files or directories from `<src>`
|
||||
and adds them to the filesystem of the container at the path `<dest>`.
|
||||
|
||||
Multiple `<src>` resource may be specified but they must be relative
|
||||
to the source directory that is being built (the context of the build).
|
||||
Multiple `<src>` resources may be specified but the paths of files and
|
||||
directories will be interpreted as relative to the source of the context
|
||||
of the build.
|
||||
|
||||
Each `<src>` may contain wildcards and matching will be done using Go's
|
||||
[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules. For example:
|
||||
@ -974,7 +1008,26 @@ named `arr[0].txt`, use the following;
|
||||
|
||||
COPY arr[[]0].txt /mydir/ # copy a file named "arr[0].txt" to /mydir/
|
||||
|
||||
All new files and directories are created with a UID and GID of 0.
|
||||
All new files and directories are created with a UID and GID of 0, unless the
|
||||
optional `--chown` flag specifies a given username, groupname, or UID/GID
|
||||
combination to request specific ownership of the copied content. The
|
||||
format of the `--chown` flag allows for either username and groupname strings
|
||||
or direct integer UID and GID in any combination. Providing a username without
|
||||
groupname or a UID without GID will use the same numeric UID as the GID. If a
|
||||
username or groupname is provided, the container's root filesystem
|
||||
`/etc/passwd` and `/etc/group` files will be used to perform the translation
|
||||
from name to integer UID or GID respectively. The following examples show
|
||||
valid definitions for the `--chown` flag:
|
||||
|
||||
COPY --chown=55:mygroup files* /somedir/
|
||||
COPY --chown=bin files* /somedir/
|
||||
COPY --chown=1 files* /somedir/
|
||||
COPY --chown=10:11 files* /somedir/
|
||||
|
||||
If the container root filesystem does not contain either `/etc/passwd` or
|
||||
`/etc/group` files and either user or group names are used in the `--chown`
|
||||
flag, the build will fail on the `COPY` operation. Using numeric IDs requires
|
||||
no lookup and will not depend on container root filesystem content.
|
||||
|
||||
> **Note**:
|
||||
> If you build using STDIN (`docker build - < somefile`), there is no
|
||||
|
||||
@ -72,6 +72,7 @@ Options:
|
||||
--max-concurrent-uploads int Set the max concurrent uploads for each push (default 5)
|
||||
--metrics-addr string Set default address and port to serve the metrics api on
|
||||
--mtu int Set the containers network MTU
|
||||
--node-generic-resources list Advertise user-defined resource
|
||||
--no-new-privileges Set no-new-privileges by default for new containers
|
||||
--oom-score-adjust int Set the oom_score_adj for the daemon (default -500)
|
||||
-p, --pidfile string Path to use for daemon PID file (default "/var/run/docker.pid")
|
||||
@ -1237,6 +1238,23 @@ Please note that this feature is still marked as experimental as metrics and met
|
||||
names could change while this feature is still in experimental. Please provide
|
||||
feedback on what you would like to see collected in the API.
|
||||
|
||||
#### Node Generic Resources
|
||||
|
||||
The `--node-generic-resources` option takes a list of key-value
|
||||
pair (`key=value`) that allows you to advertise user defined resources
|
||||
in a swarm cluster.
|
||||
|
||||
The current expected use case is to advertise NVIDIA GPUs so that services
|
||||
requesting `NVIDIA-GPU=[0-16]` can land on a node that has enough GPUs for
|
||||
the task to run.
|
||||
|
||||
Example of usage:
|
||||
```json
|
||||
{
|
||||
"node-generic-resources": ["NVIDIA-GPU=UUID1", "NVIDIA-GPU=UUID2"]
|
||||
}
|
||||
```
|
||||
|
||||
### Daemon configuration file
|
||||
|
||||
The `--config-file` option allows you to set any configuration option
|
||||
@ -1325,6 +1343,7 @@ This is a full example of the allowed configuration options on Linux:
|
||||
"no-new-privileges": false,
|
||||
"default-runtime": "runc",
|
||||
"oom-score-adjust": -500,
|
||||
"node-generic-resources": ["NVIDIA-GPU=UUID1", "NVIDIA-GPU=UUID2"],
|
||||
"runtimes": {
|
||||
"cc-runtime": {
|
||||
"path": "/usr/bin/cc-runtime"
|
||||
|
||||
@ -33,6 +33,7 @@ Options:
|
||||
--entrypoint command Overwrite the default ENTRYPOINT of the image
|
||||
-e, --env list Set environment variables
|
||||
--env-file list Read in a file of environment variables
|
||||
--generic-resource list User defined resources request
|
||||
--group list Set one or more supplementary user groups for the container
|
||||
--health-cmd string Command to run to check health
|
||||
--health-interval duration Time between running the check (ms|s|m|h)
|
||||
@ -915,6 +916,17 @@ Supported isolation modes on Windows are:
|
||||
- `process`: use process isolation (Windows server only)
|
||||
- `hyperv`: use Hyper-V isolation
|
||||
|
||||
### Create services requesting Generic Resources
|
||||
|
||||
You can narrow the kind of nodes your task can land on through the using the
|
||||
`--generic-resource` flag (if the nodes advertise these resources):
|
||||
|
||||
```bash
|
||||
$ docker service create --name cuda \
|
||||
--generic-resource "NVIDIA-GPU=2" \
|
||||
--generic-resource "SSD=1" \
|
||||
nvidia/cuda
|
||||
```
|
||||
|
||||
## Related commands
|
||||
|
||||
|
||||
@ -41,6 +41,8 @@ Options:
|
||||
--env-add list Add or update an environment variable
|
||||
--env-rm list Remove an environment variable
|
||||
--force Force update even if no changes require it
|
||||
--generic-resource-add list Add an additional generic resource to the service's resources requirements
|
||||
--generic-resource-rm list Remove a previously added generic resource to the service's resources requirements
|
||||
--group-add list Add an additional supplementary user group to the container
|
||||
--group-rm list Remove a previously added supplementary user group from the container
|
||||
--health-cmd string Command to run to check health
|
||||
|
||||
364
components/cli/docs/reference/commandline/trust_inspect.md
Normal file
364
components/cli/docs/reference/commandline/trust_inspect.md
Normal file
@ -0,0 +1,364 @@
|
||||
---
|
||||
title: "trust inspect"
|
||||
description: "The inspect command description and usage"
|
||||
keywords: "view, notary, trust"
|
||||
---
|
||||
|
||||
<!-- This file is maintained within the docker/cli GitHub
|
||||
repository at https://github.com/docker/cli/. Make all
|
||||
pull requests against that repo. If you see this file in
|
||||
another repository, consider it read-only there, as it will
|
||||
periodically be overwritten by the definitive file. Pull
|
||||
requests which include edits to this file in other repositories
|
||||
will be rejected.
|
||||
-->
|
||||
|
||||
# trust inspect
|
||||
|
||||
```markdown
|
||||
Usage: docker trust inspect IMAGE[:TAG] [IMAGE[:TAG]...]
|
||||
|
||||
Return low-level information about keys and signatures
|
||||
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
`docker trust inspect` provides low-level JSON information on signed repositories.
|
||||
This includes all image tags that are signed, who signed them, and who can sign
|
||||
new tags.
|
||||
|
||||
`docker trust inspect` prints the trust information in a machine-readable format. Refer to
|
||||
[`docker trust view`](trust_view.md) for a human-friendly output.
|
||||
|
||||
`docker trust inspect` is currently experimental.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Get low-level details about signatures for a single image tag
|
||||
|
||||
Use the `docker trust inspect` to get trust information about an image. The
|
||||
following example prints trust information for the `alpine:latest` image:
|
||||
|
||||
```bash
|
||||
$ docker trust inspect alpine:latest
|
||||
[
|
||||
{
|
||||
"Name": "alpine:latest",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "latest",
|
||||
"Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The `SignedTags` key will list the `SignedTag` name, its `Digest`, and the `Signers` responsible for the signature.
|
||||
|
||||
`AdministrativeKeys` will list the `Repository` and `Root` keys.
|
||||
|
||||
This format mirrors the output of `docker trust view`
|
||||
|
||||
If signers are set up for the repository via other `docker trust` commands, `docker trust inspect` includes a `Signers` key:
|
||||
|
||||
```bash
|
||||
$ docker trust inspect my-image:purple
|
||||
[
|
||||
{
|
||||
"Name": "my-image:purple",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "purple",
|
||||
"Digest": "941d3dba358621ce3c41ef67b47cf80f701ff80cdf46b5cc86587eaebfe45557",
|
||||
"Signers": [
|
||||
"alice",
|
||||
"bob",
|
||||
"carol"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [
|
||||
{
|
||||
"Name": "alice",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "04dd031411ed671ae1e12f47ddc8646d98f135090b01e54c3561e843084484a3"
|
||||
},
|
||||
{
|
||||
"ID": "6a11e4898a4014d400332ab0e096308c844584ff70943cdd1d6628d577f45fd8"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "bob",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "433e245c656ae9733cdcc504bfa560f90950104442c4528c9616daa45824ccba"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "carol",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "d32fa8b5ca08273a2880f455fcb318da3dc80aeae1a30610815140deef8f30d9"
|
||||
},
|
||||
{
|
||||
"ID": "9a8bbec6ba2af88a5fad6047d428d17e6d05dbdd03d15b4fc8a9a0e8049cd606"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "27df2c8187e7543345c2e0bf3a1262e0bc63a72754e9a7395eac3f747ec23a44"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "40b66ccc8b176be8c7d365a17f3e046d1c3494e053dd57cfeacfe2e19c4f8e8f"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
If the image tag is unsigned or unavailable, `docker trust inspect` does not display any signed tags.
|
||||
|
||||
```bash
|
||||
$ docker trust inspect unsigned-img
|
||||
No signatures or cannot access unsigned-img
|
||||
```
|
||||
|
||||
However, if other tags are signed in the same image repository, `docker trust inspect` reports relevant key information:
|
||||
|
||||
```bash
|
||||
$ docker trust inspect alpine:unsigned
|
||||
[
|
||||
{
|
||||
"Name": "alpine:unsigned",
|
||||
"Signers": [],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get details about signatures for all image tags in a repository
|
||||
|
||||
If no tag is specified, `docker trust inspect` will report details for all signed tags in the repository:
|
||||
|
||||
```bash
|
||||
$ docker trust inspect alpine
|
||||
[
|
||||
{
|
||||
"Name": "alpine",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "3.5",
|
||||
"Digest": "b007a354427e1880de9cdba533e8e57382b7f2853a68a478a17d447b302c219c",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "3.6",
|
||||
"Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "edge",
|
||||
"Digest": "23e7d843e63a3eee29b6b8cfcd10e23dd1ef28f47251a985606a31040bf8e096",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "latest",
|
||||
"Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
### Get details about signatures for multiple images
|
||||
|
||||
`docker trust inspect` can take multiple repositories and images as arguments, and reports the results in an ordered list:
|
||||
|
||||
```bash
|
||||
$ docker trust inspect alpine notary
|
||||
[
|
||||
{
|
||||
"Name": "alpine",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "3.5",
|
||||
"Digest": "b007a354427e1880de9cdba533e8e57382b7f2853a68a478a17d447b302c219c",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "3.6",
|
||||
"Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "edge",
|
||||
"Digest": "23e7d843e63a3eee29b6b8cfcd10e23dd1ef28f47251a985606a31040bf8e096",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "integ-test-base",
|
||||
"Digest": "3952dc48dcc4136ccdde37fbef7e250346538a55a0366e3fccc683336377e372",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "latest",
|
||||
"Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "notary",
|
||||
"SignedTags": [
|
||||
{
|
||||
"SignedTag": "server",
|
||||
"Digest": "71f64ab718a3331dee103bc5afc6bc492914738ce37c2d2f127a8133714ecf5c",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"SignedTag": "signer",
|
||||
"Digest": "a6122d79b1e74f70b5dd933b18a6d1f99329a4728011079f06b245205f158fe8",
|
||||
"Signers": [
|
||||
"Repo Admin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Signers": [],
|
||||
"AdminstrativeKeys": [
|
||||
{
|
||||
"Name": "Root",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "8cdcdef5bd039f4ab5a029126951b5985eebf57cabdcdc4d21f5b3be8bb4ce92"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "Repository",
|
||||
"Keys": [
|
||||
{
|
||||
"ID": "85bfd031017722f950d480a721f845a2944db26a3dc084040a70f1b0d9bbb3df"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
@ -34,8 +34,8 @@ func TestBuildFromContextDirectoryWithTag(t *testing.T) {
|
||||
3: equals("Step 2/4 : COPY\trun /usr/bin/run"),
|
||||
5: equals("Step 3/4 : RUN\t\trun"),
|
||||
7: equals("running"),
|
||||
9: equals("Step 4/4 : COPY\tdata /data"),
|
||||
11: prefix("Removing intermediate container "),
|
||||
8: prefix("Removing intermediate container "),
|
||||
10: equals("Step 4/4 : COPY\tdata /data"),
|
||||
12: prefix("Successfully built "),
|
||||
13: equals("Successfully tagged myimage:latest"),
|
||||
})
|
||||
|
||||
@ -56,6 +56,7 @@ dockerd - Enable daemon mode
|
||||
[**--mtu**[=*0*]]
|
||||
[**--max-concurrent-downloads**[=*3*]]
|
||||
[**--max-concurrent-uploads**[=*5*]]
|
||||
[**--node-generic-resources**[=*[]*]]
|
||||
[**-p**|**--pidfile**[=*/var/run/docker.pid*]]
|
||||
[**--raw-logs**]
|
||||
[**--registry-mirror**[=*[]*]]
|
||||
@ -326,6 +327,15 @@ unix://[/path/to/socket] to use.
|
||||
**--max-concurrent-uploads**=*5*
|
||||
Set the max concurrent uploads for each push. Default is `5`.
|
||||
|
||||
**--node-generic-resources**=*[]*
|
||||
Advertise user-defined resource. Default is `[]`.
|
||||
Use this if your swarm cluster has some nodes with custom
|
||||
resources (e.g: NVIDIA GPU, SSD, ...) and you need your services to land on
|
||||
nodes advertising these resources.
|
||||
Usage example: `--node-generic-resources "NVIDIA-GPU=UUID1"
|
||||
--node-generic-resources "NVIDIA-GPU=UUID2"`
|
||||
|
||||
|
||||
**-p**, **--pidfile**=""
|
||||
Path to use for daemon PID file. Default is `/var/run/docker.pid`
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
|
||||
github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d
|
||||
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
||||
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
|
||||
github.com/docker/swarmkit 28f91d87bd3f75fd039dbb9be49bfd2381019261
|
||||
github.com/docker/swarmkit de950a7ed842c7b7e47e9451cde9bf8f96031894
|
||||
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
|
||||
github.com/gogo/protobuf v0.4
|
||||
github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4
|
||||
|
||||
111
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/helpers.go
generated
vendored
Normal file
111
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/helpers.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
package genericresource
|
||||
|
||||
import (
|
||||
"github.com/docker/swarmkit/api"
|
||||
)
|
||||
|
||||
// NewSet creates a set object
|
||||
func NewSet(key string, vals ...string) []*api.GenericResource {
|
||||
rs := make([]*api.GenericResource, 0, len(vals))
|
||||
|
||||
for _, v := range vals {
|
||||
rs = append(rs, NewString(key, v))
|
||||
}
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
// NewString creates a String resource
|
||||
func NewString(key, val string) *api.GenericResource {
|
||||
return &api.GenericResource{
|
||||
Resource: &api.GenericResource_NamedResourceSpec{
|
||||
NamedResourceSpec: &api.NamedGenericResource{
|
||||
Kind: key,
|
||||
Value: val,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewDiscrete creates a Discrete resource
|
||||
func NewDiscrete(key string, val int64) *api.GenericResource {
|
||||
return &api.GenericResource{
|
||||
Resource: &api.GenericResource_DiscreteResourceSpec{
|
||||
DiscreteResourceSpec: &api.DiscreteGenericResource{
|
||||
Kind: key,
|
||||
Value: val,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetResource returns resources from the "resources" parameter matching the kind key
|
||||
func GetResource(kind string, resources []*api.GenericResource) []*api.GenericResource {
|
||||
var res []*api.GenericResource
|
||||
|
||||
for _, r := range resources {
|
||||
if Kind(r) != kind {
|
||||
continue
|
||||
}
|
||||
|
||||
res = append(res, r)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// ConsumeNodeResources removes "res" from nodeAvailableResources
|
||||
func ConsumeNodeResources(nodeAvailableResources *[]*api.GenericResource, res []*api.GenericResource) {
|
||||
if nodeAvailableResources == nil {
|
||||
return
|
||||
}
|
||||
|
||||
w := 0
|
||||
|
||||
loop:
|
||||
for _, na := range *nodeAvailableResources {
|
||||
for _, r := range res {
|
||||
if Kind(na) != Kind(r) {
|
||||
continue
|
||||
}
|
||||
|
||||
if remove(na, r) {
|
||||
continue loop
|
||||
}
|
||||
// If this wasn't the right element then
|
||||
// we need to continue
|
||||
}
|
||||
|
||||
(*nodeAvailableResources)[w] = na
|
||||
w++
|
||||
}
|
||||
|
||||
*nodeAvailableResources = (*nodeAvailableResources)[:w]
|
||||
}
|
||||
|
||||
// Returns true if the element is to be removed from the list
|
||||
func remove(na, r *api.GenericResource) bool {
|
||||
switch tr := r.Resource.(type) {
|
||||
case *api.GenericResource_DiscreteResourceSpec:
|
||||
if na.GetDiscreteResourceSpec() == nil {
|
||||
return false // Type change, ignore
|
||||
}
|
||||
|
||||
na.GetDiscreteResourceSpec().Value -= tr.DiscreteResourceSpec.Value
|
||||
if na.GetDiscreteResourceSpec().Value <= 0 {
|
||||
return true
|
||||
}
|
||||
case *api.GenericResource_NamedResourceSpec:
|
||||
if na.GetNamedResourceSpec() == nil {
|
||||
return false // Type change, ignore
|
||||
}
|
||||
|
||||
if tr.NamedResourceSpec.Value != na.GetNamedResourceSpec().Value {
|
||||
return false // not the right item, ignore
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
111
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/parse.go
generated
vendored
Normal file
111
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/parse.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
package genericresource
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/swarmkit/api"
|
||||
)
|
||||
|
||||
func newParseError(format string, args ...interface{}) error {
|
||||
return fmt.Errorf("could not parse GenericResource: "+format, args...)
|
||||
}
|
||||
|
||||
// discreteResourceVal returns an int64 if the string is a discreteResource
|
||||
// and an error if it isn't
|
||||
func discreteResourceVal(res string) (int64, error) {
|
||||
return strconv.ParseInt(res, 10, 64)
|
||||
}
|
||||
|
||||
// allNamedResources returns true if the array of resources are all namedResources
|
||||
// e.g: res = [red, orange, green]
|
||||
func allNamedResources(res []string) bool {
|
||||
for _, v := range res {
|
||||
if _, err := discreteResourceVal(v); err == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ParseCmd parses the Generic Resource command line argument
|
||||
// and returns a list of *api.GenericResource
|
||||
func ParseCmd(cmd string) ([]*api.GenericResource, error) {
|
||||
if strings.Contains(cmd, "\n") {
|
||||
return nil, newParseError("unexpected '\\n' character")
|
||||
}
|
||||
|
||||
r := csv.NewReader(strings.NewReader(cmd))
|
||||
records, err := r.ReadAll()
|
||||
|
||||
if err != nil {
|
||||
return nil, newParseError("%v", err)
|
||||
}
|
||||
|
||||
if len(records) != 1 {
|
||||
return nil, newParseError("found multiple records while parsing cmd %v", records)
|
||||
}
|
||||
|
||||
return Parse(records[0])
|
||||
}
|
||||
|
||||
// Parse parses a table of GenericResource resources
|
||||
func Parse(cmds []string) ([]*api.GenericResource, error) {
|
||||
tokens := make(map[string][]string)
|
||||
|
||||
for _, term := range cmds {
|
||||
kva := strings.Split(term, "=")
|
||||
if len(kva) != 2 {
|
||||
return nil, newParseError("incorrect term %s, missing"+
|
||||
" '=' or malformed expression", term)
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(kva[0])
|
||||
val := strings.TrimSpace(kva[1])
|
||||
|
||||
tokens[key] = append(tokens[key], val)
|
||||
}
|
||||
|
||||
var rs []*api.GenericResource
|
||||
for k, v := range tokens {
|
||||
if u, ok := isDiscreteResource(v); ok {
|
||||
if u < 0 {
|
||||
return nil, newParseError("cannot ask for"+
|
||||
" negative resource %s", k)
|
||||
}
|
||||
|
||||
rs = append(rs, NewDiscrete(k, u))
|
||||
continue
|
||||
}
|
||||
|
||||
if allNamedResources(v) {
|
||||
rs = append(rs, NewSet(k, v...)...)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, newParseError("mixed discrete and named resources"+
|
||||
" in expression '%s=%s'", k, v)
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// isDiscreteResource returns true if the array of resources is a
|
||||
// Discrete Resource.
|
||||
// e.g: res = [1]
|
||||
func isDiscreteResource(values []string) (int64, bool) {
|
||||
if len(values) != 1 {
|
||||
return int64(0), false
|
||||
}
|
||||
|
||||
u, err := discreteResourceVal(values[0])
|
||||
if err != nil {
|
||||
return int64(0), false
|
||||
}
|
||||
|
||||
return u, true
|
||||
|
||||
}
|
||||
202
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/resource_management.go
generated
vendored
Normal file
202
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/resource_management.go
generated
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
package genericresource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/docker/swarmkit/api"
|
||||
)
|
||||
|
||||
// Claim assigns GenericResources to a task by taking them from the
|
||||
// node's GenericResource list and storing them in the task's available list
|
||||
func Claim(nodeAvailableResources, taskAssigned *[]*api.GenericResource,
|
||||
taskReservations []*api.GenericResource) error {
|
||||
var resSelected []*api.GenericResource
|
||||
|
||||
for _, res := range taskReservations {
|
||||
tr := res.GetDiscreteResourceSpec()
|
||||
if tr == nil {
|
||||
return fmt.Errorf("task should only hold Discrete type")
|
||||
}
|
||||
|
||||
// Select the resources
|
||||
nrs, err := selectNodeResources(*nodeAvailableResources, tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resSelected = append(resSelected, nrs...)
|
||||
}
|
||||
|
||||
ClaimResources(nodeAvailableResources, taskAssigned, resSelected)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClaimResources adds the specified resources to the task's list
|
||||
// and removes them from the node's generic resource list
|
||||
func ClaimResources(nodeAvailableResources, taskAssigned *[]*api.GenericResource,
|
||||
resSelected []*api.GenericResource) {
|
||||
*taskAssigned = append(*taskAssigned, resSelected...)
|
||||
ConsumeNodeResources(nodeAvailableResources, resSelected)
|
||||
}
|
||||
|
||||
func selectNodeResources(nodeRes []*api.GenericResource,
|
||||
tr *api.DiscreteGenericResource) ([]*api.GenericResource, error) {
|
||||
var nrs []*api.GenericResource
|
||||
|
||||
for _, res := range nodeRes {
|
||||
if Kind(res) != tr.Kind {
|
||||
continue
|
||||
}
|
||||
|
||||
switch nr := res.Resource.(type) {
|
||||
case *api.GenericResource_DiscreteResourceSpec:
|
||||
if nr.DiscreteResourceSpec.Value >= tr.Value && tr.Value != 0 {
|
||||
nrs = append(nrs, NewDiscrete(tr.Kind, tr.Value))
|
||||
}
|
||||
|
||||
return nrs, nil
|
||||
case *api.GenericResource_NamedResourceSpec:
|
||||
nrs = append(nrs, res.Copy())
|
||||
|
||||
if int64(len(nrs)) == tr.Value {
|
||||
return nrs, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(nrs) == 0 {
|
||||
return nil, fmt.Errorf("not enough resources available for task reservations: %+v", tr)
|
||||
}
|
||||
|
||||
return nrs, nil
|
||||
}
|
||||
|
||||
// Reclaim adds the resources taken by the task to the node's store
|
||||
func Reclaim(nodeAvailableResources *[]*api.GenericResource, taskAssigned, nodeRes []*api.GenericResource) error {
|
||||
err := reclaimResources(nodeAvailableResources, taskAssigned)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sanitize(nodeRes, nodeAvailableResources)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func reclaimResources(nodeAvailableResources *[]*api.GenericResource, taskAssigned []*api.GenericResource) error {
|
||||
// The node could have been updated
|
||||
if nodeAvailableResources == nil {
|
||||
return fmt.Errorf("node no longer has any resources")
|
||||
}
|
||||
|
||||
for _, res := range taskAssigned {
|
||||
switch tr := res.Resource.(type) {
|
||||
case *api.GenericResource_DiscreteResourceSpec:
|
||||
nrs := GetResource(tr.DiscreteResourceSpec.Kind, *nodeAvailableResources)
|
||||
|
||||
// If the resource went down to 0 it's no longer in the
|
||||
// available list
|
||||
if len(nrs) == 0 {
|
||||
*nodeAvailableResources = append(*nodeAvailableResources, res.Copy())
|
||||
}
|
||||
|
||||
if len(nrs) != 1 {
|
||||
continue // Type change
|
||||
}
|
||||
|
||||
nr := nrs[0].GetDiscreteResourceSpec()
|
||||
if nr == nil {
|
||||
continue // Type change
|
||||
}
|
||||
|
||||
nr.Value += tr.DiscreteResourceSpec.Value
|
||||
case *api.GenericResource_NamedResourceSpec:
|
||||
*nodeAvailableResources = append(*nodeAvailableResources, res.Copy())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sanitize checks that nodeAvailableResources does not add resources unknown
|
||||
// to the nodeSpec (nodeRes) or goes over the integer bound specified
|
||||
// by the spec.
|
||||
// Note this is because the user is able to update a node's resources
|
||||
func sanitize(nodeRes []*api.GenericResource, nodeAvailableResources *[]*api.GenericResource) {
|
||||
// - We add the sanitized resources at the end, after
|
||||
// having removed the elements from the list
|
||||
|
||||
// - When a set changes to a Discrete we also need
|
||||
// to make sure that we don't add the Discrete multiple
|
||||
// time hence, the need of a map to remember that
|
||||
var sanitized []*api.GenericResource
|
||||
kindSanitized := make(map[string]struct{})
|
||||
w := 0
|
||||
|
||||
for _, na := range *nodeAvailableResources {
|
||||
ok, nrs := sanitizeResource(nodeRes, na)
|
||||
if !ok {
|
||||
if _, ok = kindSanitized[Kind(na)]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
kindSanitized[Kind(na)] = struct{}{}
|
||||
sanitized = append(sanitized, nrs...)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
(*nodeAvailableResources)[w] = na
|
||||
w++
|
||||
}
|
||||
|
||||
*nodeAvailableResources = (*nodeAvailableResources)[:w]
|
||||
*nodeAvailableResources = append(*nodeAvailableResources, sanitized...)
|
||||
}
|
||||
|
||||
// Returns true if the element is in nodeRes and "sane"
|
||||
// Returns false if the element isn't in nodeRes and "sane" and the element(s) that should be replacing it
|
||||
func sanitizeResource(nodeRes []*api.GenericResource, res *api.GenericResource) (ok bool, nrs []*api.GenericResource) {
|
||||
switch na := res.Resource.(type) {
|
||||
case *api.GenericResource_DiscreteResourceSpec:
|
||||
nrs := GetResource(na.DiscreteResourceSpec.Kind, nodeRes)
|
||||
|
||||
// Type change or removed: reset
|
||||
if len(nrs) != 1 {
|
||||
return false, nrs
|
||||
}
|
||||
|
||||
// Type change: reset
|
||||
nr := nrs[0].GetDiscreteResourceSpec()
|
||||
if nr == nil {
|
||||
return false, nrs
|
||||
}
|
||||
|
||||
// Amount change: reset
|
||||
if na.DiscreteResourceSpec.Value > nr.Value {
|
||||
return false, nrs
|
||||
}
|
||||
case *api.GenericResource_NamedResourceSpec:
|
||||
nrs := GetResource(na.NamedResourceSpec.Kind, nodeRes)
|
||||
|
||||
// Type change
|
||||
if len(nrs) == 0 {
|
||||
return false, nrs
|
||||
}
|
||||
|
||||
for _, nr := range nrs {
|
||||
// Type change: reset
|
||||
if nr.GetDiscreteResourceSpec() != nil {
|
||||
return false, nrs
|
||||
}
|
||||
|
||||
if na.NamedResourceSpec.Value == nr.GetNamedResourceSpec().Value {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Removed
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
54
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/string.go
generated
vendored
Normal file
54
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/string.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
package genericresource
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/swarmkit/api"
|
||||
)
|
||||
|
||||
func discreteToString(d *api.GenericResource_DiscreteResourceSpec) string {
|
||||
return strconv.FormatInt(d.DiscreteResourceSpec.Value, 10)
|
||||
}
|
||||
|
||||
// Kind returns the kind key as a string
|
||||
func Kind(res *api.GenericResource) string {
|
||||
switch r := res.Resource.(type) {
|
||||
case *api.GenericResource_DiscreteResourceSpec:
|
||||
return r.DiscreteResourceSpec.Kind
|
||||
case *api.GenericResource_NamedResourceSpec:
|
||||
return r.NamedResourceSpec.Kind
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Value returns the value key as a string
|
||||
func Value(res *api.GenericResource) string {
|
||||
switch res := res.Resource.(type) {
|
||||
case *api.GenericResource_DiscreteResourceSpec:
|
||||
return discreteToString(res)
|
||||
case *api.GenericResource_NamedResourceSpec:
|
||||
return res.NamedResourceSpec.Value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// EnvFormat returns the environment string version of the resource
|
||||
func EnvFormat(res []*api.GenericResource, prefix string) []string {
|
||||
envs := make(map[string][]string)
|
||||
for _, v := range res {
|
||||
key := Kind(v)
|
||||
val := Value(v)
|
||||
envs[key] = append(envs[key], val)
|
||||
}
|
||||
|
||||
env := make([]string, 0, len(res))
|
||||
for k, v := range envs {
|
||||
k = strings.ToUpper(prefix + "_" + k)
|
||||
env = append(env, k+"="+strings.Join(v, ","))
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
84
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/validate.go
generated
vendored
Normal file
84
components/cli/vendor/github.com/docker/swarmkit/api/genericresource/validate.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
package genericresource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/docker/swarmkit/api"
|
||||
)
|
||||
|
||||
// ValidateTask validates that the task only uses integers
|
||||
// for generic resources
|
||||
func ValidateTask(resources *api.Resources) error {
|
||||
for _, v := range resources.Generic {
|
||||
if v.GetDiscreteResourceSpec() != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid argument for resource %s", Kind(v))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasEnough returns true if node can satisfy the task's GenericResource request
|
||||
func HasEnough(nodeRes []*api.GenericResource, taskRes *api.GenericResource) (bool, error) {
|
||||
t := taskRes.GetDiscreteResourceSpec()
|
||||
if t == nil {
|
||||
return false, fmt.Errorf("task should only hold Discrete type")
|
||||
}
|
||||
|
||||
if nodeRes == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
nrs := GetResource(t.Kind, nodeRes)
|
||||
if len(nrs) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch nr := nrs[0].Resource.(type) {
|
||||
case *api.GenericResource_DiscreteResourceSpec:
|
||||
if t.Value > nr.DiscreteResourceSpec.Value {
|
||||
return false, nil
|
||||
}
|
||||
case *api.GenericResource_NamedResourceSpec:
|
||||
if t.Value > int64(len(nrs)) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// HasResource checks if there is enough "res" in the "resources" argument
|
||||
func HasResource(res *api.GenericResource, resources []*api.GenericResource) bool {
|
||||
for _, r := range resources {
|
||||
if Kind(res) != Kind(r) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch rtype := r.Resource.(type) {
|
||||
case *api.GenericResource_DiscreteResourceSpec:
|
||||
if res.GetDiscreteResourceSpec() == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if res.GetDiscreteResourceSpec().Value < rtype.DiscreteResourceSpec.Value {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
case *api.GenericResource_NamedResourceSpec:
|
||||
if res.GetNamedResourceSpec() == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if res.GetNamedResourceSpec().Value != rtype.NamedResourceSpec.Value {
|
||||
continue
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
302
components/cli/vendor/github.com/docker/swarmkit/api/specs.pb.go
generated
vendored
302
components/cli/vendor/github.com/docker/swarmkit/api/specs.pb.go
generated
vendored
@ -620,6 +620,9 @@ type ContainerSpec struct {
|
||||
// Isolation defines the isolation level for windows containers (default, process, hyperv).
|
||||
// Runtimes that don't support it ignore that field
|
||||
Isolation ContainerSpec_Isolation `protobuf:"varint,24,opt,name=isolation,proto3,enum=docker.swarmkit.v1.ContainerSpec_Isolation" json:"isolation,omitempty"`
|
||||
// PidsLimit prevents from OS resource damage by applications inside the container
|
||||
// using fork bomb attack.
|
||||
PidsLimit int64 `protobuf:"varint,25,opt,name=pidsLimit,proto3" json:"pidsLimit,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ContainerSpec) Reset() { *m = ContainerSpec{} }
|
||||
@ -2055,6 +2058,13 @@ func (m *ContainerSpec) MarshalTo(dAtA []byte) (int, error) {
|
||||
i++
|
||||
i = encodeVarintSpecs(dAtA, i, uint64(m.Isolation))
|
||||
}
|
||||
if m.PidsLimit != 0 {
|
||||
dAtA[i] = 0xc8
|
||||
i++
|
||||
dAtA[i] = 0x1
|
||||
i++
|
||||
i = encodeVarintSpecs(dAtA, i, uint64(m.PidsLimit))
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
@ -2787,6 +2797,9 @@ func (m *ContainerSpec) Size() (n int) {
|
||||
if m.Isolation != 0 {
|
||||
n += 2 + sovSpecs(uint64(m.Isolation))
|
||||
}
|
||||
if m.PidsLimit != 0 {
|
||||
n += 2 + sovSpecs(uint64(m.PidsLimit))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -3134,6 +3147,7 @@ func (this *ContainerSpec) String() string {
|
||||
`Privileges:` + strings.Replace(fmt.Sprintf("%v", this.Privileges), "Privileges", "Privileges", 1) + `,`,
|
||||
`Init:` + strings.Replace(fmt.Sprintf("%v", this.Init), "BoolValue", "google_protobuf4.BoolValue", 1) + `,`,
|
||||
`Isolation:` + fmt.Sprintf("%v", this.Isolation) + `,`,
|
||||
`PidsLimit:` + fmt.Sprintf("%v", this.PidsLimit) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
@ -5261,6 +5275,25 @@ func (m *ContainerSpec) Unmarshal(dAtA []byte) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 25:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field PidsLimit", wireType)
|
||||
}
|
||||
m.PidsLimit = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowSpecs
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.PidsLimit |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipSpecs(dAtA[iNdEx:])
|
||||
@ -6572,138 +6605,139 @@ var (
|
||||
func init() { proto.RegisterFile("github.com/docker/swarmkit/api/specs.proto", fileDescriptorSpecs) }
|
||||
|
||||
var fileDescriptorSpecs = []byte{
|
||||
// 2114 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4f, 0x6f, 0xdb, 0xc8,
|
||||
0x15, 0xb7, 0x6c, 0x59, 0x96, 0x1e, 0xe5, 0x44, 0x9e, 0x4d, 0xb2, 0xb4, 0xb2, 0xb1, 0x15, 0x6d,
|
||||
0x36, 0xf5, 0xee, 0xa2, 0x32, 0xea, 0x2e, 0xb6, 0xd9, 0x4d, 0xb7, 0xad, 0x64, 0x69, 0x1d, 0x35,
|
||||
0x89, 0x2d, 0x8c, 0x1c, 0xb7, 0x01, 0x0a, 0x08, 0x63, 0x72, 0x2c, 0x11, 0xa6, 0x38, 0xec, 0x70,
|
||||
0xe8, 0x40, 0xb7, 0x1e, 0x17, 0xee, 0x67, 0x30, 0x7a, 0x28, 0x7a, 0x6f, 0xbf, 0x42, 0x4f, 0x39,
|
||||
0xf6, 0xd8, 0x5e, 0x8c, 0xae, 0xbf, 0x42, 0x6f, 0xbd, 0xb4, 0x98, 0xe1, 0x90, 0xa2, 0x1c, 0x3a,
|
||||
0x0e, 0xd0, 0x1c, 0x7a, 0x9b, 0x79, 0xfc, 0xfd, 0xde, 0xfc, 0xfb, 0xbd, 0x37, 0x6f, 0x08, 0x9f,
|
||||
0x0d, 0x1d, 0x31, 0x0a, 0x0f, 0x1b, 0x16, 0x1b, 0x6f, 0xda, 0xcc, 0x3a, 0xa6, 0x7c, 0x33, 0x78,
|
||||
0x45, 0xf8, 0xf8, 0xd8, 0x11, 0x9b, 0xc4, 0x77, 0x36, 0x03, 0x9f, 0x5a, 0x41, 0xc3, 0xe7, 0x4c,
|
||||
0x30, 0x84, 0x22, 0x40, 0x23, 0x06, 0x34, 0x4e, 0x7e, 0x54, 0xbd, 0x8e, 0x2f, 0x26, 0x3e, 0xd5,
|
||||
0xfc, 0xea, 0xad, 0x21, 0x1b, 0x32, 0xd5, 0xdc, 0x94, 0x2d, 0x6d, 0x5d, 0x1b, 0x32, 0x36, 0x74,
|
||||
0xe9, 0xa6, 0xea, 0x1d, 0x86, 0x47, 0x9b, 0x76, 0xc8, 0x89, 0x70, 0x98, 0xa7, 0xbf, 0xaf, 0x5e,
|
||||
0xfe, 0x4e, 0xbc, 0xc9, 0x55, 0xd4, 0x57, 0x9c, 0xf8, 0x3e, 0xe5, 0x7a, 0xc0, 0xfa, 0x59, 0x1e,
|
||||
0x8a, 0xbb, 0xcc, 0xa6, 0x7d, 0x9f, 0x5a, 0x68, 0x07, 0x0c, 0xe2, 0x79, 0x4c, 0x28, 0xdf, 0x81,
|
||||
0x99, 0xab, 0xe5, 0x36, 0x8c, 0xad, 0xf5, 0xc6, 0x9b, 0x6b, 0x6a, 0x34, 0xa7, 0xb0, 0x56, 0xfe,
|
||||
0xf5, 0xf9, 0xfa, 0x1c, 0x4e, 0x33, 0xd1, 0xcf, 0xa1, 0x6c, 0xd3, 0xc0, 0xe1, 0xd4, 0x1e, 0x70,
|
||||
0xe6, 0x52, 0x73, 0xbe, 0x96, 0xdb, 0xb8, 0xb1, 0xf5, 0x51, 0x96, 0x27, 0x39, 0x38, 0x66, 0x2e,
|
||||
0xc5, 0x86, 0x66, 0xc8, 0x0e, 0xda, 0x01, 0x18, 0xd3, 0xf1, 0x21, 0xe5, 0xc1, 0xc8, 0xf1, 0xcd,
|
||||
0x05, 0x45, 0xff, 0xc1, 0x55, 0x74, 0x39, 0xf7, 0xc6, 0xf3, 0x04, 0x8e, 0x53, 0x54, 0xf4, 0x1c,
|
||||
0xca, 0xe4, 0x84, 0x38, 0x2e, 0x39, 0x74, 0x5c, 0x47, 0x4c, 0xcc, 0xbc, 0x72, 0xf5, 0xe9, 0x5b,
|
||||
0x5d, 0x35, 0x53, 0x04, 0x3c, 0x43, 0xaf, 0xdb, 0x00, 0xd3, 0x81, 0xd0, 0x43, 0x58, 0xea, 0x75,
|
||||
0x76, 0xdb, 0xdd, 0xdd, 0x9d, 0xca, 0x5c, 0x75, 0xf5, 0xf4, 0xac, 0x76, 0x5b, 0xfa, 0x98, 0x02,
|
||||
0x7a, 0xd4, 0xb3, 0x1d, 0x6f, 0x88, 0x36, 0xa0, 0xd8, 0xdc, 0xde, 0xee, 0xf4, 0xf6, 0x3b, 0xed,
|
||||
0x4a, 0xae, 0x5a, 0x3d, 0x3d, 0xab, 0xdd, 0x99, 0x05, 0x36, 0x2d, 0x8b, 0xfa, 0x82, 0xda, 0xd5,
|
||||
0xfc, 0x77, 0x7f, 0x5c, 0x9b, 0xab, 0x7f, 0x97, 0x83, 0x72, 0x7a, 0x12, 0xe8, 0x21, 0x14, 0x9a,
|
||||
0xdb, 0xfb, 0xdd, 0x83, 0x4e, 0x65, 0x6e, 0x4a, 0x4f, 0x23, 0x9a, 0x96, 0x70, 0x4e, 0x28, 0x7a,
|
||||
0x00, 0x8b, 0xbd, 0xe6, 0x8b, 0x7e, 0xa7, 0x92, 0x9b, 0x4e, 0x27, 0x0d, 0xeb, 0x91, 0x30, 0x50,
|
||||
0xa8, 0x36, 0x6e, 0x76, 0x77, 0x2b, 0xf3, 0xd9, 0xa8, 0x36, 0x27, 0x8e, 0xa7, 0xa7, 0xf2, 0x87,
|
||||
0x3c, 0x18, 0x7d, 0xca, 0x4f, 0x1c, 0xeb, 0x3d, 0x4b, 0xe4, 0x4b, 0xc8, 0x0b, 0x12, 0x1c, 0x2b,
|
||||
0x69, 0x18, 0xd9, 0xd2, 0xd8, 0x27, 0xc1, 0xb1, 0x1c, 0x54, 0xd3, 0x15, 0x5e, 0x2a, 0x83, 0x53,
|
||||
0xdf, 0x75, 0x2c, 0x22, 0xa8, 0xad, 0x94, 0x61, 0x6c, 0x7d, 0x92, 0xc5, 0xc6, 0x09, 0x4a, 0xcf,
|
||||
0xff, 0xc9, 0x1c, 0x4e, 0x51, 0xd1, 0x63, 0x28, 0x0c, 0x5d, 0x76, 0x48, 0x5c, 0xa5, 0x09, 0x63,
|
||||
0xeb, 0x7e, 0x96, 0x93, 0x1d, 0x85, 0x98, 0x3a, 0xd0, 0x14, 0xf4, 0x08, 0x0a, 0xa1, 0x6f, 0x13,
|
||||
0x41, 0xcd, 0x82, 0x22, 0xd7, 0xb2, 0xc8, 0x2f, 0x14, 0x62, 0x9b, 0x79, 0x47, 0xce, 0x10, 0x6b,
|
||||
0x3c, 0x7a, 0x0a, 0x45, 0x8f, 0x8a, 0x57, 0x8c, 0x1f, 0x07, 0xe6, 0x52, 0x6d, 0x61, 0xc3, 0xd8,
|
||||
0xfa, 0x3c, 0x53, 0x8c, 0x11, 0xa6, 0x29, 0x04, 0xb1, 0x46, 0x63, 0xea, 0x89, 0xc8, 0x4d, 0x6b,
|
||||
0xde, 0xcc, 0xe1, 0xc4, 0x01, 0xfa, 0x29, 0x14, 0xa9, 0x67, 0xfb, 0xcc, 0xf1, 0x84, 0x59, 0xbc,
|
||||
0x7a, 0x22, 0x1d, 0x8d, 0x91, 0x9b, 0x89, 0x13, 0x86, 0x64, 0x73, 0xe6, 0xba, 0x87, 0xc4, 0x3a,
|
||||
0x36, 0x4b, 0xef, 0xb8, 0x8c, 0x84, 0xd1, 0x2a, 0x40, 0x7e, 0xcc, 0x6c, 0x5a, 0xdf, 0x84, 0x95,
|
||||
0x37, 0xb6, 0x1a, 0x55, 0xa1, 0xa8, 0xb7, 0x3a, 0xd2, 0x48, 0x1e, 0x27, 0xfd, 0xfa, 0x4d, 0x58,
|
||||
0x9e, 0xd9, 0xd6, 0xfa, 0x9f, 0x17, 0xa1, 0x18, 0x9f, 0x35, 0x6a, 0x42, 0xc9, 0x62, 0x9e, 0x20,
|
||||
0x8e, 0x47, 0xb9, 0x96, 0x57, 0xe6, 0xc9, 0x6c, 0xc7, 0x20, 0xc9, 0x7a, 0x32, 0x87, 0xa7, 0x2c,
|
||||
0xf4, 0x2d, 0x94, 0x38, 0x0d, 0x58, 0xc8, 0x2d, 0x1a, 0x68, 0x7d, 0x6d, 0x64, 0x2b, 0x24, 0x02,
|
||||
0x61, 0xfa, 0xdb, 0xd0, 0xe1, 0x54, 0xee, 0x72, 0x80, 0xa7, 0x54, 0xf4, 0x18, 0x96, 0x38, 0x0d,
|
||||
0x04, 0xe1, 0xe2, 0x6d, 0x12, 0xc1, 0x11, 0xa4, 0xc7, 0x5c, 0xc7, 0x9a, 0xe0, 0x98, 0x81, 0x1e,
|
||||
0x43, 0xc9, 0x77, 0x89, 0xa5, 0xbc, 0x9a, 0x8b, 0x8a, 0x7e, 0x2f, 0x8b, 0xde, 0x8b, 0x41, 0x78,
|
||||
0x8a, 0x47, 0x5f, 0x01, 0xb8, 0x6c, 0x38, 0xb0, 0xb9, 0x73, 0x42, 0xb9, 0x96, 0x58, 0x35, 0x8b,
|
||||
0xdd, 0x56, 0x08, 0x5c, 0x72, 0xd9, 0x30, 0x6a, 0xa2, 0x9d, 0xff, 0x49, 0x5f, 0x29, 0x6d, 0x3d,
|
||||
0x05, 0x20, 0xc9, 0x57, 0xad, 0xae, 0x4f, 0xdf, 0xc9, 0x95, 0x3e, 0x91, 0x14, 0x1d, 0xdd, 0x87,
|
||||
0xf2, 0x11, 0xe3, 0x16, 0x1d, 0xe8, 0xa8, 0x29, 0x29, 0x4d, 0x18, 0xca, 0x16, 0xe9, 0x0b, 0xb5,
|
||||
0x60, 0x69, 0x48, 0x3d, 0xca, 0x1d, 0xcb, 0x04, 0x35, 0xd8, 0xc3, 0xcc, 0x80, 0x8c, 0x20, 0x38,
|
||||
0xf4, 0x84, 0x33, 0xa6, 0x7a, 0xa4, 0x98, 0x88, 0x7e, 0x03, 0x1f, 0xc4, 0xc7, 0x37, 0xe0, 0xf4,
|
||||
0x88, 0x72, 0xea, 0x49, 0x0d, 0x18, 0x6a, 0x1f, 0x3e, 0x79, 0xbb, 0x06, 0x34, 0x5a, 0x27, 0x1b,
|
||||
0xc4, 0x2f, 0x7f, 0x08, 0x5a, 0x25, 0x58, 0xe2, 0xd1, 0xb8, 0xf5, 0xdf, 0xe7, 0xa4, 0xea, 0x2f,
|
||||
0x21, 0xd0, 0x26, 0x18, 0xc9, 0xf0, 0x8e, 0xad, 0xd4, 0x5b, 0x6a, 0xdd, 0xb8, 0x38, 0x5f, 0x87,
|
||||
0x18, 0xdb, 0x6d, 0xcb, 0x1c, 0xa4, 0xdb, 0x36, 0xea, 0xc0, 0x72, 0x42, 0x90, 0x65, 0x80, 0xbe,
|
||||
0x28, 0x6b, 0x6f, 0x9b, 0xe9, 0xfe, 0xc4, 0xa7, 0xb8, 0xcc, 0x53, 0xbd, 0xfa, 0xaf, 0x01, 0xbd,
|
||||
0xb9, 0x2f, 0x08, 0x41, 0xfe, 0xd8, 0xf1, 0xf4, 0x34, 0xb0, 0x6a, 0xa3, 0x06, 0x2c, 0xf9, 0x64,
|
||||
0xe2, 0x32, 0x62, 0xeb, 0xc0, 0xb8, 0xd5, 0x88, 0x0a, 0x84, 0x46, 0x5c, 0x20, 0x34, 0x9a, 0xde,
|
||||
0x04, 0xc7, 0xa0, 0xfa, 0x53, 0xb8, 0x9d, 0x79, 0xbc, 0x68, 0x0b, 0xca, 0x49, 0xc0, 0x4d, 0xd7,
|
||||
0x7a, 0xf3, 0xe2, 0x7c, 0xdd, 0x48, 0x22, 0xb3, 0xdb, 0xc6, 0x46, 0x02, 0xea, 0xda, 0xf5, 0xbf,
|
||||
0x1a, 0xb0, 0x3c, 0x13, 0xb6, 0xe8, 0x16, 0x2c, 0x3a, 0x63, 0x32, 0xa4, 0x7a, 0x8e, 0x51, 0x07,
|
||||
0x75, 0xa0, 0xe0, 0x92, 0x43, 0xea, 0xca, 0xe0, 0x95, 0x07, 0xf7, 0xc3, 0x6b, 0xe3, 0xbf, 0xf1,
|
||||
0x4c, 0xe1, 0x3b, 0x9e, 0xe0, 0x13, 0xac, 0xc9, 0xc8, 0x84, 0x25, 0x8b, 0x8d, 0xc7, 0xc4, 0x93,
|
||||
0xd7, 0xc4, 0xc2, 0x46, 0x09, 0xc7, 0x5d, 0xb9, 0x33, 0x84, 0x0f, 0x03, 0x33, 0xaf, 0xcc, 0xaa,
|
||||
0x8d, 0x2a, 0xb0, 0x40, 0xbd, 0x13, 0x73, 0x51, 0x99, 0x64, 0x53, 0x5a, 0x6c, 0x27, 0x8a, 0xbe,
|
||||
0x12, 0x96, 0x4d, 0xc9, 0x0b, 0x03, 0xca, 0xcd, 0xa5, 0x68, 0x47, 0x65, 0x1b, 0xfd, 0x04, 0x0a,
|
||||
0x63, 0x16, 0x7a, 0x22, 0x30, 0x8b, 0x6a, 0xb2, 0xab, 0x59, 0x93, 0x7d, 0x2e, 0x11, 0x5a, 0x59,
|
||||
0x1a, 0x8e, 0x3a, 0xb0, 0x12, 0x08, 0xe6, 0x0f, 0x86, 0x9c, 0x58, 0x74, 0xe0, 0x53, 0xee, 0x30,
|
||||
0x5b, 0xa7, 0xe1, 0xd5, 0x37, 0x0e, 0xa5, 0xad, 0x0b, 0x3e, 0x7c, 0x53, 0x72, 0x76, 0x24, 0xa5,
|
||||
0xa7, 0x18, 0xa8, 0x07, 0x65, 0x3f, 0x74, 0xdd, 0x01, 0xf3, 0xa3, 0x1b, 0x39, 0x8a, 0x9d, 0x77,
|
||||
0xd8, 0xb2, 0x5e, 0xe8, 0xba, 0x7b, 0x11, 0x09, 0x1b, 0xfe, 0xb4, 0x83, 0xee, 0x40, 0x61, 0xc8,
|
||||
0x59, 0xe8, 0x47, 0x71, 0x53, 0xc2, 0xba, 0x87, 0xbe, 0x81, 0xa5, 0x80, 0x5a, 0x9c, 0x8a, 0xc0,
|
||||
0x2c, 0xab, 0xa5, 0x7e, 0x9c, 0x35, 0x48, 0x5f, 0x41, 0x92, 0x98, 0xc0, 0x31, 0x07, 0xad, 0xc2,
|
||||
0x82, 0x10, 0x13, 0x73, 0xb9, 0x96, 0xdb, 0x28, 0xb6, 0x96, 0x2e, 0xce, 0xd7, 0x17, 0xf6, 0xf7,
|
||||
0x5f, 0x62, 0x69, 0x93, 0xb7, 0xc5, 0x88, 0x05, 0xc2, 0x23, 0x63, 0x6a, 0xde, 0x50, 0x7b, 0x9b,
|
||||
0xf4, 0xd1, 0x4b, 0x00, 0xdb, 0x0b, 0x06, 0x96, 0x4a, 0x4f, 0xe6, 0x4d, 0xb5, 0xba, 0xcf, 0xaf,
|
||||
0x5f, 0x5d, 0x7b, 0xb7, 0xaf, 0x6f, 0xcc, 0xe5, 0x8b, 0xf3, 0xf5, 0x52, 0xd2, 0xc5, 0x25, 0xdb,
|
||||
0x0b, 0xa2, 0x26, 0x6a, 0x81, 0x31, 0xa2, 0xc4, 0x15, 0x23, 0x6b, 0x44, 0xad, 0x63, 0xb3, 0x72,
|
||||
0xf5, 0x15, 0xf8, 0x44, 0xc1, 0xb4, 0x87, 0x34, 0x49, 0x2a, 0x58, 0x4e, 0x35, 0x30, 0x57, 0xd4,
|
||||
0x5e, 0x45, 0x1d, 0x74, 0x0f, 0x80, 0xf9, 0xd4, 0x1b, 0x04, 0xc2, 0x76, 0x3c, 0x13, 0xc9, 0x25,
|
||||
0xe3, 0x92, 0xb4, 0xf4, 0xa5, 0x01, 0xdd, 0x95, 0x17, 0x14, 0xb1, 0x07, 0xcc, 0x73, 0x27, 0xe6,
|
||||
0x07, 0xea, 0x6b, 0x51, 0x1a, 0xf6, 0x3c, 0x77, 0x82, 0xd6, 0xc1, 0x50, 0xba, 0x08, 0x9c, 0xa1,
|
||||
0x47, 0x5c, 0xf3, 0x96, 0xda, 0x0f, 0x90, 0xa6, 0xbe, 0xb2, 0xc8, 0x73, 0x88, 0x76, 0x23, 0x30,
|
||||
0x6f, 0x5f, 0x7d, 0x0e, 0x7a, 0xb2, 0xd3, 0x73, 0xd0, 0x1c, 0xf4, 0x33, 0x00, 0x9f, 0x3b, 0x27,
|
||||
0x8e, 0x4b, 0x87, 0x34, 0x30, 0xef, 0xa8, 0x45, 0xaf, 0x65, 0xde, 0x4c, 0x09, 0x0a, 0xa7, 0x18,
|
||||
0xa8, 0x01, 0x79, 0xc7, 0x73, 0x84, 0xf9, 0xa1, 0xbe, 0x95, 0x2e, 0x4b, 0xb5, 0xc5, 0x98, 0x7b,
|
||||
0x40, 0xdc, 0x90, 0x62, 0x85, 0x43, 0x5d, 0x28, 0x39, 0x01, 0x73, 0x95, 0x7c, 0x4d, 0x53, 0xe5,
|
||||
0xb7, 0x77, 0x38, 0xbf, 0x6e, 0x4c, 0xc1, 0x53, 0x76, 0xf5, 0x2b, 0x30, 0x52, 0x81, 0x2e, 0x03,
|
||||
0xf4, 0x98, 0x4e, 0x74, 0xee, 0x90, 0x4d, 0x79, 0x1a, 0x27, 0x72, 0x68, 0x95, 0xdc, 0x4a, 0x38,
|
||||
0xea, 0x7c, 0x3d, 0xff, 0x28, 0x57, 0xdd, 0x02, 0x23, 0x25, 0x78, 0xf4, 0xb1, 0x4c, 0xbc, 0x43,
|
||||
0x27, 0x10, 0x7c, 0x32, 0x20, 0xa1, 0x18, 0x99, 0xbf, 0x50, 0x84, 0x72, 0x6c, 0x6c, 0x86, 0x62,
|
||||
0x54, 0x1d, 0xc0, 0x54, 0x37, 0xa8, 0x06, 0x86, 0xd4, 0x63, 0x40, 0xf9, 0x09, 0xe5, 0xb2, 0xa8,
|
||||
0x91, 0xc7, 0x9d, 0x36, 0xc9, 0xb8, 0x09, 0x28, 0xe1, 0xd6, 0x48, 0xa5, 0xad, 0x12, 0xd6, 0x3d,
|
||||
0x99, 0x87, 0xe2, 0xe0, 0xd4, 0x79, 0x48, 0x77, 0xeb, 0x7f, 0xc9, 0x41, 0x29, 0x59, 0x28, 0xfa,
|
||||
0x02, 0x56, 0xba, 0xfd, 0xbd, 0x67, 0xcd, 0xfd, 0xee, 0xde, 0xee, 0xa0, 0xdd, 0xf9, 0xb6, 0xf9,
|
||||
0xe2, 0xd9, 0x7e, 0x65, 0xae, 0x7a, 0xef, 0xf4, 0xac, 0xb6, 0x3a, 0xcd, 0xa9, 0x31, 0xbc, 0x4d,
|
||||
0x8f, 0x48, 0xe8, 0x8a, 0x59, 0x56, 0x0f, 0xef, 0x6d, 0x77, 0xfa, 0xfd, 0x4a, 0xee, 0x2a, 0x56,
|
||||
0x8f, 0x33, 0x8b, 0x06, 0x01, 0xda, 0x82, 0xca, 0x94, 0xf5, 0xe4, 0x65, 0xaf, 0x83, 0x0f, 0x2a,
|
||||
0xf3, 0xd5, 0x8f, 0x4e, 0xcf, 0x6a, 0xe6, 0x9b, 0xa4, 0x27, 0x13, 0x9f, 0xf2, 0x03, 0xfd, 0x20,
|
||||
0xf8, 0x57, 0x0e, 0xca, 0xe9, 0x7a, 0x12, 0x6d, 0x47, 0x75, 0xa0, 0x3a, 0x86, 0x1b, 0x5b, 0x9b,
|
||||
0xd7, 0xd5, 0x9f, 0xea, 0x1e, 0x73, 0x43, 0xe9, 0xf7, 0xb9, 0x7c, 0xfa, 0x29, 0x32, 0xfa, 0x02,
|
||||
0x16, 0x7d, 0xc6, 0x45, 0x9c, 0xf1, 0xb3, 0xf5, 0xc8, 0x78, 0x5c, 0xa5, 0x44, 0xe0, 0xfa, 0x08,
|
||||
0x6e, 0xcc, 0x7a, 0x43, 0x0f, 0x60, 0xe1, 0xa0, 0xdb, 0xab, 0xcc, 0x55, 0xef, 0x9e, 0x9e, 0xd5,
|
||||
0x3e, 0x9c, 0xfd, 0x78, 0xe0, 0x70, 0x11, 0x12, 0xb7, 0xdb, 0x43, 0x9f, 0xc1, 0x62, 0x7b, 0xb7,
|
||||
0x8f, 0x71, 0x25, 0x57, 0x5d, 0x3f, 0x3d, 0xab, 0xdd, 0x9d, 0xc5, 0xc9, 0x4f, 0x2c, 0xf4, 0x6c,
|
||||
0xcc, 0x0e, 0x93, 0x67, 0xd0, 0xbf, 0xe7, 0xc1, 0xd0, 0x17, 0xe1, 0xfb, 0x7e, 0x29, 0x2f, 0x47,
|
||||
0x55, 0x5e, 0x9c, 0xe1, 0xe6, 0xaf, 0x2d, 0xf6, 0xca, 0x11, 0x41, 0xeb, 0xf2, 0x3e, 0x94, 0x1d,
|
||||
0xff, 0xe4, 0xcb, 0x01, 0xf5, 0xc8, 0xa1, 0xab, 0x5f, 0x44, 0x45, 0x6c, 0x48, 0x5b, 0x27, 0x32,
|
||||
0xc9, 0xf4, 0xea, 0x78, 0x82, 0x72, 0x4f, 0xbf, 0x75, 0x8a, 0x38, 0xe9, 0xa3, 0x6f, 0x20, 0xef,
|
||||
0xf8, 0x64, 0xac, 0x2b, 0xd4, 0xcc, 0x15, 0x74, 0x7b, 0xcd, 0xe7, 0x3a, 0x6e, 0x5a, 0xc5, 0x8b,
|
||||
0xf3, 0xf5, 0xbc, 0x34, 0x60, 0x45, 0x43, 0x6b, 0x71, 0x91, 0x28, 0x47, 0x52, 0x57, 0x65, 0x11,
|
||||
0xa7, 0x2c, 0x52, 0xfb, 0x8e, 0x37, 0xe4, 0x34, 0x08, 0xd4, 0xa5, 0x59, 0xc4, 0x71, 0x17, 0x55,
|
||||
0x61, 0x49, 0x97, 0x9a, 0xaa, 0xb6, 0x2c, 0xc9, 0x32, 0x4e, 0x1b, 0x5a, 0xcb, 0x60, 0x44, 0xbb,
|
||||
0x31, 0x38, 0xe2, 0x6c, 0x5c, 0xff, 0x4f, 0x1e, 0x8c, 0x6d, 0x37, 0x0c, 0x84, 0xae, 0x1a, 0xde,
|
||||
0xdb, 0xe6, 0xbf, 0x84, 0x15, 0xa2, 0x5e, 0xde, 0xc4, 0x93, 0x57, 0xb0, 0xaa, 0xe0, 0xf5, 0x01,
|
||||
0x3c, 0xc8, 0x74, 0x97, 0x80, 0xa3, 0x6a, 0xbf, 0x55, 0x90, 0x3e, 0xcd, 0x1c, 0xae, 0x90, 0x4b,
|
||||
0x5f, 0x50, 0x1f, 0x96, 0x19, 0xb7, 0x46, 0x34, 0x10, 0xd1, 0xc5, 0xad, 0x5f, 0xaa, 0x99, 0xff,
|
||||
0x30, 0xf6, 0xd2, 0x40, 0x7d, 0x6b, 0x45, 0xb3, 0x9d, 0xf5, 0x81, 0x1e, 0x41, 0x9e, 0x93, 0xa3,
|
||||
0xf8, 0x35, 0x92, 0x19, 0x24, 0x98, 0x1c, 0x89, 0x19, 0x17, 0x8a, 0x81, 0x7e, 0x09, 0x60, 0x3b,
|
||||
0x81, 0x4f, 0x84, 0x35, 0xa2, 0x5c, 0x1f, 0x76, 0xe6, 0x12, 0xdb, 0x09, 0x6a, 0xc6, 0x4b, 0x8a,
|
||||
0x8d, 0x9e, 0x42, 0xc9, 0x22, 0xb1, 0x5c, 0x0b, 0x57, 0x3f, 0xdf, 0xb7, 0x9b, 0xda, 0x45, 0x45,
|
||||
0xba, 0xb8, 0x38, 0x5f, 0x2f, 0xc6, 0x16, 0x5c, 0xb4, 0x88, 0x96, 0xef, 0x53, 0x58, 0x96, 0xcf,
|
||||
0xfa, 0x81, 0x1d, 0xa5, 0xb3, 0x48, 0x26, 0x57, 0xdc, 0xc2, 0xf2, 0x8d, 0xa8, 0xd3, 0x5e, 0x7c,
|
||||
0x9c, 0x65, 0x91, 0xb2, 0xa1, 0x5f, 0xc1, 0x0a, 0xf5, 0x2c, 0x3e, 0x51, 0x62, 0x8d, 0x67, 0x58,
|
||||
0xbc, 0x7a, 0xb1, 0x9d, 0x04, 0x3c, 0xb3, 0xd8, 0x0a, 0xbd, 0x64, 0xaf, 0xff, 0x23, 0x07, 0x10,
|
||||
0x15, 0x36, 0xef, 0x57, 0x80, 0x08, 0xf2, 0x36, 0x11, 0x44, 0x69, 0xae, 0x8c, 0x55, 0x1b, 0x7d,
|
||||
0x0d, 0x20, 0xe8, 0xd8, 0x97, 0xa9, 0xd7, 0x1b, 0x6a, 0xd9, 0xbc, 0x2d, 0x1d, 0xa4, 0xd0, 0x68,
|
||||
0x0b, 0x0a, 0xfa, 0xcd, 0x98, 0xbf, 0x96, 0xa7, 0x91, 0xf5, 0x3f, 0xe5, 0x00, 0xa2, 0x65, 0xfe,
|
||||
0x5f, 0xaf, 0xad, 0x65, 0xbe, 0xfe, 0x7e, 0x6d, 0xee, 0xef, 0xdf, 0xaf, 0xcd, 0xfd, 0xee, 0x62,
|
||||
0x2d, 0xf7, 0xfa, 0x62, 0x2d, 0xf7, 0xb7, 0x8b, 0xb5, 0xdc, 0x3f, 0x2f, 0xd6, 0x72, 0x87, 0x05,
|
||||
0x55, 0x7b, 0xfc, 0xf8, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x1a, 0xbd, 0x13, 0xac, 0xa9, 0x15,
|
||||
0x00, 0x00,
|
||||
// 2131 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4f, 0x6f, 0x1b, 0xc7,
|
||||
0x15, 0x17, 0x25, 0x8a, 0x22, 0xdf, 0x52, 0x36, 0x35, 0x71, 0x9c, 0x15, 0x6d, 0x4b, 0x34, 0xe3,
|
||||
0xb8, 0x4a, 0x82, 0x52, 0xa8, 0x1a, 0xa4, 0x4e, 0xdc, 0xb4, 0x25, 0x45, 0x46, 0x66, 0x6d, 0x4b,
|
||||
0xc4, 0x50, 0x56, 0x6b, 0xa0, 0x00, 0x31, 0xda, 0x1d, 0x91, 0x03, 0x2d, 0x77, 0xb6, 0xb3, 0x43,
|
||||
0x19, 0xbc, 0xf5, 0x18, 0xa8, 0x9f, 0x41, 0xe8, 0xa1, 0xe8, 0xbd, 0xfd, 0x16, 0x3e, 0xf6, 0xd8,
|
||||
0x5e, 0x84, 0x44, 0x5f, 0xa1, 0xb7, 0x5e, 0x5a, 0xcc, 0xec, 0xec, 0x92, 0x94, 0x57, 0x96, 0x81,
|
||||
0xfa, 0xd0, 0xdb, 0xcc, 0xdb, 0xdf, 0xef, 0xcd, 0xbf, 0xdf, 0xbc, 0xf7, 0x66, 0xe1, 0xb3, 0x3e,
|
||||
0x93, 0x83, 0xd1, 0x61, 0xcd, 0xe1, 0xc3, 0x4d, 0x97, 0x3b, 0xc7, 0x54, 0x6c, 0x86, 0xaf, 0x88,
|
||||
0x18, 0x1e, 0x33, 0xb9, 0x49, 0x02, 0xb6, 0x19, 0x06, 0xd4, 0x09, 0x6b, 0x81, 0xe0, 0x92, 0x23,
|
||||
0x14, 0x01, 0x6a, 0x31, 0xa0, 0x76, 0xf2, 0x93, 0xf2, 0x75, 0x7c, 0x39, 0x0e, 0xa8, 0xe1, 0x97,
|
||||
0x6f, 0xf5, 0x79, 0x9f, 0xeb, 0xe6, 0xa6, 0x6a, 0x19, 0xeb, 0x5a, 0x9f, 0xf3, 0xbe, 0x47, 0x37,
|
||||
0x75, 0xef, 0x70, 0x74, 0xb4, 0xe9, 0x8e, 0x04, 0x91, 0x8c, 0xfb, 0xe6, 0xfb, 0xea, 0xe5, 0xef,
|
||||
0xc4, 0x1f, 0x5f, 0x45, 0x7d, 0x25, 0x48, 0x10, 0x50, 0x61, 0x06, 0xac, 0x9e, 0x65, 0x21, 0xbf,
|
||||
0xcb, 0x5d, 0xda, 0x0d, 0xa8, 0x83, 0x76, 0xc0, 0x22, 0xbe, 0xcf, 0xa5, 0xf6, 0x1d, 0xda, 0x99,
|
||||
0x4a, 0x66, 0xc3, 0xda, 0x5a, 0xaf, 0xbd, 0xb9, 0xa6, 0x5a, 0x7d, 0x02, 0x6b, 0x64, 0x5f, 0x9f,
|
||||
0xaf, 0xcf, 0xe1, 0x69, 0x26, 0xfa, 0x25, 0x14, 0x5d, 0x1a, 0x32, 0x41, 0xdd, 0x9e, 0xe0, 0x1e,
|
||||
0xb5, 0xe7, 0x2b, 0x99, 0x8d, 0x1b, 0x5b, 0x77, 0xd3, 0x3c, 0xa9, 0xc1, 0x31, 0xf7, 0x28, 0xb6,
|
||||
0x0c, 0x43, 0x75, 0xd0, 0x0e, 0xc0, 0x90, 0x0e, 0x0f, 0xa9, 0x08, 0x07, 0x2c, 0xb0, 0x17, 0x34,
|
||||
0xfd, 0x47, 0x57, 0xd1, 0xd5, 0xdc, 0x6b, 0xcf, 0x13, 0x38, 0x9e, 0xa2, 0xa2, 0xe7, 0x50, 0x24,
|
||||
0x27, 0x84, 0x79, 0xe4, 0x90, 0x79, 0x4c, 0x8e, 0xed, 0xac, 0x76, 0xf5, 0xe9, 0x5b, 0x5d, 0xd5,
|
||||
0xa7, 0x08, 0x78, 0x86, 0x5e, 0x75, 0x01, 0x26, 0x03, 0xa1, 0x87, 0xb0, 0xd4, 0x69, 0xed, 0x36,
|
||||
0xdb, 0xbb, 0x3b, 0xa5, 0xb9, 0xf2, 0xea, 0xe9, 0x59, 0xe5, 0x43, 0xe5, 0x63, 0x02, 0xe8, 0x50,
|
||||
0xdf, 0x65, 0x7e, 0x1f, 0x6d, 0x40, 0xbe, 0xbe, 0xbd, 0xdd, 0xea, 0xec, 0xb7, 0x9a, 0xa5, 0x4c,
|
||||
0xb9, 0x7c, 0x7a, 0x56, 0xb9, 0x3d, 0x0b, 0xac, 0x3b, 0x0e, 0x0d, 0x24, 0x75, 0xcb, 0xd9, 0xef,
|
||||
0xfe, 0xbc, 0x36, 0x57, 0xfd, 0x2e, 0x03, 0xc5, 0xe9, 0x49, 0xa0, 0x87, 0x90, 0xab, 0x6f, 0xef,
|
||||
0xb7, 0x0f, 0x5a, 0xa5, 0xb9, 0x09, 0x7d, 0x1a, 0x51, 0x77, 0x24, 0x3b, 0xa1, 0xe8, 0x01, 0x2c,
|
||||
0x76, 0xea, 0x2f, 0xba, 0xad, 0x52, 0x66, 0x32, 0x9d, 0x69, 0x58, 0x87, 0x8c, 0x42, 0x8d, 0x6a,
|
||||
0xe2, 0x7a, 0x7b, 0xb7, 0x34, 0x9f, 0x8e, 0x6a, 0x0a, 0xc2, 0x7c, 0x33, 0x95, 0x3f, 0x65, 0xc1,
|
||||
0xea, 0x52, 0x71, 0xc2, 0x9c, 0xf7, 0x2c, 0x91, 0x2f, 0x21, 0x2b, 0x49, 0x78, 0xac, 0xa5, 0x61,
|
||||
0xa5, 0x4b, 0x63, 0x9f, 0x84, 0xc7, 0x6a, 0x50, 0x43, 0xd7, 0x78, 0xa5, 0x0c, 0x41, 0x03, 0x8f,
|
||||
0x39, 0x44, 0x52, 0x57, 0x2b, 0xc3, 0xda, 0xfa, 0x24, 0x8d, 0x8d, 0x13, 0x94, 0x99, 0xff, 0x93,
|
||||
0x39, 0x3c, 0x45, 0x45, 0x8f, 0x21, 0xd7, 0xf7, 0xf8, 0x21, 0xf1, 0xb4, 0x26, 0xac, 0xad, 0xfb,
|
||||
0x69, 0x4e, 0x76, 0x34, 0x62, 0xe2, 0xc0, 0x50, 0xd0, 0x23, 0xc8, 0x8d, 0x02, 0x97, 0x48, 0x6a,
|
||||
0xe7, 0x34, 0xb9, 0x92, 0x46, 0x7e, 0xa1, 0x11, 0xdb, 0xdc, 0x3f, 0x62, 0x7d, 0x6c, 0xf0, 0xe8,
|
||||
0x29, 0xe4, 0x7d, 0x2a, 0x5f, 0x71, 0x71, 0x1c, 0xda, 0x4b, 0x95, 0x85, 0x0d, 0x6b, 0xeb, 0xf3,
|
||||
0x54, 0x31, 0x46, 0x98, 0xba, 0x94, 0xc4, 0x19, 0x0c, 0xa9, 0x2f, 0x23, 0x37, 0x8d, 0x79, 0x3b,
|
||||
0x83, 0x13, 0x07, 0xe8, 0xe7, 0x90, 0xa7, 0xbe, 0x1b, 0x70, 0xe6, 0x4b, 0x3b, 0x7f, 0xf5, 0x44,
|
||||
0x5a, 0x06, 0xa3, 0x36, 0x13, 0x27, 0x0c, 0xc5, 0x16, 0xdc, 0xf3, 0x0e, 0x89, 0x73, 0x6c, 0x17,
|
||||
0xde, 0x71, 0x19, 0x09, 0xa3, 0x91, 0x83, 0xec, 0x90, 0xbb, 0xb4, 0xba, 0x09, 0x2b, 0x6f, 0x6c,
|
||||
0x35, 0x2a, 0x43, 0xde, 0x6c, 0x75, 0xa4, 0x91, 0x2c, 0x4e, 0xfa, 0xd5, 0x9b, 0xb0, 0x3c, 0xb3,
|
||||
0xad, 0xd5, 0xbf, 0x2e, 0x42, 0x3e, 0x3e, 0x6b, 0x54, 0x87, 0x82, 0xc3, 0x7d, 0x49, 0x98, 0x4f,
|
||||
0x85, 0x91, 0x57, 0xea, 0xc9, 0x6c, 0xc7, 0x20, 0xc5, 0x7a, 0x32, 0x87, 0x27, 0x2c, 0xf4, 0x2d,
|
||||
0x14, 0x04, 0x0d, 0xf9, 0x48, 0x38, 0x34, 0x34, 0xfa, 0xda, 0x48, 0x57, 0x48, 0x04, 0xc2, 0xf4,
|
||||
0xf7, 0x23, 0x26, 0xa8, 0xda, 0xe5, 0x10, 0x4f, 0xa8, 0xe8, 0x31, 0x2c, 0x09, 0x1a, 0x4a, 0x22,
|
||||
0xe4, 0xdb, 0x24, 0x82, 0x23, 0x48, 0x87, 0x7b, 0xcc, 0x19, 0xe3, 0x98, 0x81, 0x1e, 0x43, 0x21,
|
||||
0xf0, 0x88, 0xa3, 0xbd, 0xda, 0x8b, 0x9a, 0x7e, 0x2f, 0x8d, 0xde, 0x89, 0x41, 0x78, 0x82, 0x47,
|
||||
0x5f, 0x01, 0x78, 0xbc, 0xdf, 0x73, 0x05, 0x3b, 0xa1, 0xc2, 0x48, 0xac, 0x9c, 0xc6, 0x6e, 0x6a,
|
||||
0x04, 0x2e, 0x78, 0xbc, 0x1f, 0x35, 0xd1, 0xce, 0xff, 0xa4, 0xaf, 0x29, 0x6d, 0x3d, 0x05, 0x20,
|
||||
0xc9, 0x57, 0xa3, 0xae, 0x4f, 0xdf, 0xc9, 0x95, 0x39, 0x91, 0x29, 0x3a, 0xba, 0x0f, 0xc5, 0x23,
|
||||
0x2e, 0x1c, 0xda, 0x33, 0xb7, 0xa6, 0xa0, 0x35, 0x61, 0x69, 0x5b, 0xa4, 0x2f, 0xd4, 0x80, 0xa5,
|
||||
0x3e, 0xf5, 0xa9, 0x60, 0x8e, 0x0d, 0x7a, 0xb0, 0x87, 0xa9, 0x17, 0x32, 0x82, 0xe0, 0x91, 0x2f,
|
||||
0xd9, 0x90, 0x9a, 0x91, 0x62, 0x22, 0xfa, 0x1d, 0x7c, 0x10, 0x1f, 0x5f, 0x4f, 0xd0, 0x23, 0x2a,
|
||||
0xa8, 0xaf, 0x34, 0x60, 0xe9, 0x7d, 0xf8, 0xe4, 0xed, 0x1a, 0x30, 0x68, 0x13, 0x6c, 0x90, 0xb8,
|
||||
0xfc, 0x21, 0x6c, 0x14, 0x60, 0x49, 0x44, 0xe3, 0x56, 0xff, 0x98, 0x51, 0xaa, 0xbf, 0x84, 0x40,
|
||||
0x9b, 0x60, 0x25, 0xc3, 0x33, 0x57, 0xab, 0xb7, 0xd0, 0xb8, 0x71, 0x71, 0xbe, 0x0e, 0x31, 0xb6,
|
||||
0xdd, 0x54, 0x31, 0xc8, 0xb4, 0x5d, 0xd4, 0x82, 0xe5, 0x84, 0xa0, 0xca, 0x00, 0x93, 0x28, 0x2b,
|
||||
0x6f, 0x9b, 0xe9, 0xfe, 0x38, 0xa0, 0xb8, 0x28, 0xa6, 0x7a, 0xd5, 0xdf, 0x02, 0x7a, 0x73, 0x5f,
|
||||
0x10, 0x82, 0xec, 0x31, 0xf3, 0xcd, 0x34, 0xb0, 0x6e, 0xa3, 0x1a, 0x2c, 0x05, 0x64, 0xec, 0x71,
|
||||
0xe2, 0x9a, 0x8b, 0x71, 0xab, 0x16, 0x15, 0x08, 0xb5, 0xb8, 0x40, 0xa8, 0xd5, 0xfd, 0x31, 0x8e,
|
||||
0x41, 0xd5, 0xa7, 0xf0, 0x61, 0xea, 0xf1, 0xa2, 0x2d, 0x28, 0x26, 0x17, 0x6e, 0xb2, 0xd6, 0x9b,
|
||||
0x17, 0xe7, 0xeb, 0x56, 0x72, 0x33, 0xdb, 0x4d, 0x6c, 0x25, 0xa0, 0xb6, 0x5b, 0xfd, 0xde, 0x82,
|
||||
0xe5, 0x99, 0x6b, 0x8b, 0x6e, 0xc1, 0x22, 0x1b, 0x92, 0x3e, 0x35, 0x73, 0x8c, 0x3a, 0xa8, 0x05,
|
||||
0x39, 0x8f, 0x1c, 0x52, 0x4f, 0x5d, 0x5e, 0x75, 0x70, 0x3f, 0xbe, 0xf6, 0xfe, 0xd7, 0x9e, 0x69,
|
||||
0x7c, 0xcb, 0x97, 0x62, 0x8c, 0x0d, 0x19, 0xd9, 0xb0, 0xe4, 0xf0, 0xe1, 0x90, 0xf8, 0x2a, 0x4d,
|
||||
0x2c, 0x6c, 0x14, 0x70, 0xdc, 0x55, 0x3b, 0x43, 0x44, 0x3f, 0xb4, 0xb3, 0xda, 0xac, 0xdb, 0xa8,
|
||||
0x04, 0x0b, 0xd4, 0x3f, 0xb1, 0x17, 0xb5, 0x49, 0x35, 0x95, 0xc5, 0x65, 0xd1, 0xed, 0x2b, 0x60,
|
||||
0xd5, 0x54, 0xbc, 0x51, 0x48, 0x85, 0xbd, 0x14, 0xed, 0xa8, 0x6a, 0xa3, 0x9f, 0x41, 0x6e, 0xc8,
|
||||
0x47, 0xbe, 0x0c, 0xed, 0xbc, 0x9e, 0xec, 0x6a, 0xda, 0x64, 0x9f, 0x2b, 0x84, 0x51, 0x96, 0x81,
|
||||
0xa3, 0x16, 0xac, 0x84, 0x92, 0x07, 0xbd, 0xbe, 0x20, 0x0e, 0xed, 0x05, 0x54, 0x30, 0xee, 0x9a,
|
||||
0x30, 0xbc, 0xfa, 0xc6, 0xa1, 0x34, 0x4d, 0xc1, 0x87, 0x6f, 0x2a, 0xce, 0x8e, 0xa2, 0x74, 0x34,
|
||||
0x03, 0x75, 0xa0, 0x18, 0x8c, 0x3c, 0xaf, 0xc7, 0x83, 0x28, 0x23, 0x47, 0x77, 0xe7, 0x1d, 0xb6,
|
||||
0xac, 0x33, 0xf2, 0xbc, 0xbd, 0x88, 0x84, 0xad, 0x60, 0xd2, 0x41, 0xb7, 0x21, 0xd7, 0x17, 0x7c,
|
||||
0x14, 0x44, 0xf7, 0xa6, 0x80, 0x4d, 0x0f, 0x7d, 0x03, 0x4b, 0x21, 0x75, 0x04, 0x95, 0xa1, 0x5d,
|
||||
0xd4, 0x4b, 0xfd, 0x38, 0x6d, 0x90, 0xae, 0x86, 0x24, 0x77, 0x02, 0xc7, 0x1c, 0xb4, 0x0a, 0x0b,
|
||||
0x52, 0x8e, 0xed, 0xe5, 0x4a, 0x66, 0x23, 0xdf, 0x58, 0xba, 0x38, 0x5f, 0x5f, 0xd8, 0xdf, 0x7f,
|
||||
0x89, 0x95, 0x4d, 0x65, 0x8b, 0x01, 0x0f, 0xa5, 0x4f, 0x86, 0xd4, 0xbe, 0xa1, 0xf7, 0x36, 0xe9,
|
||||
0xa3, 0x97, 0x00, 0xae, 0x1f, 0xf6, 0x1c, 0x1d, 0x9e, 0xec, 0x9b, 0x7a, 0x75, 0x9f, 0x5f, 0xbf,
|
||||
0xba, 0xe6, 0x6e, 0xd7, 0x64, 0xcc, 0xe5, 0x8b, 0xf3, 0xf5, 0x42, 0xd2, 0xc5, 0x05, 0xd7, 0x0f,
|
||||
0xa3, 0x26, 0x6a, 0x80, 0x35, 0xa0, 0xc4, 0x93, 0x03, 0x67, 0x40, 0x9d, 0x63, 0xbb, 0x74, 0x75,
|
||||
0x0a, 0x7c, 0xa2, 0x61, 0xc6, 0xc3, 0x34, 0x49, 0x29, 0x58, 0x4d, 0x35, 0xb4, 0x57, 0xf4, 0x5e,
|
||||
0x45, 0x1d, 0x74, 0x0f, 0x80, 0x07, 0xd4, 0xef, 0x85, 0xd2, 0x65, 0xbe, 0x8d, 0xd4, 0x92, 0x71,
|
||||
0x41, 0x59, 0xba, 0xca, 0x80, 0xee, 0xa8, 0x04, 0x45, 0xdc, 0x1e, 0xf7, 0xbd, 0xb1, 0xfd, 0x81,
|
||||
0xfe, 0x9a, 0x57, 0x86, 0x3d, 0xdf, 0x1b, 0xa3, 0x75, 0xb0, 0xb4, 0x2e, 0x42, 0xd6, 0xf7, 0x89,
|
||||
0x67, 0xdf, 0xd2, 0xfb, 0x01, 0xca, 0xd4, 0xd5, 0x16, 0x75, 0x0e, 0xd1, 0x6e, 0x84, 0xf6, 0x87,
|
||||
0x57, 0x9f, 0x83, 0x99, 0xec, 0xe4, 0x1c, 0x0c, 0x07, 0xfd, 0x02, 0x20, 0x10, 0xec, 0x84, 0x79,
|
||||
0xb4, 0x4f, 0x43, 0xfb, 0xb6, 0x5e, 0xf4, 0x5a, 0x6a, 0x66, 0x4a, 0x50, 0x78, 0x8a, 0x81, 0x6a,
|
||||
0x90, 0x65, 0x3e, 0x93, 0xf6, 0x47, 0x26, 0x2b, 0x5d, 0x96, 0x6a, 0x83, 0x73, 0xef, 0x80, 0x78,
|
||||
0x23, 0x8a, 0x35, 0x0e, 0xb5, 0xa1, 0xc0, 0x42, 0xee, 0x69, 0xf9, 0xda, 0xb6, 0x8e, 0x6f, 0xef,
|
||||
0x70, 0x7e, 0xed, 0x98, 0x82, 0x27, 0x6c, 0x74, 0x17, 0x0a, 0x01, 0x73, 0xc3, 0x67, 0x6c, 0xc8,
|
||||
0xa4, 0xbd, 0x5a, 0xc9, 0x6c, 0x2c, 0xe0, 0x89, 0xa1, 0xfc, 0x15, 0x58, 0x53, 0x61, 0x40, 0x5d,
|
||||
0xdf, 0x63, 0x3a, 0x36, 0x91, 0x45, 0x35, 0xd5, 0x59, 0x9d, 0xa8, 0x89, 0xe9, 0xd0, 0x57, 0xc0,
|
||||
0x51, 0xe7, 0xeb, 0xf9, 0x47, 0x99, 0xf2, 0x16, 0x58, 0x53, 0xd7, 0x01, 0x7d, 0xac, 0xc2, 0x72,
|
||||
0x9f, 0x85, 0x52, 0x8c, 0x7b, 0x64, 0x24, 0x07, 0xf6, 0xaf, 0x34, 0xa1, 0x18, 0x1b, 0xeb, 0x23,
|
||||
0x39, 0x28, 0xf7, 0x60, 0xa2, 0x2a, 0x54, 0x01, 0x4b, 0xa9, 0x35, 0xa4, 0xe2, 0x84, 0x0a, 0x55,
|
||||
0xf2, 0x28, 0x31, 0x4c, 0x9b, 0xd4, 0xad, 0x0a, 0x29, 0x11, 0xce, 0x40, 0x07, 0xb5, 0x02, 0x36,
|
||||
0x3d, 0x15, 0xa5, 0xe2, 0xab, 0x6b, 0xa2, 0x94, 0xe9, 0x56, 0xff, 0x96, 0x81, 0x42, 0xb2, 0x0d,
|
||||
0xe8, 0x0b, 0x58, 0x69, 0x77, 0xf7, 0x9e, 0xd5, 0xf7, 0xdb, 0x7b, 0xbb, 0xbd, 0x66, 0xeb, 0xdb,
|
||||
0xfa, 0x8b, 0x67, 0xfb, 0xa5, 0xb9, 0xf2, 0xbd, 0xd3, 0xb3, 0xca, 0xea, 0x24, 0xe2, 0xc6, 0xf0,
|
||||
0x26, 0x3d, 0x22, 0x23, 0x4f, 0xce, 0xb2, 0x3a, 0x78, 0x6f, 0xbb, 0xd5, 0xed, 0x96, 0x32, 0x57,
|
||||
0xb1, 0x3a, 0x82, 0x3b, 0x34, 0x0c, 0xd1, 0x16, 0x94, 0x26, 0xac, 0x27, 0x2f, 0x3b, 0x2d, 0x7c,
|
||||
0x50, 0x9a, 0x2f, 0xdf, 0x3d, 0x3d, 0xab, 0xd8, 0x6f, 0x92, 0x9e, 0x8c, 0x03, 0x2a, 0x0e, 0xcc,
|
||||
0x73, 0xe1, 0x5f, 0x19, 0x28, 0x4e, 0x57, 0x9b, 0x68, 0x3b, 0xaa, 0x12, 0xf5, 0x31, 0xdc, 0xd8,
|
||||
0xda, 0xbc, 0xae, 0x3a, 0xd5, 0x59, 0xce, 0x1b, 0x29, 0xbf, 0xcf, 0xd5, 0xc3, 0x50, 0x93, 0xd1,
|
||||
0x17, 0xb0, 0x18, 0x70, 0x21, 0xe3, 0x7c, 0x90, 0xae, 0x56, 0x2e, 0xe2, 0x1a, 0x26, 0x02, 0x57,
|
||||
0x07, 0x70, 0x63, 0xd6, 0x1b, 0x7a, 0x00, 0x0b, 0x07, 0xed, 0x4e, 0x69, 0xae, 0x7c, 0xe7, 0xf4,
|
||||
0xac, 0xf2, 0xd1, 0xec, 0xc7, 0x03, 0x26, 0xe4, 0x88, 0x78, 0xed, 0x0e, 0xfa, 0x0c, 0x16, 0x9b,
|
||||
0xbb, 0x5d, 0x8c, 0x4b, 0x99, 0xf2, 0xfa, 0xe9, 0x59, 0xe5, 0xce, 0x2c, 0x4e, 0x7d, 0xe2, 0x23,
|
||||
0xdf, 0xc5, 0xfc, 0x30, 0x79, 0x24, 0xfd, 0x7b, 0x1e, 0x2c, 0x93, 0x26, 0xdf, 0xf7, 0x3b, 0x7a,
|
||||
0x39, 0xaa, 0x01, 0xe3, 0xf8, 0x37, 0x7f, 0x6d, 0x29, 0x58, 0x8c, 0x08, 0x46, 0x97, 0xf7, 0xa1,
|
||||
0xc8, 0x82, 0x93, 0x2f, 0x7b, 0xd4, 0x27, 0x87, 0x9e, 0x79, 0x2f, 0xe5, 0xb1, 0xa5, 0x6c, 0xad,
|
||||
0xc8, 0xa4, 0x82, 0x2f, 0xf3, 0x25, 0x15, 0xbe, 0x79, 0x09, 0xe5, 0x71, 0xd2, 0x47, 0xdf, 0x40,
|
||||
0x96, 0x05, 0x64, 0x68, 0xea, 0xd7, 0xd4, 0x15, 0xb4, 0x3b, 0xf5, 0xe7, 0xe6, 0xde, 0x34, 0xf2,
|
||||
0x17, 0xe7, 0xeb, 0x59, 0x65, 0xc0, 0x9a, 0x86, 0xd6, 0xe2, 0x12, 0x52, 0x8d, 0xa4, 0x13, 0x69,
|
||||
0x1e, 0x4f, 0x59, 0x94, 0xf6, 0x99, 0xdf, 0x17, 0x34, 0x0c, 0x75, 0x4a, 0xcd, 0xe3, 0xb8, 0x8b,
|
||||
0xca, 0xb0, 0x64, 0x0a, 0x51, 0x5d, 0x79, 0x16, 0x54, 0x91, 0x67, 0x0c, 0x8d, 0x65, 0xb0, 0xa2,
|
||||
0xdd, 0xe8, 0x1d, 0x09, 0x3e, 0xac, 0xfe, 0x27, 0x0b, 0xd6, 0xb6, 0x37, 0x0a, 0xa5, 0xa9, 0x29,
|
||||
0xde, 0xdb, 0xe6, 0xbf, 0x84, 0x15, 0xa2, 0xdf, 0xe5, 0xc4, 0x57, 0x09, 0x5a, 0xd7, 0xf7, 0xe6,
|
||||
0x00, 0x1e, 0xa4, 0xba, 0x4b, 0xc0, 0xd1, 0x5b, 0xa0, 0x91, 0x53, 0x3e, 0xed, 0x0c, 0x2e, 0x91,
|
||||
0x4b, 0x5f, 0x50, 0x17, 0x96, 0xb9, 0x70, 0x06, 0x34, 0x94, 0x51, 0x5a, 0x37, 0xef, 0xd8, 0xd4,
|
||||
0x3f, 0x1c, 0x7b, 0xd3, 0x40, 0x93, 0xd3, 0xa2, 0xd9, 0xce, 0xfa, 0x40, 0x8f, 0x20, 0x2b, 0xc8,
|
||||
0x51, 0xfc, 0x56, 0x49, 0xbd, 0x24, 0x98, 0x1c, 0xc9, 0x19, 0x17, 0x9a, 0x81, 0x7e, 0x0d, 0xe0,
|
||||
0xb2, 0x30, 0x20, 0xd2, 0x19, 0x50, 0x61, 0x0e, 0x3b, 0x75, 0x89, 0xcd, 0x04, 0x35, 0xe3, 0x65,
|
||||
0x8a, 0x8d, 0x9e, 0x42, 0xc1, 0x21, 0xb1, 0x5c, 0x73, 0x57, 0x3f, 0xee, 0xb7, 0xeb, 0xc6, 0x45,
|
||||
0x49, 0xb9, 0xb8, 0x38, 0x5f, 0xcf, 0xc7, 0x16, 0x9c, 0x77, 0x88, 0x91, 0xef, 0x53, 0x58, 0x56,
|
||||
0x8f, 0xfe, 0x9e, 0x1b, 0x85, 0xb3, 0x48, 0x26, 0x57, 0xe4, 0x68, 0xf5, 0x82, 0x34, 0x61, 0x2f,
|
||||
0x3e, 0xce, 0xa2, 0x9c, 0xb2, 0xa1, 0xdf, 0xc0, 0x0a, 0xf5, 0x1d, 0x31, 0xd6, 0x62, 0x8d, 0x67,
|
||||
0x98, 0xbf, 0x7a, 0xb1, 0xad, 0x04, 0x3c, 0xb3, 0xd8, 0x12, 0xbd, 0x64, 0xaf, 0xfe, 0x33, 0x03,
|
||||
0x10, 0x95, 0x3d, 0xef, 0x57, 0x80, 0x08, 0xb2, 0x2e, 0x91, 0x44, 0x6b, 0xae, 0x88, 0x75, 0x1b,
|
||||
0x7d, 0x0d, 0x20, 0xe9, 0x30, 0x50, 0xa1, 0xd7, 0xef, 0x1b, 0xd9, 0xbc, 0x2d, 0x1c, 0x4c, 0xa1,
|
||||
0xd1, 0x16, 0xe4, 0xcc, 0x8b, 0x32, 0x7b, 0x2d, 0xcf, 0x20, 0xab, 0x7f, 0xc9, 0x00, 0x44, 0xcb,
|
||||
0xfc, 0xbf, 0x5e, 0x5b, 0xc3, 0x7e, 0xfd, 0xc3, 0xda, 0xdc, 0x3f, 0x7e, 0x58, 0x9b, 0xfb, 0xc3,
|
||||
0xc5, 0x5a, 0xe6, 0xf5, 0xc5, 0x5a, 0xe6, 0xef, 0x17, 0x6b, 0x99, 0xef, 0x2f, 0xd6, 0x32, 0x87,
|
||||
0x39, 0x5d, 0x99, 0xfc, 0xf4, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xb8, 0xa3, 0x85, 0xdc, 0xc7,
|
||||
0x15, 0x00, 0x00,
|
||||
}
|
||||
|
||||
4
components/cli/vendor/github.com/docker/swarmkit/api/specs.proto
generated
vendored
4
components/cli/vendor/github.com/docker/swarmkit/api/specs.proto
generated
vendored
@ -314,6 +314,10 @@ message ContainerSpec {
|
||||
// Isolation defines the isolation level for windows containers (default, process, hyperv).
|
||||
// Runtimes that don't support it ignore that field
|
||||
Isolation isolation = 24;
|
||||
|
||||
// PidsLimit prevents from OS resource damage by applications inside the container
|
||||
// using fork bomb attack.
|
||||
int64 pidsLimit = 25;
|
||||
}
|
||||
|
||||
// EndpointSpec defines the properties that can be configured to
|
||||
|
||||
Reference in New Issue
Block a user