From e9c8425c30cb27a12edc0390e8f4c0d50b108fb8 Mon Sep 17 00:00:00 2001 From: Vladimir Kozhukalov Date: Fri, 22 May 2020 13:57:41 +0300 Subject: [PATCH] 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 --- .../get-all-ephemeral.golden | 6 - .../get-all-target.golden | 6 - .../get-ephemeral.golden | 1 - .../get-multiple-ephemeral.golden | 6 - .../get-multiple-target.golden | 6 - .../get-target.golden | 1 - .../ephemeral/image_configuration.yaml | 14 ++ .../function/ephemeral/kustomization.yaml | 2 + .../remote_direct_configuration.yaml | 7 + pkg/api/v1alpha1/groupversion_info.go | 2 + .../v1alpha1/imageconfiguration_types.go} | 59 ++----- .../remotedirectconfiguration_types.go | 29 ++++ pkg/api/v1alpha1/zz_generated.deepcopy.go | 82 ++++++++++ pkg/bootstrap/isogen/command.go | 67 +++++--- pkg/bootstrap/isogen/command_test.go | 154 +++++++++++++++--- pkg/bootstrap/isogen/mock_document_test.go | 127 +++++++++++++++ pkg/bootstrap/isogen/testdata/config/config | 34 ++++ .../isogen/testdata/config/kubeconfig | 17 ++ .../bootstrap/image_configuration.yaml | 12 ++ .../ephemeral/bootstrap/kustomization.yaml | 2 + .../bootstrap/image_configuration.yaml | 10 ++ .../ephemeral/bootstrap/kustomization.yaml | 2 + .../bootstrap/image_configuration.yaml | 11 ++ .../ephemeral/bootstrap/kustomization.yaml | 2 + pkg/config/cluster.go | 3 - pkg/config/config.go | 23 --- pkg/config/config_test.go | 41 ----- pkg/config/constants.go | 3 - pkg/config/errors.go | 10 -- pkg/config/testdata/bootstrapinfo-string.yaml | 8 - pkg/config/testdata/builder-string.yaml | 3 - pkg/config/testdata/cluster-string.yaml | 1 - pkg/config/testdata/config-string.yaml | 12 -- pkg/config/testdata/prettycluster-string.yaml | 1 - pkg/config/utils.go | 20 +-- pkg/remote/errors.go | 9 +- pkg/remote/remote_direct.go | 32 +++- pkg/remote/remote_direct_test.go | 56 ++----- .../ephemeral/bootstrap/kustomization.yaml | 3 +- .../remote_direct_configuration.yaml | 5 + .../ephemeral/bootstrap/baremetal.yaml | 74 +++++++++ .../ephemeral/bootstrap/kustomization.yaml | 3 + .../remote_direct_configuration.yaml | 4 + .../ephemeral/bootstrap/baremetal.yaml | 74 +++++++++ .../ephemeral/bootstrap/kustomization.yaml | 2 + testutil/testconfig.go | 28 ---- tools/document/validate_site_docs.sh | 12 -- 47 files changed, 747 insertions(+), 339 deletions(-) create mode 100644 manifests/function/ephemeral/image_configuration.yaml create mode 100644 manifests/function/ephemeral/remote_direct_configuration.yaml rename pkg/{config/bootstrap.go => api/v1alpha1/imageconfiguration_types.go} (51%) create mode 100644 pkg/api/v1alpha1/remotedirectconfiguration_types.go create mode 100644 pkg/bootstrap/isogen/mock_document_test.go create mode 100644 pkg/bootstrap/isogen/testdata/config/config create mode 100644 pkg/bootstrap/isogen/testdata/config/kubeconfig create mode 100644 pkg/bootstrap/isogen/testdata/missingkinddoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml create mode 100644 pkg/bootstrap/isogen/testdata/missingkinddoc/site/test-site/ephemeral/bootstrap/kustomization.yaml create mode 100644 pkg/bootstrap/isogen/testdata/missingmetadoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml create mode 100644 pkg/bootstrap/isogen/testdata/missingmetadoc/site/test-site/ephemeral/bootstrap/kustomization.yaml create mode 100644 pkg/bootstrap/isogen/testdata/missingvoldoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml create mode 100644 pkg/bootstrap/isogen/testdata/missingvoldoc/site/test-site/ephemeral/bootstrap/kustomization.yaml delete mode 100644 pkg/config/testdata/bootstrapinfo-string.yaml delete mode 100644 pkg/config/testdata/builder-string.yaml create mode 100644 pkg/remote/testdata/base/manifests/site/test-site/ephemeral/bootstrap/remote_direct_configuration.yaml create mode 100644 pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/baremetal.yaml create mode 100644 pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml create mode 100644 pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/remote_direct_configuration.yaml create mode 100644 pkg/remote/testdata/noremote/manifests/site/test-site/ephemeral/bootstrap/baremetal.yaml create mode 100644 pkg/remote/testdata/noremote/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml diff --git a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-all-ephemeral.golden b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-all-ephemeral.golden index 36bc8e7ce..935ea6a1e 100644 --- a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-all-ephemeral.golden +++ b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-all-ephemeral.golden @@ -1,6 +1,5 @@ Cluster: clusterBar ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterBar_ephemeral managementConfiguration: "" @@ -10,7 +9,6 @@ server: "" Cluster: clusterBar target: -bootstrapInfo: "" clusterKubeconf: clusterBar_target managementConfiguration: "" @@ -20,7 +18,6 @@ server: "" Cluster: clusterBaz ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterBaz_ephemeral managementConfiguration: "" @@ -30,7 +27,6 @@ server: "" Cluster: clusterBaz target: -bootstrapInfo: "" clusterKubeconf: clusterBaz_target managementConfiguration: "" @@ -40,7 +36,6 @@ server: "" Cluster: clusterFoo ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterFoo_ephemeral managementConfiguration: "" @@ -50,7 +45,6 @@ server: "" Cluster: clusterFoo target: -bootstrapInfo: "" clusterKubeconf: clusterFoo_target managementConfiguration: "" diff --git a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-all-target.golden b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-all-target.golden index 36bc8e7ce..935ea6a1e 100644 --- a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-all-target.golden +++ b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-all-target.golden @@ -1,6 +1,5 @@ Cluster: clusterBar ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterBar_ephemeral managementConfiguration: "" @@ -10,7 +9,6 @@ server: "" Cluster: clusterBar target: -bootstrapInfo: "" clusterKubeconf: clusterBar_target managementConfiguration: "" @@ -20,7 +18,6 @@ server: "" Cluster: clusterBaz ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterBaz_ephemeral managementConfiguration: "" @@ -30,7 +27,6 @@ server: "" Cluster: clusterBaz target: -bootstrapInfo: "" clusterKubeconf: clusterBaz_target managementConfiguration: "" @@ -40,7 +36,6 @@ server: "" Cluster: clusterFoo ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterFoo_ephemeral managementConfiguration: "" @@ -50,7 +45,6 @@ server: "" Cluster: clusterFoo target: -bootstrapInfo: "" clusterKubeconf: clusterFoo_target managementConfiguration: "" diff --git a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-ephemeral.golden b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-ephemeral.golden index e46f14e2a..08030fec3 100644 --- a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-ephemeral.golden +++ b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-ephemeral.golden @@ -1,6 +1,5 @@ Cluster: clusterFoo ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterFoo_ephemeral managementConfiguration: "" diff --git a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-multiple-ephemeral.golden b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-multiple-ephemeral.golden index 36bc8e7ce..935ea6a1e 100644 --- a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-multiple-ephemeral.golden +++ b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-multiple-ephemeral.golden @@ -1,6 +1,5 @@ Cluster: clusterBar ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterBar_ephemeral managementConfiguration: "" @@ -10,7 +9,6 @@ server: "" Cluster: clusterBar target: -bootstrapInfo: "" clusterKubeconf: clusterBar_target managementConfiguration: "" @@ -20,7 +18,6 @@ server: "" Cluster: clusterBaz ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterBaz_ephemeral managementConfiguration: "" @@ -30,7 +27,6 @@ server: "" Cluster: clusterBaz target: -bootstrapInfo: "" clusterKubeconf: clusterBaz_target managementConfiguration: "" @@ -40,7 +36,6 @@ server: "" Cluster: clusterFoo ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterFoo_ephemeral managementConfiguration: "" @@ -50,7 +45,6 @@ server: "" Cluster: clusterFoo target: -bootstrapInfo: "" clusterKubeconf: clusterFoo_target managementConfiguration: "" diff --git a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-multiple-target.golden b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-multiple-target.golden index 36bc8e7ce..935ea6a1e 100644 --- a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-multiple-target.golden +++ b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-multiple-target.golden @@ -1,6 +1,5 @@ Cluster: clusterBar ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterBar_ephemeral managementConfiguration: "" @@ -10,7 +9,6 @@ server: "" Cluster: clusterBar target: -bootstrapInfo: "" clusterKubeconf: clusterBar_target managementConfiguration: "" @@ -20,7 +18,6 @@ server: "" Cluster: clusterBaz ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterBaz_ephemeral managementConfiguration: "" @@ -30,7 +27,6 @@ server: "" Cluster: clusterBaz target: -bootstrapInfo: "" clusterKubeconf: clusterBaz_target managementConfiguration: "" @@ -40,7 +36,6 @@ server: "" Cluster: clusterFoo ephemeral: -bootstrapInfo: "" clusterKubeconf: clusterFoo_ephemeral managementConfiguration: "" @@ -50,7 +45,6 @@ server: "" Cluster: clusterFoo target: -bootstrapInfo: "" clusterKubeconf: clusterFoo_target managementConfiguration: "" diff --git a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-target.golden b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-target.golden index 086c32799..39c261367 100644 --- a/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-target.golden +++ b/cmd/config/testdata/TestGetClusterCmdGoldenOutput/get-target.golden @@ -1,6 +1,5 @@ Cluster: clusterFoo target: -bootstrapInfo: "" clusterKubeconf: clusterFoo_target managementConfiguration: "" diff --git a/manifests/function/ephemeral/image_configuration.yaml b/manifests/function/ephemeral/image_configuration.yaml new file mode 100644 index 000000000..06d27667a --- /dev/null +++ b/manifests/function/ephemeral/image_configuration.yaml @@ -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 diff --git a/manifests/function/ephemeral/kustomization.yaml b/manifests/function/ephemeral/kustomization.yaml index 97a9721bd..06fc5359c 100644 --- a/manifests/function/ephemeral/kustomization.yaml +++ b/manifests/function/ephemeral/kustomization.yaml @@ -1,2 +1,4 @@ resources: - secret.yaml + - image_configuration.yaml + - remote_direct_configuration.yaml diff --git a/manifests/function/ephemeral/remote_direct_configuration.yaml b/manifests/function/ephemeral/remote_direct_configuration.yaml new file mode 100644 index 000000000..27b1f0b47 --- /dev/null +++ b/manifests/function/ephemeral/remote_direct_configuration.yaml @@ -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 diff --git a/pkg/api/v1alpha1/groupversion_info.go b/pkg/api/v1alpha1/groupversion_info.go index fc526ffe1..d4dd6f846 100644 --- a/pkg/api/v1alpha1/groupversion_info.go +++ b/pkg/api/v1alpha1/groupversion_info.go @@ -46,6 +46,8 @@ func init() { &PhasePlan{}, &KubeConfig{}, &KubernetesApply{}, + &ImageConfiguration{}, + &RemoteDirectConfiguration{}, ) _ = AddToScheme(Scheme) //nolint:errcheck } diff --git a/pkg/config/bootstrap.go b/pkg/api/v1alpha1/imageconfiguration_types.go similarity index 51% rename from pkg/config/bootstrap.go rename to pkg/api/v1alpha1/imageconfiguration_types.go index 7d6dd01b5..e0168b533 100644 --- a/pkg/config/bootstrap.go +++ b/pkg/api/v1alpha1/imageconfiguration_types.go @@ -1,6 +1,4 @@ /* -Copyright 2014 The Kubernetes Authors. - 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 @@ -14,21 +12,13 @@ See the License for the specific language governing permissions and 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 -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 +// Container structure contains parameters related to Docker runtime, used for building image type Container struct { // Container volume directory binding. Volume string `json:"volume,omitempty"` @@ -38,7 +28,7 @@ type Container struct { ContainerRuntime string `json:"containerRuntime,omitempty"` } -// Builder parameters +// Builder structure defines metadata files (including Cloud Init metadata) used for image type Builder struct { // Cloud Init user-data file name placed to the container volume root UserDataFileName string `json:"userDataFileName,omitempty"` @@ -48,35 +38,12 @@ type Builder struct { OutputMetadataFileName string `json:"outputMetadataFileName,omitempty"` } -// RemoteDirect configuration options -type RemoteDirect struct { - // IsoURL specifies url to download ISO image for ephemeral node - IsoURL string `json:"isoUrl,omitempty"` -} +// ImageConfiguration structure is inherited from apimachinery TypeMeta and ObjectMeta and is a top level +// configuration structure for building image +type ImageConfiguration struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` -// Bootstrap functions -func (b *Bootstrap) String() string { - 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) + Container *Container `json:"container,omitempty"` + Builder *Builder `json:"builder,omitempty"` } diff --git a/pkg/api/v1alpha1/remotedirectconfiguration_types.go b/pkg/api/v1alpha1/remotedirectconfiguration_types.go new file mode 100644 index 000000000..4ac0ad400 --- /dev/null +++ b/pkg/api/v1alpha1/remotedirectconfiguration_types.go @@ -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"` +} diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index b51b0d5ad..58b5dc472 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -345,3 +345,85 @@ func (in *Provider) DeepCopy() *Provider { in.DeepCopyInto(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 +} diff --git a/pkg/bootstrap/isogen/command.go b/pkg/bootstrap/isogen/command.go index a1bbb375e..ef3ca1db8 100644 --- a/pkg/bootstrap/isogen/command.go +++ b/pkg/bootstrap/isogen/command.go @@ -21,6 +21,7 @@ import ( "path/filepath" "strings" + api "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/bootstrap/cloudinit" "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/container" @@ -43,17 +44,6 @@ func GenerateBootstrapIso(settings *environment.AirshipCTLSettings) error { 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) if err != nil { return err @@ -63,23 +53,41 @@ func GenerateBootstrapIso(settings *environment.AirshipCTLSettings) error { return err } - log.Print("Creating ISO builder container") - builder, err := container.NewContainer( - &ctx, cfg.Container.ContainerRuntime, - cfg.Container.Image) + imageConfiguration := &api.ImageConfiguration{} + selector, err := document.NewSelector().ByObject(imageConfiguration, api.Scheme) + if err != nil { + return err + } + doc, err := docBundle.SelectOne(selector) if err != nil { 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 { return err } 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 == "" { return config.ErrMissingConfig{ What: "Must specify volume bind for ISO builder container", @@ -104,19 +112,22 @@ func verifyInputs(cfg *config.Bootstrap) error { 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] fls := make(map[string][]byte) fls[filepath.Join(hostVol, cfg.Builder.UserDataFileName)] = userData fls[filepath.Join(hostVol, cfg.Builder.NetworkConfigFileName)] = netConf - // TODO (dukov) Get rid of this ugly conversion byte -> string -> byte - builderData := []byte(cfg.String()) - fls[filepath.Join(hostVol, builderConfigFileName)] = builderData + fls[filepath.Join(hostVol, builderConfigFileName)] = builderCfgYaml return fls } -func verifyArtifacts(cfg *config.Bootstrap) error { +func verifyArtifacts(cfg *api.ImageConfiguration) error { hostVol := strings.Split(cfg.Container.Volume, ":")[0] metadataPath := filepath.Join(hostVol, cfg.Builder.OutputMetadataFileName) _, err := os.Stat(metadataPath) @@ -126,7 +137,8 @@ func verifyArtifacts(cfg *config.Bootstrap) error { func generateBootstrapIso( docBundle document.Bundle, builder container.Container, - cfg *config.Bootstrap, + doc document.Document, + cfg *api.ImageConfiguration, debug bool, ) error { cntVol := strings.Split(cfg.Container.Volume, ":")[1] @@ -136,7 +148,12 @@ func generateBootstrapIso( 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 { return err } diff --git a/pkg/bootstrap/isogen/command_test.go b/pkg/bootstrap/isogen/command_test.go index 70dc42d59..76ed46bcf 100644 --- a/pkg/bootstrap/isogen/command_test.go +++ b/pkg/bootstrap/isogen/command_test.go @@ -21,9 +21,12 @@ import ( "strings" "testing" + "opendev.org/airship/airshipctl/pkg/environment" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + api "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/log" @@ -66,17 +69,26 @@ func TestBootstrapIso(t *testing.T) { defer cleanup(t) volBind := tempVol + ":/dst" - testErr := fmt.Errorf("testErr") - testCfg := &config.Bootstrap{ - Container: &config.Container{ + testErr := fmt.Errorf("TestErr") + testCfg := &api.ImageConfiguration{ + Container: &api.Container{ Volume: volBind, ContainerRuntime: "docker", }, - Builder: &config.Builder{ + Builder: &api.Builder{ UserDataFileName: "user-data", 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{ "Creating cloud-init for ephemeral K8s", fmt.Sprintf("Running default container command. Mounted dir: [%s]", volBind), @@ -87,7 +99,8 @@ func TestBootstrapIso(t *testing.T) { tests := []struct { builder *mockContainer - cfg *config.Bootstrap + cfg *api.ImageConfiguration + doc *MockDocument debug bool expectedOut []string expectedErr error @@ -97,16 +110,15 @@ func TestBootstrapIso(t *testing.T) { runCommand: func() error { return testErr }, }, cfg: testCfg, + doc: testDoc, debug: false, expectedOut: []string{expOut[0], expOut[1]}, expectedErr: testErr, }, { - builder: &mockContainer{ - runCommand: func() error { return nil }, - getID: func() string { return "TESTID" }, - }, + builder: testBuilder, cfg: testCfg, + doc: testDoc, debug: true, expectedOut: []string{expOut[0], expOut[1], expOut[2], expOut[3]}, expectedErr: nil, @@ -118,16 +130,27 @@ func TestBootstrapIso(t *testing.T) { rmContainer: func() error { return testErr }, }, cfg: testCfg, + doc: testDoc, debug: false, expectedOut: []string{expOut[0], expOut[1], expOut[2], expOut[4]}, 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 { outBuf := &bytes.Buffer{} 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() for _, line := range tt.expectedOut { @@ -144,14 +167,14 @@ func TestVerifyInputs(t *testing.T) { tests := []struct { name string - cfg *config.Bootstrap + cfg *api.ImageConfiguration args []string expectedErr error }{ { name: "missing-container-field", - cfg: &config.Bootstrap{ - Container: &config.Container{}, + cfg: &api.ImageConfiguration{ + Container: &api.Container{}, }, expectedErr: config.ErrMissingConfig{ What: "Must specify volume bind for ISO builder container", @@ -159,11 +182,11 @@ func TestVerifyInputs(t *testing.T) { }, { name: "missing-filenames", - cfg: &config.Bootstrap{ - Container: &config.Container{ + cfg: &api.ImageConfiguration{ + Container: &api.Container{ Volume: tempVol + ":/dst", }, - Builder: &config.Builder{}, + Builder: &api.Builder{}, }, expectedErr: config.ErrMissingConfig{ What: "UserDataFileName or NetworkConfigFileName are not specified in ISO builder config", @@ -171,11 +194,11 @@ func TestVerifyInputs(t *testing.T) { }, { name: "invalid-host-path", - cfg: &config.Bootstrap{ - Container: &config.Container{ + cfg: &api.ImageConfiguration{ + Container: &api.Container{ Volume: tempVol + ":/dst:/dst1", }, - Builder: &config.Builder{ + Builder: &api.Builder{ UserDataFileName: "user-data", NetworkConfigFileName: "net-conf", }, @@ -186,11 +209,11 @@ func TestVerifyInputs(t *testing.T) { }, { name: "success", - cfg: &config.Bootstrap{ - Container: &config.Container{ + cfg: &api.ImageConfiguration{ + Container: &api.Container{ Volume: tempVol, }, - Builder: &config.Builder{ + Builder: &api.Builder{ UserDataFileName: "user-data", 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) + }) +} diff --git a/pkg/bootstrap/isogen/mock_document_test.go b/pkg/bootstrap/isogen/mock_document_test.go new file mode 100644 index 000000000..3882a1f10 --- /dev/null +++ b/pkg/bootstrap/isogen/mock_document_test.go @@ -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() +} diff --git a/pkg/bootstrap/isogen/testdata/config/config b/pkg/bootstrap/isogen/testdata/config/config new file mode 100644 index 000000000..51cab05e7 --- /dev/null +++ b/pkg/bootstrap/isogen/testdata/config/config @@ -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: {} diff --git a/pkg/bootstrap/isogen/testdata/config/kubeconfig b/pkg/bootstrap/isogen/testdata/config/kubeconfig new file mode 100644 index 000000000..0fc12972f --- /dev/null +++ b/pkg/bootstrap/isogen/testdata/config/kubeconfig @@ -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 diff --git a/pkg/bootstrap/isogen/testdata/missingkinddoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml b/pkg/bootstrap/isogen/testdata/missingkinddoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml new file mode 100644 index 000000000..aecfb71f0 --- /dev/null +++ b/pkg/bootstrap/isogen/testdata/missingkinddoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml @@ -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 diff --git a/pkg/bootstrap/isogen/testdata/missingkinddoc/site/test-site/ephemeral/bootstrap/kustomization.yaml b/pkg/bootstrap/isogen/testdata/missingkinddoc/site/test-site/ephemeral/bootstrap/kustomization.yaml new file mode 100644 index 000000000..86ef7db29 --- /dev/null +++ b/pkg/bootstrap/isogen/testdata/missingkinddoc/site/test-site/ephemeral/bootstrap/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - image_configuration.yaml diff --git a/pkg/bootstrap/isogen/testdata/missingmetadoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml b/pkg/bootstrap/isogen/testdata/missingmetadoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml new file mode 100644 index 000000000..91e86a591 --- /dev/null +++ b/pkg/bootstrap/isogen/testdata/missingmetadoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml @@ -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 diff --git a/pkg/bootstrap/isogen/testdata/missingmetadoc/site/test-site/ephemeral/bootstrap/kustomization.yaml b/pkg/bootstrap/isogen/testdata/missingmetadoc/site/test-site/ephemeral/bootstrap/kustomization.yaml new file mode 100644 index 000000000..86ef7db29 --- /dev/null +++ b/pkg/bootstrap/isogen/testdata/missingmetadoc/site/test-site/ephemeral/bootstrap/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - image_configuration.yaml diff --git a/pkg/bootstrap/isogen/testdata/missingvoldoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml b/pkg/bootstrap/isogen/testdata/missingvoldoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml new file mode 100644 index 000000000..cf65e1c06 --- /dev/null +++ b/pkg/bootstrap/isogen/testdata/missingvoldoc/site/test-site/ephemeral/bootstrap/image_configuration.yaml @@ -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 diff --git a/pkg/bootstrap/isogen/testdata/missingvoldoc/site/test-site/ephemeral/bootstrap/kustomization.yaml b/pkg/bootstrap/isogen/testdata/missingvoldoc/site/test-site/ephemeral/bootstrap/kustomization.yaml new file mode 100644 index 000000000..86ef7db29 --- /dev/null +++ b/pkg/bootstrap/isogen/testdata/missingvoldoc/site/test-site/ephemeral/bootstrap/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - image_configuration.yaml diff --git a/pkg/config/cluster.go b/pkg/config/cluster.go index 9e4de83b8..c8850dba5 100644 --- a/pkg/config/cluster.go +++ b/pkg/config/cluster.go @@ -34,9 +34,6 @@ type Cluster struct { // Management configuration which will be used for all hosts in the cluster 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 diff --git a/pkg/config/config.go b/pkg/config/config.go index 6a378961d..2c8824d42 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -67,9 +67,6 @@ type Config struct { // Management configuration defines management information for all baremetal hosts in a cluster. 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 // file from which this config was loaded // +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 func (c *Config) GetManifests() []*Manifest { keys := make([]string, 0, len(c.Manifests)) diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 011664c9b..331725271 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -80,30 +80,10 @@ func TestString(t *testing.T) { name: "repo-checkout", stringer: testutil.DummyRepoCheckout(), }, - { - name: "bootstrapinfo", - stringer: testutil.DummyBootstrapInfo(), - }, { name: "managementconfiguration", 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 { @@ -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) { conf, cleanup := testutil.InitConfig(t) defer cleanup(t) diff --git a/pkg/config/constants.go b/pkg/config/constants.go index d78bad049..b91e7829d 100644 --- a/pkg/config/constants.go +++ b/pkg/config/constants.go @@ -43,7 +43,6 @@ const ( AirshipConfigGroup = "airshipit.org" AirshipConfigKind = "Config" AirshipConfigVersion = "v1alpha1" - AirshipDefaultBootstrapInfo = "default" AirshipDefaultContext = "default" AirshipDefaultDirectoryPermission = 0750 AirshipDefaultFilePermission = 0640 @@ -57,8 +56,6 @@ const ( AirshipPluginPathEnv = "AIRSHIP_KUSTOMIZE_PLUGINS" // Modules - AirshipDefaultBootstrapImage = "quay.io/airshipit/isogen:latest-ubuntu_focal" - AirshipDefaultIsoURL = "http://localhost:8099/ubuntu-focal.iso" AirshipDefaultManagementType = redfish.ClientType ) diff --git a/pkg/config/errors.go b/pkg/config/errors.go index 1a8842f48..ae9ec8428 100644 --- a/pkg/config/errors.go +++ b/pkg/config/errors.go @@ -107,16 +107,6 @@ func (e ErrMissingRepoCheckoutOptions) Error() string { 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 type ErrInvalidConfig struct { What string diff --git a/pkg/config/testdata/bootstrapinfo-string.yaml b/pkg/config/testdata/bootstrapinfo-string.yaml deleted file mode 100644 index bbf742a96..000000000 --- a/pkg/config/testdata/bootstrapinfo-string.yaml +++ /dev/null @@ -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 diff --git a/pkg/config/testdata/builder-string.yaml b/pkg/config/testdata/builder-string.yaml deleted file mode 100644 index 0cf10d8b6..000000000 --- a/pkg/config/testdata/builder-string.yaml +++ /dev/null @@ -1,3 +0,0 @@ -networkConfigFileName: netconfig -outputMetadataFileName: output-metadata.yaml -userDataFileName: user-data diff --git a/pkg/config/testdata/cluster-string.yaml b/pkg/config/testdata/cluster-string.yaml index 7d7fbbfa5..e277ed957 100644 --- a/pkg/config/testdata/cluster-string.yaml +++ b/pkg/config/testdata/cluster-string.yaml @@ -1,4 +1,3 @@ -bootstrapInfo: dummy_bootstrap_config clusterKubeconf: dummy_cluster_target managementConfiguration: dummy_management_config diff --git a/pkg/config/testdata/config-string.yaml b/pkg/config/testdata/config-string.yaml index c02df114f..b4a3a65a7 100644 --- a/pkg/config/testdata/config-string.yaml +++ b/pkg/config/testdata/config-string.yaml @@ -1,23 +1,11 @@ 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: dummy_cluster: clusterType: ephemeral: - bootstrapInfo: dummy_bootstrap_config clusterKubeconf: dummy_cluster_ephemeral managementConfiguration: dummy_management_config target: - bootstrapInfo: dummy_bootstrap_config clusterKubeconf: dummy_cluster_target managementConfiguration: dummy_management_config contexts: diff --git a/pkg/config/testdata/prettycluster-string.yaml b/pkg/config/testdata/prettycluster-string.yaml index 2645747a1..bf8866c2c 100644 --- a/pkg/config/testdata/prettycluster-string.yaml +++ b/pkg/config/testdata/prettycluster-string.yaml @@ -1,6 +1,5 @@ Cluster: dummy_cluster target: -bootstrapInfo: dummy_bootstrap_config clusterKubeconf: dummy_cluster_target managementConfiguration: dummy_management_config diff --git a/pkg/config/utils.go b/pkg/config/utils.go index e15c1a310..3acc14b1f 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -25,24 +25,7 @@ func NewConfig() *Config { return &Config{ Kind: AirshipConfigKind, 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{ DirectoryPermission: AirshipDefaultDirectoryPermission, FilePermission: AirshipDefaultFilePermission, @@ -84,7 +67,6 @@ func NewContext() *Context { func NewCluster() *Cluster { return &Cluster{ NameInKubeconf: "", - Bootstrap: AirshipDefaultBootstrapInfo, ManagementConfiguration: AirshipDefaultManagementConfiguration, } } diff --git a/pkg/remote/errors.go b/pkg/remote/errors.go index a69a5d32d..efcbe5bec 100644 --- a/pkg/remote/errors.go +++ b/pkg/remote/errors.go @@ -45,14 +45,13 @@ func (e ErrUnknownManagementType) Error() string { return fmt.Sprintf("unknown management type: %s", e.Type) } -// ErrMissingBootstrapInfoOption is an error that indicates a bootstrap option is missing in the airshipctl -// bootstrapInfo configuration. -type ErrMissingBootstrapInfoOption struct { +// ErrMissingOption is an error that indicates a remote direct config option is missing +type ErrMissingOption struct { What string } -func (e ErrMissingBootstrapInfoOption) Error() string { - return fmt.Sprintf("missing bootstrapInfo option: %s", e.What) +func (e ErrMissingOption) Error() string { + 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. diff --git a/pkg/remote/remote_direct.go b/pkg/remote/remote_direct.go index 2b109fa50..f5c9a37ae 100644 --- a/pkg/remote/remote_direct.go +++ b/pkg/remote/remote_direct.go @@ -15,7 +15,9 @@ package remote import ( + api "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/config" + "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/pkg/environment" "opendev.org/airship/airshipctl/pkg/log" @@ -25,14 +27,30 @@ import ( // DoRemoteDirect bootstraps the ephemeral node. func (b baremetalHost) DoRemoteDirect(settings *environment.AirshipCTLSettings) error { cfg := settings.Config - bootstrapSettings, err := cfg.CurrentContextBootstrapInfo() + + root, err := cfg.CurrentContextEntryPoint(config.BootstrapPhase) if err != nil { return err } - remoteConfig := bootstrapSettings.RemoteDirect - if remoteConfig == nil { - return config.ErrMissingConfig{What: "RemoteDirect options not defined in bootstrap config"} + docBundle, err := document.NewBundleByPath(root) + if err != nil { + 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(), @@ -52,11 +70,11 @@ func (b baremetalHost) DoRemoteDirect(settings *environment.AirshipCTLSettings) } // Perform remote direct operations - if remoteConfig.IsoURL == "" { - return ErrMissingBootstrapInfoOption{What: "isoURL"} + if remoteDirectConfiguration.IsoURL == "" { + return ErrMissingOption{What: "isoURL"} } - err = b.SetVirtualMedia(b.Context, remoteConfig.IsoURL) + err = b.SetVirtualMedia(b.Context, remoteDirectConfiguration.IsoURL) if err != nil { return err } diff --git a/pkg/remote/remote_direct_test.go b/pkg/remote/remote_direct_test.go index af56fc611..3a7c0943c 100644 --- a/pkg/remote/remote_direct_test.go +++ b/pkg/remote/remote_direct_test.go @@ -20,8 +20,6 @@ import ( "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/redfish" "opendev.org/airship/airshipctl/testutil/redfishutils" @@ -35,18 +33,6 @@ const ( 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) { ctx, rMock, err := redfishutils.NewClient(redfishURL, false, false, username, password) assert.NoError(t, err) @@ -60,8 +46,12 @@ func TestDoRemoteDirectMissingConfigOpts(t *testing.T) { password, } - settings := initSettings(t, withRemoteDirectConfig(nil), withTestDataPath("base")) + settings := initSettings(t, withTestDataPath("noremote")) + // there must be document.ErrDocNotFound 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) } @@ -81,10 +71,10 @@ func TestDoRemoteDirectMissingISOURL(t *testing.T) { password, } - cfg := &config.RemoteDirect{} - - settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base")) + settings := initSettings(t, withTestDataPath("noisourl")) err = ephemeralHost.DoRemoteDirect(settings) + expectedErrorMessage := `missing option: isoURL` + assert.Equal(t, expectedErrorMessage, fmt.Sprintf("%s", err)) assert.Error(t, err) } @@ -108,11 +98,7 @@ func TestDoRemoteDirectRedfish(t *testing.T) { password, } - cfg := &config.RemoteDirect{ - IsoURL: isoURL, - } - - settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base")) + settings := initSettings(t, withTestDataPath("base")) err = ephemeralHost.DoRemoteDirect(settings) assert.NoError(t, err) } @@ -138,11 +124,7 @@ func TestDoRemoteDirectRedfishNodePoweredOff(t *testing.T) { password, } - cfg := &config.RemoteDirect{ - IsoURL: isoURL, - } - - settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base")) + settings := initSettings(t, withTestDataPath("base")) err = ephemeralHost.DoRemoteDirect(settings) assert.NoError(t, err) } @@ -169,11 +151,7 @@ func TestDoRemoteDirectRedfishVirtualMediaError(t *testing.T) { password, } - cfg := &config.RemoteDirect{ - IsoURL: isoURL, - } - - settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base")) + settings := initSettings(t, withTestDataPath("base")) err = ephemeralHost.DoRemoteDirect(settings) _, ok := err.(redfish.ErrRedfishClient) @@ -202,11 +180,7 @@ func TestDoRemoteDirectRedfishBootSourceError(t *testing.T) { password, } - cfg := &config.RemoteDirect{ - IsoURL: isoURL, - } - - settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base")) + settings := initSettings(t, withTestDataPath("base")) err = ephemeralHost.DoRemoteDirect(settings) _, ok := err.(redfish.ErrRedfishClient) @@ -236,11 +210,7 @@ func TestDoRemoteDirectRedfishRebootError(t *testing.T) { password, } - cfg := &config.RemoteDirect{ - IsoURL: isoURL, - } - - settings := initSettings(t, withRemoteDirectConfig(cfg), withTestDataPath("base")) + settings := initSettings(t, withTestDataPath("base")) err = ephemeralHost.DoRemoteDirect(settings) _, ok := err.(redfish.ErrRedfishClient) diff --git a/pkg/remote/testdata/base/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml b/pkg/remote/testdata/base/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml index 6177200c1..3a72d08f4 100644 --- a/pkg/remote/testdata/base/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml +++ b/pkg/remote/testdata/base/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml @@ -1,2 +1,3 @@ resources: - - baremetal.yaml \ No newline at end of file + - baremetal.yaml + - remote_direct_configuration.yaml diff --git a/pkg/remote/testdata/base/manifests/site/test-site/ephemeral/bootstrap/remote_direct_configuration.yaml b/pkg/remote/testdata/base/manifests/site/test-site/ephemeral/bootstrap/remote_direct_configuration.yaml new file mode 100644 index 000000000..91e81caae --- /dev/null +++ b/pkg/remote/testdata/base/manifests/site/test-site/ephemeral/bootstrap/remote_direct_configuration.yaml @@ -0,0 +1,5 @@ +apiVersion: airshipit.org/v1alpha1 +kind: RemoteDirectConfiguration +metadata: + name: default +isoUrl: https://localhost:8080/ubuntu.iso diff --git a/pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/baremetal.yaml b/pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/baremetal.yaml new file mode 100644 index 000000000..3d26b2cd6 --- /dev/null +++ b/pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/baremetal.yaml @@ -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 +... diff --git a/pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml b/pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml new file mode 100644 index 000000000..3a72d08f4 --- /dev/null +++ b/pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml @@ -0,0 +1,3 @@ +resources: + - baremetal.yaml + - remote_direct_configuration.yaml diff --git a/pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/remote_direct_configuration.yaml b/pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/remote_direct_configuration.yaml new file mode 100644 index 000000000..669e670b9 --- /dev/null +++ b/pkg/remote/testdata/noisourl/manifests/site/test-site/ephemeral/bootstrap/remote_direct_configuration.yaml @@ -0,0 +1,4 @@ +apiVersion: airshipit.org/v1alpha1 +kind: RemoteDirectConfiguration +metadata: + name: default diff --git a/pkg/remote/testdata/noremote/manifests/site/test-site/ephemeral/bootstrap/baremetal.yaml b/pkg/remote/testdata/noremote/manifests/site/test-site/ephemeral/bootstrap/baremetal.yaml new file mode 100644 index 000000000..3d26b2cd6 --- /dev/null +++ b/pkg/remote/testdata/noremote/manifests/site/test-site/ephemeral/bootstrap/baremetal.yaml @@ -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 +... diff --git a/pkg/remote/testdata/noremote/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml b/pkg/remote/testdata/noremote/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml new file mode 100644 index 000000000..db5b66552 --- /dev/null +++ b/pkg/remote/testdata/noremote/manifests/site/test-site/ephemeral/bootstrap/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - baremetal.yaml diff --git a/testutil/testconfig.go b/testutil/testconfig.go index 0491e268e..44428242b 100644 --- a/testutil/testconfig.go +++ b/testutil/testconfig.go @@ -46,9 +46,6 @@ func DummyConfig() *config.Config { DirectoryPermission: config.AirshipDefaultDirectoryPermission, FilePermission: config.AirshipDefaultFilePermission, }, - BootstrapInfo: map[string]*config.Bootstrap{ - "dummy_bootstrap_config": DummyBootstrapInfo(), - }, Contexts: map[string]*config.Context{ "dummy_context": DummyContext(), }, @@ -92,7 +89,6 @@ func DummyCluster() *config.Cluster { cluster.CertificateAuthority = "dummy_ca" c.SetKubeCluster(cluster) c.NameInKubeconf = "dummy_cluster_target" - c.Bootstrap = "dummy_bootstrap_config" c.ManagementConfiguration = "dummy_management_config" return c } @@ -227,26 +223,6 @@ func DummyAuthInfoOptions() *config.AuthInfoOptions { 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 func DummyManagementConfiguration() *config.ManagementConfiguration { return &config.ManagementConfiguration{ @@ -283,22 +259,18 @@ clusters: def: clusterType: ephemeral: - bootstrapInfo: "" clusterKubeconf: def_ephemeral target: - bootstrapInfo: "" clusterKubeconf: def_target onlyinkubeconf: clusterType: target: - bootstrapInfo: "" clusterKubeconf: onlyinkubeconf_target wrongonlyinconfig: clusterType: {} wrongonlyinkubeconf: clusterType: target: - bootstrapInfo: "" clusterKubeconf: wrongonlyinkubeconf_target clustertypenil: clusterType: null diff --git a/tools/document/validate_site_docs.sh b/tools/document/validate_site_docs.sh index 94e5fc3d2..17cfd6e6c 100755 --- a/tools/document/validate_site_docs.sh +++ b/tools/document/validate_site_docs.sh @@ -54,18 +54,6 @@ function generate_airshipconf { cat < ${AIRSHIPCONFIG} 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: ${CONTEXT}_${cluster}: clusterType: