| package convert // import "github.com/docker/docker/daemon/cluster/convert" |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "github.com/docker/docker/api/types/container" |
| mounttypes "github.com/docker/docker/api/types/mount" |
| types "github.com/docker/docker/api/types/swarm" |
| swarmapi "github.com/docker/swarmkit/api" |
| gogotypes "github.com/gogo/protobuf/types" |
| "github.com/pkg/errors" |
| "github.com/sirupsen/logrus" |
| ) |
| |
| func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec { |
| if c == nil { |
| return nil |
| } |
| containerSpec := &types.ContainerSpec{ |
| Image: c.Image, |
| Labels: c.Labels, |
| Command: c.Command, |
| Args: c.Args, |
| Hostname: c.Hostname, |
| Env: c.Env, |
| Dir: c.Dir, |
| User: c.User, |
| Groups: c.Groups, |
| StopSignal: c.StopSignal, |
| TTY: c.TTY, |
| OpenStdin: c.OpenStdin, |
| ReadOnly: c.ReadOnly, |
| Hosts: c.Hosts, |
| Secrets: secretReferencesFromGRPC(c.Secrets), |
| Configs: configReferencesFromGRPC(c.Configs), |
| Isolation: IsolationFromGRPC(c.Isolation), |
| Init: initFromGRPC(c.Init), |
| Sysctls: c.Sysctls, |
| } |
| |
| if c.DNSConfig != nil { |
| containerSpec.DNSConfig = &types.DNSConfig{ |
| Nameservers: c.DNSConfig.Nameservers, |
| Search: c.DNSConfig.Search, |
| Options: c.DNSConfig.Options, |
| } |
| } |
| |
| // Privileges |
| if c.Privileges != nil { |
| containerSpec.Privileges = &types.Privileges{} |
| |
| if c.Privileges.CredentialSpec != nil { |
| containerSpec.Privileges.CredentialSpec = credentialSpecFromGRPC(c.Privileges.CredentialSpec) |
| } |
| |
| if c.Privileges.SELinuxContext != nil { |
| containerSpec.Privileges.SELinuxContext = &types.SELinuxContext{ |
| Disable: c.Privileges.SELinuxContext.Disable, |
| User: c.Privileges.SELinuxContext.User, |
| Type: c.Privileges.SELinuxContext.Type, |
| Role: c.Privileges.SELinuxContext.Role, |
| Level: c.Privileges.SELinuxContext.Level, |
| } |
| } |
| } |
| |
| // Mounts |
| for _, m := range c.Mounts { |
| mount := mounttypes.Mount{ |
| Target: m.Target, |
| Source: m.Source, |
| Type: mounttypes.Type(strings.ToLower(swarmapi.Mount_MountType_name[int32(m.Type)])), |
| ReadOnly: m.ReadOnly, |
| } |
| |
| if m.BindOptions != nil { |
| mount.BindOptions = &mounttypes.BindOptions{ |
| Propagation: mounttypes.Propagation(strings.ToLower(swarmapi.Mount_BindOptions_MountPropagation_name[int32(m.BindOptions.Propagation)])), |
| } |
| } |
| |
| if m.VolumeOptions != nil { |
| mount.VolumeOptions = &mounttypes.VolumeOptions{ |
| NoCopy: m.VolumeOptions.NoCopy, |
| Labels: m.VolumeOptions.Labels, |
| } |
| if m.VolumeOptions.DriverConfig != nil { |
| mount.VolumeOptions.DriverConfig = &mounttypes.Driver{ |
| Name: m.VolumeOptions.DriverConfig.Name, |
| Options: m.VolumeOptions.DriverConfig.Options, |
| } |
| } |
| } |
| |
| if m.TmpfsOptions != nil { |
| mount.TmpfsOptions = &mounttypes.TmpfsOptions{ |
| SizeBytes: m.TmpfsOptions.SizeBytes, |
| Mode: m.TmpfsOptions.Mode, |
| } |
| } |
| containerSpec.Mounts = append(containerSpec.Mounts, mount) |
| } |
| |
| if c.StopGracePeriod != nil { |
| grace, _ := gogotypes.DurationFromProto(c.StopGracePeriod) |
| containerSpec.StopGracePeriod = &grace |
| } |
| |
| if c.Healthcheck != nil { |
| containerSpec.Healthcheck = healthConfigFromGRPC(c.Healthcheck) |
| } |
| |
| return containerSpec |
| } |
| |
| func initFromGRPC(v *gogotypes.BoolValue) *bool { |
| if v == nil { |
| return nil |
| } |
| value := v.GetValue() |
| return &value |
| } |
| |
| func initToGRPC(v *bool) *gogotypes.BoolValue { |
| if v == nil { |
| return nil |
| } |
| return &gogotypes.BoolValue{Value: *v} |
| } |
| |
| func secretReferencesToGRPC(sr []*types.SecretReference) []*swarmapi.SecretReference { |
| refs := make([]*swarmapi.SecretReference, 0, len(sr)) |
| for _, s := range sr { |
| ref := &swarmapi.SecretReference{ |
| SecretID: s.SecretID, |
| SecretName: s.SecretName, |
| } |
| if s.File != nil { |
| ref.Target = &swarmapi.SecretReference_File{ |
| File: &swarmapi.FileTarget{ |
| Name: s.File.Name, |
| UID: s.File.UID, |
| GID: s.File.GID, |
| Mode: s.File.Mode, |
| }, |
| } |
| } |
| |
| refs = append(refs, ref) |
| } |
| |
| return refs |
| } |
| |
| func secretReferencesFromGRPC(sr []*swarmapi.SecretReference) []*types.SecretReference { |
| refs := make([]*types.SecretReference, 0, len(sr)) |
| for _, s := range sr { |
| target := s.GetFile() |
| if target == nil { |
| // not a file target |
| logrus.Warnf("secret target not a file: secret=%s", s.SecretID) |
| continue |
| } |
| refs = append(refs, &types.SecretReference{ |
| File: &types.SecretReferenceFileTarget{ |
| Name: target.Name, |
| UID: target.UID, |
| GID: target.GID, |
| Mode: target.Mode, |
| }, |
| SecretID: s.SecretID, |
| SecretName: s.SecretName, |
| }) |
| } |
| |
| return refs |
| } |
| |
| func configReferencesToGRPC(sr []*types.ConfigReference) ([]*swarmapi.ConfigReference, error) { |
| refs := make([]*swarmapi.ConfigReference, 0, len(sr)) |
| for _, s := range sr { |
| ref := &swarmapi.ConfigReference{ |
| ConfigID: s.ConfigID, |
| ConfigName: s.ConfigName, |
| } |
| 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{ |
| File: &swarmapi.FileTarget{ |
| Name: s.File.Name, |
| UID: s.File.UID, |
| GID: s.File.GID, |
| Mode: s.File.Mode, |
| }, |
| } |
| } |
| |
| refs = append(refs, ref) |
| } |
| |
| return refs, nil |
| } |
| |
| func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigReference { |
| refs := make([]*types.ConfigReference, 0, len(sr)) |
| for _, s := range sr { |
| |
| r := &types.ConfigReference{ |
| ConfigID: s.ConfigID, |
| ConfigName: s.ConfigName, |
| } |
| if target := s.GetRuntime(); target != nil { |
| r.Runtime = &types.ConfigReferenceRuntimeTarget{} |
| } else if target := s.GetFile(); target != nil { |
| r.File = &types.ConfigReferenceFileTarget{ |
| Name: target.Name, |
| UID: target.UID, |
| GID: target.GID, |
| Mode: target.Mode, |
| } |
| } else { |
| // not a file target |
| logrus.Warnf("config target not known: config=%s", s.ConfigID) |
| continue |
| } |
| refs = append(refs, r) |
| } |
| |
| return refs |
| } |
| |
| func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) { |
| containerSpec := &swarmapi.ContainerSpec{ |
| Image: c.Image, |
| Labels: c.Labels, |
| Command: c.Command, |
| Args: c.Args, |
| Hostname: c.Hostname, |
| Env: c.Env, |
| Dir: c.Dir, |
| User: c.User, |
| Groups: c.Groups, |
| StopSignal: c.StopSignal, |
| TTY: c.TTY, |
| OpenStdin: c.OpenStdin, |
| ReadOnly: c.ReadOnly, |
| Hosts: c.Hosts, |
| Secrets: secretReferencesToGRPC(c.Secrets), |
| Isolation: isolationToGRPC(c.Isolation), |
| Init: initToGRPC(c.Init), |
| Sysctls: c.Sysctls, |
| } |
| |
| if c.DNSConfig != nil { |
| containerSpec.DNSConfig = &swarmapi.ContainerSpec_DNSConfig{ |
| Nameservers: c.DNSConfig.Nameservers, |
| Search: c.DNSConfig.Search, |
| Options: c.DNSConfig.Options, |
| } |
| } |
| |
| if c.StopGracePeriod != nil { |
| containerSpec.StopGracePeriod = gogotypes.DurationProto(*c.StopGracePeriod) |
| } |
| |
| // Privileges |
| if c.Privileges != nil { |
| containerSpec.Privileges = &swarmapi.Privileges{} |
| |
| if c.Privileges.CredentialSpec != nil { |
| cs, err := credentialSpecToGRPC(c.Privileges.CredentialSpec) |
| if err != nil { |
| return nil, errors.Wrap(err, "invalid CredentialSpec") |
| } |
| containerSpec.Privileges.CredentialSpec = cs |
| } |
| |
| if c.Privileges.SELinuxContext != nil { |
| containerSpec.Privileges.SELinuxContext = &swarmapi.Privileges_SELinuxContext{ |
| Disable: c.Privileges.SELinuxContext.Disable, |
| User: c.Privileges.SELinuxContext.User, |
| Type: c.Privileges.SELinuxContext.Type, |
| Role: c.Privileges.SELinuxContext.Role, |
| Level: c.Privileges.SELinuxContext.Level, |
| } |
| } |
| } |
| |
| if c.Configs != nil { |
| configs, err := configReferencesToGRPC(c.Configs) |
| if err != nil { |
| return nil, errors.Wrap(err, "invalid Config") |
| } |
| containerSpec.Configs = configs |
| } |
| |
| // Mounts |
| for _, m := range c.Mounts { |
| mount := swarmapi.Mount{ |
| Target: m.Target, |
| Source: m.Source, |
| ReadOnly: m.ReadOnly, |
| } |
| |
| if mountType, ok := swarmapi.Mount_MountType_value[strings.ToUpper(string(m.Type))]; ok { |
| mount.Type = swarmapi.Mount_MountType(mountType) |
| } else if string(m.Type) != "" { |
| return nil, fmt.Errorf("invalid MountType: %q", m.Type) |
| } |
| |
| if m.BindOptions != nil { |
| if mountPropagation, ok := swarmapi.Mount_BindOptions_MountPropagation_value[strings.ToUpper(string(m.BindOptions.Propagation))]; ok { |
| mount.BindOptions = &swarmapi.Mount_BindOptions{Propagation: swarmapi.Mount_BindOptions_MountPropagation(mountPropagation)} |
| } else if string(m.BindOptions.Propagation) != "" { |
| return nil, fmt.Errorf("invalid MountPropagation: %q", m.BindOptions.Propagation) |
| } |
| |
| if m.BindOptions.NonRecursive { |
| // TODO(AkihiroSuda): NonRecursive is unsupported for Swarm-mode now because of mutual vendoring |
| // across moby and swarmkit. Will be available soon after the moby PR gets merged. |
| return nil, fmt.Errorf("invalid NonRecursive: %q", m.BindOptions.Propagation) |
| } |
| } |
| |
| if m.VolumeOptions != nil { |
| mount.VolumeOptions = &swarmapi.Mount_VolumeOptions{ |
| NoCopy: m.VolumeOptions.NoCopy, |
| Labels: m.VolumeOptions.Labels, |
| } |
| if m.VolumeOptions.DriverConfig != nil { |
| mount.VolumeOptions.DriverConfig = &swarmapi.Driver{ |
| Name: m.VolumeOptions.DriverConfig.Name, |
| Options: m.VolumeOptions.DriverConfig.Options, |
| } |
| } |
| } |
| |
| if m.TmpfsOptions != nil { |
| mount.TmpfsOptions = &swarmapi.Mount_TmpfsOptions{ |
| SizeBytes: m.TmpfsOptions.SizeBytes, |
| Mode: m.TmpfsOptions.Mode, |
| } |
| } |
| |
| containerSpec.Mounts = append(containerSpec.Mounts, mount) |
| } |
| |
| if c.Healthcheck != nil { |
| containerSpec.Healthcheck = healthConfigToGRPC(c.Healthcheck) |
| } |
| |
| 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 { |
| interval, _ := gogotypes.DurationFromProto(h.Interval) |
| timeout, _ := gogotypes.DurationFromProto(h.Timeout) |
| startPeriod, _ := gogotypes.DurationFromProto(h.StartPeriod) |
| return &container.HealthConfig{ |
| Test: h.Test, |
| Interval: interval, |
| Timeout: timeout, |
| Retries: int(h.Retries), |
| StartPeriod: startPeriod, |
| } |
| } |
| |
| func healthConfigToGRPC(h *container.HealthConfig) *swarmapi.HealthConfig { |
| return &swarmapi.HealthConfig{ |
| Test: h.Test, |
| Interval: gogotypes.DurationProto(h.Interval), |
| Timeout: gogotypes.DurationProto(h.Timeout), |
| Retries: int32(h.Retries), |
| StartPeriod: gogotypes.DurationProto(h.StartPeriod), |
| } |
| } |
| |
| // IsolationFromGRPC converts a swarm api container isolation to a moby isolation representation |
| func IsolationFromGRPC(i swarmapi.ContainerSpec_Isolation) container.Isolation { |
| switch i { |
| case swarmapi.ContainerIsolationHyperV: |
| return container.IsolationHyperV |
| case swarmapi.ContainerIsolationProcess: |
| return container.IsolationProcess |
| case swarmapi.ContainerIsolationDefault: |
| return container.IsolationDefault |
| } |
| return container.IsolationEmpty |
| } |
| |
| func isolationToGRPC(i container.Isolation) swarmapi.ContainerSpec_Isolation { |
| if i.IsHyperV() { |
| return swarmapi.ContainerIsolationHyperV |
| } |
| if i.IsProcess() { |
| return swarmapi.ContainerIsolationProcess |
| } |
| return swarmapi.ContainerIsolationDefault |
| } |