diff --git a/pkg/api/v1alpha1/bootconfiguration_types.go b/pkg/api/v1alpha1/bootconfiguration_types.go index 157f091f4..9b2be0020 100644 --- a/pkg/api/v1alpha1/bootconfiguration_types.go +++ b/pkg/api/v1alpha1/bootconfiguration_types.go @@ -41,7 +41,7 @@ type BootstrapContainer struct { ContainerRuntime string `json:"containerRuntime,omitempty"` Image string `json:"image,omitempty"` Volume string `json:"volume,omitempty"` - Kubeconfig string `json:"kubeconfig,omitempty"` + Kubeconfig string `json:"saveKubeconfigFileName,omitempty"` } // DefaultBootConfiguration can be used to safely unmarshal BootConfiguration object without nil pointers diff --git a/pkg/bootstrap/ephemeral/command.go b/pkg/bootstrap/ephemeral/command.go index 0366e2db0..bb3d0a1f8 100644 --- a/pkg/bootstrap/ephemeral/command.go +++ b/pkg/bootstrap/ephemeral/command.go @@ -104,6 +104,14 @@ func (options *BootstrapContainerOptions) VerifyInputs() error { return nil } +// VerifyArtifacts verifies the artifacts +func (options *BootstrapContainerOptions) VerifyArtifacts() error { + hostVol := strings.Split(options.Cfg.BootstrapContainer.Volume, ":")[0] + configPath := filepath.Join(hostVol, options.Cfg.EphemeralCluster.ConfigFilename) + _, err := os.Stat(configPath) + return err +} + // GetContainerStatus returns the Bootstrap Container state func (options *BootstrapContainerOptions) GetContainerStatus() (container.Status, error) { // Check status of the container, e.g., "running" diff --git a/pkg/bootstrap/ephemeral/command_test.go b/pkg/bootstrap/ephemeral/command_test.go index fe7e2d5e9..b827c90df 100644 --- a/pkg/bootstrap/ephemeral/command_test.go +++ b/pkg/bootstrap/ephemeral/command_test.go @@ -26,46 +26,9 @@ import ( "opendev.org/airship/airshipctl/pkg/bootstrap/ephemeral" "opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/log" + testcontainer "opendev.org/airship/airshipctl/testutil/container" ) -type mockContainer struct { - imagePull func() error - runCommand func() error - getContainerLogs func() (io.ReadCloser, error) - rmContainer func() error - getID func() string - waitUntilFinished func() error - inspectContainer func() (container.State, error) -} - -func (mc *mockContainer) ImagePull() error { - return mc.imagePull() -} - -func (mc *mockContainer) RunCommand([]string, io.Reader, []string, []string) error { - return mc.runCommand() -} - -func (mc *mockContainer) GetContainerLogs() (io.ReadCloser, error) { - return mc.getContainerLogs() -} - -func (mc *mockContainer) RmContainer() error { - return mc.rmContainer() -} - -func (mc *mockContainer) GetID() string { - return mc.getID() -} - -func (mc *mockContainer) WaitUntilFinished() error { - return mc.waitUntilFinished() -} - -func (mc *mockContainer) InspectContainer() (container.State, error) { - return mc.inspectContainer() -} - // Unit test for the method getContainerStatus() func TestGetContainerStatus(t *testing.T) { testCfg := &api.BootConfiguration{ @@ -82,16 +45,49 @@ func TestGetContainerStatus(t *testing.T) { } tests := []struct { - container *mockContainer + container *testcontainer.MockContainer cfg *api.BootConfiguration debug bool expectedStatus container.Status expectedErr error }{ + { + // options.Container.InspectContainer() returning error + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { + state := container.State{} + state.Status = ephemeral.BootNullString + state.ExitCode = 0 + return state, ephemeral.ErrInvalidBootstrapCommand{} + }, + }, + cfg: testCfg, + debug: false, + expectedStatus: ephemeral.BootNullString, + expectedErr: ephemeral.ErrInvalidBootstrapCommand{}, + }, + { + // options.Container.GetContainerLogs() returns error + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { + state := container.State{} + state.Status = container.RunningContainerStatus + state.ExitCode = 1 + return state, nil + }, + MockGetContainerLogs: func() (io.ReadCloser, error) { + return nil, ephemeral.ErrInvalidBootstrapCommand{} + }, + }, + cfg: testCfg, + debug: false, + expectedStatus: ephemeral.BootNullString, + expectedErr: ephemeral.ErrInvalidBootstrapCommand{}, + }, { // Container running and no errors - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.RunningContainerStatus state.ExitCode = 0 @@ -105,14 +101,14 @@ func TestGetContainerStatus(t *testing.T) { }, { // Container running and with ContainerLoadEphemeralConfigError - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 1 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -126,14 +122,14 @@ func TestGetContainerStatus(t *testing.T) { }, { // Container running and with ContainerValidationEphemeralConfigError - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 2 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -147,14 +143,14 @@ func TestGetContainerStatus(t *testing.T) { }, { // Container running and with ContainerSetEnvVarsError - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 3 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -168,14 +164,14 @@ func TestGetContainerStatus(t *testing.T) { }, { // Container running and with ContainerUnknownCommandError - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 4 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -189,14 +185,14 @@ func TestGetContainerStatus(t *testing.T) { }, { // Container running and with ContainerCreationEphemeralFailedError - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 5 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -210,14 +206,14 @@ func TestGetContainerStatus(t *testing.T) { }, { // Container running and with ContainerDeletionEphemeralFailedError - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 6 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -231,14 +227,14 @@ func TestGetContainerStatus(t *testing.T) { }, { // Container running and with ContainerHelpCommandFailedError - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 7 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -252,14 +248,14 @@ func TestGetContainerStatus(t *testing.T) { }, { // Container running and with ContainerUnknownError - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 8 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -304,7 +300,7 @@ func TestWaitUntilContainerExitsOrTimesout(t *testing.T) { } tests := []struct { - container *mockContainer + container *testcontainer.MockContainer cfg *api.BootConfiguration debug bool maxRetries int @@ -312,14 +308,14 @@ func TestWaitUntilContainerExitsOrTimesout(t *testing.T) { }{ { // Test: container exits normally - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 0 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -330,14 +326,14 @@ func TestWaitUntilContainerExitsOrTimesout(t *testing.T) { }, { // Test: container times out - container: &mockContainer{ - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.RunningContainerStatus state.ExitCode = 0 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -346,6 +342,21 @@ func TestWaitUntilContainerExitsOrTimesout(t *testing.T) { maxRetries: 1, expectedErr: ephemeral.ErrNumberOfRetriesExceeded{}, }, + { + // Test: options.GetContainerStatus() returns error + container: &testcontainer.MockContainer{ + MockInspectContainer: func() (container.State, error) { + state := container.State{} + state.Status = ephemeral.BootNullString + state.ExitCode = 0 + return state, ephemeral.ErrInvalidBootstrapCommand{} + }, + }, + cfg: testCfg, + debug: false, + maxRetries: 10, + expectedErr: ephemeral.ErrInvalidBootstrapCommand{}, + }, } for _, tt := range tests { outBuf := &bytes.Buffer{} @@ -377,7 +388,7 @@ func TestCreateBootstrapContainer(t *testing.T) { } tests := []struct { - container *mockContainer + container *testcontainer.MockContainer cfg *api.BootConfiguration debug bool bootstrapCommand string @@ -385,16 +396,16 @@ func TestCreateBootstrapContainer(t *testing.T) { }{ { // Test: Create Ephemeral Cluster successful - container: &mockContainer{ - runCommand: func() error { return nil }, - rmContainer: func() error { return nil }, - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockRunCommand: func() error { return nil }, + MockRmContainer: func() error { return nil }, + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 0 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -405,16 +416,16 @@ func TestCreateBootstrapContainer(t *testing.T) { }, { // Test: Delete Ephemeral Cluster successful - container: &mockContainer{ - runCommand: func() error { return nil }, - rmContainer: func() error { return nil }, - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockRunCommand: func() error { return nil }, + MockRmContainer: func() error { return nil }, + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 0 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -425,16 +436,16 @@ func TestCreateBootstrapContainer(t *testing.T) { }, { // Test: Create Ephemeral Cluster exit with error - container: &mockContainer{ - runCommand: func() error { return nil }, - rmContainer: func() error { return nil }, - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockRunCommand: func() error { return nil }, + MockRmContainer: func() error { return nil }, + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 1 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -448,16 +459,16 @@ func TestCreateBootstrapContainer(t *testing.T) { }, { // Test: Delete Ephemeral Cluster exit with error - container: &mockContainer{ - runCommand: func() error { return nil }, - rmContainer: func() error { return nil }, - inspectContainer: func() (container.State, error) { + container: &testcontainer.MockContainer{ + MockRunCommand: func() error { return nil }, + MockRmContainer: func() error { return nil }, + MockInspectContainer: func() (container.State, error) { state := container.State{} state.Status = container.ExitedContainerStatus state.ExitCode = 1 return state, nil }, - getContainerLogs: func() (io.ReadCloser, error) { + MockGetContainerLogs: func() (io.ReadCloser, error) { return nil, nil }, }, @@ -469,6 +480,16 @@ func TestCreateBootstrapContainer(t *testing.T) { ErrMsg: ephemeral.ContainerLoadEphemeralConfigError, }, }, + { + // Test: options.Container.RunCommand() returns error + container: &testcontainer.MockContainer{ + MockRunCommand: func() error { return ephemeral.ErrBootstrapContainerRun{} }, + }, + cfg: testCfg, + bootstrapCommand: ephemeral.BootCmdCreate, + debug: false, + expectedErr: ephemeral.ErrBootstrapContainerRun{}, + }, } for _, tt := range tests { diff --git a/pkg/phase/client.go b/pkg/phase/client.go index 3d67b10cf..5d2e299a2 100644 --- a/pkg/phase/client.go +++ b/pkg/phase/client.go @@ -49,6 +49,9 @@ func DefaultExecutorRegistry() map[schema.GroupVersionKind]ifc.ExecutorFactory { if err := executors.RegisterContainerExecutor(execMap); err != nil { log.Fatal(ErrExecutorRegistration{ExecutorName: "generic-container", Err: err}) } + if err := executors.RegisterEphemeralExecutor(execMap); err != nil { + log.Fatal(ErrExecutorRegistration{ExecutorName: "ephemeral", Err: err}) + } return execMap } diff --git a/pkg/phase/executors/ephemeral.go b/pkg/phase/executors/ephemeral.go new file mode 100644 index 000000000..89a35edfc --- /dev/null +++ b/pkg/phase/executors/ephemeral.go @@ -0,0 +1,164 @@ +/* + 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 executors + +import ( + "context" + "io" + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" + + "opendev.org/airship/airshipctl/pkg/api/v1alpha1" + "opendev.org/airship/airshipctl/pkg/bootstrap/ephemeral" + "opendev.org/airship/airshipctl/pkg/container" + "opendev.org/airship/airshipctl/pkg/document" + "opendev.org/airship/airshipctl/pkg/errors" + "opendev.org/airship/airshipctl/pkg/events" + "opendev.org/airship/airshipctl/pkg/log" + "opendev.org/airship/airshipctl/pkg/phase/ifc" +) + +var _ ifc.Executor = &EphemeralExecutor{} + +// EphemeralExecutor contains resources for ephemeral executor +type EphemeralExecutor struct { + ExecutorBundle document.Bundle + ExecutorDocument document.Document + + BootConf *v1alpha1.BootConfiguration + Container container.Container +} + +// RegisterEphemeralExecutor adds executor to phase executor registry +func RegisterEphemeralExecutor(registry map[schema.GroupVersionKind]ifc.ExecutorFactory) error { + obj := v1alpha1.DefaultBootConfiguration() + gvks, _, err := v1alpha1.Scheme.ObjectKinds(obj) + if err != nil { + return err + } + registry[gvks[0]] = NewEphemeralExecutor + return nil +} + +// NewEphemeralExecutor creates instance of phase executor +func NewEphemeralExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) { + apiObj := &v1alpha1.BootConfiguration{} + + err := cfg.ExecutorDocument.ToAPIObject(apiObj, v1alpha1.Scheme) + if err != nil { + return nil, err + } + + return &EphemeralExecutor{ + ExecutorDocument: cfg.ExecutorDocument, + BootConf: apiObj, + }, nil +} + +// Run ephemeral as a phase runner +func (c *EphemeralExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) { + defer close(evtCh) + + evtCh <- events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapStart, + Message: "Processing Ephemeral cluster operation ...", + }) + + if opts.DryRun { + log.Print("Dryrun: bootstrap container command will be skipped") + evtCh <- events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapDryRun, + }) + return + } + + if c.Container == nil { + ctx := context.Background() + builder, err := container.NewContainer( + ctx, + c.BootConf.BootstrapContainer.ContainerRuntime, + c.BootConf.BootstrapContainer.Image) + if err != nil { + handleEphemeralError(evtCh, err) + return + } + c.Container = builder + } + + bootstrapOpts := ephemeral.BootstrapContainerOptions{ + Container: c.Container, + Cfg: c.BootConf, + Sleep: time.Sleep, + } + + evtCh <- events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapValidation, + Message: "Verifying executor manifest document ...", + }) + + err := bootstrapOpts.VerifyInputs() + if err != nil { + handleEphemeralError(evtCh, err) + return + } + + evtCh <- events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapRun, + Message: "Creating and starting the Bootstrap Container ...", + }) + + err = bootstrapOpts.CreateBootstrapContainer() + if err != nil { + handleEphemeralError(evtCh, err) + return + } + + evtCh <- events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapValidation, + Message: "Verifying generation of kubeconfig file ...", + }) + + err = bootstrapOpts.VerifyArtifacts() + if err != nil { + handleEphemeralError(evtCh, err) + return + } + + evtCh <- events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapEnd, + Message: "Ephemeral cluster operation has completed successfully and artifacts verified", + }) +} + +// Validate executor configuration and documents +func (c *EphemeralExecutor) Validate() error { + return errors.ErrNotImplemented{} +} + +// Render executor documents +func (c *EphemeralExecutor) Render(w io.Writer, _ ifc.RenderOptions) error { + log.Print("Ephemeral Executor Render() will be implemented later.") + return nil +} + +func handleEphemeralError(ch chan<- events.Event, err error) { + ch <- events.Event{ + Type: events.ErrorType, + ErrorEvent: events.ErrorEvent{ + Error: err, + }, + } +} diff --git a/pkg/phase/executors/ephemeral_test.go b/pkg/phase/executors/ephemeral_test.go new file mode 100644 index 000000000..6676347f1 --- /dev/null +++ b/pkg/phase/executors/ephemeral_test.go @@ -0,0 +1,214 @@ +/* + 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 executors_test + +import ( + "bytes" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/runtime/schema" + + "opendev.org/airship/airshipctl/pkg/api/v1alpha1" + "opendev.org/airship/airshipctl/pkg/bootstrap/ephemeral" + "opendev.org/airship/airshipctl/pkg/container" + "opendev.org/airship/airshipctl/pkg/document" + "opendev.org/airship/airshipctl/pkg/errors" + "opendev.org/airship/airshipctl/pkg/events" + "opendev.org/airship/airshipctl/pkg/phase/executors" + "opendev.org/airship/airshipctl/pkg/phase/ifc" + "opendev.org/airship/airshipctl/testutil" + testcontainer "opendev.org/airship/airshipctl/testutil/container" +) + +var ( + executorEphemeralDoc = ` +apiVersion: airshipit.org/v1alpha1 +kind: BootConfiguration +metadata: + name: ephemeral-az-genesis + labels: + airshipit.org/deploy-k8s: "false" +ephemeralCluster: + bootstrapCommand: create + configFilename: azure-config.yaml +bootstrapContainer: + containerRuntime: docker + image: quay.io/sshiba/capz-bootstrap:latest + volume: /home/esidshi/.airship:/kube + saveKubeconfigFileName: capz.kubeconfig +` +) + +// TestRegisterEphemeralExecutor - Unit testing function RegisterEphemeralExecutor() +func TestRegisterEphemeralExecutor(t *testing.T) { + registry := make(map[schema.GroupVersionKind]ifc.ExecutorFactory) + expectedGVK := schema.GroupVersionKind{ + Group: "airshipit.org", + Version: "v1alpha1", + Kind: "BootConfiguration", + } + err := executors.RegisterEphemeralExecutor(registry) + require.NoError(t, err) + + _, found := registry[expectedGVK] + assert.True(t, found) +} + +// TestNewEphemeralExecutor - Unit testing function NewExecutor() +func TestNewEphemeralExecutor(t *testing.T) { + execDoc, err := document.NewDocumentFromBytes([]byte(executorEphemeralDoc)) + require.NoError(t, err) + _, err = executors.NewEphemeralExecutor(ifc.ExecutorConfig{ExecutorDocument: execDoc}) + require.NoError(t, err) +} + +// TestExecutorEphemeralRun - Unit testing method (c *Executor) Run() +func TestExecutorEphemeralRun(t *testing.T) { + tempVol, cleanup := testutil.TempDir(t, "bootstrap-test") + defer cleanup(t) + + volBind := tempVol + ":/dst" + configFilename := "dummy-config.yaml" + configPath := filepath.Join(tempVol, configFilename) + + assert.NoError(t, testConfigFile(configPath), + ephemeral.ErrBootstrapContainerRun{}, + "Failed to create dummy config file") + + testCfg := &v1alpha1.BootConfiguration{ + BootstrapContainer: v1alpha1.BootstrapContainer{ + Volume: volBind, + ContainerRuntime: "docker", + Image: "quay.io/sshiba/capz-bootstrap:latest", + Kubeconfig: "dummy.kubeconfig", + }, + EphemeralCluster: v1alpha1.EphemeralCluster{ + BootstrapCommand: "create", + ConfigFilename: configFilename, + }, + } + + testCases := []struct { + name string + container *testcontainer.MockContainer + expectedEvt []events.Event + }{ + { + name: "Run bootstrap container successfully", + container: &testcontainer.MockContainer{ + MockRunCommand: func() error { return nil }, + MockRmContainer: func() error { return nil }, + MockInspectContainer: func() (container.State, error) { + state := container.State{} + state.Status = "exited" + state.ExitCode = 0 + return state, nil + }, + }, + expectedEvt: []events.Event{ + events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapStart, + }), + events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapValidation, + }), + events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapRun, + }), + events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapValidation, + }), + events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapEnd, + }), + }, + }, + { + name: "Run bootstrap container with Unknow Error", + container: &testcontainer.MockContainer{ + MockRunCommand: func() error { return ephemeral.ErrBootstrapContainerRun{} }, + MockRmContainer: func() error { return nil }, + }, + expectedEvt: []events.Event{ + events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapStart, + }), + events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapValidation, + }), + events.NewEvent().WithBootstrapEvent(events.BootstrapEvent{ + Operation: events.BootstrapRun, + }), + events.NewEvent().WithErrorEvent(events.ErrorEvent{ + Error: ephemeral.ErrBootstrapContainerRun{}, + }), + }, + }, + } + for _, test := range testCases { + tt := test + t.Run(tt.name, func(t *testing.T) { + executor := &executors.EphemeralExecutor{ + BootConf: testCfg, + Container: tt.container, + } + ch := make(chan events.Event) + go executor.Run(ch, ifc.RunOptions{}) + var actualEvt []events.Event + for evt := range ch { + if evt.Type == events.BootstrapType { + // Set message to empty string, so it's not compared + evt.BootstrapEvent.Message = "" + } + actualEvt = append(actualEvt, evt) + } + for i := range tt.expectedEvt { + // Fix timestamps for comparison + timeStamp := time.Time{} + tt.expectedEvt[i].Timestamp = timeStamp + actualEvt[i].Timestamp = timeStamp + } + assert.Equal(t, tt.expectedEvt, actualEvt) + }) + } +} + +func testConfigFile(path string) error { + _, err := os.Create(path) + return err +} + +// TestValidate - Unit testing function Validate() +func TestEphemeralValidate(t *testing.T) { + executor := &executors.EphemeralExecutor{} + + actualErr := executor.Validate() + assert.Equal(t, errors.ErrNotImplemented{}, actualErr) +} + +// TestEphemeralRender - Unit testing function Render() +func TestEphemeralRender(t *testing.T) { + executor := &executors.EphemeralExecutor{} + + writerReader := bytes.NewBuffer([]byte{}) + actualErr := executor.Render(writerReader, ifc.RenderOptions{}) + assert.Equal(t, nil, actualErr) +}