mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #38632 from dperny/gmsa-support
Add support for GMSA CredentialSpecs from Swarmkit configs
This commit is contained in:
commit
cbb885b07a
14 changed files with 656 additions and 70 deletions
|
@ -213,19 +213,7 @@ func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter,
|
||||||
if versions.LessThan(cliVersion, "1.30") {
|
if versions.LessThan(cliVersion, "1.30") {
|
||||||
queryRegistry = true
|
queryRegistry = true
|
||||||
}
|
}
|
||||||
if versions.LessThan(cliVersion, "1.40") {
|
adjustForAPIVersion(cliVersion, &service)
|
||||||
if service.TaskTemplate.ContainerSpec != nil {
|
|
||||||
// Sysctls for docker swarm services weren't supported before
|
|
||||||
// API version 1.40
|
|
||||||
service.TaskTemplate.ContainerSpec.Sysctls = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.TaskTemplate.Placement != nil {
|
|
||||||
// MaxReplicas for docker swarm services weren't supported before
|
|
||||||
// API version 1.40
|
|
||||||
service.TaskTemplate.Placement.MaxReplicas = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := sr.backend.CreateService(service, encodedAuth, queryRegistry)
|
resp, err := sr.backend.CreateService(service, encodedAuth, queryRegistry)
|
||||||
|
@ -265,19 +253,7 @@ func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter,
|
||||||
if versions.LessThan(cliVersion, "1.30") {
|
if versions.LessThan(cliVersion, "1.30") {
|
||||||
queryRegistry = true
|
queryRegistry = true
|
||||||
}
|
}
|
||||||
if versions.LessThan(cliVersion, "1.40") {
|
adjustForAPIVersion(cliVersion, &service)
|
||||||
if service.TaskTemplate.ContainerSpec != nil {
|
|
||||||
// Sysctls for docker swarm services weren't supported before
|
|
||||||
// API version 1.40
|
|
||||||
service.TaskTemplate.ContainerSpec.Sysctls = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.TaskTemplate.Placement != nil {
|
|
||||||
// MaxReplicas for docker swarm services weren't supported before
|
|
||||||
// API version 1.40
|
|
||||||
service.TaskTemplate.Placement.MaxReplicas = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := sr.backend.UpdateService(vars["id"], version, service, flags, queryRegistry)
|
resp, err := sr.backend.UpdateService(vars["id"], version, service, flags, queryRegistry)
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"github.com/docker/docker/api/server/httputils"
|
"github.com/docker/docker/api/server/httputils"
|
||||||
basictypes "github.com/docker/docker/api/types"
|
basictypes "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/backend"
|
"github.com/docker/docker/api/types/backend"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// swarmLogs takes an http response, request, and selector, and writes the logs
|
// swarmLogs takes an http response, request, and selector, and writes the logs
|
||||||
|
@ -64,3 +66,33 @@ func (sr *swarmRouter) swarmLogs(ctx context.Context, w io.Writer, r *http.Reque
|
||||||
httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
|
httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adjustForAPIVersion takes a version and service spec and removes fields to
|
||||||
|
// make the spec compatible with the specified version.
|
||||||
|
func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
|
||||||
|
if cliVersion == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if versions.LessThan(cliVersion, "1.40") {
|
||||||
|
if service.TaskTemplate.ContainerSpec != nil {
|
||||||
|
// Sysctls for docker swarm services weren't supported before
|
||||||
|
// API version 1.40
|
||||||
|
service.TaskTemplate.ContainerSpec.Sysctls = nil
|
||||||
|
|
||||||
|
if service.TaskTemplate.ContainerSpec.Privileges != nil && service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec != nil {
|
||||||
|
// Support for setting credential-spec through configs was added in API 1.40
|
||||||
|
service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config = ""
|
||||||
|
}
|
||||||
|
for _, config := range service.TaskTemplate.ContainerSpec.Configs {
|
||||||
|
// support for the Runtime target was added in API 1.40
|
||||||
|
config.Runtime = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.TaskTemplate.Placement != nil {
|
||||||
|
// MaxReplicas for docker swarm services weren't supported before
|
||||||
|
// API version 1.40
|
||||||
|
service.TaskTemplate.Placement.MaxReplicas = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
87
api/server/router/swarm/helpers_test.go
Normal file
87
api/server/router/swarm/helpers_test.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package swarm // import "github.com/docker/docker/api/server/router/swarm"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdjustForAPIVersion(t *testing.T) {
|
||||||
|
var (
|
||||||
|
expectedSysctls = map[string]string{"foo": "bar"}
|
||||||
|
)
|
||||||
|
// testing the negative -- does this leave everything else alone? -- is
|
||||||
|
// prohibitively time-consuming to write, because it would need an object
|
||||||
|
// with literally every field filled in.
|
||||||
|
spec := &swarm.ServiceSpec{
|
||||||
|
TaskTemplate: swarm.TaskSpec{
|
||||||
|
ContainerSpec: &swarm.ContainerSpec{
|
||||||
|
Sysctls: expectedSysctls,
|
||||||
|
Privileges: &swarm.Privileges{
|
||||||
|
CredentialSpec: &swarm.CredentialSpec{
|
||||||
|
Config: "someconfig",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Configs: []*swarm.ConfigReference{
|
||||||
|
{
|
||||||
|
File: &swarm.ConfigReferenceFileTarget{
|
||||||
|
Name: "foo",
|
||||||
|
UID: "bar",
|
||||||
|
GID: "baz",
|
||||||
|
},
|
||||||
|
ConfigID: "configFile",
|
||||||
|
ConfigName: "configFile",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
|
||||||
|
ConfigID: "configRuntime",
|
||||||
|
ConfigName: "configRuntime",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Placement: &swarm.Placement{
|
||||||
|
MaxReplicas: 222,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// first, does calling this with a later version correctly NOT strip
|
||||||
|
// fields? do the later version first, so we can reuse this spec in the
|
||||||
|
// next test.
|
||||||
|
adjustForAPIVersion("1.40", spec)
|
||||||
|
if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls) {
|
||||||
|
t.Error("Sysctls was stripped from spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "someconfig" {
|
||||||
|
t.Error("CredentialSpec.Config field was stripped from spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.TaskTemplate.ContainerSpec.Configs[1].Runtime == nil {
|
||||||
|
t.Error("ConfigReferenceRuntimeTarget was stripped from spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.TaskTemplate.Placement.MaxReplicas != 222 {
|
||||||
|
t.Error("MaxReplicas was stripped from spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
// next, does calling this with an earlier version correctly strip fields?
|
||||||
|
adjustForAPIVersion("1.29", spec)
|
||||||
|
if spec.TaskTemplate.ContainerSpec.Sysctls != nil {
|
||||||
|
t.Error("Sysctls was not stripped from spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "" {
|
||||||
|
t.Error("CredentialSpec.Config field was not stripped from spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.TaskTemplate.ContainerSpec.Configs[1].Runtime != nil {
|
||||||
|
t.Error("ConfigReferenceRuntimeTarget was not stripped from spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
if spec.TaskTemplate.Placement.MaxReplicas != 0 {
|
||||||
|
t.Error("MaxReplicas was not stripped from spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2623,8 +2623,20 @@ definitions:
|
||||||
type: "object"
|
type: "object"
|
||||||
description: "CredentialSpec for managed service account (Windows only)"
|
description: "CredentialSpec for managed service account (Windows only)"
|
||||||
properties:
|
properties:
|
||||||
|
Config:
|
||||||
|
type: "string"
|
||||||
|
example: "0bt9dmxjvjiqermk6xrop3ekq"
|
||||||
|
description: |
|
||||||
|
Load credential spec from a Swarm Config with the given ID.
|
||||||
|
The specified config must also be present in the Configs field with the Runtime property set.
|
||||||
|
|
||||||
|
<p><br /></p>
|
||||||
|
|
||||||
|
|
||||||
|
> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, and `CredentialSpec.Config` are mutually exclusive.
|
||||||
File:
|
File:
|
||||||
type: "string"
|
type: "string"
|
||||||
|
example: "spec.json"
|
||||||
description: |
|
description: |
|
||||||
Load credential spec from this file. The file is read by the daemon, and must be present in the
|
Load credential spec from this file. The file is read by the daemon, and must be present in the
|
||||||
`CredentialSpecs` subdirectory in the docker data directory, which defaults to
|
`CredentialSpecs` subdirectory in the docker data directory, which defaults to
|
||||||
|
@ -2634,7 +2646,7 @@ definitions:
|
||||||
|
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
> **Note**: `CredentialSpec.File` and `CredentialSpec.Registry` are mutually exclusive.
|
> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, and `CredentialSpec.Config` are mutually exclusive.
|
||||||
Registry:
|
Registry:
|
||||||
type: "string"
|
type: "string"
|
||||||
description: |
|
description: |
|
||||||
|
@ -2646,7 +2658,7 @@ definitions:
|
||||||
<p><br /></p>
|
<p><br /></p>
|
||||||
|
|
||||||
|
|
||||||
> **Note**: `CredentialSpec.File` and `CredentialSpec.Registry` are mutually exclusive.
|
> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`, and `CredentialSpec.Config` are mutually exclusive.
|
||||||
SELinuxContext:
|
SELinuxContext:
|
||||||
type: "object"
|
type: "object"
|
||||||
description: "SELinux labels of the container"
|
description: "SELinux labels of the container"
|
||||||
|
@ -2757,7 +2769,12 @@ definitions:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
File:
|
File:
|
||||||
description: "File represents a specific target that is backed by a file."
|
description: |
|
||||||
|
File represents a specific target that is backed by a file.
|
||||||
|
|
||||||
|
<p><br /><p>
|
||||||
|
|
||||||
|
> **Note**: `Configs.File` and `Configs.Runtime` are mutually exclusive
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
Name:
|
Name:
|
||||||
|
@ -2773,6 +2790,14 @@ definitions:
|
||||||
description: "Mode represents the FileMode of the file."
|
description: "Mode represents the FileMode of the file."
|
||||||
type: "integer"
|
type: "integer"
|
||||||
format: "uint32"
|
format: "uint32"
|
||||||
|
Runtime:
|
||||||
|
description: |
|
||||||
|
Runtime represents a target that is not mounted into the container but is used by the task
|
||||||
|
|
||||||
|
<p><br /><p>
|
||||||
|
|
||||||
|
> **Note**: `Configs.File` and `Configs.Runtime` are mutually exclusive
|
||||||
|
type: "object"
|
||||||
ConfigID:
|
ConfigID:
|
||||||
description: "ConfigID represents the ID of the specific config that we're referencing."
|
description: "ConfigID represents the ID of the specific config that we're referencing."
|
||||||
type: "string"
|
type: "string"
|
||||||
|
|
|
@ -27,9 +27,14 @@ type ConfigReferenceFileTarget struct {
|
||||||
Mode os.FileMode
|
Mode os.FileMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigReferenceRuntimeTarget is a target for a config specifying that it
|
||||||
|
// isn't mounted into the container but instead has some other purpose.
|
||||||
|
type ConfigReferenceRuntimeTarget struct{}
|
||||||
|
|
||||||
// ConfigReference is a reference to a config in swarm
|
// ConfigReference is a reference to a config in swarm
|
||||||
type ConfigReference struct {
|
type ConfigReference struct {
|
||||||
File *ConfigReferenceFileTarget
|
File *ConfigReferenceFileTarget `json:",omitempty"`
|
||||||
|
Runtime *ConfigReferenceRuntimeTarget `json:",omitempty"`
|
||||||
ConfigID string
|
ConfigID string
|
||||||
ConfigName string
|
ConfigName string
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ type SELinuxContext struct {
|
||||||
|
|
||||||
// CredentialSpec for managed service account (Windows only)
|
// CredentialSpec for managed service account (Windows only)
|
||||||
type CredentialSpec struct {
|
type CredentialSpec struct {
|
||||||
|
Config string
|
||||||
File string
|
File string
|
||||||
Registry string
|
Registry string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package convert // import "github.com/docker/docker/daemon/cluster/convert"
|
package convert // import "github.com/docker/docker/daemon/cluster/convert"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -10,6 +9,7 @@ import (
|
||||||
types "github.com/docker/docker/api/types/swarm"
|
types "github.com/docker/docker/api/types/swarm"
|
||||||
swarmapi "github.com/docker/swarmkit/api"
|
swarmapi "github.com/docker/swarmkit/api"
|
||||||
gogotypes "github.com/gogo/protobuf/types"
|
gogotypes "github.com/gogo/protobuf/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,13 +52,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
|
||||||
containerSpec.Privileges = &types.Privileges{}
|
containerSpec.Privileges = &types.Privileges{}
|
||||||
|
|
||||||
if c.Privileges.CredentialSpec != nil {
|
if c.Privileges.CredentialSpec != nil {
|
||||||
containerSpec.Privileges.CredentialSpec = &types.CredentialSpec{}
|
containerSpec.Privileges.CredentialSpec = credentialSpecFromGRPC(c.Privileges.CredentialSpec)
|
||||||
switch c.Privileges.CredentialSpec.Source.(type) {
|
|
||||||
case *swarmapi.Privileges_CredentialSpec_File:
|
|
||||||
containerSpec.Privileges.CredentialSpec.File = c.Privileges.CredentialSpec.GetFile()
|
|
||||||
case *swarmapi.Privileges_CredentialSpec_Registry:
|
|
||||||
containerSpec.Privileges.CredentialSpec.Registry = c.Privileges.CredentialSpec.GetRegistry()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Privileges.SELinuxContext != nil {
|
if c.Privileges.SELinuxContext != nil {
|
||||||
|
@ -184,14 +178,26 @@ func secretReferencesFromGRPC(sr []*swarmapi.SecretReference) []*types.SecretRef
|
||||||
return refs
|
return refs
|
||||||
}
|
}
|
||||||
|
|
||||||
func configReferencesToGRPC(sr []*types.ConfigReference) []*swarmapi.ConfigReference {
|
func configReferencesToGRPC(sr []*types.ConfigReference) ([]*swarmapi.ConfigReference, error) {
|
||||||
refs := make([]*swarmapi.ConfigReference, 0, len(sr))
|
refs := make([]*swarmapi.ConfigReference, 0, len(sr))
|
||||||
for _, s := range sr {
|
for _, s := range sr {
|
||||||
ref := &swarmapi.ConfigReference{
|
ref := &swarmapi.ConfigReference{
|
||||||
ConfigID: s.ConfigID,
|
ConfigID: s.ConfigID,
|
||||||
ConfigName: s.ConfigName,
|
ConfigName: s.ConfigName,
|
||||||
}
|
}
|
||||||
if s.File != nil {
|
switch {
|
||||||
|
case s.Runtime == nil && s.File == nil:
|
||||||
|
return nil, errors.New("either File or Runtime should be set")
|
||||||
|
case s.Runtime != nil && s.File != nil:
|
||||||
|
return nil, errors.New("cannot specify both File and Runtime")
|
||||||
|
case s.Runtime != nil:
|
||||||
|
// Runtime target was added in API v1.40 and takes precedence over
|
||||||
|
// File target. However, File and Runtime targets are mutually exclusive,
|
||||||
|
// so we should never have both.
|
||||||
|
ref.Target = &swarmapi.ConfigReference_Runtime{
|
||||||
|
Runtime: &swarmapi.RuntimeTarget{},
|
||||||
|
}
|
||||||
|
case s.File != nil:
|
||||||
ref.Target = &swarmapi.ConfigReference_File{
|
ref.Target = &swarmapi.ConfigReference_File{
|
||||||
File: &swarmapi.FileTarget{
|
File: &swarmapi.FileTarget{
|
||||||
Name: s.File.Name,
|
Name: s.File.Name,
|
||||||
|
@ -205,28 +211,32 @@ func configReferencesToGRPC(sr []*types.ConfigReference) []*swarmapi.ConfigRefer
|
||||||
refs = append(refs, ref)
|
refs = append(refs, ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
return refs
|
return refs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigReference {
|
func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigReference {
|
||||||
refs := make([]*types.ConfigReference, 0, len(sr))
|
refs := make([]*types.ConfigReference, 0, len(sr))
|
||||||
for _, s := range sr {
|
for _, s := range sr {
|
||||||
target := s.GetFile()
|
|
||||||
if target == nil {
|
r := &types.ConfigReference{
|
||||||
// not a file target
|
ConfigID: s.ConfigID,
|
||||||
logrus.Warnf("config target not a file: config=%s", s.ConfigID)
|
ConfigName: s.ConfigName,
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
refs = append(refs, &types.ConfigReference{
|
if target := s.GetRuntime(); target != nil {
|
||||||
File: &types.ConfigReferenceFileTarget{
|
r.Runtime = &types.ConfigReferenceRuntimeTarget{}
|
||||||
|
} else if target := s.GetFile(); target != nil {
|
||||||
|
r.File = &types.ConfigReferenceFileTarget{
|
||||||
Name: target.Name,
|
Name: target.Name,
|
||||||
UID: target.UID,
|
UID: target.UID,
|
||||||
GID: target.GID,
|
GID: target.GID,
|
||||||
Mode: target.Mode,
|
Mode: target.Mode,
|
||||||
},
|
}
|
||||||
ConfigID: s.ConfigID,
|
} else {
|
||||||
ConfigName: s.ConfigName,
|
// not a file target
|
||||||
})
|
logrus.Warnf("config target not known: config=%s", s.ConfigID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
refs = append(refs, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
return refs
|
return refs
|
||||||
|
@ -249,7 +259,6 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
||||||
ReadOnly: c.ReadOnly,
|
ReadOnly: c.ReadOnly,
|
||||||
Hosts: c.Hosts,
|
Hosts: c.Hosts,
|
||||||
Secrets: secretReferencesToGRPC(c.Secrets),
|
Secrets: secretReferencesToGRPC(c.Secrets),
|
||||||
Configs: configReferencesToGRPC(c.Configs),
|
|
||||||
Isolation: isolationToGRPC(c.Isolation),
|
Isolation: isolationToGRPC(c.Isolation),
|
||||||
Init: initToGRPC(c.Init),
|
Init: initToGRPC(c.Init),
|
||||||
Sysctls: c.Sysctls,
|
Sysctls: c.Sysctls,
|
||||||
|
@ -272,22 +281,11 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
||||||
containerSpec.Privileges = &swarmapi.Privileges{}
|
containerSpec.Privileges = &swarmapi.Privileges{}
|
||||||
|
|
||||||
if c.Privileges.CredentialSpec != nil {
|
if c.Privileges.CredentialSpec != nil {
|
||||||
containerSpec.Privileges.CredentialSpec = &swarmapi.Privileges_CredentialSpec{}
|
cs, err := credentialSpecToGRPC(c.Privileges.CredentialSpec)
|
||||||
|
if err != nil {
|
||||||
if c.Privileges.CredentialSpec.File != "" && c.Privileges.CredentialSpec.Registry != "" {
|
return nil, errors.Wrap(err, "invalid CredentialSpec")
|
||||||
return nil, errors.New("cannot specify both \"file\" and \"registry\" credential specs")
|
|
||||||
}
|
|
||||||
if c.Privileges.CredentialSpec.File != "" {
|
|
||||||
containerSpec.Privileges.CredentialSpec.Source = &swarmapi.Privileges_CredentialSpec_File{
|
|
||||||
File: c.Privileges.CredentialSpec.File,
|
|
||||||
}
|
|
||||||
} else if c.Privileges.CredentialSpec.Registry != "" {
|
|
||||||
containerSpec.Privileges.CredentialSpec.Source = &swarmapi.Privileges_CredentialSpec_Registry{
|
|
||||||
Registry: c.Privileges.CredentialSpec.Registry,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("must either provide \"file\" or \"registry\" for credential spec")
|
|
||||||
}
|
}
|
||||||
|
containerSpec.Privileges.CredentialSpec = cs
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Privileges.SELinuxContext != nil {
|
if c.Privileges.SELinuxContext != nil {
|
||||||
|
@ -301,6 +299,14 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Configs != nil {
|
||||||
|
configs, err := configReferencesToGRPC(c.Configs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "invalid Config")
|
||||||
|
}
|
||||||
|
containerSpec.Configs = configs
|
||||||
|
}
|
||||||
|
|
||||||
// Mounts
|
// Mounts
|
||||||
for _, m := range c.Mounts {
|
for _, m := range c.Mounts {
|
||||||
mount := swarmapi.Mount{
|
mount := swarmapi.Mount{
|
||||||
|
@ -359,6 +365,60 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
||||||
return containerSpec, nil
|
return containerSpec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func credentialSpecFromGRPC(c *swarmapi.Privileges_CredentialSpec) *types.CredentialSpec {
|
||||||
|
cs := &types.CredentialSpec{}
|
||||||
|
switch c.Source.(type) {
|
||||||
|
case *swarmapi.Privileges_CredentialSpec_Config:
|
||||||
|
cs.Config = c.GetConfig()
|
||||||
|
case *swarmapi.Privileges_CredentialSpec_File:
|
||||||
|
cs.File = c.GetFile()
|
||||||
|
case *swarmapi.Privileges_CredentialSpec_Registry:
|
||||||
|
cs.Registry = c.GetRegistry()
|
||||||
|
}
|
||||||
|
return cs
|
||||||
|
}
|
||||||
|
|
||||||
|
func credentialSpecToGRPC(c *types.CredentialSpec) (*swarmapi.Privileges_CredentialSpec, error) {
|
||||||
|
var opts []string
|
||||||
|
|
||||||
|
if c.Config != "" {
|
||||||
|
opts = append(opts, `"config"`)
|
||||||
|
}
|
||||||
|
if c.File != "" {
|
||||||
|
opts = append(opts, `"file"`)
|
||||||
|
}
|
||||||
|
if c.Registry != "" {
|
||||||
|
opts = append(opts, `"registry"`)
|
||||||
|
}
|
||||||
|
l := len(opts)
|
||||||
|
switch {
|
||||||
|
case l == 0:
|
||||||
|
return nil, errors.New(`must either provide "file", "registry", or "config" for credential spec`)
|
||||||
|
case l == 2:
|
||||||
|
return nil, fmt.Errorf("cannot specify both %s and %s credential specs", opts[0], opts[1])
|
||||||
|
case l > 2:
|
||||||
|
return nil, fmt.Errorf("cannot specify both %s, and %s credential specs", strings.Join(opts[:l-1], ", "), opts[l-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := &swarmapi.Privileges_CredentialSpec{}
|
||||||
|
switch {
|
||||||
|
case c.Config != "":
|
||||||
|
spec.Source = &swarmapi.Privileges_CredentialSpec_Config{
|
||||||
|
Config: c.Config,
|
||||||
|
}
|
||||||
|
case c.File != "":
|
||||||
|
spec.Source = &swarmapi.Privileges_CredentialSpec_File{
|
||||||
|
File: c.File,
|
||||||
|
}
|
||||||
|
case c.Registry != "":
|
||||||
|
spec.Source = &swarmapi.Privileges_CredentialSpec_Registry{
|
||||||
|
Registry: c.Registry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec, nil
|
||||||
|
}
|
||||||
|
|
||||||
func healthConfigFromGRPC(h *swarmapi.HealthConfig) *container.HealthConfig {
|
func healthConfigFromGRPC(h *swarmapi.HealthConfig) *container.HealthConfig {
|
||||||
interval, _ := gogotypes.DurationFromProto(h.Interval)
|
interval, _ := gogotypes.DurationFromProto(h.Interval)
|
||||||
timeout, _ := gogotypes.DurationFromProto(h.Timeout)
|
timeout, _ := gogotypes.DurationFromProto(h.Timeout)
|
||||||
|
|
|
@ -233,6 +233,167 @@ func TestServiceConvertFromGRPCIsolation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServiceConvertToGRPCCredentialSpec(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
from swarmtypes.CredentialSpec
|
||||||
|
to swarmapi.Privileges_CredentialSpec
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty credential spec",
|
||||||
|
from: swarmtypes.CredentialSpec{},
|
||||||
|
to: swarmapi.Privileges_CredentialSpec{},
|
||||||
|
expectedErr: `invalid CredentialSpec: must either provide "file", "registry", or "config" for credential spec`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config and file credential spec",
|
||||||
|
from: swarmtypes.CredentialSpec{
|
||||||
|
Config: "0bt9dmxjvjiqermk6xrop3ekq",
|
||||||
|
File: "spec.json",
|
||||||
|
},
|
||||||
|
to: swarmapi.Privileges_CredentialSpec{},
|
||||||
|
expectedErr: `invalid CredentialSpec: cannot specify both "config" and "file" credential specs`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config and registry credential spec",
|
||||||
|
from: swarmtypes.CredentialSpec{
|
||||||
|
Config: "0bt9dmxjvjiqermk6xrop3ekq",
|
||||||
|
Registry: "testing",
|
||||||
|
},
|
||||||
|
to: swarmapi.Privileges_CredentialSpec{},
|
||||||
|
expectedErr: `invalid CredentialSpec: cannot specify both "config" and "registry" credential specs`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file and registry credential spec",
|
||||||
|
from: swarmtypes.CredentialSpec{
|
||||||
|
File: "spec.json",
|
||||||
|
Registry: "testing",
|
||||||
|
},
|
||||||
|
to: swarmapi.Privileges_CredentialSpec{},
|
||||||
|
expectedErr: `invalid CredentialSpec: cannot specify both "file" and "registry" credential specs`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config and file and registry credential spec",
|
||||||
|
from: swarmtypes.CredentialSpec{
|
||||||
|
Config: "0bt9dmxjvjiqermk6xrop3ekq",
|
||||||
|
File: "spec.json",
|
||||||
|
Registry: "testing",
|
||||||
|
},
|
||||||
|
to: swarmapi.Privileges_CredentialSpec{},
|
||||||
|
expectedErr: `invalid CredentialSpec: cannot specify both "config", "file", and "registry" credential specs`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config credential spec",
|
||||||
|
from: swarmtypes.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
|
||||||
|
to: swarmapi.Privileges_CredentialSpec{
|
||||||
|
Source: &swarmapi.Privileges_CredentialSpec_Config{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file credential spec",
|
||||||
|
from: swarmtypes.CredentialSpec{File: "foo.json"},
|
||||||
|
to: swarmapi.Privileges_CredentialSpec{
|
||||||
|
Source: &swarmapi.Privileges_CredentialSpec_File{File: "foo.json"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "registry credential spec",
|
||||||
|
from: swarmtypes.CredentialSpec{Registry: "testing"},
|
||||||
|
to: swarmapi.Privileges_CredentialSpec{
|
||||||
|
Source: &swarmapi.Privileges_CredentialSpec_Registry{Registry: "testing"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
c := c
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
s := swarmtypes.ServiceSpec{
|
||||||
|
TaskTemplate: swarmtypes.TaskSpec{
|
||||||
|
ContainerSpec: &swarmtypes.ContainerSpec{
|
||||||
|
Privileges: &swarmtypes.Privileges{
|
||||||
|
CredentialSpec: &c.from,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := ServiceSpecToGRPC(s)
|
||||||
|
if c.expectedErr != "" {
|
||||||
|
assert.Error(t, err, c.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
v, ok := res.Task.Runtime.(*swarmapi.TaskSpec_Container)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected type swarmapi.TaskSpec_Container")
|
||||||
|
}
|
||||||
|
assert.DeepEqual(t, c.to, *v.Container.Privileges.CredentialSpec)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceConvertFromGRPCCredentialSpec(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
from swarmapi.Privileges_CredentialSpec
|
||||||
|
to *swarmtypes.CredentialSpec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty credential spec",
|
||||||
|
from: swarmapi.Privileges_CredentialSpec{},
|
||||||
|
to: &swarmtypes.CredentialSpec{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config credential spec",
|
||||||
|
from: swarmapi.Privileges_CredentialSpec{
|
||||||
|
Source: &swarmapi.Privileges_CredentialSpec_Config{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
|
||||||
|
},
|
||||||
|
to: &swarmtypes.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file credential spec",
|
||||||
|
from: swarmapi.Privileges_CredentialSpec{
|
||||||
|
Source: &swarmapi.Privileges_CredentialSpec_File{File: "foo.json"},
|
||||||
|
},
|
||||||
|
to: &swarmtypes.CredentialSpec{File: "foo.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "registry credential spec",
|
||||||
|
from: swarmapi.Privileges_CredentialSpec{
|
||||||
|
Source: &swarmapi.Privileges_CredentialSpec_Registry{Registry: "testing"},
|
||||||
|
},
|
||||||
|
to: &swarmtypes.CredentialSpec{Registry: "testing"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
gs := swarmapi.Service{
|
||||||
|
Spec: swarmapi.ServiceSpec{
|
||||||
|
Task: swarmapi.TaskSpec{
|
||||||
|
Runtime: &swarmapi.TaskSpec_Container{
|
||||||
|
Container: &swarmapi.ContainerSpec{
|
||||||
|
Privileges: &swarmapi.Privileges{
|
||||||
|
CredentialSpec: &tc.from,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc, err := ServiceFromGRPC(gs)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, svc.Spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec, tc.to)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestServiceConvertToGRPCNetworkAtachmentRuntime(t *testing.T) {
|
func TestServiceConvertToGRPCNetworkAtachmentRuntime(t *testing.T) {
|
||||||
someid := "asfjkl"
|
someid := "asfjkl"
|
||||||
s := swarmtypes.ServiceSpec{
|
s := swarmtypes.ServiceSpec{
|
||||||
|
@ -306,3 +467,147 @@ func TestTaskConvertFromGRPCNetworkAttachment(t *testing.T) {
|
||||||
t.Fatalf("expected Runtime to be %v", swarmtypes.RuntimeNetworkAttachment)
|
t.Fatalf("expected Runtime to be %v", swarmtypes.RuntimeNetworkAttachment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestServiceConvertFromGRPCConfigs tests that converting config references
|
||||||
|
// from GRPC is correct
|
||||||
|
func TestServiceConvertFromGRPCConfigs(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
from *swarmapi.ConfigReference
|
||||||
|
to *swarmtypes.ConfigReference
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "file",
|
||||||
|
from: &swarmapi.ConfigReference{
|
||||||
|
ConfigID: "configFile",
|
||||||
|
ConfigName: "configFile",
|
||||||
|
Target: &swarmapi.ConfigReference_File{
|
||||||
|
// skip mode, if everything else here works mode will too. otherwise we'd need to import os.
|
||||||
|
File: &swarmapi.FileTarget{Name: "foo", UID: "bar", GID: "baz"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
to: &swarmtypes.ConfigReference{
|
||||||
|
ConfigID: "configFile",
|
||||||
|
ConfigName: "configFile",
|
||||||
|
File: &swarmtypes.ConfigReferenceFileTarget{Name: "foo", UID: "bar", GID: "baz"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "runtime",
|
||||||
|
from: &swarmapi.ConfigReference{
|
||||||
|
ConfigID: "configRuntime",
|
||||||
|
ConfigName: "configRuntime",
|
||||||
|
Target: &swarmapi.ConfigReference_Runtime{Runtime: &swarmapi.RuntimeTarget{}},
|
||||||
|
},
|
||||||
|
to: &swarmtypes.ConfigReference{
|
||||||
|
ConfigID: "configRuntime",
|
||||||
|
ConfigName: "configRuntime",
|
||||||
|
Runtime: &swarmtypes.ConfigReferenceRuntimeTarget{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
grpcService := swarmapi.Service{
|
||||||
|
Spec: swarmapi.ServiceSpec{
|
||||||
|
Task: swarmapi.TaskSpec{
|
||||||
|
Runtime: &swarmapi.TaskSpec_Container{
|
||||||
|
Container: &swarmapi.ContainerSpec{
|
||||||
|
Configs: []*swarmapi.ConfigReference{tc.from},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
engineService, err := ServiceFromGRPC(grpcService)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t,
|
||||||
|
engineService.Spec.TaskTemplate.ContainerSpec.Configs[0],
|
||||||
|
tc.to,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestServiceConvertToGRPCConfigs tests that converting config references to
|
||||||
|
// GRPC is correct
|
||||||
|
func TestServiceConvertToGRPCConfigs(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
from *swarmtypes.ConfigReference
|
||||||
|
to *swarmapi.ConfigReference
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "file",
|
||||||
|
from: &swarmtypes.ConfigReference{
|
||||||
|
ConfigID: "configFile",
|
||||||
|
ConfigName: "configFile",
|
||||||
|
File: &swarmtypes.ConfigReferenceFileTarget{Name: "foo", UID: "bar", GID: "baz"},
|
||||||
|
},
|
||||||
|
to: &swarmapi.ConfigReference{
|
||||||
|
ConfigID: "configFile",
|
||||||
|
ConfigName: "configFile",
|
||||||
|
Target: &swarmapi.ConfigReference_File{
|
||||||
|
// skip mode, if everything else here works mode will too. otherwise we'd need to import os.
|
||||||
|
File: &swarmapi.FileTarget{Name: "foo", UID: "bar", GID: "baz"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "runtime",
|
||||||
|
from: &swarmtypes.ConfigReference{
|
||||||
|
ConfigID: "configRuntime",
|
||||||
|
ConfigName: "configRuntime",
|
||||||
|
Runtime: &swarmtypes.ConfigReferenceRuntimeTarget{},
|
||||||
|
},
|
||||||
|
to: &swarmapi.ConfigReference{
|
||||||
|
ConfigID: "configRuntime",
|
||||||
|
ConfigName: "configRuntime",
|
||||||
|
Target: &swarmapi.ConfigReference_Runtime{Runtime: &swarmapi.RuntimeTarget{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file and runtime",
|
||||||
|
from: &swarmtypes.ConfigReference{
|
||||||
|
ConfigID: "fileAndRuntime",
|
||||||
|
ConfigName: "fileAndRuntime",
|
||||||
|
File: &swarmtypes.ConfigReferenceFileTarget{},
|
||||||
|
Runtime: &swarmtypes.ConfigReferenceRuntimeTarget{},
|
||||||
|
},
|
||||||
|
expectedErr: "invalid Config: cannot specify both File and Runtime",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none",
|
||||||
|
from: &swarmtypes.ConfigReference{
|
||||||
|
ConfigID: "none",
|
||||||
|
ConfigName: "none",
|
||||||
|
},
|
||||||
|
expectedErr: "invalid Config: either File or Runtime should be set",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
engineServiceSpec := swarmtypes.ServiceSpec{
|
||||||
|
TaskTemplate: swarmtypes.TaskSpec{
|
||||||
|
ContainerSpec: &swarmtypes.ContainerSpec{
|
||||||
|
Configs: []*swarmtypes.ConfigReference{tc.from},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcServiceSpec, err := ServiceSpecToGRPC(engineServiceSpec)
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
assert.Error(t, err, tc.expectedErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
taskRuntime := grpcServiceSpec.Task.Runtime.(*swarmapi.TaskSpec_Container)
|
||||||
|
assert.DeepEqual(t, taskRuntime.Container.Configs[0], tc.to)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -651,6 +651,8 @@ func (c *containerConfig) applyPrivileges(hc *enginecontainer.HostConfig) {
|
||||||
hc.SecurityOpt = append(hc.SecurityOpt, "credentialspec=file://"+credentials.GetFile())
|
hc.SecurityOpt = append(hc.SecurityOpt, "credentialspec=file://"+credentials.GetFile())
|
||||||
case *api.Privileges_CredentialSpec_Registry:
|
case *api.Privileges_CredentialSpec_Registry:
|
||||||
hc.SecurityOpt = append(hc.SecurityOpt, "credentialspec=registry://"+credentials.GetRegistry())
|
hc.SecurityOpt = append(hc.SecurityOpt, "credentialspec=registry://"+credentials.GetRegistry())
|
||||||
|
case *api.Privileges_CredentialSpec_Config:
|
||||||
|
hc.SecurityOpt = append(hc.SecurityOpt, "credentialspec=config://"+credentials.GetConfig())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,3 +80,56 @@ func TestContainerLabels(t *testing.T) {
|
||||||
labels := c.labels()
|
labels := c.labels()
|
||||||
assert.DeepEqual(t, expected, labels)
|
assert.DeepEqual(t, expected, labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCredentialSpecConversion(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
from swarmapi.Privileges_CredentialSpec
|
||||||
|
to []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "none",
|
||||||
|
from: swarmapi.Privileges_CredentialSpec{},
|
||||||
|
to: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config",
|
||||||
|
from: swarmapi.Privileges_CredentialSpec{
|
||||||
|
Source: &swarmapi.Privileges_CredentialSpec_Config{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
|
||||||
|
},
|
||||||
|
to: []string{"credentialspec=config://0bt9dmxjvjiqermk6xrop3ekq"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file",
|
||||||
|
from: swarmapi.Privileges_CredentialSpec{
|
||||||
|
Source: &swarmapi.Privileges_CredentialSpec_File{File: "foo.json"},
|
||||||
|
},
|
||||||
|
to: []string{"credentialspec=file://foo.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "registry",
|
||||||
|
from: swarmapi.Privileges_CredentialSpec{
|
||||||
|
Source: &swarmapi.Privileges_CredentialSpec_Registry{Registry: "testing"},
|
||||||
|
},
|
||||||
|
to: []string{"credentialspec=registry://testing"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
c := c
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
task := swarmapi.Task{
|
||||||
|
Spec: swarmapi.TaskSpec{
|
||||||
|
Runtime: &swarmapi.TaskSpec_Container{
|
||||||
|
Container: &swarmapi.ContainerSpec{
|
||||||
|
Privileges: &swarmapi.Privileges{
|
||||||
|
CredentialSpec: &c.from,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config := containerConfig{task: &task}
|
||||||
|
assert.DeepEqual(t, c.to, config.hostConfig().SecurityOpt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -230,7 +230,14 @@ func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) {
|
||||||
for _, ref := range c.ConfigReferences {
|
for _, ref := range c.ConfigReferences {
|
||||||
// TODO (ehazlett): use type switch when more are supported
|
// TODO (ehazlett): use type switch when more are supported
|
||||||
if ref.File == nil {
|
if ref.File == nil {
|
||||||
logrus.Error("config target type is not a file target")
|
// Runtime configs are not mounted into the container, but they're
|
||||||
|
// a valid type of config so we should not error when we encounter
|
||||||
|
// one.
|
||||||
|
if ref.Runtime == nil {
|
||||||
|
logrus.Error("config target type is not a file or runtime target")
|
||||||
|
}
|
||||||
|
// However, in any case, this isn't a file config, so we have no
|
||||||
|
// further work to do
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,14 @@ func (daemon *Daemon) setupConfigDir(c *container.Container) (setupErr error) {
|
||||||
for _, configRef := range c.ConfigReferences {
|
for _, configRef := range c.ConfigReferences {
|
||||||
// TODO (ehazlett): use type switch when more are supported
|
// TODO (ehazlett): use type switch when more are supported
|
||||||
if configRef.File == nil {
|
if configRef.File == nil {
|
||||||
logrus.Error("config target type is not a file target")
|
// Runtime configs are not mounted into the container, but they're
|
||||||
|
// a valid type of config so we should not error when we encounter
|
||||||
|
// one.
|
||||||
|
if configRef.Runtime == nil {
|
||||||
|
logrus.Error("config target type is not a file or runtime target")
|
||||||
|
}
|
||||||
|
// However, in any case, this isn't a file config, so we have no
|
||||||
|
// further work to do
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -288,6 +288,28 @@ func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.S
|
||||||
if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil {
|
if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if match, csValue = getCredentialSpec("config://", splitsOpt[1]); match {
|
||||||
|
// if the container does not have a DependencyStore, then it
|
||||||
|
// isn't swarmkit managed. In order to avoid creating any
|
||||||
|
// impression that `config://` is a valid API, return the same
|
||||||
|
// error as if you'd passed any other random word.
|
||||||
|
if c.DependencyStore == nil {
|
||||||
|
return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// after this point, we can return regular swarmkit-relevant
|
||||||
|
// errors, because we'll know this container is managed.
|
||||||
|
if csValue == "" {
|
||||||
|
return fmt.Errorf("no value supplied for config:// credential spec security option")
|
||||||
|
}
|
||||||
|
|
||||||
|
csConfig, err := c.DependencyStore.Configs().Get(csValue)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error getting value from config store")
|
||||||
|
}
|
||||||
|
// stuff the resulting secret data into a string to use as the
|
||||||
|
// CredentialSpec
|
||||||
|
cs = string(csConfig.Spec.Data)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
|
return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ keywords: "API, Docker, rcli, REST, documentation"
|
||||||
* `GET /services/{id}` now returns `Sysctls` as part of the `ContainerSpec`.
|
* `GET /services/{id}` now returns `Sysctls` as part of the `ContainerSpec`.
|
||||||
* `POST /services/create` now accepts `Sysctls` as part of the `ContainerSpec`.
|
* `POST /services/create` now accepts `Sysctls` as part of the `ContainerSpec`.
|
||||||
* `POST /services/{id}/update` now accepts `Sysctls` as part of the `ContainerSpec`.
|
* `POST /services/{id}/update` now accepts `Sysctls` as part of the `ContainerSpec`.
|
||||||
|
* `POST /services/create` now accepts `Config` as part of `ContainerSpec.Privileges.CredentialSpec`.
|
||||||
|
* `POST /services/{id}/update` now accepts `Config` as part of `ContainerSpec.Privileges.CredentialSpec`.
|
||||||
|
* `POST /services/create` now includes `Runtime` as an option in `ContainerSpec.Configs`
|
||||||
|
* `POST /services/{id}/update` now includes `Runtime` as an option in `ContainerSpec.Configs`
|
||||||
* `GET /tasks` now returns `Sysctls` as part of the `ContainerSpec`.
|
* `GET /tasks` now returns `Sysctls` as part of the `ContainerSpec`.
|
||||||
* `GET /tasks/{id}` now returns `Sysctls` as part of the `ContainerSpec`.
|
* `GET /tasks/{id}` now returns `Sysctls` as part of the `ContainerSpec`.
|
||||||
* `GET /nodes` now supports a filter type `node.label` filter to filter nodes based
|
* `GET /nodes` now supports a filter type `node.label` filter to filter nodes based
|
||||||
|
|
Loading…
Add table
Reference in a new issue