diff --git a/components/cli/cli/command/service/create.go b/components/cli/cli/command/service/create.go index 84944fd23c..299cdc383f 100644 --- a/components/cli/cli/command/service/create.go +++ b/components/cli/cli/command/service/create.go @@ -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 } diff --git a/components/cli/cli/command/service/generic_resource_opts.go b/components/cli/cli/command/service/generic_resource_opts.go new file mode 100644 index 0000000000..66385888e1 --- /dev/null +++ b/components/cli/cli/command/service/generic_resource_opts.go @@ -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 +} diff --git a/components/cli/cli/command/service/generic_resource_opts_test.go b/components/cli/cli/command/service/generic_resource_opts_test.go new file mode 100644 index 0000000000..99217e9f36 --- /dev/null +++ b/components/cli/cli/command/service/generic_resource_opts_test.go @@ -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) + } +} diff --git a/components/cli/cli/command/service/opts.go b/components/cli/cli/command/service/opts.go index 071998607e..b57c5c8c4e 100644 --- a/components/cli/cli/command/service/opts.go +++ b/components/cli/cli/command/service/opts.go @@ -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" diff --git a/components/cli/cli/command/service/opts_test.go b/components/cli/cli/command/service/opts_test.go index 6bf00ef6bb..e373c6479d 100644 --- a/components/cli/cli/command/service/opts_test.go +++ b/components/cli/cli/command/service/opts_test.go @@ -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)) + } + +} diff --git a/components/cli/cli/command/service/update.go b/components/cli/cli/command/service/update.go index a7c82ec0c1..d657871ff2 100644 --- a/components/cli/cli/command/service/update.go +++ b/components/cli/cli/command/service/update.go @@ -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() diff --git a/components/cli/cli/command/service/update_test.go b/components/cli/cli/command/service/update_test.go index 92b4d6bc59..ba55269cb0 100644 --- a/components/cli/cli/command/service/update_test.go +++ b/components/cli/cli/command/service/update_test.go @@ -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) +} diff --git a/components/cli/cli/command/trust/client_test.go b/components/cli/cli/command/trust/client_test.go index cc4fec5074..726dd89761 100644 --- a/components/cli/cli/command/trust/client_test.go +++ b/components/cli/cli/command/trust/client_test.go @@ -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) { diff --git a/components/cli/cli/command/trust/cmd.go b/components/cli/cli/command/trust/cmd.go index cb8408d1e7..11131be6df 100644 --- a/components/cli/cli/command/trust/cmd.go +++ b/components/cli/cli/command/trust/cmd.go @@ -20,6 +20,7 @@ func NewTrustCommand(dockerCli command.Cli) *cobra.Command { newSignCommand(dockerCli), newTrustKeyCommand(dockerCli), newTrustSignerCommand(dockerCli), + newInspectCommand(dockerCli), ) return cmd } diff --git a/components/cli/cli/command/trust/common.go b/components/cli/cli/command/trust/common.go new file mode 100644 index 0000000000..29b0ee622e --- /dev/null +++ b/components/cli/cli/command/trust/common.go @@ -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 +} diff --git a/components/cli/cli/command/trust/inspect.go b/components/cli/cli/command/trust/inspect.go new file mode 100644 index 0000000000..772c95e888 --- /dev/null +++ b/components/cli/cli/command/trust/inspect.go @@ -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, + }) +} diff --git a/components/cli/cli/command/trust/inspect_test.go b/components/cli/cli/command/trust/inspect_test.go new file mode 100644 index 0000000000..cb2ee800a8 --- /dev/null +++ b/components/cli/cli/command/trust/inspect_test.go @@ -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") +} diff --git a/components/cli/cli/command/trust/sign.go b/components/cli/cli/command/trust/sign.go index 60ad0126cc..2fc233da71 100644 --- a/components/cli/cli/command/trust/sign.go +++ b/components/cli/cli/command/trust/sign.go @@ -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 { diff --git a/components/cli/cli/command/trust/testdata/trust-inspect-empty-repo.golden b/components/cli/cli/command/trust/testdata/trust-inspect-empty-repo.golden new file mode 100644 index 0000000000..ae6fd9c880 --- /dev/null +++ b/components/cli/cli/command/trust/testdata/trust-inspect-empty-repo.golden @@ -0,0 +1,25 @@ +[ + { + "Name": "reg/img:unsigned-tag", + "SignedTags": [], + "Signers": [], + "AdminstrativeKeys": [ + { + "Name": "Root", + "Keys": [ + { + "ID": "rootID" + } + ] + }, + { + "Name": "Repository", + "Keys": [ + { + "ID": "targetsID" + } + ] + } + ] + } +] diff --git a/components/cli/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden b/components/cli/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden index 6fb3b5b34b..cda9b40e0e 100644 --- a/components/cli/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden +++ b/components/cli/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden @@ -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" + } + ] + } + ] + } +] diff --git a/components/cli/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden b/components/cli/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden index 7e73f06726..496b312bdc 100644 --- a/components/cli/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden +++ b/components/cli/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden @@ -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" + } + ] + } + ] + } +] diff --git a/components/cli/cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden b/components/cli/cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden new file mode 100644 index 0000000000..fd87979e5c --- /dev/null +++ b/components/cli/cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden @@ -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" + } + ] + } + ] + } +] diff --git a/components/cli/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden b/components/cli/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden index 6fb3b5b34b..b1745d5c75 100644 --- a/components/cli/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden +++ b/components/cli/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden @@ -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" + } + ] + } + ] + } +] diff --git a/components/cli/cli/command/trust/testdata/trust-inspect-uninitialized.golden b/components/cli/cli/command/trust/testdata/trust-inspect-uninitialized.golden new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/components/cli/cli/command/trust/testdata/trust-inspect-uninitialized.golden @@ -0,0 +1 @@ +[] diff --git a/components/cli/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden b/components/cli/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden index c00a9feecf..8c0a84eb29 100644 --- a/components/cli/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden +++ b/components/cli/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden @@ -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" + } + ] + } + ] + } +] diff --git a/components/cli/cli/command/trust/testdata/trust-view-full-repo-no-signers.golden b/components/cli/cli/command/trust/testdata/trust-view-full-repo-no-signers.golden new file mode 100644 index 0000000000..6fb3b5b34b --- /dev/null +++ b/components/cli/cli/command/trust/testdata/trust-view-full-repo-no-signers.golden @@ -0,0 +1,6 @@ +SIGNED TAG DIGEST SIGNERS +green 677265656e2d646967657374 (Repo Admin) + +Administrative keys for signed-repo: +Repository Key: targetsID +Root Key: rootID diff --git a/components/cli/cli/command/trust/testdata/trust-view-full-repo-with-signers.golden b/components/cli/cli/command/trust/testdata/trust-view-full-repo-with-signers.golden new file mode 100644 index 0000000000..7e73f06726 --- /dev/null +++ b/components/cli/cli/command/trust/testdata/trust-view-full-repo-with-signers.golden @@ -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 diff --git a/components/cli/cli/command/trust/testdata/trust-view-one-tag-no-signers.golden b/components/cli/cli/command/trust/testdata/trust-view-one-tag-no-signers.golden new file mode 100644 index 0000000000..6fb3b5b34b --- /dev/null +++ b/components/cli/cli/command/trust/testdata/trust-view-one-tag-no-signers.golden @@ -0,0 +1,6 @@ +SIGNED TAG DIGEST SIGNERS +green 677265656e2d646967657374 (Repo Admin) + +Administrative keys for signed-repo: +Repository Key: targetsID +Root Key: rootID diff --git a/components/cli/cli/command/trust/testdata/trust-view-unsigned-tag-with-signers.golden b/components/cli/cli/command/trust/testdata/trust-view-unsigned-tag-with-signers.golden new file mode 100644 index 0000000000..c00a9feecf --- /dev/null +++ b/components/cli/cli/command/trust/testdata/trust-view-unsigned-tag-with-signers.golden @@ -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 diff --git a/components/cli/cli/command/trust/view.go b/components/cli/cli/command/trust/view.go index 8dd2e47347..16e7f13f72 100644 --- a/components/cli/cli/command/trust/view.go +++ b/components/cli/cli/command/trust/view.go @@ -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, }) } diff --git a/components/cli/cli/command/trust/view_test.go b/components/cli/cli/command/trust/view_test.go index b22fbce458..a8a69d207b 100644 --- a/components/cli/cli/command/trust/view_test.go +++ b/components/cli/cli/command/trust/view_test.go @@ -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) { diff --git a/components/cli/cli/compose/convert/service.go b/components/cli/cli/compose/convert/service.go index 3c9a6a661a..e58c241c22 100644 --- a/components/cli/cli/compose/convert/service.go +++ b/components/cli/cli/compose/convert/service.go @@ -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 diff --git a/components/cli/cli/compose/convert/service_test.go b/components/cli/cli/compose/convert/service_test.go index ebcce495c6..265937339f 100644 --- a/components/cli/cli/compose/convert/service_test.go +++ b/components/cli/cli/compose/convert/service_test.go @@ -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 +} diff --git a/components/cli/cli/compose/loader/full-example.yml b/components/cli/cli/compose/loader/full-example.yml index aefb56869e..0a52111c32 100644 --- a/components/cli/cli/compose/loader/full-example.yml +++ b/components/cli/cli/compose/loader/full-example.yml @@ -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 diff --git a/components/cli/cli/compose/loader/loader_test.go b/components/cli/cli/compose/loader/loader_test.go index e56202d86c..d609311abc 100644 --- a/components/cli/cli/compose/loader/loader_test.go +++ b/components/cli/cli/compose/loader/loader_test.go @@ -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{ diff --git a/components/cli/cli/compose/schema/bindata.go b/components/cli/cli/compose/schema/bindata.go index da71abb1a7..e234c2cddd 100644 --- a/components/cli/cli/compose/schema/bindata.go +++ b/components/cli/cli/compose/schema/bindata.go @@ -173,7 +173,7 @@ func dataConfig_schema_v34Json() (*asset, error) { return a, nil } -var _dataConfig_schema_v35Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x1b\x4b\x73\xdb\xbc\xf1\xae\x5f\xc1\xe1\xf7\xdd\x22\xd9\x99\x69\xda\x99\xe6\xd6\x63\x4f\xed\xb9\x1e\x85\x03\x81\x2b\x09\x31\x08\x20\x0b\x50\xb6\x92\xf1\x7f\xef\xf0\x29\x90\x04\x09\x50\xa2\x63\xa7\xcd\xc9\x16\xb9\xbb\xc0\xbe\x1f\x00\x7f\xac\xa2\x28\xfe\x53\xd3\x23\x64\x24\xfe\x1c\xc5\x47\x63\xd4\xe7\xfb\xfb\xaf\x5a\x8a\x4d\xf5\xf4\x4e\xe2\xe1\x3e\x45\xb2\x37\x9b\x8f\x9f\xee\xab\x67\x7f\xc4\xeb\x02\x8f\xa5\x05\x0a\x95\x62\xcf\x0e\x49\xf5\x26\x39\xfd\xe5\xee\xaf\x77\x05\x7a\x05\x62\xce\x0a\x0a\x20\xb9\xfb\x0a\xd4\x54\xcf\x10\xbe\xe5\x0c\xa1\x40\x7e\x88\x4f\x80\x9a\x49\x11\x6f\xd7\xab\xe2\x9d\x42\xa9\x00\x0d\x03\x1d\x7f\x8e\x8a\xcd\x45\x51\x0b\xd2\x3c\xb0\xc8\x6a\x83\x4c\x1c\xe2\xf2\xf1\x4b\x49\x21\x8a\x62\x0d\x78\x62\xd4\xa2\xd0\x6e\xf5\x8f\xfb\x0b\xfd\xfb\x16\x6c\xdd\xa7\x6a\x6d\xb6\x7c\xae\x88\x31\x80\xe2\xdf\xc3\xbd\x95\xaf\xbf\x3c\x90\xcd\xf7\x7f\x6c\xfe\xf3\x71\xf3\xf7\xbb\x64\xb3\xfd\xf0\x67\xe7\x75\x21\x5f\x84\x7d\xb5\x7c\x0a\x7b\x26\x98\x61\x52\xb4\xeb\xc7\x2d\xe4\x4b\xfd\xdf\x4b\xbb\x30\x49\xd3\x12\x98\xf0\xce\xda\x7b\xc2\x35\x74\x79\x16\x60\x9e\x24\x3e\xfa\x78\x6e\xc1\xde\x88\xe7\x7a\x7d\x07\xcf\x5d\x76\x4e\x92\xe7\x99\x57\x83\x0d\xd4\x1b\x31\x53\x2d\xbf\x8c\xfe\x34\x50\x04\xe3\x37\xd9\x0a\xea\xcd\x2c\xb6\x58\x7e\x19\x86\xab\xa8\xe1\x63\xb8\x81\x7a\x23\x86\xab\xe5\x6f\x63\x78\xd5\x30\xed\xde\x63\xfc\xe5\x79\x53\xfc\x7d\x29\x69\x4e\xd2\xab\xa8\x58\xfb\x2b\x99\xe8\xc4\x3c\x97\x38\x5d\x31\x67\x5c\x9e\xad\x40\x47\x24\x99\x82\xe2\xf2\x5c\xee\xdc\x2d\xb3\x0a\x20\x03\x61\xe2\x56\x4c\x51\x14\xef\x72\xc6\xd3\xbe\xd4\xa5\x80\x7f\x15\x24\x1e\xac\x87\x51\xf4\xa3\x1f\xde\x2d\x3a\xe5\xfb\xce\xaf\x71\xa3\x68\xdf\x8f\xf0\xd2\xbe\xa7\x52\x18\x78\x36\x25\x53\xd3\x4b\x57\x22\x90\xf4\x11\x70\xcf\x38\x84\x62\x10\xac\x2c\x7d\x44\x64\x9c\x69\x93\x48\x4c\x52\x46\x8d\x13\x9f\x93\x1d\xf0\x9b\x28\x50\x42\x8f\x90\xec\x51\x66\x5e\x2a\xfb\xa4\xe2\x44\x3b\x09\x35\x11\x3c\x90\x73\x43\xf0\x00\x6e\xc9\xf6\x80\x07\xd8\x7e\xdf\x6a\x51\xad\x5f\xdb\x95\x83\x60\x4c\x89\x4a\x48\x9a\x76\xf6\x41\x10\xc9\x39\x5e\x47\x31\x33\x90\x69\x37\x43\x51\x9c\x0b\xf6\x2d\x87\x7f\xd6\x20\x06\x73\xe8\xd3\x4d\x51\xaa\xe5\x09\x1f\x50\xe6\x2a\x51\x04\x0b\x47\x9a\x16\x76\x4c\x65\x96\x11\xb1\x94\x77\xcd\xe1\x23\x40\xf2\x83\x38\x1f\xd9\x2e\x5b\xaf\x61\xbf\x6a\x57\xeb\x6c\x6b\x84\x1b\x3f\x3f\xc3\x78\xe1\x8f\x18\xfe\x98\x51\x84\x5c\x99\x23\x0d\x0d\x01\xd3\xae\xe0\x84\xcf\x59\x1a\x0e\x7c\x98\x03\x9c\xc9\xb4\xbb\x6f\x91\x67\x3b\xc0\x81\x4b\x76\x3d\x6b\xf8\x7b\xbb\x72\xbd\xe9\x69\xdf\x10\x26\x00\x13\x41\x32\x9f\xac\x62\x8a\x90\x82\x30\x8c\xf0\x44\x2b\xa0\x1d\xf0\x46\x53\x13\x9a\x89\x83\x42\x72\x8c\x70\x60\xda\xe0\x79\x3a\x28\xbd\xd8\x1b\x4b\x41\x81\x48\x75\x52\x35\x21\xf3\xa3\x67\x9c\x42\xdb\x91\x2c\x1a\x26\x52\x31\x95\x15\x2a\x32\x45\x5e\x28\xf6\x16\xf7\x10\x13\x0d\x04\xe9\xf1\x4a\x7c\x99\x11\x26\x42\x94\x0a\xc2\xe0\x59\x49\x56\x85\xb1\x77\x17\x9f\x40\x9c\x92\xd6\x6e\x66\x8b\x01\xc4\x89\xa1\x14\x59\x13\xa4\xc3\xb2\xb3\x85\xff\xac\xa4\x86\xdb\x83\x63\x8d\xf1\xd0\x30\xbe\x6e\x7d\x7a\xdb\x95\x5e\xbc\x97\x98\x91\x62\xb3\xcd\xda\xb6\x0f\x77\x96\x1a\x5a\x9e\x2d\x40\x9b\x87\xa2\xaa\x25\x3c\xe1\x4c\x3c\x2e\x6f\xe2\xf0\x6c\x90\x24\x47\xa9\xcd\x35\x05\x50\x7c\x04\xc2\xcd\x91\x1e\x81\x3e\x4e\xa0\xdb\x50\x1d\x6c\xa9\x4d\x88\x91\xb3\x8c\x1c\xfc\x40\x8a\x7a\x41\xb4\xe4\xc4\xd4\xc3\x8e\x29\xc0\xab\x2b\xc2\x78\x51\x2d\x59\x64\xe5\xe1\x50\x80\x8e\x99\xe6\xa0\xc3\xa8\x5f\xfb\x6a\xf3\x14\xd9\x09\x30\xb4\xdc\x94\xea\xd2\x18\xf5\x5f\x86\xa4\x7d\x6f\x27\xd9\x01\xfd\x72\x57\x35\x92\x13\xee\x57\xfe\xc7\x79\xbc\x1d\xe6\xd6\x61\x76\xed\x3f\xe9\x71\x18\x56\x10\x77\xb4\x92\x11\x5a\xd4\xbd\x08\x7a\x44\xaf\x17\xd0\xba\xb0\x4f\x06\xc5\xc1\x05\x76\x00\x3c\xc8\xc0\x63\x21\xfd\xaa\x7e\x63\x7e\x9f\x17\xa4\x3a\xef\x30\xc0\xc3\xcd\xd8\xf6\x42\xb7\x79\xd9\xae\xdf\xc4\x4a\x38\xc2\x19\xd1\xe0\x77\xf6\xc9\xc6\xad\xa5\xc6\xd4\xe9\x53\xa0\x4d\xb8\x70\xff\x36\x89\x3b\x82\x3a\x4a\x33\xbc\xc7\xf3\x90\xb2\x6b\x59\xce\x9d\x1b\xd9\xfa\xab\xdb\xd7\x6c\x41\x55\xb7\x42\xef\xc6\x8a\x32\x42\xd8\x0e\xa6\x24\x9a\x9f\xd2\x34\x5d\xe2\xd4\xa5\x32\xa8\x16\x1f\xf6\x51\x7d\x75\x07\x21\xbd\x4e\xf3\x35\x11\xa5\x1c\xd0\x8e\xd6\x8b\x09\x03\x87\xa2\xe7\x71\x27\x81\x7c\xc7\x99\x3e\x42\x3a\x07\x07\xa5\x91\x54\xf2\x30\xc7\x70\x8e\x89\xc2\x9d\x61\xa2\x11\xbb\xaa\x88\x53\xc8\x4e\x8c\xc3\xa1\xc7\xf1\x4e\x4a\x0e\x44\x74\x12\x05\x02\x49\x13\x29\xf8\x39\x00\x52\x1b\x82\xde\xf1\x85\x06\x9a\x23\x33\xe7\x44\x2a\xb3\x78\xf9\xa8\x8f\x59\xa2\xd9\x77\xe8\xfa\xde\xc5\xea\x6b\x42\xdb\xde\x86\x7a\xc3\xf8\xe8\xf7\xcc\xe2\xff\x66\x66\xa1\xcf\x9a\x9a\xeb\x6a\x6b\x6d\x52\x26\x12\xa9\x40\x78\x7d\x43\x1b\xa9\x92\x03\x12\x0a\x89\x02\x64\xd2\x29\x8a\x4e\x80\x4d\x73\xac\x5a\x83\x01\x19\xcd\x0e\x82\xb8\xe3\x8e\x05\x6a\x32\xb5\xbf\x72\x5a\x60\x8c\xdf\xd9\x73\xce\x32\x36\xee\x34\x0e\xab\x0d\xa8\xd7\xaa\x5a\xcd\x5d\xa2\x4d\x94\x67\x41\x21\x7b\xa2\x43\x98\x6e\x10\x02\x3a\x83\x23\xc1\x19\xa9\xa3\x74\xcc\xfd\x48\x7e\x72\xf5\x0d\xce\x7d\x75\x8e\xd5\x4b\x7a\xeb\x7a\x23\x5b\x27\xfc\xac\xd2\xab\xbf\x8d\xed\x68\xf5\xe3\x76\xaa\x5c\x7b\x9b\xb8\x12\x46\xe8\xa9\x06\xa4\x05\x1d\x9e\x0f\x47\xbf\x44\x84\xee\xe8\xa8\x04\x77\xe8\x26\x20\x8e\xd7\x2b\x05\xc6\xce\xd7\x8e\xfa\xc1\x15\x81\x85\x43\xa5\xd0\x4c\x1b\x10\xd4\x3d\x88\x75\x22\xed\xd8\xe0\x94\x63\x28\x94\xe9\xbe\x2b\xac\xeb\x2a\xa1\xc8\x61\x7c\x14\xe3\xee\x4d\x82\x7d\xb5\xbe\x3a\xf0\x53\x58\x11\x92\x4a\x35\xa2\x9a\x70\x36\xe6\xa6\xd9\xde\xe8\x62\xa2\x0e\x1d\x0b\x19\x4f\x12\x1f\x8b\x84\x94\x32\x77\xe4\x58\xf5\x50\x66\x5c\x3e\xe8\x0d\x05\x1b\x02\xae\x13\x73\x1b\xd4\x7b\x0b\x61\xfa\xf4\xbe\x06\x1a\x3d\x59\x67\x9a\xec\x7a\x07\x18\xae\x44\x5b\x64\x06\x3c\xb9\xf3\xbd\xbf\x60\x40\x30\xc8\x7a\x27\x11\x4d\x29\x65\x67\x7c\xd0\xef\x73\x5e\x6f\x58\x06\x32\x77\x06\xa7\x90\x6a\x89\xa0\x99\x5f\x6f\xad\x6c\x33\xad\xe9\xc5\xd6\x3d\x07\x8f\x09\x59\x90\x7d\x0b\x7a\xb0\xce\xb5\xaa\x29\x80\xd7\x4c\x42\xd2\x23\x88\xb4\x3c\x71\x09\xca\xa5\x08\x8a\x33\x4a\xb4\xaf\x5e\xb9\x61\xe6\x9c\xab\x94\x18\x48\xea\xeb\x34\x73\x2a\xc4\x89\xd2\x50\x11\x24\x9c\x03\x67\x3a\x0b\x29\xb5\xe2\x14\x38\x71\xe6\x1a\xaf\xdd\x94\xe8\x7b\xc2\x78\x8e\x90\x10\x1a\x30\x9f\xaf\x35\x25\x98\x91\xce\xe0\x15\xb6\x64\x46\x9e\x93\x66\xd9\x12\xc4\xe3\xb5\x25\x92\xc4\xd4\x5d\x6a\xad\x0b\xbb\xc8\x33\x47\xb1\x53\xf9\xc5\x66\xcf\x50\x9b\xaa\x27\x96\xaa\xfe\xd5\x0d\xea\x2f\xa3\x73\x86\xd0\xd1\xb4\x65\x75\x55\x55\x32\xaf\x61\x98\x30\x87\x4b\xfb\x31\x62\x9d\xcd\x8a\x03\x89\x21\xe8\x22\xa0\xb6\x27\x07\x5e\xfc\x45\xa5\x50\x85\x24\xc9\x59\x55\x08\x2d\x21\x0a\x2a\x45\xb5\x8f\x10\x2b\xbd\xd1\x2d\x0a\x1b\x2d\xda\xb8\x4c\x19\x6f\x04\x29\x11\x9e\x98\x48\xe5\xd3\xfc\xe8\xbb\x80\xb4\x15\x27\x14\x7a\x11\xfb\x56\x41\x6b\x83\x84\x09\x33\xfb\x44\xad\x2f\x16\x85\xb0\x07\x04\x31\xf4\x88\x68\xba\xb3\x89\xc6\xbb\x1b\x1f\x6f\x7e\x0e\x6b\x08\xad\x8a\x12\xff\x0d\x06\x9b\xb7\x2a\xff\x86\x1a\xb1\x75\x77\x4f\x76\x6f\xe1\xbc\xd5\xe1\x58\x46\xa7\x2a\xf7\x9e\xce\x65\x90\xc9\xe9\x2b\x2b\x37\xdc\x1a\xf7\xb1\xd8\x80\x2d\x50\xbd\x04\x1d\xe7\xd6\x50\x89\x54\xcb\xcf\x93\xfc\x47\xb6\x5b\xff\x34\x83\x29\x92\x2d\x15\x43\x82\x0f\xb8\x63\x67\xf9\x14\xbd\x83\xe8\x90\xef\x44\xd8\x1d\xcf\x77\x16\x1d\xec\x8a\xb9\xbe\xc4\x32\xa2\xd5\x87\xb6\x13\x5b\xb7\xb2\xda\x06\xab\x78\xf4\x06\xc9\x72\xfb\x2f\x9b\xc2\xfe\x10\xd8\xd5\x3d\x12\x63\x08\x3d\x06\x35\x9a\x33\xeb\xfd\x1b\xe2\xd0\x60\x1c\xe2\x0c\x43\x35\xd4\x02\x51\x28\xe4\x4a\xcf\xff\x46\xa4\xfa\xd5\xed\xfa\xe7\xd9\x60\xfd\x71\x8b\xf7\x03\x8a\x12\xea\xea\x5c\x1f\x62\x79\x01\xd7\x58\xdf\x81\x5e\xdf\x58\x5d\x83\x64\xe8\x54\x57\x0d\xf5\x5b\x5d\x6f\xad\xae\xde\xe1\xa2\xa5\xb6\xe1\x54\x71\x4a\x92\xc1\x37\xa0\x6a\x8c\x6d\x77\x1b\x7d\x30\xc7\x27\xa5\xdd\x1a\x6a\xea\xea\x41\x03\x32\x32\xdb\xee\x2d\x5a\x0b\x71\x9a\xf3\x05\xf3\xc7\xdd\x87\x89\x4a\x71\xea\xa6\xe2\x2b\x95\x58\x0b\x5c\xeb\x70\xeb\xb4\xd7\x84\x37\xd2\x1d\x7e\x11\x37\x1e\x23\x1a\xfc\xc1\xf7\x71\x05\x9f\xe2\x3c\x98\x7a\xff\xe8\x1e\xe4\x55\xdf\xb6\x6d\x3b\xf2\xe9\x81\x54\x17\x84\xad\x84\xbd\xb5\xe7\x12\xa3\x5f\x34\xb8\xbe\x9a\xeb\x1f\x23\x36\x5f\xaf\x8d\xdc\x6c\x58\xd9\x7f\xcb\xaf\x11\x57\x2f\xab\xff\x06\x00\x00\xff\xff\x9b\x23\x27\xbe\xf7\x3d\x00\x00") +var _dataConfig_schema_v35Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x1b\xc9\x72\xec\xb6\xf1\x3e\x5f\xc1\x82\x7d\xb3\x16\x57\xc5\x49\x55\xde\x2d\xc7\x9c\x92\x73\x54\xf3\x58\x18\xb0\x87\x03\x0b\x04\x60\x00\x1c\x69\xfc\x4a\xff\x9e\xe2\x2a\x90\xc4\x46\x0d\xf5\x24\x27\x3e\x49\x43\x76\x37\x7a\x43\x6f\x00\xbf\xed\xb2\x0c\xfd\xa8\xc9\x09\x2a\x8c\xbe\x64\xe8\x64\x8c\xfc\x72\x7f\xff\xab\x16\xfc\xb6\x7b\x7a\x27\x54\x79\x5f\x28\x7c\x34\xb7\x3f\xff\x72\xdf\x3d\xfb\x01\xdd\x34\x78\xb4\x68\x50\x88\xe0\x47\x5a\xe6\xdd\x9b\xfc\xfc\x97\xbb\xbf\xde\x35\xe8\x1d\x88\xb9\x48\x68\x80\xc4\xe1\x57\x20\xa6\x7b\xa6\xe0\xb7\x9a\x2a\x68\x90\x1f\xd0\x19\x94\xa6\x82\xa3\xfd\xcd\xae\x79\x27\x95\x90\xa0\x0c\x05\x8d\xbe\x64\x0d\x73\x59\x36\x82\x0c\x0f\x2c\xb2\xda\x28\xca\x4b\xd4\x3e\x7e\x69\x29\x64\x19\xd2\xa0\xce\x94\x58\x14\x46\x56\x7f\xb8\x7f\xa5\x7f\x3f\x82\xdd\xcc\xa9\x5a\xcc\xb6\xcf\x25\x36\x06\x14\xff\xf7\x92\xb7\xf6\xf5\xd7\x07\x7c\xfb\xfb\x3f\x6e\xff\xf3\xf3\xed\xdf\xef\xf2\xdb\xfd\x4f\x3f\x4e\x5e\x37\xfa\x55\x70\xec\x96\x2f\xe0\x48\x39\x35\x54\xf0\x71\x7d\x34\x42\xbe\xf4\xff\xbd\x8c\x0b\xe3\xa2\x68\x81\x31\x9b\xac\x7d\xc4\x4c\xc3\x54\x66\x0e\xe6\x49\xa8\xc7\x98\xcc\x23\xd8\x07\xc9\xdc\xaf\xef\x90\x79\x2a\xce\x59\xb0\xba\x8a\x5a\x70\x80\xfa\x20\x61\xba\xe5\xb7\xb1\x9f\x06\xa2\xc0\xc4\x5d\xb6\x83\xfa\x30\x8f\x6d\x96\xdf\x46\xe0\x2e\x6a\xc4\x04\x1e\xa0\x3e\x48\xe0\x6e\xf9\xeb\x04\xde\x0d\x42\xbb\x79\x44\x5f\x9f\x6f\x9b\xbf\x2f\x2d\xcd\x20\xbd\x8e\x8a\xc5\x5f\x2b\xc4\x24\xe6\xb9\xd4\xe9\x8a\x39\x7e\x7d\x8e\x0a\xf5\x68\xb2\x00\xc9\xc4\xa5\xe5\xdc\xad\xb3\x0e\xa0\x02\x6e\xd0\xa8\xa6\x2c\x43\x87\x9a\xb2\x62\xae\x75\xc1\xe1\x5f\x0d\x89\x07\xeb\x61\x96\x7d\x9b\x87\x77\x8b\x4e\xfb\x7e\xf2\xcb\xef\x14\xe3\x7b\x8f\x2c\xe3\x7b\x22\xb8\x81\x67\xd3\x0a\x15\x5e\xba\x53\x81\x20\x8f\xa0\x8e\x94\x41\x2a\x06\x56\x9d\xa7\x7b\x54\xc6\xa8\x36\xb9\x50\x79\x41\x89\x71\xe2\x33\x7c\x00\x76\x15\x05\x82\xc9\x09\xf2\xa3\x12\x55\x94\xca\x31\xef\x24\xd1\x4e\x42\x43\x04\x4f\x94\xdc\x60\x55\x82\x5b\xb3\x33\xe0\x05\x76\x7c\x6f\x8d\xa8\xd6\xaf\xfd\xce\x41\x10\x11\x2c\x73\x5c\x14\x13\x3e\xb0\x52\xf8\x82\x6e\x32\x44\x0d\x54\xda\x2d\x50\x86\x6a\x4e\x7f\xab\xe1\x9f\x3d\x88\x51\x35\xcc\xe9\x16\x4a\xc8\xed\x09\x97\x4a\xd4\x32\x97\x58\x35\x1b\x29\xac\x6c\x44\x44\x55\x61\xbe\xd5\xee\x5a\x23\x47\x82\xe6\x17\x71\x3e\xb3\xb7\x6c\xbf\x86\xfd\x6a\x5c\x6d\xc2\x96\x47\x9a\xb8\x3c\xcb\x78\x11\x8f\x18\xf1\x98\xd1\x84\x5c\x51\x2b\x92\x1a\x02\xc2\x5b\xc1\x09\x5f\xd3\x22\x1d\xb8\x5c\x03\x5c\x89\x62\xca\x37\xaf\xab\x03\xa8\xc5\x96\x9c\xee\xac\xe5\xef\xfd\xce\xf5\x66\x66\x7d\x83\x29\x07\x95\x73\x5c\xc5\x74\x85\x88\x82\x02\xb8\xa1\x98\xe5\x5a\x02\x99\x80\x0f\x96\x0a\x58\x06\x25\x85\x64\xa4\xa0\xa4\xda\xa8\x4b\x38\x28\xbd\xd8\x8c\x15\x20\x81\x17\x3a\xef\x9a\x90\xf5\xd1\x13\x15\x30\x76\x24\x9b\x86\x89\x82\x87\xb2\x42\x47\xa6\xc9\x0b\x0d\x6f\x68\x86\x98\x6b\xc0\x8a\x9c\xde\x88\x2f\x2a\x4c\x79\x8a\x51\x81\x1b\x75\x91\x82\x76\x61\xec\xd3\xc5\x27\xe0\xe7\x7c\xf4\x9b\xd5\x6a\x00\x7e\xa6\x4a\xf0\x6a\x08\xd2\x69\xd9\xd9\xc2\x7f\x96\x42\xc3\xf5\xc1\xb1\xc7\x78\x18\x04\xbf\x19\xf7\xf4\x7e\xaa\x3d\x74\x14\xaa\xc2\x0d\xb3\xc3\xda\xf6\x1e\x9e\x2c\xb5\xf4\x3c\x5b\x81\xb6\x0c\x4d\x55\x8b\x59\xce\x28\x7f\xdc\xde\xc5\xe1\xd9\x28\x9c\x9f\x84\x36\x6f\x29\x80\xd0\x09\x30\x33\x27\x72\x02\xf2\x18\x40\xb7\xa1\x26\xd8\x42\x9b\x14\x27\xa7\x15\x2e\xe3\x40\x92\x44\x41\xb4\x60\xd8\xf4\xc3\x8e\x10\xe0\x9b\x2b\x42\xb4\xa9\x95\x2c\xb2\xa2\x2c\x1b\x50\x9f\x6b\x2e\x3a\x8c\xfe\x75\xac\x36\x2f\x14\x3d\x83\x4a\x2d\x37\x85\x7c\x6d\x8c\xe6\x2f\x53\xd2\x7e\xb4\x93\x9c\x80\x7e\xbd\xeb\x1a\xc9\xc0\xf6\x6b\xff\x63\x0c\xed\x97\xb9\x75\x99\x5d\xe7\x4f\x66\x12\xa6\x15\xc4\x13\xab\x54\x98\x34\x75\xaf\x02\xed\xb1\xeb\x2b\x68\x5f\xd8\xe7\x8b\xe2\xe0\x15\x76\x01\xbc\xc8\xc0\xbe\x90\xfe\xa6\x7e\x63\x7d\x9f\x97\x64\xba\xe8\x30\x20\x22\x8d\x8f\xbd\x54\x36\x5f\xd9\x8d\xbb\x58\x0b\x87\x19\xc5\x1a\xe2\x9b\x3d\xd8\xb8\x8d\xd4\xa8\x3c\xff\x92\xe8\x13\x2e\xdc\xbf\x05\x71\x3d\xa8\x5e\x9a\xe9\x3d\x5e\x84\x94\x5d\xcb\x32\xe6\x64\x64\x1f\xaf\x6e\xdf\xb3\x05\x95\xd3\x0a\x7d\x1a\x2b\xda\x08\x61\x6f\x30\x29\x94\xf9\x2e\x4d\xd3\x6b\x9c\x7a\xad\x0c\xba\xc5\x97\x7d\xd4\xdc\xdc\x49\x48\xef\xd3\x7c\x05\xa2\x94\x03\xda\xd1\x7a\x51\x6e\xa0\x6c\x7a\x1e\x77\x12\xa8\x0f\x8c\xea\x13\x14\x6b\x70\x94\x30\x82\x08\x96\xb6\x31\x9c\x63\xa2\xf4\xcd\x10\x68\xc4\xde\x54\xc4\x49\x45\xcf\x94\x41\x39\x93\xf8\x20\x04\x03\xcc\x27\x89\x42\x01\x2e\x72\xc1\xd9\x25\x01\x52\x1b\xac\xa2\xe3\x0b\x0d\xa4\x56\xd4\x5c\x72\x21\xcd\xe6\xe5\xa3\x3e\x55\xb9\xa6\xbf\xc3\x74\xef\xbd\x7a\x7d\x4f\x68\x3f\x63\x68\x36\x8c\xcf\xfe\x9c\x59\xfc\xdf\xcc\x2c\xf4\x45\x13\xf3\xb6\xda\x5a\x9b\x82\xf2\x5c\x48\xe0\xd1\xbd\xa1\x8d\x90\x79\xa9\x30\x81\x5c\x82\xa2\xc2\xa9\x8a\x49\x80\x2d\x6a\xd5\xb5\x06\x0b\x32\x9a\x96\x1c\xbb\xe3\x8e\x05\x6a\x2a\x79\x7c\xe3\xb4\xc0\x98\xf8\x66\xaf\x19\xad\xa8\x7f\xd3\x38\xbc\x36\xa1\x5e\xeb\x6a\x35\x77\x89\x16\x28\xcf\x92\x42\x76\xa0\x43\x08\x37\x08\x09\x9d\xc1\x09\xab\x15\xa9\xa3\xdd\x98\x47\x4f\x7e\x72\xf5\x0d\x4e\xbe\x26\xc7\xea\x2d\xbd\x9b\x9e\x91\xbd\x13\x7e\x55\xe9\x35\x67\x63\xef\xad\x7e\xdc\x9b\xaa\xd6\xd1\x26\xae\x85\xe1\x3a\xd4\x80\x8c\xa0\xcb\xf3\xe1\xec\x0f\x11\xa1\x27\x36\x6a\xc1\x1d\xb6\x49\x88\xe3\xfd\x4a\x89\xb1\xf3\xbd\xa3\x7e\x72\x45\x60\xe1\x10\xc1\x35\xd5\x06\x38\x71\x0f\x62\x9d\x48\x07\xba\x38\xe5\x58\x2a\x25\xdc\x77\xa5\x75\x5d\x2d\x14\x2e\xfd\xa3\x18\x77\x6f\x92\xbc\x57\xfb\xab\x03\xdf\x45\x14\x2e\x88\x90\x1e\xd3\xa4\x8b\xb1\x36\xcd\xce\x46\x17\x81\x3a\xd4\x17\x32\x9e\x84\x7a\x6c\x12\x52\x41\xdd\x91\x63\x37\x43\x59\x71\xf9\x60\x36\x14\x1c\x08\xb8\x4e\xcc\x6d\xd0\xe8\x2d\x84\xf0\xe9\x7d\x0f\xe4\x3d\x59\xa7\x1a\x1f\x66\x07\x18\xae\x44\xdb\x64\x06\x75\x76\xe7\xfb\x78\xc1\xa0\xc0\x28\x3a\x3b\x89\x18\x4a\x29\x3b\xe3\x83\xfe\x9c\xf3\x7a\x43\x2b\x10\xb5\x33\x38\xa5\x54\x4b\x58\x99\xf5\xf5\xd6\xce\x76\xd3\x9e\x1e\xb2\xee\x39\x44\x5c\xc8\x82\x9c\x7b\xd0\x83\x75\xae\xd5\x4d\x01\xa2\x6e\x92\x92\x1e\x81\x17\xed\x89\x4b\x52\x2e\x55\x20\x19\x25\x58\xc7\xea\x95\x2b\x66\xce\xb5\x2c\xb0\x81\xbc\xbf\x4e\xb3\xa6\x42\x0c\x94\x86\x12\x2b\xcc\x18\x30\xaa\xab\x94\x52\x0b\x15\xc0\xb0\x33\xd7\x44\xfd\xa6\x45\x3f\x62\xca\x6a\x05\x39\x26\x09\xf3\xf9\xde\x52\x9c\x1a\xe1\x0c\x5e\x69\x4b\x56\xf8\x39\x1f\x96\x6d\x41\x22\xbb\xb6\x45\x12\xaa\x70\x97\x5a\x37\x8d\x5f\xd4\x95\xa3\xd8\xe9\xf6\xc5\xed\x91\x2a\x6d\xba\x9e\x58\xc8\xfe\xd7\x34\xa8\xbf\x78\xe7\x0c\xa9\xa3\x69\xcb\xeb\xba\xaa\x64\x5d\xc3\x10\x70\x07\x67\xfb\x11\xa3\x18\xa3\xda\xbe\x27\xb2\x4e\x9d\x94\xa2\x0a\x2a\x11\x3b\x5a\x76\xe8\x2f\x5d\x87\x0e\xd4\x46\x97\x4d\x46\xf0\x1d\x7d\x7c\x16\x05\x38\xa0\x4b\xe0\xa0\x28\xc9\x27\xde\xe0\x89\x2e\x4b\xd8\x77\x9a\xdf\x5e\xef\xd9\x5d\x9a\x11\x8c\x76\xc5\xed\x16\xee\x4d\x04\xef\xf8\x48\x89\x3c\x57\x86\xba\x26\xee\x34\xad\x79\x25\x4d\x34\x2b\xb4\x08\x4f\x94\x17\xe2\x69\x7d\x46\xdd\x40\xdb\x92\x61\x02\xb3\x2c\x7c\xad\xa2\xb5\x51\x98\x72\xb3\xfa\x94\x74\xae\x16\xa9\xe0\x08\x0a\xf8\x32\xca\x65\xe1\x6e\x35\xf3\x77\xac\x31\xd9\xe2\x12\xf6\x10\x5a\x36\x6d\xdb\x07\x0c\xab\xaf\x35\xfe\x15\x75\xbf\x33\xdc\x84\x4a\xb7\x25\xc2\xa2\x07\x98\x5a\xcf\x61\x35\xbf\xb5\x02\x57\x99\x0a\xaa\x89\x02\x03\xe3\xca\xe3\x8d\xa8\xa9\x8b\x05\x3d\x21\xec\x05\xe8\xb1\xef\xa6\xa3\x81\x1a\x9d\x31\xab\x13\xa6\xaf\x6f\x3a\xaf\xf6\xb5\x7f\x09\xc8\xb3\x8f\x06\xac\xab\xa9\x21\x9b\x0e\x60\x1b\xd4\xe2\x49\x97\x13\x7a\xa8\x5c\xc8\xed\xa7\xa3\xf1\x0b\x08\xfb\xf8\x6c\x8e\x4a\x5c\x6d\x15\x3d\x93\xaf\x6b\x20\x67\x33\x90\x7d\x82\xb8\x58\x1f\x78\xda\x8d\xe5\x4f\x16\x17\xed\xfe\xaf\xbf\x92\xe5\xb1\xea\xc3\x38\x57\xb8\x19\x75\xb5\x4f\x36\xb1\xf7\x3e\xd4\x76\xfc\xb7\x23\x8e\xf9\x91\x86\x6b\x16\x82\x8d\xc1\xe4\x94\x34\x36\x59\xd9\xbd\x5e\x91\x65\x16\xc3\x3d\x67\x18\xea\xa1\x36\x88\x42\x29\x17\xd4\xfe\x37\x22\xd5\x1f\xdd\xaf\xbf\x9f\x0f\xf6\x9f\x6a\x45\x3f\x07\x6a\xa1\xa2\x73\xcd\x6b\x3c\x2f\xe1\x52\xf6\x27\xb0\xeb\x07\x9b\x6b\x91\x0c\x9d\xe6\xea\xa1\xfe\x34\xd7\x47\x9b\x6b\x76\x54\x6e\x99\x6d\x39\x23\x0f\x69\x32\xf9\x3e\x5f\x8f\xb1\x9f\xb2\x31\x07\x73\x7c\x20\xed\xeb\x4e\xbc\x4c\xf9\x4e\x6a\x66\x8b\xf6\x4a\x0c\x4b\xbe\x61\xfe\xb8\xfb\x29\x50\x29\x86\xee\xdd\xbe\x53\x89\xb5\xc1\x25\x25\xb7\x4d\x67\xe3\x87\x41\xbb\xcb\xef\x3b\xfd\x31\x62\xc0\x5f\x7c\xed\xd9\xc8\xc9\x2f\x8b\x33\x9c\x6f\xd3\x63\xe9\xee\x4b\xcd\xfd\x44\x3f\x33\x90\xee\xba\xbb\x95\xb0\xf7\x49\x4d\xad\xeb\x1b\xd0\xf9\xa1\xf8\xf0\x2d\xa6\xe7\x9e\xce\xce\xfe\xdb\x7e\x5b\xbb\x7b\xd9\xfd\x37\x00\x00\xff\xff\x8f\xfe\xaa\xe6\xc5\x40\x00\x00") func dataConfig_schema_v35JsonBytes() ([]byte, error) { return bindataRead( diff --git a/components/cli/cli/compose/schema/data/config_schema_v3.5.json b/components/cli/cli/compose/schema/data/config_schema_v3.5.json index bcd3b11091..d3352c7d52 100644 --- a/components/cli/cli/compose/schema/data/config_schema_v3.5.json +++ b/components/cli/cli/compose/schema/data/config_schema_v3.5.json @@ -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": { diff --git a/components/cli/cli/compose/types/types.go b/components/cli/cli/compose/types/types.go index 27177718e2..c68187efe1 100644 --- a/components/cli/cli/compose/types/types.go +++ b/components/cli/cli/compose/types/types.go @@ -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 diff --git a/components/cli/contrib/completion/bash/docker b/components/cli/contrib/completion/bash/docker index 990934b610..5083e19449 100644 --- a/components/cli/contrib/completion/bash/docker +++ b/components/cli/contrib/completion/bash/docker @@ -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) diff --git a/components/cli/dockerfiles/Dockerfile.cross b/components/cli/dockerfiles/Dockerfile.cross index bbcf9e3431..ec1fb4fde6 100644 --- a/components/cli/dockerfiles/Dockerfile.cross +++ b/components/cli/dockerfiles/Dockerfile.cross @@ -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 diff --git a/components/cli/dockerfiles/Dockerfile.dev b/components/cli/dockerfiles/Dockerfile.dev index d85e9eef08..5459051901 100644 --- a/components/cli/dockerfiles/Dockerfile.dev +++ b/components/cli/dockerfiles/Dockerfile.dev @@ -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 diff --git a/components/cli/dockerfiles/Dockerfile.lint b/components/cli/dockerfiles/Dockerfile.lint index 43def14e53..d6ce2b9b8d 100644 --- a/components/cli/dockerfiles/Dockerfile.lint +++ b/components/cli/dockerfiles/Dockerfile.lint @@ -1,4 +1,4 @@ -FROM golang:1.8.5-alpine3.6 +FROM golang:1.9.2-alpine3.6 RUN apk add -U git diff --git a/components/cli/docs/reference/builder.md b/components/cli/docs/reference/builder.md index 15392b0003..4da646370d 100644 --- a/components/cli/docs/reference/builder.md +++ b/components/cli/docs/reference/builder.md @@ -823,16 +823,23 @@ change them using `docker run --env =`. ADD has two forms: -- `ADD ... ` -- `ADD ["",... ""]` (this form is required for paths containing +- `ADD [--chown=:] ... ` +- `ADD [--chown=:] ["",... ""]` (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 `` and adds them to the filesystem of the image at the path ``. -Multiple `` 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 `` 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 `` 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 `` 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 ... ` -- `COPY ["",... ""]` (this form is required for paths containing +- `COPY [--chown=:] ... ` +- `COPY [--chown=:] ["",... ""]` (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 `` and adds them to the filesystem of the container at the path ``. -Multiple `` resource may be specified but they must be relative -to the source directory that is being built (the context of the build). +Multiple `` 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 `` 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 diff --git a/components/cli/docs/reference/commandline/dockerd.md b/components/cli/docs/reference/commandline/dockerd.md index f8573910f1..e95016bd6b 100644 --- a/components/cli/docs/reference/commandline/dockerd.md +++ b/components/cli/docs/reference/commandline/dockerd.md @@ -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" diff --git a/components/cli/docs/reference/commandline/service_create.md b/components/cli/docs/reference/commandline/service_create.md index f196a66996..5634d72cff 100644 --- a/components/cli/docs/reference/commandline/service_create.md +++ b/components/cli/docs/reference/commandline/service_create.md @@ -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 diff --git a/components/cli/docs/reference/commandline/service_update.md b/components/cli/docs/reference/commandline/service_update.md index f57a26e802..ef9e0816d2 100644 --- a/components/cli/docs/reference/commandline/service_update.md +++ b/components/cli/docs/reference/commandline/service_update.md @@ -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 diff --git a/components/cli/docs/reference/commandline/trust_inspect.md b/components/cli/docs/reference/commandline/trust_inspect.md new file mode 100644 index 0000000000..db970a7d6c --- /dev/null +++ b/components/cli/docs/reference/commandline/trust_inspect.md @@ -0,0 +1,364 @@ +--- +title: "trust inspect" +description: "The inspect command description and usage" +keywords: "view, notary, trust" +--- + + + +# 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" + } + ] + } + ] + } +] +``` diff --git a/components/cli/e2e/image/build_test.go b/components/cli/e2e/image/build_test.go index 8445929ad3..b94566d321 100644 --- a/components/cli/e2e/image/build_test.go +++ b/components/cli/e2e/image/build_test.go @@ -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"), }) diff --git a/components/cli/man/dockerd.8.md b/components/cli/man/dockerd.8.md index 5ff7dcd331..f4e2be4c99 100644 --- a/components/cli/man/dockerd.8.md +++ b/components/cli/man/dockerd.8.md @@ -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` diff --git a/components/cli/vendor.conf b/components/cli/vendor.conf index 8c1ef581b1..97f8374527 100755 --- a/components/cli/vendor.conf +++ b/components/cli/vendor.conf @@ -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 diff --git a/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/helpers.go b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/helpers.go new file mode 100644 index 0000000000..350ab730c1 --- /dev/null +++ b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/helpers.go @@ -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 +} diff --git a/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/parse.go b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/parse.go new file mode 100644 index 0000000000..f39a7077a8 --- /dev/null +++ b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/parse.go @@ -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 + +} diff --git a/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/resource_management.go b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/resource_management.go new file mode 100644 index 0000000000..a89a118d62 --- /dev/null +++ b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/resource_management.go @@ -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 +} diff --git a/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/string.go b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/string.go new file mode 100644 index 0000000000..5e388bebb0 --- /dev/null +++ b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/string.go @@ -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 +} diff --git a/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/validate.go b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/validate.go new file mode 100644 index 0000000000..eee3706c74 --- /dev/null +++ b/components/cli/vendor/github.com/docker/swarmkit/api/genericresource/validate.go @@ -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 +} diff --git a/components/cli/vendor/github.com/docker/swarmkit/api/specs.pb.go b/components/cli/vendor/github.com/docker/swarmkit/api/specs.pb.go index b150d6f81e..dfd18a6d78 100644 --- a/components/cli/vendor/github.com/docker/swarmkit/api/specs.pb.go +++ b/components/cli/vendor/github.com/docker/swarmkit/api/specs.pb.go @@ -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, } diff --git a/components/cli/vendor/github.com/docker/swarmkit/api/specs.proto b/components/cli/vendor/github.com/docker/swarmkit/api/specs.proto index 2b002c54f2..14448d0409 100644 --- a/components/cli/vendor/github.com/docker/swarmkit/api/specs.proto +++ b/components/cli/vendor/github.com/docker/swarmkit/api/specs.proto @@ -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