Update metadata.yaml to kubernetes style

* Define GVK for metadata.yaml
* Added supoorting type and registered the kind 'AirshipMetadata' to
  runtime scheme
* Used document object to read metadata instead of reading it as config

Relates-To: #530

Change-Id: I748cd0921ba7bb04ca6fb2999294776e6803ed3e
This commit is contained in:
siraj.yasin 2021-05-19 02:13:30 +00:00 committed by Sirajudeen
parent 655fdb2661
commit f45ece8294
28 changed files with 452 additions and 167 deletions

View File

@ -1,3 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: manifests/site/az-test-site/phases
docEntryPointPrefix: manifests/site/az-test-site
inventory:
path: ""

View File

@ -1,3 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: manifests/site/docker-test-site/phases
docEntryPointPrefix: manifests/site/docker-test-site
inventory:
path: ""

View File

@ -1,3 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: manifests/site/gcp-test-site/phases
docEntryPointPrefix: manifests/site/gcp-test-site
inventory:
path: ""

View File

@ -1,3 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: manifests/site/openstack-test-site/phases
docEntryPointPrefix: manifests/site/openstack-test-site
inventory:
path: ""

View File

@ -0,0 +1,2 @@
resources:
- metadata.yaml

View File

@ -1,3 +1,8 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: manifests/site/test-site/phases
docEntryPointPrefix: manifests/site/test-site

View File

@ -53,6 +53,7 @@ func init() {
&BootConfiguration{},
&GenericContainer{},
&BaremetalManager{},
&ManifestMetadata{},
)
_ = AddToScheme(Scheme) //nolint:errcheck
}

View File

@ -0,0 +1,49 @@
/*
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 v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
// ManifestMetadata defines site specific metadata like inventory and phase path
type ManifestMetadata struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Phase PhaseSpec `json:"phase,omitempty"`
Inventory InventorySpec `json:"inventory,omitempty"`
}
// PhaseSpec represents configuration for a particular phase. It contains a reference to
// the site specific manifest path and doument entry prefix
type PhaseSpec struct {
Path string `json:"path"`
DocumentEntryPointPrefix string `json:"documentEntryPointPrefix"`
}
// InventorySpec contains the path to the host inventory
type InventorySpec struct {
Path string `json:"path"`
}
// DefaultManifestMetadata can be used to safely unmarshal phase object without nil pointers
func DefaultManifestMetadata() *ManifestMetadata {
return &ManifestMetadata{
Phase: PhaseSpec{},
Inventory: InventorySpec{},
}
}

View File

@ -826,6 +826,21 @@ func (in *InitOptions) DeepCopy() *InitOptions {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *InventorySpec) DeepCopyInto(out *InventorySpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InventorySpec.
func (in *InventorySpec) DeepCopy() *InventorySpec {
if in == nil {
return nil
}
out := new(InventorySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IronicSpec) DeepCopyInto(out *IronicSpec) {
*out = *in
@ -1096,6 +1111,33 @@ func (in *Link) DeepCopy() *Link {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ManifestMetadata) DeepCopyInto(out *ManifestMetadata) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Phase = in.Phase
out.Inventory = in.Inventory
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManifestMetadata.
func (in *ManifestMetadata) DeepCopy() *ManifestMetadata {
if in == nil {
return nil
}
out := new(ManifestMetadata)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ManifestMetadata) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MoveOptions) DeepCopyInto(out *MoveOptions) {
*out = *in
@ -1298,6 +1340,21 @@ func (in *PhasePlan) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PhaseSpec) DeepCopyInto(out *PhaseSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhaseSpec.
func (in *PhaseSpec) DeepCopy() *PhaseSpec {
if in == nil {
return nil
}
out := new(PhaseSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PhaseStep) DeepCopyInto(out *PhaseStep) {
*out = *in

View File

@ -327,6 +327,15 @@ func (c *Config) CurrentContextManifest() (*Manifest, error) {
return manifest, nil
}
// CurrentContextMetadataPath returns metadata path from current context's manifest
func (c *Config) CurrentContextMetadataPath() (string, error) {
ccm, err := c.CurrentContextManifest()
if err != nil {
return "", err
}
return ccm.GetMetadataPath(), nil
}
// CurrentContextTargetPath returns target path from current context's manifest
func (c *Config) CurrentContextTargetPath() (string, error) {
ccm, err := c.CurrentContextManifest()
@ -509,34 +518,6 @@ func (c *Config) Purge() error {
return c.fileSystem.RemoveAll(c.loadedConfigPath)
}
// CurrentContextManifestMetadata gets manifest metadata
func (c *Config) CurrentContextManifestMetadata() (*Metadata, error) {
manifest, err := c.CurrentContextManifest()
if err != nil {
return nil, err
}
phaseRepoDir, err := c.CurrentContextPhaseRepositoryDir()
if err != nil {
return nil, err
}
meta := &Metadata{
// Populate with empty values to avoid nil pointers
Inventory: &InventoryMeta{},
PhaseMeta: &PhaseMeta{},
}
data, err := c.fileSystem.ReadFile(filepath.Join(manifest.GetTargetPath(), phaseRepoDir, manifest.MetadataPath))
if err != nil {
return nil, err
}
err = yaml.Unmarshal(data, meta)
if err != nil {
return nil, err
}
return meta, nil
}
// WorkDir returns working directory for airshipctl. Creates if it doesn't exist
func (c *Config) WorkDir() (dir string, err error) {
dir = filepath.Join(util.UserHomeDir(), AirshipConfigDir)

View File

@ -19,7 +19,6 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"opendev.org/airship/airshipctl/pkg/util"
@ -371,94 +370,6 @@ func TestCurrentInventoryRepositoryDir(t *testing.T) {
invRepoDir)
}
func TestCurrentContextManifestMetadata(t *testing.T) {
expectedMeta := &config.Metadata{
Inventory: &config.InventoryMeta{
Path: "manifests/site/inventory",
},
PhaseMeta: &config.PhaseMeta{
Path: "manifests/site/phases",
},
}
conf, cleanup := testutil.InitConfig(t)
defer cleanup(t)
tests := []struct {
name string
metaPath string
currentContext string
expectErr bool
errorChecker func(error) bool
meta *config.Metadata
}{
{
name: "default metadata",
metaPath: "metadata.yaml",
expectErr: false,
currentContext: "testContext",
meta: &config.Metadata{
Inventory: &config.InventoryMeta{
Path: "manifests/site/inventory",
},
PhaseMeta: &config.PhaseMeta{
Path: "manifests/site/phases",
},
},
},
{
name: "no such file or directory",
metaPath: "doesn't exist",
currentContext: "testContext",
expectErr: true,
errorChecker: os.IsNotExist,
},
{
name: "missing context",
currentContext: "doesn't exist",
expectErr: true,
errorChecker: func(err error) bool {
return strings.Contains(err.Error(), "missing configuration")
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
context := &config.Context{
Manifest: "testManifest",
}
repos := map[string]*config.Repository{
config.DefaultTestPhaseRepo: {
URLString: "",
},
}
manifest := &config.Manifest{
MetadataPath: tt.metaPath,
TargetPath: "testdata",
PhaseRepositoryName: config.DefaultTestPhaseRepo,
Repositories: repos,
}
conf.Manifests = map[string]*config.Manifest{
"testManifest": manifest,
}
conf.Contexts = map[string]*config.Context{
"testContext": context,
}
conf.CurrentContext = tt.currentContext
meta, err := conf.CurrentContextManifestMetadata()
if tt.expectErr {
t.Logf("error is %v", err)
require.Error(t, err)
require.NotNil(t, tt.errorChecker)
assert.True(t, tt.errorChecker(err))
} else {
require.NoError(t, err)
require.NotNil(t, meta)
assert.Equal(t, expectedMeta, meta)
}
})
}
}
func TestManagementConfigurationByName(t *testing.T) {
conf, cleanupConfig := testutil.InitConfig(t)
defer cleanupConfig(t)

View File

@ -81,3 +81,8 @@ func (m *Manifest) String() string {
func (m *Manifest) GetTargetPath() string {
return m.TargetPath
}
// GetMetadataPath returns MetadataPath field
func (m *Manifest) GetMetadataPath() string {
return m.MetadataPath
}

View File

@ -0,0 +1,69 @@
/*
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 metadata
import (
"io/ioutil"
apiv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
"opendev.org/airship/airshipctl/pkg/document"
)
// Metadata defines the site specific Phase properties like
// PhasePath, DocEntryPointPrefix & InventoryPath
type Metadata struct {
MetadataPhasePath string
DocEntryPointPrefix string
InventoryPath string
}
// Config returns Metadata with the attributes of Metadata updated
func Config(phaseBundlePath string) (Metadata, error) {
m := Metadata{}
data, err := ioutil.ReadFile(phaseBundlePath)
if err != nil {
return Metadata{}, err
}
bundle, err := document.NewBundleFromBytes(data)
if err != nil {
return Metadata{}, err
}
metaConfig := &apiv1.ManifestMetadata{}
selector, err := document.NewSelector().ByObject(metaConfig, apiv1.Scheme)
if err != nil {
return Metadata{}, err
}
doc, err := bundle.SelectOne(selector)
if err != nil {
return Metadata{}, err
}
m.MetadataPhasePath, err = doc.GetString("spec.phase.path")
if err != nil {
return Metadata{}, err
}
m.DocEntryPointPrefix, err = doc.GetString("spec.phase.docEntryPointPrefix")
if err != nil {
return Metadata{}, err
}
m.InventoryPath, err = doc.GetString("spec.inventory.path")
if err != nil {
return Metadata{}, err
}
return m, nil
}

View File

@ -0,0 +1,80 @@
/*
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 metadata_test
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"opendev.org/airship/airshipctl/pkg/config"
meta "opendev.org/airship/airshipctl/pkg/document/metadata"
"opendev.org/airship/airshipctl/testutil"
)
func TestConfig(t *testing.T) {
rs := testutil.DummyConfig()
dummyManifest := rs.Manifests["dummy_manifest"]
dummyManifest.TargetPath = "testdata"
dummyManifest.PhaseRepositoryName = config.DefaultTestPhaseRepo
dummyManifest.Repositories = map[string]*config.Repository{
config.DefaultTestPhaseRepo: {
URLString: "",
},
}
tests := []struct {
name string
metadataFile string
expError string
}{
{
name: "valid metadata file",
metadataFile: "valid_metadata.yaml",
expError: "",
},
{
name: "non existent metadata file",
metadataFile: "nonexistent_metadata.yaml",
expError: "no such file or directory",
},
{
name: "invalid metadata file",
metadataFile: "invalid_metadata.yaml",
expError: "missing Resource metadata",
},
{
name: "incomplete metadata file",
metadataFile: "incomplete_metadata.yaml",
expError: "no field named 'spec.phase.docEntryPointPrefix'",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
dummyManifest.MetadataPath = tt.metadataFile
metadataPath := filepath.Join("testdata", tt.metadataFile)
metadata, err := meta.Config(metadataPath)
if tt.expError != "" {
assert.Contains(t, err.Error(), tt.expError)
assert.Equal(t, metadata, meta.Metadata{})
} else {
assert.NoError(t, err)
assert.NotNil(t, metadata)
}
})
}
}

View File

@ -0,0 +1,7 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: manifests/site/test-site/phases

View File

@ -0,0 +1,5 @@
phase:
path: manifests/site/test-site/phases
docEntryPointPrefix: manifests/site/test-site
inventory:
path: manifests/site/test-site/host-inventory

View File

@ -0,0 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: manifests/site/test-site/phases
docEntryPointPrefix: manifests/site/test-site
inventory:
path: manifests/site/test-site/host-inventory

View File

@ -19,6 +19,7 @@ import (
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/pkg/document/metadata"
"opendev.org/airship/airshipctl/pkg/inventory/baremetal"
"opendev.org/airship/airshipctl/pkg/inventory/ifc"
)
@ -59,13 +60,19 @@ func (i Inventory) BaremetalInventory() (ifc.BaremetalInventory, error) {
return nil, err
}
metadata, err := cfg.CurrentContextManifestMetadata()
metadataPath, err := cfg.CurrentContextMetadataPath()
if err != nil {
return nil, err
}
inventoryBundle := filepath.Join(targetPath, phaseDir, metadata.Inventory.Path)
metadataBundle := filepath.Join(targetPath, phaseDir, metadataPath)
meta, err := metadata.Config(metadataBundle)
if err != nil {
return nil, err
}
inventoryBundle := filepath.Join(targetPath, phaseDir, meta.InventoryPath)
bundle, err := document.NewBundleByPath(inventoryBundle)
if err != nil {
return nil, err

View File

@ -1,2 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: ""
docEntryPointPrefix: ""
inventory:
path: "."

View File

@ -807,7 +807,6 @@ func TestPlanValidateCommand(t *testing.T) {
err := cmd.RunE()
if tt.expectedErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.expectedErr)
} else {
assert.NoError(t, err)
}

View File

@ -24,6 +24,7 @@ import (
"opendev.org/airship/airshipctl/pkg/cluster/clustermap"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/pkg/document/metadata"
"opendev.org/airship/airshipctl/pkg/inventory"
inventoryifc "opendev.org/airship/airshipctl/pkg/inventory/ifc"
"opendev.org/airship/airshipctl/pkg/log"
@ -36,12 +37,13 @@ type Helper struct {
phaseBundleRoot string
inventoryRoot string
targetPath string
metadataPath string
phaseRepoDir string
phaseEntryPointBasePath string
workDir string
docEntryPointPrefix string
inventory inventoryifc.Inventory
metadata *config.Metadata
phaseConfigBundle document.Bundle
}
@ -58,18 +60,28 @@ func NewHelper(cfg *config.Config) (ifc.Helper, error) {
if err != nil {
return nil, err
}
helper.metadata, err = cfg.CurrentContextManifestMetadata()
helper.metadataPath, err = cfg.CurrentContextMetadataPath()
if err != nil {
return nil, err
}
metadataFilePath := filepath.Join(helper.targetPath, helper.phaseRepoDir, helper.metadataPath)
meta, err := metadata.Config(metadataFilePath)
if err != nil {
return nil, err
}
helper.workDir, err = cfg.WorkDir()
if err != nil {
return nil, err
}
helper.phaseBundleRoot = filepath.Join(helper.targetPath, helper.phaseRepoDir, helper.metadata.PhaseMeta.Path)
helper.inventoryRoot = filepath.Join(helper.targetPath, helper.phaseRepoDir, helper.metadata.Inventory.Path)
helper.docEntryPointPrefix = meta.DocEntryPointPrefix
helper.phaseBundleRoot = filepath.Join(helper.targetPath, helper.phaseRepoDir, meta.MetadataPhasePath)
helper.inventoryRoot = filepath.Join(helper.targetPath, helper.phaseRepoDir, meta.InventoryPath)
helper.phaseEntryPointBasePath = filepath.Join(helper.targetPath, helper.phaseRepoDir,
helper.metadata.PhaseMeta.DocEntryPointPrefix)
helper.docEntryPointPrefix)
helper.inventory = inventory.NewInventory(func() (*config.Config, error) { return cfg, nil })
if helper.phaseConfigBundle, err = document.NewBundleByPath(helper.phaseBundleRoot); err != nil {
return nil, err
@ -289,7 +301,7 @@ func (helper *Helper) PhaseRepoDir() string {
// DocumentEntryPoint field in the phase struct
// so the full entry point is DocEntryPointPrefix + DocumentEntryPoint
func (helper *Helper) DocEntryPointPrefix() string {
return helper.metadata.PhaseMeta.DocEntryPointPrefix
return helper.docEntryPointPrefix
}
// PhaseBundleRoot returns path to document root with phase documents

View File

@ -1,2 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: "doesnot-exist"
docEntryPointPrefix: ""
inventory:
path: ""

View File

@ -1,2 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: phases
docEntryPointPrefix: ""
inventory:
path: ""

View File

@ -1,4 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
inventory:
path: "manifests/site/inventory"
phase:
path: "no_plan_site/phases"
docEntryPointPrefix: "no_plan_site"

View File

@ -1,4 +1,11 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
inventory:
path: "manifests/site/inventory"
phase:
path: "valid_site/phases"
docEntryPointPrefix: "valid_site"

View File

@ -1,3 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
phase:
path: "valid_site_with_doc_prefix/phases"
docEntryPointPrefix: "valid_site_with_doc_prefix/phases"
docEntryPointPrefix: "valid_site_with_doc_prefix"
inventory:
path: ""

View File

@ -1,4 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
inventory:
path: "manifests/site/inventory"
phase:
path: "valid_site/phases"
docEntryPointPrefix: ""

View File

@ -1,3 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: ManifestMetadata
metadata:
name: manifest-metadata
spec:
inventory:
path: "manifests/site/inventory"
phase:
path: "valid_site_with_doc_prefix/phases"
docEntryPointPrefix: "valid_site_with_doc_prefix/phases"