From 355b7ee5c97b0d4d4bf0fca847987124130618da Mon Sep 17 00:00:00 2001 From: adohe Date: Thu, 24 Aug 2023 12:16:52 +0800 Subject: [PATCH] feat: add lifecycle type and encoding/decoding support --- .../workload/container/container.go | 188 ++++++++-- .../workload/container/container_test.go | 320 +++++++++++++++++- 2 files changed, 471 insertions(+), 37 deletions(-) diff --git a/pkg/models/appconfiguration/workload/container/container.go b/pkg/models/appconfiguration/workload/container/container.go index a5a849578..ff50cad02 100644 --- a/pkg/models/appconfiguration/workload/container/container.go +++ b/pkg/models/appconfiguration/workload/container/container.go @@ -5,6 +5,7 @@ import ( "errors" ) +// Container describes how the Application's tasks are expected to be run. type Container struct { // Image to run for this container Image string `yaml:"image" json:"image"` @@ -19,12 +20,36 @@ type Container struct { Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"` // The current working directory of the running process defined in entrypoint. WorkingDir string `yaml:"workingDir,omitempty" json:"workingDir,omitempty"` + // Resource requirements for this container. + Resources map[string]string `yaml:"resources,omitempty" json:"resources,omitempty"` + // Files configures one or more files to be created in the container. + Files map[string]FileSpec `yaml:"files,omitempty" json:"files,omitempty"` + // Dirs configures one or more volumes to be mounted to the specified folder. + Dirs map[string]string `yaml:"dirs,omitempty" json:"dirs,omitempty"` // Periodic probe of container liveness. LivenessProbe *Probe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"` // Periodic probe of container service readiness. ReadinessProbe *Probe `yaml:"readinessProbe,omitempty" json:"readinessProbe,omitempty"` // StartupProbe indicates that the Pod has successfully initialized. StartupProbe *Probe `yaml:"startupProbe,omitempty" json:"startupProbe,omitempty"` + // Actions that the management system should take in response to container lifecycle events. + Lifecycle *Lifecycle `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"` +} + +// FileSpec defines the target file in a Container +type FileSpec struct { + // The content of target file in plain text. + Content string `yaml:"content,omitempty" json:"content,omitempty"` + // Source for the file content, might be a reference to a secret value. + ContentFrom string `yaml:"contentFrom,omitempty" json:"contentFrom,omitempty"` + // Mode bits used to set permissions on this file. + Mode string `yaml:"mode" json:"mode"` +} + +// TypeWrapper is a thin wrapper to make YAML decoder happy. +type TypeWrapper struct { + // Type of action to be taken. + Type string `yaml:"_type" json:"_type"` } // Probe describes a health check to be performed against a container to determine whether it is @@ -44,16 +69,11 @@ type Probe struct { FailureThreshold int32 `yaml:"failureThreshold,omitempty" json:"failureThreshold,omitempty"` } -type ProbeType struct { - // Type of action to be taken. - Type string `yaml:"_type" json:"_type"` -} - // ProbeHandler defines a specific action that should be taken in a probe. // One and only one of the fields must be specified. type ProbeHandler struct { // Type of action to be taken. - ProbeType `yaml:"_type" json:"_type"` + TypeWrapper `yaml:"_type" json:"_type"` // Exec specifies the action to take. // +optional *ExecAction `yaml:",inline" json:",inline"` @@ -87,31 +107,55 @@ type TCPSocketAction struct { URL string `yaml:"url,omitempty" json:"url,omitempty"` } +// Lifecycle describes actions that the management system should take in response +// to container lifecycle events. +type Lifecycle struct { + // PreStop is called immediately before a container is terminated due to an + // API request or management event such as liveness/startup probe failure, + // preemption, resource contention, etc. + PreStop *LifecycleHandler `yaml:"preStop,omitempty" json:"preStop,omitempty"` + // PostStart is called immediately after a container is created. + PostStart *LifecycleHandler `yaml:"postStart,omitempty" json:"postStart,omitempty"` +} + +// LifecycleHandler defines a specific action that should be taken in a lifecycle +// hook. One and only one of the fields, except TCPSocket must be specified. +type LifecycleHandler struct { + // Type of action to be taken. + TypeWrapper `yaml:"_type" json:"_type"` + // Exec specifies the action to take. + // +optional + *ExecAction `yaml:",inline" json:",inline"` + // HTTPGet specifies the http request to perform. + // +optional + *HTTPGetAction `yaml:",inline" json:",inline"` +} + // MarshalJSON implements the json.Marshaler interface for ProbeHandler. func (p *ProbeHandler) MarshalJSON() ([]byte, error) { switch p.Type { case "Http": return json.Marshal(struct { - ProbeType `json:",inline"` + TypeWrapper `json:",inline"` *HTTPGetAction `json:",inline"` }{ - ProbeType: ProbeType{p.Type}, + TypeWrapper: TypeWrapper{p.Type}, HTTPGetAction: p.HTTPGetAction, }) case "Exec": return json.Marshal(struct { - ProbeType `json:",inline"` + TypeWrapper `json:",inline"` *ExecAction `json:",inline"` }{ - ProbeType: ProbeType{p.Type}, - ExecAction: p.ExecAction, + TypeWrapper: TypeWrapper{p.Type}, + ExecAction: p.ExecAction, }) case "Tcp": return json.Marshal(struct { - ProbeType `json:",inline"` + TypeWrapper `json:",inline"` *TCPSocketAction `json:",inline"` }{ - ProbeType: ProbeType{p.Type}, + TypeWrapper: TypeWrapper{p.Type}, TCPSocketAction: p.TCPSocketAction, }) default: @@ -121,7 +165,7 @@ func (p *ProbeHandler) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the json.Unmarshaller interface for ProbeHandler. func (p *ProbeHandler) UnmarshalJSON(data []byte) error { - var probeType ProbeType + var probeType TypeWrapper err := json.Unmarshal(data, &probeType) if err != nil { return err @@ -153,26 +197,26 @@ func (p *ProbeHandler) MarshalYAML() (interface{}, error) { switch p.Type { case "Http": return struct { - ProbeType `yaml:",inline" json:",inline"` + TypeWrapper `yaml:",inline" json:",inline"` HTTPGetAction `yaml:",inline" json:",inline"` }{ - ProbeType: ProbeType{Type: "Http"}, + TypeWrapper: TypeWrapper{Type: p.Type}, HTTPGetAction: *p.HTTPGetAction, }, nil case "Exec": return struct { - ProbeType `yaml:",inline" json:",inline"` - ExecAction `yaml:",inline" json:",inline"` + TypeWrapper `yaml:",inline" json:",inline"` + ExecAction `yaml:",inline" json:",inline"` }{ - ProbeType: ProbeType{Type: "Exec"}, - ExecAction: *p.ExecAction, + TypeWrapper: TypeWrapper{Type: p.Type}, + ExecAction: *p.ExecAction, }, nil case "Tcp": return struct { - ProbeType `yaml:",inline" json:",inline"` + TypeWrapper `yaml:",inline" json:",inline"` TCPSocketAction `yaml:",inline" json:",inline"` }{ - ProbeType: ProbeType{Type: "Tcp"}, + TypeWrapper: TypeWrapper{Type: p.Type}, TCPSocketAction: *p.TCPSocketAction, }, nil } @@ -182,7 +226,7 @@ func (p *ProbeHandler) MarshalYAML() (interface{}, error) { // UnmarshalYAML implements the yaml.Unmarshaler interface for ProbeHandler. func (p *ProbeHandler) UnmarshalYAML(unmarshal func(interface{}) error) error { - var probeType ProbeType + var probeType TypeWrapper err := unmarshal(&probeType) if err != nil { return err @@ -208,3 +252,101 @@ func (p *ProbeHandler) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } + +// MarshalJSON implements the json.Marshaler interface for LifecycleHandler. +func (l *LifecycleHandler) MarshalJSON() ([]byte, error) { + switch l.Type { + case "Http": + return json.Marshal(struct { + TypeWrapper `json:",inline"` + *HTTPGetAction `json:",inline"` + }{ + TypeWrapper: TypeWrapper{l.Type}, + HTTPGetAction: l.HTTPGetAction, + }) + case "Exec": + return json.Marshal(struct { + TypeWrapper `json:",inline"` + *ExecAction `json:",inline"` + }{ + TypeWrapper: TypeWrapper{l.Type}, + ExecAction: l.ExecAction, + }) + default: + return nil, errors.New("unrecognized lifecycle handler type") + } +} + +// UnmarshalJSON implements the json.Unmarshaller interface for LifecycleHandler. +func (l *LifecycleHandler) UnmarshalJSON(data []byte) error { + var handlerType TypeWrapper + err := json.Unmarshal(data, &handlerType) + if err != nil { + return err + } + + l.Type = handlerType.Type + switch l.Type { + case "Http": + handler := &HTTPGetAction{} + err = json.Unmarshal(data, handler) + l.HTTPGetAction = handler + case "Exec": + handler := &ExecAction{} + err = json.Unmarshal(data, handler) + l.ExecAction = handler + default: + return errors.New("unrecognized lifecycle handler type") + } + + return err +} + +// MarshalYAML implements the yaml.Marshaler interface for LifecycleHandler. +func (l *LifecycleHandler) MarshalYAML() (interface{}, error) { + switch l.Type { + case "Http": + return struct { + TypeWrapper `yaml:",inline" json:",inline"` + HTTPGetAction `yaml:",inline" json:",inline"` + }{ + TypeWrapper: TypeWrapper{Type: l.Type}, + HTTPGetAction: *l.HTTPGetAction, + }, nil + case "Exec": + return struct { + TypeWrapper `yaml:",inline" json:",inline"` + ExecAction `yaml:",inline" json:",inline"` + }{ + TypeWrapper: TypeWrapper{Type: l.Type}, + ExecAction: *l.ExecAction, + }, nil + default: + return nil, errors.New("unrecognized lifecycle handler type") + } +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for LifecycleHandler. +func (l *LifecycleHandler) UnmarshalYAML(unmarshal func(interface{}) error) error { + var handlerType TypeWrapper + err := unmarshal(&handlerType) + if err != nil { + return err + } + + l.Type = handlerType.Type + switch l.Type { + case "Http": + handler := &HTTPGetAction{} + err = unmarshal(handler) + l.HTTPGetAction = handler + case "Exec": + handler := &ExecAction{} + err = unmarshal(handler) + l.ExecAction = handler + default: + return errors.New("unrecognized lifecycle handler type") + } + + return err +} diff --git a/pkg/models/appconfiguration/workload/container/container_test.go b/pkg/models/appconfiguration/workload/container/container_test.go index a032431d2..0b425a675 100644 --- a/pkg/models/appconfiguration/workload/container/container_test.go +++ b/pkg/models/appconfiguration/workload/container/container_test.go @@ -16,15 +16,25 @@ func TestContainerMarshalJSON(t *testing.T) { { input: Container{ Image: "nginx:v1", + Resources: map[string]string{ + "cpu": "4", + "memory": "8Gi", + }, + Files: map[string]FileSpec{ + "/tmp/test.txt": { + Content: "hello world", + Mode: "0644", + }, + }, }, - result: `{"image":"nginx:v1"}`, + result: `{"image":"nginx:v1","resources":{"cpu":"4","memory":"8Gi"},"files":{"/tmp/test.txt":{"content":"hello world","mode":"0644"}}}`, }, { input: Container{ Image: "nginx:v1", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Http"}, + TypeWrapper: TypeWrapper{"Http"}, HTTPGetAction: &HTTPGetAction{ URL: "http://localhost:80", }, @@ -39,7 +49,7 @@ func TestContainerMarshalJSON(t *testing.T) { Image: "nginx:v1", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Exec"}, + TypeWrapper: TypeWrapper{"Exec"}, ExecAction: &ExecAction{ Command: []string{"cat", "/tmp/healthy"}, }, @@ -54,7 +64,7 @@ func TestContainerMarshalJSON(t *testing.T) { Image: "nginx:v1", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Tcp"}, + TypeWrapper: TypeWrapper{Type: "Tcp"}, TCPSocketAction: &TCPSocketAction{ URL: "127.0.0.1:8080", }, @@ -64,6 +74,46 @@ func TestContainerMarshalJSON(t *testing.T) { }, result: `{"image":"nginx:v1","readinessProbe":{"probeHandler":{"_type":"Tcp","url":"127.0.0.1:8080"},"initialDelaySeconds":10}}`, }, + { + input: Container{ + Image: "nginx:v1", + Lifecycle: &Lifecycle{ + PostStart: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Exec"}, + ExecAction: &ExecAction{ + Command: []string{"/bin/sh", "-c", "nginx -s quit; while killall -0 nginx; do sleep 1; done"}, + }, + }, + PreStop: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Exec"}, + ExecAction: &ExecAction{ + Command: []string{"/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"}, + }, + }, + }, + }, + result: `{"image":"nginx:v1","lifecycle":{"preStop":{"_type":"Exec","command":["/bin/sh","-c","echo Hello from the postStart handler \u003e /usr/share/message"]},"postStart":{"_type":"Exec","command":["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]}}}`, + }, + { + input: Container{ + Image: "nginx:v1", + Lifecycle: &Lifecycle{ + PostStart: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Http"}, + HTTPGetAction: &HTTPGetAction{ + URL: "http://localhost:80", + }, + }, + PreStop: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Http"}, + HTTPGetAction: &HTTPGetAction{ + URL: "http://localhost:80", + }, + }, + }, + }, + result: `{"image":"nginx:v1","lifecycle":{"preStop":{"_type":"Http","url":"http://localhost:80"},"postStart":{"_type":"Http","url":"http://localhost:80"}}}`, + }, } for _, c := range cases { @@ -83,9 +133,19 @@ func TestContainerUnmarshalJSON(t *testing.T) { result Container }{ { - input: `{"image":"nginx:v1"}`, + input: `{"image":"nginx:v1","resources":{"cpu":"4","memory":"8Gi"},"files":{"/tmp/test.txt":{"content":"hello world","mode":"0644"}}}`, result: Container{ Image: "nginx:v1", + Resources: map[string]string{ + "cpu": "4", + "memory": "8Gi", + }, + Files: map[string]FileSpec{ + "/tmp/test.txt": { + Content: "hello world", + Mode: "0644", + }, + }, }, }, { @@ -94,7 +154,7 @@ func TestContainerUnmarshalJSON(t *testing.T) { Image: "nginx:v1", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Http"}, + TypeWrapper: TypeWrapper{Type: "Http"}, HTTPGetAction: &HTTPGetAction{ URL: "http://localhost:80", }, @@ -109,7 +169,7 @@ func TestContainerUnmarshalJSON(t *testing.T) { Image: "nginx:v1", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Exec"}, + TypeWrapper: TypeWrapper{Type: "Exec"}, ExecAction: &ExecAction{ Command: []string{"cat", "/tmp/healthy"}, }, @@ -124,7 +184,7 @@ func TestContainerUnmarshalJSON(t *testing.T) { Image: "nginx:v1", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Tcp"}, + TypeWrapper: TypeWrapper{Type: "Tcp"}, TCPSocketAction: &TCPSocketAction{ URL: "127.0.0.1:8080", }, @@ -133,6 +193,46 @@ func TestContainerUnmarshalJSON(t *testing.T) { }, }, }, + { + input: `{"image":"nginx:v1","lifecycle":{"preStop":{"_type":"Exec","command":["/bin/sh","-c","echo Hello from the postStart handler \u003e /usr/share/message"]},"postStart":{"_type":"Exec","command":["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]}}}`, + result: Container{ + Image: "nginx:v1", + Lifecycle: &Lifecycle{ + PostStart: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Exec"}, + ExecAction: &ExecAction{ + Command: []string{"/bin/sh", "-c", "nginx -s quit; while killall -0 nginx; do sleep 1; done"}, + }, + }, + PreStop: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Exec"}, + ExecAction: &ExecAction{ + Command: []string{"/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"}, + }, + }, + }, + }, + }, + { + input: `{"image":"nginx:v1","lifecycle":{"preStop":{"_type":"Http","url":"http://localhost:80"},"postStart":{"_type":"Http","url":"http://localhost:80"}}}`, + result: Container{ + Image: "nginx:v1", + Lifecycle: &Lifecycle{ + PostStart: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Http"}, + HTTPGetAction: &HTTPGetAction{ + URL: "http://localhost:80", + }, + }, + PreStop: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Http"}, + HTTPGetAction: &HTTPGetAction{ + URL: "http://localhost:80", + }, + }, + }, + }, + }, } for _, c := range cases { @@ -186,7 +286,7 @@ workingDir: /tmp WorkingDir: "/tmp", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Http"}, + TypeWrapper: TypeWrapper{Type: "Http"}, HTTPGetAction: &HTTPGetAction{ URL: "http://localhost:80", }, @@ -224,7 +324,7 @@ readinessProbe: WorkingDir: "/tmp", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Exec"}, + TypeWrapper: TypeWrapper{Type: "Exec"}, ExecAction: &ExecAction{ Command: []string{"cat", "/tmp/healthy"}, }, @@ -264,7 +364,7 @@ readinessProbe: WorkingDir: "/tmp", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Tcp"}, + TypeWrapper: TypeWrapper{Type: "Tcp"}, TCPSocketAction: &TCPSocketAction{ URL: "127.0.0.1:8080", }, @@ -289,6 +389,102 @@ readinessProbe: _type: Tcp url: 127.0.0.1:8080 initialDelaySeconds: 10 +`, + }, + { + input: Container{ + Image: "nginx:v1", + Command: []string{"/bin/sh", "-c", "echo hi"}, + Args: []string{"/bin/sh", "-c", "echo hi"}, + Env: map[string]string{ + "env1": "VALUE", + }, + WorkingDir: "/tmp", + Lifecycle: &Lifecycle{ + PostStart: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Exec"}, + ExecAction: &ExecAction{ + Command: []string{"/bin/sh", "-c", "nginx -s quit; while killall -0 nginx; do sleep 1; done"}, + }, + }, + PreStop: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Exec"}, + ExecAction: &ExecAction{ + Command: []string{"/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"}, + }, + }, + }, + }, + result: `image: nginx:v1 +command: +- /bin/sh +- -c +- echo hi +args: +- /bin/sh +- -c +- echo hi +env: + env1: VALUE +workingDir: /tmp +lifecycle: + preStop: + _type: Exec + command: + - /bin/sh + - -c + - echo Hello from the postStart handler > /usr/share/message + postStart: + _type: Exec + command: + - /bin/sh + - -c + - nginx -s quit; while killall -0 nginx; do sleep 1; done +`, + }, + { + input: Container{ + Image: "nginx:v1", + Command: []string{"/bin/sh", "-c", "echo hi"}, + Args: []string{"/bin/sh", "-c", "echo hi"}, + Env: map[string]string{ + "env1": "VALUE", + }, + WorkingDir: "/tmp", + Lifecycle: &Lifecycle{ + PostStart: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Http"}, + HTTPGetAction: &HTTPGetAction{ + URL: "http://localhost:80", + }, + }, + PreStop: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Http"}, + HTTPGetAction: &HTTPGetAction{ + URL: "http://localhost:80", + }, + }, + }, + }, + result: `image: nginx:v1 +command: +- /bin/sh +- -c +- echo hi +args: +- /bin/sh +- -c +- echo hi +env: + env1: VALUE +workingDir: /tmp +lifecycle: + preStop: + _type: Http + url: http://localhost:80 + postStart: + _type: Http + url: http://localhost:80 `, }, } @@ -362,7 +558,7 @@ readinessProbe: WorkingDir: "/tmp", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Http"}, + TypeWrapper: TypeWrapper{Type: "Http"}, HTTPGetAction: &HTTPGetAction{ URL: "http://localhost:80", }, @@ -402,7 +598,7 @@ readinessProbe: WorkingDir: "/tmp", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Exec"}, + TypeWrapper: TypeWrapper{Type: "Exec"}, ExecAction: &ExecAction{ Command: []string{"cat", "/tmp/healthy"}, }, @@ -440,7 +636,7 @@ readinessProbe: WorkingDir: "/tmp", ReadinessProbe: &Probe{ ProbeHandler: &ProbeHandler{ - ProbeType: ProbeType{Type: "Tcp"}, + TypeWrapper: TypeWrapper{Type: "Tcp"}, TCPSocketAction: &TCPSocketAction{ URL: "127.0.0.1:8080", }, @@ -449,6 +645,102 @@ readinessProbe: }, }, }, + { + input: `image: nginx:v1 +command: +- /bin/sh +- -c +- echo hi +args: +- /bin/sh +- -c +- echo hi +env: + env1: VALUE +workingDir: /tmp +lifecycle: + preStop: + _type: Exec + command: + - /bin/sh + - -c + - echo Hello from the postStart handler > /usr/share/message + postStart: + _type: Exec + command: + - /bin/sh + - -c + - nginx -s quit; while killall -0 nginx; do sleep 1; done +`, + result: Container{ + Image: "nginx:v1", + Command: []string{"/bin/sh", "-c", "echo hi"}, + Args: []string{"/bin/sh", "-c", "echo hi"}, + Env: map[string]string{ + "env1": "VALUE", + }, + WorkingDir: "/tmp", + Lifecycle: &Lifecycle{ + PostStart: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Exec"}, + ExecAction: &ExecAction{ + Command: []string{"/bin/sh", "-c", "nginx -s quit; while killall -0 nginx; do sleep 1; done"}, + }, + }, + PreStop: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Exec"}, + ExecAction: &ExecAction{ + Command: []string{"/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"}, + }, + }, + }, + }, + }, + { + input: `image: nginx:v1 +command: +- /bin/sh +- -c +- echo hi +args: +- /bin/sh +- -c +- echo hi +env: + env1: VALUE +workingDir: /tmp +lifecycle: + preStop: + _type: Http + url: http://localhost:80 + postStart: + _type: Http + url: http://localhost:80 +`, + result: Container{ + Image: "nginx:v1", + Command: []string{"/bin/sh", "-c", "echo hi"}, + Args: []string{"/bin/sh", "-c", "echo hi"}, + Env: map[string]string{ + "env1": "VALUE", + }, + WorkingDir: "/tmp", + Lifecycle: &Lifecycle{ + PostStart: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Http"}, + HTTPGetAction: &HTTPGetAction{ + URL: "http://localhost:80", + }, + }, + PreStop: &LifecycleHandler{ + TypeWrapper: TypeWrapper{"Http"}, + HTTPGetAction: &HTTPGetAction{ + URL: "http://localhost:80", + }, + }, + }, + }, + }, } for _, c := range cases {