Add phase run method
Change introduces phase executor interface as well as method to call it. Each registered executor must implement this interface. Relates-To: #259 Change-Id: I44665e5318ae59b4549cc77d10526a71bd40b40a
This commit is contained in:
parent
d63cdc6c24
commit
66b6acf565
31
pkg/phase/errors.go
Normal file
31
pkg/phase/errors.go
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// ErrExecutorNotFound is returned if phase executor was not found in executor
|
||||
// registry map
|
||||
type ErrExecutorNotFound struct {
|
||||
GVK schema.GroupVersionKind
|
||||
}
|
||||
|
||||
func (e ErrExecutorNotFound) Error() string {
|
||||
return fmt.Sprintf("executor identified by '%s' is not found", e.GVK)
|
||||
}
|
42
pkg/phase/ifc/executor.go
Normal file
42
pkg/phase/ifc/executor.go
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 ifc
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
// Executor interface should be implemented by each runner
|
||||
type Executor interface {
|
||||
Run(dryrun, debug bool) error
|
||||
Render(io.Writer) error
|
||||
Validate() error
|
||||
Wait() error
|
||||
}
|
||||
|
||||
// ExecutorFactory for executor instantiation
|
||||
// First argument is document object which represents executor
|
||||
// configuration.
|
||||
// Second argument is document bundle used by executor.
|
||||
// Third argument airship configuration settings since each phase
|
||||
// has to be aware of execution context and global settings
|
||||
type ExecutorFactory func(
|
||||
document.Document,
|
||||
document.Bundle,
|
||||
*environment.AirshipCTLSettings,
|
||||
) (Executor, error)
|
@ -17,9 +17,13 @@ package phase
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -28,6 +32,11 @@ const (
|
||||
PhaseDirName = "phases"
|
||||
)
|
||||
|
||||
var (
|
||||
// ExecutorRegistry contins registered runner factories
|
||||
ExecutorRegistry = make(map[schema.GroupVersionKind]ifc.ExecutorFactory)
|
||||
)
|
||||
|
||||
// Cmd object to work with phase api
|
||||
type Cmd struct {
|
||||
*environment.AirshipCTLSettings
|
||||
@ -42,6 +51,84 @@ func (p *Cmd) getBundle() (document.Bundle, error) {
|
||||
return document.NewBundleByPath(filepath.Join(ccm.TargetPath, ccm.SubPath, PhaseDirName))
|
||||
}
|
||||
|
||||
// GetPhase returns particular phase object identified by name
|
||||
func (p *Cmd) GetPhase(name string) (*airshipv1.Phase, error) {
|
||||
bundle, err := p.getBundle()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phaseConfig := &airshipv1.Phase{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
selector, err := document.NewSelector().ByObject(phaseConfig, airshipv1.Scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
doc, err := bundle.SelectOne(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = doc.ToAPIObject(phaseConfig, airshipv1.Scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return phaseConfig, nil
|
||||
}
|
||||
|
||||
// GetExecutor referenced in a phase configuration
|
||||
func (p *Cmd) GetExecutor(phase *airshipv1.Phase) (ifc.Executor, error) {
|
||||
bundle, err := p.getBundle()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phaseConfig := phase.Config
|
||||
// Searching executor configuration document referenced in
|
||||
// phase configuration
|
||||
refGVK := phaseConfig.ExecutorRef.GroupVersionKind()
|
||||
selector := document.NewSelector().
|
||||
ByGvk(refGVK.Group, refGVK.Version, refGVK.Kind).
|
||||
ByName(phaseConfig.ExecutorRef.Name).
|
||||
ByNamespace(phaseConfig.ExecutorRef.Namespace)
|
||||
doc, err := bundle.SelectOne(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Define executor configuration options
|
||||
targetPath, err := p.Config.CurrentContextTargetPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
executorDocBundle, err := document.NewBundleByPath(filepath.Join(targetPath, phaseConfig.DocumentEntryPoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Look for executor factory defined in registry
|
||||
executorFactory, found := ExecutorRegistry[refGVK]
|
||||
if !found {
|
||||
return nil, ErrExecutorNotFound{GVK: refGVK}
|
||||
}
|
||||
return executorFactory(doc, executorDocBundle, p.AirshipCTLSettings)
|
||||
}
|
||||
|
||||
// Exec particular phase
|
||||
func (p *Cmd) Exec(name string) error {
|
||||
phaseConfig, err := p.GetPhase(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
executor, err := p.GetExecutor(phaseConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return executor.Run(p.DryRun, p.Debug)
|
||||
}
|
||||
|
||||
// Plan shows available phase names
|
||||
func (p *Cmd) Plan() (map[string][]string, error) {
|
||||
bundle, err := p.getBundle()
|
||||
|
@ -19,13 +19,20 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/document"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
"opendev.org/airship/airshipctl/pkg/phase"
|
||||
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
||||
)
|
||||
|
||||
func TestPhasePlan(t *testing.T) {
|
||||
@ -52,6 +59,8 @@ func TestPhasePlan(t *testing.T) {
|
||||
"isogen",
|
||||
"remotedirect",
|
||||
"initinfra",
|
||||
"some_phase",
|
||||
"capi_init",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -89,6 +98,149 @@ func TestPhasePlan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPhase(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
settings func() *environment.AirshipCTLSettings
|
||||
phaseName string
|
||||
expectedPhase *airshipv1.Phase
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "No context",
|
||||
settings: func() *environment.AirshipCTLSettings {
|
||||
s := makeDefaultSettings()
|
||||
s.Config.CurrentContext = "badCtx"
|
||||
return s
|
||||
},
|
||||
expectedErr: config.ErrMissingConfig{What: "Context with name 'badCtx'"},
|
||||
},
|
||||
{
|
||||
name: "Get existing phase",
|
||||
settings: makeDefaultSettings,
|
||||
phaseName: "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: "manifests/site/test-site/auth",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Get non-existing phase",
|
||||
settings: makeDefaultSettings,
|
||||
phaseName: "some_name",
|
||||
expectedErr: document.ErrDocNotFound{
|
||||
Selector: document.Selector{
|
||||
Selector: types.Selector{
|
||||
Gvk: resid.Gvk{
|
||||
Group: "airshipit.org",
|
||||
Version: "v1alpha1",
|
||||
Kind: "Phase",
|
||||
},
|
||||
Name: "some_name",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := phase.Cmd{AirshipCTLSettings: tt.settings()}
|
||||
actualPhase, actualErr := cmd.GetPhase(tt.phaseName)
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
assert.Equal(t, tt.expectedPhase, actualPhase)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExecutor(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
settings func() *environment.AirshipCTLSettings
|
||||
phase *airshipv1.Phase
|
||||
expectedExc ifc.Executor
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "No context",
|
||||
settings: func() *environment.AirshipCTLSettings {
|
||||
s := makeDefaultSettings()
|
||||
s.Config.CurrentContext = "badCtx"
|
||||
return s
|
||||
},
|
||||
expectedErr: config.ErrMissingConfig{What: "Context with name 'badCtx'"},
|
||||
},
|
||||
{
|
||||
name: "Get non-existing executor",
|
||||
settings: makeDefaultSettings,
|
||||
phase: &airshipv1.Phase{
|
||||
Config: airshipv1.PhaseConfig{
|
||||
ExecutorRef: &corev1.ObjectReference{
|
||||
APIVersion: "example.com/v1",
|
||||
Kind: "SomeKind",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: document.ErrDocNotFound{
|
||||
Selector: document.Selector{
|
||||
Selector: types.Selector{
|
||||
Gvk: resid.Gvk{
|
||||
Group: "example.com",
|
||||
Version: "v1",
|
||||
Kind: "SomeKind",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Get unregistered executor",
|
||||
settings: makeDefaultSettings,
|
||||
phase: &airshipv1.Phase{
|
||||
Config: airshipv1.PhaseConfig{
|
||||
ExecutorRef: &corev1.ObjectReference{
|
||||
APIVersion: "airshipit.org/v1alpha1",
|
||||
Kind: "SomeExecutor",
|
||||
Name: "executor-name",
|
||||
},
|
||||
DocumentEntryPoint: "valid_site/phases",
|
||||
},
|
||||
},
|
||||
expectedErr: phase.ErrExecutorNotFound{
|
||||
GVK: schema.GroupVersionKind{
|
||||
Group: "airshipit.org",
|
||||
Version: "v1alpha1",
|
||||
Kind: "SomeExecutor",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
tt := test
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := phase.Cmd{AirshipCTLSettings: tt.settings()}
|
||||
actualExc, actualErr := cmd.GetExecutor(tt.phase)
|
||||
assert.Equal(t, tt.expectedErr, actualErr)
|
||||
assert.Equal(t, tt.expectedExc, actualExc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeDefaultSettings() *environment.AirshipCTLSettings {
|
||||
testSettings := &environment.AirshipCTLSettings{
|
||||
AirshipConfigPath: "testdata/airshipconfig.yaml",
|
||||
|
10
pkg/phase/testdata/valid_site/phases/capi_init.yaml
vendored
Normal file
10
pkg/phase/testdata/valid_site/phases/capi_init.yaml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: Phase
|
||||
metadata:
|
||||
name: capi_init
|
||||
config:
|
||||
executorRef:
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: Clusterctl
|
||||
name: clusterctl-v1
|
||||
documentEntryPoint: manifests/site/test-site/auth
|
12
pkg/phase/testdata/valid_site/phases/clusterctl.yaml
vendored
Normal file
12
pkg/phase/testdata/valid_site/phases/clusterctl.yaml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: Clusterctl
|
||||
metadata:
|
||||
name: clusterctl-v1
|
||||
action: init
|
||||
init-options:
|
||||
core-provider: "cluster-api:v0.3.3"
|
||||
providers:
|
||||
- name: "cluster-api"
|
||||
type: "CoreProvider"
|
||||
versions:
|
||||
v0.3.3: manifests/function/capi/v0.3.3
|
@ -1,2 +1,6 @@
|
||||
resources:
|
||||
- phaseplan.yaml
|
||||
- some_phase.yaml
|
||||
- some_exc.yaml
|
||||
- capi_init.yaml
|
||||
- clusterctl.yaml
|
||||
|
@ -8,3 +8,5 @@ phaseGroups:
|
||||
- name: isogen
|
||||
- name: remotedirect
|
||||
- name: initinfra
|
||||
- name: some_phase
|
||||
- name: capi_init
|
||||
|
13
pkg/phase/testdata/valid_site/phases/some_exc.yaml
vendored
Normal file
13
pkg/phase/testdata/valid_site/phases/some_exc.yaml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: SomeExecutor
|
||||
metadata:
|
||||
labels:
|
||||
airshipit.org/deploy-k8s: "false"
|
||||
name: executor-name
|
||||
init-options:
|
||||
core-provider: "cluster-api:v0.3.3"
|
||||
providers:
|
||||
- name: "cluster-api"
|
||||
type: "CoreProvider"
|
||||
versions:
|
||||
v0.3.3: manifests/function/capi/v0.3.3
|
10
pkg/phase/testdata/valid_site/phases/some_phase.yaml
vendored
Normal file
10
pkg/phase/testdata/valid_site/phases/some_phase.yaml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: Phase
|
||||
metadata:
|
||||
name: some_phase
|
||||
config:
|
||||
executorRef:
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
kind: SomeExecutor
|
||||
name: executor-name
|
||||
documentEntryPoint: manifests/site/test-site/auth
|
Loading…
Reference in New Issue
Block a user