diff --git a/pkg/bootstrap/isogen/command_test.go b/pkg/bootstrap/isogen/command_test.go index 813349645..aa225f0df 100644 --- a/pkg/bootstrap/isogen/command_test.go +++ b/pkg/bootstrap/isogen/command_test.go @@ -27,6 +27,7 @@ import ( api "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/testutil" @@ -39,6 +40,7 @@ type mockContainer struct { rmContainer func() error getID func() string waitUntilFinished func() error + inspectContainer func() (container.State, error) } func (mc *mockContainer) ImagePull() error { @@ -65,6 +67,10 @@ func (mc *mockContainer) WaitUntilFinished() error { return mc.waitUntilFinished() } +func (mc *mockContainer) InspectContainer() (container.State, error) { + return mc.inspectContainer() +} + const testID = "TESTID" func TestBootstrapIso(t *testing.T) { diff --git a/pkg/container/constants.go b/pkg/container/constants.go new file mode 100644 index 000000000..7f5b477a9 --- /dev/null +++ b/pkg/container/constants.go @@ -0,0 +1,32 @@ +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package container + +const ( + // CreatedContainerStatus indicates container has been created + CreatedContainerStatus = "created" + // RunningContainerStatus indicates container is in running state + RunningContainerStatus = "running" + // PausedContainerStatus indicates container is in paused state + PausedContainerStatus = "paused" + // RestartedContainerStatus indicates container has re-started + RestartedContainerStatus = "restarted" + // RemovingContainerStatus indicates container is being removed + RemovingContainerStatus = "removing" + // ExitedContainerStatus indicates container has exited + ExitedContainerStatus = "exited" + // DeadContainerStatus indicates container is dead + DeadContainerStatus = "dead" +) diff --git a/pkg/container/container.go b/pkg/container/container.go index af8851dc1..36df0c483 100644 --- a/pkg/container/container.go +++ b/pkg/container/container.go @@ -19,6 +19,17 @@ import ( "io" ) +// Status type provides container status +type Status string + +// State provides information about the Container +type State struct { + // ExitCode: returns the container's exit code. Zero means exited normally, otherwise errored + ExitCode int + // Status: String representation of the container state. + Status Status +} + // Container interface abstraction for container. // Particular implementation depends on container runtime environment (CRE). Interface // defines methods that must be implemented for CRE (e.g. docker, containerd or CRI-O) @@ -26,9 +37,10 @@ type Container interface { ImagePull() error RunCommand([]string, io.Reader, []string, []string) error GetContainerLogs() (io.ReadCloser, error) + InspectContainer() (State, error) + WaitUntilFinished() error RmContainer() error GetID() string - WaitUntilFinished() error } // NewContainer returns instance of Container interface implemented by particular driver diff --git a/pkg/container/container_docker.go b/pkg/container/container_docker.go index db5a51bfe..0505ea4a9 100644 --- a/pkg/container/container_docker.go +++ b/pkg/container/container_docker.go @@ -87,6 +87,11 @@ type DockerClient interface { string, types.ContainerRemoveOptions, ) error + // ContainerInspect returns the container state + ContainerInspect( + ctx context.Context, + containerID string, + ) (types.ContainerJSON, error) } // DockerContainer docker container object wrapper @@ -298,6 +303,21 @@ func (c *DockerContainer) RmContainer() error { ) } +// InspectContainer inspect the running container +func (c *DockerContainer) InspectContainer() (State, error) { + json, err := c.dockerClient.ContainerInspect(context.Background(), c.id) + if err != nil { + log.Debug("Failed to inspect container status") + return State{}, err + } + + state := State{ + ExitCode: json.ContainerJSONBase.State.ExitCode, + Status: Status(json.ContainerJSONBase.State.Status), + } + return state, err +} + // WaitUntilFinished waits unit container command is finished, return an error if failed func (c *DockerContainer) WaitUntilFinished() error { statusCh, errCh := c.dockerClient.ContainerWait(c.ctx, c.id, container.WaitConditionNotRunning) diff --git a/pkg/container/container_docker_test.go b/pkg/container/container_docker_test.go index d50db1c45..632b7963a 100644 --- a/pkg/container/container_docker_test.go +++ b/pkg/container/container_docker_test.go @@ -56,6 +56,7 @@ type mockDockerClient struct { containerStart func() error containerWait func() (<-chan container.ContainerWaitOKBody, <-chan error) containerLogs func() (io.ReadCloser, error) + containerInspect func() (types.ContainerJSON, error) } func (mdc *mockDockerClient) ImageInspectWithRaw(context.Context, string) (types.ImageInspect, []byte, error) { @@ -119,6 +120,13 @@ func (mdc *mockDockerClient) ContainerRemove(context.Context, string, types.Cont return nil } +func (mdc *mockDockerClient) ContainerInspect(context.Context, string) (types.ContainerJSON, error) { + if mdc.containerInspect != nil { + return mdc.containerInspect() + } + return types.ContainerJSON{}, nil +} + func getDockerContainerMock(mdc mockDockerClient) *DockerContainer { ctx := context.Background() cnt := &DockerContainer{ @@ -555,3 +563,95 @@ func TestRmContainer(t *testing.T) { assert.Equal(t, tt.expectedErr, actualErr) } } + +func TestInspectContainer(t *testing.T) { + tests := []struct { + cli mockDockerClient + expectedState State + expectedErr error + }{ + { + // Status: String representation of the container state. + // Testing Status == CreatedContainerStatus and ExitCode == 0 + cli: mockDockerClient{ + containerInspect: func() (types.ContainerJSON, error) { + json := types.ContainerJSON{} + json.ContainerJSONBase = &types.ContainerJSONBase{} + json.ContainerJSONBase.State = &types.ContainerState{} + json.ContainerJSONBase.State.ExitCode = 0 + json.ContainerJSONBase.State.Status = CreatedContainerStatus + return json, nil + }, + }, + expectedState: State{ + ExitCode: 0, + Status: CreatedContainerStatus, + }, + expectedErr: nil, + }, + { + // Status: String representation of the container state. + // Testing Status == RunningContainerStatus and ExitCode == 0 + cli: mockDockerClient{ + containerInspect: func() (types.ContainerJSON, error) { + json := types.ContainerJSON{} + json.ContainerJSONBase = &types.ContainerJSONBase{} + json.ContainerJSONBase.State = &types.ContainerState{} + json.ContainerJSONBase.State.ExitCode = 0 + json.ContainerJSONBase.State.Status = RunningContainerStatus + return json, nil + }, + }, + expectedState: State{ + ExitCode: 0, + Status: RunningContainerStatus, + }, + expectedErr: nil, + }, + { + // Status: String representation of the container state. + // Testing Status == ExitedContainerStatus and ExitCode == 0 + cli: mockDockerClient{ + containerInspect: func() (types.ContainerJSON, error) { + json := types.ContainerJSON{} + json.ContainerJSONBase = &types.ContainerJSONBase{} + json.ContainerJSONBase.State = &types.ContainerState{} + json.ContainerJSONBase.State.ExitCode = 0 + json.ContainerJSONBase.State.Status = ExitedContainerStatus + return json, nil + }, + }, + expectedState: State{ + ExitCode: 0, + Status: ExitedContainerStatus, + }, + expectedErr: nil, + }, + { + // Status: String representation of the container state. + // Testing Status == ExitedContainerStatus and ExitCode == 1 + cli: mockDockerClient{ + containerInspect: func() (types.ContainerJSON, error) { + json := types.ContainerJSON{} + json.ContainerJSONBase = &types.ContainerJSONBase{} + json.ContainerJSONBase.State = &types.ContainerState{} + json.ContainerJSONBase.State.ExitCode = 1 + json.ContainerJSONBase.State.Status = ExitedContainerStatus + return json, nil + }, + }, + expectedState: State{ + ExitCode: 1, + Status: ExitedContainerStatus, + }, + expectedErr: nil, + }, + } + + for _, tt := range tests { + cnt := getDockerContainerMock(tt.cli) + actualState, actualErr := cnt.InspectContainer() + assert.Equal(t, tt.expectedState, actualState) + assert.Equal(t, tt.expectedErr, actualErr) + } +}