From a2b5ff7a68a3ab8d900fcfb3cc72bcbaf0b9bf05 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 22 Sep 2023 14:55:42 -0600 Subject: [PATCH 01/15] Copy Rufio Machine v1alpha1 types from upstream: This allows us to not have to import Rufio. Rufio uses controller-runtime v0.15.0 (and soon v0.16.2). EKS Anywhere use v0.14.2. Upgrading EKS Anywhere means all other dependent libraries will need upgraded too. This is not feasible at the moment from a time perspective, there are too many to update. For example: capv, capc, capd, abhay-krishna/cluster-api, aws/etcdadm-bootstrap-provider, etc. Signed-off-by: Jacob Weinstock --- internal/test/envtest/environment.go | 1 + manager/main.go | 1 + 2 files changed, 2 insertions(+) diff --git a/internal/test/envtest/environment.go b/internal/test/envtest/environment.go index 7520e7a80748..ee7f7a1fdfe8 100644 --- a/internal/test/envtest/environment.go +++ b/internal/test/envtest/environment.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + rufiov1alpha1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1/thirdparty/tinkerbell/rufio" eksdv1alpha1 "github.com/aws/eks-distro-build-tooling/release/api/v1alpha1" etcdv1 "github.com/aws/etcdadm-controller/api/v1beta1" tinkerbellv1 "github.com/tinkerbell/cluster-api-provider-tinkerbell/api/v1beta1" diff --git a/manager/main.go b/manager/main.go index 3dd5dfee5399..777cc5db97b2 100644 --- a/manager/main.go +++ b/manager/main.go @@ -5,6 +5,7 @@ import ( "flag" "os" + rufiov1alpha1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1/thirdparty/tinkerbell/rufio" eksdv1alpha1 "github.com/aws/eks-distro-build-tooling/release/api/v1alpha1" etcdv1 "github.com/aws/etcdadm-controller/api/v1beta1" "github.com/go-logr/logr" From 2eb3549f0af2bfeb118c0429acddfc5b01b09e6e Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 22 Sep 2023 15:13:47 -0600 Subject: [PATCH 02/15] Fix linting issues Signed-off-by: Jacob Weinstock --- internal/test/envtest/environment.go | 1 - manager/main.go | 1 - 2 files changed, 2 deletions(-) diff --git a/internal/test/envtest/environment.go b/internal/test/envtest/environment.go index ee7f7a1fdfe8..7520e7a80748 100644 --- a/internal/test/envtest/environment.go +++ b/internal/test/envtest/environment.go @@ -9,7 +9,6 @@ import ( "strings" "testing" - rufiov1alpha1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1/thirdparty/tinkerbell/rufio" eksdv1alpha1 "github.com/aws/eks-distro-build-tooling/release/api/v1alpha1" etcdv1 "github.com/aws/etcdadm-controller/api/v1beta1" tinkerbellv1 "github.com/tinkerbell/cluster-api-provider-tinkerbell/api/v1beta1" diff --git a/manager/main.go b/manager/main.go index 777cc5db97b2..3dd5dfee5399 100644 --- a/manager/main.go +++ b/manager/main.go @@ -5,7 +5,6 @@ import ( "flag" "os" - rufiov1alpha1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1/thirdparty/tinkerbell/rufio" eksdv1alpha1 "github.com/aws/eks-distro-build-tooling/release/api/v1alpha1" etcdv1 "github.com/aws/etcdadm-controller/api/v1beta1" "github.com/go-logr/logr" From 6341f8827f70d639728baa24a9fcadeb5fc90147 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Wed, 27 Sep 2023 21:08:04 -0600 Subject: [PATCH 03/15] Add Rufio RPC options to hardware.Machine struct: CSV and yaml unmarshalling and writing secrets from the hardware.Machine RPC options were updated and added. This sets the stage for allowing the Rufio RPC provider options to be pulled in. These options will all come from env vars. Signed-off-by: Jacob Weinstock --- .../tinkerbell/hardware/catalogue_secret.go | 58 ++++++++++++---- .../hardware/catalogue_secret_test.go | 28 ++++++++ pkg/providers/tinkerbell/hardware/csv.go | 1 + pkg/providers/tinkerbell/hardware/machine.go | 69 +++++++++++++++++++ pkg/providers/tinkerbell/hardware/yaml.go | 12 +++- 5 files changed, 152 insertions(+), 16 deletions(-) diff --git a/pkg/providers/tinkerbell/hardware/catalogue_secret.go b/pkg/providers/tinkerbell/hardware/catalogue_secret.go index 0aa8ffc52770..452d04d408c8 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_secret.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_secret.go @@ -1,6 +1,8 @@ package hardware import ( + "fmt" + corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" @@ -78,25 +80,51 @@ func NewSecretCatalogueWriter(catalogue *Catalogue) *SecretCatalogueWriter { // Write converts m to a Tinkerbell BaseboardManagement and inserts it into w's Catalogue. func (w *SecretCatalogueWriter) Write(m Machine) error { if m.HasBMC() { - return w.catalogue.InsertSecret(baseboardManagementSecretFromMachine(m)) + for _, s := range baseboardManagementSecretFromMachine(m) { + if err := w.catalogue.InsertSecret(s); err != nil { + return err + } + } } return nil } -func baseboardManagementSecretFromMachine(m Machine) *corev1.Secret { - return &corev1.Secret{ - TypeMeta: newSecretTypeMeta(), - ObjectMeta: v1.ObjectMeta{ - Name: formatBMCSecretRef(m), - Namespace: constants.EksaSystemNamespace, - Labels: map[string]string{ - v1alpha3.ClusterctlMoveLabel: "true", +func baseboardManagementSecretFromMachine(m Machine) []*corev1.Secret { + var s []*corev1.Secret + + if m.BMCMachineOptions != nil && m.BMCMachineOptions.RPC.ConsumerURL != "" { + for idx, secret := range m.BMCMachineOptions.RPC.HMAC.Secrets { + s = append(s, &corev1.Secret{ + TypeMeta: newSecretTypeMeta(), + ObjectMeta: v1.ObjectMeta{ + Name: fmt.Sprintf("%v-%v", formatBMCSecretRef(m), idx), + Namespace: constants.EksaSystemNamespace, + Labels: map[string]string{ + v1alpha3.ClusterctlMoveLabel: "true", + }, + }, + Type: "Opaque", + Data: map[string][]byte{ + "secret": []byte(secret), + }, + }) + } + } else { + s = append(s, &corev1.Secret{ + TypeMeta: newSecretTypeMeta(), + ObjectMeta: v1.ObjectMeta{ + Name: formatBMCSecretRef(m), + Namespace: constants.EksaSystemNamespace, + Labels: map[string]string{ + v1alpha3.ClusterctlMoveLabel: "true", + }, + }, + Type: "kubernetes.io/basic-auth", + Data: map[string][]byte{ + "username": []byte(m.BMCUsername), + "password": []byte(m.BMCPassword), }, - }, - Type: "kubernetes.io/basic-auth", - Data: map[string][]byte{ - "username": []byte(m.BMCUsername), - "password": []byte(m.BMCPassword), - }, + }) } + return s } diff --git a/pkg/providers/tinkerbell/hardware/catalogue_secret_test.go b/pkg/providers/tinkerbell/hardware/catalogue_secret_test.go index 61c33dc719fb..920568888d18 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_secret_test.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_secret_test.go @@ -1,6 +1,7 @@ package hardware_test import ( + "fmt" "testing" "github.com/onsi/gomega" @@ -79,3 +80,30 @@ func TestSecretCatalogueWriter_Write(t *testing.T) { g.Expect(secrets[0].Data).To(gomega.HaveKeyWithValue("username", []byte(machine.BMCUsername))) g.Expect(secrets[0].Data).To(gomega.HaveKeyWithValue("password", []byte(machine.BMCPassword))) } + +// TestRPCSecrets add RPC secrets to the catalogue and verifies that they are added correctly. +func TestRPCSecrets(t *testing.T) { + g := gomega.NewWithT(t) + + catalogue := hardware.NewCatalogue() + writer := hardware.NewSecretCatalogueWriter(catalogue) + machine := NewValidMachine() + machine.BMCMachineOptions = &hardware.BMCMachineOptions{ + RPC: &hardware.RPCOpts{ + ConsumerURL: "http://localhost:8080", + HMAC: hardware.HMACOpts{ + Secrets: []string{"superSecret1", "superSecret2"}, + }, + }, + } + + err := writer.Write(machine) + g.Expect(err).To(gomega.Succeed()) + + secrets := catalogue.AllSecrets() + g.Expect(secrets).To(gomega.HaveLen(2)) + for idx, secret := range secrets { + g.Expect(secret.Name).To(gomega.ContainSubstring(fmt.Sprintf("bmc-%v-auth-%v", machine.Hostname, idx))) + g.Expect(secret.Data).To(gomega.HaveKeyWithValue("secret", []byte(fmt.Sprintf("superSecret%v", idx+1)))) + } +} diff --git a/pkg/providers/tinkerbell/hardware/csv.go b/pkg/providers/tinkerbell/hardware/csv.go index 1d8e1a02a4d8..f21ac1fa8c12 100644 --- a/pkg/providers/tinkerbell/hardware/csv.go +++ b/pkg/providers/tinkerbell/hardware/csv.go @@ -43,6 +43,7 @@ func (cr CSVReader) Read() (Machine, error) { if err != nil { return Machine{}, err } + return machine.(Machine), nil } diff --git a/pkg/providers/tinkerbell/hardware/machine.go b/pkg/providers/tinkerbell/hardware/machine.go index d84df0832293..924f342c3d58 100644 --- a/pkg/providers/tinkerbell/hardware/machine.go +++ b/pkg/providers/tinkerbell/hardware/machine.go @@ -2,6 +2,7 @@ package hardware import ( "fmt" + "net/http" "sort" "strings" ) @@ -27,6 +28,74 @@ type Machine struct { BMCUsername string `csv:"bmc_username, omitempty"` BMCPassword string `csv:"bmc_password, omitempty"` VLANID string `csv:"vlan_id, omitempty"` + + // BMCMachineOptions are the options used for Rufio providers. + BMCMachineOptions *BMCMachineOptions `csv:"-"` +} + +// BMCMachineOptions are the options used to configure the Rufio providers. +// Right now we only support the RPC provider. +type BMCMachineOptions struct { + // RPC are the options for the Rufio RPC provider. + RPC *RPCOpts `csv:"-"` +} + +// RPCOpts are the options used for the Rufio RPC provider. +type RPCOpts struct { + // ConsumerURL is the URL where an rpc consumer/listener is running + // and to which we will send and receive all notifications. + ConsumerURL string `csv:"-"` + // Request is the options used to create the rpc HTTP request. + Request RequestOpts `csv:"-"` + // Signature is the options used for adding an HMAC signature to an HTTP request. + Signature SignatureOpts `csv:"-"` + // HMAC is the options used to create a HMAC signature. + HMAC HMACOpts `csv:"-"` + // Experimental options. + Experimental ExperimentalOpts `csv:"-"` +} + +// ExperimentalOpts are the experimental options used in the Rufio RPC provider. +type ExperimentalOpts struct { + // CustomRequestPayload must be in json. + CustomRequestPayload string `csv:"-"` + // DotPath is the path to the json object where the bmclib RequestPayload{} struct will be embedded. For example: object.data.body + DotPath string `csv:"-"` +} + +// SignatureOpts are the options used for adding an HMAC signature to an HTTP request. +type SignatureOpts struct { + // HeaderName is the header name that should contain the signature(s). Example: X-BMCLIB-Signature + HeaderName string `csv:"-"` + // AppendAlgoToHeaderDisabled decides whether to append the algorithm to the signature header or not. + // Example: X-BMCLIB-Signature becomes X-BMCLIB-Signature-256 + // When set to true, a header will be added for each algorithm. Example: X-BMCLIB-Signature-256 and X-BMCLIB-Signature-512 + AppendAlgoToHeaderDisabled bool `csv:"-"` + // IncludedPayloadHeaders are headers whose values will be included in the signature payload. Example: X-BMCLIB-My-Custom-Header + // All headers will be deduplicated. + IncludedPayloadHeaders []string `csv:"-"` +} + +// RequestOpts are the options used to create the rpc HTTP request. +type RequestOpts struct { + // HTTPContentType is the content type to use for the rpc request notification. + HTTPContentType string `csv:"-"` + // HTTPMethod is the HTTP method to use for the rpc request notification. + HTTPMethod string `csv:"-"` + // StaticHeaders are predefined headers that will be added to every request. + StaticHeaders http.Header `csv:"-"` + // TimestampFormat is the time format for the timestamp header. + TimestampFormat string `csv:"-"` + // TimestampHeader is the header name that should contain the timestamp. Example: X-BMCLIB-Timestamp + TimestampHeader string `csv:"-"` +} + +// HMACOpts are the options used to create a HMAC signature. +type HMACOpts struct { + // PrefixSigDisabled determines whether the algorithm will be prefixed to the signature. Example: sha256=abc123 + PrefixSigDisabled bool `csv:"-"` + // Secrets used for signing. + Secrets []string `csv:"-"` } // HasBMC determines if m has a BMC configuration. A BMC configuration is present if any of the BMC fields diff --git a/pkg/providers/tinkerbell/hardware/yaml.go b/pkg/providers/tinkerbell/hardware/yaml.go index 573b05d7a756..477339782844 100644 --- a/pkg/providers/tinkerbell/hardware/yaml.go +++ b/pkg/providers/tinkerbell/hardware/yaml.go @@ -78,7 +78,17 @@ func marshalTinkerbellBMCYAML(m Machine) ([]byte, error) { } func marshalSecretYAML(m Machine) ([]byte, error) { - return yaml.Marshal(baseboardManagementSecretFromMachine(m)) + var final []byte + for _, s := range baseboardManagementSecretFromMachine(m) { + data, err := yaml.Marshal(s) + if err != nil { + return nil, err + } + final = append(final, data...) + final = append(final, yamlSeparatorWithNewline...) + } + + return final, nil } // CreateOrStdout will create path and return an *os.File if path is not empty. If path is empty From 330f3f5880c336d163125a550c23eb5bb12f7334 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Thu, 28 Sep 2023 10:56:10 -0600 Subject: [PATCH 04/15] Add population of options in toRufioMachine: This plumbs through non empty options from hardware.Machine to v1alpha1.Machine. Signed-off-by: Jacob Weinstock --- .../tinkerbell/hardware/catalogue_bmc.go | 146 +++++++++++++++++- .../tinkerbell/hardware/catalogue_bmc_test.go | 61 ++++++++ .../tinkerbell/hardware/validator_test.go | 44 ++++++ 3 files changed, 243 insertions(+), 8 deletions(-) diff --git a/pkg/providers/tinkerbell/hardware/catalogue_bmc.go b/pkg/providers/tinkerbell/hardware/catalogue_bmc.go index a71bfe06d8b2..48a083bf4287 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_bmc.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_bmc.go @@ -1,9 +1,12 @@ package hardware import ( + "fmt" + corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/aws/eks-anywhere/pkg/api/v1alpha1/thirdparty/tinkerbell/rufio" v1alpha1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1/thirdparty/tinkerbell/rufio" "github.com/aws/eks-anywhere/pkg/constants" ) @@ -87,6 +90,19 @@ func toRufioMachine(m Machine) *v1alpha1.Machine { // TODO(chrisdoherty4) // - Set the namespace to the CAPT namespace. // - Patch through insecure TLS. + conn := v1alpha1.Connection{ + Host: m.BMCIPAddress, + AuthSecretRef: corev1.SecretReference{ + Name: formatBMCSecretRef(m), + Namespace: constants.EksaSystemNamespace, + }, + InsecureTLS: true, + } + if m.BMCMachineOptions != nil && m.BMCMachineOptions.RPC.ConsumerURL != "" { + conn.ProviderOptions = &v1alpha1.ProviderOptions{ + RPC: toRPCOptions(m.BMCMachineOptions.RPC), + } + } return &v1alpha1.Machine{ TypeMeta: newMachineTypeMeta(), ObjectMeta: v1.ObjectMeta{ @@ -94,14 +110,128 @@ func toRufioMachine(m Machine) *v1alpha1.Machine { Namespace: constants.EksaSystemNamespace, }, Spec: v1alpha1.MachineSpec{ - Connection: v1alpha1.Connection{ - Host: m.BMCIPAddress, - AuthSecretRef: corev1.SecretReference{ - Name: formatBMCSecretRef(m), - Namespace: constants.EksaSystemNamespace, - }, - InsecureTLS: true, - }, + Connection: conn, }, } } + +func toRPCOptions(r *RPCOpts) *v1alpha1.RPCOptions { + opts := &v1alpha1.RPCOptions{ + ConsumerURL: r.ConsumerURL, + } + if req := toRequestOpts(r.Request); req != nil { + opts.Request = *req + } + if sig := toSignatureOpts(r.Signature); sig != nil { + opts.Signature = *sig + } + if hmac := toHMACOpts(r.HMAC); hmac != nil { + opts.HMAC = *hmac + } + if exp := toExperimentalOpts(r.Experimental); exp != nil { + opts.Experimental = *exp + } + + return opts +} + +func toRequestOpts(r RequestOpts) *v1alpha1.RequestOpts { + req := &v1alpha1.RequestOpts{} + empty := true + if r.HTTPContentType != "" { + req.HTTPContentType = r.HTTPContentType + empty = false + } + if r.HTTPMethod != "" { + req.HTTPMethod = r.HTTPMethod + empty = false + } + if r.TimestampFormat != "" { + req.TimestampFormat = r.TimestampFormat + empty = false + } + if r.TimestampHeader != "" { + req.TimestampHeader = r.TimestampHeader + empty = false + } + if len(r.StaticHeaders) > 0 { + req.StaticHeaders = r.StaticHeaders + empty = false + } + + if empty { + return nil + } + + return req +} + +func toSignatureOpts(s SignatureOpts) *v1alpha1.SignatureOpts { + sig := &v1alpha1.SignatureOpts{} + empty := true + if s.HeaderName != "" { + sig.HeaderName = s.HeaderName + empty = false + } + if s.AppendAlgoToHeaderDisabled { + sig.AppendAlgoToHeaderDisabled = s.AppendAlgoToHeaderDisabled + empty = false + } + if len(s.IncludedPayloadHeaders) > 0 { + sig.IncludedPayloadHeaders = s.IncludedPayloadHeaders + empty = false + } + + if empty { + return nil + } + + return sig +} + +func toHMACOpts(h HMACOpts) *v1alpha1.HMACOpts { + hmac := &v1alpha1.HMACOpts{} + empty := true + if h.PrefixSigDisabled { + hmac.PrefixSigDisabled = h.PrefixSigDisabled + empty = false + } + if len(h.Secrets) > 0 { + hmac.Secrets = make(map[rufio.HMACAlgorithm][]corev1.SecretReference) + for idx := range h.Secrets { + s := corev1.SecretReference{ + // TODO(jacobweinstock): get hostname from the machine object. + Name: fmt.Sprintf("%v-%v", "bmc-localhost-auth", idx), + Namespace: constants.EksaSystemNamespace, + } + hmac.Secrets[rufio.HMACAlgorithm("sha256")] = append(hmac.Secrets[rufio.HMACAlgorithm("sha256")], s) + hmac.Secrets[rufio.HMACAlgorithm("sha512")] = append(hmac.Secrets[rufio.HMACAlgorithm("sha512")], s) + } + empty = false + } + + if empty { + return nil + } + + return hmac +} + +func toExperimentalOpts(e ExperimentalOpts) *v1alpha1.ExperimentalOpts { + exp := &v1alpha1.ExperimentalOpts{} + empty := true + if e.CustomRequestPayload != "" { + exp.CustomRequestPayload = e.CustomRequestPayload + empty = false + } + if e.DotPath != "" { + exp.DotPath = e.DotPath + empty = false + } + + if empty { + return nil + } + + return exp +} diff --git a/pkg/providers/tinkerbell/hardware/catalogue_bmc_test.go b/pkg/providers/tinkerbell/hardware/catalogue_bmc_test.go index 4d55a3a5f444..8d5548e380fb 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_bmc_test.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_bmc_test.go @@ -4,9 +4,11 @@ import ( "testing" "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1alpha1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1/thirdparty/tinkerbell/rufio" + "github.com/aws/eks-anywhere/pkg/constants" "github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware" ) @@ -79,3 +81,62 @@ func TestBMCCatalogueWriter_Write(t *testing.T) { g.Expect(bmcs[0].Spec.Connection.Host).To(gomega.Equal(machine.BMCIPAddress)) g.Expect(bmcs[0].Spec.Connection.AuthSecretRef.Name).To(gomega.ContainSubstring(machine.Hostname)) } + +func TestBMCMachineWithOptions(t *testing.T) { + g := gomega.NewWithT(t) + + catalogue := hardware.NewCatalogue() + writer := hardware.NewBMCCatalogueWriter(catalogue) + machine := NewMachineWithOptions() + want := &v1alpha1.Machine{Spec: v1alpha1.MachineSpec{ + Connection: v1alpha1.Connection{ + Host: "10.10.10.11", + Port: 0, + AuthSecretRef: v1.SecretReference{ + Name: "bmc-localhost-auth", + Namespace: constants.EksaSystemNamespace, + }, + InsecureTLS: true, + ProviderOptions: &v1alpha1.ProviderOptions{ + RPC: &v1alpha1.RPCOptions{ + ConsumerURL: "https://example.net", + Request: v1alpha1.RequestOpts{ + HTTPContentType: "application/vnd.api+json", + HTTPMethod: "POST", + StaticHeaders: map[string][]string{"myheader": {"myvalue"}}, + TimestampFormat: "2006-01-02T15:04:05Z07:00", // time.RFC3339 + TimestampHeader: "X-Example-Timestamp", + }, + Signature: v1alpha1.SignatureOpts{ + HeaderName: "X-Example-Signature", + AppendAlgoToHeaderDisabled: true, + IncludedPayloadHeaders: []string{"X-Example-Timestamp"}, + }, + HMAC: v1alpha1.HMACOpts{ + PrefixSigDisabled: true, + Secrets: map[v1alpha1.HMACAlgorithm][]v1.SecretReference{ + v1alpha1.HMACAlgorithm("sha256"): { + {Name: "bmc-localhost-auth-0", Namespace: constants.EksaSystemNamespace}, + {Name: "bmc-localhost-auth-1", Namespace: constants.EksaSystemNamespace}, + }, + v1alpha1.HMACAlgorithm("sha512"): { + {Name: "bmc-localhost-auth-0", Namespace: constants.EksaSystemNamespace}, + {Name: "bmc-localhost-auth-1", Namespace: constants.EksaSystemNamespace}, + }, + }, + }, + Experimental: v1alpha1.ExperimentalOpts{ + CustomRequestPayload: `{"data":{"type":"articles","id":"1","attributes":{"title": "Rails is Omakase"},"relationships":{"author":{"links":{"self":"/articles/1/relationships/author","related":"/articles/1/author"},"data":null}}}}`, + DotPath: "data.relationships.author.data", + }, + }, + }, + }, + }} + + err := writer.Write(machine) + g.Expect(err).To(gomega.Succeed()) + + got := catalogue.AllBMCs()[0] + g.Expect(got.Spec).To(gomega.Equal(want.Spec)) +} diff --git a/pkg/providers/tinkerbell/hardware/validator_test.go b/pkg/providers/tinkerbell/hardware/validator_test.go index bdc6f833fd0f..d74d029da2fc 100644 --- a/pkg/providers/tinkerbell/hardware/validator_test.go +++ b/pkg/providers/tinkerbell/hardware/validator_test.go @@ -2,7 +2,9 @@ package hardware_test import ( "errors" + "net/http" "testing" + "time" "github.com/onsi/gomega" @@ -242,3 +244,45 @@ func NewValidMachine() hardware.Machine { VLANID: "200", } } + +func NewMachineWithOptions() hardware.Machine { + return hardware.Machine{ + IPAddress: "10.10.10.10", + Gateway: "10.10.10.1", + Nameservers: []string{"ns1"}, + MACAddress: "00:00:00:00:00:00", + Netmask: "255.255.255.255", + Hostname: "localhost", + Labels: hardware.Labels{"type": "cp"}, + Disk: "/dev/sda", + BMCIPAddress: "10.10.10.11", + BMCUsername: "username", + BMCPassword: "password", + VLANID: "200", + BMCMachineOptions: &hardware.BMCMachineOptions{ + RPC: &hardware.RPCOpts{ + ConsumerURL: "https://example.net", + Request: hardware.RequestOpts{ + HTTPContentType: "application/vnd.api+json", + HTTPMethod: http.MethodPost, + StaticHeaders: map[string][]string{"myheader": {"myvalue"}}, + TimestampFormat: time.RFC3339, + TimestampHeader: "X-Example-Timestamp", + }, + Signature: hardware.SignatureOpts{ + HeaderName: "X-Example-Signature", + AppendAlgoToHeaderDisabled: true, + IncludedPayloadHeaders: []string{"X-Example-Timestamp"}, + }, + HMAC: hardware.HMACOpts{ + PrefixSigDisabled: true, + Secrets: []string{"superSecret1", "superSecret2"}, + }, + Experimental: hardware.ExperimentalOpts{ + CustomRequestPayload: `{"data":{"type":"articles","id":"1","attributes":{"title": "Rails is Omakase"},"relationships":{"author":{"links":{"self":"/articles/1/relationships/author","related":"/articles/1/author"},"data":null}}}}`, + DotPath: "data.relationships.author.data", + }, + }, + }, + } +} From 0c86fe854e363d42f82348ca93e361cff3e04f4d Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Thu, 28 Sep 2023 13:09:36 -0600 Subject: [PATCH 05/15] Name change: The "Machine" in BMCMachineOptions didnt provide any additional value over just BMCOptions. Signed-off-by: Jacob Weinstock --- pkg/providers/tinkerbell/hardware/catalogue_bmc.go | 4 ++-- pkg/providers/tinkerbell/hardware/catalogue_secret.go | 4 ++-- .../tinkerbell/hardware/catalogue_secret_test.go | 2 +- pkg/providers/tinkerbell/hardware/machine.go | 8 ++++---- pkg/providers/tinkerbell/hardware/validator_test.go | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/providers/tinkerbell/hardware/catalogue_bmc.go b/pkg/providers/tinkerbell/hardware/catalogue_bmc.go index 48a083bf4287..c87836eb4bd1 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_bmc.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_bmc.go @@ -98,9 +98,9 @@ func toRufioMachine(m Machine) *v1alpha1.Machine { }, InsecureTLS: true, } - if m.BMCMachineOptions != nil && m.BMCMachineOptions.RPC.ConsumerURL != "" { + if m.BMCOptions != nil && m.BMCOptions.RPC.ConsumerURL != "" { conn.ProviderOptions = &v1alpha1.ProviderOptions{ - RPC: toRPCOptions(m.BMCMachineOptions.RPC), + RPC: toRPCOptions(m.BMCOptions.RPC), } } return &v1alpha1.Machine{ diff --git a/pkg/providers/tinkerbell/hardware/catalogue_secret.go b/pkg/providers/tinkerbell/hardware/catalogue_secret.go index 452d04d408c8..24c266e1c65c 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_secret.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_secret.go @@ -92,8 +92,8 @@ func (w *SecretCatalogueWriter) Write(m Machine) error { func baseboardManagementSecretFromMachine(m Machine) []*corev1.Secret { var s []*corev1.Secret - if m.BMCMachineOptions != nil && m.BMCMachineOptions.RPC.ConsumerURL != "" { - for idx, secret := range m.BMCMachineOptions.RPC.HMAC.Secrets { + if m.BMCOptions != nil && m.BMCOptions.RPC.ConsumerURL != "" { + for idx, secret := range m.BMCOptions.RPC.HMAC.Secrets { s = append(s, &corev1.Secret{ TypeMeta: newSecretTypeMeta(), ObjectMeta: v1.ObjectMeta{ diff --git a/pkg/providers/tinkerbell/hardware/catalogue_secret_test.go b/pkg/providers/tinkerbell/hardware/catalogue_secret_test.go index 920568888d18..82b1a2a4bfa3 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_secret_test.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_secret_test.go @@ -88,7 +88,7 @@ func TestRPCSecrets(t *testing.T) { catalogue := hardware.NewCatalogue() writer := hardware.NewSecretCatalogueWriter(catalogue) machine := NewValidMachine() - machine.BMCMachineOptions = &hardware.BMCMachineOptions{ + machine.BMCOptions = &hardware.BMCOptions{ RPC: &hardware.RPCOpts{ ConsumerURL: "http://localhost:8080", HMAC: hardware.HMACOpts{ diff --git a/pkg/providers/tinkerbell/hardware/machine.go b/pkg/providers/tinkerbell/hardware/machine.go index 924f342c3d58..e4e0190527ac 100644 --- a/pkg/providers/tinkerbell/hardware/machine.go +++ b/pkg/providers/tinkerbell/hardware/machine.go @@ -29,13 +29,13 @@ type Machine struct { BMCPassword string `csv:"bmc_password, omitempty"` VLANID string `csv:"vlan_id, omitempty"` - // BMCMachineOptions are the options used for Rufio providers. - BMCMachineOptions *BMCMachineOptions `csv:"-"` + // BMCOptions are the options used for Rufio providers. + BMCOptions *BMCOptions `csv:"-"` } -// BMCMachineOptions are the options used to configure the Rufio providers. +// BMCOptions are the options used to configure the Rufio providers. // Right now we only support the RPC provider. -type BMCMachineOptions struct { +type BMCOptions struct { // RPC are the options for the Rufio RPC provider. RPC *RPCOpts `csv:"-"` } diff --git a/pkg/providers/tinkerbell/hardware/validator_test.go b/pkg/providers/tinkerbell/hardware/validator_test.go index d74d029da2fc..ef9396b9b582 100644 --- a/pkg/providers/tinkerbell/hardware/validator_test.go +++ b/pkg/providers/tinkerbell/hardware/validator_test.go @@ -259,7 +259,7 @@ func NewMachineWithOptions() hardware.Machine { BMCUsername: "username", BMCPassword: "password", VLANID: "200", - BMCMachineOptions: &hardware.BMCMachineOptions{ + BMCOptions: &hardware.BMCOptions{ RPC: &hardware.RPCOpts{ ConsumerURL: "https://example.net", Request: hardware.RequestOpts{ From 0651f64da50259b344eb2d52b905d3c0d4db471d Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Thu, 28 Sep 2023 13:56:13 -0600 Subject: [PATCH 06/15] Enable CLI flags to be mapped to env vars: This enables passing env vars instead of cli flags. Will be used for BMC secrets. --- cmd/eksctl-anywhere/cmd/flags.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cmd/eksctl-anywhere/cmd/flags.go b/cmd/eksctl-anywhere/cmd/flags.go index 49b12fb6e027..6ce557433489 100644 --- a/cmd/eksctl-anywhere/cmd/flags.go +++ b/cmd/eksctl-anywhere/cmd/flags.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "log" + "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -25,14 +26,24 @@ https://anywhere.eks.amazonaws.com/docs/troubleshooting/troubleshooting/#bootstr ) func bindFlagsToViper(cmd *cobra.Command, args []string) error { - var err error cmd.Flags().VisitAll(func(flag *pflag.Flag) { - if err != nil { + if err := viper.BindPFlag(flag.Name, flag); err != nil { return } - err = viper.BindPFlag(flag.Name, flag) + viper.AutomaticEnv() + // Environment variables can't have dashes in them, so bind them to their equivalent + // keys with underscores, e.g. --hardware-csv to HARDWARE_CSV + viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + // viper.AutomaticEnv() needs help with dashes in flag names. + if !flag.Changed && viper.IsSet(flag.Name) { + val := viper.Get(flag.Name) + if err := cmd.Flags().Set(flag.Name, fmt.Sprintf("%v", val)); err != nil { + return + } + } }) - return err + + return nil } func applyClusterOptionFlags(flagSet *pflag.FlagSet, clusterOpt *clusterOptions) { From e4357fd56857c40cf87d5bb454613a3fbda7dc48 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Thu, 28 Sep 2023 20:09:37 -0600 Subject: [PATCH 07/15] Plumb bmc options through from cli to provider: Adds cli flags for all bmc options. The flags are hidden by default but the cli mechanism we use also makes them available as env vars. The dependencies factory is updated with a new func parameter for provider options that will allow future modifications without breaking the fn signature. the cmd/eksctl-anywhere/cmd/flags pkg was renamed to be singular. It was named similar to the pflags library but with "a" for anywhere. The csv reader was modified to take in bmc options so that the options can be added during a Read. Signed-off-by: Jacob Weinstock --- cmd/eksctl-anywhere/cmd/aflag/aflag.go | 44 ++++++++++++ .../cmd/{flags => aflag}/cluster.go | 2 +- .../cmd/{flags/require.go => aflag/marker.go} | 11 ++- .../require_test.go => aflag/marker_test.go} | 8 +-- cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go | 68 +++++++++++++++++++ cmd/eksctl-anywhere/cmd/createcluster.go | 44 ++++++++++-- cmd/eksctl-anywhere/cmd/deletecluster.go | 15 +++- cmd/eksctl-anywhere/cmd/flags.go | 6 +- cmd/eksctl-anywhere/cmd/flags/flag.go | 23 ------- cmd/eksctl-anywhere/cmd/flags/tinkerbell.go | 8 --- .../generate_tinkerbell_template_config.go | 10 +-- .../cmd/generatebundleconfig.go | 17 +---- cmd/eksctl-anywhere/cmd/generatehardware.go | 28 +++++--- cmd/eksctl-anywhere/cmd/supportbundle.go | 16 +---- cmd/eksctl-anywhere/cmd/upgradecluster.go | 19 ++++-- cmd/eksctl-anywhere/cmd/upgradeplancluster.go | 16 +---- .../cmd/validatecreatecluster.go | 15 +++- pkg/dependencies/factory.go | 18 ++++- pkg/dependencies/factory_test.go | 66 ++++++++++-------- pkg/providers/tinkerbell/create.go | 2 +- .../tinkerbell/hardware/catalogue_bmc.go | 18 ++--- .../tinkerbell/hardware/catalogue_bmc_test.go | 8 +-- pkg/providers/tinkerbell/hardware/csv.go | 20 ++++-- pkg/providers/tinkerbell/hardware/csv_test.go | 14 ++-- .../tinkerbell/hardware/validator.go | 14 ++-- pkg/providers/tinkerbell/tinkerbell.go | 2 + pkg/providers/tinkerbell/upgrade.go | 2 +- 27 files changed, 340 insertions(+), 174 deletions(-) create mode 100644 cmd/eksctl-anywhere/cmd/aflag/aflag.go rename cmd/eksctl-anywhere/cmd/{flags => aflag}/cluster.go (96%) rename cmd/eksctl-anywhere/cmd/{flags/require.go => aflag/marker.go} (55%) rename cmd/eksctl-anywhere/cmd/{flags/require_test.go => aflag/marker_test.go} (94%) create mode 100644 cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go delete mode 100644 cmd/eksctl-anywhere/cmd/flags/flag.go delete mode 100644 cmd/eksctl-anywhere/cmd/flags/tinkerbell.go diff --git a/cmd/eksctl-anywhere/cmd/aflag/aflag.go b/cmd/eksctl-anywhere/cmd/aflag/aflag.go new file mode 100644 index 000000000000..f7396ae89107 --- /dev/null +++ b/cmd/eksctl-anywhere/cmd/aflag/aflag.go @@ -0,0 +1,44 @@ +// Package aflag is the eks anywhere flag handling package. +package aflag + +import "github.com/spf13/pflag" + +// Flag defines a CLI flag. +type Flag[T any] struct { + Name string + Short string + Usage string + Default T +} + +// String applies f to fs and writes the value to dst. +func String(f Flag[string], dst *string, fs *pflag.FlagSet) { + switch { + // With short form + case f.Short != "": + fs.StringVarP(dst, f.Name, f.Short, f.Default, f.Usage) + // Without short form + default: + fs.StringVar(dst, f.Name, f.Default, f.Usage) + } +} + +// Bool applies f to fs and writes the value to dst. +func Bool(f Flag[bool], dst *bool, fs *pflag.FlagSet) { + switch { + case f.Short != "": + fs.BoolVarP(dst, f.Name, f.Short, f.Default, f.Usage) + default: + fs.BoolVar(dst, f.Name, f.Default, f.Usage) + } +} + +// StringSlice applies f to fs and writes the value to dst. +func StringSlice(f Flag[[]string], dst *[]string, fs *pflag.FlagSet) { + switch { + case f.Short != "": + fs.StringSliceVarP(dst, f.Name, f.Short, f.Default, f.Usage) + default: + fs.StringSliceVar(dst, f.Name, f.Default, f.Usage) + } +} diff --git a/cmd/eksctl-anywhere/cmd/flags/cluster.go b/cmd/eksctl-anywhere/cmd/aflag/cluster.go similarity index 96% rename from cmd/eksctl-anywhere/cmd/flags/cluster.go rename to cmd/eksctl-anywhere/cmd/aflag/cluster.go index 5d1a0c5f2b7e..c1016306486b 100644 --- a/cmd/eksctl-anywhere/cmd/flags/cluster.go +++ b/cmd/eksctl-anywhere/cmd/aflag/cluster.go @@ -1,4 +1,4 @@ -package flags +package aflag // ClusterConfig is the path to a cluster specification YAML. var ClusterConfig = Flag[string]{ diff --git a/cmd/eksctl-anywhere/cmd/flags/require.go b/cmd/eksctl-anywhere/cmd/aflag/marker.go similarity index 55% rename from cmd/eksctl-anywhere/cmd/flags/require.go rename to cmd/eksctl-anywhere/cmd/aflag/marker.go index aba7bbaba80e..a22417ec9db9 100644 --- a/cmd/eksctl-anywhere/cmd/flags/require.go +++ b/cmd/eksctl-anywhere/cmd/aflag/marker.go @@ -1,4 +1,4 @@ -package flags +package aflag import ( "github.com/spf13/cobra" @@ -13,3 +13,12 @@ func MarkRequired(set *pflag.FlagSet, flags ...string) { } } } + +// MarkHidden is a helper to mark flags hidden on cmd. If a flag does not exist, it panics. +func MarkHidden(set *pflag.FlagSet, flags ...string) { + for _, flag := range flags { + if err := set.MarkHidden(flag); err != nil { + panic(err) + } + } +} diff --git a/cmd/eksctl-anywhere/cmd/flags/require_test.go b/cmd/eksctl-anywhere/cmd/aflag/marker_test.go similarity index 94% rename from cmd/eksctl-anywhere/cmd/flags/require_test.go rename to cmd/eksctl-anywhere/cmd/aflag/marker_test.go index fa86fdd3c107..41228219813d 100644 --- a/cmd/eksctl-anywhere/cmd/flags/require_test.go +++ b/cmd/eksctl-anywhere/cmd/aflag/marker_test.go @@ -1,4 +1,4 @@ -package flags_test +package aflag_test import ( "testing" @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/flags" + "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/aflag" ) func TestMarkRequired(t *testing.T) { @@ -77,7 +77,7 @@ func TestMarkRequired(t *testing.T) { // so we need to parse the args using the flag set before calling it. _ = cmd.Flags().Parse(tc.Args) - flags.MarkRequired(cmd.Flags(), required...) + aflag.MarkRequired(cmd.Flags(), required...) err := cmd.ValidateRequiredFlags() @@ -109,7 +109,7 @@ func TestMarkRequired_FlagDoesNotExist(t *testing.T) { }() flgs := pflag.NewFlagSet("", pflag.ContinueOnError) - flags.MarkRequired(flgs, "does-not-exist") + aflag.MarkRequired(flgs, "does-not-exist") } func nopPflag(name string) pflag.Flag { diff --git a/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go b/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go new file mode 100644 index 000000000000..9b23115abcf8 --- /dev/null +++ b/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go @@ -0,0 +1,68 @@ +package aflag + +// TinkerbellBootstrapIP is used to override the Tinkerbell IP for serving a Tinkerbell stack +// from an admin machine. +var TinkerbellBootstrapIP = Flag[string]{ + Name: "tinkerbell-bootstrap-ip", + Usage: "The IP used to expose the Tinkerbell stack from the bootstrap cluster", +} + +// TinkerbellBMCConsumerURL is a Rufio RPC provider option. +var TinkerbellBMCConsumerURL = Flag[string]{ + Name: "tinkerbell-bmc-consumer-url", + Usage: "The URL used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +} + +// TinkerbellBMCHTTPContentType is a Rufio RPC provider option. +var TinkerbellBMCHTTPContentType = Flag[string]{ + Name: "tinkerbell-bmc-http-content-type", + Usage: "The HTTP content type used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +} + +// TinkerbellBMCHTTPMethod is a Rufio RPC provider option. +var TinkerbellBMCHTTPMethod = Flag[string]{ + Name: "tinkerbell-bmc-http-method", + Usage: "The HTTP method used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +} + +// TinkerbellBMCTimestampHeader is a Rufio RPC provider option. +var TinkerbellBMCTimestampHeader = Flag[string]{ + Name: "tinkerbell-bmc-timestamp-header", + Usage: "The HTTP timestamp header used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +} + +// TinkerbellBMCStaticHeaders is a Rufio RPC provider option. +var TinkerbellBMCStaticHeaders = Flag[string]{ + Name: "tinkerbell-bmc-static-headers", + Usage: "The HTTP static headers used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +} + +// TinkerbellBMCHeaderName is a Rufio RPC provider option. +var TinkerbellBMCHeaderName = Flag[string]{ + Name: "tinkerbell-bmc-header-name", + Usage: "The HTTP header name used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +} + +// TinkerbellBMCAppendAlgoToHeaderDisabled is a Rufio RPC provider option. +var TinkerbellBMCAppendAlgoToHeaderDisabled = Flag[bool]{ + Name: "tinkerbell-bmc-append-algo-to-header-disabled", + Usage: "The HTTP append algo to header disabled used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +} + +// TinkerbellBMCIncludedPayloadHeaders is a Rufio RPC provider option. +var TinkerbellBMCIncludedPayloadHeaders = Flag[[]string]{ + Name: "tinkerbell-bmc-included-payload-headers", + Usage: "The HTTP included payload headers used to expose the Tinkerbell BMC consumer from the bootstrap cluster. If you specify a Timestamp header, it must be included here.", +} + +// TinkerbellBMCPrefixSigDisabled is a Rufio RPC provider option. +var TinkerbellBMCPrefixSigDisabled = Flag[bool]{ + Name: "tinkerbell-bmc-prefix-sig-disabled", + Usage: "The HTTP prefix sig disabled used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +} + +// TinkerbellBMCWebhookSecrets is a Rufio RPC provider option. +var TinkerbellBMCWebhookSecrets = Flag[[]string]{ + Name: "tinkerbell-bmc-webhook-secrets", + Usage: "The webhook secrets used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +} diff --git a/cmd/eksctl-anywhere/cmd/createcluster.go b/cmd/eksctl-anywhere/cmd/createcluster.go index dbc2f0564042..c3d0713d6c63 100644 --- a/cmd/eksctl-anywhere/cmd/createcluster.go +++ b/cmd/eksctl-anywhere/cmd/createcluster.go @@ -6,8 +6,9 @@ import ( "strings" "github.com/spf13/cobra" + "github.com/spf13/pflag" - "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/flags" + "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/aflag" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/awsiamauth" "github.com/aws/eks-anywhere/pkg/clustermanager" @@ -16,6 +17,7 @@ import ( "github.com/aws/eks-anywhere/pkg/features" "github.com/aws/eks-anywhere/pkg/kubeconfig" "github.com/aws/eks-anywhere/pkg/logger" + "github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware" "github.com/aws/eks-anywhere/pkg/types" "github.com/aws/eks-anywhere/pkg/validations" "github.com/aws/eks-anywhere/pkg/validations/createvalidations" @@ -32,9 +34,18 @@ type createClusterOptions struct { tinkerbellBootstrapIP string installPackages string skipValidations []string + providerOptions *dependencies.ProviderOptions } -var cc = &createClusterOptions{} +var cc = &createClusterOptions{ + providerOptions: &dependencies.ProviderOptions{ + Tinkerbell: &dependencies.TinkerbellOptions{ + BMCOptions: &hardware.BMCOptions{ + RPC: &hardware.RPCOpts{}, + }, + }, + }, +} var createClusterCmd = &cobra.Command{ Use: "cluster -f [flags]", @@ -50,14 +61,37 @@ func init() { applyClusterOptionFlags(createClusterCmd.Flags(), &cc.clusterOptions) applyTimeoutFlags(createClusterCmd.Flags(), &cc.timeoutOptions) applyTinkerbellHardwareFlag(createClusterCmd.Flags(), &cc.hardwareCSVPath) - flags.String(flags.TinkerbellBootstrapIP, &cc.tinkerbellBootstrapIP, createClusterCmd.Flags()) + aflag.String(aflag.TinkerbellBootstrapIP, &cc.tinkerbellBootstrapIP, createClusterCmd.Flags()) createClusterCmd.Flags().BoolVar(&cc.forceClean, "force-cleanup", false, "Force deletion of previously created bootstrap cluster") hideForceCleanup(createClusterCmd.Flags()) createClusterCmd.Flags().BoolVar(&cc.skipIpCheck, "skip-ip-check", false, "Skip check for whether cluster control plane ip is in use") createClusterCmd.Flags().StringVar(&cc.installPackages, "install-packages", "", "Location of curated packages configuration files to install to the cluster") createClusterCmd.Flags().StringArrayVar(&cc.skipValidations, "skip-validations", []string{}, fmt.Sprintf("Bypass create validations by name. Valid arguments you can pass are --skip-validations=%s", strings.Join(createvalidations.SkippableValidations[:], ","))) + tinkerbellFlags(createClusterCmd.Flags(), cc.providerOptions.Tinkerbell.BMCOptions.RPC) + + aflag.MarkRequired(createClusterCmd.Flags(), aflag.ClusterConfig.Name) +} - flags.MarkRequired(createClusterCmd.Flags(), flags.ClusterConfig.Name) +func tinkerbellFlags(fs *pflag.FlagSet, r *hardware.RPCOpts) { + aflag.String(aflag.TinkerbellBMCConsumerURL, &r.ConsumerURL, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCConsumerURL.Name) + aflag.String(aflag.TinkerbellBMCHTTPContentType, &r.Request.HTTPContentType, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCHTTPContentType.Name) + aflag.String(aflag.TinkerbellBMCHTTPMethod, &r.Request.HTTPMethod, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCHTTPMethod.Name) + aflag.String(aflag.TinkerbellBMCTimestampHeader, &r.Request.TimestampHeader, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCTimestampHeader.Name) + // add static headers here + aflag.String(aflag.TinkerbellBMCHeaderName, &r.Signature.HeaderName, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCHeaderName.Name) + aflag.Bool(aflag.TinkerbellBMCAppendAlgoToHeaderDisabled, &r.Signature.AppendAlgoToHeaderDisabled, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCAppendAlgoToHeaderDisabled.Name) + aflag.StringSlice(aflag.TinkerbellBMCIncludedPayloadHeaders, &r.Signature.IncludedPayloadHeaders, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCIncludedPayloadHeaders.Name) + aflag.Bool(aflag.TinkerbellBMCPrefixSigDisabled, &r.HMAC.PrefixSigDisabled, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCPrefixSigDisabled.Name) + aflag.StringSlice(aflag.TinkerbellBMCWebhookSecrets, &r.HMAC.Secrets, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCWebhookSecrets.Name) } func (cc *createClusterOptions) createCluster(cmd *cobra.Command, _ []string) error { @@ -141,7 +175,7 @@ func (cc *createClusterOptions) createCluster(cmd *cobra.Command, _ []string) er WithBootstrapper(). WithCliConfig(cliConfig). WithClusterManager(clusterSpec.Cluster, clusterManagerTimeoutOpts). - WithProvider(cc.fileName, clusterSpec.Cluster, cc.skipIpCheck, cc.hardwareCSVPath, cc.forceClean, cc.tinkerbellBootstrapIP, skippedValidations). + WithProvider(cc.fileName, clusterSpec.Cluster, cc.skipIpCheck, cc.hardwareCSVPath, cc.forceClean, cc.tinkerbellBootstrapIP, skippedValidations, cc.providerOptions). WithGitOpsFlux(clusterSpec.Cluster, clusterSpec.FluxConfig, cliConfig). WithWriter(). WithEksdInstaller(). diff --git a/cmd/eksctl-anywhere/cmd/deletecluster.go b/cmd/eksctl-anywhere/cmd/deletecluster.go index f5c92afc83a6..f653de634b44 100644 --- a/cmd/eksctl-anywhere/cmd/deletecluster.go +++ b/cmd/eksctl-anywhere/cmd/deletecluster.go @@ -10,6 +10,7 @@ import ( "github.com/aws/eks-anywhere/pkg/dependencies" "github.com/aws/eks-anywhere/pkg/kubeconfig" "github.com/aws/eks-anywhere/pkg/logger" + "github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware" "github.com/aws/eks-anywhere/pkg/types" "github.com/aws/eks-anywhere/pkg/validations" "github.com/aws/eks-anywhere/pkg/workflows" @@ -21,9 +22,18 @@ type deleteClusterOptions struct { forceCleanup bool hardwareFileName string tinkerbellBootstrapIP string + providerOptions *dependencies.ProviderOptions } -var dc = &deleteClusterOptions{} +var dc = &deleteClusterOptions{ + providerOptions: &dependencies.ProviderOptions{ + Tinkerbell: &dependencies.TinkerbellOptions{ + BMCOptions: &hardware.BMCOptions{ + RPC: &hardware.RPCOpts{}, + }, + }, + }, +} var deleteClusterCmd = &cobra.Command{ Use: "cluster (|-f )", @@ -50,6 +60,7 @@ func init() { hideForceCleanup(deleteClusterCmd.Flags()) deleteClusterCmd.Flags().StringVar(&dc.managementKubeconfig, "kubeconfig", "", "kubeconfig file pointing to a management cluster") deleteClusterCmd.Flags().StringVar(&dc.bundlesOverride, "bundles-override", "", "Override default Bundles manifest (not recommended)") + tinkerbellFlags(deleteClusterCmd.Flags(), dc.providerOptions.Tinkerbell.BMCOptions.RPC) } func (dc *deleteClusterOptions) validate(ctx context.Context, args []string) error { @@ -101,7 +112,7 @@ func (dc *deleteClusterOptions) deleteCluster(ctx context.Context) error { WithBootstrapper(). WithCliConfig(cliConfig). WithClusterManager(clusterSpec.Cluster, nil). - WithProvider(dc.fileName, clusterSpec.Cluster, cc.skipIpCheck, dc.hardwareFileName, false, dc.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(dc.fileName, clusterSpec.Cluster, cc.skipIpCheck, dc.hardwareFileName, false, dc.tinkerbellBootstrapIP, map[string]bool{}, dc.providerOptions). WithGitOpsFlux(clusterSpec.Cluster, clusterSpec.FluxConfig, cliConfig). WithWriter(). Build(ctx) diff --git a/cmd/eksctl-anywhere/cmd/flags.go b/cmd/eksctl-anywhere/cmd/flags.go index 6ce557433489..e7c7283f1ab6 100644 --- a/cmd/eksctl-anywhere/cmd/flags.go +++ b/cmd/eksctl-anywhere/cmd/flags.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/flags" + "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/aflag" "github.com/aws/eks-anywhere/pkg/validations" ) @@ -47,8 +47,8 @@ func bindFlagsToViper(cmd *cobra.Command, args []string) error { } func applyClusterOptionFlags(flagSet *pflag.FlagSet, clusterOpt *clusterOptions) { - flags.String(flags.ClusterConfig, &clusterOpt.fileName, flagSet) - flags.String(flags.BundleOverride, &clusterOpt.bundlesOverride, flagSet) + aflag.String(aflag.ClusterConfig, &clusterOpt.fileName, flagSet) + aflag.String(aflag.BundleOverride, &clusterOpt.bundlesOverride, flagSet) flagSet.StringVar(&clusterOpt.managementKubeconfig, "kubeconfig", "", "Management cluster kubeconfig file") } diff --git a/cmd/eksctl-anywhere/cmd/flags/flag.go b/cmd/eksctl-anywhere/cmd/flags/flag.go deleted file mode 100644 index 963acecac628..000000000000 --- a/cmd/eksctl-anywhere/cmd/flags/flag.go +++ /dev/null @@ -1,23 +0,0 @@ -package flags - -import "github.com/spf13/pflag" - -// Flag defines a CLI flag. -type Flag[T any] struct { - Name string - Short string - Usage string - Default T -} - -// String applies f to fs and writes the value to dst. -func String(f Flag[string], dst *string, fs *pflag.FlagSet) { - switch { - // With short form - case f.Short != "": - fs.StringVarP(dst, f.Name, f.Short, f.Default, f.Usage) - // Without short form - default: - fs.StringVar(dst, f.Name, f.Default, f.Usage) - } -} diff --git a/cmd/eksctl-anywhere/cmd/flags/tinkerbell.go b/cmd/eksctl-anywhere/cmd/flags/tinkerbell.go deleted file mode 100644 index 44c1d05dcd57..000000000000 --- a/cmd/eksctl-anywhere/cmd/flags/tinkerbell.go +++ /dev/null @@ -1,8 +0,0 @@ -package flags - -// TinkerbellBootstrapIP is used to override the Tinkerbell IP for serving a Tinkerbell stack -// from an admin machine. -var TinkerbellBootstrapIP = Flag[string]{ - Name: "tinkerbell-bootstrap-ip", - Usage: "The IP used to expose the Tinkerbell stack from the bootstrap cluster", -} diff --git a/cmd/eksctl-anywhere/cmd/generate_tinkerbell_template_config.go b/cmd/eksctl-anywhere/cmd/generate_tinkerbell_template_config.go index 058eb1826598..69061be2c008 100644 --- a/cmd/eksctl-anywhere/cmd/generate_tinkerbell_template_config.go +++ b/cmd/eksctl-anywhere/cmd/generate_tinkerbell_template_config.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/flags" + "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/aflag" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/logger" "github.com/aws/eks-anywhere/pkg/networkutils" @@ -42,9 +42,9 @@ func NewGenerateTinkerbellTemplateConfig() *cobra.Command { // Configure the flagset. Some of these flags are duplicated from other parts of the cmd code // for consistency but their descriptions may vary because of the commands use-case. flgs := pflag.NewFlagSet("", pflag.ContinueOnError) - flags.String(flags.ClusterConfig, &opts.fileName, flgs) - flags.String(flags.BundleOverride, &opts.bundlesOverride, flgs) - flags.String(flags.TinkerbellBootstrapIP, &opts.BootstrapTinkerbellIP, flgs) + aflag.String(aflag.ClusterConfig, &opts.fileName, flgs) + aflag.String(aflag.BundleOverride, &opts.bundlesOverride, flgs) + aflag.String(aflag.TinkerbellBootstrapIP, &opts.BootstrapTinkerbellIP, flgs) cmd := &cobra.Command{ Use: "tinkerbelltemplateconfig", @@ -54,7 +54,7 @@ func NewGenerateTinkerbellTemplateConfig() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { // When the bootstrap IP is unspecified attempt to derive it from IPs assigned to the // primary interface. - if f := flgs.Lookup(flags.TinkerbellBootstrapIP.Name); !f.Changed { + if f := flgs.Lookup(aflag.TinkerbellBootstrapIP.Name); !f.Changed { bootstrapIP, err := networkutils.GetLocalIP() if err != nil { return fmt.Errorf("tinkerbell bootstrap ip: %v", err) diff --git a/cmd/eksctl-anywhere/cmd/generatebundleconfig.go b/cmd/eksctl-anywhere/cmd/generatebundleconfig.go index 8c42af4cb85f..57ea017e0276 100644 --- a/cmd/eksctl-anywhere/cmd/generatebundleconfig.go +++ b/cmd/eksctl-anywhere/cmd/generatebundleconfig.go @@ -3,11 +3,8 @@ package cmd import ( "context" "fmt" - "log" "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/dependencies" @@ -29,7 +26,7 @@ var generateBundleConfigCmd = &cobra.Command{ Use: "support-bundle-config", Short: "Generate support bundle config", Long: "This command is used to generate a default support bundle config yaml", - PreRunE: preRunGenerateBundleConfigCmd, + PreRunE: bindFlagsToViper, RunE: func(cmd *cobra.Command, args []string) error { err := gsbo.validateCmdInput() if err != nil { @@ -52,16 +49,6 @@ func init() { generateBundleConfigCmd.Flags().StringVarP(&gsbo.fileName, "filename", "f", "", "Filename that contains EKS-A cluster configuration") } -func preRunGenerateBundleConfigCmd(cmd *cobra.Command, args []string) error { - cmd.Flags().VisitAll(func(flag *pflag.Flag) { - err := viper.BindPFlag(flag.Name, flag) - if err != nil { - log.Fatalf("Error initializing flags: %v", err) - } - }) - return nil -} - func (gsbo *generateSupportBundleOptions) validateCmdInput() error { f := gsbo.fileName if f != "" { @@ -89,7 +76,7 @@ func (gsbo *generateSupportBundleOptions) generateBundleConfig(ctx context.Conte } deps, err := dependencies.ForSpec(ctx, clusterSpec). - WithProvider(clusterConfigPath, clusterSpec.Cluster, cc.skipIpCheck, gsbo.hardwareFileName, false, gsbo.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(clusterConfigPath, clusterSpec.Cluster, cc.skipIpCheck, gsbo.hardwareFileName, false, gsbo.tinkerbellBootstrapIP, map[string]bool{}, nil). WithDiagnosticBundleFactory(). Build(ctx) if err != nil { diff --git a/cmd/eksctl-anywhere/cmd/generatehardware.go b/cmd/eksctl-anywhere/cmd/generatehardware.go index 7ddb6f9af424..0f565d8d3840 100644 --- a/cmd/eksctl-anywhere/cmd/generatehardware.go +++ b/cmd/eksctl-anywhere/cmd/generatehardware.go @@ -6,15 +6,25 @@ import ( "github.com/spf13/cobra" + "github.com/aws/eks-anywhere/pkg/dependencies" "github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware" ) type hardwareOptions struct { - csvPath string - outputPath string + csvPath string + outputPath string + providerOptions *dependencies.ProviderOptions } -var hOpts = &hardwareOptions{} +var hOpts = &hardwareOptions{ + providerOptions: &dependencies.ProviderOptions{ + Tinkerbell: &dependencies.TinkerbellOptions{ + BMCOptions: &hardware.BMCOptions{ + RPC: &hardware.RPCOpts{}, + }, + }, + }, +} var generateHardwareCmd = &cobra.Command{ Use: "hardware", @@ -22,15 +32,16 @@ var generateHardwareCmd = &cobra.Command{ Long: ` Generate Kubernetes hardware YAML manifests for each Hardware entry in the source. `, - RunE: hOpts.generateHardware, + RunE: hOpts.generateHardware, + PreRunE: bindFlagsToViper, } func init() { generateCmd.AddCommand(generateHardwareCmd) - flags := generateHardwareCmd.Flags() - flags.StringVarP(&hOpts.outputPath, "output", "o", "", "Path to output hardware YAML.") - flags.StringVarP( + fset := generateHardwareCmd.Flags() + fset.StringVarP(&hOpts.outputPath, "output", "o", "", "Path to output hardware YAML.") + fset.StringVarP( &hOpts.csvPath, TinkerbellHardwareCSVFlagName, TinkerbellHardwareCSVFlagAlias, @@ -41,10 +52,11 @@ func init() { if err := generateHardwareCmd.MarkFlagRequired(TinkerbellHardwareCSVFlagName); err != nil { panic(err) } + tinkerbellFlags(fset, hOpts.providerOptions.Tinkerbell.BMCOptions.RPC) } func (hOpts *hardwareOptions) generateHardware(cmd *cobra.Command, args []string) error { - hardwareYaml, err := hardware.BuildHardwareYAML(hOpts.csvPath) + hardwareYaml, err := hardware.BuildHardwareYAML(hOpts.csvPath, hOpts.providerOptions.Tinkerbell.BMCOptions) if err != nil { return fmt.Errorf("building hardware yaml from csv: %v", err) } diff --git a/cmd/eksctl-anywhere/cmd/supportbundle.go b/cmd/eksctl-anywhere/cmd/supportbundle.go index 1916a376434b..d576cfee876e 100644 --- a/cmd/eksctl-anywhere/cmd/supportbundle.go +++ b/cmd/eksctl-anywhere/cmd/supportbundle.go @@ -7,8 +7,6 @@ import ( "time" "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" "github.com/aws/eks-anywhere/pkg/dependencies" "github.com/aws/eks-anywhere/pkg/diagnostics" @@ -32,7 +30,7 @@ var supportbundleCmd = &cobra.Command{ Use: "support-bundle -f my-cluster.yaml", Short: "Generate a support bundle", Long: "This command is used to create a support bundle to troubleshoot a cluster", - PreRunE: preRunSupportBundle, + PreRunE: bindFlagsToViper, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { if err := csbo.validate(cmd.Context()); err != nil { @@ -72,16 +70,6 @@ func (csbo *createSupportBundleOptions) validate(ctx context.Context) error { return nil } -func preRunSupportBundle(cmd *cobra.Command, args []string) error { - cmd.Flags().VisitAll(func(flag *pflag.Flag) { - err := viper.BindPFlag(flag.Name, flag) - if err != nil { - log.Fatalf("Error initializing flags: %v", err) - } - }) - return nil -} - func (csbo *createSupportBundleOptions) createBundle(ctx context.Context, since, sinceTime, bundleConfig string) error { clusterSpec, err := readAndValidateClusterSpec(csbo.fileName, version.Get()) if err != nil { @@ -89,7 +77,7 @@ func (csbo *createSupportBundleOptions) createBundle(ctx context.Context, since, } deps, err := dependencies.ForSpec(ctx, clusterSpec). - WithProvider(csbo.fileName, clusterSpec.Cluster, cc.skipIpCheck, csbo.hardwareFileName, false, csbo.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(csbo.fileName, clusterSpec.Cluster, cc.skipIpCheck, csbo.hardwareFileName, false, csbo.tinkerbellBootstrapIP, map[string]bool{}, nil). WithDiagnosticBundleFactory(). Build(ctx) if err != nil { diff --git a/cmd/eksctl-anywhere/cmd/upgradecluster.go b/cmd/eksctl-anywhere/cmd/upgradecluster.go index 12e2098ab953..26557d583789 100644 --- a/cmd/eksctl-anywhere/cmd/upgradecluster.go +++ b/cmd/eksctl-anywhere/cmd/upgradecluster.go @@ -8,12 +8,13 @@ import ( "github.com/spf13/cobra" - "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/flags" + "github.com/aws/eks-anywhere/cmd/eksctl-anywhere/cmd/aflag" "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/dependencies" "github.com/aws/eks-anywhere/pkg/features" "github.com/aws/eks-anywhere/pkg/kubeconfig" "github.com/aws/eks-anywhere/pkg/logger" + "github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware" "github.com/aws/eks-anywhere/pkg/types" "github.com/aws/eks-anywhere/pkg/validations" "github.com/aws/eks-anywhere/pkg/validations/upgradevalidations" @@ -29,9 +30,18 @@ type upgradeClusterOptions struct { hardwareCSVPath string tinkerbellBootstrapIP string skipValidations []string + providerOptions *dependencies.ProviderOptions } -var uc = &upgradeClusterOptions{} +var uc = &upgradeClusterOptions{ + providerOptions: &dependencies.ProviderOptions{ + Tinkerbell: &dependencies.TinkerbellOptions{ + BMCOptions: &hardware.BMCOptions{ + RPC: &hardware.RPCOpts{}, + }, + }, + }, +} var upgradeClusterCmd = &cobra.Command{ Use: "cluster", @@ -62,7 +72,8 @@ func init() { hideForceCleanup(upgradeClusterCmd.Flags()) upgradeClusterCmd.Flags().StringArrayVar(&uc.skipValidations, "skip-validations", []string{}, fmt.Sprintf("Bypass upgrade validations by name. Valid arguments you can pass are --skip-validations=%s", strings.Join(upgradevalidations.SkippableValidations[:], ","))) - flags.MarkRequired(createClusterCmd.Flags(), flags.ClusterConfig.Name) + aflag.MarkRequired(createClusterCmd.Flags(), aflag.ClusterConfig.Name) + tinkerbellFlags(upgradeClusterCmd.Flags(), uc.providerOptions.Tinkerbell.BMCOptions.RPC) } func (uc *upgradeClusterOptions) upgradeCluster(cmd *cobra.Command) error { @@ -129,7 +140,7 @@ func (uc *upgradeClusterOptions) upgradeCluster(cmd *cobra.Command) error { WithCliConfig(cliConfig). WithClusterManager(clusterSpec.Cluster, clusterManagerTimeoutOpts). WithClusterApplier(). - WithProvider(uc.fileName, clusterSpec.Cluster, cc.skipIpCheck, uc.hardwareCSVPath, uc.forceClean, uc.tinkerbellBootstrapIP, skippedValidations). + WithProvider(uc.fileName, clusterSpec.Cluster, cc.skipIpCheck, uc.hardwareCSVPath, uc.forceClean, uc.tinkerbellBootstrapIP, skippedValidations, uc.providerOptions). WithGitOpsFlux(clusterSpec.Cluster, clusterSpec.FluxConfig, cliConfig). WithWriter(). WithCAPIManager(). diff --git a/cmd/eksctl-anywhere/cmd/upgradeplancluster.go b/cmd/eksctl-anywhere/cmd/upgradeplancluster.go index 9cc2b01d34bb..0f163be9e4a3 100644 --- a/cmd/eksctl-anywhere/cmd/upgradeplancluster.go +++ b/cmd/eksctl-anywhere/cmd/upgradeplancluster.go @@ -9,8 +9,6 @@ import ( "text/tabwriter" "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" capiupgrader "github.com/aws/eks-anywhere/pkg/clusterapi" eksaupgrader "github.com/aws/eks-anywhere/pkg/clustermanager" @@ -34,7 +32,7 @@ var upgradePlanClusterCmd = &cobra.Command{ Use: "cluster", Short: "Provides new release versions for the next cluster upgrade", Long: "Provides a list of target versions for upgrading the core components in the workload cluster", - PreRunE: preRunUpgradePlanCluster, + PreRunE: bindFlagsToViper, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { if err := uc.upgradePlanCluster(cmd.Context()); err != nil { @@ -44,16 +42,6 @@ var upgradePlanClusterCmd = &cobra.Command{ }, } -func preRunUpgradePlanCluster(cmd *cobra.Command, args []string) error { - cmd.Flags().VisitAll(func(flag *pflag.Flag) { - err := viper.BindPFlag(flag.Name, flag) - if err != nil { - log.Fatalf("Error initializing flags: %v", err) - } - }) - return nil -} - func init() { upgradePlanCmd.AddCommand(upgradePlanClusterCmd) upgradePlanClusterCmd.Flags().StringVarP(&uc.fileName, "filename", "f", "", "Filename that contains EKS-A cluster configuration") @@ -78,7 +66,7 @@ func (uc *upgradeClusterOptions) upgradePlanCluster(ctx context.Context) error { deps, err := dependencies.ForSpec(ctx, newClusterSpec). WithClusterManager(newClusterSpec.Cluster, nil). - WithProvider(uc.fileName, newClusterSpec.Cluster, false, uc.hardwareCSVPath, uc.forceClean, uc.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(uc.fileName, newClusterSpec.Cluster, false, uc.hardwareCSVPath, uc.forceClean, uc.tinkerbellBootstrapIP, map[string]bool{}, uc.providerOptions). WithGitOpsFlux(newClusterSpec.Cluster, newClusterSpec.FluxConfig, nil). WithCAPIManager(). Build(ctx) diff --git a/cmd/eksctl-anywhere/cmd/validatecreatecluster.go b/cmd/eksctl-anywhere/cmd/validatecreatecluster.go index de3bda127f4f..c63855a8b2b8 100644 --- a/cmd/eksctl-anywhere/cmd/validatecreatecluster.go +++ b/cmd/eksctl-anywhere/cmd/validatecreatecluster.go @@ -9,6 +9,7 @@ import ( "github.com/aws/eks-anywhere/pkg/api/v1alpha1" "github.com/aws/eks-anywhere/pkg/dependencies" "github.com/aws/eks-anywhere/pkg/kubeconfig" + "github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware" "github.com/aws/eks-anywhere/pkg/types" "github.com/aws/eks-anywhere/pkg/validations" "github.com/aws/eks-anywhere/pkg/validations/createcluster" @@ -20,9 +21,18 @@ type validateOptions struct { clusterOptions hardwareCSVPath string tinkerbellBootstrapIP string + providerOptions *dependencies.ProviderOptions } -var valOpt = &validateOptions{} +var valOpt = &validateOptions{ + providerOptions: &dependencies.ProviderOptions{ + Tinkerbell: &dependencies.TinkerbellOptions{ + BMCOptions: &hardware.BMCOptions{ + RPC: &hardware.RPCOpts{}, + }, + }, + }, +} var validateCreateClusterCmd = &cobra.Command{ Use: "cluster -f [flags]", @@ -42,6 +52,7 @@ func init() { if err := validateCreateClusterCmd.MarkFlagRequired("filename"); err != nil { log.Fatalf("Error marking flag as required: %v", err) } + tinkerbellFlags(validateCreateClusterCmd.Flags(), valOpt.providerOptions.Tinkerbell.BMCOptions.RPC) } func (valOpt *validateOptions) validateCreateCluster(cmd *cobra.Command, _ []string) error { @@ -73,7 +84,7 @@ func (valOpt *validateOptions) validateCreateCluster(cmd *cobra.Command, _ []str WithWriterFolder(tmpPath). WithDocker(). WithKubectl(). - WithProvider(valOpt.fileName, clusterSpec.Cluster, false, valOpt.hardwareCSVPath, true, valOpt.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(valOpt.fileName, clusterSpec.Cluster, false, valOpt.hardwareCSVPath, true, valOpt.tinkerbellBootstrapIP, map[string]bool{}, valOpt.providerOptions). WithGitOpsFlux(clusterSpec.Cluster, clusterSpec.FluxConfig, cliConfig). WithUnAuthKubeClient(). WithValidatorClients(). diff --git a/pkg/dependencies/factory.go b/pkg/dependencies/factory.go index 51c158080574..d8494436cfad 100644 --- a/pkg/dependencies/factory.go +++ b/pkg/dependencies/factory.go @@ -47,6 +47,7 @@ import ( "github.com/aws/eks-anywhere/pkg/providers/nutanix" "github.com/aws/eks-anywhere/pkg/providers/snow" "github.com/aws/eks-anywhere/pkg/providers/tinkerbell" + "github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware" "github.com/aws/eks-anywhere/pkg/providers/validator" "github.com/aws/eks-anywhere/pkg/providers/vsphere" "github.com/aws/eks-anywhere/pkg/registrymirror" @@ -381,8 +382,20 @@ func (f *Factory) WithExecutableBuilder() *Factory { return f } +// ProviderOptions contains per provider options. +type ProviderOptions struct { + // Tinkerbell contains Tinkerbell specific options. + Tinkerbell *TinkerbellOptions +} + +// TinkerbellOptions contains Tinkerbell specific options. +type TinkerbellOptions struct { + // BMCOptions contains options for configuring BMC interactions. + BMCOptions *hardware.BMCOptions +} + // WithProvider initializes the provider dependency and adds to the build steps. -func (f *Factory) WithProvider(clusterConfigFile string, clusterConfig *v1alpha1.Cluster, skipIPCheck bool, hardwareCSVPath string, force bool, tinkerbellBootstrapIP string, skippedValidations map[string]bool) *Factory { // nolint:gocyclo +func (f *Factory) WithProvider(clusterConfigFile string, clusterConfig *v1alpha1.Cluster, skipIPCheck bool, hardwareCSVPath string, force bool, tinkerbellBootstrapIP string, skippedValidations map[string]bool, opts *ProviderOptions) *Factory { // nolint:gocyclo switch clusterConfig.Spec.DatacenterRef.Kind { case v1alpha1.VSphereDatacenterKind: f.WithKubectl().WithGovc().WithWriter().WithIPValidator() @@ -489,6 +502,9 @@ func (f *Factory) WithProvider(clusterConfigFile string, clusterConfig *v1alpha1 if err != nil { return err } + if opts != nil && opts.Tinkerbell != nil && opts.Tinkerbell.BMCOptions != nil { + provider.BMCOptions = opts.Tinkerbell.BMCOptions + } f.dependencies.Provider = provider diff --git a/pkg/dependencies/factory_test.go b/pkg/dependencies/factory_test.go index b419a1706098..b5d9e5c86241 100644 --- a/pkg/dependencies/factory_test.go +++ b/pkg/dependencies/factory_test.go @@ -18,6 +18,7 @@ import ( "github.com/aws/eks-anywhere/pkg/dependencies" "github.com/aws/eks-anywhere/pkg/executables" "github.com/aws/eks-anywhere/pkg/providers/cloudstack/decoder" + "github.com/aws/eks-anywhere/pkg/providers/tinkerbell/hardware" "github.com/aws/eks-anywhere/pkg/registrymirror" "github.com/aws/eks-anywhere/pkg/version" "github.com/aws/eks-anywhere/release/api/v1alpha1" @@ -33,6 +34,7 @@ type factoryTest struct { cliConfig config.CliConfig createCLIConfig config.CreateClusterCLIConfig upgradeCLIConfig config.UpgradeClusterCLIConfig + providerOptions *dependencies.ProviderOptions } type provider string @@ -45,44 +47,50 @@ const ( ) func newTest(t *testing.T, p provider) *factoryTest { - var clusterConfigFile string + f := &factoryTest{ + WithT: NewGomegaWithT(t), + ctx: context.Background(), + createCLIConfig: config.CreateClusterCLIConfig{ + SkipCPIPCheck: false, + }, + upgradeCLIConfig: config.UpgradeClusterCLIConfig{ + NodeStartupTimeout: 5 * time.Minute, + UnhealthyMachineTimeout: 5 * time.Minute, + }, + } + switch p { case vsphere: - clusterConfigFile = "testdata/cluster_vsphere.yaml" + f.clusterConfigFile = "testdata/cluster_vsphere.yaml" case tinkerbell: - clusterConfigFile = "testdata/cluster_tinkerbell.yaml" + f.clusterConfigFile = "testdata/cluster_tinkerbell.yaml" + f.providerOptions = &dependencies.ProviderOptions{ + Tinkerbell: &dependencies.TinkerbellOptions{ + BMCOptions: &hardware.BMCOptions{ + RPC: &hardware.RPCOpts{ + ConsumerURL: "http://example.com", + }, + }, + }, + } case nutanix: - clusterConfigFile = "testdata/nutanix/cluster_nutanix.yaml" + f.clusterConfigFile = "testdata/nutanix/cluster_nutanix.yaml" case snow: - clusterConfigFile = "testdata/snow/cluster_snow.yaml" + f.clusterConfigFile = "testdata/snow/cluster_snow.yaml" default: t.Fatalf("Not a valid provider: %v", p) } - createCLIConfig := config.CreateClusterCLIConfig{ - SkipCPIPCheck: false, - } + f.clusterSpec = test.NewFullClusterSpec(t, f.clusterConfigFile) - upgradeCLIConfig := config.UpgradeClusterCLIConfig{ - NodeStartupTimeout: 5 * time.Minute, - UnhealthyMachineTimeout: 5 * time.Minute, - } - - return &factoryTest{ - WithT: NewGomegaWithT(t), - clusterConfigFile: clusterConfigFile, - clusterSpec: test.NewFullClusterSpec(t, clusterConfigFile), - ctx: context.Background(), - createCLIConfig: createCLIConfig, - upgradeCLIConfig: upgradeCLIConfig, - } + return f } func TestFactoryBuildWithProvidervSphere(t *testing.T) { tt := newTest(t, vsphere) deps, err := dependencies.NewFactory(). WithLocalExecutables(). - WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}, tt.providerOptions). Build(context.Background()) tt.Expect(err).To(BeNil()) @@ -94,7 +102,7 @@ func TestFactoryBuildWithProviderTinkerbell(t *testing.T) { tt := newTest(t, tinkerbell) deps, err := dependencies.NewFactory(). WithLocalExecutables(). - WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}, tt.providerOptions). Build(context.Background()) tt.Expect(err).To(BeNil()) @@ -110,7 +118,7 @@ func TestFactoryBuildWithProviderSnow(t *testing.T) { deps, err := dependencies.NewFactory(). WithLocalExecutables(). - WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}, tt.providerOptions). Build(context.Background()) tt.Expect(err).To(BeNil()) @@ -143,7 +151,7 @@ func TestFactoryBuildWithProviderNutanix(t *testing.T) { deps, err := dependencies.NewFactory(). WithLocalExecutables(). - WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}, tt.providerOptions). WithNutanixValidator(). Build(context.Background()) @@ -164,7 +172,7 @@ func TestFactoryBuildWithInvalidProvider(t *testing.T) { deps, err := dependencies.NewFactory(). WithLocalExecutables(). - WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}, tt.providerOptions). Build(context.Background()) tt.Expect(err).NotTo(BeNil()) @@ -209,7 +217,7 @@ func TestFactoryBuildWithMultipleDependencies(t *testing.T) { WithBootstrapper(). WithCliConfig(&tt.cliConfig). WithClusterManager(tt.clusterSpec.Cluster, timeoutOpts). - WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}, tt.providerOptions). WithGitOpsFlux(tt.clusterSpec.Cluster, tt.clusterSpec.FluxConfig, nil). WithWriter(). WithEksdInstaller(). @@ -525,7 +533,7 @@ func TestFactoryBuildWithCNIInstallerCilium(t *testing.T) { factory := dependencies.NewFactory() deps, err := factory. WithLocalExecutables(). - WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}, tt.providerOptions). Build(tt.ctx) tt.Expect(err).To(BeNil()) @@ -547,7 +555,7 @@ func TestFactoryBuildWithCNIInstallerKindnetd(t *testing.T) { factory := dependencies.NewFactory() deps, err := factory. WithLocalExecutables(). - WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}). + WithProvider(tt.clusterConfigFile, tt.clusterSpec.Cluster, false, tt.hardwareConfigFile, false, tt.tinkerbellBootstrapIP, map[string]bool{}, tt.providerOptions). Build(tt.ctx) tt.Expect(err).To(BeNil()) diff --git a/pkg/providers/tinkerbell/create.go b/pkg/providers/tinkerbell/create.go index 1d95a639ed6d..d6207f97962a 100644 --- a/pkg/providers/tinkerbell/create.go +++ b/pkg/providers/tinkerbell/create.go @@ -233,7 +233,7 @@ func (p *Provider) readCSVToCatalogue() error { // Translate all Machine instances from the p.machines source into Kubernetes object types. // The PostBootstrapSetup() call invoked elsewhere in the program serializes the catalogue // and submits it to the clsuter. - machines, err := hardware.NewNormalizedCSVReaderFromFile(p.hardwareCSVFile) + machines, err := hardware.NewNormalizedCSVReaderFromFile(p.hardwareCSVFile, p.BMCOptions) if err != nil { return err } diff --git a/pkg/providers/tinkerbell/hardware/catalogue_bmc.go b/pkg/providers/tinkerbell/hardware/catalogue_bmc.go index c87836eb4bd1..d1cecacff45a 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_bmc.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_bmc.go @@ -100,7 +100,7 @@ func toRufioMachine(m Machine) *v1alpha1.Machine { } if m.BMCOptions != nil && m.BMCOptions.RPC.ConsumerURL != "" { conn.ProviderOptions = &v1alpha1.ProviderOptions{ - RPC: toRPCOptions(m.BMCOptions.RPC), + RPC: toRPCOptions(m.BMCOptions.RPC, m), } } return &v1alpha1.Machine{ @@ -115,21 +115,21 @@ func toRufioMachine(m Machine) *v1alpha1.Machine { } } -func toRPCOptions(r *RPCOpts) *v1alpha1.RPCOptions { +func toRPCOptions(r *RPCOpts, m Machine) *v1alpha1.RPCOptions { opts := &v1alpha1.RPCOptions{ ConsumerURL: r.ConsumerURL, } if req := toRequestOpts(r.Request); req != nil { - opts.Request = *req + opts.Request = req } if sig := toSignatureOpts(r.Signature); sig != nil { - opts.Signature = *sig + opts.Signature = sig } - if hmac := toHMACOpts(r.HMAC); hmac != nil { - opts.HMAC = *hmac + if hmac := toHMACOpts(r.HMAC, m); hmac != nil { + opts.HMAC = hmac } if exp := toExperimentalOpts(r.Experimental); exp != nil { - opts.Experimental = *exp + opts.Experimental = exp } return opts @@ -189,7 +189,7 @@ func toSignatureOpts(s SignatureOpts) *v1alpha1.SignatureOpts { return sig } -func toHMACOpts(h HMACOpts) *v1alpha1.HMACOpts { +func toHMACOpts(h HMACOpts, m Machine) *v1alpha1.HMACOpts { hmac := &v1alpha1.HMACOpts{} empty := true if h.PrefixSigDisabled { @@ -201,7 +201,7 @@ func toHMACOpts(h HMACOpts) *v1alpha1.HMACOpts { for idx := range h.Secrets { s := corev1.SecretReference{ // TODO(jacobweinstock): get hostname from the machine object. - Name: fmt.Sprintf("%v-%v", "bmc-localhost-auth", idx), + Name: fmt.Sprintf("%v-%v", formatBMCSecretRef(m), idx), Namespace: constants.EksaSystemNamespace, } hmac.Secrets[rufio.HMACAlgorithm("sha256")] = append(hmac.Secrets[rufio.HMACAlgorithm("sha256")], s) diff --git a/pkg/providers/tinkerbell/hardware/catalogue_bmc_test.go b/pkg/providers/tinkerbell/hardware/catalogue_bmc_test.go index 8d5548e380fb..3c6154f8c33d 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_bmc_test.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_bmc_test.go @@ -100,19 +100,19 @@ func TestBMCMachineWithOptions(t *testing.T) { ProviderOptions: &v1alpha1.ProviderOptions{ RPC: &v1alpha1.RPCOptions{ ConsumerURL: "https://example.net", - Request: v1alpha1.RequestOpts{ + Request: &v1alpha1.RequestOpts{ HTTPContentType: "application/vnd.api+json", HTTPMethod: "POST", StaticHeaders: map[string][]string{"myheader": {"myvalue"}}, TimestampFormat: "2006-01-02T15:04:05Z07:00", // time.RFC3339 TimestampHeader: "X-Example-Timestamp", }, - Signature: v1alpha1.SignatureOpts{ + Signature: &v1alpha1.SignatureOpts{ HeaderName: "X-Example-Signature", AppendAlgoToHeaderDisabled: true, IncludedPayloadHeaders: []string{"X-Example-Timestamp"}, }, - HMAC: v1alpha1.HMACOpts{ + HMAC: &v1alpha1.HMACOpts{ PrefixSigDisabled: true, Secrets: map[v1alpha1.HMACAlgorithm][]v1.SecretReference{ v1alpha1.HMACAlgorithm("sha256"): { @@ -125,7 +125,7 @@ func TestBMCMachineWithOptions(t *testing.T) { }, }, }, - Experimental: v1alpha1.ExperimentalOpts{ + Experimental: &v1alpha1.ExperimentalOpts{ CustomRequestPayload: `{"data":{"type":"articles","id":"1","attributes":{"title": "Rails is Omakase"},"relationships":{"author":{"links":{"self":"/articles/1/relationships/author","related":"/articles/1/author"},"data":null}}}}`, DotPath: "data.relationships.author.data", }, diff --git a/pkg/providers/tinkerbell/hardware/csv.go b/pkg/providers/tinkerbell/hardware/csv.go index f21ac1fa8c12..4f57788c50db 100644 --- a/pkg/providers/tinkerbell/hardware/csv.go +++ b/pkg/providers/tinkerbell/hardware/csv.go @@ -18,11 +18,13 @@ import ( // the Machine is optional in the CSV. If unspecified, CSVReader will generate a UUID and apply it to the machine. type CSVReader struct { reader *csv.Unmarshaller + // BMCOptions used in a Machine that do not have a corresponding column in the CSV. + BMCOptions *BMCOptions } // NewCSVReader returns a new CSVReader instance that consumes csv data from r. r should return io.EOF when no more // records are available. -func NewCSVReader(r io.Reader) (CSVReader, error) { +func NewCSVReader(r io.Reader, opts *BMCOptions) (CSVReader, error) { stdreader := stdcsv.NewReader(r) reader, err := csv.NewUnmarshaller(stdreader, Machine{}) @@ -34,7 +36,7 @@ func NewCSVReader(r io.Reader) (CSVReader, error) { return CSVReader{}, err } - return CSVReader{reader: reader}, nil + return CSVReader{reader: reader, BMCOptions: opts}, nil } // Read reads a single entry from the CSV data source and returns a new Machine representation. @@ -43,19 +45,23 @@ func (cr CSVReader) Read() (Machine, error) { if err != nil { return Machine{}, err } + m := machine.(Machine) + if cr.BMCOptions != nil { + m.BMCOptions = cr.BMCOptions + } - return machine.(Machine), nil + return m, nil } // NewNormalizedCSVReaderFromFile creates a MachineReader instance backed by a CSVReader reading from path // that applies default normalizations to machines. -func NewNormalizedCSVReaderFromFile(path string) (MachineReader, error) { +func NewNormalizedCSVReaderFromFile(path string, opts *BMCOptions) (MachineReader, error) { fh, err := os.Open(path) if err != nil { return CSVReader{}, err } - reader, err := NewCSVReader(bufio.NewReader(fh)) + reader, err := NewCSVReader(bufio.NewReader(fh), opts) if err != nil { return nil, err } @@ -93,8 +99,8 @@ func ensureRequiredColumnsInCSV(unmatched []string) error { } // BuildHardwareYAML builds a hardware yaml from the csv at the provided path. -func BuildHardwareYAML(path string) ([]byte, error) { - reader, err := NewNormalizedCSVReaderFromFile(path) +func BuildHardwareYAML(path string, opts *BMCOptions) ([]byte, error) { + reader, err := NewNormalizedCSVReaderFromFile(path, opts) if err != nil { return nil, fmt.Errorf("reading csv: %v", err) } diff --git a/pkg/providers/tinkerbell/hardware/csv_test.go b/pkg/providers/tinkerbell/hardware/csv_test.go index 48d7fcad8ef4..91adb1741f2c 100644 --- a/pkg/providers/tinkerbell/hardware/csv_test.go +++ b/pkg/providers/tinkerbell/hardware/csv_test.go @@ -25,7 +25,7 @@ func TestCSVReaderReads(t *testing.T) { err := csv.MarshalCSV([]hardware.Machine{expect}, buf) g.Expect(err).ToNot(gomega.HaveOccurred()) - reader, err := hardware.NewCSVReader(buf.Buffer) + reader, err := hardware.NewCSVReader(buf.Buffer, nil) g.Expect(err).ToNot(gomega.HaveOccurred()) machine, err := reader.Read() @@ -45,7 +45,7 @@ func TestCSVReaderWithMultipleLabels(t *testing.T) { err := csv.MarshalCSV([]hardware.Machine{expect}, buf) g.Expect(err).ToNot(gomega.HaveOccurred()) - reader, err := hardware.NewCSVReader(buf.Buffer) + reader, err := hardware.NewCSVReader(buf.Buffer, nil) g.Expect(err).ToNot(gomega.HaveOccurred()) machine, err := reader.Read() @@ -56,7 +56,7 @@ func TestCSVReaderWithMultipleLabels(t *testing.T) { func TestCSVReaderFromFile(t *testing.T) { g := gomega.NewWithT(t) - reader, err := hardware.NewNormalizedCSVReaderFromFile("./testdata/hardware.csv") + reader, err := hardware.NewNormalizedCSVReaderFromFile("./testdata/hardware.csv", nil) g.Expect(err).ToNot(gomega.HaveOccurred()) machine, err := reader.Read() @@ -83,7 +83,7 @@ func TestNewCSVReaderWithIOReaderError(t *testing.T) { expect := errors.New("read err") - _, err := hardware.NewCSVReader(iotest.ErrReader(expect)) + _, err := hardware.NewCSVReader(iotest.ErrReader(expect), nil) g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(expect.Error())) } @@ -91,7 +91,7 @@ func TestNewCSVReaderWithIOReaderError(t *testing.T) { func TestCSVReaderWithoutBMCHeaders(t *testing.T) { g := gomega.NewWithT(t) - reader, err := hardware.NewNormalizedCSVReaderFromFile("./testdata/hardware_no_bmc_headers.csv") + reader, err := hardware.NewNormalizedCSVReaderFromFile("./testdata/hardware_no_bmc_headers.csv", nil) g.Expect(err).ToNot(gomega.HaveOccurred()) machine, err := reader.Read() @@ -137,7 +137,7 @@ func TestCSVReaderWithMissingRequiredColumns(t *testing.T) { buf := bytes.NewBufferString(fmt.Sprintf("%v", strings.Join(included, ","))) g := gomega.NewWithT(t) - _, err := hardware.NewCSVReader(buf) + _, err := hardware.NewCSVReader(buf, nil) g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(missing)) }) @@ -147,7 +147,7 @@ func TestCSVReaderWithMissingRequiredColumns(t *testing.T) { func TestCSVBuildHardwareYamlFromCSV(t *testing.T) { g := gomega.NewWithT(t) - hardwareYaml, err := hardware.BuildHardwareYAML("./testdata/hardware.csv") + hardwareYaml, err := hardware.BuildHardwareYAML("./testdata/hardware.csv", nil) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(hardwareYaml).To(gomega.Equal([]byte(`apiVersion: tinkerbell.org/v1alpha1 kind: Hardware diff --git a/pkg/providers/tinkerbell/hardware/validator.go b/pkg/providers/tinkerbell/hardware/validator.go index 6c97e769155f..e37d099f6a5d 100644 --- a/pkg/providers/tinkerbell/hardware/validator.go +++ b/pkg/providers/tinkerbell/hardware/validator.go @@ -127,12 +127,14 @@ func StaticMachineAssertions() MachineAssertion { return fmt.Errorf("BMCIPAddress: %v", err) } - if m.BMCUsername == "" { - return newEmptyFieldError("BMCUsername") - } - - if m.BMCPassword == "" { - return newEmptyFieldError("BMCPassword") + if m.BMCOptions == nil || m.BMCOptions.RPC == nil { + if m.BMCUsername == "" { + return newEmptyFieldError("BMCUsername") + } + + if m.BMCPassword == "" { + return newEmptyFieldError("BMCPassword") + } } } diff --git a/pkg/providers/tinkerbell/tinkerbell.go b/pkg/providers/tinkerbell/tinkerbell.go index cdc5210104e5..d9cbf51478e3 100644 --- a/pkg/providers/tinkerbell/tinkerbell.go +++ b/pkg/providers/tinkerbell/tinkerbell.go @@ -60,6 +60,8 @@ type Provider struct { hardwareCSVFile string catalogue *hardware.Catalogue tinkerbellIP string + // BMCOptions are Rufio BMC options that are used when creating Rufio machine CRDs. + BMCOptions *hardware.BMCOptions // TODO(chrisdoheryt4) Temporarily depend on the netclient until the validator can be injected. // This is already a dependency, just uncached, because we require it during the initializing diff --git a/pkg/providers/tinkerbell/upgrade.go b/pkg/providers/tinkerbell/upgrade.go index efebea19be7c..46a8ab63a0cf 100644 --- a/pkg/providers/tinkerbell/upgrade.go +++ b/pkg/providers/tinkerbell/upgrade.go @@ -68,7 +68,7 @@ func (p *Provider) SetupAndValidateUpgradeCluster(ctx context.Context, cluster * if p.hardwareCSVIsProvided() { machineCatalogueWriter := hardware.NewMachineCatalogueWriter(p.catalogue) - machines, err := hardware.NewNormalizedCSVReaderFromFile(p.hardwareCSVFile) + machines, err := hardware.NewNormalizedCSVReaderFromFile(p.hardwareCSVFile, p.BMCOptions) if err != nil { return err } From 816bf058fc50eab7d5328b4ec0530ce8ea6467ea Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Thu, 28 Sep 2023 20:22:08 -0600 Subject: [PATCH 08/15] Remove TODO code comment Signed-off-by: Jacob Weinstock --- pkg/providers/tinkerbell/hardware/catalogue_bmc.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/providers/tinkerbell/hardware/catalogue_bmc.go b/pkg/providers/tinkerbell/hardware/catalogue_bmc.go index d1cecacff45a..15da6055f276 100644 --- a/pkg/providers/tinkerbell/hardware/catalogue_bmc.go +++ b/pkg/providers/tinkerbell/hardware/catalogue_bmc.go @@ -200,7 +200,6 @@ func toHMACOpts(h HMACOpts, m Machine) *v1alpha1.HMACOpts { hmac.Secrets = make(map[rufio.HMACAlgorithm][]corev1.SecretReference) for idx := range h.Secrets { s := corev1.SecretReference{ - // TODO(jacobweinstock): get hostname from the machine object. Name: fmt.Sprintf("%v-%v", formatBMCSecretRef(m), idx), Namespace: constants.EksaSystemNamespace, } From 10034b194ad9bf90a7d907901d6b10d8351a5a5d Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Mon, 2 Oct 2023 20:38:17 -0600 Subject: [PATCH 09/15] Add http.Header env/flag parser, update code comments: Signed-off-by: Jacob Weinstock --- cmd/eksctl-anywhere/cmd/aflag/aflag.go | 23 +++++++- cmd/eksctl-anywhere/cmd/aflag/header.go | 63 +++++++++++++++++++++ cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go | 54 +++++++++++------- cmd/eksctl-anywhere/cmd/createcluster.go | 15 ++--- 4 files changed, 127 insertions(+), 28 deletions(-) create mode 100644 cmd/eksctl-anywhere/cmd/aflag/header.go diff --git a/cmd/eksctl-anywhere/cmd/aflag/aflag.go b/cmd/eksctl-anywhere/cmd/aflag/aflag.go index f7396ae89107..861040650c46 100644 --- a/cmd/eksctl-anywhere/cmd/aflag/aflag.go +++ b/cmd/eksctl-anywhere/cmd/aflag/aflag.go @@ -1,7 +1,9 @@ // Package aflag is the eks anywhere flag handling package. package aflag -import "github.com/spf13/pflag" +import ( + "github.com/spf13/pflag" +) // Flag defines a CLI flag. type Flag[T any] struct { @@ -42,3 +44,22 @@ func StringSlice(f Flag[[]string], dst *[]string, fs *pflag.FlagSet) { fs.StringSliceVar(dst, f.Name, f.Default, f.Usage) } } + +// StringSlice applies f to fs and writes the value to dst. +func StringString(f Flag[map[string]string], dst *map[string]string, fs *pflag.FlagSet) { + switch { + case f.Short != "": + fs.StringToStringVarP(dst, f.Name, f.Short, f.Default, f.Usage) + default: + fs.StringToStringVar(dst, f.Name, f.Default, f.Usage) + } +} + +func HTTPHeader(f Flag[Header], dst *Header, fs *pflag.FlagSet) { + switch { + case f.Short != "": + fs.VarP(dst, f.Name, f.Short, f.Usage) + default: + fs.Var(dst, f.Name, f.Usage) + } +} diff --git a/cmd/eksctl-anywhere/cmd/aflag/header.go b/cmd/eksctl-anywhere/cmd/aflag/header.go new file mode 100644 index 000000000000..fb0075deb4cf --- /dev/null +++ b/cmd/eksctl-anywhere/cmd/aflag/header.go @@ -0,0 +1,63 @@ +package aflag + +import ( + "encoding/csv" + "encoding/json" + "fmt" + "net/http" + "strings" +) + +// -- Header Value +type Header http.Header + +// NewHeader returns a new Header pointer. +func NewHeader(h *http.Header) *Header { + return (*Header)(h) +} + +// String returns the string representation of the Header. +func (h *Header) String() string { + if b, err := json.Marshal(h); err == nil { + return string(b) + } + + return "" +} + +// Set sets the value of the Header. +// Format: "a=1;2,b=2;4;5" +func (h *Header) Set(val string) error { + var ss []string + n := strings.Count(val, "=") + switch n { + case 0: + return fmt.Errorf("%s must be formatted as key=value;value", val) + case 1: + ss = append(ss, strings.Trim(val, `"`)) + default: + r := csv.NewReader(strings.NewReader(val)) + var err error + ss, err = r.Read() + if err != nil { + return err + } + } + + out := make(map[string][]string, len(ss)) + for _, pair := range ss { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + return fmt.Errorf("%s must be formatted as key=value;value", pair) + } + out[kv[0]] = strings.Split(kv[1], ";") + } + *h = out + + return nil +} + +// Type returns the flag type. +func (h *Header) Type() string { + return "header" +} diff --git a/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go b/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go index 9b23115abcf8..2428ea79eed2 100644 --- a/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go +++ b/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go @@ -8,61 +8,75 @@ var TinkerbellBootstrapIP = Flag[string]{ } // TinkerbellBMCConsumerURL is a Rufio RPC provider option. +// ConsumerURL is the URL where an rpc consumer/listener is running and to which we will send and receive all notifications. var TinkerbellBMCConsumerURL = Flag[string]{ Name: "tinkerbell-bmc-consumer-url", - Usage: "The URL used to expose the Tinkerbell BMC consumer from the bootstrap cluster", + Usage: "The URL of a BMC RPC consumer/listener used for BMC interactions", } // TinkerbellBMCHTTPContentType is a Rufio RPC provider option. +// The content type header to use for the rpc request notification var TinkerbellBMCHTTPContentType = Flag[string]{ Name: "tinkerbell-bmc-http-content-type", - Usage: "The HTTP content type used to expose the Tinkerbell BMC consumer from the bootstrap cluster", + Usage: "The HTTP content type used for the RPC BMC interactions", } // TinkerbellBMCHTTPMethod is a Rufio RPC provider option. +// The HTTP method to use for the rpc request notification var TinkerbellBMCHTTPMethod = Flag[string]{ Name: "tinkerbell-bmc-http-method", - Usage: "The HTTP method used to expose the Tinkerbell BMC consumer from the bootstrap cluster", + Usage: "The HTTP method used for the RPC BMC interactions", } // TinkerbellBMCTimestampHeader is a Rufio RPC provider option. +// The the header name that should contain the timestamp. Example: X-BMCLIB-Timestamp (in RFC3339 format) var TinkerbellBMCTimestampHeader = Flag[string]{ Name: "tinkerbell-bmc-timestamp-header", - Usage: "The HTTP timestamp header used to expose the Tinkerbell BMC consumer from the bootstrap cluster", + Usage: "The HTTP timestamp header used for the RPC BMC interactions", } // TinkerbellBMCStaticHeaders is a Rufio RPC provider option. -var TinkerbellBMCStaticHeaders = Flag[string]{ +// Predefined headers that will be added to every request (comma separated, values are semicolon separated) +// Example: "X-My-Header=1;2;3,X-Custom-Header=abc;def" +var TinkerbellBMCStaticHeaders = Flag[Header]{ Name: "tinkerbell-bmc-static-headers", - Usage: "The HTTP static headers used to expose the Tinkerbell BMC consumer from the bootstrap cluster", + Usage: "Static HTTP headers added to all RPC BMC interactions", } -// TinkerbellBMCHeaderName is a Rufio RPC provider option. -var TinkerbellBMCHeaderName = Flag[string]{ - Name: "tinkerbell-bmc-header-name", - Usage: "The HTTP header name used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +// TinkerbellBMCSignatureHeaderName is a Rufio RPC provider option. +// The header name that should contain the signature(s). Example: X-BMCLIB-Signature +var TinkerbellBMCSigHeaderName = Flag[string]{ + Name: "tinkerbell-bmc-sig-header-name", + Usage: "The HTTP header name for the HMAC signature used in RPC BMC interactions", } // TinkerbellBMCAppendAlgoToHeaderDisabled is a Rufio RPC provider option. +// decides whether to append the algorithm to the signature header or not. Example: X-BMCLIB-Signature becomes X-BMCLIB-Signature-256 var TinkerbellBMCAppendAlgoToHeaderDisabled = Flag[bool]{ Name: "tinkerbell-bmc-append-algo-to-header-disabled", - Usage: "The HTTP append algo to header disabled used to expose the Tinkerbell BMC consumer from the bootstrap cluster", + Usage: "This disables appending of the algorithm type to the signature header used in RPC BMC interactions", } -// TinkerbellBMCIncludedPayloadHeaders is a Rufio RPC provider option. -var TinkerbellBMCIncludedPayloadHeaders = Flag[[]string]{ - Name: "tinkerbell-bmc-included-payload-headers", - Usage: "The HTTP included payload headers used to expose the Tinkerbell BMC consumer from the bootstrap cluster. If you specify a Timestamp header, it must be included here.", +// TinkerbellBMCSigIncludedPayloadHeaders is a Rufio RPC provider option. +// The headers whose values will be included in the signature payload. +// Example: given these headers in a request: X-My-Header=123,X-Another=456, +// and IncludedPayloadHeaders := []string{"X-Another"}, the value of "X-Another" +// will be included in the signature payload (comma separated) +var TinkerbellBMCSigIncludedPayloadHeaders = Flag[[]string]{ + Name: "tinkerbell-bmc-sig-included-payload-headers", + Usage: "The HTTP headers to be included in the HMAC signature payload used in RPC BMC interactions", } // TinkerbellBMCPrefixSigDisabled is a Rufio RPC provider option. +// determines whether the algorithm will be prefixed to the signature. Example: sha256=abc123 var TinkerbellBMCPrefixSigDisabled = Flag[bool]{ Name: "tinkerbell-bmc-prefix-sig-disabled", - Usage: "The HTTP prefix sig disabled used to expose the Tinkerbell BMC consumer from the bootstrap cluster", + Usage: "This disables prefixing the signature with the algorithm type used in RPC BMC interactions", } -// TinkerbellBMCWebhookSecrets is a Rufio RPC provider option. -var TinkerbellBMCWebhookSecrets = Flag[[]string]{ - Name: "tinkerbell-bmc-webhook-secrets", - Usage: "The webhook secrets used to expose the Tinkerbell BMC consumer from the bootstrap cluster", +// TinkerbellBMCHMACSecrets is a Rufio RPC provider option. +// secrets used for signing the payload, all secrets with used to sign with both sha256 and sha512 +var TinkerbellBMCHMACSecrets = Flag[[]string]{ + Name: "tinkerbell-bmc-hmac-secrets", + Usage: "The secrets used to HMAC sign a payload, used in RPC BMC interactions", } diff --git a/cmd/eksctl-anywhere/cmd/createcluster.go b/cmd/eksctl-anywhere/cmd/createcluster.go index c3d0713d6c63..a549c5ef2787 100644 --- a/cmd/eksctl-anywhere/cmd/createcluster.go +++ b/cmd/eksctl-anywhere/cmd/createcluster.go @@ -81,17 +81,18 @@ func tinkerbellFlags(fs *pflag.FlagSet, r *hardware.RPCOpts) { aflag.MarkHidden(fs, aflag.TinkerbellBMCHTTPMethod.Name) aflag.String(aflag.TinkerbellBMCTimestampHeader, &r.Request.TimestampHeader, fs) aflag.MarkHidden(fs, aflag.TinkerbellBMCTimestampHeader.Name) - // add static headers here - aflag.String(aflag.TinkerbellBMCHeaderName, &r.Signature.HeaderName, fs) - aflag.MarkHidden(fs, aflag.TinkerbellBMCHeaderName.Name) + aflag.HTTPHeader(aflag.TinkerbellBMCStaticHeaders, aflag.NewHeader(&r.Request.StaticHeaders), fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCStaticHeaders.Name) + aflag.String(aflag.TinkerbellBMCSigHeaderName, &r.Signature.HeaderName, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCSigHeaderName.Name) aflag.Bool(aflag.TinkerbellBMCAppendAlgoToHeaderDisabled, &r.Signature.AppendAlgoToHeaderDisabled, fs) aflag.MarkHidden(fs, aflag.TinkerbellBMCAppendAlgoToHeaderDisabled.Name) - aflag.StringSlice(aflag.TinkerbellBMCIncludedPayloadHeaders, &r.Signature.IncludedPayloadHeaders, fs) - aflag.MarkHidden(fs, aflag.TinkerbellBMCIncludedPayloadHeaders.Name) + aflag.StringSlice(aflag.TinkerbellBMCSigIncludedPayloadHeaders, &r.Signature.IncludedPayloadHeaders, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCSigIncludedPayloadHeaders.Name) aflag.Bool(aflag.TinkerbellBMCPrefixSigDisabled, &r.HMAC.PrefixSigDisabled, fs) aflag.MarkHidden(fs, aflag.TinkerbellBMCPrefixSigDisabled.Name) - aflag.StringSlice(aflag.TinkerbellBMCWebhookSecrets, &r.HMAC.Secrets, fs) - aflag.MarkHidden(fs, aflag.TinkerbellBMCWebhookSecrets.Name) + aflag.StringSlice(aflag.TinkerbellBMCHMACSecrets, &r.HMAC.Secrets, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCHMACSecrets.Name) } func (cc *createClusterOptions) createCluster(cmd *cobra.Command, _ []string) error { From 70bac638b65811dba4b6b4618b247c7216c6478e Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Mon, 2 Oct 2023 20:43:18 -0600 Subject: [PATCH 10/15] Fix linting issues Signed-off-by: Jacob Weinstock --- cmd/eksctl-anywhere/cmd/aflag/aflag.go | 3 ++- cmd/eksctl-anywhere/cmd/aflag/header.go | 4 ++-- cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go | 25 +++++++++++++-------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/cmd/eksctl-anywhere/cmd/aflag/aflag.go b/cmd/eksctl-anywhere/cmd/aflag/aflag.go index 861040650c46..88ad59fb6213 100644 --- a/cmd/eksctl-anywhere/cmd/aflag/aflag.go +++ b/cmd/eksctl-anywhere/cmd/aflag/aflag.go @@ -45,7 +45,7 @@ func StringSlice(f Flag[[]string], dst *[]string, fs *pflag.FlagSet) { } } -// StringSlice applies f to fs and writes the value to dst. +// StringString applies f to fs and writes the value to dst. func StringString(f Flag[map[string]string], dst *map[string]string, fs *pflag.FlagSet) { switch { case f.Short != "": @@ -55,6 +55,7 @@ func StringString(f Flag[map[string]string], dst *map[string]string, fs *pflag.F } } +// HTTPHeader applies f to fs and writes the value to dst. func HTTPHeader(f Flag[Header], dst *Header, fs *pflag.FlagSet) { switch { case f.Short != "": diff --git a/cmd/eksctl-anywhere/cmd/aflag/header.go b/cmd/eksctl-anywhere/cmd/aflag/header.go index fb0075deb4cf..39a2a0fec245 100644 --- a/cmd/eksctl-anywhere/cmd/aflag/header.go +++ b/cmd/eksctl-anywhere/cmd/aflag/header.go @@ -8,7 +8,7 @@ import ( "strings" ) -// -- Header Value +// Header Value. type Header http.Header // NewHeader returns a new Header pointer. @@ -26,7 +26,7 @@ func (h *Header) String() string { } // Set sets the value of the Header. -// Format: "a=1;2,b=2;4;5" +// Format: "a=1;2,b=2;4;5". func (h *Header) Set(val string) error { var ss []string n := strings.Count(val, "=") diff --git a/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go b/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go index 2428ea79eed2..d29219d23707 100644 --- a/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go +++ b/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go @@ -15,21 +15,23 @@ var TinkerbellBMCConsumerURL = Flag[string]{ } // TinkerbellBMCHTTPContentType is a Rufio RPC provider option. -// The content type header to use for the rpc request notification +// The content type header to use for the rpc request notification. var TinkerbellBMCHTTPContentType = Flag[string]{ Name: "tinkerbell-bmc-http-content-type", Usage: "The HTTP content type used for the RPC BMC interactions", } // TinkerbellBMCHTTPMethod is a Rufio RPC provider option. -// The HTTP method to use for the rpc request notification +// The HTTP method to use for the rpc request notification. var TinkerbellBMCHTTPMethod = Flag[string]{ Name: "tinkerbell-bmc-http-method", Usage: "The HTTP method used for the RPC BMC interactions", } // TinkerbellBMCTimestampHeader is a Rufio RPC provider option. -// The the header name that should contain the timestamp. Example: X-BMCLIB-Timestamp (in RFC3339 format) +// The the header name that should contain the timestamp. +// Example: X-BMCLIB-Timestamp (in RFC3339 format) +// . var TinkerbellBMCTimestampHeader = Flag[string]{ Name: "tinkerbell-bmc-timestamp-header", Usage: "The HTTP timestamp header used for the RPC BMC interactions", @@ -38,20 +40,25 @@ var TinkerbellBMCTimestampHeader = Flag[string]{ // TinkerbellBMCStaticHeaders is a Rufio RPC provider option. // Predefined headers that will be added to every request (comma separated, values are semicolon separated) // Example: "X-My-Header=1;2;3,X-Custom-Header=abc;def" +// . var TinkerbellBMCStaticHeaders = Flag[Header]{ Name: "tinkerbell-bmc-static-headers", Usage: "Static HTTP headers added to all RPC BMC interactions", } -// TinkerbellBMCSignatureHeaderName is a Rufio RPC provider option. -// The header name that should contain the signature(s). Example: X-BMCLIB-Signature +// TinkerbellBMCSigHeaderName is a Rufio RPC provider option. +// The header name that should contain the signature(s). +// Example: X-BMCLIB-Signature +// . var TinkerbellBMCSigHeaderName = Flag[string]{ Name: "tinkerbell-bmc-sig-header-name", Usage: "The HTTP header name for the HMAC signature used in RPC BMC interactions", } // TinkerbellBMCAppendAlgoToHeaderDisabled is a Rufio RPC provider option. -// decides whether to append the algorithm to the signature header or not. Example: X-BMCLIB-Signature becomes X-BMCLIB-Signature-256 +// decides whether to append the algorithm to the signature header or not. +// Example: X-BMCLIB-Signature becomes X-BMCLIB-Signature-256 +// . var TinkerbellBMCAppendAlgoToHeaderDisabled = Flag[bool]{ Name: "tinkerbell-bmc-append-algo-to-header-disabled", Usage: "This disables appending of the algorithm type to the signature header used in RPC BMC interactions", @@ -61,21 +68,21 @@ var TinkerbellBMCAppendAlgoToHeaderDisabled = Flag[bool]{ // The headers whose values will be included in the signature payload. // Example: given these headers in a request: X-My-Header=123,X-Another=456, // and IncludedPayloadHeaders := []string{"X-Another"}, the value of "X-Another" -// will be included in the signature payload (comma separated) +// will be included in the signature payload (comma separated). var TinkerbellBMCSigIncludedPayloadHeaders = Flag[[]string]{ Name: "tinkerbell-bmc-sig-included-payload-headers", Usage: "The HTTP headers to be included in the HMAC signature payload used in RPC BMC interactions", } // TinkerbellBMCPrefixSigDisabled is a Rufio RPC provider option. -// determines whether the algorithm will be prefixed to the signature. Example: sha256=abc123 +// Example: sha256=abc123 ; Determines whether the algorithm will be prefixed to the signature. var TinkerbellBMCPrefixSigDisabled = Flag[bool]{ Name: "tinkerbell-bmc-prefix-sig-disabled", Usage: "This disables prefixing the signature with the algorithm type used in RPC BMC interactions", } // TinkerbellBMCHMACSecrets is a Rufio RPC provider option. -// secrets used for signing the payload, all secrets with used to sign with both sha256 and sha512 +// secrets used for signing the payload, all secrets with used to sign with both sha256 and sha512. var TinkerbellBMCHMACSecrets = Flag[[]string]{ Name: "tinkerbell-bmc-hmac-secrets", Usage: "The secrets used to HMAC sign a payload, used in RPC BMC interactions", From 77f0b4ee1615b187d81902a507265284f6b43a06 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Wed, 4 Oct 2023 12:48:30 -0600 Subject: [PATCH 11/15] Add BMC env opts: TinkerbellBMCCustomPayload and TinkerbellBMCCustomPayloadDotLocation. Signed-off-by: Jacob Weinstock --- cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go | 12 ++++++++++++ cmd/eksctl-anywhere/cmd/createcluster.go | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go b/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go index d29219d23707..e42599b94a5b 100644 --- a/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go +++ b/cmd/eksctl-anywhere/cmd/aflag/tinkerbell.go @@ -87,3 +87,15 @@ var TinkerbellBMCHMACSecrets = Flag[[]string]{ Name: "tinkerbell-bmc-hmac-secrets", Usage: "The secrets used to HMAC sign a payload, used in RPC BMC interactions", } + +// TinkerbellBMCCustomPayload allows providing a JSON payload that will be used in the RPC request. +var TinkerbellBMCCustomPayload = Flag[string]{ + Name: "tinkerbell-bmc-custom-payload", + Usage: "The custom payload used in RPC BMC interactions, must be used with tinkerbell-bmc-custom-payload-dot-location", +} + +// TinkerbellBMCCustomPayloadDotLocation is the path to where the bmclib RequestPayload{} will be embedded. For example: object.data.body. +var TinkerbellBMCCustomPayloadDotLocation = Flag[string]{ + Name: "tinkerbell-bmc-custom-payload-dot-location", + Usage: "The dot location of the custom payload used in RPC BMC interactions, must be used with tinkerbell-bmc-custom-payload", +} diff --git a/cmd/eksctl-anywhere/cmd/createcluster.go b/cmd/eksctl-anywhere/cmd/createcluster.go index a549c5ef2787..260f46b03aa6 100644 --- a/cmd/eksctl-anywhere/cmd/createcluster.go +++ b/cmd/eksctl-anywhere/cmd/createcluster.go @@ -93,6 +93,10 @@ func tinkerbellFlags(fs *pflag.FlagSet, r *hardware.RPCOpts) { aflag.MarkHidden(fs, aflag.TinkerbellBMCPrefixSigDisabled.Name) aflag.StringSlice(aflag.TinkerbellBMCHMACSecrets, &r.HMAC.Secrets, fs) aflag.MarkHidden(fs, aflag.TinkerbellBMCHMACSecrets.Name) + aflag.String(aflag.TinkerbellBMCCustomPayload, &r.Experimental.CustomRequestPayload, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCCustomPayload.Name) + aflag.String(aflag.TinkerbellBMCCustomPayloadDotLocation, &r.Experimental.DotPath, fs) + aflag.MarkHidden(fs, aflag.TinkerbellBMCCustomPayloadDotLocation.Name) } func (cc *createClusterOptions) createCluster(cmd *cobra.Command, _ []string) error { From 9783af544af721e1474f042041c8325be7562172 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 13 Oct 2023 13:04:15 -0600 Subject: [PATCH 12/15] Add header flag test Signed-off-by: Jacob Weinstock --- cmd/eksctl-anywhere/cmd/aflag/header_test.go | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 cmd/eksctl-anywhere/cmd/aflag/header_test.go diff --git a/cmd/eksctl-anywhere/cmd/aflag/header_test.go b/cmd/eksctl-anywhere/cmd/aflag/header_test.go new file mode 100644 index 000000000000..e5c1a08b4d85 --- /dev/null +++ b/cmd/eksctl-anywhere/cmd/aflag/header_test.go @@ -0,0 +1,35 @@ +package aflag + +import ( + "net/http" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestHeaderM(t *testing.T) { + tests := map[string]struct { + shouldErr bool + want http.Header + input string + }{ + "success": {want: map[string][]string{"A": {"1", "2", "3"}, "B": {"2"}, "C": {"4"}}, input: `A=1;2;3,B=2,C=4`}, + "bad input format 1": {shouldErr: true, input: `abc`, want: http.Header{}}, + "bad input format 2": {shouldErr: true, input: `abc=123;abd=345,`, want: http.Header{}}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + h := http.Header{} + got := NewHeader(&h) + if err := got.Set(tc.input); err != nil { + if !tc.shouldErr { + t.Fatal(err) + } + } + if diff := cmp.Diff(tc.want, h); diff != "" { + t.Fatalf("diff: %s", diff) + } + }) + } +} From 11f319ff2302e4215fb0b70ba1b34903a34587dc Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 13 Oct 2023 13:16:43 -0600 Subject: [PATCH 13/15] Add test for MarkHidden Signed-off-by: Jacob Weinstock --- cmd/eksctl-anywhere/cmd/aflag/marker_test.go | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cmd/eksctl-anywhere/cmd/aflag/marker_test.go b/cmd/eksctl-anywhere/cmd/aflag/marker_test.go index 41228219813d..0af871e2be4d 100644 --- a/cmd/eksctl-anywhere/cmd/aflag/marker_test.go +++ b/cmd/eksctl-anywhere/cmd/aflag/marker_test.go @@ -125,3 +125,26 @@ type nopValue struct{} func (nopValue) String() string { return "" } func (nopValue) Set(string) error { return nil } func (nopValue) Type() string { return "" } + +func TestMarkHidden(t *testing.T) { + tests := map[string]struct { + flags []string + }{ + "success": {flags: []string{"foo", "bar"}}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + cmd := &cobra.Command{} + for _, flag := range tc.flags { + cmd.Flags().AddFlag(&pflag.Flag{Name: flag}) + } + aflag.MarkHidden(cmd.Flags(), tc.flags...) + for _, flag := range tc.flags { + if !cmd.Flags().Lookup(flag).Hidden { + t.Errorf("flag %s should be hidden", flag) + } + } + }) + } +} From 32d7e71f2298628ac2818255e0b4f4f4d0c7d84a Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 13 Oct 2023 13:33:45 -0600 Subject: [PATCH 14/15] Add test case for MarkHidden Signed-off-by: Jacob Weinstock --- cmd/eksctl-anywhere/cmd/aflag/marker_test.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cmd/eksctl-anywhere/cmd/aflag/marker_test.go b/cmd/eksctl-anywhere/cmd/aflag/marker_test.go index 0af871e2be4d..5293cba9e71c 100644 --- a/cmd/eksctl-anywhere/cmd/aflag/marker_test.go +++ b/cmd/eksctl-anywhere/cmd/aflag/marker_test.go @@ -128,18 +128,28 @@ func (nopValue) Type() string { return "" } func TestMarkHidden(t *testing.T) { tests := map[string]struct { - flags []string + flags []string + hidden []string + shouldPanic bool }{ - "success": {flags: []string{"foo", "bar"}}, + "success": {flags: []string{"foo", "bar"}, hidden: []string{"foo", "bar"}}, + "flag does not exist": {flags: []string{"foo", "bar"}, hidden: []string{"foo", "bar", "baz"}, shouldPanic: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { + if tc.shouldPanic { + defer func() { + if recover() == nil { + t.Error("no panic received") + } + }() + } cmd := &cobra.Command{} for _, flag := range tc.flags { cmd.Flags().AddFlag(&pflag.Flag{Name: flag}) } - aflag.MarkHidden(cmd.Flags(), tc.flags...) + aflag.MarkHidden(cmd.Flags(), tc.hidden...) for _, flag := range tc.flags { if !cmd.Flags().Lookup(flag).Hidden { t.Errorf("flag %s should be hidden", flag) From 97fde971fd95f7e3218c4ee48d3510a0731922c5 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 13 Oct 2023 14:03:24 -0600 Subject: [PATCH 15/15] Add tests for the basic aflag types Signed-off-by: Jacob Weinstock --- cmd/eksctl-anywhere/cmd/aflag/aflag_test.go | 212 ++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 cmd/eksctl-anywhere/cmd/aflag/aflag_test.go diff --git a/cmd/eksctl-anywhere/cmd/aflag/aflag_test.go b/cmd/eksctl-anywhere/cmd/aflag/aflag_test.go new file mode 100644 index 000000000000..5d97216b9477 --- /dev/null +++ b/cmd/eksctl-anywhere/cmd/aflag/aflag_test.go @@ -0,0 +1,212 @@ +package aflag + +import ( + "testing" + + "github.com/spf13/pflag" +) + +func TestString(t *testing.T) { + type args struct { + f Flag[string] + dst *string + fs *pflag.FlagSet + } + tests := map[string]struct { + name string + args args + }{ + "success long form": { + args: args{ + f: Flag[string]{ + Name: "test", + Usage: "test usage", + Default: "test default", + }, + dst: new(string), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + "success short form": { + args: args{ + f: Flag[string]{ + Name: "test", + Short: "t", + Usage: "test usage", + Default: "test default", + }, + dst: new(string), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + String(tt.args.f, tt.args.dst, tt.args.fs) + }) + } +} + +func TestBool(t *testing.T) { + type args struct { + f Flag[bool] + dst *bool + fs *pflag.FlagSet + } + tests := map[string]struct { + name string + args args + }{ + "success long form": { + args: args{ + f: Flag[bool]{ + Name: "test", + Usage: "test usage", + Default: true, + }, + dst: new(bool), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + "success short form": { + args: args{ + f: Flag[bool]{ + Name: "test", + Short: "t", + Usage: "test usage", + Default: true, + }, + dst: new(bool), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + Bool(tt.args.f, tt.args.dst, tt.args.fs) + }) + } +} + +func TestStringSlice(t *testing.T) { + type args struct { + f Flag[[]string] + dst *[]string + fs *pflag.FlagSet + } + tests := map[string]struct { + name string + args args + }{ + "success long form": { + args: args{ + f: Flag[[]string]{ + Name: "test", + Usage: "test usage", + Default: []string{"test"}, + }, + dst: new([]string), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + "success short form": { + args: args{ + f: Flag[[]string]{ + Name: "test", + Short: "t", + Usage: "test usage", + Default: []string{"test"}, + }, + dst: new([]string), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + StringSlice(tt.args.f, tt.args.dst, tt.args.fs) + }) + } +} + +func TestStringString(t *testing.T) { + type args struct { + f Flag[map[string]string] + dst *map[string]string + fs *pflag.FlagSet + } + tests := map[string]struct { + name string + args args + }{ + "success long form": { + args: args{ + f: Flag[map[string]string]{ + Name: "test", + Usage: "test usage", + Default: map[string]string{"test": "test"}, + }, + dst: new(map[string]string), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + "success short form": { + args: args{ + f: Flag[map[string]string]{ + Name: "test", + Short: "t", + Usage: "test usage", + Default: map[string]string{"test": "test"}, + }, + dst: new(map[string]string), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + StringString(tt.args.f, tt.args.dst, tt.args.fs) + }) + } +} + +func TestHTTPHeader(t *testing.T) { + type args struct { + f Flag[Header] + dst *Header + fs *pflag.FlagSet + } + tests := map[string]struct { + name string + args args + }{ + "success long form": { + args: args{ + f: Flag[Header]{ + Name: "test", + Usage: "test usage", + Default: Header{}, + }, + dst: new(Header), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + "success short form": { + args: args{ + f: Flag[Header]{ + Name: "test", + Short: "t", + Usage: "test usage", + Default: Header{}, + }, + dst: new(Header), + fs: pflag.NewFlagSet("test", pflag.ContinueOnError), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + HTTPHeader(tt.args.f, tt.args.dst, tt.args.fs) + }) + } +}