/* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package phase_test import ( "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" "opendev.org/airship/airshipctl/pkg/api/v1alpha1" airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1" "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/phase" "opendev.org/airship/airshipctl/pkg/phase/executors/errors" "opendev.org/airship/airshipctl/pkg/phase/ifc" ) const ( brokenMetaPath = "broken_metadata.yaml" noPlanMetaPath = "no_plan_site/metadata.yaml" ) func TestHelperPhase(t *testing.T) { testCases := []struct { name string errContains string helperErr bool phaseID ifc.ID config func(t *testing.T) *config.Config expectedPhase *airshipv1.Phase }{ { name: "Success Get existing phase", config: testConfig, phaseID: ifc.ID{Name: "capi_init"}, expectedPhase: &airshipv1.Phase{ TypeMeta: metav1.TypeMeta{ APIVersion: "airshipit.org/v1alpha1", Kind: "Phase", }, ObjectMeta: metav1.ObjectMeta{ Name: "capi_init", }, Config: airshipv1.PhaseConfig{ ExecutorRef: &corev1.ObjectReference{ Kind: "Clusterctl", APIVersion: "airshipit.org/v1alpha1", Name: "clusterctl-v1", }, DocumentEntryPoint: "valid_site/phases", }, }, }, { name: "Error get non-existing phase", config: testConfig, phaseID: ifc.ID{Name: "some_name"}, errContains: "found no documents", }, { name: "Error bundle path doesn't exist", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = brokenMetaPath return conf }, errContains: "no such file or directory", helperErr: true, }, } for _, test := range testCases { tt := test t.Run(tt.name, func(t *testing.T) { helper, err := phase.NewHelper(tt.config(t)) if tt.helperErr { require.Error(t, err) return } require.NotNil(t, helper) actualPhase, actualErr := helper.Phase(tt.phaseID) if tt.errContains != "" { require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errContains) } else { require.NoError(t, actualErr) assert.Equal(t, tt.expectedPhase, actualPhase) } }) } } func TestHelperPlan(t *testing.T) { testPlanName := "phasePlan" testCases := []struct { name string errContains string expectedPlan *v1alpha1.PhasePlan planID ifc.ID config func(t *testing.T) *config.Config bundleErr bool }{ { name: "Valid Phase Plan", config: testConfig, planID: ifc.ID{Name: "phasePlan"}, expectedPlan: &airshipv1.PhasePlan{ TypeMeta: metav1.TypeMeta{ Kind: "PhasePlan", APIVersion: "airshipit.org/v1alpha1", }, ObjectMeta: metav1.ObjectMeta{ Name: testPlanName, }, Phases: []airshipv1.PhaseStep{ { Name: "remotedirect", }, { Name: "initinfra", }, { Name: "some_phase", }, { Name: "capi_init", }, }, }, }, { name: "No Phase Plan", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = noPlanMetaPath return conf }, errContains: "found no documents", }, { name: "Error bundle path doesn't exist", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = brokenMetaPath return conf }, errContains: "no such file or directory", bundleErr: true, }, } for _, test := range testCases { tt := test t.Run(tt.name, func(t *testing.T) { helper, err := phase.NewHelper(tt.config(t)) if tt.bundleErr { require.Error(t, err) return } require.NoError(t, err) require.NotNil(t, helper) actualPlan, actualErr := helper.Plan(ifc.ID{Name: testPlanName}) if tt.errContains != "" { require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errContains) } else { require.NoError(t, actualErr) assert.Equal(t, tt.expectedPlan, actualPlan) } }) } } func TestHelperListPhases(t *testing.T) { testCases := []struct { name string errContains string phaseLen int config func(t *testing.T) *config.Config options ifc.ListPhaseOptions bundleErr bool }{ { name: "Success phase list", phaseLen: 8, config: testConfig, }, { name: "Error bundle path doesn't exist", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = brokenMetaPath return conf }, errContains: "no such file or directory", bundleErr: true, }, { name: "Success 0 phases", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = noPlanMetaPath return conf }, phaseLen: 0, }, { name: "Success with cluster name and phase plan", config: func(t *testing.T) *config.Config { conf := testConfig(t) return conf }, options: ifc.ListPhaseOptions{ClusterName: "some_cluster", PlanID: ifc.ID{Name: "phasePlan"}}, phaseLen: 1, }, { name: "Invalid phase plan name", config: func(t *testing.T) *config.Config { conf := testConfig(t) return conf }, options: ifc.ListPhaseOptions{PlanID: ifc.ID{Name: "NonExistentPlan"}}, phaseLen: 0, errContains: "found no documents", }, } for _, test := range testCases { tt := test t.Run(tt.name, func(t *testing.T) { helper, err := phase.NewHelper(tt.config(t)) if tt.bundleErr { require.Error(t, err) return } require.NoError(t, err) require.NotNil(t, helper) actualList, actualErr := helper.ListPhases(tt.options) if tt.errContains != "" { require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errContains) } else { require.NoError(t, actualErr) assert.Len(t, actualList, tt.phaseLen) } }) } } func TestHelperListPlans(t *testing.T) { testCases := []struct { name string errContains string bundleErr bool expectedLen int config func(t *testing.T) *config.Config }{ { name: "Success plan list", expectedLen: 5, config: testConfig, }, { name: "Error bundle path doesn't exist", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = brokenMetaPath return conf }, errContains: "no such file or directory", bundleErr: true, }, { name: "Success 0 plans", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = noPlanMetaPath return conf }, expectedLen: 0, }, } for _, test := range testCases { tt := test t.Run(tt.name, func(t *testing.T) { helper, err := phase.NewHelper(tt.config(t)) if tt.bundleErr { require.Error(t, err) return } require.NoError(t, err) require.NotNil(t, helper) actualList, actualErr := helper.ListPlans() if tt.errContains != "" { require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errContains) } else { require.NoError(t, actualErr) assert.Len(t, actualList, tt.expectedLen) } }) } } func TestHelperClusterMapAPI(t *testing.T) { testCases := []struct { name string errContains string helperErr bool expectedCMap *v1alpha1.ClusterMap config func(t *testing.T) *config.Config }{ { name: "Success cluster map", expectedCMap: &airshipv1.ClusterMap{ TypeMeta: metav1.TypeMeta{ APIVersion: "airshipit.org/v1alpha1", Kind: "ClusterMap", }, ObjectMeta: metav1.ObjectMeta{ Name: "clusterctl-v1", }, Map: map[string]*airshipv1.Cluster{ "target": { Parent: "ephemeral", Sources: []airshipv1.KubeconfigSource{ { Type: airshipv1.KubeconfigSourceTypeBundle, }, }, }, "ephemeral": { Sources: []airshipv1.KubeconfigSource{ { Type: airshipv1.KubeconfigSourceTypeBundle, }, }, }, }, }, config: testConfig, }, { name: "Error bundle path doesn't exist", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = brokenMetaPath return conf }, errContains: "no such file or directory", helperErr: true, }, { name: "Error no cluster map", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = noPlanMetaPath return conf }, errContains: "found no documents", }, } for _, test := range testCases { tt := test t.Run(tt.name, func(t *testing.T) { helper, err := phase.NewHelper(tt.config(t)) if tt.helperErr { require.Error(t, err) return } require.NoError(t, err) require.NotNil(t, helper) actualCMap, actualErr := helper.ClusterMapAPIobj() if tt.errContains != "" { require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errContains) } else { require.NoError(t, actualErr) assert.Equal(t, tt.expectedCMap, actualCMap) } }) } } func TestHelperClusterMap(t *testing.T) { testCases := []struct { name string errContains string config func(t *testing.T) *config.Config }{ { name: "Success phase list", config: testConfig, }, { name: "Error no cluster map", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = noPlanMetaPath return conf }, errContains: "found no documents", }, } for _, test := range testCases { tt := test t.Run(tt.name, func(t *testing.T) { helper, err := phase.NewHelper(tt.config(t)) require.NoError(t, err) require.NotNil(t, helper) actualCMap, actualErr := helper.ClusterMap() if tt.errContains != "" { require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errContains) } else { require.NoError(t, actualErr) assert.NotNil(t, actualCMap) } }) } } func TestHelperExecutorDoc(t *testing.T) { testCases := []struct { name string errContains string expectedExecutor string helperErr bool phaseID ifc.ID config func(t *testing.T) *config.Config }{ { name: "Success Get existing phase", config: testConfig, phaseID: ifc.ID{Name: "capi_init"}, expectedExecutor: "clusterctl-v1", }, { name: "Error get non-existing phase", config: testConfig, phaseID: ifc.ID{Name: "some_name"}, errContains: "found no documents", }, { name: "Error bundle path doesn't exist", config: func(t *testing.T) *config.Config { conf := testConfig(t) conf.Manifests["dummy_manifest"].MetadataPath = brokenMetaPath return conf }, helperErr: true, }, { name: "Error get phase without executor", config: testConfig, phaseID: ifc.ID{Name: "no_executor_phase"}, errContains: errors.ErrExecutorRefNotDefined{ PhaseName: "no_executor_phase", }.Error(), }, } for _, test := range testCases { tt := test t.Run(tt.name, func(t *testing.T) { helper, err := phase.NewHelper(tt.config(t)) if tt.helperErr { require.Error(t, err) return } require.NoError(t, err) actualDoc, actualErr := helper.ExecutorDoc(tt.phaseID) if tt.errContains != "" { require.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errContains) } else { require.NoError(t, actualErr) require.NotNil(t, actualDoc) assert.Equal(t, tt.expectedExecutor, actualDoc.GetName()) } }) } } func TestHelperTargetPath(t *testing.T) { helper, err := phase.NewHelper(testConfig(t)) require.NoError(t, err) require.NotNil(t, helper) assert.Equal(t, "testdata", helper.TargetPath()) } func TestHelperPhaseBundleRoot(t *testing.T) { helper, err := phase.NewHelper(testConfig(t)) require.NoError(t, err) require.NotNil(t, helper) expectedPhaseBundleRoot := filepath.Join("testdata", "valid_site", "phases") assert.Equal(t, expectedPhaseBundleRoot, helper.PhaseBundleRoot()) } func TestHelperPhaseEntryPointBasePath(t *testing.T) { cfg := testConfig(t) cfg.Manifests["dummy_manifest"].TargetPath = "testdata" cfg.Manifests["dummy_manifest"].Repositories["primary"].URLString = "http://dummy.org/reponame.git" cfg.Manifests["dummy_manifest"].MetadataPath = "../valid_site_with_doc_prefix/metadata.yaml" helper, err := phase.NewHelper(cfg) require.NoError(t, err) require.NotNil(t, helper) expectedPhaseEntryPointBasePath := filepath.Join("testdata", "reponame", "valid_site_with_doc_prefix", "phases") assert.Equal(t, expectedPhaseEntryPointBasePath, helper.PhaseEntryPointBasePath()) } func TestHelperPhaseRepoDir(t *testing.T) { cfg := testConfig(t) cfg.Manifests["dummy_manifest"].Repositories["primary"].URLString = "http://dummy.org/reponame.git" cfg.Manifests["dummy_manifest"].MetadataPath = "../valid_site/metadata.yaml" helper, err := phase.NewHelper(cfg) require.NoError(t, err) require.NotNil(t, helper) assert.Equal(t, "reponame", helper.PhaseRepoDir()) } func TestHelperDocEntryPointPrefix(t *testing.T) { cfg := testConfig(t) cfg.Manifests["dummy_manifest"].MetadataPath = "valid_site_with_doc_prefix/metadata.yaml" helper, err := phase.NewHelper(cfg) require.NoError(t, err) require.NotNil(t, helper) assert.Equal(t, "valid_site_with_doc_prefix/phases", helper.DocEntryPointPrefix()) } func TestHelperEmptyDocEntryPointPrefix(t *testing.T) { cfg := testConfig(t) helper, err := phase.NewHelper(cfg) require.NoError(t, err) require.NotNil(t, helper) assert.Equal(t, "", helper.DocEntryPointPrefix()) } func TestHelperWorkdir(t *testing.T) { helper, err := phase.NewHelper(testConfig(t)) require.NoError(t, err) require.NotNil(t, helper) workDir := helper.WorkDir() assert.Greater(t, len(workDir), 0) } func TestHelperInventory(t *testing.T) { helper, err := phase.NewHelper(testConfig(t)) require.NoError(t, err) require.NotNil(t, helper) inv := helper.Inventory() assert.NotNil(t, inv) } func testConfig(t *testing.T) *config.Config { t.Helper() confString := `apiVersion: airshipit.org/v1alpha1 contexts: dummy_cluster: manifest: dummy_manifest currentContext: dummy_cluster kind: Config manifests: dummy_manifest: phaseRepositoryName: primary targetPath: testdata metadataPath: valid_site/metadata.yaml repositories: primary: url: "empty/filename/"` conf := config.NewConfig() err := yaml.Unmarshal([]byte(confString), conf) require.NoError(t, err) return conf }