diff --git a/api/variables/variables.go b/api/variables/variables.go new file mode 100644 index 000000000..8530f40ba --- /dev/null +++ b/api/variables/variables.go @@ -0,0 +1,33 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package variables + +import ( + "encoding/json" + "fmt" + + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" +) + +func UnmarshalRuntimeVariable[T any](runtimeVariable *runtimehooksv1.Variable, obj *T) error { + err := json.Unmarshal(runtimeVariable.Value.Raw, obj) + if err != nil { + return fmt.Errorf("error unmarshalling variable: %w", err) + } + + return nil +} + +//nolint:gocritic // no need for named results +func GetRuntimhookVariableByName( + name string, + variables []runtimehooksv1.Variable, +) (*runtimehooksv1.Variable, int) { + for i, runtimevar := range variables { + if runtimevar.Name == name { + return &runtimevar, i + } + } + return nil, -1 +} diff --git a/cmd/main.go b/cmd/main.go index f43f3ff7e..bf46db546 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -34,6 +34,7 @@ import ( dockermutation "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/docker/mutation" dockerworkerconfig "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/docker/workerconfig" "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/lifecycle" + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/validation" nutanixclusterconfig "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/clusterconfig" nutanixmutation "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/mutation" nutanixworkerconfig "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/workerconfig" @@ -84,6 +85,7 @@ func main() { genericLifecycleHandlers := lifecycle.New(globalOptions) + validationHandlers := validation.New() // Initialize and parse command line flags. logs.AddFlags(pflag.CommandLine, logs.SkipLoggingConfigurationFlags()) logsv1.AddFlags(logOptions, pflag.CommandLine) @@ -142,6 +144,7 @@ func main() { } var allHandlers []handlers.Named + allHandlers = append(allHandlers, validationHandlers.AllHandlers(mgr)...) allHandlers = append(allHandlers, genericLifecycleHandlers.AllHandlers(mgr)...) allHandlers = append(allHandlers, awsMetaHandlers...) allHandlers = append(allHandlers, dockerMetaHandlers...) diff --git a/pkg/handlers/generic/validation/handlers.go b/pkg/handlers/generic/validation/handlers.go new file mode 100644 index 000000000..9827d4f1d --- /dev/null +++ b/pkg/handlers/generic/validation/handlers.go @@ -0,0 +1,24 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package validation + +import ( + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/handlers" + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/validation/helm" +) + +type Handlers struct{} + +func New() *Handlers { + return &Handlers{} +} + +func (h *Handlers) AllHandlers(mgr manager.Manager) []handlers.Named { + validationHandler := helm.New(mgr.GetClient()) + return []handlers.Named{ + validationHandler, + } +} diff --git a/pkg/handlers/generic/validation/helm/handler.go b/pkg/handlers/generic/validation/helm/handler.go new file mode 100644 index 000000000..57c133401 --- /dev/null +++ b/pkg/handlers/generic/validation/helm/handler.go @@ -0,0 +1,87 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package helm + +import ( + "context" + "crypto/tls" + "fmt" + "net/http" + + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/api/v1alpha1" + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/api/variables" + "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/generic/clusterconfig" +) + +type HelmRegistryValidator struct { + client ctrlclient.Client + variableName string +} + +func New( + c ctrlclient.Client, +) *HelmRegistryValidator { + return &HelmRegistryValidator{ + client: c, + variableName: clusterconfig.MetaVariableName, + } +} + +func (h *HelmRegistryValidator) Name() string { + return "HelmRegistryValidator" +} + +func (h *HelmRegistryValidator) ValidateTopology( + ctx context.Context, + req *runtimehooksv1.ValidateTopologyRequest, + res *runtimehooksv1.ValidateTopologyResponse, +) { + log := ctrl.LoggerFrom(ctx) + clusterVar, ind := variables.GetRuntimhookVariableByName(h.variableName, req.Variables) + if ind == -1 { + log.V(5).Info(fmt.Sprintf("did not find variable %s in %v", h.variableName, req.Variables)) + return + } + var cluster v1alpha1.ClusterConfig + if err := variables.UnmarshalRuntimeVariable[v1alpha1.ClusterConfig](clusterVar, &cluster); err != nil { + failString := fmt.Sprintf("failed to unmarshal variable %v to clusterConfig", clusterVar) + log.Error(err, failString) + res.SetStatus(runtimehooksv1.ResponseStatusFailure) + res.SetMessage(failString) + return + } + helmChartRepo := cluster.Spec.Addons.HelmChartRepository + cl := &http.Client{ + Transport: &http.Transport{ + //nolint:gosec // this is done because customers can occasionally have self signed + // or no certificates to OCI registries + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + resp, err := cl.Get(fmt.Sprintf("%s/v2", *helmChartRepo)) + if err != nil { + failString := fmt.Sprintf("failed to ping provided helm registry %s", *helmChartRepo) + log.Error(err, failString) + res.SetStatus(runtimehooksv1.ResponseStatusFailure) + res.SetMessage(failString) + return + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized { + res.SetStatus(runtimehooksv1.ResponseStatusSuccess) + return + } + failString := fmt.Sprintf( + "failed to get 401 or 200 response from hitting registry: %s got status: %d", + *helmChartRepo, + resp.StatusCode, + ) + log.Error(err, failString) + res.SetStatus(runtimehooksv1.ResponseStatusFailure) + res.SetMessage(failString) +} diff --git a/pkg/handlers/nutanix/mutation/controlplaneendpoint/variables_test.go b/pkg/handlers/nutanix/mutation/controlplaneendpoint/variables_test.go index eec2f61f9..3acc680f1 100644 --- a/pkg/handlers/nutanix/mutation/controlplaneendpoint/variables_test.go +++ b/pkg/handlers/nutanix/mutation/controlplaneendpoint/variables_test.go @@ -17,7 +17,10 @@ import ( nutanixclusterconfig "github.com/d2iq-labs/cluster-api-runtime-extensions-nutanix/pkg/handlers/nutanix/clusterconfig" ) -var testPrismCentralURL = fmt.Sprintf("https://prism-central.nutanix.com:%d", v1alpha1.DefaultPrismCentralPort) +var testPrismCentralURL = fmt.Sprintf( + "https://prism-central.nutanix.com:%d", + v1alpha1.DefaultPrismCentralPort, +) func TestVariableValidation(t *testing.T) { capitest.ValidateDiscoverVariables( diff --git a/pkg/handlers/nutanix/mutation/prismcentralendpoint/inject_test.go b/pkg/handlers/nutanix/mutation/prismcentralendpoint/inject_test.go index ca7b695b2..e9fbf6e55 100644 --- a/pkg/handlers/nutanix/mutation/prismcentralendpoint/inject_test.go +++ b/pkg/handlers/nutanix/mutation/prismcentralendpoint/inject_test.go @@ -98,7 +98,10 @@ var _ = Describe("Generate Nutanix Prism Central Endpoint patches", func() { "address", gomega.BeEquivalentTo("prism-central.nutanix.com"), ), - gomega.HaveKeyWithValue("port", gomega.BeEquivalentTo(v1alpha1.DefaultPrismCentralPort)), + gomega.HaveKeyWithValue( + "port", + gomega.BeEquivalentTo(v1alpha1.DefaultPrismCentralPort), + ), gomega.HaveKeyWithValue("insecure", true), gomega.HaveKey("credentialRef"), gomega.Not(gomega.HaveKey("additionalTrustBundle")), diff --git a/pkg/handlers/nutanix/mutation/prismcentralendpoint/variables_test.go b/pkg/handlers/nutanix/mutation/prismcentralendpoint/variables_test.go index 0f8009218..85964cb07 100644 --- a/pkg/handlers/nutanix/mutation/prismcentralendpoint/variables_test.go +++ b/pkg/handlers/nutanix/mutation/prismcentralendpoint/variables_test.go @@ -29,7 +29,10 @@ func TestVariableValidation(t *testing.T) { Vals: v1alpha1.ClusterConfigSpec{ Nutanix: &v1alpha1.NutanixSpec{ PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{ - URL: fmt.Sprintf("https://prism-central.nutanix.com:%d", v1alpha1.DefaultPrismCentralPort), + URL: fmt.Sprintf( + "https://prism-central.nutanix.com:%d", + v1alpha1.DefaultPrismCentralPort, + ), Insecure: false, Credentials: corev1.LocalObjectReference{ Name: "credentials", @@ -48,7 +51,10 @@ func TestVariableValidation(t *testing.T) { Vals: v1alpha1.ClusterConfigSpec{ Nutanix: &v1alpha1.NutanixSpec{ PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{ - URL: fmt.Sprintf("https://10.0.0.1:%d", v1alpha1.DefaultPrismCentralPort), + URL: fmt.Sprintf( + "https://10.0.0.1:%d", + v1alpha1.DefaultPrismCentralPort, + ), Insecure: false, Credentials: corev1.LocalObjectReference{ Name: "credentials", @@ -145,7 +151,10 @@ func TestVariableValidation(t *testing.T) { Vals: v1alpha1.ClusterConfigSpec{ Nutanix: &v1alpha1.NutanixSpec{ PrismCentralEndpoint: v1alpha1.NutanixPrismCentralEndpointSpec{ - URL: fmt.Sprintf("https://prism-central.nutanix.com:%d", v1alpha1.DefaultPrismCentralPort), + URL: fmt.Sprintf( + "https://prism-central.nutanix.com:%d", + v1alpha1.DefaultPrismCentralPort, + ), Insecure: false, }, // ControlPlaneEndpoint is a required field and must always be set