Move bootstrapInfo config section to manifests

This is a part of two activities
  * Refactor config and store all parameters to document model
  * Implement everything as phases (which are supposed to be purely document driven)

 This patch removes bootstrapInfo section from the airshipctl config and
 makes these to commands
  * airshipctl baremetal isogen
  * airthipctl baremetal remotedirect
 to take necessary parameters from documents

 We introduce two airship API kinds ImageGenerator and RemoteDirect.
 Instead of storing config parameters in cm/secrets we use these two
 API objects.

Relates-To: #246
Closes: #254
Change-Id: I42903c45dce1c73da184c07277fec76fd88c700f
This commit is contained in:
Vladimir Kozhukalov 2020-05-22 13:57:41 +03:00
parent 61633d30fc
commit e9c8425c30
47 changed files with 747 additions and 339 deletions

View File

@ -1,6 +1,5 @@
Cluster: clusterBar Cluster: clusterBar
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterBar_ephemeral clusterKubeconf: clusterBar_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -10,7 +9,6 @@ server: ""
Cluster: clusterBar Cluster: clusterBar
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterBar_target clusterKubeconf: clusterBar_target
managementConfiguration: "" managementConfiguration: ""
@ -20,7 +18,6 @@ server: ""
Cluster: clusterBaz Cluster: clusterBaz
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterBaz_ephemeral clusterKubeconf: clusterBaz_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -30,7 +27,6 @@ server: ""
Cluster: clusterBaz Cluster: clusterBaz
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterBaz_target clusterKubeconf: clusterBaz_target
managementConfiguration: "" managementConfiguration: ""
@ -40,7 +36,6 @@ server: ""
Cluster: clusterFoo Cluster: clusterFoo
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_ephemeral clusterKubeconf: clusterFoo_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -50,7 +45,6 @@ server: ""
Cluster: clusterFoo Cluster: clusterFoo
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_target clusterKubeconf: clusterFoo_target
managementConfiguration: "" managementConfiguration: ""

View File

@ -1,6 +1,5 @@
Cluster: clusterBar Cluster: clusterBar
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterBar_ephemeral clusterKubeconf: clusterBar_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -10,7 +9,6 @@ server: ""
Cluster: clusterBar Cluster: clusterBar
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterBar_target clusterKubeconf: clusterBar_target
managementConfiguration: "" managementConfiguration: ""
@ -20,7 +18,6 @@ server: ""
Cluster: clusterBaz Cluster: clusterBaz
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterBaz_ephemeral clusterKubeconf: clusterBaz_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -30,7 +27,6 @@ server: ""
Cluster: clusterBaz Cluster: clusterBaz
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterBaz_target clusterKubeconf: clusterBaz_target
managementConfiguration: "" managementConfiguration: ""
@ -40,7 +36,6 @@ server: ""
Cluster: clusterFoo Cluster: clusterFoo
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_ephemeral clusterKubeconf: clusterFoo_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -50,7 +45,6 @@ server: ""
Cluster: clusterFoo Cluster: clusterFoo
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_target clusterKubeconf: clusterFoo_target
managementConfiguration: "" managementConfiguration: ""

View File

@ -1,6 +1,5 @@
Cluster: clusterFoo Cluster: clusterFoo
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_ephemeral clusterKubeconf: clusterFoo_ephemeral
managementConfiguration: "" managementConfiguration: ""

View File

@ -1,6 +1,5 @@
Cluster: clusterBar Cluster: clusterBar
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterBar_ephemeral clusterKubeconf: clusterBar_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -10,7 +9,6 @@ server: ""
Cluster: clusterBar Cluster: clusterBar
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterBar_target clusterKubeconf: clusterBar_target
managementConfiguration: "" managementConfiguration: ""
@ -20,7 +18,6 @@ server: ""
Cluster: clusterBaz Cluster: clusterBaz
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterBaz_ephemeral clusterKubeconf: clusterBaz_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -30,7 +27,6 @@ server: ""
Cluster: clusterBaz Cluster: clusterBaz
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterBaz_target clusterKubeconf: clusterBaz_target
managementConfiguration: "" managementConfiguration: ""
@ -40,7 +36,6 @@ server: ""
Cluster: clusterFoo Cluster: clusterFoo
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_ephemeral clusterKubeconf: clusterFoo_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -50,7 +45,6 @@ server: ""
Cluster: clusterFoo Cluster: clusterFoo
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_target clusterKubeconf: clusterFoo_target
managementConfiguration: "" managementConfiguration: ""

View File

@ -1,6 +1,5 @@
Cluster: clusterBar Cluster: clusterBar
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterBar_ephemeral clusterKubeconf: clusterBar_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -10,7 +9,6 @@ server: ""
Cluster: clusterBar Cluster: clusterBar
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterBar_target clusterKubeconf: clusterBar_target
managementConfiguration: "" managementConfiguration: ""
@ -20,7 +18,6 @@ server: ""
Cluster: clusterBaz Cluster: clusterBaz
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterBaz_ephemeral clusterKubeconf: clusterBaz_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -30,7 +27,6 @@ server: ""
Cluster: clusterBaz Cluster: clusterBaz
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterBaz_target clusterKubeconf: clusterBaz_target
managementConfiguration: "" managementConfiguration: ""
@ -40,7 +36,6 @@ server: ""
Cluster: clusterFoo Cluster: clusterFoo
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_ephemeral clusterKubeconf: clusterFoo_ephemeral
managementConfiguration: "" managementConfiguration: ""
@ -50,7 +45,6 @@ server: ""
Cluster: clusterFoo Cluster: clusterFoo
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_target clusterKubeconf: clusterFoo_target
managementConfiguration: "" managementConfiguration: ""

View File

@ -1,6 +1,5 @@
Cluster: clusterFoo Cluster: clusterFoo
target: target:
bootstrapInfo: ""
clusterKubeconf: clusterFoo_target clusterKubeconf: clusterFoo_target
managementConfiguration: "" managementConfiguration: ""

View File

@ -0,0 +1,14 @@
apiVersion: airshipit.org/v1alpha1
kind: ImageConfiguration
metadata:
name: default
labels:
airshipit.org/deploy-k8s: "false"
builder:
networkConfigFileName: network-config
outputMetadataFileName: output-metadata.yaml
userDataFileName: user-data
container:
containerRuntime: docker
image: quay.io/airshipit/isogen:latest-ubuntu_focal
volume: /srv/iso:/config

View File

@ -1,2 +1,4 @@
resources: resources:
- secret.yaml - secret.yaml
- image_configuration.yaml
- remote_direct_configuration.yaml

View File

@ -0,0 +1,7 @@
apiVersion: airshipit.org/v1alpha1
kind: RemoteDirectConfiguration
metadata:
name: default
labels:
airshipit.org/deploy-k8s: "false"
isoUrl: http://localhost:8099/ubuntu-focal.iso

View File

@ -46,6 +46,8 @@ func init() {
&PhasePlan{}, &PhasePlan{},
&KubeConfig{}, &KubeConfig{},
&KubernetesApply{}, &KubernetesApply{},
&ImageConfiguration{},
&RemoteDirectConfiguration{},
) )
_ = AddToScheme(Scheme) //nolint:errcheck _ = AddToScheme(Scheme) //nolint:errcheck
} }

View File

@ -1,6 +1,4 @@
/* /*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
@ -14,21 +12,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package config package v1alpha1
import "sigs.k8s.io/yaml" import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Bootstrap holds configurations for bootstrap steps // Container structure contains parameters related to Docker runtime, used for building image
type Bootstrap struct {
// Configuration parameters for container
Container *Container `json:"container,omitempty"`
// Configuration parameters for ISO builder
Builder *Builder `json:"builder,omitempty"`
// Configuration parameters for ephemeral node remote management
RemoteDirect *RemoteDirect `json:"remoteDirect,omitempty"`
}
// Container parameters
type Container struct { type Container struct {
// Container volume directory binding. // Container volume directory binding.
Volume string `json:"volume,omitempty"` Volume string `json:"volume,omitempty"`
@ -38,7 +28,7 @@ type Container struct {
ContainerRuntime string `json:"containerRuntime,omitempty"` ContainerRuntime string `json:"containerRuntime,omitempty"`
} }
// Builder parameters // Builder structure defines metadata files (including Cloud Init metadata) used for image
type Builder struct { type Builder struct {
// Cloud Init user-data file name placed to the container volume root // Cloud Init user-data file name placed to the container volume root
UserDataFileName string `json:"userDataFileName,omitempty"` UserDataFileName string `json:"userDataFileName,omitempty"`
@ -48,35 +38,12 @@ type Builder struct {
OutputMetadataFileName string `json:"outputMetadataFileName,omitempty"` OutputMetadataFileName string `json:"outputMetadataFileName,omitempty"`
} }
// RemoteDirect configuration options // ImageConfiguration structure is inherited from apimachinery TypeMeta and ObjectMeta and is a top level
type RemoteDirect struct { // configuration structure for building image
// IsoURL specifies url to download ISO image for ephemeral node type ImageConfiguration struct {
IsoURL string `json:"isoUrl,omitempty"` metav1.TypeMeta `json:",inline"`
} metav1.ObjectMeta `json:"metadata,omitempty"`
// Bootstrap functions Container *Container `json:"container,omitempty"`
func (b *Bootstrap) String() string { Builder *Builder `json:"builder,omitempty"`
yamlData, err := yaml.Marshal(&b)
if err != nil {
return ""
}
return string(yamlData)
}
// String returns Container object in a serialized string format
func (c *Container) String() string {
yamlData, err := yaml.Marshal(&c)
if err != nil {
return ""
}
return string(yamlData)
}
// String returns Builder object in a serialized string format
func (b *Builder) String() string {
yamlData, err := yaml.Marshal(&b)
if err != nil {
return ""
}
return string(yamlData)
} }

View File

@ -0,0 +1,29 @@
/*
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
http://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 v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// RemoteDirectConfiguration structure is inherited from apimachinery TypeMeta and ObjectMeta structures
// and defines parameters used to bootstrap the ephemeral node during the remote direct
type RemoteDirectConfiguration struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// IsoURL specifies url to download ISO image for ephemeral node
IsoURL string `json:"isoUrl,omitempty"`
}

View File

@ -345,3 +345,85 @@ func (in *Provider) DeepCopy() *Provider {
in.DeepCopyInto(out) in.DeepCopyInto(out)
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImageConfiguration) DeepCopyInto(out *ImageConfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Container.DeepCopyInto(out.Container)
in.Builder.DeepCopyInto(out.Builder)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Phase.
func (in *ImageConfiguration) DeepCopy() *ImageConfiguration {
if in == nil {
return nil
}
out := new(ImageConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ImageConfiguration) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Container) DeepCopyInto(out *Container) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhaseConfig.
func (in *Container) DeepCopy() *Container {
if in == nil {
return nil
}
out := new(Container)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Builder) DeepCopyInto(out *Builder) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhaseConfig.
func (in *Builder) DeepCopy() *Builder {
if in == nil {
return nil
}
out := new(Builder)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RemoteDirectConfiguration) DeepCopyInto(out *RemoteDirectConfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Phase.
func (in *RemoteDirectConfiguration) DeepCopy() *RemoteDirectConfiguration {
if in == nil {
return nil
}
out := new(RemoteDirectConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RemoteDirectConfiguration) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -21,6 +21,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
api "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
"opendev.org/airship/airshipctl/pkg/bootstrap/cloudinit" "opendev.org/airship/airshipctl/pkg/bootstrap/cloudinit"
"opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/container" "opendev.org/airship/airshipctl/pkg/container"
@ -43,17 +44,6 @@ func GenerateBootstrapIso(settings *environment.AirshipCTLSettings) error {
return err return err
} }
cfg, err := globalConf.CurrentContextBootstrapInfo()
if err != nil {
return err
}
if err = verifyInputs(cfg); err != nil {
return err
}
// TODO (dukov) replace with the appropriate function once it's available
// in document module
root, err := globalConf.CurrentContextEntryPoint(config.BootstrapPhase) root, err := globalConf.CurrentContextEntryPoint(config.BootstrapPhase)
if err != nil { if err != nil {
return err return err
@ -63,23 +53,41 @@ func GenerateBootstrapIso(settings *environment.AirshipCTLSettings) error {
return err return err
} }
log.Print("Creating ISO builder container") imageConfiguration := &api.ImageConfiguration{}
builder, err := container.NewContainer( selector, err := document.NewSelector().ByObject(imageConfiguration, api.Scheme)
&ctx, cfg.Container.ContainerRuntime, if err != nil {
cfg.Container.Image) return err
}
doc, err := docBundle.SelectOne(selector)
if err != nil { if err != nil {
return err return err
} }
err = generateBootstrapIso(docBundle, builder, cfg, log.DebugEnabled()) err = doc.ToAPIObject(imageConfiguration, api.Scheme)
if err != nil {
return err
}
if err = verifyInputs(imageConfiguration); err != nil {
return err
}
log.Print("Creating ISO builder container")
builder, err := container.NewContainer(
&ctx, imageConfiguration.Container.ContainerRuntime,
imageConfiguration.Container.Image)
if err != nil {
return err
}
err = generateBootstrapIso(docBundle, builder, doc, imageConfiguration, log.DebugEnabled())
if err != nil { if err != nil {
return err return err
} }
log.Print("Checking artifacts") log.Print("Checking artifacts")
return verifyArtifacts(cfg) return verifyArtifacts(imageConfiguration)
} }
func verifyInputs(cfg *config.Bootstrap) error { func verifyInputs(cfg *api.ImageConfiguration) error {
if cfg.Container.Volume == "" { if cfg.Container.Volume == "" {
return config.ErrMissingConfig{ return config.ErrMissingConfig{
What: "Must specify volume bind for ISO builder container", What: "Must specify volume bind for ISO builder container",
@ -104,19 +112,22 @@ func verifyInputs(cfg *config.Bootstrap) error {
return nil return nil
} }
func getContainerCfg(cfg *config.Bootstrap, userData []byte, netConf []byte) map[string][]byte { func getContainerCfg(
cfg *api.ImageConfiguration,
builderCfgYaml []byte,
userData []byte,
netConf []byte,
) map[string][]byte {
hostVol := strings.Split(cfg.Container.Volume, ":")[0] hostVol := strings.Split(cfg.Container.Volume, ":")[0]
fls := make(map[string][]byte) fls := make(map[string][]byte)
fls[filepath.Join(hostVol, cfg.Builder.UserDataFileName)] = userData fls[filepath.Join(hostVol, cfg.Builder.UserDataFileName)] = userData
fls[filepath.Join(hostVol, cfg.Builder.NetworkConfigFileName)] = netConf fls[filepath.Join(hostVol, cfg.Builder.NetworkConfigFileName)] = netConf
// TODO (dukov) Get rid of this ugly conversion byte -> string -> byte fls[filepath.Join(hostVol, builderConfigFileName)] = builderCfgYaml
builderData := []byte(cfg.String())
fls[filepath.Join(hostVol, builderConfigFileName)] = builderData
return fls return fls
} }
func verifyArtifacts(cfg *config.Bootstrap) error { func verifyArtifacts(cfg *api.ImageConfiguration) error {
hostVol := strings.Split(cfg.Container.Volume, ":")[0] hostVol := strings.Split(cfg.Container.Volume, ":")[0]
metadataPath := filepath.Join(hostVol, cfg.Builder.OutputMetadataFileName) metadataPath := filepath.Join(hostVol, cfg.Builder.OutputMetadataFileName)
_, err := os.Stat(metadataPath) _, err := os.Stat(metadataPath)
@ -126,7 +137,8 @@ func verifyArtifacts(cfg *config.Bootstrap) error {
func generateBootstrapIso( func generateBootstrapIso(
docBundle document.Bundle, docBundle document.Bundle,
builder container.Container, builder container.Container,
cfg *config.Bootstrap, doc document.Document,
cfg *api.ImageConfiguration,
debug bool, debug bool,
) error { ) error {
cntVol := strings.Split(cfg.Container.Volume, ":")[1] cntVol := strings.Split(cfg.Container.Volume, ":")[1]
@ -136,7 +148,12 @@ func generateBootstrapIso(
return err return err
} }
fls := getContainerCfg(cfg, userData, netConf) builderCfgYaml, err := doc.AsYAML()
if err != nil {
return err
}
fls := getContainerCfg(cfg, builderCfgYaml, userData, netConf)
if err = util.WriteFiles(fls, 0600); err != nil { if err = util.WriteFiles(fls, 0600); err != nil {
return err return err
} }

View File

@ -21,9 +21,12 @@ import (
"strings" "strings"
"testing" "testing"
"opendev.org/airship/airshipctl/pkg/environment"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
api "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
"opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/pkg/log"
@ -66,17 +69,26 @@ func TestBootstrapIso(t *testing.T) {
defer cleanup(t) defer cleanup(t)
volBind := tempVol + ":/dst" volBind := tempVol + ":/dst"
testErr := fmt.Errorf("testErr") testErr := fmt.Errorf("TestErr")
testCfg := &config.Bootstrap{ testCfg := &api.ImageConfiguration{
Container: &config.Container{ Container: &api.Container{
Volume: volBind, Volume: volBind,
ContainerRuntime: "docker", ContainerRuntime: "docker",
}, },
Builder: &config.Builder{ Builder: &api.Builder{
UserDataFileName: "user-data", UserDataFileName: "user-data",
NetworkConfigFileName: "net-conf", NetworkConfigFileName: "net-conf",
}, },
} }
testDoc := &MockDocument{
MockAsYAML: func() ([]byte, error) { return []byte("TESTDOC"), nil },
}
testBuilder := &mockContainer{
runCommand: func() error { return nil },
getID: func() string { return "TESTID" },
rmContainer: func() error { return nil },
}
expOut := []string{ expOut := []string{
"Creating cloud-init for ephemeral K8s", "Creating cloud-init for ephemeral K8s",
fmt.Sprintf("Running default container command. Mounted dir: [%s]", volBind), fmt.Sprintf("Running default container command. Mounted dir: [%s]", volBind),
@ -87,7 +99,8 @@ func TestBootstrapIso(t *testing.T) {
tests := []struct { tests := []struct {
builder *mockContainer builder *mockContainer
cfg *config.Bootstrap cfg *api.ImageConfiguration
doc *MockDocument
debug bool debug bool
expectedOut []string expectedOut []string
expectedErr error expectedErr error
@ -97,16 +110,15 @@ func TestBootstrapIso(t *testing.T) {
runCommand: func() error { return testErr }, runCommand: func() error { return testErr },
}, },
cfg: testCfg, cfg: testCfg,
doc: testDoc,
debug: false, debug: false,
expectedOut: []string{expOut[0], expOut[1]}, expectedOut: []string{expOut[0], expOut[1]},
expectedErr: testErr, expectedErr: testErr,
}, },
{ {
builder: &mockContainer{ builder: testBuilder,
runCommand: func() error { return nil },
getID: func() string { return "TESTID" },
},
cfg: testCfg, cfg: testCfg,
doc: testDoc,
debug: true, debug: true,
expectedOut: []string{expOut[0], expOut[1], expOut[2], expOut[3]}, expectedOut: []string{expOut[0], expOut[1], expOut[2], expOut[3]},
expectedErr: nil, expectedErr: nil,
@ -118,16 +130,27 @@ func TestBootstrapIso(t *testing.T) {
rmContainer: func() error { return testErr }, rmContainer: func() error { return testErr },
}, },
cfg: testCfg, cfg: testCfg,
doc: testDoc,
debug: false, debug: false,
expectedOut: []string{expOut[0], expOut[1], expOut[2], expOut[4]}, expectedOut: []string{expOut[0], expOut[1], expOut[2], expOut[4]},
expectedErr: testErr, expectedErr: testErr,
}, },
{
builder: testBuilder,
cfg: testCfg,
doc: &MockDocument{
MockAsYAML: func() ([]byte, error) { return nil, testErr },
},
debug: false,
expectedOut: []string{expOut[0]},
expectedErr: testErr,
},
} }
for _, tt := range tests { for _, tt := range tests {
outBuf := &bytes.Buffer{} outBuf := &bytes.Buffer{}
log.Init(tt.debug, outBuf) log.Init(tt.debug, outBuf)
actualErr := generateBootstrapIso(bundle, tt.builder, tt.cfg, tt.debug) actualErr := generateBootstrapIso(bundle, tt.builder, tt.doc, tt.cfg, tt.debug)
actualOut := outBuf.String() actualOut := outBuf.String()
for _, line := range tt.expectedOut { for _, line := range tt.expectedOut {
@ -144,14 +167,14 @@ func TestVerifyInputs(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
cfg *config.Bootstrap cfg *api.ImageConfiguration
args []string args []string
expectedErr error expectedErr error
}{ }{
{ {
name: "missing-container-field", name: "missing-container-field",
cfg: &config.Bootstrap{ cfg: &api.ImageConfiguration{
Container: &config.Container{}, Container: &api.Container{},
}, },
expectedErr: config.ErrMissingConfig{ expectedErr: config.ErrMissingConfig{
What: "Must specify volume bind for ISO builder container", What: "Must specify volume bind for ISO builder container",
@ -159,11 +182,11 @@ func TestVerifyInputs(t *testing.T) {
}, },
{ {
name: "missing-filenames", name: "missing-filenames",
cfg: &config.Bootstrap{ cfg: &api.ImageConfiguration{
Container: &config.Container{ Container: &api.Container{
Volume: tempVol + ":/dst", Volume: tempVol + ":/dst",
}, },
Builder: &config.Builder{}, Builder: &api.Builder{},
}, },
expectedErr: config.ErrMissingConfig{ expectedErr: config.ErrMissingConfig{
What: "UserDataFileName or NetworkConfigFileName are not specified in ISO builder config", What: "UserDataFileName or NetworkConfigFileName are not specified in ISO builder config",
@ -171,11 +194,11 @@ func TestVerifyInputs(t *testing.T) {
}, },
{ {
name: "invalid-host-path", name: "invalid-host-path",
cfg: &config.Bootstrap{ cfg: &api.ImageConfiguration{
Container: &config.Container{ Container: &api.Container{
Volume: tempVol + ":/dst:/dst1", Volume: tempVol + ":/dst:/dst1",
}, },
Builder: &config.Builder{ Builder: &api.Builder{
UserDataFileName: "user-data", UserDataFileName: "user-data",
NetworkConfigFileName: "net-conf", NetworkConfigFileName: "net-conf",
}, },
@ -186,11 +209,11 @@ func TestVerifyInputs(t *testing.T) {
}, },
{ {
name: "success", name: "success",
cfg: &config.Bootstrap{ cfg: &api.ImageConfiguration{
Container: &config.Container{ Container: &api.Container{
Volume: tempVol, Volume: tempVol,
}, },
Builder: &config.Builder{ Builder: &api.Builder{
UserDataFileName: "user-data", UserDataFileName: "user-data",
NetworkConfigFileName: "net-conf", NetworkConfigFileName: "net-conf",
}, },
@ -207,3 +230,90 @@ func TestVerifyInputs(t *testing.T) {
}) })
} }
} }
func TestGenerateBootstrapIso(t *testing.T) {
t.Run("EnsureCompleteError", func(t *testing.T) {
settings := &environment.AirshipCTLSettings{
Debug: false,
AirshipConfigPath: "testdata/config/config",
KubeConfigPath: "testdata/config/kubeconfig",
Config: &config.Config{},
}
expectedErr := config.ErrMissingConfig{What: "Current Context is not defined"}
settings.InitConfig()
settings.Config.CurrentContext = ""
actualErr := GenerateBootstrapIso(settings)
assert.Equal(t, expectedErr, actualErr)
})
t.Run("ContextEntryPointError", func(t *testing.T) {
settings := &environment.AirshipCTLSettings{
Debug: false,
AirshipConfigPath: "testdata/config/config",
KubeConfigPath: "testdata/config/kubeconfig",
Config: &config.Config{},
}
expectedErr := config.ErrMissingPrimaryRepo{}
settings.InitConfig()
settings.Config.Manifests["default"].Repositories = make(map[string]*config.Repository)
actualErr := GenerateBootstrapIso(settings)
assert.Equal(t, expectedErr, actualErr)
})
t.Run("NewBundleByPathError", func(t *testing.T) {
settings := &environment.AirshipCTLSettings{
Debug: false,
AirshipConfigPath: "testdata/config/config",
KubeConfigPath: "testdata/config/kubeconfig",
Config: &config.Config{},
}
expectedErr := config.ErrMissingPhaseDocument{PhaseName: "bootstrap"}
settings.InitConfig()
settings.Config.Manifests["default"].TargetPath = "/nonexistent"
actualErr := GenerateBootstrapIso(settings)
assert.Equal(t, expectedErr, actualErr)
})
t.Run("SelectOneError", func(t *testing.T) {
settings := &environment.AirshipCTLSettings{
Debug: false,
AirshipConfigPath: "testdata/config/config",
KubeConfigPath: "testdata/config/kubeconfig",
Config: &config.Config{},
}
expectedErr := document.ErrDocNotFound{
Selector: document.NewSelector().ByGvk("airshipit.org", "v1alpha1", "ImageConfiguration")}
settings.InitConfig()
settings.Config.Manifests["default"].SubPath = "missingkinddoc/site/test-site"
actualErr := GenerateBootstrapIso(settings)
assert.Equal(t, expectedErr, actualErr)
})
t.Run("ToObjectError", func(t *testing.T) {
settings := &environment.AirshipCTLSettings{
Debug: false,
AirshipConfigPath: "testdata/config/config",
KubeConfigPath: "testdata/config/kubeconfig",
Config: &config.Config{},
}
expectedErrMessage := "missing metadata.name in object"
settings.InitConfig()
settings.Config.Manifests["default"].SubPath = "missingmetadoc/site/test-site"
actualErr := GenerateBootstrapIso(settings)
assert.Contains(t, actualErr.Error(), expectedErrMessage)
})
t.Run("verifyInputsError", func(t *testing.T) {
settings := &environment.AirshipCTLSettings{
Debug: false,
AirshipConfigPath: "testdata/config/config",
KubeConfigPath: "testdata/config/kubeconfig",
Config: &config.Config{},
}
expectedErr := config.ErrMissingConfig{What: "Must specify volume bind for ISO builder container"}
settings.InitConfig()
settings.Config.Manifests["default"].SubPath = "missingvoldoc/site/test-site"
actualErr := GenerateBootstrapIso(settings)
assert.Equal(t, expectedErr, actualErr)
})
}

View File

@ -0,0 +1,127 @@
/*
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 isogen
import (
"k8s.io/apimachinery/pkg/runtime"
)
type MockDocument struct {
MockAnnotate func()
MockAsYAML func() ([]byte, error)
MockGetAnnotations func() map[string]string
MockGetBool func() (bool, error)
MockGetFloat64 func() (float64, error)
MockGetGroup func() string
MockGetInt64 func() (int64, error)
MockGetKind func() string
MockGetLabels func() map[string]string
MockGetMap func() (map[string]interface{}, error)
MockGetName func() string
MockGetNamespace func() string
MockGetSlice func() ([]interface{}, error)
MockGetString func() (string, error)
MockGetStringMap func() (map[string]string, error)
MockGetStringSlice func() ([]string, error)
MockGetVersion func() string
MockLabel func()
MockMarshalJSON func() ([]byte, error)
MockToObject func() error
MockToAPIObject func() error
}
func (md *MockDocument) Annotate(_ map[string]string) {
md.MockAnnotate()
}
func (md *MockDocument) AsYAML() ([]byte, error) {
return md.MockAsYAML()
}
func (md *MockDocument) GetAnnotations() map[string]string {
return md.MockGetAnnotations()
}
func (md *MockDocument) GetBool(_ string) (bool, error) {
return md.MockGetBool()
}
func (md *MockDocument) GetFloat64(_ string) (float64, error) {
return md.MockGetFloat64()
}
func (md *MockDocument) GetGroup() string {
return md.MockGetGroup()
}
func (md *MockDocument) GetInt64(_ string) (int64, error) {
return md.MockGetInt64()
}
func (md *MockDocument) GetKind() string {
return md.MockGetKind()
}
func (md *MockDocument) GetLabels() map[string]string {
return md.MockGetLabels()
}
func (md *MockDocument) GetMap(_ string) (map[string]interface{}, error) {
return md.MockGetMap()
}
func (md *MockDocument) GetName() string {
return md.MockGetName()
}
func (md *MockDocument) GetNamespace() string {
return md.MockGetNamespace()
}
func (md *MockDocument) GetSlice(_ string) ([]interface{}, error) {
return md.MockGetSlice()
}
func (md *MockDocument) GetString(_ string) (string, error) {
return md.MockGetString()
}
func (md *MockDocument) GetStringMap(_ string) (map[string]string, error) {
return md.MockGetStringMap()
}
func (md *MockDocument) GetStringSlice(_ string) ([]string, error) {
return md.MockGetStringSlice()
}
func (md *MockDocument) GetVersion() string {
return md.MockGetVersion()
}
func (md *MockDocument) Label(_ map[string]string) {
md.MockLabel()
}
func (md *MockDocument) MarshalJSON() ([]byte, error) {
return md.MockMarshalJSON()
}
func (md *MockDocument) ToObject(_ interface{}) error {
return md.MockToObject()
}
func (md *MockDocument) ToAPIObject(obj runtime.Object, scheme *runtime.Scheme) error {
return md.MockToAPIObject()
}

View File

@ -0,0 +1,34 @@
apiVersion: airshipit.org/v1alpha1
clusters:
default:
clusterType:
ephemeral:
clusterKubeconf: default_ephemeral
managementConfiguration: default
contexts:
default:
contextKubeconf: default_ephemeral
manifest: default
currentContext: default
kind: Config
managementConfiguration:
default:
insecure: true
systemActionRetries: 30
systemRebootDelay: 30
type: redfish
manifests:
default:
primaryRepositoryName: primary
repositories:
primary:
checkout:
branch: master
commitHash: ""
force: false
tag: ""
url: https://opendev.org/airship/treasuremap
subPath: primary/site/test-site
targetPath: testdata
users:
admin: {}

View File

@ -0,0 +1,17 @@
apiVersion: v1
clusters:
- cluster:
server: https://172.17.0.1:6443
name: default_ephemeral
contexts:
- context:
cluster: default_ephemeral
user: admin
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: admin
user:
username: airship-admin

View File

@ -0,0 +1,12 @@
apiVersion: airshipit.org/v1alpha1
kind: FakeImageConfiguration
metadata:
name: default
builder:
networkConfigFileName: network-config
outputMetadataFileName: output-metadata.yaml
userDataFileName: user-data
container:
containerRuntime: docker
image: quay.io/airshipit/isogen:latest-debian_stable
volume: /srv/iso:/config

View File

@ -0,0 +1,2 @@
resources:
- image_configuration.yaml

View File

@ -0,0 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ImageConfiguration
builder:
networkConfigFileName: network-config
outputMetadataFileName: output-metadata.yaml
userDataFileName: user-data
container:
containerRuntime: docker
image: quay.io/airshipit/isogen:latest-debian_stable
volume: /srv/iso:/config

View File

@ -0,0 +1,2 @@
resources:
- image_configuration.yaml

View File

@ -0,0 +1,11 @@
apiVersion: airshipit.org/v1alpha1
kind: ImageConfiguration
metadata:
name: default
builder:
networkConfigFileName: network-config
outputMetadataFileName: output-metadata.yaml
userDataFileName: user-data
container:
containerRuntime: docker
image: quay.io/airshipit/isogen:latest-debian_stable

View File

@ -0,0 +1,2 @@
resources:
- image_configuration.yaml

View File

@ -34,9 +34,6 @@ type Cluster struct {
// Management configuration which will be used for all hosts in the cluster // Management configuration which will be used for all hosts in the cluster
ManagementConfiguration string `json:"managementConfiguration"` ManagementConfiguration string `json:"managementConfiguration"`
// Bootstrap configuration this clusters ephemeral hosts will rely on
Bootstrap string `json:"bootstrapInfo"`
} }
// ClusterPurpose encapsulates the Cluster Type as an enumeration // ClusterPurpose encapsulates the Cluster Type as an enumeration

View File

@ -67,9 +67,6 @@ type Config struct {
// Management configuration defines management information for all baremetal hosts in a cluster. // Management configuration defines management information for all baremetal hosts in a cluster.
ManagementConfiguration map[string]*ManagementConfiguration `json:"managementConfiguration"` ManagementConfiguration map[string]*ManagementConfiguration `json:"managementConfiguration"`
// BootstrapInfo is the configuration for container runtime, ISO builder and remote management
BootstrapInfo map[string]*Bootstrap `json:"bootstrapInfo"`
// loadedConfigPath is the full path to the the location of the config // loadedConfigPath is the full path to the the location of the config
// file from which this config was loaded // file from which this config was loaded
// +not persisted in file // +not persisted in file
@ -952,26 +949,6 @@ func (c *Config) importAuthInfos(importKubeConfig *clientcmdapi.Config) {
} }
} }
// CurrentContextBootstrapInfo returns bootstrap info for current context
func (c *Config) CurrentContextBootstrapInfo() (*Bootstrap, error) {
currentCluster, err := c.CurrentContextCluster()
if err != nil {
return nil, err
}
if currentCluster.Bootstrap == "" {
return nil, ErrMissingConfig{
What: fmt.Sprintf("No bootstrapInfo defined for context %q", c.CurrentContext),
}
}
bootstrap, exists := c.BootstrapInfo[currentCluster.Bootstrap]
if !exists {
return nil, ErrBootstrapInfoNotFound{Name: currentCluster.Bootstrap}
}
return bootstrap, nil
}
// GetManifests returns all of the Manifests associated with the Config sorted by name // GetManifests returns all of the Manifests associated with the Config sorted by name
func (c *Config) GetManifests() []*Manifest { func (c *Config) GetManifests() []*Manifest {
keys := make([]string, 0, len(c.Manifests)) keys := make([]string, 0, len(c.Manifests))

View File

@ -80,30 +80,10 @@ func TestString(t *testing.T) {
name: "repo-checkout", name: "repo-checkout",
stringer: testutil.DummyRepoCheckout(), stringer: testutil.DummyRepoCheckout(),
}, },
{
name: "bootstrapinfo",
stringer: testutil.DummyBootstrapInfo(),
},
{ {
name: "managementconfiguration", name: "managementconfiguration",
stringer: testutil.DummyManagementConfiguration(), stringer: testutil.DummyManagementConfiguration(),
}, },
{
name: "builder",
stringer: &config.Builder{
UserDataFileName: "user-data",
NetworkConfigFileName: "netconfig",
OutputMetadataFileName: "output-metadata.yaml",
},
},
{
name: "container",
stringer: &config.Container{
Volume: "/dummy:dummy",
Image: "dummy_image:dummy_tag",
ContainerRuntime: "docker",
},
},
} }
for _, tt := range tests { for _, tt := range tests {
@ -258,27 +238,6 @@ func TestEnsureComplete(t *testing.T) {
} }
} }
func TestCurrentContextBootstrapInfo(t *testing.T) {
conf, cleanup := testutil.InitConfig(t)
defer cleanup(t)
clusterName := "def"
clusterType := "ephemeral"
bootstrapInfo, err := conf.CurrentContextBootstrapInfo()
require.Error(t, err)
assert.Nil(t, bootstrapInfo)
conf.CurrentContext = currentContextName
conf.Clusters[clusterName].ClusterTypes[clusterType].Bootstrap = defaultString
conf.Contexts[currentContextName].Manifest = defaultString
conf.Contexts[currentContextName].KubeContext().Cluster = clusterName
bootstrapInfo, err = conf.CurrentContextBootstrapInfo()
require.NoError(t, err)
assert.Equal(t, conf.BootstrapInfo[defaultString], bootstrapInfo)
}
func TestCurrentContextManagementConfig(t *testing.T) { func TestCurrentContextManagementConfig(t *testing.T) {
conf, cleanup := testutil.InitConfig(t) conf, cleanup := testutil.InitConfig(t)
defer cleanup(t) defer cleanup(t)

View File

@ -43,7 +43,6 @@ const (
AirshipConfigGroup = "airshipit.org" AirshipConfigGroup = "airshipit.org"
AirshipConfigKind = "Config" AirshipConfigKind = "Config"
AirshipConfigVersion = "v1alpha1" AirshipConfigVersion = "v1alpha1"
AirshipDefaultBootstrapInfo = "default"
AirshipDefaultContext = "default" AirshipDefaultContext = "default"
AirshipDefaultDirectoryPermission = 0750 AirshipDefaultDirectoryPermission = 0750
AirshipDefaultFilePermission = 0640 AirshipDefaultFilePermission = 0640
@ -57,8 +56,6 @@ const (
AirshipPluginPathEnv = "AIRSHIP_KUSTOMIZE_PLUGINS" AirshipPluginPathEnv = "AIRSHIP_KUSTOMIZE_PLUGINS"
// Modules // Modules
AirshipDefaultBootstrapImage = "quay.io/airshipit/isogen:latest-ubuntu_focal"
AirshipDefaultIsoURL = "http://localhost:8099/ubuntu-focal.iso"
AirshipDefaultManagementType = redfish.ClientType AirshipDefaultManagementType = redfish.ClientType
) )

View File

@ -107,16 +107,6 @@ func (e ErrMissingRepoCheckoutOptions) Error() string {
return "Missing repository checkout options." return "Missing repository checkout options."
} }
// ErrBootstrapInfoNotFound returned if bootstrap
// information is not found for cluster
type ErrBootstrapInfoNotFound struct {
Name string
}
func (e ErrBootstrapInfoNotFound) Error() string {
return fmt.Sprintf("Bootstrap info %q not found.", e.Name)
}
// ErrInvalidConfig returned in case of incorrect configuration // ErrInvalidConfig returned in case of incorrect configuration
type ErrInvalidConfig struct { type ErrInvalidConfig struct {
What string What string

View File

@ -1,8 +0,0 @@
builder:
networkConfigFileName: netconfig
outputMetadataFileName: output-metadata.yaml
userDataFileName: user-data
container:
containerRuntime: docker
image: dummy_image:dummy_tag
volume: /dummy:dummy

View File

@ -1,3 +0,0 @@
networkConfigFileName: netconfig
outputMetadataFileName: output-metadata.yaml
userDataFileName: user-data

View File

@ -1,4 +1,3 @@
bootstrapInfo: dummy_bootstrap_config
clusterKubeconf: dummy_cluster_target clusterKubeconf: dummy_cluster_target
managementConfiguration: dummy_management_config managementConfiguration: dummy_management_config

View File

@ -1,23 +1,11 @@
apiVersion: airshipit.org/v1alpha1 apiVersion: airshipit.org/v1alpha1
bootstrapInfo:
dummy_bootstrap_config:
builder:
networkConfigFileName: netconfig
outputMetadataFileName: output-metadata.yaml
userDataFileName: user-data
container:
containerRuntime: docker
image: dummy_image:dummy_tag
volume: /dummy:dummy
clusters: clusters:
dummy_cluster: dummy_cluster:
clusterType: clusterType:
ephemeral: ephemeral:
bootstrapInfo: dummy_bootstrap_config
clusterKubeconf: dummy_cluster_ephemeral clusterKubeconf: dummy_cluster_ephemeral
managementConfiguration: dummy_management_config managementConfiguration: dummy_management_config
target: target:
bootstrapInfo: dummy_bootstrap_config
clusterKubeconf: dummy_cluster_target clusterKubeconf: dummy_cluster_target
managementConfiguration: dummy_management_config managementConfiguration: dummy_management_config
contexts: contexts:

View File

@ -1,6 +1,5 @@
Cluster: dummy_cluster Cluster: dummy_cluster
target: target:
bootstrapInfo: dummy_bootstrap_config
clusterKubeconf: dummy_cluster_target clusterKubeconf: dummy_cluster_target
managementConfiguration: dummy_management_config managementConfiguration: dummy_management_config

View File

@ -25,23 +25,6 @@ func NewConfig() *Config {
return &Config{ return &Config{
Kind: AirshipConfigKind, Kind: AirshipConfigKind,
APIVersion: AirshipConfigAPIVersion, APIVersion: AirshipConfigAPIVersion,
BootstrapInfo: map[string]*Bootstrap{
AirshipDefaultBootstrapInfo: {
Container: &Container{
Volume: "/srv/iso:/config",
Image: AirshipDefaultBootstrapImage,
ContainerRuntime: "docker",
},
Builder: &Builder{
UserDataFileName: "user-data",
NetworkConfigFileName: "network-config",
OutputMetadataFileName: "output-metadata.yaml",
},
RemoteDirect: &RemoteDirect{
IsoURL: AirshipDefaultIsoURL,
},
},
},
Clusters: make(map[string]*ClusterPurpose), Clusters: make(map[string]*ClusterPurpose),
Permissions: Permissions{ Permissions: Permissions{
DirectoryPermission: AirshipDefaultDirectoryPermission, DirectoryPermission: AirshipDefaultDirectoryPermission,
@ -84,7 +67,6 @@ func NewContext() *Context {
func NewCluster() *Cluster { func NewCluster() *Cluster {
return &Cluster{ return &Cluster{
NameInKubeconf: "", NameInKubeconf: "",
Bootstrap: AirshipDefaultBootstrapInfo,
ManagementConfiguration: AirshipDefaultManagementConfiguration, ManagementConfiguration: AirshipDefaultManagementConfiguration,
} }
} }

View File

@ -45,14 +45,13 @@ func (e ErrUnknownManagementType) Error() string {
return fmt.Sprintf("unknown management type: %s", e.Type) return fmt.Sprintf("unknown management type: %s", e.Type)
} }
// ErrMissingBootstrapInfoOption is an error that indicates a bootstrap option is missing in the airshipctl // ErrMissingOption is an error that indicates a remote direct config option is missing
// bootstrapInfo configuration. type ErrMissingOption struct {
type ErrMissingBootstrapInfoOption struct {
What string What string
} }
func (e ErrMissingBootstrapInfoOption) Error() string { func (e ErrMissingOption) Error() string {
return fmt.Sprintf("missing bootstrapInfo option: %s", e.What) return fmt.Sprintf("missing option: %s", e.What)
} }
// ErrNoHostsFound is an error that indicates that no hosts matched the selection criteria passed to a manager. // ErrNoHostsFound is an error that indicates that no hosts matched the selection criteria passed to a manager.

View File

@ -15,7 +15,9 @@
package remote package remote
import ( import (
api "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
"opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/pkg/environment" "opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/pkg/log" "opendev.org/airship/airshipctl/pkg/log"
@ -25,14 +27,30 @@ import (
// DoRemoteDirect bootstraps the ephemeral node. // DoRemoteDirect bootstraps the ephemeral node.
func (b baremetalHost) DoRemoteDirect(settings *environment.AirshipCTLSettings) error { func (b baremetalHost) DoRemoteDirect(settings *environment.AirshipCTLSettings) error {
cfg := settings.Config cfg := settings.Config
bootstrapSettings, err := cfg.CurrentContextBootstrapInfo()
root, err := cfg.CurrentContextEntryPoint(config.BootstrapPhase)
if err != nil { if err != nil {
return err return err
} }
remoteConfig := bootstrapSettings.RemoteDirect docBundle, err := document.NewBundleByPath(root)
if remoteConfig == nil { if err != nil {
return config.ErrMissingConfig{What: "RemoteDirect options not defined in bootstrap config"} return err
}
remoteDirectConfiguration := &api.RemoteDirectConfiguration{}
selector, err := document.NewSelector().ByObject(remoteDirectConfiguration, api.Scheme)
if err != nil {
return err
}
doc, err := docBundle.SelectOne(selector)
if err != nil {
return err
}
err = doc.ToAPIObject(remoteDirectConfiguration, api.Scheme)
if err != nil {
return err
} }
log.Debugf("Bootstrapping ephemeral host '%s' with ID '%s' and BMC Address '%s'.", b.HostName, b.NodeID(), log.Debugf("Bootstrapping ephemeral host '%s' with ID '%s' and BMC Address '%s'.", b.HostName, b.NodeID(),
@ -52,11 +70,11 @@ func (b baremetalHost) DoRemoteDirect(settings *environment.AirshipCTLSettings)
} }
// Perform remote direct operations // Perform remote direct operations
if remoteConfig.IsoURL == "" { if remoteDirectConfiguration.IsoURL == "" {
return ErrMissingBootstrapInfoOption{What: "isoURL"} return ErrMissingOption{What: "isoURL"}
} }
err = b.SetVirtualMedia(b.Context, remoteConfig.IsoURL) err = b.SetVirtualMedia(b.Context, remoteDirectConfiguration.IsoURL)
if err != nil { if err != nil {
return err return err
} }

View File

@ -20,8 +20,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/pkg/remote/power" "opendev.org/airship/airshipctl/pkg/remote/power"
"opendev.org/airship/airshipctl/pkg/remote/redfish" "opendev.org/airship/airshipctl/pkg/remote/redfish"
"opendev.org/airship/airshipctl/testutil/redfishutils" "opendev.org/airship/airshipctl/testutil/redfishutils"
@ -35,18 +33,6 @@ const (
password = "password" password = "password"
) )
// withRemoteDirectConfig initializes the remote direct settings when used as an argument to "initSettings".
func withRemoteDirectConfig(cfg *config.RemoteDirect) Configuration {
return func(settings *environment.AirshipCTLSettings) {
bootstrapInfo, err := settings.Config.CurrentContextBootstrapInfo()
if err != nil {
panic(fmt.Sprintf("Unable to initialize remote direct tests. Current Context error %q", err))
}
bootstrapInfo.RemoteDirect = cfg
}
}
func TestDoRemoteDirectMissingConfigOpts(t *testing.T) { func TestDoRemoteDirectMissingConfigOpts(t *testing.T) {
ctx, rMock, err := redfishutils.NewClient(redfishURL, false, false, username, password) ctx, rMock, err := redfishutils.NewClient(redfishURL, false, false, username, password)
assert.NoError(t, err) assert.NoError(t, err)
@ -60,8 +46,12 @@ func TestDoRemoteDirectMissingConfigOpts(t *testing.T) {
password, password,
} }
settings := initSettings(t, withRemoteDirectConfig(nil), withTestDataPath("base")) settings := initSettings(t, withTestDataPath("noremote"))
// there must be document.ErrDocNotFound
err = ephemeralHost.DoRemoteDirect(settings) err = ephemeralHost.DoRemoteDirect(settings)
expectedErrorMessage := `document filtered by selector [Group="airshipit.org", Version="v1alpha1", ` +
`Kind="RemoteDirectConfiguration"] found no documents`
assert.Equal(t, expectedErrorMessage, fmt.Sprintf("%s", err))
assert.Error(t, err) assert.Error(t, err)
} }
@ -81,10 +71,10 @@ func TestDoRemoteDirectMissingISOURL(t *testing.T) {
password, password,
} }
cfg := &config.RemoteDirect{} settings := initSettings(t, withTestDataPath("noisourl"))
settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base"))
err = ephemeralHost.DoRemoteDirect(settings) err = ephemeralHost.DoRemoteDirect(settings)
expectedErrorMessage := `missing option: isoURL`
assert.Equal(t, expectedErrorMessage, fmt.Sprintf("%s", err))
assert.Error(t, err) assert.Error(t, err)
} }
@ -108,11 +98,7 @@ func TestDoRemoteDirectRedfish(t *testing.T) {
password, password,
} }
cfg := &config.RemoteDirect{ settings := initSettings(t, withTestDataPath("base"))
IsoURL: isoURL,
}
settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base"))
err = ephemeralHost.DoRemoteDirect(settings) err = ephemeralHost.DoRemoteDirect(settings)
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -138,11 +124,7 @@ func TestDoRemoteDirectRedfishNodePoweredOff(t *testing.T) {
password, password,
} }
cfg := &config.RemoteDirect{ settings := initSettings(t, withTestDataPath("base"))
IsoURL: isoURL,
}
settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base"))
err = ephemeralHost.DoRemoteDirect(settings) err = ephemeralHost.DoRemoteDirect(settings)
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -169,11 +151,7 @@ func TestDoRemoteDirectRedfishVirtualMediaError(t *testing.T) {
password, password,
} }
cfg := &config.RemoteDirect{ settings := initSettings(t, withTestDataPath("base"))
IsoURL: isoURL,
}
settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base"))
err = ephemeralHost.DoRemoteDirect(settings) err = ephemeralHost.DoRemoteDirect(settings)
_, ok := err.(redfish.ErrRedfishClient) _, ok := err.(redfish.ErrRedfishClient)
@ -202,11 +180,7 @@ func TestDoRemoteDirectRedfishBootSourceError(t *testing.T) {
password, password,
} }
cfg := &config.RemoteDirect{ settings := initSettings(t, withTestDataPath("base"))
IsoURL: isoURL,
}
settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base"))
err = ephemeralHost.DoRemoteDirect(settings) err = ephemeralHost.DoRemoteDirect(settings)
_, ok := err.(redfish.ErrRedfishClient) _, ok := err.(redfish.ErrRedfishClient)
@ -236,11 +210,7 @@ func TestDoRemoteDirectRedfishRebootError(t *testing.T) {
password, password,
} }
cfg := &config.RemoteDirect{ settings := initSettings(t, withTestDataPath("base"))
IsoURL: isoURL,
}
settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base"))
err = ephemeralHost.DoRemoteDirect(settings) err = ephemeralHost.DoRemoteDirect(settings)
_, ok := err.(redfish.ErrRedfishClient) _, ok := err.(redfish.ErrRedfishClient)

View File

@ -1,2 +1,3 @@
resources: resources:
- baremetal.yaml - baremetal.yaml
- remote_direct_configuration.yaml

View File

@ -0,0 +1,5 @@
apiVersion: airshipit.org/v1alpha1
kind: RemoteDirectConfiguration
metadata:
name: default
isoUrl: https://localhost:8080/ubuntu.iso

View File

@ -0,0 +1,74 @@
---
apiVersion: metal3.io/v1alpha1
kind: BareMetalHost
metadata:
labels:
airshipit.org/ephemeral-node: "true"
name: master-0
spec:
online: true
bootMACAddress: 00:3b:8b:0c:ec:8b
bmc:
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/ephemeral
credentialsName: master-0-bmc-secret
---
apiVersion: v1
kind: Secret
metadata:
labels:
airshipit.org/ephemeral-node: "true"
name: master-0-bmc-secret
type: Opaque
data:
username: YWRtaW4=
password: cGFzc3dvcmQ=
...
---
apiVersion: metal3.io/v1alpha1
kind: BareMetalHost
metadata:
labels:
airshipit.org/test-node: "true"
name: master-1
spec:
online: true
bootMACAddress: 00:3b:8b:0c:ec:8b
bmc:
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/node-master-1
credentialsName: master-1-bmc-secret
---
apiVersion: metal3.io/v1alpha1
kind: BareMetalHost
metadata:
labels:
airshipit.org/test-node: "true"
name: master-2
spec:
online: true
bootMACAddress: 00:3b:8b:0c:ec:8b
bmc:
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/node-master-2
credentialsName: master-1-bmc-secret
---
apiVersion: v1
kind: Secret
metadata:
labels:
airshipit.org/ephemeral-node: "true"
name: master-1-bmc-secret
type: Opaque
data:
username: YWRtaW4=
password: cGFzc3dvcmQ=
...
---
apiVersion: metal3.io/v1alpha1
kind: BareMetalHost
metadata:
name: no-creds
spec:
online: true
bootMACAddress: 00:3b:8b:0c:ec:8b
bmc:
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/test-node
...

View File

@ -0,0 +1,3 @@
resources:
- baremetal.yaml
- remote_direct_configuration.yaml

View File

@ -0,0 +1,4 @@
apiVersion: airshipit.org/v1alpha1
kind: RemoteDirectConfiguration
metadata:
name: default

View File

@ -0,0 +1,74 @@
---
apiVersion: metal3.io/v1alpha1
kind: BareMetalHost
metadata:
labels:
airshipit.org/ephemeral-node: "true"
name: master-0
spec:
online: true
bootMACAddress: 00:3b:8b:0c:ec:8b
bmc:
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/ephemeral
credentialsName: master-0-bmc-secret
---
apiVersion: v1
kind: Secret
metadata:
labels:
airshipit.org/ephemeral-node: "true"
name: master-0-bmc-secret
type: Opaque
data:
username: YWRtaW4=
password: cGFzc3dvcmQ=
...
---
apiVersion: metal3.io/v1alpha1
kind: BareMetalHost
metadata:
labels:
airshipit.org/test-node: "true"
name: master-1
spec:
online: true
bootMACAddress: 00:3b:8b:0c:ec:8b
bmc:
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/node-master-1
credentialsName: master-1-bmc-secret
---
apiVersion: metal3.io/v1alpha1
kind: BareMetalHost
metadata:
labels:
airshipit.org/test-node: "true"
name: master-2
spec:
online: true
bootMACAddress: 00:3b:8b:0c:ec:8b
bmc:
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/node-master-2
credentialsName: master-1-bmc-secret
---
apiVersion: v1
kind: Secret
metadata:
labels:
airshipit.org/ephemeral-node: "true"
name: master-1-bmc-secret
type: Opaque
data:
username: YWRtaW4=
password: cGFzc3dvcmQ=
...
---
apiVersion: metal3.io/v1alpha1
kind: BareMetalHost
metadata:
name: no-creds
spec:
online: true
bootMACAddress: 00:3b:8b:0c:ec:8b
bmc:
address: redfish+http://nolocalhost:8888/redfish/v1/Systems/test-node
...

View File

@ -0,0 +1,2 @@
resources:
- baremetal.yaml

View File

@ -46,9 +46,6 @@ func DummyConfig() *config.Config {
DirectoryPermission: config.AirshipDefaultDirectoryPermission, DirectoryPermission: config.AirshipDefaultDirectoryPermission,
FilePermission: config.AirshipDefaultFilePermission, FilePermission: config.AirshipDefaultFilePermission,
}, },
BootstrapInfo: map[string]*config.Bootstrap{
"dummy_bootstrap_config": DummyBootstrapInfo(),
},
Contexts: map[string]*config.Context{ Contexts: map[string]*config.Context{
"dummy_context": DummyContext(), "dummy_context": DummyContext(),
}, },
@ -92,7 +89,6 @@ func DummyCluster() *config.Cluster {
cluster.CertificateAuthority = "dummy_ca" cluster.CertificateAuthority = "dummy_ca"
c.SetKubeCluster(cluster) c.SetKubeCluster(cluster)
c.NameInKubeconf = "dummy_cluster_target" c.NameInKubeconf = "dummy_cluster_target"
c.Bootstrap = "dummy_bootstrap_config"
c.ManagementConfiguration = "dummy_management_config" c.ManagementConfiguration = "dummy_management_config"
return c return c
} }
@ -227,26 +223,6 @@ func DummyAuthInfoOptions() *config.AuthInfoOptions {
return authinfo return authinfo
} }
// DummyBootstrapInfo creates a dummy BootstrapInfo config object for unit testing
func DummyBootstrapInfo() *config.Bootstrap {
bs := &config.Bootstrap{}
cont := config.Container{
Volume: "/dummy:dummy",
Image: "dummy_image:dummy_tag",
ContainerRuntime: "docker",
}
builder := config.Builder{
UserDataFileName: "user-data",
NetworkConfigFileName: "netconfig",
OutputMetadataFileName: "output-metadata.yaml",
}
bs.Container = &cont
bs.Builder = &builder
return bs
}
// DummyManagementConfiguration creates a management configuration for unit testing // DummyManagementConfiguration creates a management configuration for unit testing
func DummyManagementConfiguration() *config.ManagementConfiguration { func DummyManagementConfiguration() *config.ManagementConfiguration {
return &config.ManagementConfiguration{ return &config.ManagementConfiguration{
@ -283,22 +259,18 @@ clusters:
def: def:
clusterType: clusterType:
ephemeral: ephemeral:
bootstrapInfo: ""
clusterKubeconf: def_ephemeral clusterKubeconf: def_ephemeral
target: target:
bootstrapInfo: ""
clusterKubeconf: def_target clusterKubeconf: def_target
onlyinkubeconf: onlyinkubeconf:
clusterType: clusterType:
target: target:
bootstrapInfo: ""
clusterKubeconf: onlyinkubeconf_target clusterKubeconf: onlyinkubeconf_target
wrongonlyinconfig: wrongonlyinconfig:
clusterType: {} clusterType: {}
wrongonlyinkubeconf: wrongonlyinkubeconf:
clusterType: clusterType:
target: target:
bootstrapInfo: ""
clusterKubeconf: wrongonlyinkubeconf_target clusterKubeconf: wrongonlyinkubeconf_target
clustertypenil: clustertypenil:
clusterType: null clusterType: null

View File

@ -54,18 +54,6 @@ function generate_airshipconf {
cat <<EOL > ${AIRSHIPCONFIG} cat <<EOL > ${AIRSHIPCONFIG}
apiVersion: airshipit.org/v1alpha1 apiVersion: airshipit.org/v1alpha1
bootstrapInfo:
default:
builder:
networkConfigFileName: network-config
outputMetadataFileName: output-metadata.yaml
userDataFileName: user-data
container:
containerRuntime: docker
image: quay.io/airshipit/isogen:latest-ubuntu_focal
volume: /srv/iso:/config
remoteDirect:
isoUrl: http://localhost:8099/ubuntu-focal.iso
clusters: clusters:
${CONTEXT}_${cluster}: ${CONTEXT}_${cluster}:
clusterType: clusterType: