airshipctl/pkg/phase/helper_test.go
Ruslan Aliev 31995eaf9d 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 <raliev@mirantis.com>
Relates-To: #503
Closes: #2
Closes: #19
2021-04-22 11:37:12 -05:00

605 lines
15 KiB
Go

/*
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
}