From 28db50e6d65c77d53471c63e32377eb3bc3e20a2 Mon Sep 17 00:00:00 2001 From: Alan Meadows Date: Wed, 19 Feb 2020 12:10:38 +0400 Subject: [PATCH] [#45] iso generation pulls network data from ephemeral host This commit updates the iso generation process to pull the network data from the ephemeral host network data from the baremetalhost spec of the host with the right label, namely: airshipit.org/ephemeral-node=true It will back into the secret name and namespace specified for the given host and extract the network data from this secret which should be identical to the network configuration the host would receive during normal provisioning. It also pulls the user-data for the iso generation process which is specific to the iso generation process from a secret with a similar special label: airshipit.org/ephemeral-user-data=true Change-Id: Iae6edeb231d9dbae0b316aa73009f145d57ac316 --- pkg/bootstrap/cloudinit/cloud-init.go | 169 ++++++++++++------ pkg/bootstrap/cloudinit/cloud-init_test.go | 79 +++++--- pkg/bootstrap/cloudinit/errors.go | 11 ++ .../testdata/ephemeralduplicate.yaml | 36 ++++ .../cloudinit/testdata/ephemeralmissing.yaml | 27 +++ .../cloudinit/testdata/kustomization.yaml | 9 +- .../testdata/networkdatabadpointer.yaml | 38 ++++ .../testdata/networkdatamalformed.yaml | 50 ++++++ .../testdata/networkdatamissing.yaml | 46 +++++ pkg/bootstrap/cloudinit/testdata/secret.yaml | 41 ----- .../cloudinit/testdata/userdatamalformed.yaml | 12 ++ .../cloudinit/testdata/userdatamissing.yaml | 11 ++ .../cloudinit/testdata/validdocset.yaml | 66 +++++++ pkg/bootstrap/isogen/command.go | 3 +- pkg/bootstrap/isogen/testdata/secret.yaml | 65 ++++++- pkg/cluster/initinfra/infra.go | 2 +- pkg/document/bundle.go | 32 ++++ pkg/document/errors.go | 14 +- pkg/document/selectors.go | 12 ++ pkg/remote/remote_direct.go | 3 +- 20 files changed, 598 insertions(+), 128 deletions(-) create mode 100644 pkg/bootstrap/cloudinit/testdata/ephemeralduplicate.yaml create mode 100644 pkg/bootstrap/cloudinit/testdata/ephemeralmissing.yaml create mode 100644 pkg/bootstrap/cloudinit/testdata/networkdatabadpointer.yaml create mode 100644 pkg/bootstrap/cloudinit/testdata/networkdatamalformed.yaml create mode 100644 pkg/bootstrap/cloudinit/testdata/networkdatamissing.yaml delete mode 100644 pkg/bootstrap/cloudinit/testdata/secret.yaml create mode 100644 pkg/bootstrap/cloudinit/testdata/userdatamalformed.yaml create mode 100644 pkg/bootstrap/cloudinit/testdata/userdatamissing.yaml create mode 100644 pkg/bootstrap/cloudinit/testdata/validdocset.yaml 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, } }