Bootstrap Ephemeral - Command methods

This patch set includes the methods supporting the bootstrap
container feature, which will be used by the BootConfiguration
executor, as part of "phase run" implementation.

The methods under command.go file are responsible for creating the
bootstrap container, monitor its state, etc.

Change-Id: I3f6b1a0bb7c7be7ac29487d09ae2ccd5e9f4bd87
This commit is contained in:
Sidney Shiba 2020-11-09 10:22:50 -06:00
parent f4e532e91a
commit fb49020d00
3 changed files with 966 additions and 0 deletions

View File

@ -0,0 +1,246 @@
/*
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 ephemeral
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
"opendev.org/airship/airshipctl/pkg/container"
"opendev.org/airship/airshipctl/pkg/log"
)
const (
// BootCmdCreate is the string for the command "create" Ephemeral cluster
BootCmdCreate = "create"
// BootCmdDelete is the string for the command "delete" Ephemeral cluster
BootCmdDelete = "delete"
// BootCmdHelp is the string for the command "help" for the Ephemeral cluster
BootCmdHelp = "help"
// BootVolumeSeparator is the string Container volume mount
BootVolumeSeparator = ":"
// BootNullString represents an empty string
BootNullString = ""
// BootHelpFilename is the help filename
BootHelpFilename = "help.txt"
// Bootstrap Environment Variables
envBootstrapCommand = "BOOTSTRAP_COMMAND"
envBootstrapConfig = "BOOTSTRAP_CONFIG"
envBootstrapVolume = "BOOTSTRAP_VOLUME"
)
var exitCodeMap = map[int]string{
1: ContainerLoadEphemeralConfigError,
2: ContainerValidationEphemeralConfigError,
3: ContainerSetEnvVarsError,
4: ContainerUnknownCommandError,
5: ContainerCreationEphemeralFailedError,
6: ContainerDeletionEphemeralFailedError,
7: ContainerHelpCommandFailedError,
8: ContainerUnknownError,
}
// BootstrapContainerOptions structure used by the executor
type BootstrapContainerOptions struct {
Container container.Container
Cfg *v1alpha1.BootConfiguration
Sleep func(d time.Duration)
// optional fields for verbose output
Debug bool
}
// VerifyInputs verify if all input data to the container is correct
func (options *BootstrapContainerOptions) VerifyInputs() error {
if options.Cfg.BootstrapContainer.Volume == "" {
return ErrInvalidInput{
What: MissingVolumeError,
}
}
if options.Cfg.BootstrapContainer.Image == "" {
return ErrInvalidInput{
What: MissingContainerImageError,
}
}
if options.Cfg.BootstrapContainer.ContainerRuntime == "" {
return ErrInvalidInput{
What: MissingContainerRuntimeError,
}
}
if options.Cfg.EphemeralCluster.ConfigFilename == "" {
return ErrInvalidInput{
What: MissingConfigError,
}
}
vols := strings.Split(options.Cfg.BootstrapContainer.Volume, ":")
switch {
case len(vols) == 1:
options.Cfg.BootstrapContainer.Volume = fmt.Sprintf("%s:%s", vols[0], vols[0])
case len(vols) > 2:
return ErrVolumeMalFormed{}
}
return nil
}
// GetContainerStatus returns the Bootstrap Container state
func (options *BootstrapContainerOptions) GetContainerStatus() (container.Status, error) {
// Check status of the container, e.g., "running"
state, err := options.Container.InspectContainer()
if err != nil {
return BootNullString, err
}
var exitCode int
exitCode = state.ExitCode
if exitCode > 0 {
reader, err := options.Container.GetContainerLogs()
if err != nil {
log.Printf("Error while trying to retrieve the container logs")
return BootNullString, err
}
containerError := ErrBootstrapContainerRun{}
containerError.ExitCode = exitCode
containerError.ErrMsg = exitCodeMap[exitCode]
if reader != nil {
logs := new(bytes.Buffer)
_, err = logs.ReadFrom(reader)
if err != nil {
return BootNullString, err
}
reader.Close()
containerError.StdErr = logs.String()
}
return state.Status, containerError
}
return state.Status, nil
}
// WaitUntilContainerExitsOrTimesout waits for the container to exit or time out
func (options *BootstrapContainerOptions) WaitUntilContainerExitsOrTimesout(
maxRetries int,
configFilename string,
bootstrapCommand string) error {
// Give 2 seconds before checking if container is still running
// This period should be enough to detect some initial errors thrown by the container
options.Sleep(2 * time.Second)
// Wait until container finished executing bootstrap of ephemeral cluster
status, err := options.GetContainerStatus()
if err != nil {
return err
}
if status == container.ExitedContainerStatus {
// bootstrap container command execution completed
return nil
}
for attempt := 1; attempt <= maxRetries; attempt++ {
log.Printf("Waiting for bootstrap container using %s config file to %s Ephemeral cluster (%d/%d)",
configFilename, bootstrapCommand, attempt, maxRetries)
// Wait for 15 seconds and check again bootstrap container state
options.Sleep(15 * time.Second)
status, err = options.GetContainerStatus()
if err != nil {
return err
}
if status == container.ExitedContainerStatus {
// bootstrap container command execution completed
return nil
}
}
return ErrNumberOfRetriesExceeded{}
}
// CreateBootstrapContainer creates a Bootstrap Container
func (options *BootstrapContainerOptions) CreateBootstrapContainer() error {
containerVolMount := options.Cfg.BootstrapContainer.Volume
vols := []string{containerVolMount}
log.Printf("Running default container command. Mounted dir: %s", vols)
bootstrapCommand := options.Cfg.EphemeralCluster.BootstrapCommand
configFilename := options.Cfg.EphemeralCluster.ConfigFilename
envVars := []string{
fmt.Sprintf("%s=%s", envBootstrapCommand, bootstrapCommand),
fmt.Sprintf("%s=%s", envBootstrapConfig, configFilename),
fmt.Sprintf("%s=%s", envBootstrapVolume, containerVolMount),
}
err := options.Container.RunCommand([]string{}, nil, vols, envVars)
if err != nil {
return err
}
maxRetries := 50
switch bootstrapCommand {
case BootCmdCreate:
// Wait until container finished executing bootstrap of ephemeral cluster
err = options.WaitUntilContainerExitsOrTimesout(maxRetries, configFilename, bootstrapCommand)
if err != nil {
log.Printf("Failed to create Ephemeral cluster using %s config file", configFilename)
return err
}
log.Printf("Ephemeral cluster created successfully using %s config file", configFilename)
case BootCmdDelete:
// Wait until container finished executing bootstrap of ephemeral cluster
err = options.WaitUntilContainerExitsOrTimesout(maxRetries, configFilename, bootstrapCommand)
if err != nil {
log.Printf("Failed to delete Ephemeral cluster using %s config file", configFilename)
return err
}
log.Printf("Ephemeral cluster deleted successfully using %s config file", configFilename)
case BootCmdHelp:
// Display Ephemeral Config file format for help
sepPos := strings.Index(containerVolMount, BootVolumeSeparator)
helpPath := filepath.Join(containerVolMount[:sepPos], BootHelpFilename)
// Display help.txt on stdout
data, err := ioutil.ReadFile(helpPath)
if err != nil {
log.Printf("File reading %s error: %s", helpPath, err)
return err
}
// Printing the help.txt content to stdout
fmt.Println(string(data))
// Delete help.txt file
err = os.Remove(helpPath)
if err != nil {
log.Printf("Could not delete %s", helpPath)
return err
}
default:
return ErrInvalidBootstrapCommand{}
}
log.Printf("Ephemeral cluster %s command completed successfully.", bootstrapCommand)
if !options.Debug {
log.Print("Removing bootstrap container.")
return options.Container.RmContainer()
}
return nil
}

View File

@ -0,0 +1,625 @@
/*
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 ephemeral_test
import (
"bytes"
"io"
"testing"
"time"
"github.com/stretchr/testify/assert"
api "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/log"
)
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{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "docker",
Image: "quay.io/dummy/dummy:latest",
Volume: "/dummy:/dummy",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "dummy.yaml",
},
}
tests := []struct {
container *mockContainer
cfg *api.BootConfiguration
debug bool
expectedStatus container.Status
expectedErr error
}{
{
// Container running and no errors
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.RunningContainerStatus
state.ExitCode = 0
return state, nil
},
},
cfg: testCfg,
debug: false,
expectedStatus: container.RunningContainerStatus,
expectedErr: nil,
},
{
// Container running and with ContainerLoadEphemeralConfigError
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 1
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
expectedStatus: container.ExitedContainerStatus,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 1,
ErrMsg: ephemeral.ContainerLoadEphemeralConfigError,
},
},
{
// Container running and with ContainerValidationEphemeralConfigError
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 2
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
expectedStatus: container.ExitedContainerStatus,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 2,
ErrMsg: ephemeral.ContainerValidationEphemeralConfigError,
},
},
{
// Container running and with ContainerSetEnvVarsError
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 3
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
expectedStatus: container.ExitedContainerStatus,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 3,
ErrMsg: ephemeral.ContainerSetEnvVarsError,
},
},
{
// Container running and with ContainerUnknownCommandError
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 4
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
expectedStatus: container.ExitedContainerStatus,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 4,
ErrMsg: ephemeral.ContainerUnknownCommandError,
},
},
{
// Container running and with ContainerCreationEphemeralFailedError
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 5
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
expectedStatus: container.ExitedContainerStatus,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 5,
ErrMsg: ephemeral.ContainerCreationEphemeralFailedError,
},
},
{
// Container running and with ContainerDeletionEphemeralFailedError
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 6
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
expectedStatus: container.ExitedContainerStatus,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 6,
ErrMsg: ephemeral.ContainerDeletionEphemeralFailedError,
},
},
{
// Container running and with ContainerHelpCommandFailedError
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 7
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
expectedStatus: container.ExitedContainerStatus,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 7,
ErrMsg: ephemeral.ContainerHelpCommandFailedError,
},
},
{
// Container running and with ContainerUnknownError
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 8
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
expectedStatus: container.ExitedContainerStatus,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 8,
ErrMsg: ephemeral.ContainerUnknownError,
},
},
}
for _, tt := range tests {
outBuf := &bytes.Buffer{}
log.Init(tt.debug, outBuf)
bootstrapOpts := ephemeral.BootstrapContainerOptions{
Container: tt.container,
Cfg: tt.cfg,
Sleep: func(_ time.Duration) {},
Debug: tt.debug,
}
actualStatus, actualErr := bootstrapOpts.GetContainerStatus()
assert.Equal(t, tt.expectedStatus, actualStatus)
assert.Equal(t, tt.expectedErr, actualErr)
}
}
// Unit test for the method waitUntilContainerExitsOrTimesout()
func TestWaitUntilContainerExitsOrTimesout(t *testing.T) {
testCfg := &api.BootConfiguration{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "docker",
Image: "quay.io/dummy/dummy:latest",
Volume: "/dummy:/dummy",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "dummy.yaml",
},
}
tests := []struct {
container *mockContainer
cfg *api.BootConfiguration
debug bool
maxRetries int
expectedErr error
}{
{
// Test: container exits normally
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 0
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
maxRetries: 10,
expectedErr: nil,
},
{
// Test: container times out
container: &mockContainer{
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.RunningContainerStatus
state.ExitCode = 0
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
debug: false,
maxRetries: 1,
expectedErr: ephemeral.ErrNumberOfRetriesExceeded{},
},
}
for _, tt := range tests {
outBuf := &bytes.Buffer{}
log.Init(tt.debug, outBuf)
bootstrapOpts := ephemeral.BootstrapContainerOptions{
Container: tt.container,
Cfg: tt.cfg,
Sleep: func(_ time.Duration) {},
Debug: tt.debug,
}
actualErr := bootstrapOpts.WaitUntilContainerExitsOrTimesout(tt.maxRetries, "dummy-config.yaml", "dummy")
assert.Equal(t, tt.expectedErr, actualErr)
}
}
// Unit test for the method createBootstrapContainer()
func TestCreateBootstrapContainer(t *testing.T) {
testCfg := &api.BootConfiguration{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "docker",
Image: "quay.io/dummy/dummy:latest",
Volume: "/dummy:/dummy",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "dummy.yaml",
},
}
tests := []struct {
container *mockContainer
cfg *api.BootConfiguration
debug bool
bootstrapCommand string
expectedErr error
}{
{
// Test: Create Ephemeral Cluster successful
container: &mockContainer{
runCommand: func() error { return nil },
rmContainer: func() error { return nil },
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 0
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
bootstrapCommand: ephemeral.BootCmdCreate,
debug: false,
expectedErr: nil,
},
{
// Test: Delete Ephemeral Cluster successful
container: &mockContainer{
runCommand: func() error { return nil },
rmContainer: func() error { return nil },
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 0
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
bootstrapCommand: ephemeral.BootCmdDelete,
debug: false,
expectedErr: nil,
},
{
// Test: Create Ephemeral Cluster exit with error
container: &mockContainer{
runCommand: func() error { return nil },
rmContainer: func() error { return nil },
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 1
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
bootstrapCommand: ephemeral.BootCmdCreate,
debug: false,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 1,
ErrMsg: ephemeral.ContainerLoadEphemeralConfigError,
},
},
{
// Test: Delete Ephemeral Cluster exit with error
container: &mockContainer{
runCommand: func() error { return nil },
rmContainer: func() error { return nil },
inspectContainer: func() (container.State, error) {
state := container.State{}
state.Status = container.ExitedContainerStatus
state.ExitCode = 1
return state, nil
},
getContainerLogs: func() (io.ReadCloser, error) {
return nil, nil
},
},
cfg: testCfg,
bootstrapCommand: ephemeral.BootCmdDelete,
debug: false,
expectedErr: ephemeral.ErrBootstrapContainerRun{
ExitCode: 1,
ErrMsg: ephemeral.ContainerLoadEphemeralConfigError,
},
},
}
for _, tt := range tests {
tt.cfg.EphemeralCluster.BootstrapCommand = tt.bootstrapCommand
bootstrapOpts := ephemeral.BootstrapContainerOptions{
Container: tt.container,
Cfg: tt.cfg,
Sleep: func(_ time.Duration) {},
Debug: tt.debug,
}
actualErr := bootstrapOpts.CreateBootstrapContainer()
assert.Equal(t, tt.expectedErr, actualErr)
}
}
func TestVerifyInputs(t *testing.T) {
tests := []struct {
name string
cfg *api.BootConfiguration
expectedErr error
}{
{
name: "Verify successful",
cfg: &api.BootConfiguration{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "docker",
Image: "quay.io/dummy/dummy:latest",
Volume: "/dummy:/dummy",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "dummy.yaml",
},
},
expectedErr: nil,
},
{
name: "Missing Ephemeral cluster config file",
cfg: &api.BootConfiguration{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "docker",
Image: "quay.io/dummy/dummy:latest",
Volume: "/dummy:/dummy",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "",
},
},
expectedErr: ephemeral.ErrInvalidInput{
What: ephemeral.MissingConfigError,
},
},
{
name: "Missing Volume mount for the Bootstrap container",
cfg: &api.BootConfiguration{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "docker",
Image: "quay.io/dummy/dummy:latest",
Volume: "",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "dummy.yaml",
},
},
expectedErr: ephemeral.ErrInvalidInput{
What: ephemeral.MissingVolumeError,
},
},
{
name: "Bootstrap container Volume mount mal formed 1",
cfg: &api.BootConfiguration{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "docker",
Image: "quay.io/dummy/dummy:latest",
Volume: "/dummy",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "dummy.yaml",
},
},
expectedErr: nil,
},
{
name: "Bootstrap container Volume mount mal formed 2",
cfg: &api.BootConfiguration{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "docker",
Image: "quay.io/dummy/dummy:latest",
Volume: "/dummy:/dummy:/dummy",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "dummy.yaml",
},
},
expectedErr: ephemeral.ErrVolumeMalFormed{},
},
{
name: "Bootstrap container image missing",
cfg: &api.BootConfiguration{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "docker",
Image: "",
Volume: "/dummy:/dummy:/dummy",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "dummy.yaml",
},
},
expectedErr: ephemeral.ErrInvalidInput{
What: ephemeral.MissingContainerImageError,
},
},
{
name: "Bootstrap container runtime missing",
cfg: &api.BootConfiguration{
BootstrapContainer: api.BootstrapContainer{
ContainerRuntime: "",
Image: "quay.io/dummy/dummy:latest",
Volume: "/dummy:/dummy:/dummy",
Kubeconfig: "dummy.kubeconfig",
},
EphemeralCluster: api.EphemeralCluster{
BootstrapCommand: "dummy",
ConfigFilename: "dummy.yaml",
},
},
expectedErr: ephemeral.ErrInvalidInput{
What: ephemeral.MissingContainerRuntimeError,
},
},
}
for _, tt := range tests {
tt := tt
bootstrapOpts := ephemeral.BootstrapContainerOptions{
Cfg: tt.cfg,
}
t.Run(tt.name, func(subTest *testing.T) {
actualErr := bootstrapOpts.VerifyInputs()
assert.Equal(subTest, tt.expectedErr, actualErr)
})
}
}

View File

@ -0,0 +1,95 @@
/*
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 ephemeral
import "fmt"
const (
// ContainerLoadEphemeralConfigError ...
ContainerLoadEphemeralConfigError = "Container error: Failed to load Ephemeral cluster config file"
// ContainerValidationEphemeralConfigError ...
ContainerValidationEphemeralConfigError = "Container error: Ephemeral cluster configuration file validation failed"
// ContainerSetEnvVarsError ...
ContainerSetEnvVarsError = "Container error: Failed to set environment variables"
// ContainerUnknownCommandError ...
ContainerUnknownCommandError = "Container error: Container unknown command"
// ContainerCreationEphemeralFailedError ...
ContainerCreationEphemeralFailedError = "Container error: Creation of Ephemeral cluster failed"
// ContainerDeletionEphemeralFailedError ...
ContainerDeletionEphemeralFailedError = "Container error: Deletion of Ephemeral cluster failed"
// ContainerHelpCommandFailedError ...
ContainerHelpCommandFailedError = "Container error: Help command failed"
// ContainerUnknownError ...
ContainerUnknownError = "Container error: Unknown error code"
// NumberOfRetriesExceededError ...
NumberOfRetriesExceededError = "number of retries exceeded"
// MissingConfigError ...
MissingConfigError = "Missing Ephemeral Cluster ConfigFilename"
// MissingVolumeError ...
MissingVolumeError = "Must specify volume bind for Bootstrap builder container"
// VolumeMalFormedError ...
VolumeMalFormedError = "Bootstrap Container volume mount is mal formed"
// MissingContainerImageError ...
MissingContainerImageError = "Missing Bootstrap Container image"
// MissingContainerRuntimeError ...
MissingContainerRuntimeError = "Missing Container runtime information"
// InvalidBootstrapCommandError ...
InvalidBootstrapCommandError = "Invalid Bootstrap Container command"
)
// ErrBootstrapContainerRun is returned when there is an error executing the container
type ErrBootstrapContainerRun struct {
ExitCode int
StdErr string
ErrMsg string
}
func (e ErrBootstrapContainerRun) Error() string {
return fmt.Sprintf("Error from boostrap container: %s exit Code: %d\nContainer Logs: %s",
e.ErrMsg, e.ExitCode, e.StdErr)
}
// ErrNumberOfRetriesExceeded is returned when number of retries have exceeded a limit
type ErrNumberOfRetriesExceeded struct {
}
func (e ErrNumberOfRetriesExceeded) Error() string {
return NumberOfRetriesExceededError
}
// ErrInvalidInput is returned when invalid values were passed to the Bootstrap Container
type ErrInvalidInput struct {
What string
}
func (e ErrInvalidInput) Error() string {
return fmt.Sprintf("Invalid Bootstrap Container input: %s", e.What)
}
// ErrVolumeMalFormed is returned when error volume mount is mal formed
type ErrVolumeMalFormed struct {
}
func (e ErrVolumeMalFormed) Error() string {
return VolumeMalFormedError
}
// ErrInvalidBootstrapCommand is returned when invalid command is invoked
type ErrInvalidBootstrapCommand struct {
}
func (e ErrInvalidBootstrapCommand) Error() string {
return InvalidBootstrapCommandError
}