diff --git a/pkg/bootstrap/cloudinit/cloud-init.go b/pkg/bootstrap/cloudinit/cloud-init.go index 136d2b0d4..12f49be21 100644 --- a/pkg/bootstrap/cloudinit/cloud-init.go +++ b/pkg/bootstrap/cloudinit/cloud-init.go @@ -7,72 +7,137 @@ import ( ) const ( - // TODO (dukov) This should depend on cluster api version once it is - // fully available for Metal3. In other words: - // - Secret for v1alpha1 - // - KubeAdmConfig for v1alpha2 - EphemeralClusterConfKind = "Secret" + UserDataKind = "Secret" + NetworkDataKind = "Secret" + BareMetalHostKind = "BareMetalHost" + EphemeralHostLabel = "airshipit.org/ephemeral-node=true" + EphemeralUserDataLabel = "airshipit.org/ephemeral-user-data=true" + networkDataKey = "networkData" + userDataKey = "userData" ) -func decodeData(cfg document.Document, key string) ([]byte, error) { - data, err := cfg.GetStringMap("data") +// GetCloudData reads YAML document input and generates cloud-init data for +// ephemeral node. +func GetCloudData(docBundle document.Bundle) (userData []byte, netConf []byte, err error) { + userData, err = getUserData(docBundle) + if err != nil { - return nil, ErrDataNotSupplied{DocName: cfg.GetName(), Key: key} + return nil, nil, err } - res, ok := data[key] - if !ok { - return nil, ErrDataNotSupplied{DocName: cfg.GetName(), Key: key} + netConf, err = getNetworkData(docBundle) + + if err != nil { + return nil, nil, err } - return b64.StdEncoding.DecodeString(res) + return userData, netConf, err } -// getDataFromSecret extracts data from Secret with respect to overrides -func getDataFromSecret(cfg document.Document, key string) ([]byte, error) { - data, err := cfg.GetStringMap("stringData") +func getUserData(docBundle document.Bundle) ([]byte, error) { + // find the user-data document + selector := document.NewSelector().ByKind(UserDataKind).ByLabel(EphemeralUserDataLabel) + docs, err := docBundle.Select(selector) if err != nil { - return decodeData(cfg, key) + return nil, err + } + var userDataDoc document.Document = &document.Factory{} + switch numDocsFound := len(docs); { + case numDocsFound == 0: + return nil, document.ErrDocNotFound{Selector: selector} + case numDocsFound > 1: + return nil, document.ErrMultipleDocsFound{Selector: selector} + case numDocsFound == 1: + userDataDoc = docs[0] + } + + // finally, try and retrieve the data we want from the document + userData, err := decodeData(userDataDoc, userDataKey) + if err != nil { + return nil, err + } + + return userData, nil +} + +func getNetworkData(docBundle document.Bundle) ([]byte, error) { + // find the baremetal host indicated as the ephemeral node + selector := document.NewSelector().ByKind(BareMetalHostKind).ByLabel(EphemeralHostLabel) + docs, err := docBundle.Select(selector) + if err != nil { + return nil, err + } + + var bmhDoc document.Document = &document.Factory{} + switch numDocsFound := len(docs); { + case numDocsFound == 0: + return nil, document.ErrDocNotFound{Selector: selector} + case numDocsFound > 1: + return nil, document.ErrMultipleDocsFound{Selector: selector} + case numDocsFound == 1: + bmhDoc = docs[0] + } + + // extract the network data document pointer from the bmh document + netConfDocName, err := bmhDoc.GetString("spec.networkData.name") + if err != nil { + return nil, err + } + netConfDocNamespace, err := bmhDoc.GetString("spec.networkData.namespace") + if err != nil { + return nil, err + } + + // try and find these documents in our bundle + selector = document.NewSelector().ByKind(NetworkDataKind).ByNamespace(netConfDocNamespace).ByName(netConfDocName) + docs, err = docBundle.Select(selector) + + if err != nil { + return nil, err + } + + var networkDataDoc document.Document = &document.Factory{} + switch numDocsFound := len(docs); { + case numDocsFound == 0: + return nil, document.ErrDocNotFound{Selector: selector} + case numDocsFound > 1: + return nil, document.ErrMultipleDocsFound{Selector: selector} + case numDocsFound == 1: + networkDataDoc = docs[0] + } + + // finally, try and retrieve the data we want from the document + netData, err := decodeData(networkDataDoc, networkDataKey) + if err != nil { + return nil, err + } + + return netData, nil +} + +func decodeData(cfg document.Document, key string) ([]byte, error) { + var needsBase64Decode = false + + // TODO(alanmeadows): distinguish between missing net-data key + // and missing data/stringData keys in the Secret + data, err := cfg.GetStringMap("data") + if err == nil { + needsBase64Decode = true + } else { + // we'll catch any error below + data, err = cfg.GetStringMap("stringData") + if err != nil { + return nil, ErrDataNotSupplied{DocName: cfg.GetName(), Key: "data or stringData"} + } } res, ok := data[key] if !ok { - return decodeData(cfg, key) + return nil, ErrDataNotSupplied{DocName: cfg.GetName(), Key: key} + } + + if needsBase64Decode { + return b64.StdEncoding.DecodeString(res) } return []byte(res), nil } - -// GetCloudData reads YAML document input and generates cloud-init data for -// node (i.e. Cluster API Machine) with bootstrap label. -func GetCloudData(docBundle document.Bundle, bsSelector string) ([]byte, []byte, error) { - var userData []byte - var netConf []byte - docs, err := docBundle.GetByLabel(bsSelector) - if err != nil { - return nil, nil, err - } - var ephemeralCfg document.Document - for _, doc := range docs { - if doc.GetKind() == EphemeralClusterConfKind { - ephemeralCfg = doc - break - } - } - if ephemeralCfg == nil { - return nil, nil, document.ErrDocNotFound{ - Selector: bsSelector, - Kind: EphemeralClusterConfKind, - } - } - - netConf, err = getDataFromSecret(ephemeralCfg, "netconfig") - if err != nil { - return nil, nil, err - } - - userData, err = getDataFromSecret(ephemeralCfg, "userdata") - if err != nil { - return nil, nil, err - } - return userData, netConf, nil -} diff --git a/pkg/bootstrap/cloudinit/cloud-init_test.go b/pkg/bootstrap/cloudinit/cloud-init_test.go index 5c256b983..f843bb388 100644 --- a/pkg/bootstrap/cloudinit/cloud-init_test.go +++ b/pkg/bootstrap/cloudinit/cloud-init_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "sigs.k8s.io/kustomize/v3/pkg/types" "opendev.org/airship/airshipctl/pkg/document" "opendev.org/airship/airshipctl/testutil" @@ -16,54 +17,90 @@ func TestGetCloudData(t *testing.T) { require.NoError(t, err, "Building Bundle Failed") tests := []struct { - selector string + labelFilter string expectedUserData []byte expectedNetData []byte expectedErr error }{ { - selector: "test=test", + labelFilter: "test=validdocset", + expectedUserData: []byte("cloud-init"), + expectedNetData: []byte("net-config"), + expectedErr: nil, + }, + { + labelFilter: "test=ephemeralmissing", expectedUserData: nil, expectedNetData: nil, expectedErr: document.ErrDocNotFound{ - Selector: "test=test", - Kind: "Secret", + Selector: document.NewSelector(). + ByLabel("airshipit.org/ephemeral-node=true"). + ByKind("BareMetalHost"), }, }, { - selector: "airshipit.org/ephemeral=false", + labelFilter: "test=ephemeralduplicate", expectedUserData: nil, expectedNetData: nil, - expectedErr: ErrDataNotSupplied{ - DocName: "node1-bmc-secret1", - Key: "netconfig", + expectedErr: document.ErrMultipleDocsFound{ + Selector: document.NewSelector(). + ByLabel("airshipit.org/ephemeral-node=true"). + ByKind("BareMetalHost"), }, }, { - selector: "test=nodataforcfg", + labelFilter: "test=networkdatabadpointer", expectedUserData: nil, expectedNetData: nil, - expectedErr: ErrDataNotSupplied{ - DocName: "node1-bmc-secret2", - Key: "netconfig", + expectedErr: document.ErrDocNotFound{ + Selector: document.NewSelector(). + ByKind("Secret"). + ByNamespace("networkdatabadpointer-missing"). + ByName("networkdatabadpointer-missing"), }, }, { - selector: "airshipit.org/ephemeral=true", - expectedUserData: []byte("cloud-init"), - expectedNetData: []byte("netconfig\n"), - expectedErr: nil, + labelFilter: "test=networkdatamalformed", + expectedUserData: nil, + expectedNetData: nil, + expectedErr: ErrDataNotSupplied{DocName: "networkdatamalformed-malformed", Key: networkDataKey}, }, { - selector: "some-data in (true, True)", - expectedUserData: []byte("cloud-init"), - expectedNetData: []byte("netconfig\n"), - expectedErr: nil, + labelFilter: "test=networkdatamissing", + expectedUserData: nil, + expectedNetData: nil, + expectedErr: types.NoFieldError{Field: "spec.networkData.name"}, + }, + { + labelFilter: "test=userdatamalformed", + expectedUserData: nil, + expectedNetData: nil, + expectedErr: ErrDataNotSupplied{DocName: "userdatamalformed-somesecret", Key: userDataKey}, + }, + { + labelFilter: "test=userdatamissing", + expectedUserData: nil, + expectedNetData: nil, + expectedErr: document.ErrDocNotFound{ + Selector: document.NewSelector(). + ByKind("Secret"). + ByLabel("airshipit.org/ephemeral-user-data=true"), + }, }, } for _, tt := range tests { - actualUserData, actualNetData, actualErr := GetCloudData(bundle, tt.selector) + // prune the bundle down using the label filter for the specific test + selector := document.NewSelector().ByLabel(tt.labelFilter) + filteredBundle, err := bundle.SelectBundle(selector) + require.NoError(t, err, "Building filtered bundle for %s failed", tt.labelFilter) + + // ensure each test case filter has at least one document + docs, err := filteredBundle.GetAllDocuments() + require.NoError(t, err, "GetAllDocuments failed") + require.NotZero(t, docs) + + actualUserData, actualNetData, actualErr := GetCloudData(filteredBundle) assert.Equal(t, tt.expectedUserData, actualUserData) assert.Equal(t, tt.expectedNetData, actualNetData) diff --git a/pkg/bootstrap/cloudinit/errors.go b/pkg/bootstrap/cloudinit/errors.go index c076b2773..157de26dc 100644 --- a/pkg/bootstrap/cloudinit/errors.go +++ b/pkg/bootstrap/cloudinit/errors.go @@ -11,6 +11,17 @@ type ErrDataNotSupplied struct { Key string } +// ErrDuplicateNetworkDataDocuments error returned if multiple network documents +// were found with the same name in the same namespace +type ErrDuplicateNetworkDataDocuments struct { + DocName string + Namespace string +} + func (e ErrDataNotSupplied) Error() string { return fmt.Sprintf("Document %s has no key %s", e.DocName, e.Key) } + +func (e ErrDuplicateNetworkDataDocuments) Error() string { + return fmt.Sprintf("Found more than one document with the name %s in namespace %s", e.DocName, e.Namespace) +} diff --git a/pkg/bootstrap/cloudinit/testdata/ephemeralduplicate.yaml b/pkg/bootstrap/cloudinit/testdata/ephemeralduplicate.yaml new file mode 100644 index 000000000..714b5b128 --- /dev/null +++ b/pkg/bootstrap/cloudinit/testdata/ephemeralduplicate.yaml @@ -0,0 +1,36 @@ +# in this document set, we have no ephemerally labeled node +# which should cause an error +apiVersion: v1 +kind: Secret +metadata: + labels: + test: ephemeralduplicate + name: ephemeralduplicate +type: Opaque +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + test: ephemeralduplicate + airshipit.org/ephemeral-node: 'true' + name: ephemeralduplicate-master-1 +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + test: ephemeralduplicate + airshipit.org/ephemeral-node: 'true' + name: ephemeralduplicate-master-2 +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: 'true' + test: ephemeralduplicate + name: ephemeralduplicate-airship-isogen-userdata +type: Opaque +stringData: + userData: cloudinit \ No newline at end of file diff --git a/pkg/bootstrap/cloudinit/testdata/ephemeralmissing.yaml b/pkg/bootstrap/cloudinit/testdata/ephemeralmissing.yaml new file mode 100644 index 000000000..3bd2302b8 --- /dev/null +++ b/pkg/bootstrap/cloudinit/testdata/ephemeralmissing.yaml @@ -0,0 +1,27 @@ +# in this document set, we have no ephemerally labeled node +# which should cause an error +apiVersion: v1 +kind: Secret +metadata: + labels: + test: ephemeralmissing + name: ephemeralmissing +type: Opaque +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + test: ephemeralmissing + name: ephemeralmissing-master-1 +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: 'true' + test: ephemeralmissing + name: ephemeralmissing-airship-isogen-userdata +type: Opaque +stringData: + userData: cloud-init \ No newline at end of file diff --git a/pkg/bootstrap/cloudinit/testdata/kustomization.yaml b/pkg/bootstrap/cloudinit/testdata/kustomization.yaml index 97a9721bd..c301ece0e 100644 --- a/pkg/bootstrap/cloudinit/testdata/kustomization.yaml +++ b/pkg/bootstrap/cloudinit/testdata/kustomization.yaml @@ -1,2 +1,9 @@ resources: - - secret.yaml + - ephemeralduplicate.yaml + - ephemeralmissing.yaml + - networkdatabadpointer.yaml + - networkdatamalformed.yaml + - networkdatamissing.yaml + - userdatamalformed.yaml + - userdatamissing.yaml + - validdocset.yaml \ No newline at end of file diff --git a/pkg/bootstrap/cloudinit/testdata/networkdatabadpointer.yaml b/pkg/bootstrap/cloudinit/testdata/networkdatabadpointer.yaml new file mode 100644 index 000000000..c7701b550 --- /dev/null +++ b/pkg/bootstrap/cloudinit/testdata/networkdatabadpointer.yaml @@ -0,0 +1,38 @@ +# in this document set, we have an ephemeral node however +# it lacks a networkData clause +apiVersion: v1 +kind: Secret +metadata: + labels: + test: networkdatabadpointer + name: networkdatabadpointer-master-1-bmc +type: Opaque +stringData: + username: foobar + password: goober +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + airshipit.org/ephemeral-node: 'true' + test: networkdatabadpointer + name: networkdatabadpointer-master-1 +spec: + bmc: + address: ipmi://127.0.0.1 + credentialsName: networkdatabadpointer-master-1-bmc + networkData: + name: networkdatabadpointer-missing + namespace: networkdatabadpointer-missing +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: 'true' + test: networkdatabadpointer + name: networkdatabadpointer-airship-isogen-userdata +type: Opaque +stringData: + userData: cloud-init \ No newline at end of file diff --git a/pkg/bootstrap/cloudinit/testdata/networkdatamalformed.yaml b/pkg/bootstrap/cloudinit/testdata/networkdatamalformed.yaml new file mode 100644 index 000000000..2515fa737 --- /dev/null +++ b/pkg/bootstrap/cloudinit/testdata/networkdatamalformed.yaml @@ -0,0 +1,50 @@ +# in this document set, we have an ephemeral node with +# resolvable network data, but it is malformed lacking +# the proper field +apiVersion: v1 +kind: Secret +metadata: + labels: + test: networkdatamalformed + name: networkdatamalformed-master-1-bmc +type: Opaque +stringData: + username: foobar + password: goober +--- +apiVersion: v1 +kind: Secret +namespace: malformed +metadata: + labels: + test: networkdatamalformed + name: networkdatamalformed-malformed +type: Opaque +stringData: + no-net-data-key: the required 'net-data' key is missing +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + airshipit.org/ephemeral-node: 'true' + test: networkdatamalformed + name: networkdatamalformed-master-1 +spec: + bmc: + address: ipmi://127.0.0.1 + credentialsName: networkdatamalformed-master-1-bmc + networkData: + name: networkdatamalformed-malformed + namespace: malformed +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: 'true' + test: networkdatamalformed + name: networkdatamalformed-airship-isogen-userdata +type: Opaque +stringData: + userData: cloud-init \ No newline at end of file diff --git a/pkg/bootstrap/cloudinit/testdata/networkdatamissing.yaml b/pkg/bootstrap/cloudinit/testdata/networkdatamissing.yaml new file mode 100644 index 000000000..d40ef57bb --- /dev/null +++ b/pkg/bootstrap/cloudinit/testdata/networkdatamissing.yaml @@ -0,0 +1,46 @@ +# in this document set, we have an ephemeral node with +# but it lacks a networkData clause +apiVersion: v1 +kind: Secret +metadata: + labels: + test: networkdatamissing + name: networkdatamissing-master-1-bmc +type: Opaque +stringData: + username: foobar + password: goober +--- +apiVersion: v1 +kind: Secret +namespace: missing +metadata: + labels: + test: missing + name: networkdatamissing-missing +type: Opaque +stringData: + networkData: there is network data here, but we have no reference to it +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + airshipit.org/ephemeral-node: 'true' + test: networkdatamissing + name: networkdatamissing-master-1 +spec: + bmc: + address: ipmi://127.0.0.1 + credentialsName: networkdatamissing-master-1-bmc +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: 'true' + test: networkdatamissing + name: networkdatamissing-airship-isogen-userdata +type: Opaque +stringData: + userData: cloud-init \ No newline at end of file diff --git a/pkg/bootstrap/cloudinit/testdata/secret.yaml b/pkg/bootstrap/cloudinit/testdata/secret.yaml deleted file mode 100644 index d6abc1196..000000000 --- a/pkg/bootstrap/cloudinit/testdata/secret.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - labels: - airshipit.org/ephemeral: "true" - name: node1-bmc-secret -type: Opaque -data: - netconfig: bmV0Y29uZmlnCg== -stringData: - userdata: cloud-init ---- -apiVersion: v1 -kind: Secret -metadata: - labels: - airshipit.org/ephemeral: "false" - name: node1-bmc-secret1 -type: Opaque ---- -apiVersion: v1 -kind: Secret -metadata: - labels: - test: nodataforcfg - name: node1-bmc-secret2 -type: Opaque -data: - foo: bmV0Y29uZmlnCg== ---- -apiVersion: v1 -kind: Secret -metadata: - labels: - some-data: "True" - name: node1-bmc-in-secret2 -type: Opaque -data: - netconfig: bmV0Y29uZmlnCg== -stringData: - userdata: cloud-init diff --git a/pkg/bootstrap/cloudinit/testdata/userdatamalformed.yaml b/pkg/bootstrap/cloudinit/testdata/userdatamalformed.yaml new file mode 100644 index 000000000..892f25e98 --- /dev/null +++ b/pkg/bootstrap/cloudinit/testdata/userdatamalformed.yaml @@ -0,0 +1,12 @@ +# in this document set, we have a secret that contains a label for our +# iso generation userdata, but it is malformed lacking a user-data key +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: 'true' + test: userdatamalformed + name: userdatamalformed-somesecret +type: Opaque +stringData: + no-user-data: this secret has the right label but is missing the 'user-data' key \ No newline at end of file diff --git a/pkg/bootstrap/cloudinit/testdata/userdatamissing.yaml b/pkg/bootstrap/cloudinit/testdata/userdatamissing.yaml new file mode 100644 index 000000000..a8237cbac --- /dev/null +++ b/pkg/bootstrap/cloudinit/testdata/userdatamissing.yaml @@ -0,0 +1,11 @@ +# in this document set, we lack a document that contains our ephemeral +# iso generation userdata +apiVersion: v1 +kind: Secret +metadata: + labels: + test: userdatamissing + name: userdatamissing-somesecret +type: Opaque +stringData: + userData: "this secret lacks the label airshipit.org/ephemeral-user-data: true" \ No newline at end of file diff --git a/pkg/bootstrap/cloudinit/testdata/validdocset.yaml b/pkg/bootstrap/cloudinit/testdata/validdocset.yaml new file mode 100644 index 000000000..28afaa74e --- /dev/null +++ b/pkg/bootstrap/cloudinit/testdata/validdocset.yaml @@ -0,0 +1,66 @@ +# in this document set, we have an ephemeral node with +# the right label, resolvable/valid network data and +# a user-data secret with the right label +# +# we also introduce a second baremetal host that is not +# labeled as the ephemeral node to facilitate testing +apiVersion: v1 +kind: Secret +metadata: + labels: + test: validdocset + name: master-1-bmc +type: Opaque +stringData: + username: foobar + password: goober +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: 'true' + test: validdocset + name: airship-isogen-userdata +type: Opaque +stringData: + userData: cloud-init +--- +apiVersion: v1 +kind: Secret +namespace: metal3 +metadata: + labels: + test: validdocset + name: master-1-networkdata +type: Opaque +stringData: + networkData: net-config +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + test: validdocset + name: master-2 + bmc: + address: ipmi://127.0.0.1 + credentialsName: master-2-bmc + networkData: + name: master-2-networkdata + namespace: metal3 +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + airshipit.org/ephemeral-node: 'true' + test: validdocset + name: master-1 +spec: + bmc: + address: ipmi://127.0.0.1 + credentialsName: master-1-bmc + networkData: + name: master-1-networkdata + namespace: metal3 \ No newline at end of file diff --git a/pkg/bootstrap/isogen/command.go b/pkg/bootstrap/isogen/command.go index 62c1e4d5f..6e4364e43 100644 --- a/pkg/bootstrap/isogen/command.go +++ b/pkg/bootstrap/isogen/command.go @@ -119,8 +119,7 @@ func generateBootstrapIso( ) error { cntVol := strings.Split(cfg.Container.Volume, ":")[1] log.Print("Creating cloud-init for ephemeral K8s") - label := document.EphemeralClusterSelector - userData, netConf, err := cloudinit.GetCloudData(docBundle, label) + userData, netConf, err := cloudinit.GetCloudData(docBundle) if err != nil { return err } diff --git a/pkg/bootstrap/isogen/testdata/secret.yaml b/pkg/bootstrap/isogen/testdata/secret.yaml index 08a451624..28afaa74e 100644 --- a/pkg/bootstrap/isogen/testdata/secret.yaml +++ b/pkg/bootstrap/isogen/testdata/secret.yaml @@ -1,11 +1,66 @@ +# in this document set, we have an ephemeral node with +# the right label, resolvable/valid network data and +# a user-data secret with the right label +# +# we also introduce a second baremetal host that is not +# labeled as the ephemeral node to facilitate testing apiVersion: v1 kind: Secret metadata: labels: - airshipit.org/ephemeral: "true" - name: node1-bmc-secret + test: validdocset + name: master-1-bmc type: Opaque -data: - netconfig: bmV0Y29uZmlnCg== stringData: - userdata: cloud-init + username: foobar + password: goober +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + airshipit.org/ephemeral-user-data: 'true' + test: validdocset + name: airship-isogen-userdata +type: Opaque +stringData: + userData: cloud-init +--- +apiVersion: v1 +kind: Secret +namespace: metal3 +metadata: + labels: + test: validdocset + name: master-1-networkdata +type: Opaque +stringData: + networkData: net-config +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + test: validdocset + name: master-2 + bmc: + address: ipmi://127.0.0.1 + credentialsName: master-2-bmc + networkData: + name: master-2-networkdata + namespace: metal3 +--- +apiVersion: metal3.io/v1alpha1 +kind: BareMetalHost +metadata: + labels: + airshipit.org/ephemeral-node: 'true' + test: validdocset + name: master-1 +spec: + bmc: + address: ipmi://127.0.0.1 + credentialsName: master-1-bmc + networkData: + name: master-1-networkdata + namespace: metal3 \ No newline at end of file diff --git a/pkg/cluster/initinfra/infra.go b/pkg/cluster/initinfra/infra.go index 292695735..886b9dbe5 100644 --- a/pkg/cluster/initinfra/infra.go +++ b/pkg/cluster/initinfra/infra.go @@ -77,7 +77,7 @@ func (infra *Infra) Deploy() error { } if len(docs) == 0 { return document.ErrDocNotFound{ - Selector: ls, + Selector: selector, } } diff --git a/pkg/document/bundle.go b/pkg/document/bundle.go index 239e64461..a647e8379 100644 --- a/pkg/document/bundle.go +++ b/pkg/document/bundle.go @@ -49,6 +49,7 @@ type Bundle interface { SetFileSystem(FileSystem) error GetFileSystem() FileSystem Select(selector Selector) ([]Document, error) + SelectBundle(selector Selector) (Bundle, error) GetByGvk(string, string, string) ([]Document, error) GetByName(string) (Document, error) GetByAnnotation(annotationSelector string) ([]Document, error) @@ -207,6 +208,37 @@ func (b *BundleFactory) Select(selector Selector) ([]Document, error) { return docSet, err } +// SelectBundle offers an interface to pass a Selector, built on top of kustomize Selector +// to the bundle returning a new Bundle that matches the criteria. This is useful +// where you want to actually prune the underlying bundle you are working with +// rather then getting back the matching documents for scenarios like +// test cases where you want to pass in custom "filtered" bundles +// specific to the test case +func (b *BundleFactory) SelectBundle(selector Selector) (Bundle, error) { + // use the kustomize select method + resources, err := b.ResMap.Select(selector.Selector) + if err != nil { + return nil, err + } + + // create a blank resourcemap and append the found resources + // into the new resource map + resourceMap := resmap.New() + for _, res := range resources { + if err = resourceMap.Append(res); err != nil { + return nil, err + } + } + + // return a new bundle with the same options and filesystem + // as this one but with a reduced resourceMap + return &BundleFactory{ + KustomizeBuildOptions: b.KustomizeBuildOptions, + ResMap: resourceMap, + FileSystem: b.FileSystem, + }, nil +} + // GetByAnnotation is a convenience method to get documents for a particular annotation func (b *BundleFactory) GetByAnnotation(annotationSelector string) ([]Document, error) { // Construct kustomize annotation selector diff --git a/pkg/document/errors.go b/pkg/document/errors.go index be6e09a5f..769c7f208 100644 --- a/pkg/document/errors.go +++ b/pkg/document/errors.go @@ -6,10 +6,18 @@ import ( // ErrDocNotFound returned if desired document not found type ErrDocNotFound struct { - Selector string - Kind string + Selector Selector +} + +// ErrMultipleDocsFound returned if desired document not found +type ErrMultipleDocsFound struct { + Selector Selector } func (e ErrDocNotFound) Error() string { - return fmt.Sprintf("Document filtered by selector %s with Kind %s not found", e.Selector, e.Kind) + return fmt.Sprintf("Document filtered by selector %q found no documents", e.Selector) +} + +func (e ErrMultipleDocsFound) Error() string { + return fmt.Sprintf("Document filtered by selector %q found more than one document", e.Selector) } diff --git a/pkg/document/selectors.go b/pkg/document/selectors.go index 6f7a5b971..87bee4f40 100644 --- a/pkg/document/selectors.go +++ b/pkg/document/selectors.go @@ -26,12 +26,24 @@ func (s Selector) ByName(name string) Selector { return s } +// ByNamespace select by namepace +func (s Selector) ByNamespace(namespace string) Selector { + s.Namespace = namespace + return s +} + // ByGvk select by gvk func (s Selector) ByGvk(group, version, kind string) Selector { s.Gvk = gvk.Gvk{Group: group, Version: version, Kind: kind} return s } +// ByKind select by Kind +func (s Selector) ByKind(kind string) Selector { + s.Gvk = gvk.Gvk{Kind: kind} + return s +} + // ByLabel select by label selector func (s Selector) ByLabel(labelSelector string) Selector { if s.LabelSelector != "" { diff --git a/pkg/remote/remote_direct.go b/pkg/remote/remote_direct.go index cee56536a..2ba10eb5d 100644 --- a/pkg/remote/remote_direct.go +++ b/pkg/remote/remote_direct.go @@ -95,8 +95,7 @@ func getRemoteDirectConfig(settings *environment.AirshipCTLSettings) (*config.Re } if len(docs) == 0 { return nil, "", document.ErrDocNotFound{ - Selector: ls, - Kind: AirshipHostKind, + Selector: selector, } }