Introduce phases and phase plan

Airship API is extended with Phase and PhasePlan objects which
implemet k8s runtime.Object interface. Phase plan command is
added to airshipctl.

Closes: #262
Closes: #261
Closes: #260
Change-Id: I4c299ec9078af4ace29ab46bd6440f47ae18a094
This commit is contained in:
Dmitry Ukov 2020-06-03 19:35:17 +04:00
parent c05286d3f6
commit 325d824503
20 changed files with 619 additions and 2 deletions

View File

@ -45,6 +45,7 @@ func NewPhaseCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Comman
phaseRootCmd.AddCommand(NewApplyCommand(rootSettings, client.DefaultClient)) phaseRootCmd.AddCommand(NewApplyCommand(rootSettings, client.DefaultClient))
phaseRootCmd.AddCommand(NewRenderCommand(rootSettings)) phaseRootCmd.AddCommand(NewRenderCommand(rootSettings))
phaseRootCmd.AddCommand(NewPlanCommand(rootSettings))
return phaseRootCmd return phaseRootCmd
} }

61
cmd/phase/plan.go Normal file
View File

@ -0,0 +1,61 @@
/*
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"
"github.com/spf13/cobra"
"opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/pkg/phase"
"opendev.org/airship/airshipctl/pkg/util"
)
const (
cmdLong = `
List life-cycle phases which were defined in document model by group.
Phases within a group are executed sequentially. Multiple phase groups
are executed in parallel.
`
)
// NewPlanCommand creates a command which prints available phases
func NewPlanCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
p := &phase.Cmd{AirshipCTLSettings: rootSettings}
planCmd := &cobra.Command{
Use: "plan",
Short: "List phases",
Long: cmdLong[1:],
RunE: func(cmd *cobra.Command, args []string) error {
phases, err := p.Plan()
if err != nil {
return err
}
tw := util.NewTabWriter(cmd.OutOrStdout())
defer tw.Flush()
fmt.Fprintf(tw, "GROUP\tPHASE\n")
for group, phaseList := range phases {
fmt.Fprintf(tw, "%s\t\n", group)
for _, phase := range phaseList {
fmt.Fprintf(tw, "\t%s\n", phase)
}
}
return nil
},
}
return planCmd
}

35
cmd/phase/plan_test.go Normal file
View File

@ -0,0 +1,35 @@
/*
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 (
"testing"
"opendev.org/airship/airshipctl/cmd/phase"
"opendev.org/airship/airshipctl/testutil"
)
func TestNewPlanCommand(t *testing.T) {
tests := []*testutil.CmdTest{
{
Name: "phase-plan-cmd-with-help",
CmdLine: "--help",
Cmd: phase.NewPlanCommand(nil),
},
}
for _, testcase := range tests {
testutil.RunTest(t, testcase)
}
}

View File

@ -7,6 +7,7 @@ Usage:
Available Commands: Available Commands:
apply Apply phase to a cluster apply Apply phase to a cluster
help Help about any command help Help about any command
plan List phases
render Render phase documents from model render Render phase documents from model
Flags: Flags:

View File

@ -0,0 +1,9 @@
List life-cycle phases which were defined in document model by group.
Phases within a group are executed sequentially. Multiple phase groups
are executed in parallel.
Usage:
plan [flags]
Flags:
-h, --help help for plan

View File

@ -31,5 +31,6 @@ Manage the airshipctl config file
* [airshipctl config set-cluster](airshipctl_config_set-cluster.md) - Manage clusters * [airshipctl config set-cluster](airshipctl_config_set-cluster.md) - Manage clusters
* [airshipctl config set-context](airshipctl_config_set-context.md) - Manage contexts * [airshipctl config set-context](airshipctl_config_set-context.md) - Manage contexts
* [airshipctl config set-credentials](airshipctl_config_set-credentials.md) - Manage user credentials * [airshipctl config set-credentials](airshipctl_config_set-credentials.md) - Manage user credentials
* [airshipctl config set-management-config](airshipctl_config_set-management-config.md) - Modify an out-of-band management configuration
* [airshipctl config use-context](airshipctl_config_use-context.md) - Switch to a different context * [airshipctl config use-context](airshipctl_config_use-context.md) - Switch to a different context

View File

@ -0,0 +1,33 @@
## airshipctl config set-management-config
Modify an out-of-band management configuration
### Synopsis
Modify an out-of-band management configuration
```
airshipctl config set-management-config NAME [flags]
```
### Options
```
-h, --help help for set-management-config
--insecure Ignore SSL certificate verification on out-of-band management requests
--management-type string Set the out-of-band management type (default "redfish")
--use-proxy Use the proxy configuration specified in the local environment (default true)
```
### Options inherited from parent commands
```
--airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config")
--debug enable verbose output
--kubeconfig string Path to kubeconfig associated with airshipctl configuration. (default "$HOME/.airship/kubeconfig")
```
### SEE ALSO
* [airshipctl config](airshipctl_config.md) - Manage the airshipctl config file

View File

@ -26,5 +26,6 @@ such as getting list and applying specific one.
* [airshipctl](airshipctl.md) - A unified entrypoint to various airship components * [airshipctl](airshipctl.md) - A unified entrypoint to various airship components
* [airshipctl phase apply](airshipctl_phase_apply.md) - Apply phase to a cluster * [airshipctl phase apply](airshipctl_phase_apply.md) - Apply phase to a cluster
* [airshipctl phase plan](airshipctl_phase_plan.md) - List phases
* [airshipctl phase render](airshipctl_phase_render.md) - Render phase documents from model * [airshipctl phase render](airshipctl_phase_render.md) - Render phase documents from model

View File

@ -0,0 +1,33 @@
## airshipctl phase plan
List phases
### Synopsis
List life-cycle phases which were defined in document model by group.
Phases within a group are executed sequentially. Multiple phase groups
are executed in parallel.
```
airshipctl phase plan [flags]
```
### Options
```
-h, --help help for plan
```
### Options inherited from parent commands
```
--airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config")
--debug enable verbose output
--kubeconfig string Path to kubeconfig associated with airshipctl configuration. (default "$HOME/.airship/kubeconfig")
```
### SEE ALSO
* [airshipctl phase](airshipctl_phase.md) - Manage phases

View File

@ -40,6 +40,10 @@ var (
func init() { func init() {
Scheme = runtime.NewScheme() Scheme = runtime.NewScheme()
// NOTE add all api objects to scheme here // NOTE add all api objects to scheme here
SchemeBuilder.Register(&Clusterctl{}) SchemeBuilder.Register(
&Clusterctl{},
&Phase{},
&PhasePlan{},
)
_ = AddToScheme(Scheme) //nolint:errcheck _ = AddToScheme(Scheme) //nolint:errcheck
} }

View File

@ -0,0 +1,36 @@
/*
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 (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
// Phase object to control deployment steps
type Phase struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Config PhaseConfig `json:"config,omitempty"`
}
// PhaseConfig represents configuration for a particular phase. It contins a reference to
// phase runner object which should contain runner configuration
type PhaseConfig struct {
ExecutorRef *corev1.ObjectReference `json:"executorRef"`
DocumentEntryPoint string `json:"documentEntryPoint"`
}

View File

@ -0,0 +1,40 @@
/*
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
// PhasePlan object represents phase execution sequence
type PhasePlan struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
PhaseGroups []PhaseGroup `json:"phaseGroups,omitempty"`
}
// PhaseGroup represents set of phases (i.e. steps) executed sequentially.
// Phase groups are executed simultaneously
type PhaseGroup struct {
Name string `json:"name,omitempty"`
Phases []PhaseGroupStep `json:"phases,omitempty"`
}
// PhaseGroupStep represents phase (or step) within phase group
type PhaseGroupStep struct {
Name string `json:"name,omitempty"`
}

View File

@ -5,7 +5,8 @@
package v1alpha1 package v1alpha1
import ( import (
runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
) )
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -99,6 +100,119 @@ func (in *MoveOptions) DeepCopy() *MoveOptions {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Phase) DeepCopyInto(out *Phase) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Config.DeepCopyInto(&out.Config)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Phase.
func (in *Phase) DeepCopy() *Phase {
if in == nil {
return nil
}
out := new(Phase)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Phase) 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 *PhaseConfig) DeepCopyInto(out *PhaseConfig) {
*out = *in
if in.ExecutorRef != nil {
in, out := &in.ExecutorRef, &out.ExecutorRef
*out = new(v1.ObjectReference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhaseConfig.
func (in *PhaseConfig) DeepCopy() *PhaseConfig {
if in == nil {
return nil
}
out := new(PhaseConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PhaseGroup) DeepCopyInto(out *PhaseGroup) {
*out = *in
if in.Phases != nil {
in, out := &in.Phases, &out.Phases
*out = make([]PhaseGroupStep, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhaseGroup.
func (in *PhaseGroup) DeepCopy() *PhaseGroup {
if in == nil {
return nil
}
out := new(PhaseGroup)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PhaseGroupStep) DeepCopyInto(out *PhaseGroupStep) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhaseGroupStep.
func (in *PhaseGroupStep) DeepCopy() *PhaseGroupStep {
if in == nil {
return nil
}
out := new(PhaseGroupStep)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PhasePlan) DeepCopyInto(out *PhasePlan) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
if in.PhaseGroups != nil {
in, out := &in.PhaseGroups, &out.PhaseGroups
*out = make([]PhaseGroup, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PhasePlan.
func (in *PhasePlan) DeepCopy() *PhasePlan {
if in == nil {
return nil
}
out := new(PhasePlan)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PhasePlan) 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Provider) DeepCopyInto(out *Provider) { func (in *Provider) DeepCopyInto(out *Provider) {
*out = *in *out = *in

74
pkg/phase/phase.go Normal file
View File

@ -0,0 +1,74 @@
/*
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 (
"path/filepath"
airshipv1 "opendev.org/airship/airshipctl/pkg/api/v1alpha1"
"opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/pkg/environment"
)
const (
// PhaseDirName directory for bundle with phases
// TODO (dukov) Remove this once repository metadata is ready
PhaseDirName = "phases"
)
// Cmd object to work with phase api
type Cmd struct {
*environment.AirshipCTLSettings
DryRun bool
}
func (p *Cmd) getBundle() (document.Bundle, error) {
ccm, err := p.Config.CurrentContextManifest()
if err != nil {
return nil, err
}
return document.NewBundleByPath(filepath.Join(ccm.TargetPath, ccm.SubPath, PhaseDirName))
}
// Plan shows available phase names
func (p *Cmd) Plan() (map[string][]string, error) {
bundle, err := p.getBundle()
if err != nil {
return nil, err
}
plan := &airshipv1.PhasePlan{}
selector, err := document.NewSelector().ByObject(plan, airshipv1.Scheme)
if err != nil {
return nil, err
}
doc, err := bundle.SelectOne(selector)
if err != nil {
return nil, err
}
if err := doc.ToAPIObject(plan, airshipv1.Scheme); err != nil {
return nil, err
}
result := make(map[string][]string)
for _, phaseGroup := range plan.PhaseGroups {
phases := make([]string, len(phaseGroup.Phases))
for i, phase := range phaseGroup.Phases {
phases[i] = phase.Name
}
result[phaseGroup.Name] = phases
}
return result, nil
}

99
pkg/phase/phase_test.go Normal file
View File

@ -0,0 +1,99 @@
/*
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 (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/resid"
"sigs.k8s.io/kustomize/api/types"
"opendev.org/airship/airshipctl/pkg/config"
"opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/pkg/phase"
)
func TestPhasePlan(t *testing.T) {
testCases := []struct {
name string
settings func() *environment.AirshipCTLSettings
expectedPlan map[string][]string
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: "Valid Phase Plan",
settings: makeDefaultSettings,
expectedPlan: map[string][]string{
"group1": {
"isogen",
"remotedirect",
"initinfra",
},
},
},
{
name: "No Phase Plan",
settings: func() *environment.AirshipCTLSettings {
s := makeDefaultSettings()
m, err := s.Config.CurrentContextManifest()
require.NoError(t, err)
m.SubPath = "no_plan_site"
return s
},
expectedErr: document.ErrDocNotFound{
Selector: document.Selector{
Selector: types.Selector{
Gvk: resid.Gvk{
Group: "airshipit.org",
Version: "v1alpha1",
Kind: "PhasePlan",
},
},
},
},
},
}
for _, test := range testCases {
tt := test
t.Run(tt.name, func(t *testing.T) {
cmd := phase.Cmd{AirshipCTLSettings: tt.settings()}
actualPlan, actualErr := cmd.Plan()
assert.Equal(t, tt.expectedErr, actualErr)
assert.Equal(t, tt.expectedPlan, actualPlan)
})
}
}
func makeDefaultSettings() *environment.AirshipCTLSettings {
testSettings := &environment.AirshipCTLSettings{
AirshipConfigPath: "testdata/airshipconfig.yaml",
KubeConfigPath: "testdata/kubeconfig.yaml",
}
testSettings.InitConfig()
return testSettings
}

44
pkg/phase/testdata/airshipconfig.yaml vendored Normal file
View File

@ -0,0 +1,44 @@
apiVersion: airshipit.org/v1alpha1
bootstrapInfo:
dummy_bootstrap_config:
container:
volume: /tmp/airship:/config
image: quay.io/airshipit/isogen:latest-debian_stable
containerRuntime: docker
builder:
userDataFileName: user-data
networkConfigFileName: network-config
outputMetadataFileName: output-metadata.yaml
remoteDirect:
isoUrl: http://localhost:8099/debian-custom.iso
remoteType: redfish
clusters:
dummycluster:
clusterType:
ephemeral:
bootstrapInfo: dummy_bootstrap_config
clusterKubeconf: dummycluster_ephemeral
contexts:
dummy_cluster:
contextKubeconf: dummy_cluster
manifest: dummy_manifest
currentContext: dummy_cluster
kind: Config
manifests:
dummy_manifest:
primaryRepositoryName: primary
repositories:
primary:
auth:
sshKey: testdata/test-key.pem
type: ssh-key
checkout:
branch: ""
force: false
remoteRef: ""
tag: v1.0.1
url: http://dummy.url.com/primary.git
subPath: valid_site
targetPath: testdata
users:
dummy_user: {}

19
pkg/phase/testdata/kubeconfig.yaml vendored Normal file
View File

@ -0,0 +1,19 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1Ea3lPVEUzTURNd09Wb1hEVEk1TURreU5qRTNNRE13T1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUZyCkdxM0kyb2dZci81Y01Udy9Na1pORTNWQURzdEdyU240WjU2TDhPUGhMcUhDN2t1dno2dVpES3dCSGtGeTBNK2MKRXIzd2piUGE1aTV5NmkyMGtxSHBVMjdPZTA0dzBXV2s4N0RSZVlWaGNoZVJHRXoraWt3SndIcGRmMjJVemZNKwpkSDBzaUhuMVd6UnovYk4za3hMUzJlMnZ2U1Y3bmNubk1YRUd4OXV0MUY0NThHeWxxdmxXTUlWMzg5Q2didXFDCkcwcFdiMTBLM0RVZWdiT25Xa1FmSm5sTWRRVVZDUVdZZEZaaklrcWtkWi9hVTRobkNEV01oZXNWRnFNaDN3VVAKczhQay9BNWh1ZFFPbnFRNDVIWXZLdjZ5RjJWcDUyWExBRUx3NDJ4aVRKZlh0V1h4eHR6cU4wY1lyL2VxeS9XMQp1YVVGSW5xQjFVM0JFL1oxbmFrQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFKUUVKQVBLSkFjVDVuK3dsWGJsdU9mS0J3c2gKZTI4R1c5R2QwM0N0NGF3RzhzMXE1ZHNua2tpZmVTUENHVFZ1SXF6UTZDNmJaSk9SMDMvVEl5ejh6NDJnaitDVApjWUZXZkltM2RKTnpRL08xWkdySXZZNWdtcWJtWDlpV0JaU24rRytEOGxubzd2aGMvY0tBRFR5OTMvVU92MThuCkdhMnIrRGJJcHcyTWVBVEl2elpxRS9RWlVSQ25DMmdjUFhTVzFqN2h4R3o1a3ZNcGVDZTdQYVUvdVFvblVHSWsKZ2t6ZzI4NHQvREhUUzc4N1V1SUg5cXBaV09yTFNMOGFBeUxQUHhWSXBteGZmbWRETE9TS2VUemRlTmxoSitUMwowQlBVaHBQTlJBNTNJN0hRQjhVUDR2elNONTkzZ1VFbVlFQ2Jic2RYSzB6ZVR6SDdWWHR2Zmd5WTVWWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
server: https://127.0.0.1:6443
name: dummycluster_ephemeral
contexts:
- context:
cluster: dummycluster_ephemeral
user: kubernetes-admin
name: dummy_cluster
current-context: dummy_cluster
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQXhEdzk2RUY4SXN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RBNU1qa3hOekF6TURsYUZ3MHlNREE1TWpneE56QXpNVEphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXV6R0pZdlBaNkRvaTQyMUQKSzhXSmFaQ25OQWQycXo1cC8wNDJvRnpRUGJyQWd6RTJxWVZrek9MOHhBVmVSN1NONXdXb1RXRXlGOEVWN3JyLwo0K0hoSEdpcTVQbXF1SUZ5enpuNi9JWmM4alU5eEVmenZpa2NpckxmVTR2UlhKUXdWd2dBU05sMkFXQUloMmRECmRUcmpCQ2ZpS1dNSHlqMFJiSGFsc0J6T3BnVC9IVHYzR1F6blVRekZLdjJkajVWMU5rUy9ESGp5UlJKK0VMNlEKQlltR3NlZzVQNE5iQzllYnVpcG1NVEFxL0p1bU9vb2QrRmpMMm5acUw2Zkk2ZkJ0RjVPR2xwQ0IxWUo4ZnpDdApHUVFaN0hUSWJkYjJ0cDQzRlZPaHlRYlZjSHFUQTA0UEoxNSswV0F5bVVKVXo4WEE1NDRyL2J2NzRKY0pVUkZoCmFyWmlRd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMMmhIUmVibEl2VHJTMFNmUVg1RG9ueVVhNy84aTg1endVWApSd3dqdzFuS0U0NDJKbWZWRGZ5b0hRYUM4Ti9MQkxyUXM0U0lqU1JYdmFHU1dSQnRnT1RRV21Db1laMXdSbjdwCndDTXZQTERJdHNWWm90SEZpUFl2b1lHWFFUSXA3YlROMmg1OEJaaEZ3d25nWUovT04zeG1rd29IN1IxYmVxWEYKWHF1TTluekhESk41VlZub1lQR09yRHMwWlg1RnNxNGtWVU0wVExNQm9qN1ZIRDhmU0E5RjRYNU4yMldsZnNPMAo4aksrRFJDWTAyaHBrYTZQQ0pQS0lNOEJaMUFSMG9ZakZxT0plcXpPTjBqcnpYWHh4S2pHVFVUb1BldVA5dCtCCjJOMVA1TnI4a2oxM0lrend5Q1NZclFVN09ZM3ltZmJobHkrcXZxaFVFa014MlQ1SkpmQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=

View File

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

View File

@ -0,0 +1,10 @@
apiVersion: airshipit.org/v1alpha1
kind: PhasePlan
metadata:
name: phasePlan
phaseGroups:
- name: group1
phases:
- name: isogen
- name: remotedirect
- name: initinfra