diff --git a/pkg/core/.DS_Store b/pkg/core/.DS_Store deleted file mode 100644 index 0824785..0000000 Binary files a/pkg/core/.DS_Store and /dev/null differ diff --git a/pkg/core/pod/.DS_Store b/pkg/core/pod/.DS_Store deleted file mode 100644 index 9c02194..0000000 Binary files a/pkg/core/pod/.DS_Store and /dev/null differ diff --git a/pkg/core/pod/affinity/to_kube.go b/pkg/core/pod/affinity/to_kube.go index e171527..70d6981 100644 --- a/pkg/core/pod/affinity/to_kube.go +++ b/pkg/core/pod/affinity/to_kube.go @@ -22,25 +22,19 @@ func (a *Affinity) ToKube(version string) (interface{}, error) { } func (a *Affinity) toKubeV1() (*v1.Affinity, error) { - var nodeAffinity *v1.NodeAffinity - var podAffinity *v1.PodAffinity - var podAntiAffinity *v1.PodAntiAffinity - - if a != nil { - nodeAffinity = a.toKubeNodeAffinityV1(a.NodeAffinity) - podAffinity = a.toKubePodAffinityV1(a.PodAffinity) - podAntiAffinity = a.toKubePodAntiAffinityV1(a.PodAntiAffinity) + if a == nil { + return nil, nil } - if podAffinity != nil || podAntiAffinity != nil || nodeAffinity != nil { - return &v1.Affinity{ - NodeAffinity: nodeAffinity, - PodAffinity: podAffinity, - PodAntiAffinity: podAntiAffinity, - }, nil - } + nodeAffinity := a.toKubeNodeAffinityV1(a.NodeAffinity) + podAffinity := a.toKubePodAffinityV1(a.PodAffinity) + podAntiAffinity := a.toKubePodAntiAffinityV1(a.PodAntiAffinity) - return nil, nil + return &v1.Affinity{ + NodeAffinity: nodeAffinity, + PodAffinity: podAffinity, + PodAntiAffinity: podAntiAffinity, + }, nil } func (a *Affinity) toKubePodAffinityV1(affinities map[AffinityType][]PodTerm) *v1.PodAffinity { diff --git a/pkg/core/pod/container/container.go b/pkg/core/pod/container/container.go index 8b95696..a832350 100644 --- a/pkg/core/pod/container/container.go +++ b/pkg/core/pod/container/container.go @@ -1,11 +1,14 @@ package container import ( + "fmt" + "mantle/pkg/core/action" "mantle/pkg/core/pod/container/env" "mantle/pkg/core/pod/container/port" "mantle/pkg/core/pod/container/probe" "mantle/pkg/core/pod/container/resources" + "mantle/pkg/core/pod/container/volumedevice" "mantle/pkg/core/pod/container/volumemount" "mantle/pkg/core/selinux" "mantle/pkg/util/floatstr" @@ -14,42 +17,45 @@ import ( ) type Container struct { - Command []string `json:"command,omitempty"` - Args []floatstr.FloatOrString `json:"args,omitempty"` - Env []env.Env `json:"env,omitempty"` - Image string `json:"image"` - Pull PullPolicy `json:"pull,omitempty"` - OnStart *action.Action `json:"on_start,omitempty"` - PreStop *action.Action `json:"pre_stop,omitempty"` - CPU *resources.CPU `json:"cpu,omitempty"` - Mem *resources.Mem `json:"mem,omitempty"` - Name string `json:"name,omitempty"` - AddCapabilities []string `json:"cap_add,omitempty"` - DelCapabilities []string `json:"cap_drop,omitempty"` - Privileged *bool `json:"privileged,omitempty"` - AllowEscalation *bool `json:"allow_escalation,omitempty"` - RW *bool `json:"rw,omitempty"` - RO *bool `json:"ro,omitempty"` - ForceNonRoot *bool `json:"force_non_root,omitempty"` - UID *int64 `json:"uid,omitempty"` - GID *int64 `json:"gid,omitempty"` - SELinux *selinux.SELinux `json:"selinux,omitempty"` - LivenessProbe *probe.Probe `json:"liveness_probe,omitempty"` - ReadinessProbe *probe.Probe `json:"readiness_probe,omitempty"` - Expose []port.Port `json:"expose,omitempty"` - Stdin bool `json:"stdin,omitempty"` - StdinOnce bool `json:"stdin_once,omitempty"` - TTY bool `json:"tty,omitempty"` - WorkingDir string `json:"wd,omitempty"` - TerminationMsgPath string `json:"termination_msg_path,omitempty"` - TerminationMsgPolicy TerminationMessagePolicy `json:"termination_msg_policy,omitempty"` - ContainerID string `json:"container_id,omitempty"` - ImageID string `json:"image_id,omitempty"` - Ready bool `json:"ready,omitempty"` - LastState *ContainerState `json:"last_state,omitempty"` - CurrentState *ContainerState `json:"current_state,omitempty"` - VolumeMounts []volumemount.VolumeMount `json:"volume,omitempty"` - Restarts int32 `json:"restarts,omitempty"` + Command []string `json:"command,omitempty"` + Args []floatstr.FloatOrString `json:"args,omitempty"` + Env []env.Env `json:"env,omitempty"` + Image string `json:"image"` + Pull PullPolicy `json:"pull,omitempty"` + OnStart *action.Action `json:"on_start,omitempty"` + PreStop *action.Action `json:"pre_stop,omitempty"` + CPU *resources.CPU `json:"cpu,omitempty"` + Mem *resources.Mem `json:"mem,omitempty"` + Name string `json:"name,omitempty"` + AddCapabilities []string `json:"cap_add,omitempty"` + DelCapabilities []string `json:"cap_drop,omitempty"` + Privileged *bool `json:"privileged,omitempty"` + AllowEscalation *bool `json:"allow_escalation,omitempty"` + RO *bool `json:"ro,omitempty"` + ForceNonRoot *bool `json:"force_non_root,omitempty"` + UID *int64 `json:"uid,omitempty"` + GID *int64 `json:"gid,omitempty"` + SELinux *selinux.SELinux `json:"selinux,omitempty"` + LivenessProbe *probe.Probe `json:"liveness_probe,omitempty"` + ReadinessProbe *probe.Probe `json:"readiness_probe,omitempty"` + Expose []port.Port `json:"expose,omitempty"` + Stdin bool `json:"stdin,omitempty"` + StdinOnce bool `json:"stdin_once,omitempty"` + TTY bool `json:"tty,omitempty"` + ProcMount *MountType `json:"procMount,omitempty"` + WorkingDir string `json:"wd,omitempty"` + TerminationMsgPath string `json:"termination_msg_path,omitempty"` + TerminationMsgPolicy TerminationMessagePolicy `json:"termination_msg_policy,omitempty"` + VolumeMounts []volumemount.VolumeMount `json:"volume,omitempty"` + VolumeDevices []volumedevice.VolumeDevice `json:"device,omitempty ` + + // Status + ContainerID string `json:"container_id,omitempty"` + ImageID string `json:"image_id,omitempty"` + Ready bool `json:"ready,omitempty"` + LastState *ContainerState `json:"last_state,omitempty"` + CurrentState *ContainerState `json:"current_state,omitempty"` + Restarts int32 `json:"restarts,omitempty"` } type ContainerState struct { @@ -92,3 +98,24 @@ const ( PullIfNotPresent PullDefault ) + +type MountType int + +const ( + MountTypeDefault MountType = iota + MountTypeUnmasked + MountTypeInvalid +) + +func (m *MountType) ToString() string { + switch *m { + case MountTypeDefault: + return "MountTypeDefault" + case MountTypeUnmasked: + return "MountTypeUnmasked" + case MountTypeInvalid: + return "MountTypeInvalid" + default: + return fmt.Sprintf("unknown mount type: %d", *m) + } +} diff --git a/pkg/core/pod/container/container_test.go b/pkg/core/pod/container/container_test.go new file mode 100644 index 0000000..9931582 --- /dev/null +++ b/pkg/core/pod/container/container_test.go @@ -0,0 +1,178 @@ +package container + +import ( + "mantle/pkg/core/action" + "mantle/pkg/core/pod/container/env" + "mantle/pkg/core/pod/container/port" + "mantle/pkg/core/pod/container/probe" + "mantle/pkg/core/pod/container/resources" + "mantle/pkg/core/pod/container/volumedevice" + "mantle/pkg/core/pod/container/volumemount" + "mantle/pkg/core/protocol" + "mantle/pkg/core/selinux" + "mantle/pkg/util/floatstr" +) + +var int64Val = int64(5) +var boolEntry = true +var int32Val = int32(1) +var strEntry = "testStr" +var mountType = MountTypeDefault +var mountPropagation = volumemount.MountPropagationNone +var FullTestMantleContainer = Container{ + Command: []string{"cmd1", "cmd2"}, + Args: []floatstr.FloatOrString{ + { + Type: floatstr.Float, + FloatVal: 3.1459267, + }, + { + Type: floatstr.String, + StringVal: "strVal", + }, + }, + Env: []env.Env{ + { + Type: env.EnvValEnvType, + Val: &env.EnvVal{ + Key: "key1", + Val: "val1", + }, + }, + { + Type: env.EnvFromEnvType, + From: &env.EnvFrom{ + From: env.EnvFromTypeSecret, + VarNameOrPrefix: "EnvName", + ConfigMapOrSecretName: "SecretName", + ConfigMapOrSecretKey: "SecretKey", + Required: &boolEntry, + }, + }, + }, + Image: "registry.io/path/to/image", + Pull: PullAlways, + OnStart: &action.Action{ + ActionType: action.ActionTypeTCP, + Host: "actionHost", + Port: "22", + }, + PreStop: &action.Action{ + ActionType: action.ActionTypeHTTPS, + Path: "/action/path", + Host: "actionHost", + Port: "443", + Headers: []string{"Key:Value", "HEADER:VALUE"}, + }, + CPU: &resources.CPU{ + Min: "1", + Max: "3", + }, + Mem: &resources.Mem{ + Min: "10Gi", + Max: "10Ti", + }, + Name: "container-name", + AddCapabilities: []string{"CAP1", "CAP2"}, + DelCapabilities: []string{"CAP3"}, + Privileged: &boolEntry, + AllowEscalation: &boolEntry, + RO: &boolEntry, + ForceNonRoot: &boolEntry, + UID: &int64Val, + GID: &int64Val, + SELinux: &selinux.SELinux{ + Level: "selinuxlevel", + Role: "selinuxrole", + Type: "selinuxtype", + User: "selinuxuser", + }, + LivenessProbe: &probe.Probe{ + Action: action.Action{ + ActionType: action.ActionTypeCommand, + Command: []string{"cmd1", "cmd2"}, + }, + Delay: int32Val, + Interval: int32Val, + MinCountSuccess: int32Val, + MinCountFailure: int32Val, + Timeout: int32Val, + }, + ReadinessProbe: &probe.Probe{ + Action: action.Action{ + ActionType: action.ActionTypeCommand, + Command: []string{"cmd3", "cmd4"}, + }, + Delay: int32Val, + Interval: int32Val, + MinCountSuccess: int32Val, + MinCountFailure: int32Val, + Timeout: int32Val, + }, + Expose: []port.Port{ + { + Name: "port1", + Protocol: protocol.ProtocolTCP, + IP: "1.1.1.1", + HostPort: "1000", + ContainerPort: "1000", + }, + }, + Stdin: boolEntry, + StdinOnce: boolEntry, + TTY: boolEntry, + ProcMount: &mountType, + WorkingDir: "/path/to/dir", + TerminationMsgPath: "/msg/path", + TerminationMsgPolicy: TerminationMessageReadFile, + VolumeMounts: []volumemount.VolumeMount{ + { + MountPath: "/path/to/mount", + Propagation: &mountPropagation, + Store: "storeName", + ReadOnly: false, + }, + }, + VolumeDevices: []volumedevice.VolumeDevice{ + { + Name: "deviceName", + Path: "/path/to/device", + }, + }, +} + +var EmptyTestMantleContainer = Container{ + Command: []string{}, + Args: []floatstr.FloatOrString{}, + Env: []env.Env{}, + Image: "", + Pull: PullDefault, + OnStart: &action.Action{}, + PreStop: &action.Action{}, + CPU: &resources.CPU{}, + Mem: &resources.Mem{}, + Name: "", + AddCapabilities: []string{}, + DelCapabilities: []string{}, + Privileged: nil, + AllowEscalation: nil, + RO: nil, + ForceNonRoot: nil, + UID: nil, + GID: nil, + SELinux: &selinux.SELinux{}, + LivenessProbe: &probe.Probe{ + Action: action.Action{}, + }, + ReadinessProbe: &probe.Probe{}, + Expose: []port.Port{}, + Stdin: false, + StdinOnce: false, + TTY: false, + ProcMount: nil, + WorkingDir: "", + TerminationMsgPath: "", + TerminationMsgPolicy: TerminationMessageDefault, + VolumeMounts: []volumemount.VolumeMount{}, + VolumeDevices: []volumedevice.VolumeDevice{}, +} diff --git a/pkg/core/pod/container/from_kube.go b/pkg/core/pod/container/from_kube.go index bc33d18..9b5318b 100644 --- a/pkg/core/pod/container/from_kube.go +++ b/pkg/core/pod/container/from_kube.go @@ -9,14 +9,14 @@ import ( "mantle/pkg/core/pod/container/port" "mantle/pkg/core/pod/container/probe" "mantle/pkg/core/pod/container/resources" + "mantle/pkg/core/pod/container/volumedevice" "mantle/pkg/core/pod/container/volumemount" "mantle/pkg/core/selinux" - "mantle/pkg/util" "mantle/pkg/util/floatstr" "k8s.io/api/core/v1" - "github.com/imdario/mergo" + serrors "github.com/koki/structurederrors" ) // NewContainerFromKubeContainer will create a new Container object with @@ -45,60 +45,63 @@ func fromKubeContainerV1(container *v1.Container) (*Container, error) { onStart, preStop, err := fromKubeLifeCycleV1(container.Lifecycle) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "life cycle") } mantleContainer.OnStart = onStart mantleContainer.PreStop = preStop cpu, err := resources.NewCPUFromKubeResourceRequirements(container.Resources) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "cpu resource requirements") } mantleContainer.CPU = cpu mem, err := resources.NewMemFromKubeResourceRequirements(container.Resources) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "mem resource requirements") } mantleContainer.Mem = mem if container.SecurityContext != nil { mantleContainer.Privileged = container.SecurityContext.Privileged mantleContainer.AllowEscalation = container.SecurityContext.AllowPrivilegeEscalation - - if container.SecurityContext.ReadOnlyRootFilesystem != nil { - mantleContainer.RO = container.SecurityContext.ReadOnlyRootFilesystem - mantleContainer.RW = util.BoolPtrOrNil(!(*mantleContainer.RO)) - } - + mantleContainer.RO = container.SecurityContext.ReadOnlyRootFilesystem mantleContainer.ForceNonRoot = container.SecurityContext.RunAsNonRoot mantleContainer.UID = container.SecurityContext.RunAsUser mantleContainer.GID = container.SecurityContext.RunAsGroup sel, err := selinux.NewSELinuxFromKubeSELinuxOptions(container.SecurityContext.SELinuxOptions) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "selinux options") } mantleContainer.SELinux = sel mantleContainer.AddCapabilities, mantleContainer.DelCapabilities = fromKubeCapabilitiesV1(container.SecurityContext.Capabilities) + + if container.SecurityContext.ProcMount != nil { + procMount, err := fromKubeProcMountV1(container.SecurityContext.ProcMount) + if err != nil { + return nil, serrors.ContextualizeErrorf(err, "proc mount") + } + mantleContainer.ProcMount = &procMount + } } livenessProbe, err := probe.NewProbeFromKubeProbe(container.LivenessProbe) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "liveness probe") } mantleContainer.LivenessProbe = livenessProbe readinessProbe, err := probe.NewProbeFromKubeProbe(container.ReadinessProbe) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "readiness probe") } mantleContainer.ReadinessProbe = readinessProbe ports, err := fromKubeContainerPortsV1(container.Ports) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "container ports") } mantleContainer.Expose = ports @@ -110,27 +113,29 @@ func fromKubeContainerV1(container *v1.Container) (*Container, error) { envs, err := fromKubeEnvVarsV1(container.Env) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "env var") } envFroms, err := fromKubeEnvFromSourceV1(container.EnvFrom) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "env from source") } - err = mergo.Merge(&envs, envFroms) - if err != nil { - return nil, err - } + envs = append(envs, envFroms...) mantleContainer.Env = envs volumeMounts, err := fromKubeVolumeMountsV1(container.VolumeMounts) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "volume mount") } - mantleContainer.VolumeMounts = volumeMounts + volumeDevices, err := fromKubeVolumeDevicesV1(container.VolumeDevices) + if err != nil { + return nil, serrors.ContextualizeErrorf(err, "volume device") + } + mantleContainer.VolumeDevices = volumeDevices + return mantleContainer, nil } @@ -274,3 +279,28 @@ func fromKubeVolumeMountsV1(kubeMounts []v1.VolumeMount) ([]volumemount.VolumeMo return mounts, nil } + +func fromKubeVolumeDevicesV1(kubeDevices []v1.VolumeDevice) ([]volumedevice.VolumeDevice, error) { + devices := []volumedevice.VolumeDevice{} + + for _, device := range kubeDevices { + d, err := volumedevice.NewVolumeDeviceFromKubeVolumeDevice(device) + if err != nil { + return nil, err + } + devices = append(devices, *d) + } + + return devices, nil +} + +func fromKubeProcMountV1(procMount *v1.ProcMountType) (MountType, error) { + switch *procMount { + case v1.DefaultProcMount: + return MountTypeDefault, nil + case v1.UnmaskedProcMount: + return MountTypeUnmasked, nil + default: + return MountTypeInvalid, fmt.Errorf("unknown ProcMountType: %s", *procMount) + } +} diff --git a/pkg/core/pod/container/to_kube.go b/pkg/core/pod/container/to_kube.go index 8472b54..64708f4 100644 --- a/pkg/core/pod/container/to_kube.go +++ b/pkg/core/pod/container/to_kube.go @@ -5,10 +5,6 @@ import ( "reflect" "strings" - "mantle/pkg/util" - - serrors "github.com/koki/structurederrors" - "k8s.io/api/core/v1" "github.com/imdario/mergo" @@ -81,6 +77,12 @@ func (c *Container) toKubeV1() (v1.Container, error) { } kubeContainer.VolumeMounts = vm + vd, err := c.toKubeVolumeDeviceV1() + if err != nil { + return v1.Container{}, err + } + kubeContainer.VolumeDevices = vd + kubeContainer.Stdin = c.Stdin kubeContainer.StdinOnce = c.StdinOnce kubeContainer.TTY = c.TTY @@ -246,6 +248,21 @@ func (c *Container) toKubeVolumeMountV1() ([]v1.VolumeMount, error) { return kubeMounts, nil } +func (c *Container) toKubeVolumeDeviceV1() ([]v1.VolumeDevice, error) { + var kubeDevices []v1.VolumeDevice + + for _, device := range c.VolumeDevices { + d, err := device.ToKube("v1") + if err != nil { + return nil, err + } + deviceV1 := d.(*v1.VolumeDevice) + kubeDevices = append(kubeDevices, *deviceV1) + } + + return kubeDevices, nil +} + func (c *Container) toKubeLifecycleV1() (*v1.Lifecycle, error) { var lc *v1.Lifecycle var kubeOnStart *v1.Handler @@ -292,15 +309,8 @@ func (c *Container) toKubeSecurityContextV1() (*v1.SecurityContext, error) { mark = true } - if c.RO != nil || c.RW != nil { - ro := util.FromBoolPtr(c.RO) - rw := util.FromBoolPtr(c.RW) - - if !((!ro && rw) || (!rw && ro)) { - return nil, serrors.InvalidInstanceErrorf(c, "conflicting value (Read Only) %v and (ReadWrite) %v", ro, rw) - } - - sc.ReadOnlyRootFilesystem = &ro + if c.RO != nil { + sc.ReadOnlyRootFilesystem = c.RO mark = true } @@ -347,6 +357,15 @@ func (c *Container) toKubeSecurityContextV1() (*v1.SecurityContext, error) { mark = true } + if c.ProcMount != nil { + procMount, err := c.toKubeProcMountV1() + if err != nil { + return nil, err + } + sc.ProcMount = &procMount + mark = true + } + if !mark { return nil, nil } @@ -354,6 +373,17 @@ func (c *Container) toKubeSecurityContextV1() (*v1.SecurityContext, error) { return sc, nil } +func (c *Container) toKubeProcMountV1() (v1.ProcMountType, error) { + switch *c.ProcMount { + case MountTypeDefault: + return v1.DefaultProcMount, nil + case MountTypeUnmasked: + return v1.UnmaskedProcMount, nil + default: + return "", fmt.Errorf("unknown MountType: %s", c.ProcMount.ToString()) + } +} + // ToKube will return a kubernetes container status object of the api version provided func (c *Container) ToKubeStatus(version string) (interface{}, error) { switch strings.ToLower(version) { diff --git a/pkg/core/pod/container/volumedevice/from_kube.go b/pkg/core/pod/container/volumedevice/from_kube.go new file mode 100644 index 0000000..1e49ff5 --- /dev/null +++ b/pkg/core/pod/container/volumedevice/from_kube.go @@ -0,0 +1,30 @@ +package volumedevice + +import ( + "fmt" + "reflect" + + "k8s.io/api/core/v1" +) + +// NewVolumeDeviceFromKubeVolumeDevice will create a new +// VolumeDevice object with the data from a provided kubernetes +// VolumeDevice object +func NewVolumeDeviceFromKubeVolumeDevice(obj interface{}) (*VolumeDevice, error) { + switch reflect.TypeOf(obj) { + case reflect.TypeOf(v1.VolumeDevice{}): + return fromKubeVolumeDeviceV1(obj.(v1.VolumeDevice)) + case reflect.TypeOf(&v1.VolumeDevice{}): + o := obj.(*v1.VolumeDevice) + return fromKubeVolumeDeviceV1(*o) + default: + return nil, fmt.Errorf("unknown VolumeDevice version: %s", reflect.TypeOf(obj)) + } +} + +func fromKubeVolumeDeviceV1(device v1.VolumeDevice) (*VolumeDevice, error) { + return &VolumeDevice{ + Name: device.Name, + Path: device.DevicePath, + }, nil +} diff --git a/pkg/core/pod/container/volumedevice/to_kube.go b/pkg/core/pod/container/volumedevice/to_kube.go new file mode 100644 index 0000000..69039c3 --- /dev/null +++ b/pkg/core/pod/container/volumedevice/to_kube.go @@ -0,0 +1,27 @@ +package volumedevice + +import ( + "fmt" + "strings" + + "k8s.io/api/core/v1" +) + +// ToKube will return a kubernetes VolumeDevice object of the api version provided +func (vd *VolumeDevice) ToKube(version string) (interface{}, error) { + switch strings.ToLower(version) { + case "v1": + return vd.toKubeV1() + case "": + return vd.toKubeV1() + default: + return nil, fmt.Errorf("unsupported api version for VolumeDevice: %s", version) + } +} + +func (vd *VolumeDevice) toKubeV1() (*v1.VolumeDevice, error) { + return &v1.VolumeDevice{ + Name: vd.Name, + DevicePath: vd.Path, + }, nil +} diff --git a/pkg/core/pod/container/volumedevice/volume_device.go b/pkg/core/pod/container/volumedevice/volume_device.go new file mode 100644 index 0000000..09bdbdb --- /dev/null +++ b/pkg/core/pod/container/volumedevice/volume_device.go @@ -0,0 +1,7 @@ +package volumedevice + +// VolumeDevice defines a volume device for a container +type VolumeDevice struct { + Name string `json:"name,omitempty"` + Path string `json:"path,omitempty"` +} diff --git a/pkg/core/pod/from_kube.go b/pkg/core/pod/from_kube.go index 5f65be1..623db69 100644 --- a/pkg/core/pod/from_kube.go +++ b/pkg/core/pod/from_kube.go @@ -41,13 +41,13 @@ func fromKubePodV1(pod *v1.Pod) (*Pod, error) { templateMeta, err := NewPodTemplateMetaFromKubeObjectMeta(pod.ObjectMeta) if err != nil { - return nil, err + return nil, fmt.Errorf("error converting PodTemplateMeta: %s", err) } mantlePod.PodTemplateMeta = *templateMeta template, err := NewPodTemplateFromKubePodSpec(pod.Spec) if err != nil { - return nil, err + return nil, fmt.Errorf("error converting PodTemplate: %s", err) } mantlePod.PodTemplate = *template @@ -55,7 +55,7 @@ func fromKubePodV1(pod *v1.Pod) (*Pod, error) { mantlePod.Reason = pod.Status.Reason phase, err := fromKubePodPhaseV1(pod.Status.Phase) if err != nil { - return nil, err + return nil, fmt.Errorf("error converting PodPhase: %s", err) } mantlePod.Phase = phase mantlePod.IP = pod.Status.PodIP @@ -64,17 +64,18 @@ func fromKubePodV1(pod *v1.Pod) (*Pod, error) { qosClass, err := fromKubePodQOSClassV1(pod.Status.QOSClass) if err != nil { - return nil, err + return nil, fmt.Errorf("error converting QOSClass: %s", err) } mantlePod.QOS = qosClass conditions, err := fromKubePodConditionV1(pod.Status.Conditions) if err != nil { - return nil, err + return nil, fmt.Errorf("error converting PodCondition: %s", err) } mantlePod.Conditions = conditions - fromKubeContainerStatusV1(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses, mantlePod.Containers) + fromKubeContainerStatusV1(pod.Status.InitContainerStatuses, mantlePod.InitContainers) + fromKubeContainerStatusV1(pod.Status.ContainerStatuses, mantlePod.Containers) return mantlePod, nil } @@ -137,13 +138,13 @@ func fromKubePodConditionV1(kubeConditions []v1.PodCondition) ([]PodCondition, e typ, err := FromKubePodConditionTypeV1(kubeCondition.Type) if err != nil { - return nil, err + return nil, fmt.Errorf("error converting PodConditionType: %s", err) } condition.Type = typ status, err := fromKubeConditionStatusV1(kubeCondition.Status) if err != nil { - return nil, err + return nil, fmt.Errorf("error converting ConditionStatus: %s", err) } condition.Status = status @@ -172,10 +173,8 @@ func fromKubeConditionStatusV1(status v1.ConditionStatus) (ConditionStatus, erro } } -func fromKubeContainerStatusV1(initContainerStatuses, containerStatuses []v1.ContainerStatus, containers []Container) { - allContainerStatuses := append(initContainerStatuses, containerStatuses...) - - for _, status := range allContainerStatuses { +func fromKubeContainerStatusV1(containerStatuses []v1.ContainerStatus, containers []Container) { + for _, status := range containerStatuses { for _, container := range containers { if container.Name == status.Name { container.Restarts = status.RestartCount diff --git a/pkg/core/pod/pod.go b/pkg/core/pod/pod.go index 5569ef4..5c613b5 100644 --- a/pkg/core/pod/pod.go +++ b/pkg/core/pod/pod.go @@ -49,14 +49,15 @@ const ( type Pod struct { Version string `json:"version,omitempty"` - Conditions []PodCondition `json:"condition,omitempty"` - NodeIP string `json:"node_ip,omitempty"` - StartTime *metav1.Time `json:"start_time,omitempty"` - Msg string `json:"msg,omitempty"` - Phase PodPhase `json:"phase,omitempty"` - IP string `json:"ip,omitempty"` - QOS PodQOSClass `json:"qos,omitempty"` - Reason string `json:"reason,omitempty"` + Conditions []PodCondition `json:"condition,omitempty"` + NodeIP string `json:"node_ip,omitempty"` + StartTime *metav1.Time `json:"start_time,omitempty"` + Msg string `json:"msg,omitempty"` + Phase PodPhase `json:"phase,omitempty"` + IP string `json:"ip,omitempty"` + QOS PodQOSClass `json:"qos,omitempty"` + Reason string `json:"reason,omitempty"` + NominatedName string `json:"nominatedName,omitempty"` PodTemplateMeta `json:",inline"` PodTemplate `json:",inline"` diff --git a/pkg/core/pod/pod_test.go b/pkg/core/pod/pod_test.go new file mode 100644 index 0000000..01fb868 --- /dev/null +++ b/pkg/core/pod/pod_test.go @@ -0,0 +1,718 @@ +package pod + +import ( + "reflect" + "testing" + + "mantle/pkg/core/action" + "mantle/pkg/core/pod/affinity" + "mantle/pkg/core/pod/container" + "mantle/pkg/core/pod/container/env" + "mantle/pkg/core/pod/container/port" + "mantle/pkg/core/pod/container/probe" + "mantle/pkg/core/pod/container/resources" + "mantle/pkg/core/pod/container/volumedevice" + "mantle/pkg/core/pod/container/volumemount" + "mantle/pkg/core/pod/hostalias" + "mantle/pkg/core/pod/podtemplate" + "mantle/pkg/core/pod/toleration" + "mantle/pkg/core/pod/volume" + "mantle/pkg/core/protocol" + "mantle/pkg/core/selinux" + "mantle/pkg/util/floatstr" + + "k8s.io/api/core/v1" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + intstr "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/google/go-cmp/cmp" +) + +var int64Val = int64(5) +var boolEntry = true +var inverseBoolEntry = !boolEntry +var int32Val = int32(1) +var strEntry = "testStr" +var strArray = []string{strEntry} +var propagation = volumemount.MountPropagationNone +var kubePropagation = v1.MountPropagationNone +var procMount = container.MountTypeDefault +var kubeProcMount = v1.DefaultProcMount +var cpuMin = "1" +var cpuMax = "3" +var memMin = "10Gi" +var memMax = "10Ti" +var cpuLimit, _ = resource.ParseQuantity(cpuMax) +var cpuRequest, _ = resource.ParseQuantity(cpuMin) +var memLimit, _ = resource.ParseQuantity(memMax) +var memRequest, _ = resource.ParseQuantity(memMin) +var fullMantleContainer = container.Container{ + Command: []string{"cmd1", "cmd2"}, + Args: []floatstr.FloatOrString{ + { + Type: floatstr.Float, + FloatVal: 3.1459267, + }, + { + Type: floatstr.String, + StringVal: "strVal", + }, + }, + Env: []env.Env{ + { + Type: env.EnvValEnvType, + Val: &env.EnvVal{ + Key: "key1", + Val: "val1", + }, + }, + { + Type: env.EnvFromEnvType, + From: &env.EnvFrom{ + From: env.EnvFromTypeSecret, + VarNameOrPrefix: "SecretEnvName", + ConfigMapOrSecretName: "SecretName", + ConfigMapOrSecretKey: "SecretKey", + Required: &boolEntry, + }, + }, + { + Type: env.EnvFromEnvType, + From: &env.EnvFrom{ + From: env.EnvFromTypeConfig, + VarNameOrPrefix: "ConfigEnvName", + ConfigMapOrSecretName: "ConfigName", + Required: &boolEntry, + }, + }, + }, + Image: "registry.io/path/to/image", + Pull: container.PullAlways, + OnStart: &action.Action{ + ActionType: action.ActionTypeTCP, + Host: "actionHost", + Port: "22", + }, + PreStop: &action.Action{ + ActionType: action.ActionTypeHTTPS, + Path: "/action/path", + Host: "actionHost", + Port: "443", + Headers: []string{"Key:Value", "HEADER:VALUE"}, + }, + CPU: &resources.CPU{ + Min: cpuMin, + Max: cpuMax, + }, + Mem: &resources.Mem{ + Min: memMin, + Max: memMax, + }, + Name: "container-name", + AddCapabilities: []string{"CAP1", "CAP2"}, + DelCapabilities: []string{"CAP3"}, + Privileged: &boolEntry, + AllowEscalation: &boolEntry, + RO: &boolEntry, + ForceNonRoot: &boolEntry, + UID: &int64Val, + GID: &int64Val, + SELinux: &selinux.SELinux{ + Level: "selinuxlevel", + Role: "selinuxrole", + Type: "selinuxtype", + User: "selinuxuser", + }, + LivenessProbe: &probe.Probe{ + Action: action.Action{ + ActionType: action.ActionTypeCommand, + Command: []string{"cmd1", "cmd2"}, + }, + Delay: int32Val, + Interval: int32Val, + MinCountSuccess: int32Val, + MinCountFailure: int32Val, + Timeout: int32Val, + }, + ReadinessProbe: &probe.Probe{ + Action: action.Action{ + ActionType: action.ActionTypeCommand, + Command: []string{"cmd3", "cmd4"}, + }, + Delay: int32Val, + Interval: int32Val, + MinCountSuccess: int32Val, + MinCountFailure: int32Val, + Timeout: int32Val, + }, + Expose: []port.Port{ + { + Name: "port1", + Protocol: protocol.ProtocolTCP, + IP: "1.1.1.1", + HostPort: "1000", + ContainerPort: "1000", + }, + }, + Stdin: boolEntry, + StdinOnce: boolEntry, + TTY: boolEntry, + ProcMount: &procMount, + WorkingDir: "/path/to/dir", + TerminationMsgPath: "/msg/path", + TerminationMsgPolicy: container.TerminationMessageReadFile, + VolumeMounts: []volumemount.VolumeMount{ + { + MountPath: "/path/to/mount", + Propagation: &propagation, + Store: "volumeName:/path/to/volume", + ReadOnly: false, + }, + }, + VolumeDevices: []volumedevice.VolumeDevice{ + { + Name: "deviceName", + Path: "/path/to/device", + }, + }, +} + +var fullKubeContainer = v1.Container{ + Name: "container-name", + Image: "registry.io/path/to/image", + Command: []string{"cmd1", "cmd2"}, + Args: []string{"3.1459267", "strVal"}, + WorkingDir: "/path/to/dir", + Ports: []v1.ContainerPort{ + { + Name: "port1", + Protocol: v1.ProtocolTCP, + HostIP: "1.1.1.1", + HostPort: int32(1000), + ContainerPort: int32(1000), + }, + }, + EnvFrom: []v1.EnvFromSource{ + { + Prefix: "ConfigEnvName", + ConfigMapRef: &v1.ConfigMapEnvSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "ConfigName", + }, + Optional: &inverseBoolEntry, + }, + }, + }, + Env: []v1.EnvVar{ + { + Name: "key1", + Value: "val1", + }, + { + Name: "SecretEnvName", + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "SecretName", + }, + Key: "SecretKey", + Optional: &inverseBoolEntry, + }, + }, + }, + }, + Resources: v1.ResourceRequirements{ + Limits: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpuLimit, + v1.ResourceMemory: memLimit, + }, + Requests: map[v1.ResourceName]resource.Quantity{ + v1.ResourceCPU: cpuRequest, + v1.ResourceMemory: memRequest, + }, + }, + VolumeMounts: []v1.VolumeMount{ + { + MountPath: "/path/to/mount", + MountPropagation: &kubePropagation, + Name: "volumeName", + SubPath: "/path/to/volume", + ReadOnly: false, + }, + }, + VolumeDevices: []v1.VolumeDevice{ + { + Name: "deviceName", + DevicePath: "/path/to/device", + }, + }, + LivenessProbe: &v1.Probe{ + Handler: v1.Handler{ + Exec: &v1.ExecAction{ + Command: []string{"cmd1", "cmd2"}, + }, + }, + InitialDelaySeconds: int32Val, + TimeoutSeconds: int32Val, + PeriodSeconds: int32Val, + SuccessThreshold: int32Val, + FailureThreshold: int32Val, + }, + ReadinessProbe: &v1.Probe{ + Handler: v1.Handler{ + Exec: &v1.ExecAction{ + Command: []string{"cmd3", "cmd4"}, + }, + }, + InitialDelaySeconds: int32Val, + TimeoutSeconds: int32Val, + PeriodSeconds: int32Val, + SuccessThreshold: int32Val, + FailureThreshold: int32Val, + }, + Lifecycle: &v1.Lifecycle{ + PostStart: &v1.Handler{ + TCPSocket: &v1.TCPSocketAction{ + Host: "actionHost", + Port: intstr.FromString("22"), + }, + }, + PreStop: &v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/action/path", + Host: "actionHost", + Port: intstr.FromString("443"), + HTTPHeaders: []v1.HTTPHeader{ + { + Name: "Key", + Value: "Value", + }, + { + Name: "HEADER", + Value: "VALUE", + }, + }, + Scheme: v1.URISchemeHTTPS, + }, + }, + }, + TerminationMessagePath: "/msg/path", + TerminationMessagePolicy: v1.TerminationMessageReadFile, + ImagePullPolicy: v1.PullAlways, + SecurityContext: &v1.SecurityContext{ + Capabilities: &v1.Capabilities{ + Add: []v1.Capability{"CAP1", "CAP2"}, + Drop: []v1.Capability{"CAP3"}, + }, + Privileged: &boolEntry, + SELinuxOptions: &v1.SELinuxOptions{ + Level: "selinuxlevel", + Role: "selinuxrole", + Type: "selinuxtype", + User: "selinuxuser", + }, + RunAsUser: &int64Val, + RunAsGroup: &int64Val, + RunAsNonRoot: &boolEntry, + ReadOnlyRootFilesystem: &boolEntry, + AllowPrivilegeEscalation: &boolEntry, + ProcMount: &kubeProcMount, + }, + Stdin: boolEntry, + StdinOnce: boolEntry, + TTY: boolEntry, +} + +var emptyMantleContainer = container.Container{ + Command: []string{}, + Args: []floatstr.FloatOrString{}, + Env: []env.Env{}, + Image: "", + Pull: container.PullDefault, + OnStart: &action.Action{}, + PreStop: &action.Action{}, + CPU: &resources.CPU{}, + Mem: &resources.Mem{}, + Name: "", + AddCapabilities: []string{}, + DelCapabilities: []string{}, + Privileged: nil, + AllowEscalation: nil, + RO: nil, + ForceNonRoot: nil, + UID: nil, + GID: nil, + SELinux: &selinux.SELinux{}, + LivenessProbe: &probe.Probe{ + Action: action.Action{}, + }, + ReadinessProbe: &probe.Probe{}, + Expose: []port.Port{}, + Stdin: false, + StdinOnce: false, + TTY: false, + ProcMount: nil, + WorkingDir: "", + TerminationMsgPath: "", + TerminationMsgPolicy: container.TerminationMessageDefault, + VolumeMounts: []volumemount.VolumeMount{}, + VolumeDevices: []volumedevice.VolumeDevice{}, +} + +var emptyKubeStatus = v1.PodStatus{ + Message: "", + Reason: "", + HostIP: "", + PodIP: "", +} + +type kubePodConfig struct { + basePod v1.Pod + volumes []v1.Volume + initContainers []v1.Container + containers []v1.Container + context *v1.PodSecurityContext + secrets []v1.LocalObjectReference + affinity *v1.Affinity + tolerations []v1.Toleration + aliases []v1.HostAlias + dnsConfig *v1.PodDNSConfig + gates []v1.PodReadinessGate +} + +func (k *kubePodConfig) Generate() v1.Pod { + pod := k.basePod + + if k.volumes != nil { + pod.Spec.Volumes = k.volumes + } + + if k.initContainers != nil { + pod.Spec.InitContainers = k.initContainers + } + + if k.containers != nil { + pod.Spec.Containers = k.containers + } + + if k.secrets != nil { + pod.Spec.ImagePullSecrets = k.secrets + } + + if k.context != nil { + pod.Spec.SecurityContext = k.context + } + if k.affinity != nil { + pod.Spec.Affinity = k.affinity + } + + if k.tolerations != nil { + pod.Spec.Tolerations = k.tolerations + } + + if k.aliases != nil { + pod.Spec.HostAliases = k.aliases + } + + if k.dnsConfig != nil { + pod.Spec.DNSConfig = k.dnsConfig + } + + if k.gates != nil { + pod.Spec.ReadinessGates = k.gates + } + + return pod +} + +type mantlePodConfig struct { + basePod Pod + volumes map[string]volume.Volume + initContainers []container.Container + containers []container.Container + fsgid *int64 + gids []int64 + registries []string + affinity *affinity.Affinity + tolerations []toleration.Toleration + hostAliases []hostalias.HostAlias + nameservers []string + searchDomains []string + resolverOptions []podtemplate.ResolverOptions + gates []podtemplate.PodConditionType +} + +func (m *mantlePodConfig) Generate() Pod { + pod := m.basePod + + if m.volumes != nil { + pod.Volumes = m.volumes + } + + if m.initContainers != nil { + pod.InitContainers = m.initContainers + } + + if m.containers != nil { + pod.Containers = m.containers + } + + if m.fsgid != nil { + pod.FSGID = m.fsgid + } + + if m.gids != nil { + pod.GIDs = m.gids + } + + if m.registries != nil { + pod.Registries = m.registries + } + + if m.affinity != nil { + pod.Affinity = m.affinity + } + + if m.tolerations != nil { + pod.Tolerations = m.tolerations + } + + if m.hostAliases != nil { + pod.HostAliases = m.hostAliases + } + + if m.nameservers != nil { + pod.Nameservers = m.nameservers + } + + if m.searchDomains != nil { + pod.SearchDomains = m.searchDomains + } + + if m.resolverOptions != nil { + pod.ResolverOptions = m.resolverOptions + } + + if m.gates != nil { + pod.Gates = m.gates + } + + return pod +} + +var kubePod = v1.Pod{ + TypeMeta: metav1.TypeMeta{ + Kind: "Pod", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "testPod", + Namespace: "testNS", + ClusterName: "testCluster", + Labels: map[string]string{"label1": "value1"}, + Annotations: map[string]string{"annotation1": "value2"}, + }, + Spec: v1.PodSpec{ + Volumes: []v1.Volume{}, + RestartPolicy: v1.RestartPolicyAlways, + TerminationGracePeriodSeconds: &int64Val, + ActiveDeadlineSeconds: &int64Val, + DNSPolicy: v1.DNSDefault, + NodeSelector: map[string]string{"label": "value"}, + ServiceAccountName: "svcAccount", + AutomountServiceAccountToken: &boolEntry, + NodeName: "testNode", + HostNetwork: true, + HostPID: true, + HostIPC: true, + ShareProcessNamespace: &boolEntry, + SecurityContext: nil, + Hostname: "testHostname", + Subdomain: "testSubdomain.com", + Affinity: &v1.Affinity{}, + //Affinity: nil, + SchedulerName: "schedName", + Tolerations: []v1.Toleration{}, + HostAliases: []v1.HostAlias{}, + PriorityClassName: "className", + Priority: &int32Val, + DNSConfig: &v1.PodDNSConfig{}, + //DNSConfig: nil, + ReadinessGates: []v1.PodReadinessGate{}, + RuntimeClassName: &strEntry, + EnableServiceLinks: &boolEntry, + }, +} + +var mantlePod = Pod{ + Version: "v1", + Phase: PodPhaseNone, + QOS: PodQOSClassNone, + PodTemplateMeta: PodTemplateMeta{ + Name: "testPod", + Namespace: "testNS", + Cluster: "testCluster", + Labels: map[string]string{"label1": "value1"}, + Annotations: map[string]string{"annotation1": "value2"}, + }, + PodTemplate: podtemplate.PodTemplate{ + Volumes: map[string]volume.Volume{}, + RestartPolicy: podtemplate.RestartPolicyAlways, + TerminationGracePeriod: &int64Val, + ActiveDeadline: &int64Val, + DNSPolicy: podtemplate.DNSDefault, + NodeSelector: map[string]string{"label": "value"}, + Account: "svcAccount", + AutomountAccountToken: &boolEntry, + Node: "testNode", + HostMode: []podtemplate.HostMode{ + podtemplate.HostModeNet, + podtemplate.HostModePID, + podtemplate.HostModeIPC, + }, + ShareNamespace: &boolEntry, + Hostname: "testHostname.testSubdomain.com", + Affinity: &affinity.Affinity{}, + //Affinity: nil, + SchedulerName: "schedName", + Tolerations: []toleration.Toleration{}, + HostAliases: []hostalias.HostAlias{}, + PriorityClass: "className", + Priority: &int32Val, + ResolverOptions: []podtemplate.ResolverOptions{}, + Gates: []podtemplate.PodConditionType{}, + RuntimeClass: &strEntry, + ServiceLinks: &boolEntry, + }, +} + +func TestPodFromKube(t *testing.T) { + testCases := []struct { + name string + original kubePodConfig + expected mantlePodConfig + fail bool + }{ + { + name: "empty kube v1 pod", + original: kubePodConfig{basePod: v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1"}}}, + expected: mantlePodConfig{basePod: Pod{Version: "v1", Phase: PodPhaseNone, QOS: PodQOSClassNone, PodTemplate: podtemplate.PodTemplate{DNSPolicy: podtemplate.DNSUnset}}}, + fail: false, + }, + { + name: "all fields defined v1 pod", + original: kubePodConfig{ + basePod: kubePod, + initContainers: []v1.Container{fullKubeContainer}, + containers: []v1.Container{fullKubeContainer}, + secrets: []v1.LocalObjectReference{{Name: strEntry}}, + context: &v1.PodSecurityContext{ + FSGroup: &int64Val, + SupplementalGroups: []int64{int64Val}, + }, + dnsConfig: &v1.PodDNSConfig{ + Nameservers: []string{strEntry}, + Searches: []string{strEntry}, + Options: []v1.PodDNSConfigOption{ + { + Name: strEntry, + Value: &strEntry, + }, + }, + }, + }, + expected: mantlePodConfig{ + basePod: mantlePod, + containers: []container.Container{fullMantleContainer}, + initContainers: []container.Container{fullMantleContainer}, + fsgid: &int64Val, + gids: []int64{int64Val}, + registries: []string{strEntry}, + nameservers: []string{strEntry}, + searchDomains: []string{strEntry}, + resolverOptions: []podtemplate.ResolverOptions{ + { + Name: strEntry, + Value: &strEntry, + }, + }, + }, + fail: false, + }, + { + name: "invalid pod version", + original: kubePodConfig{basePod: v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "invalid"}}}, + expected: mantlePodConfig{}, + fail: true, + }, + } + + for _, tc := range testCases { + pod, err := NewPodFromKubePod(tc.original.Generate()) + if err != nil && !tc.fail { + t.Errorf("%s: error converting pod: %+v", tc.name, err) + } + + if pod != nil { + expectedPod := tc.expected.Generate() + if !reflect.DeepEqual(*pod, expectedPod) { + t.Errorf("%s: bad pod conversion from kube: %s", tc.name, cmp.Diff(*pod, expectedPod)) + } + } + } +} + +func TestPodToKube(t *testing.T) { + testCases := []struct { + name string + original mantlePodConfig + expected kubePodConfig + fail bool + }{ + { + name: "empty pod with version v1", + original: mantlePodConfig{basePod: Pod{Version: "v1", Phase: PodPhaseNone, QOS: PodQOSClassNone, PodTemplate: podtemplate.PodTemplate{DNSPolicy: podtemplate.DNSUnset}}}, + expected: kubePodConfig{basePod: v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"}, Status: emptyKubeStatus}}, + fail: false, + }, + { + name: "all fields defined pod", + original: mantlePodConfig{ + basePod: mantlePod, + containers: []container.Container{fullMantleContainer}, + initContainers: []container.Container{fullMantleContainer}, + fsgid: &int64Val, + gids: []int64{int64Val}, + registries: []string{strEntry}, + nameservers: []string{strEntry}, + searchDomains: []string{strEntry}, + }, + expected: kubePodConfig{basePod: kubePod}, + fail: false, + }, + { + name: "invalid pod version", + original: mantlePodConfig{basePod: Pod{Version: "invalid"}}, + expected: kubePodConfig{}, + fail: true, + }, + } + + for _, tc := range testCases { + p := tc.original.Generate() + pod, err := p.ToKube() + if err != nil && !tc.fail { + t.Errorf("%s: error converting pod: %+v", tc.name, err) + } + + if pod != nil { + expectedPod := tc.expected.Generate() + p := pod.(*v1.Pod) + if !cmp.Equal(p, expectedPod) { + t.Errorf("%s: bad pod conversion to kube: %s", tc.name, cmp.Diff(p, expectedPod)) + } + } + } +} diff --git a/pkg/core/pod/podtemplate/from_kube.go b/pkg/core/pod/podtemplate/from_kube.go index a3317fb..03f0661 100644 --- a/pkg/core/pod/podtemplate/from_kube.go +++ b/pkg/core/pod/podtemplate/from_kube.go @@ -42,7 +42,7 @@ func fromKubePodSpecV1(kubeSpec v1.PodSpec) (*PodTemplate, error) { a, err := affinity.NewAffinityFromKubeAffinity(kubeSpec.Affinity) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "affinity") } if a != nil { mantlePod.Affinity = a @@ -56,7 +56,7 @@ func fromKubePodSpecV1(kubeSpec v1.PodSpec) (*PodTemplate, error) { for _, kubeContainer := range kubeSpec.InitContainers { c, err := container.NewContainerFromKubeContainer(&kubeContainer) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "initContainer") } initContainers = append(initContainers, *c) } @@ -70,7 +70,7 @@ func fromKubePodSpecV1(kubeSpec v1.PodSpec) (*PodTemplate, error) { for _, kubeContainer := range kubeSpec.Containers { c, err := container.NewContainerFromKubeContainer(&kubeContainer) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "container") } containers = append(containers, *c) } @@ -78,13 +78,13 @@ func fromKubePodSpecV1(kubeSpec v1.PodSpec) (*PodTemplate, error) { dnsPolicy, err := fromKubeDNSPolicyV1(kubeSpec.DNSPolicy) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "dns policy") } mantlePod.DNSPolicy = dnsPolicy aliases, err := fromKubeHostAliasesV1(kubeSpec.HostAliases) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "host aliases") } mantlePod.HostAliases = aliases @@ -95,7 +95,7 @@ func fromKubePodSpecV1(kubeSpec v1.PodSpec) (*PodTemplate, error) { restartPolicy, err := fromKubeRestartPolicyV1(kubeSpec.RestartPolicy) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "restart policy") } mantlePod.RestartPolicy = restartPolicy @@ -106,7 +106,7 @@ func fromKubePodSpecV1(kubeSpec v1.PodSpec) (*PodTemplate, error) { tolerations, err := fromKubeTolerationsV1(kubeSpec.Tolerations) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "tolerations") } mantlePod.Tolerations = tolerations @@ -125,7 +125,7 @@ func fromKubePodSpecV1(kubeSpec v1.PodSpec) (*PodTemplate, error) { if container.SELinux == nil { sel, err := selinux.NewSELinuxFromKubeSELinuxOptions(securityContext.SELinuxOptions) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "selinux") } container.SELinux = sel } @@ -144,7 +144,7 @@ func fromKubePodSpecV1(kubeSpec v1.PodSpec) (*PodTemplate, error) { mantlePod.Nameservers, mantlePod.SearchDomains, mantlePod.ResolverOptions = fromKubePodDNSConfigV1(kubeSpec.DNSConfig) gates, err := fromKubePodReadinessGateV1(kubeSpec.ReadinessGates) if err != nil { - return nil, err + return nil, serrors.ContextualizeErrorf(err, "readiness gates") } mantlePod.Gates = gates mantlePod.RuntimeClass = kubeSpec.RuntimeClassName diff --git a/pkg/core/pod/podtemplate/to_kube.go b/pkg/core/pod/podtemplate/to_kube.go index c1e73dc..4498f88 100644 --- a/pkg/core/pod/podtemplate/to_kube.go +++ b/pkg/core/pod/podtemplate/to_kube.go @@ -41,6 +41,10 @@ func (pt *PodTemplate) toKubeV1(apiVersion string) (*v1.PodSpec, error) { } var initContainers []v1.Container + + if pt.InitContainers != nil { + initContainers = make([]v1.Container, 0) + } for _, container := range pt.InitContainers { kc, err := container.ToKube("v1") if err != nil { @@ -52,6 +56,10 @@ func (pt *PodTemplate) toKubeV1(apiVersion string) (*v1.PodSpec, error) { spec.InitContainers = initContainers var kubeContainers []v1.Container + + if pt.Containers != nil { + kubeContainers = make([]v1.Container, 0) + } for _, container := range pt.Containers { kc, err := container.ToKube("v1") if err != nil { @@ -129,7 +137,11 @@ func (pt *PodTemplate) toKubeV1(apiVersion string) (*v1.PodSpec, error) { } func (pt *PodTemplate) toKubeVolumesV1() ([]v1.Volume, error) { - kubeVolumes := []v1.Volume{} + var kubeVolumes []v1.Volume + + if pt.Volumes != nil { + kubeVolumes = make([]v1.Volume, 0) + } for name, vol := range pt.Volumes { v, err := vol.ToKube("v1") @@ -147,6 +159,10 @@ func (pt *PodTemplate) toKubeVolumesV1() ([]v1.Volume, error) { func (pt *PodTemplate) toKubeHostAliasesV1() ([]v1.HostAlias, error) { var hostAliases []v1.HostAlias + if pt.HostAliases != nil { + hostAliases = []v1.HostAlias{} + } + for _, alias := range pt.HostAliases { hostAlias, err := alias.ToKube("v1") if err != nil { @@ -218,6 +234,10 @@ func (pt *PodTemplate) toKubeHostModesV1() (net bool, pid bool, ipc bool, err er func (pt *PodTemplate) toKubeRegistriesV1() []v1.LocalObjectReference { var kubeRegistries []v1.LocalObjectReference + if pt.Registries != nil { + kubeRegistries = make([]v1.LocalObjectReference, 0) + } + for _, reg := range pt.Registries { ref := v1.LocalObjectReference{ Name: reg, @@ -231,6 +251,10 @@ func (pt *PodTemplate) toKubeRegistriesV1() []v1.LocalObjectReference { func (pt *PodTemplate) toKubeTolerationsV1() ([]v1.Toleration, error) { var tolerations []v1.Toleration + if pt.Tolerations != nil { + tolerations = make([]v1.Toleration, 0) + } + for _, t := range pt.Tolerations { tol, err := t.ToKube("v1") if err != nil { @@ -245,11 +269,26 @@ func (pt *PodTemplate) toKubeTolerationsV1() ([]v1.Toleration, error) { } func (pt *PodTemplate) toKubeDNSConfigV1() *v1.PodDNSConfig { - if len(pt.Nameservers) == 0 && len(pt.SearchDomains) == 0 && len(pt.ResolverOptions) == 0 { + var ns []string + var ds []string + var options []v1.PodDNSConfigOption + + if pt.Nameservers == nil && pt.SearchDomains == nil && pt.ResolverOptions == nil { return nil } - options := []v1.PodDNSConfigOption{} + if pt.Nameservers != nil && len(pt.Nameservers) > 0 { + ns = pt.Nameservers + } + + if pt.SearchDomains != nil && len(pt.SearchDomains) > 0 { + ds = pt.SearchDomains + } + + if pt.ResolverOptions != nil && len(pt.ResolverOptions) > 0 { + options = []v1.PodDNSConfigOption{} + } + for _, opt := range pt.ResolverOptions { o := v1.PodDNSConfigOption{ Name: opt.Name, @@ -259,8 +298,8 @@ func (pt *PodTemplate) toKubeDNSConfigV1() *v1.PodDNSConfig { } return &v1.PodDNSConfig{ - Nameservers: pt.Nameservers, - Searches: pt.SearchDomains, + Nameservers: ns, + Searches: ds, Options: options, } } @@ -268,8 +307,8 @@ func (pt *PodTemplate) toKubeDNSConfigV1() *v1.PodDNSConfig { func (pt *PodTemplate) toKubePodReadinessGatesV1() []v1.PodReadinessGate { var readinessGates []v1.PodReadinessGate - if len(pt.Gates) > 0 { - readinessGates = []v1.PodReadinessGate{} + if pt.Gates != nil { + readinessGates = make([]v1.PodReadinessGate, 0) } for _, gate := range pt.Gates { diff --git a/pkg/core/pod/to_kube.go b/pkg/core/pod/to_kube.go index 4e7c6ce..0f0f5c2 100644 --- a/pkg/core/pod/to_kube.go +++ b/pkg/core/pod/to_kube.go @@ -50,6 +50,7 @@ func (pod *Pod) toKubeV1() (*v1.Pod, error) { kubePod.Status.Phase = pod.toKubePodPhaseV1() kubePod.Status.Message = pod.Msg kubePod.Status.Reason = pod.Reason + kubePod.Status.NominatedNodeName = pod.NominatedName kubePod.Status.HostIP = pod.NodeIP kubePod.Status.PodIP = pod.IP kubePod.Status.QOSClass = pod.toKubeQOSClassV1() diff --git a/pkg/core/protocol/from_kube.go b/pkg/core/protocol/from_kube.go index 14106bc..ae3f9b0 100644 --- a/pkg/core/protocol/from_kube.go +++ b/pkg/core/protocol/from_kube.go @@ -27,6 +27,8 @@ func fromKubeProtocolV1(kubeProtocol v1.Protocol) (*Protocol, error) { protocol = ProtocolTCP case v1.Protocol("UDP"): protocol = ProtocolUDP + case v1.Protocol("SCTP"): + protocol = ProtocolSCTP } return &protocol, nil diff --git a/pkg/core/protocol/protocol.go b/pkg/core/protocol/protocol.go index 02e229d..f5cc725 100644 --- a/pkg/core/protocol/protocol.go +++ b/pkg/core/protocol/protocol.go @@ -5,4 +5,5 @@ type Protocol int const ( ProtocolTCP Protocol = iota ProtocolUDP + ProtocolSCTP ) diff --git a/pkg/core/protocol/to_kube.go b/pkg/core/protocol/to_kube.go index f62f055..c902a2c 100644 --- a/pkg/core/protocol/to_kube.go +++ b/pkg/core/protocol/to_kube.go @@ -27,6 +27,8 @@ func (p *Protocol) toKubeV1() (v1.Protocol, error) { protocol = v1.Protocol("TCP") case ProtocolUDP: protocol = v1.Protocol("UDP") + case ProtocolSCTP: + protocol = v1.Protocol("SCTP") } return protocol, nil diff --git a/pkg/util/floatstr/.floatstr.go.swp b/pkg/util/floatstr/.floatstr.go.swp new file mode 100644 index 0000000..90ed23f Binary files /dev/null and b/pkg/util/floatstr/.floatstr.go.swp differ