From 31995eaf9d95f64a76439b2f256b5367650e69f0 Mon Sep 17 00:00:00 2001 From: Ruslan Aliev Date: Mon, 15 Mar 2021 18:32:31 -0500 Subject: [PATCH] Add validation phases This patch introduces ability to validate phases using kubeval. Appropriate functionality was embedded into phase/plan validate command. Change-Id: I1e1ccae2b7e4948bdc97a199c96c07a3eb7292b2 Signed-off-by: Ruslan Aliev Relates-To: #503 Closes: #2 Closes: #19 --- .../function/validator/kustomization.yaml | 2 + manifests/function/validator/template.yaml | 26 ++++++ manifests/function/workers-capd/workers.yaml | 2 +- manifests/function/workers-capg/workers.yaml | 2 +- manifests/function/workers-capo/workers.yaml | 2 +- .../function/workers-capz/v0.4.8/workers.yaml | 2 +- .../function/workers-capz/v0.4.9/workers.yaml | 2 +- .../metal3.io_baremetalhosts_crd.yaml | 10 +++ manifests/phases/executors.yaml | 82 +++++++++++++------ .../site/test-site/phases/kustomization.yaml | 2 + .../test-site/phases/validation-config.yaml | 73 +++++++++++++++++ pkg/api/v1alpha1/genericcontainer_types.go | 5 ++ pkg/document/bundle.go | 21 +++++ pkg/document/constants.go | 9 ++ pkg/document/selectors.go | 8 ++ pkg/phase/client.go | 73 ++++++++++++----- pkg/phase/client_test.go | 15 ++-- pkg/phase/command.go | 2 + pkg/phase/command_test.go | 2 + pkg/phase/helper.go | 2 +- pkg/phase/helper_test.go | 3 +- tools/document/validate_site_docs.sh | 23 +----- tools/validate_docs | 5 ++ zuul.d/jobs.yaml | 2 +- 24 files changed, 295 insertions(+), 80 deletions(-) create mode 100755 manifests/function/validator/kustomization.yaml create mode 100755 manifests/function/validator/template.yaml create mode 100755 manifests/site/test-site/phases/validation-config.yaml diff --git a/manifests/function/validator/kustomization.yaml b/manifests/function/validator/kustomization.yaml new file mode 100755 index 000000000..b1e944410 --- /dev/null +++ b/manifests/function/validator/kustomization.yaml @@ -0,0 +1,2 @@ +generators: + - template.yaml diff --git a/manifests/function/validator/template.yaml b/manifests/function/validator/template.yaml new file mode 100755 index 000000000..bd778dd7e --- /dev/null +++ b/manifests/function/validator/template.yaml @@ -0,0 +1,26 @@ +apiVersion: airshipit.org/v1alpha1 +kind: Templater +metadata: + name: validator-config-patch-template + annotations: + config.kubernetes.io/function: | + container: + image: quay.io/airshipit/templater:v2 + envs: + - AIRSHIPCTL_CURRENT_PHASE + - AIRSHIPCTL_CURRENT_PLAN +template: | + {{- $currentPhase := env "AIRSHIPCTL_CURRENT_PHASE" }} + {{- $currentPlan := env "AIRSHIPCTL_CURRENT_PLAN" }} + apiVersion: builtin + kind: PatchStrategicMergeTransformer + metadata: + name: smp + patches: |- + --- + apiVersion: airshipit.org/v1alpha1 + kind: KubevalOptions + metadata: + name: kubeval-options + phaseName: {{ $currentPhase }} + planName: {{ $currentPlan }} diff --git a/manifests/function/workers-capd/workers.yaml b/manifests/function/workers-capd/workers.yaml index 68a8ff61c..33f4f03b6 100644 --- a/manifests/function/workers-capd/workers.yaml +++ b/manifests/function/workers-capd/workers.yaml @@ -27,7 +27,7 @@ spec: clusterName: "target-cluster" replicas: ${ WORKER_MACHINE_COUNT } selector: - matchLabels: + matchLabels: {} template: spec: clusterName: "target-cluster" diff --git a/manifests/function/workers-capg/workers.yaml b/manifests/function/workers-capg/workers.yaml index 13a0ca395..a6834095c 100644 --- a/manifests/function/workers-capg/workers.yaml +++ b/manifests/function/workers-capg/workers.yaml @@ -7,7 +7,7 @@ spec: clusterName: "target-cluster" replicas: "${WORKER_MACHINE_COUNT}" selector: - matchLabels: + matchLabels: {} template: spec: clusterName: "target-cluster" diff --git a/manifests/function/workers-capo/workers.yaml b/manifests/function/workers-capo/workers.yaml index 4b9714cd0..2f5fcf951 100644 --- a/manifests/function/workers-capo/workers.yaml +++ b/manifests/function/workers-capo/workers.yaml @@ -7,7 +7,7 @@ spec: clusterName: target-cluster replicas: 0 selector: - matchLabels: null + matchLabels: {} template: spec: bootstrap: diff --git a/manifests/function/workers-capz/v0.4.8/workers.yaml b/manifests/function/workers-capz/v0.4.8/workers.yaml index 7af91b5ad..11aa8b303 100644 --- a/manifests/function/workers-capz/v0.4.8/workers.yaml +++ b/manifests/function/workers-capz/v0.4.8/workers.yaml @@ -7,7 +7,7 @@ spec: clusterName: target-cluster replicas: 3 selector: - matchLabels: null + matchLabels: {} template: spec: bootstrap: diff --git a/manifests/function/workers-capz/v0.4.9/workers.yaml b/manifests/function/workers-capz/v0.4.9/workers.yaml index d69ff6f03..22c4074e8 100644 --- a/manifests/function/workers-capz/v0.4.9/workers.yaml +++ b/manifests/function/workers-capz/v0.4.9/workers.yaml @@ -8,7 +8,7 @@ spec: clusterName: target-cluster replicas: 3 selector: - matchLabels: null + matchLabels: {} template: spec: bootstrap: diff --git a/manifests/global/crd/baremetal-operator/metal3.io_baremetalhosts_crd.yaml b/manifests/global/crd/baremetal-operator/metal3.io_baremetalhosts_crd.yaml index bb28fe98b..381afc1c8 100644 --- a/manifests/global/crd/baremetal-operator/metal3.io_baremetalhosts_crd.yaml +++ b/manifests/global/crd/baremetal-operator/metal3.io_baremetalhosts_crd.yaml @@ -177,6 +177,16 @@ spec: name must be unique. type: string type: object + firmware: + description: firmware holds the reference for creating and consuming hardware profiles + properties: + simultaneousMultithreadingDisabled: + type: boolean + sriovEnabled: + type: boolean + virtualizationDisabled: + type: boolean + type: object online: description: Should the server be online? type: boolean diff --git a/manifests/phases/executors.yaml b/manifests/phases/executors.yaml index 550918779..bb72f99a1 100644 --- a/manifests/phases/executors.yaml +++ b/manifests/phases/executors.yaml @@ -58,8 +58,8 @@ spec: sinkOutputDir: "target/generator/results/generated" image: gcr.io/kpt-fn-contrib/sops:v0.1.0 envVars: - - SOPS_IMPORT_PGP - - SOPS_PGP_FP + - SOPS_IMPORT_PGP + - SOPS_PGP_FP config: | apiVersion: v1 kind: ConfigMap @@ -77,8 +77,8 @@ spec: type: krm image: gcr.io/kpt-fn-contrib/sops:v0.1.0 envVars: - - SOPS_IMPORT_PGP - - SOPS_PGP_FP + - SOPS_IMPORT_PGP + - SOPS_PGP_FP config: | apiVersion: v1 kind: ConfigMap @@ -211,10 +211,10 @@ spec: type: krm image: quay.io/airshipit/cloud-init:v2 mounts: - - type: bind - src: /srv/images - dst: /config - rw: true + - type: bind + src: /srv/images + dst: /config + rw: true config: | apiVersion: airshipit.org/v1alpha1 kind: IsoConfiguration @@ -246,28 +246,28 @@ spec: privileged: true containerRuntime: docker cmd: - - /bin/bash - - -c - - /usr/bin/local/entrypoint.sh 1>&2 + - /bin/bash + - -c + - /usr/bin/local/entrypoint.sh 1>&2 image: quay.io/airshipit/image-builder:latest-ubuntu_focal mounts: - - type: bind - src: /srv/images - dst: /config - rw: true + - type: bind + src: /srv/images + dst: /config + rw: true envVars: - - IMAGE_TYPE=iso - - BUILDER_CONFIG=/config/builder-conf.yaml - - USER_DATA_FILE=user-data - - NET_CONFIG_FILE=network-data - - OUTPUT_FILE_NAME=ephemerial.iso - - OUTPUT_METADATA_FILE_NAME=output-metadata.yaml - - http_proxy - - https_proxy - - HTTP_PROXY - - HTTPS_PROXY - - no_proxy - - NO_PROXY + - IMAGE_TYPE=iso + - BUILDER_CONFIG=/config/builder-conf.yaml + - USER_DATA_FILE=user-data + - NET_CONFIG_FILE=network-data + - OUTPUT_FILE_NAME=ephemerial.iso + - OUTPUT_METADATA_FILE_NAME=output-metadata.yaml + - http_proxy + - https_proxy + - HTTP_PROXY + - HTTPS_PROXY + - no_proxy + - NO_PROXY config: | apiVersion: airshipit.org/v1alpha1 kind: DoesNotMatter @@ -363,3 +363,31 @@ configRef: kind: ConfigMap name: kubectl-wait-pods apiVersion: v1 +--- +apiVersion: airshipit.org/v1alpha1 +kind: GenericContainer +metadata: + name: document-validation + labels: + airshipit.org/deploy-k8s: "false" +spec: + type: krm + image: quay.io/airshipit/kubeval-validator:latest + envVars: + - VALIDATOR_PREVENT_CLEANUP # Validator won't cleanup its working directory after finish + - VALIDATOR_PLAN_VALIDATION # Validator will not use phase-specific settings for validation + - VALIDATOR_REWRITE_SCHEMAS # Validator will rewrite schemas for kubeval if they already exist + mounts: + - type: bind + src: airshipctl/manifests + dst: /manifests + rw: false + - type: bind + src: ~/.airship + dst: /workdir + rw: true + hostNetwork: true +configRef: + apiVersion: airshipit.org/v1alpha1 + kind: KubevalOptions + name: kubeval-options diff --git a/manifests/site/test-site/phases/kustomization.yaml b/manifests/site/test-site/phases/kustomization.yaml index 55159500e..9c0f2f0b6 100644 --- a/manifests/site/test-site/phases/kustomization.yaml +++ b/manifests/site/test-site/phases/kustomization.yaml @@ -2,5 +2,7 @@ resources: - ../kubeconfig - ../../../phases - catalogue.yaml + - validation-config.yaml transformers: - ../../../function/bootstrap/replacements + - ../../../function/validator diff --git a/manifests/site/test-site/phases/validation-config.yaml b/manifests/site/test-site/phases/validation-config.yaml new file mode 100755 index 000000000..06fe03f3b --- /dev/null +++ b/manifests/site/test-site/phases/validation-config.yaml @@ -0,0 +1,73 @@ +apiVersion: airshipit.org/v1alpha1 +kind: KubevalOptions +metadata: + name: kubeval-options + labels: + airshipit.org/deploy-k8s: "false" +siteConfig: + strict: true + kubernetesVersion: "1.16.0" + ignoreMissingSchemas: false +planName: AIRSHIPCTL_CURRENT_PLAN +planConfigs: + phasePlan: + kindsToSkip: + - Clusterctl + - VariableCatalogue + crdList: + - function/airshipctl-schemas/versions-catalogue.yaml + - function/airshipctl-schemas/network-catalogue.yaml +phaseName: AIRSHIPCTL_CURRENT_PHASE +phaseConfigs: + initinfra-ephemeral: + kindsToSkip: + - Clusterctl + - VariableCatalogue + crdList: + - function/airshipctl-schemas/versions-catalogue.yaml + - function/airshipctl-schemas/network-catalogue.yaml + clusterctl-init-ephemeral: + crdList: + - function/cert-manager/v1.1.0/upstream/cert-manager.yaml + controlplane-ephemeral: + kindsToSkip: + - VariableCatalogue + crdList: + - function/airshipctl-schemas/network-catalogue.yaml + - function/airshipctl-schemas/versions-catalogue.yaml + - function/capi/v0.3.7/crd/bases/cluster.x-k8s.io_clusters.yaml + - function/cacpk/v0.3.7/crd/bases/controlplane.cluster.x-k8s.io_kubeadmcontrolplanes.yaml + - function/capm3/v0.4.0/crd/bases/infrastructure.cluster.x-k8s.io_metal3clusters.yaml + - function/capm3/v0.4.0/crd/bases/infrastructure.cluster.x-k8s.io_metal3machinetemplates.yaml + - global/crd/baremetal-operator/metal3.io_baremetalhosts_crd.yaml + clusterctl-init-target: + crdList: + - function/cert-manager/v1.1.0/upstream/cert-manager.yaml + initinfra-target: + kindsToSkip: + - Clusterctl + - VariableCatalogue + crdList: + - function/airshipctl-schemas/network-catalogue.yaml + - function/airshipctl-schemas/versions-catalogue.yaml + workers-target: + crdList: + - global/crd/baremetal-operator/metal3.io_baremetalhosts_crd.yaml + workers-classification: + kindsToSkip: + - VariableCatalogue + crdList: + - function/airshipctl-schemas/network-catalogue.yaml + - function/airshipctl-schemas/versions-catalogue.yaml + - function/cabpk/v0.3.7/crd/bases/bootstrap.cluster.x-k8s.io_kubeadmconfigtemplates.yaml + - function/capi/v0.3.7/crd/bases/cluster.x-k8s.io_machinedeployments.yaml + - function/capm3/v0.4.0/crd/bases/infrastructure.cluster.x-k8s.io_metal3machinetemplates.yaml + - function/hwcc/crd/bases/metal3.io_hardwareclassifications.yaml + workload-target: + kindsToSkip: + - VariableCatalogue + crdList: + - function/airshipctl-schemas/network-catalogue.yaml + - function/airshipctl-schemas/versions-catalogue.yaml + - function/flux/helm-controller/upstream/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml + - function/flux/source-controller/upstream/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml diff --git a/pkg/api/v1alpha1/genericcontainer_types.go b/pkg/api/v1alpha1/genericcontainer_types.go index 60b8db72d..9e48f47e3 100644 --- a/pkg/api/v1alpha1/genericcontainer_types.go +++ b/pkg/api/v1alpha1/genericcontainer_types.go @@ -35,6 +35,11 @@ const ( KubeConfigEnvKeyContext = "KCTL_CONTEXT" // KubeConfigEnv uses as a kubeconfig env variable KubeConfigEnv = KubeConfigEnvKey + "=" + KubeConfigPath + + // ValidatorPreventCleanup is an env variable that prevents validator to clean up its working directory after finish + ValidatorPreventCleanup = "VALIDATOR_PREVENT_CLEANUP" + // ValidatorPlanValidation is an env variable that tells validator not to use phase-specific config for validation + ValidatorPlanValidation = "VALIDATOR_PLAN_VALIDATION" ) // +kubebuilder:object:root=true diff --git a/pkg/document/bundle.go b/pkg/document/bundle.go index deee6bf89..7cea4f5ad 100644 --- a/pkg/document/bundle.go +++ b/pkg/document/bundle.go @@ -59,10 +59,31 @@ type Bundle interface { Append(Document) error } +// DocFactoryFunc is a type of function which returns (Document, error) and can be used on demand +type DocFactoryFunc func() (Document, error) + // BundleFactoryFunc is a function that returns bundle, can be used to build bundle on demand // instead of inplace, useful, when you don't know if bundle will be needed or not, see phase for detail type BundleFactoryFunc func() (Bundle, error) +// BundleFactoryFromBytes is a function which returns BundleFactoryFunc based on new bundle from bytes +func BundleFactoryFromBytes(data []byte) BundleFactoryFunc { + return func() (Bundle, error) { + return NewBundleFromBytes(data) + } +} + +// BundleFactoryFromDocRoot is a function which returns BundleFactoryFunc based on new bundle from DocumentRoot path +func BundleFactoryFromDocRoot(docRootFunc func() (string, error)) BundleFactoryFunc { + return func() (Bundle, error) { + path, err := docRootFunc() + if err != nil { + return nil, err + } + return NewBundleByPath(path) + } +} + // NewBundleByPath is a function which builds new document.Bundle from kustomize rootPath using default FS object // example: document.NewBundleByPath("path/to/phase-root") func NewBundleByPath(rootPath string) (Bundle, error) { diff --git a/pkg/document/constants.go b/pkg/document/constants.go index e18570a52..97096b96b 100644 --- a/pkg/document/constants.go +++ b/pkg/document/constants.go @@ -36,6 +36,15 @@ const ( ClusterctlMetadataKind = "Metadata" ClusterctlMetadataVersion = "v1alpha3" ClusterctlMetadataGroup = "clusterctl.cluster.x-k8s.io" + + // DocumentValidationGroup defines Group for document-validation container + DocumentValidationGroup = "airshipit.org" + // DocumentValidationVersion defines Version for document-validation container + DocumentValidationVersion = "v1alpha1" + // DocumentValidationKind defines Kind for document-validation container + DocumentValidationKind = "GenericContainer" + // DocumentValidationName defines Name for document-validation container + DocumentValidationName = "document-validation" ) // KustomizationFile is used for kustomization file diff --git a/pkg/document/selectors.go b/pkg/document/selectors.go index e72f105a7..4f42c645d 100644 --- a/pkg/document/selectors.go +++ b/pkg/document/selectors.go @@ -190,6 +190,14 @@ func NewClusterctlMetadataSelector() Selector { ClusterctlMetadataKind) } +// NewValidatorExecutorSelector returns selector to get validator executor documents +func NewValidatorExecutorSelector() Selector { + return NewSelector().ByGvk(DocumentValidationGroup, + DocumentValidationVersion, + DocumentValidationKind). + ByName(DocumentValidationName) +} + //GetSecretData returns data located with a given key of a given document func GetSecretData(docBundle Bundle, apiSelector types.Selector, key string) ([]byte, error) { s := NewSelector() diff --git a/pkg/phase/client.go b/pkg/phase/client.go index 1221ccd20..ea5cac3d1 100644 --- a/pkg/phase/client.go +++ b/pkg/phase/client.go @@ -15,8 +15,8 @@ package phase import ( + "bytes" "io" - "io/ioutil" "path/filepath" "k8s.io/apimachinery/pkg/runtime/schema" @@ -32,6 +32,7 @@ import ( "opendev.org/airship/airshipctl/pkg/phase/executors" executorerrors "opendev.org/airship/airshipctl/pkg/phase/executors/errors" "opendev.org/airship/airshipctl/pkg/phase/ifc" + "opendev.org/airship/airshipctl/pkg/util" ) // ExecutorRegistry returns map with executor factories @@ -60,22 +61,33 @@ type phase struct { processor events.EventProcessor } +func (p *phase) defaultBundleFactory() document.BundleFactoryFunc { + return document.BundleFactoryFromDocRoot(p.DocumentRoot) +} + +func (p *phase) defaultDocFactory() document.DocFactoryFunc { + return func() (document.Document, error) { + return p.helper.ExecutorDoc(ifc.ID{Name: p.apiObj.Name, Namespace: p.apiObj.Namespace}) + } +} + // Executor returns executor interface associated with the phase func (p *phase) Executor() (ifc.Executor, error) { - executorDoc, err := p.helper.ExecutorDoc(ifc.ID{Name: p.apiObj.Name, Namespace: p.apiObj.Namespace}) + return p.executor(p.defaultDocFactory(), p.defaultBundleFactory()) +} + +func (p *phase) executor(docFactory document.DocFactoryFunc, + bundleFactory document.BundleFactoryFunc) (ifc.Executor, error) { + executorDoc, err := docFactory() if err != nil { return nil, err } - var bundleFactory document.BundleFactoryFunc = func() (document.Bundle, error) { - docRoot, bundleFactoryFuncErr := p.DocumentRoot() - if bundleFactoryFuncErr != nil { - return nil, bundleFactoryFuncErr - } - return document.NewBundleByPath(docRoot) + refGVK := schema.GroupVersionKind{ + Group: executorDoc.GetGroup(), + Version: executorDoc.GetVersion(), + Kind: executorDoc.GetKind(), } - - refGVK := p.apiObj.Config.ExecutorRef.GroupVersionKind() // Look for executor factory defined in registry executorFactory, found := p.registry()[refGVK] if !found { @@ -134,18 +146,34 @@ func (p *phase) Run(ro ifc.RunOptions) error { // Validate makes sure that phase is properly configured func (p *phase) Validate() error { - // Check that we can render documents supplied to phase - err := p.Render(ioutil.Discard, false, ifc.RenderOptions{}) - if err != nil { - return err - } - - // Check that executor if properly configured executor, err := p.Executor() if err != nil { return err } - return executor.Validate() + if err = executor.Validate(); err != nil { + return err + } + + buf := &bytes.Buffer{} + if err = executor.Render(buf, ifc.RenderOptions{FilterSelector: document.NewSelector()}); err != nil { + return err + } + + defer p.processor.Close() + + executor, err = p.executor(func() (document.Document, error) { + return p.helper.PhaseConfigBundle(). + SelectOne(document.NewValidatorExecutorSelector()) + }, document.BundleFactoryFromBytes(buf.Bytes())) + if err != nil { + return err + } + + ch := make(chan events.Event) + go func() { + executor.Run(ch, ifc.RunOptions{}) + }() + return p.processor.Process(ch) } // Render executor documents @@ -219,7 +247,12 @@ type plan struct { // Validate makes sure that phase plan is properly configured func (p *plan) Validate() error { - for _, step := range p.apiObj.Phases { + util.Setenv(util.EnvVar{Key: v1alpha1.ValidatorPreventCleanup}, util.EnvVar{Key: v1alpha1.ValidatorPlanValidation}) + for i, step := range p.apiObj.Phases { + log.Printf("validating phase: %s\n", step.Name) + if i == len(p.apiObj.Phases)-1 { + util.Unsetenv(util.EnvVar{Key: v1alpha1.ValidatorPreventCleanup}) + } phaseRunner, err := p.phaseClient.PhaseByID(ifc.ID{Name: step.Name}) if err != nil { return err @@ -239,7 +272,7 @@ func (p *plan) Run(ro ifc.RunOptions) error { return err } - log.Printf("executing phase: %s\n", step) + log.Printf("executing phase: %s\n", step.Name) if err = phaseRunner.Run(ro); err != nil { return err } diff --git a/pkg/phase/client_test.go b/pkg/phase/client_test.go index b04a1237c..d47ff193d 100644 --- a/pkg/phase/client_test.go +++ b/pkg/phase/client_test.go @@ -69,7 +69,7 @@ func TestClientPhaseExecutor(t *testing.T) { for _, tt := range tests { tt := tt - t.Run("", func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { conf := tt.configFunc(t) helper, err := phase.NewHelper(conf) require.NoError(t, err) @@ -77,7 +77,8 @@ func TestClientPhaseExecutor(t *testing.T) { client := phase.NewClient(helper, phase.InjectRegistry(tt.registryFunc)) require.NotNil(t, client) p, err := client.PhaseByID(tt.phaseID) - require.NotNil(t, client) + require.NotNil(t, p) + require.NoError(t, err) executor, err := p.Executor() if tt.errContains != "" { require.Error(t, err) @@ -150,13 +151,15 @@ func TestPhaseValidate(t *testing.T) { configFunc: testConfig, phaseID: ifc.ID{Name: "capi_init"}, registryFunc: fakeRegistry, + errContains: `document filtered by selector [Group="airshipit.org", Version="v1alpha1", ` + + `Kind="GenericContainer", Name="document-validation"] found no documents`, }, { name: "Error no document entry point", configFunc: testConfig, phaseID: ifc.ID{Name: "no_entry_point"}, registryFunc: fakeRegistry, - errContains: "documentEntryPoint is not defined for the phase 'no_entry_point' in namespace ''", + errContains: "executor identified by 'airshipit.org/v1alpha1, Kind=SomeExecutor' is not found", }, { name: "Error no executor", @@ -391,13 +394,15 @@ func TestPlanValidate(t *testing.T) { configFunc: testConfig, planID: ifc.ID{Name: "init"}, registryFunc: fakeRegistry, + errContains: `document filtered by selector [Group="airshipit.org", Version="v1alpha1", ` + + `Kind="GenericContainer", Name="document-validation"] found no documents`, }, { name: "Invalid fake executor", configFunc: testConfig, planID: ifc.ID{Name: "plan_invalid_phase"}, registryFunc: fakeRegistry, - errContains: "documentEntryPoint is not defined for the phase 'no_entry_point' in namespace ''", + errContains: "executor identified by 'airshipit.org/v1alpha1, Kind=SomeExecutor' is not found", }, { name: "Phase does not exist", @@ -430,7 +435,7 @@ func TestPlanValidate(t *testing.T) { } } -func fakeExecFactory(config ifc.ExecutorConfig) (ifc.Executor, error) { +func fakeExecFactory(_ ifc.ExecutorConfig) (ifc.Executor, error) { return fakeExecutor{}, nil } diff --git a/pkg/phase/command.go b/pkg/phase/command.go index 9efae238d..353aa9423 100644 --- a/pkg/phase/command.go +++ b/pkg/phase/command.go @@ -298,6 +298,7 @@ func (c *ValidateCommand) RunE() error { return err } + util.Setenv(util.EnvVar{Key: "AIRSHIPCTL_CURRENT_PHASE", Value: c.Options.PhaseID.Name}) helper, err := NewHelper(cfg) if err != nil { return err @@ -364,6 +365,7 @@ func (c *PlanValidateCommand) RunE() error { return err } + util.Setenv(util.EnvVar{Key: "AIRSHIPCTL_CURRENT_PLAN", Value: c.Options.PlanID.Name}) helper, err := NewHelper(cfg) if err != nil { return err diff --git a/pkg/phase/command_test.go b/pkg/phase/command_test.go index 352936f47..3507ca698 100644 --- a/pkg/phase/command_test.go +++ b/pkg/phase/command_test.go @@ -606,6 +606,8 @@ func TestValidateCommand(t *testing.T) { } return conf, nil }, + errContains: `document filtered by selector [Group="airshipit.org", Version="v1alpha1", ` + + `Kind="GenericContainer", Name="document-validation"] found no documents`, }, } diff --git a/pkg/phase/helper.go b/pkg/phase/helper.go index 6b938bf2d..7d8d4887d 100644 --- a/pkg/phase/helper.go +++ b/pkg/phase/helper.go @@ -258,8 +258,8 @@ func (helper *Helper) ExecutorDoc(phaseID ifc.ID) (document.Document, error) { if err != nil { return nil, err } - phaseConfig := phaseObj.Config + phaseConfig := phaseObj.Config if phaseConfig.ExecutorRef == nil { return nil, errors.ErrExecutorRefNotDefined{PhaseName: phaseID.Name, PhaseNamespace: phaseID.Namespace} } diff --git a/pkg/phase/helper_test.go b/pkg/phase/helper_test.go index fae6971c4..3088b05ac 100644 --- a/pkg/phase/helper_test.go +++ b/pkg/phase/helper_test.go @@ -475,8 +475,7 @@ func TestHelperExecutorDoc(t *testing.T) { conf.Manifests["dummy_manifest"].MetadataPath = brokenMetaPath return conf }, - errContains: "no such file or directory", - helperErr: true, + helperErr: true, }, { name: "Error get phase without executor", diff --git a/tools/document/validate_site_docs.sh b/tools/document/validate_site_docs.sh index f7beef38f..e5d5ed779 100755 --- a/tools/document/validate_site_docs.sh +++ b/tools/document/validate_site_docs.sh @@ -20,7 +20,6 @@ set -xe # The location of sites whose manifests should be validated. # This are relative to MANIFEST_ROOT above : ${SITE_ROOT:="$(basename "${PWD}")/manifests/site"} -: ${SCHEMAS_ROOT:="${PWD}/manifests/function/airshipctl-schemas"} : ${MANIFEST_REPO_URL:="https://review.opendev.org/airship/airshipctl"} : ${SITE:="test-workload"} : ${CONTEXT:="kind-airship"} @@ -97,13 +96,14 @@ if [ -f "${AIRSHIPKUBECONFIG}" ]; then cp "${AIRSHIPKUBECONFIG}" "${AIRSHIPKUBECONFIG_BACKUP}" fi - generate_airshipconf "default" -catalogues=("versions" "networking") - phase_plans=$(airshipctl --airshipconf ${AIRSHIPCONFIG} plan list | grep "PhasePlan" | awk -F '/' '{print $2}' | awk '{print $1}') for plan in $phase_plans; do + # Perform static validation first, add support of all plans later + if [ "$plan" = "phasePlan" ]; then + airshipctl --airshipconf ${AIRSHIPCONFIG} plan validate $plan + fi cluster_list=$(airshipctl --airshipconf ${AIRSHIPCONFIG} cluster list) # Loop over all cluster types and phases for the given site @@ -123,9 +123,6 @@ for plan in $phase_plans; do # In the meantime, as new phases are added, please add them here as well. phases=$(airshipctl --airshipconf ${AIRSHIPCONFIG} phase list --plan $plan -c $cluster | grep Phase | awk -F '/' '{print $2}' | awk '{print $1}' || true) - # apply catalogue CRDs - ${KUBECTL} --context ${CLUSTER} --kubeconfig ${KUBECONFIG} apply -k ${SCHEMAS_ROOT} - for phase in $phases; do # Guard against bootstrap or initinfra being missing, which could be the case for some configs echo -e "\n*** Rendering ${cluster}/${phase}" @@ -135,10 +132,6 @@ for plan in $phase_plans; do # e.g., load CRDs from initinfra first, so they're present when validating later phases ${AIRSHIPCTL} --airshipconf ${AIRSHIPCONFIG} phase render ${phase} -s executor -k CustomResourceDefinition >${TMP}/${phase}-crds.yaml - # extract rendered catalogue CRs - ${AIRSHIPCTL} --airshipconf ${AIRSHIPCONFIG} phase render ${phase} -s executor -k NetworkCatalogue >${TMP}/${phase}-networking.yaml - ${AIRSHIPCTL} --airshipconf ${AIRSHIPCONFIG} phase render ${phase} -s executor -k VersionsCatalogue >${TMP}/${phase}-versions.yaml - if [ -s ${TMP}/${phase}-crds.yaml ]; then ${KUBECTL} --context ${CLUSTER} apply -f ${TMP}/${phase}-crds.yaml fi @@ -146,14 +139,6 @@ for plan in $phase_plans; do # step 2: dry-run the entire phase ${ACTL} phase run --dry-run ${phase} - # catalogues have the label deploy-k8s: false, so they won't get applied during the dry-run - # and will have to be applied manually here - for catalogue in "${catalogues[@]}" - do - if [ -s ${TMP}/${phase}-${catalogue}.yaml ]; then - ${KUBECTL} --context ${CLUSTER} --kubeconfig ${KUBECONFIG} apply -f ${TMP}/$phase-${catalogue}.yaml --dry-run=client - fi - done done # Delete cluster kubeconfig rm ${KUBECONFIG} diff --git a/tools/validate_docs b/tools/validate_docs index 2d0e79d86..1602739b4 100755 --- a/tools/validate_docs +++ b/tools/validate_docs @@ -29,8 +29,13 @@ TMP=$(KIND_URL=${KIND_URL} ./tools/document/get_kind.sh) export KIND=${TMP}/kind export KUBECTL_URL +sites_to_skip=(az-test-site docker-test-site gcp-test-site openstack-test-site) + for site_root in ${SITE_ROOTS}; do for site in $(ls ${MANIFEST_ROOT}/${site_root}); do + if [[ " ${sites_to_skip[@]} " =~ " ${site} " ]]; then + continue + fi echo -e "\nValidating site: ${MANIFEST_ROOT}/${site_root}/${site}\n****************" MANIFEST_ROOT=${MANIFEST_ROOT} SITE_ROOT=${site_root} SITE=${site} \ ./tools/document/validate_site_docs.sh diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml index 9c4beeb1a..31fdb1583 100644 --- a/zuul.d/jobs.yaml +++ b/zuul.d/jobs.yaml @@ -61,7 +61,7 @@ - job: name: airship-airshipctl-validate-site-docs - timeout: 5400 + timeout: 6600 pre-run: - playbooks/airship-airshipctl-deploy-docker.yaml run: playbooks/airshipctl-gate-runner.yaml