Integrate cli-utils applier with phases.
This will also enable us to wait for kubernetes resources to reach required state Relates-To: #238 Change-Id: Ia2c9cebd94ca2ef5bfd9ed5830ae469e6aa6b167
This commit is contained in:
parent
f5cf2d379a
commit
e4adce2bcf
@ -17,10 +17,11 @@ limitations under the License.
|
|||||||
package phase
|
package phase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/environment"
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
"opendev.org/airship/airshipctl/pkg/k8s/client"
|
|
||||||
"opendev.org/airship/airshipctl/pkg/phase/apply"
|
"opendev.org/airship/airshipctl/pkg/phase/apply"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,9 +36,10 @@ airshipctl phase apply initinfra
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewApplyCommand creates a command to apply phase to k8s cluster.
|
// NewApplyCommand creates a command to apply phase to k8s cluster.
|
||||||
func NewApplyCommand(rootSettings *environment.AirshipCTLSettings, factory client.Factory) *cobra.Command {
|
func NewApplyCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Command {
|
||||||
i := apply.NewOptions(rootSettings)
|
i := &apply.Options{
|
||||||
|
RootSettings: rootSettings,
|
||||||
|
}
|
||||||
applyCmd := &cobra.Command{
|
applyCmd := &cobra.Command{
|
||||||
Use: "apply PHASE_NAME",
|
Use: "apply PHASE_NAME",
|
||||||
Short: "Apply phase to a cluster",
|
Short: "Apply phase to a cluster",
|
||||||
@ -46,12 +48,7 @@ func NewApplyCommand(rootSettings *environment.AirshipCTLSettings, factory clien
|
|||||||
Example: applyExample,
|
Example: applyExample,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
i.PhaseName = args[0]
|
i.PhaseName = args[0]
|
||||||
client, err := factory(rootSettings)
|
i.Initialize()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.Client = client
|
|
||||||
|
|
||||||
return i.Run()
|
return i.Run()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -73,4 +70,10 @@ func addApplyFlags(i *apply.Options, cmd *cobra.Command) {
|
|||||||
false,
|
false,
|
||||||
`if set to true, command will delete all kubernetes resources that are not`+
|
`if set to true, command will delete all kubernetes resources that are not`+
|
||||||
` defined in airship documents and have airshipit.org/deployed=apply label`)
|
` defined in airship documents and have airshipit.org/deployed=apply label`)
|
||||||
|
|
||||||
|
flags.DurationVar(
|
||||||
|
&i.WaitTimeout,
|
||||||
|
"wait-timeout",
|
||||||
|
time.Second*120,
|
||||||
|
`number of seconds to wait for resources to become ready, if set to 0 will not wait`)
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,6 @@ import (
|
|||||||
|
|
||||||
"opendev.org/airship/airshipctl/cmd/phase"
|
"opendev.org/airship/airshipctl/cmd/phase"
|
||||||
"opendev.org/airship/airshipctl/pkg/environment"
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
"opendev.org/airship/airshipctl/pkg/k8s/client"
|
|
||||||
"opendev.org/airship/airshipctl/pkg/k8s/client/fake"
|
|
||||||
"opendev.org/airship/airshipctl/testutil"
|
"opendev.org/airship/airshipctl/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,15 +28,12 @@ func TestNewApplyCommand(t *testing.T) {
|
|||||||
KubeConfigPath: "../../testdata/k8s/kubeconfig.yaml",
|
KubeConfigPath: "../../testdata/k8s/kubeconfig.yaml",
|
||||||
}
|
}
|
||||||
fakeRootSettings.InitConfig()
|
fakeRootSettings.InitConfig()
|
||||||
testClientFactory := func(_ *environment.AirshipCTLSettings) (client.Interface, error) {
|
|
||||||
return fake.NewClient(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []*testutil.CmdTest{
|
tests := []*testutil.CmdTest{
|
||||||
{
|
{
|
||||||
Name: "phase-apply-cmd-with-help",
|
Name: "phase-apply-cmd-with-help",
|
||||||
CmdLine: "--help",
|
CmdLine: "--help",
|
||||||
Cmd: phase.NewApplyCommand(fakeRootSettings, testClientFactory),
|
Cmd: phase.NewApplyCommand(fakeRootSettings),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, testcase := range tests {
|
for _, testcase := range tests {
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/environment"
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
"opendev.org/airship/airshipctl/pkg/k8s/client"
|
|
||||||
"opendev.org/airship/airshipctl/pkg/log"
|
"opendev.org/airship/airshipctl/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,13 +36,12 @@ func NewPhaseCommand(rootSettings *environment.AirshipCTLSettings) *cobra.Comman
|
|||||||
Long: clusterLong[1:],
|
Long: clusterLong[1:],
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
log.Init(rootSettings.Debug, cmd.OutOrStderr())
|
log.Init(rootSettings.Debug, cmd.OutOrStderr())
|
||||||
|
|
||||||
// Load or Initialize airship Config
|
// Load or Initialize airship Config
|
||||||
rootSettings.InitConfig()
|
rootSettings.InitConfig()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
phaseRootCmd.AddCommand(NewApplyCommand(rootSettings, client.DefaultClient))
|
phaseRootCmd.AddCommand(NewApplyCommand(rootSettings))
|
||||||
phaseRootCmd.AddCommand(NewRenderCommand(rootSettings))
|
phaseRootCmd.AddCommand(NewRenderCommand(rootSettings))
|
||||||
phaseRootCmd.AddCommand(NewPlanCommand(rootSettings))
|
phaseRootCmd.AddCommand(NewPlanCommand(rootSettings))
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ airshipctl phase apply initinfra
|
|||||||
|
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
--dry-run don't deliver documents to the cluster, simulate the changes instead
|
--dry-run don't deliver documents to the cluster, simulate the changes instead
|
||||||
-h, --help help for apply
|
-h, --help help for apply
|
||||||
--prune if set to true, command will delete all kubernetes resources that are not defined in airship documents and have airshipit.org/deployed=apply label
|
--prune if set to true, command will delete all kubernetes resources that are not defined in airship documents and have airshipit.org/deployed=apply label
|
||||||
|
--wait-timeout duration number of seconds to wait for resources to become ready, if set to 0 will not wait (default 2m0s)
|
||||||
|
@ -23,9 +23,10 @@ airshipctl phase apply initinfra
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
```
|
```
|
||||||
--dry-run don't deliver documents to the cluster, simulate the changes instead
|
--dry-run don't deliver documents to the cluster, simulate the changes instead
|
||||||
-h, --help help for apply
|
-h, --help help for apply
|
||||||
--prune if set to true, command will delete all kubernetes resources that are not defined in airship documents and have airshipit.org/deployed=apply label
|
--prune if set to true, command will delete all kubernetes resources that are not defined in airship documents and have airshipit.org/deployed=apply label
|
||||||
|
--wait-timeout duration number of seconds to wait for resources to become ready, if set to 0 will not wait (default 2m0s)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options inherited from parent commands
|
### Options inherited from parent commands
|
||||||
|
@ -15,66 +15,72 @@
|
|||||||
package apply
|
package apply
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/document"
|
"opendev.org/airship/airshipctl/pkg/document"
|
||||||
"opendev.org/airship/airshipctl/pkg/environment"
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
"opendev.org/airship/airshipctl/pkg/k8s/client"
|
"opendev.org/airship/airshipctl/pkg/events"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/k8s/applier"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/k8s/utils"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options is an abstraction used to apply the phase
|
// Options is an abstraction used to apply the phase
|
||||||
type Options struct {
|
type Options struct {
|
||||||
RootSettings *environment.AirshipCTLSettings
|
RootSettings *environment.AirshipCTLSettings
|
||||||
Client client.Interface
|
Applier *applier.Applier
|
||||||
|
Processor events.EventProcessor
|
||||||
|
|
||||||
DryRun bool
|
WaitTimeout time.Duration
|
||||||
Prune bool
|
DryRun bool
|
||||||
PhaseName string
|
Prune bool
|
||||||
|
PhaseName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOptions return instance of Options
|
// Initialize Options with required field, such as Applier
|
||||||
func NewOptions(settings *environment.AirshipCTLSettings) *Options {
|
func (o *Options) Initialize() {
|
||||||
// At this point AirshipCTLSettings may not be fully initialized
|
f := utils.FactoryFromKubeConfigPath(o.RootSettings.KubeConfigPath)
|
||||||
applyOptions := &Options{RootSettings: settings}
|
streams := utils.Streams()
|
||||||
return applyOptions
|
o.Applier = applier.NewApplier(f, streams)
|
||||||
|
o.Processor = events.NewDefaultProcessor(streams)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run apply subcommand logic
|
// Run apply subcommand logic
|
||||||
func (applyOptions *Options) Run() error {
|
func (o *Options) Run() error {
|
||||||
kctl := applyOptions.Client.Kubectl()
|
ao := applier.ApplyOptions{
|
||||||
ao, err := kctl.ApplyOptions()
|
DryRun: o.DryRun,
|
||||||
|
Prune: o.Prune,
|
||||||
|
WaitTimeout: o.WaitTimeout,
|
||||||
|
}
|
||||||
|
globalConf := o.RootSettings.Config
|
||||||
|
|
||||||
|
if err := globalConf.EnsureComplete(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clusterName, err := globalConf.CurrentContextClusterName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
clusterType, err := globalConf.CurrentContextClusterType()
|
||||||
ao.SetDryRun(applyOptions.DryRun)
|
|
||||||
// If prune is true, set selector for pruning
|
|
||||||
if applyOptions.Prune {
|
|
||||||
ao.SetPrune(document.ApplyPhaseSelector + applyOptions.PhaseName)
|
|
||||||
}
|
|
||||||
|
|
||||||
globalConf := applyOptions.RootSettings.Config
|
|
||||||
|
|
||||||
if err = globalConf.EnsureComplete(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
kustomizePath, err := globalConf.CurrentContextEntryPoint(applyOptions.PhaseName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ao.BundleName = fmt.Sprintf("%s-%s-%s", clusterName, clusterType, o.PhaseName)
|
||||||
|
kustomizePath, err := globalConf.CurrentContextEntryPoint(o.PhaseName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("building bundle from kustomize path %s", kustomizePath)
|
||||||
b, err := document.NewBundleByPath(kustomizePath)
|
b, err := document.NewBundleByPath(kustomizePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns all documents for this phase
|
// Returns all documents for this phase
|
||||||
docs, err := b.Select(document.NewDeployToK8sSelector())
|
bundle, err := b.SelectBundle(document.NewDeployToK8sSelector())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(docs) == 0 {
|
ch := o.Applier.ApplyBundle(bundle, ao)
|
||||||
return document.ErrDocNotFound{}
|
return o.Processor.Process(ch)
|
||||||
}
|
|
||||||
|
|
||||||
return kctl.Apply(docs, ao)
|
|
||||||
}
|
}
|
||||||
|
@ -15,19 +15,19 @@
|
|||||||
package apply_test
|
package apply_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/config"
|
||||||
"opendev.org/airship/airshipctl/pkg/document"
|
"opendev.org/airship/airshipctl/pkg/document"
|
||||||
"opendev.org/airship/airshipctl/pkg/environment"
|
"opendev.org/airship/airshipctl/pkg/environment"
|
||||||
"opendev.org/airship/airshipctl/pkg/k8s/client"
|
"opendev.org/airship/airshipctl/pkg/k8s/applier"
|
||||||
"opendev.org/airship/airshipctl/pkg/k8s/client/fake"
|
|
||||||
"opendev.org/airship/airshipctl/pkg/k8s/kubectl"
|
|
||||||
"opendev.org/airship/airshipctl/pkg/phase/apply"
|
"opendev.org/airship/airshipctl/pkg/phase/apply"
|
||||||
"opendev.org/airship/airshipctl/testutil"
|
"opendev.org/airship/airshipctl/testutil"
|
||||||
"opendev.org/airship/airshipctl/testutil/k8sutils"
|
"opendev.org/airship/airshipctl/testutil/k8sutils"
|
||||||
@ -38,12 +38,7 @@ const (
|
|||||||
airshipConfigFile = "testdata/config.yaml"
|
airshipConfigFile = "testdata/config.yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrDynamicClientError = errors.New("ErrDynamicClientError")
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDeploy(t *testing.T) {
|
func TestDeploy(t *testing.T) {
|
||||||
rs := makeNewFakeRootSettings(t, kubeconfigPath, airshipConfigFile)
|
|
||||||
bundle := testutil.NewTestBundle(t, "testdata/primary/site/test-site/ephemeral/initinfra")
|
bundle := testutil.NewTestBundle(t, "testdata/primary/site/test-site/ephemeral/initinfra")
|
||||||
replicationController, err := bundle.SelectOne(document.NewSelector().ByKind("ReplicationController"))
|
replicationController, err := bundle.SelectOne(document.NewSelector().ByKind("ReplicationController"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -61,44 +56,61 @@ func TestDeploy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
defer f.Cleanup()
|
defer f.Cleanup()
|
||||||
|
|
||||||
ao := apply.NewOptions(rs)
|
|
||||||
ao.PhaseName = "initinfra"
|
|
||||||
ao.DryRun = true
|
|
||||||
|
|
||||||
kctl := kubectl.NewKubectl(f)
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
theApplyOptions *apply.Options
|
name string
|
||||||
client client.Interface
|
expectedErrorString string
|
||||||
prune bool
|
cliApplier *applier.Applier
|
||||||
expectedError error
|
clusterPurposes map[string]*config.ClusterPurpose
|
||||||
|
phaseName string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
name: "success",
|
||||||
client: fake.NewClient(fake.WithKubectl(
|
expectedErrorString: "",
|
||||||
kubectl.NewKubectl(k8sutils.
|
cliApplier: applier.NewFakeApplier(genericclioptions.IOStreams{
|
||||||
NewMockKubectlFactory().
|
In: os.Stdin,
|
||||||
WithDynamicClientByError(nil, ErrDynamicClientError)))),
|
Out: os.Stdout,
|
||||||
expectedError: ErrDynamicClientError,
|
ErrOut: os.Stderr,
|
||||||
|
}, k8sutils.SuccessEvents(), f),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedError: nil,
|
name: "missing clusters",
|
||||||
prune: false,
|
expectedErrorString: "At least one cluster needs to be defined",
|
||||||
client: fake.NewClient(fake.WithKubectl(kctl)),
|
clusterPurposes: map[string]*config.ClusterPurpose{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedError: nil,
|
name: "missing phase",
|
||||||
prune: true,
|
expectedErrorString: "Phase document 'missingPhase' was not found",
|
||||||
client: fake.NewClient(fake.WithKubectl(kctl)),
|
phaseName: "missingPhase",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, tt := range tests {
|
||||||
ao.Prune = test.prune
|
tt := tt
|
||||||
ao.Client = test.client
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
actualErr := ao.Run()
|
rs := makeNewFakeRootSettings(t, kubeconfigPath, airshipConfigFile)
|
||||||
assert.Equal(t, test.expectedError, actualErr)
|
ao := &apply.Options{
|
||||||
|
RootSettings: rs,
|
||||||
|
}
|
||||||
|
ao.Initialize()
|
||||||
|
ao.PhaseName = "initinfra"
|
||||||
|
ao.DryRun = true
|
||||||
|
if tt.cliApplier != nil {
|
||||||
|
ao.Applier = tt.cliApplier
|
||||||
|
}
|
||||||
|
if tt.clusterPurposes != nil {
|
||||||
|
ao.RootSettings.Config.Clusters = tt.clusterPurposes
|
||||||
|
}
|
||||||
|
if tt.phaseName != "" {
|
||||||
|
ao.PhaseName = tt.phaseName
|
||||||
|
}
|
||||||
|
actualErr := ao.Run()
|
||||||
|
if tt.expectedErrorString != "" {
|
||||||
|
require.Error(t, actualErr)
|
||||||
|
assert.Contains(t, actualErr.Error(), tt.expectedErrorString)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, actualErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,10 +17,9 @@ set -xe
|
|||||||
export KUBECONFIG=${KUBECONFIG:-"$HOME/.airship/kubeconfig"}
|
export KUBECONFIG=${KUBECONFIG:-"$HOME/.airship/kubeconfig"}
|
||||||
|
|
||||||
echo "Deploy metal3.io components to ephemeral node"
|
echo "Deploy metal3.io components to ephemeral node"
|
||||||
airshipctl phase apply initinfra --debug
|
airshipctl phase apply initinfra --wait-timeout 1000s --debug
|
||||||
|
|
||||||
echo "Waiting for metal3 pods to come up"
|
echo "Getting metal3 pods as debug information"
|
||||||
kubectl --kubeconfig $KUBECONFIG wait --for=condition=ready pods --all --timeout=1000s -A
|
|
||||||
kubectl --kubeconfig $KUBECONFIG --namespace metal3 get pods
|
kubectl --kubeconfig $KUBECONFIG --namespace metal3 get pods
|
||||||
|
|
||||||
echo "Deploy cluster components to ephemeral node"
|
echo "Deploy cluster components to ephemeral node"
|
||||||
|
Loading…
Reference in New Issue
Block a user