Merge "Introduce phases and phase plan"

This commit is contained in:
Zuul 2020-06-25 21:20:58 +00:00 committed by Gerrit Code Review
commit 671881e1d5
18 changed files with 585 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(NewRenderCommand(rootSettings))
phaseRootCmd.AddCommand(NewPlanCommand(rootSettings))
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:
apply Apply phase to a cluster
help Help about any command
plan List phases
render Render phase documents from model
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

@ -26,5 +26,6 @@ such as getting list and applying specific one.
* [airshipctl](airshipctl.md) - A unified entrypoint to various airship components
* [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

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() {
Scheme = runtime.NewScheme()
// NOTE add all api objects to scheme here
SchemeBuilder.Register(&Clusterctl{})
SchemeBuilder.Register(
&Clusterctl{},
&Phase{},
&PhasePlan{},
)
_ = 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
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.
@ -99,6 +100,119 @@ func (in *MoveOptions) DeepCopy() *MoveOptions {
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.
func (in *Provider) DeepCopyInto(out *Provider) {
*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