mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 18:10:21 +00:00
Feat: rework vela up to support specified revision (#3634)
* Feat: rework vela up to support specified revision Signed-off-by: Somefive <yd219913@alibaba-inc.com> * Fix: add legacy compatibility Signed-off-by: Somefive <yd219913@alibaba-inc.com> * Feat: fix test Signed-off-by: Somefive <yd219913@alibaba-inc.com>
This commit is contained in:
116
pkg/cmd/builder.go
Normal file
116
pkg/cmd/builder.go
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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
|
||||
|
||||
http://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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/term"
|
||||
)
|
||||
|
||||
// Builder build command with factory
|
||||
type Builder struct {
|
||||
cmd *cobra.Command
|
||||
f Factory
|
||||
}
|
||||
|
||||
// NamespaceFlagConfig config for namespace flag in cmd
|
||||
type NamespaceFlagConfig struct {
|
||||
completion bool
|
||||
usage string
|
||||
loadEnv bool
|
||||
}
|
||||
|
||||
// NamespaceFlagOption the option for configuring namespace flag in cmd
|
||||
type NamespaceFlagOption interface {
|
||||
ApplyToNamespaceFlagOptions(*NamespaceFlagConfig)
|
||||
}
|
||||
|
||||
func newNamespaceFlagOptions(options ...NamespaceFlagOption) NamespaceFlagConfig {
|
||||
cfg := NamespaceFlagConfig{
|
||||
completion: true,
|
||||
usage: usageNamespace,
|
||||
loadEnv: true,
|
||||
}
|
||||
for _, option := range options {
|
||||
option.ApplyToNamespaceFlagOptions(&cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// NamespaceFlagNoCompletionOption disable auto-completion for namespace flag
|
||||
type NamespaceFlagNoCompletionOption struct{}
|
||||
|
||||
// ApplyToNamespaceFlagOptions .
|
||||
func (option NamespaceFlagNoCompletionOption) ApplyToNamespaceFlagOptions(cfg *NamespaceFlagConfig) {
|
||||
cfg.completion = false
|
||||
}
|
||||
|
||||
// NamespaceFlagUsageOption the usage description for namespace flag
|
||||
type NamespaceFlagUsageOption string
|
||||
|
||||
// ApplyToNamespaceFlagOptions .
|
||||
func (option NamespaceFlagUsageOption) ApplyToNamespaceFlagOptions(cfg *NamespaceFlagConfig) {
|
||||
cfg.usage = string(option)
|
||||
}
|
||||
|
||||
// NamespaceFlagDisableEnvOption disable loading namespace from env
|
||||
type NamespaceFlagDisableEnvOption struct{}
|
||||
|
||||
// ApplyToNamespaceFlagOptions .
|
||||
func (option NamespaceFlagDisableEnvOption) ApplyToNamespaceFlagOptions(cfg *NamespaceFlagConfig) {
|
||||
cfg.loadEnv = false
|
||||
}
|
||||
|
||||
// WithNamespaceFlag add namespace flag to the command, by default, it will also add env flag to the command
|
||||
func (builder *Builder) WithNamespaceFlag(options ...NamespaceFlagOption) *Builder {
|
||||
cfg := newNamespaceFlagOptions(options...)
|
||||
builder.cmd.Flags().StringP(flagNamespace, "n", "", cfg.usage)
|
||||
if cfg.completion {
|
||||
cmdutil.CheckErr(builder.cmd.RegisterFlagCompletionFunc(
|
||||
flagNamespace,
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return GetNamespacesForCompletion(cmd.Context(), builder.f, toComplete)
|
||||
}))
|
||||
}
|
||||
if cfg.loadEnv {
|
||||
return builder.WithEnvFlag()
|
||||
}
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithEnvFlag add env flag to the command
|
||||
func (builder *Builder) WithEnvFlag() *Builder {
|
||||
builder.cmd.PersistentFlags().StringP(flagEnv, "e", "", usageEnv)
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithResponsiveWriter format the command outputs
|
||||
func (builder *Builder) WithResponsiveWriter() *Builder {
|
||||
builder.cmd.SetOut(term.NewResponsiveWriter(builder.cmd.OutOrStdout()))
|
||||
return builder
|
||||
}
|
||||
|
||||
// Build construct the command
|
||||
func (builder *Builder) Build() *cobra.Command {
|
||||
return builder.cmd
|
||||
}
|
||||
|
||||
// NewCommandBuilder builder for command
|
||||
func NewCommandBuilder(f Factory, cmd *cobra.Command) *Builder {
|
||||
return &Builder{cmd: cmd, f: f}
|
||||
}
|
||||
72
pkg/cmd/completion.go
Normal file
72
pkg/cmd/completion.go
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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
|
||||
|
||||
http://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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
func listObjectNamesForCompletion(ctx context.Context, f Factory, gvk schema.GroupVersionKind, listOptions []client.ListOption, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
uns := &unstructured.UnstructuredList{}
|
||||
uns.SetGroupVersionKind(gvk)
|
||||
if err := f.Client().List(ctx, uns, listOptions...); err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
var candidates []string
|
||||
for _, obj := range uns.Items {
|
||||
if name := obj.GetName(); strings.HasPrefix(name, toComplete) {
|
||||
candidates = append(candidates, name)
|
||||
}
|
||||
}
|
||||
return candidates, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
// GetNamespacesForCompletion auto-complete the namespace
|
||||
func GetNamespacesForCompletion(ctx context.Context, f Factory, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return listObjectNamesForCompletion(ctx, f, corev1.SchemeGroupVersion.WithKind("Namespace"), nil, toComplete)
|
||||
}
|
||||
|
||||
// GetRevisionForCompletion auto-complete the revision according to the application
|
||||
func GetRevisionForCompletion(ctx context.Context, f Factory, appName string, namespace string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
var options []client.ListOption
|
||||
if namespace != "" {
|
||||
options = append(options, client.InNamespace(namespace))
|
||||
}
|
||||
if appName != "" {
|
||||
options = append(options, client.MatchingLabels{oam.LabelAppName: appName})
|
||||
}
|
||||
return listObjectNamesForCompletion(ctx, f, v1beta1.SchemeGroupVersion.WithKind(v1beta1.ApplicationRevisionKind), options, toComplete)
|
||||
}
|
||||
|
||||
// GetApplicationsForCompletion auto-complete application
|
||||
func GetApplicationsForCompletion(ctx context.Context, f Factory, namespace string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
var options []client.ListOption
|
||||
if namespace != "" {
|
||||
options = append(options, client.InNamespace(namespace))
|
||||
}
|
||||
return listObjectNamesForCompletion(ctx, f, v1beta1.SchemeGroupVersion.WithKind(v1beta1.ApplicationKind), options, toComplete)
|
||||
}
|
||||
46
pkg/cmd/factory.go
Normal file
46
pkg/cmd/factory.go
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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
|
||||
|
||||
http://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 cmd
|
||||
|
||||
import (
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Factory client factory for running command
|
||||
type Factory interface {
|
||||
Client() client.Client
|
||||
}
|
||||
|
||||
// ClientGetter function for getting client
|
||||
type ClientGetter func() (client.Client, error)
|
||||
|
||||
type defaultFactory struct {
|
||||
ClientGetter
|
||||
}
|
||||
|
||||
// Client return the client for command line use, interrupt if error encountered
|
||||
func (f *defaultFactory) Client() client.Client {
|
||||
cli, err := f.ClientGetter()
|
||||
cmdutil.CheckErr(err)
|
||||
return cli
|
||||
}
|
||||
|
||||
// NewDefaultFactory create a factory based on client getter function
|
||||
func NewDefaultFactory(clientGetter ClientGetter) Factory {
|
||||
return &defaultFactory{ClientGetter: clientGetter}
|
||||
}
|
||||
27
pkg/cmd/types.go
Normal file
27
pkg/cmd/types.go
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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
|
||||
|
||||
http://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 cmd
|
||||
|
||||
const (
|
||||
flagNamespace = "namespace"
|
||||
flagEnv = "env"
|
||||
)
|
||||
|
||||
const (
|
||||
usageNamespace = "If present, the namespace scope for this CLI request"
|
||||
usageEnv = "The environment name for the CLI request"
|
||||
)
|
||||
52
pkg/cmd/utils.go
Normal file
52
pkg/cmd/utils.go
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
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
|
||||
|
||||
http://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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/env"
|
||||
)
|
||||
|
||||
// GetNamespace get namespace from command flags and env
|
||||
func GetNamespace(f Factory, cmd *cobra.Command) string {
|
||||
namespace, err := cmd.Flags().GetString(flagNamespace)
|
||||
cmdutil.CheckErr(err)
|
||||
if namespace != "" {
|
||||
return namespace
|
||||
}
|
||||
// find namespace from env
|
||||
envName, err := cmd.Flags().GetString(flagEnv)
|
||||
if err != nil {
|
||||
// ignore env if the command does not use the flag
|
||||
return ""
|
||||
}
|
||||
cmdutil.CheckErr(common.SetGlobalClient(f.Client()))
|
||||
var envMeta *types.EnvMeta
|
||||
if envName != "" {
|
||||
envMeta, err = env.GetEnvByName(envName)
|
||||
} else {
|
||||
envMeta, err = env.GetCurrentEnv()
|
||||
}
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return envMeta.Namespace
|
||||
}
|
||||
51
pkg/controller/utils/actions.go
Normal file
51
pkg/controller/utils/actions.go
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
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
|
||||
|
||||
http://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 utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// FreezeApplication freeze application to disable the reconciling process for it
|
||||
func FreezeApplication(ctx context.Context, cli client.Client, app *v1beta1.Application, mutate func()) (string, error) {
|
||||
return oam.GetControllerRequirement(app), _updateApplicationWithControllerRequirement(ctx, cli, app, mutate, "Disabled")
|
||||
}
|
||||
|
||||
// UnfreezeApplication unfreeze application to enable the reconciling process for it
|
||||
func UnfreezeApplication(ctx context.Context, cli client.Client, app *v1beta1.Application, mutate func(), originalControllerRequirement string) error {
|
||||
return _updateApplicationWithControllerRequirement(ctx, cli, app, mutate, originalControllerRequirement)
|
||||
}
|
||||
|
||||
func _updateApplicationWithControllerRequirement(ctx context.Context, cli client.Client, app *v1beta1.Application, mutate func(), controllerRequirement string) error {
|
||||
appKey := client.ObjectKeyFromObject(app)
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err := cli.Get(ctx, appKey, app); err != nil {
|
||||
return err
|
||||
}
|
||||
oam.SetControllerRequirement(app, controllerRequirement)
|
||||
if mutate != nil {
|
||||
mutate()
|
||||
}
|
||||
return cli.Update(ctx, app)
|
||||
})
|
||||
}
|
||||
@@ -62,3 +62,34 @@ func GetLastAppliedTime(o client.Object) time.Time {
|
||||
}
|
||||
return o.GetCreationTimestamp().Time
|
||||
}
|
||||
|
||||
// SetPublishVersion set PublishVersion for object
|
||||
func SetPublishVersion(o client.Object, publishVersion string) {
|
||||
annotations := o.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations[AnnotationPublishVersion] = publishVersion
|
||||
o.SetAnnotations(annotations)
|
||||
}
|
||||
|
||||
// GetControllerRequirement get ControllerRequirement from object
|
||||
func GetControllerRequirement(o client.Object) string {
|
||||
if annotations := o.GetAnnotations(); annotations != nil {
|
||||
return annotations[AnnotationControllerRequirement]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetControllerRequirement set ControllerRequirement for object
|
||||
func SetControllerRequirement(o client.Object, controllerRequirement string) {
|
||||
annotations := o.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations[AnnotationControllerRequirement] = controllerRequirement
|
||||
if controllerRequirement == "" {
|
||||
delete(annotations, AnnotationControllerRequirement)
|
||||
}
|
||||
o.SetAnnotations(annotations)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -442,6 +443,7 @@ func generateAddonInfo(name string, status pkgaddon.Status) string {
|
||||
for c := range status.Clusters {
|
||||
ic = append(ic, c)
|
||||
}
|
||||
sort.Strings(ic)
|
||||
res += fmt.Sprintf("installedClusters: %s \n", ic)
|
||||
}
|
||||
return res
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
velacmd "github.com/oam-dev/kubevela/pkg/cmd"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
@@ -66,6 +67,7 @@ func NewCommand() *cobra.Command {
|
||||
commandArgs := common.Args{
|
||||
Schema: common.Scheme,
|
||||
}
|
||||
f := velacmd.NewDefaultFactory(commandArgs.GetClient)
|
||||
|
||||
if err := system.InitDirs(); err != nil {
|
||||
fmt.Println("InitDir err", err)
|
||||
@@ -76,7 +78,7 @@ func NewCommand() *cobra.Command {
|
||||
// Getting Start
|
||||
NewEnvCommand(commandArgs, "3", ioStream),
|
||||
NewInitCommand(commandArgs, "2", ioStream),
|
||||
NewUpCommand(commandArgs, "1", ioStream),
|
||||
NewUpCommand(f, "1"),
|
||||
NewCapabilityShowCommand(commandArgs, ioStream),
|
||||
|
||||
// Manage Apps
|
||||
|
||||
@@ -84,8 +84,10 @@ func NewRevisionListCommand(c common.Args) *cobra.Command {
|
||||
status = "Succeeded"
|
||||
case rev.Status.Workflow.Terminated || rev.Status.Workflow.Suspend || rev.Status.Workflow.Finished:
|
||||
status = "Failed"
|
||||
default:
|
||||
case app.Status.LatestRevision != nil && app.Status.LatestRevision.Name == rev.Name:
|
||||
status = "Executing"
|
||||
default:
|
||||
status = "Failed"
|
||||
}
|
||||
}
|
||||
if labels := rev.GetLabels(); labels != nil {
|
||||
|
||||
@@ -19,71 +19,272 @@ package cli
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
corev1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
apicommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
velacmd "github.com/oam-dev/kubevela/pkg/cmd"
|
||||
"github.com/oam-dev/kubevela/pkg/component"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
utilcommon "github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/references/common"
|
||||
)
|
||||
|
||||
// UpCommandOptions command args for vela up
|
||||
type UpCommandOptions struct {
|
||||
AppName string
|
||||
Namespace string
|
||||
File string
|
||||
PublishVersion string
|
||||
RevisionName string
|
||||
}
|
||||
|
||||
// Complete fill the args for vela up
|
||||
func (opt *UpCommandOptions) Complete(f velacmd.Factory, cmd *cobra.Command, args []string) {
|
||||
if len(args) > 0 {
|
||||
opt.AppName = args[0]
|
||||
}
|
||||
opt.Namespace = velacmd.GetNamespace(f, cmd)
|
||||
}
|
||||
|
||||
// Validate if vela up args is valid, interrupt the command
|
||||
func (opt *UpCommandOptions) Validate() error {
|
||||
if opt.AppName != "" && opt.File != "" {
|
||||
return errors.Errorf("cannot use app name and file at the same time")
|
||||
}
|
||||
if opt.AppName == "" && opt.File == "" {
|
||||
return errors.Errorf("either app name or file should be set")
|
||||
}
|
||||
if opt.AppName != "" && opt.PublishVersion == "" {
|
||||
return errors.Errorf("publish-version must be set if you want to force existing application to re-run")
|
||||
}
|
||||
if opt.AppName == "" && opt.RevisionName != "" {
|
||||
return errors.Errorf("revision name must be used with application name")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run execute the vela up command
|
||||
func (opt *UpCommandOptions) Run(f velacmd.Factory, cmd *cobra.Command) error {
|
||||
if opt.File != "" {
|
||||
return opt.deployApplicationFromFile(f, cmd)
|
||||
}
|
||||
if opt.RevisionName == "" {
|
||||
return opt.deployExistingApp(f, cmd)
|
||||
}
|
||||
return opt.deployExistingAppUsingRevision(f, cmd)
|
||||
}
|
||||
|
||||
func (opt *UpCommandOptions) deployExistingAppUsingRevision(f velacmd.Factory, cmd *cobra.Command) error {
|
||||
ctx, cli := cmd.Context(), f.Client()
|
||||
app := &v1beta1.Application{}
|
||||
if err := cli.Get(ctx, apitypes.NamespacedName{Name: opt.AppName, Namespace: opt.Namespace}, app); err != nil {
|
||||
return err
|
||||
}
|
||||
if publishVersion := oam.GetPublishVersion(app); publishVersion == opt.PublishVersion {
|
||||
return errors.Errorf("current PublishVersion is %s", publishVersion)
|
||||
}
|
||||
// check revision
|
||||
revs, err := application.GetSortedAppRevisions(ctx, cli, opt.AppName, opt.Namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var matchedRev *v1beta1.ApplicationRevision
|
||||
for _, rev := range revs {
|
||||
if rev.Name == opt.RevisionName {
|
||||
matchedRev = rev.DeepCopy()
|
||||
}
|
||||
}
|
||||
if matchedRev == nil {
|
||||
return errors.Errorf("failed to find revision %s matching application %s", opt.RevisionName, opt.AppName)
|
||||
}
|
||||
if app.Status.LatestRevision != nil && app.Status.LatestRevision.Name == opt.RevisionName {
|
||||
return nil
|
||||
}
|
||||
|
||||
// freeze the application
|
||||
appKey := client.ObjectKeyFromObject(app)
|
||||
controllerRequirement, err := utils.FreezeApplication(ctx, cli, app, func() {
|
||||
app.Spec = matchedRev.Spec.Application.Spec
|
||||
oam.SetPublishVersion(app, opt.PublishVersion)
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to freeze application %s before update", appKey)
|
||||
}
|
||||
|
||||
// create new revision based on the matched revision
|
||||
revName, revisionNum := utils.GetAppNextRevision(app)
|
||||
matchedRev.Name = revName
|
||||
oam.SetPublishVersion(matchedRev, opt.PublishVersion)
|
||||
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(matchedRev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
un := &unstructured.Unstructured{Object: obj}
|
||||
component.ClearRefObjectForDispatch(un)
|
||||
if err = cli.Create(ctx, un); err != nil {
|
||||
return errors.Wrapf(err, "failed to update application %s to create new revision %s", appKey, revName)
|
||||
}
|
||||
|
||||
// update application status to point to the new revision
|
||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err = cli.Get(ctx, appKey, app); err != nil {
|
||||
return err
|
||||
}
|
||||
app.Status = apicommon.AppStatus{
|
||||
LatestRevision: &apicommon.Revision{Name: revName, Revision: revisionNum, RevisionHash: matchedRev.GetLabels()[oam.LabelAppRevisionHash]},
|
||||
}
|
||||
return cli.Status().Update(ctx, app)
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to update application %s to use new revision %s", appKey, revName)
|
||||
}
|
||||
|
||||
// unfreeze application
|
||||
if err = utils.UnfreezeApplication(ctx, cli, app, nil, controllerRequirement); err != nil {
|
||||
return errors.Wrapf(err, "failed to unfreeze application %s after update", appKey)
|
||||
}
|
||||
|
||||
cmd.Printf("Application updated with new PublishVersion %s using revision %s\n", opt.PublishVersion, opt.RevisionName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opt *UpCommandOptions) deployExistingApp(f velacmd.Factory, cmd *cobra.Command) error {
|
||||
ctx, cli := cmd.Context(), f.Client()
|
||||
app := &v1beta1.Application{}
|
||||
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err := cli.Get(ctx, apitypes.NamespacedName{Name: opt.AppName, Namespace: opt.Namespace}, app); err != nil {
|
||||
return err
|
||||
}
|
||||
if publishVersion := oam.GetPublishVersion(app); publishVersion == opt.PublishVersion {
|
||||
return errors.Errorf("current PublishVersion is %s", publishVersion)
|
||||
}
|
||||
oam.SetPublishVersion(app, opt.PublishVersion)
|
||||
return cli.Update(ctx, app)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Printf("Application updated with new PublishVersion %s\n", opt.PublishVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opt *UpCommandOptions) deployApplicationFromFile(f velacmd.Factory, cmd *cobra.Command) error {
|
||||
cli := f.Client()
|
||||
body, err := common.ReadRemoteOrLocalPath(opt.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ioStream := util.IOStreams{
|
||||
In: cmd.InOrStdin(),
|
||||
Out: cmd.OutOrStdout(),
|
||||
ErrOut: cmd.ErrOrStderr(),
|
||||
}
|
||||
if common.IsAppfile(body) { // legacy compatibility
|
||||
o := &common.AppfileOptions{Kubecli: cli, IO: ioStream, Namespace: opt.Namespace}
|
||||
return o.Run(opt.File, o.Namespace, utilcommon.Args{Schema: utilcommon.Scheme})
|
||||
}
|
||||
var app v1beta1.Application
|
||||
err = yaml.Unmarshal(body, &app)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "File format is illegal, only support vela appfile format or OAM Application object yaml")
|
||||
}
|
||||
|
||||
// Override namespace if namespace flag is set. We should check if namespace is `default` or not
|
||||
// since GetFlagNamespaceOrEnv returns default namespace when failed to get current env.
|
||||
if opt.Namespace != "" && opt.Namespace != types.DefaultAppNamespace {
|
||||
app.SetNamespace(opt.Namespace)
|
||||
}
|
||||
if opt.PublishVersion != "" {
|
||||
oam.SetPublishVersion(&app, opt.PublishVersion)
|
||||
}
|
||||
err = common.ApplyApplication(app, ioStream, cli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Printf("Application %s/%s applied.\n", app.Namespace, app.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
upLong = templates.LongDesc(i18n.T(`
|
||||
Deploy one application
|
||||
|
||||
Deploy one application based on local files or re-deploy an existing application.
|
||||
With the -n/--namespace flag, you can choose the location of the target application.
|
||||
|
||||
To apply application from file, use the -f/--file flag to specify the application
|
||||
file location.
|
||||
|
||||
To give a particular version to this deploy, use the -v/--publish-version flag. When
|
||||
you are deploying an existing application, the version name must be different from
|
||||
the current name. You can also use a history revision for the deploy and override the
|
||||
current application by using the -r/--revision flag.`))
|
||||
|
||||
upExample = templates.Examples(i18n.T(`
|
||||
# Deploy an application from file
|
||||
vela up -f ./app.yaml
|
||||
|
||||
# Deploy an application with a version name
|
||||
vela up example-app -n example-ns --publish-version beta
|
||||
|
||||
# Deploy an application using existing revision
|
||||
vela up example-app -n example-ns --publish-version beta --revision example-app-v2`))
|
||||
)
|
||||
|
||||
// NewUpCommand will create command for applying an AppFile
|
||||
func NewUpCommand(c common2.Args, order string, ioStream cmdutil.IOStreams) *cobra.Command {
|
||||
appFilePath := new(string)
|
||||
func NewUpCommand(f velacmd.Factory, order string) *cobra.Command {
|
||||
o := &UpCommandOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "up",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: "Apply an appfile or application from file",
|
||||
Long: "Create or update vela application from file or URL, both appfile or application object format are supported.",
|
||||
Short: i18n.T("Deploy one application"),
|
||||
Long: upLong,
|
||||
Example: upExample,
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandOrder: order,
|
||||
types.TagCommandType: types.TypeStart,
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
|
||||
if err != nil {
|
||||
return err
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
o.Complete(f, cmd, args)
|
||||
if o.File == "" {
|
||||
return velacmd.GetApplicationsForCompletion(cmd.Context(), f, o.Namespace, toComplete)
|
||||
}
|
||||
kubecli, err := c.GetClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := common.ReadRemoteOrLocalPath(*appFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if common.IsAppfile(body) {
|
||||
o := &common.AppfileOptions{
|
||||
Kubecli: kubecli,
|
||||
IO: ioStream,
|
||||
Namespace: namespace,
|
||||
}
|
||||
return o.Run(*appFilePath, o.Namespace, c)
|
||||
}
|
||||
var app corev1beta1.Application
|
||||
err = yaml.Unmarshal(body, &app)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "File format is illegal, only support vela appfile format or OAM Application object yaml")
|
||||
}
|
||||
|
||||
// Override namespace if namespace flag is set. We should check if namespace is `default` or not
|
||||
// since GetFlagNamespaceOrEnv returns default namespace when failed to get current env.
|
||||
if namespace != "" && namespace != types.DefaultAppNamespace {
|
||||
app.SetNamespace(namespace)
|
||||
}
|
||||
err = common.ApplyApplication(app, ioStream, kubecli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
o.Complete(f, cmd, args)
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run(f, cmd))
|
||||
},
|
||||
}
|
||||
cmd.SetOut(ioStream.Out)
|
||||
cmd.Flags().StringVarP(appFilePath, "file", "f", "", "specify file path for appfile or application, it could be a remote url.")
|
||||
cmd.Flags().StringVarP(&o.File, "file", "f", o.File, "The file path for appfile or application. It could be a remote url.")
|
||||
cmd.Flags().StringVarP(&o.PublishVersion, "publish-version", "v", o.PublishVersion, "The publish version for deploying application.")
|
||||
cmd.Flags().StringVarP(&o.RevisionName, "revision", "r", o.RevisionName, "The revision to use for deploying the application, if empty, the current application configuration will be used.")
|
||||
cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
|
||||
"revision",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
var appName string
|
||||
if len(args) > 0 {
|
||||
appName = args[0]
|
||||
}
|
||||
namespace := velacmd.GetNamespace(f, cmd)
|
||||
return velacmd.GetRevisionForCompletion(cmd.Context(), f, appName, namespace, toComplete)
|
||||
}))
|
||||
|
||||
addNamespaceAndEnvArg(cmd)
|
||||
return cmd
|
||||
return velacmd.NewCommandBuilder(f, cmd).
|
||||
WithNamespaceFlag().
|
||||
WithResponsiveWriter().
|
||||
Build()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
velacmd "github.com/oam-dev/kubevela/pkg/cmd"
|
||||
"github.com/oam-dev/kubevela/references/common"
|
||||
)
|
||||
|
||||
@@ -128,7 +128,10 @@ spec:
|
||||
}))
|
||||
|
||||
var buf bytes.Buffer
|
||||
cmd := NewUpCommand(args, "", util.IOStreams{In: os.Stdin, Out: &buf, ErrOut: &buf})
|
||||
cmd := NewUpCommand(velacmd.NewDefaultFactory(args.GetClient), "")
|
||||
cmd.SetArgs([]string{})
|
||||
cmd.SetOut(&buf)
|
||||
cmd.SetErr(&buf)
|
||||
if c.namespace != "" {
|
||||
require.NoError(t, cmd.Flags().Set(FlagNamespace, c.namespace))
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
@@ -384,18 +383,12 @@ func rollbackApplicationWithPublishVersion(cmd *cobra.Command, cli client.Client
|
||||
cmd.Printf("Find succeeded application revision %s (PublishVersion: %s) to rollback.\n", rev.Name, publishVersion)
|
||||
|
||||
appKey := client.ObjectKeyFromObject(app)
|
||||
var controllerRequirement string
|
||||
// rollback application spec and freeze
|
||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err = cli.Get(ctx, appKey, app); err != nil {
|
||||
return err
|
||||
}
|
||||
v1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationPublishVersion, publishVersion)
|
||||
controllerRequirement = app.GetAnnotations()[oam.AnnotationControllerRequirement]
|
||||
v1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationControllerRequirement, "Not Available")
|
||||
controllerRequirement, err := utils.FreezeApplication(ctx, cli, app, func() {
|
||||
app.Spec = rev.Spec.Application.Spec
|
||||
return cli.Update(ctx, app)
|
||||
}); err != nil {
|
||||
oam.SetPublishVersion(app, publishVersion)
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to rollback application spec to revision %s (PublishVersion: %s)", rev.Name, publishVersion)
|
||||
}
|
||||
cmd.Printf("Application spec rollback successfully.\n")
|
||||
@@ -435,19 +428,7 @@ func rollbackApplicationWithPublishVersion(cmd *cobra.Command, cli client.Client
|
||||
}
|
||||
|
||||
// unfreeze application
|
||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err = cli.Get(ctx, appKey, app); err != nil {
|
||||
return err
|
||||
}
|
||||
annotations := app.GetAnnotations()
|
||||
if controllerRequirement != "" {
|
||||
annotations[oam.AnnotationControllerRequirement] = controllerRequirement
|
||||
} else {
|
||||
delete(annotations, oam.AnnotationControllerRequirement)
|
||||
}
|
||||
app.SetAnnotations(annotations)
|
||||
return cli.Update(ctx, app)
|
||||
}); err != nil {
|
||||
if err = utils.UnfreezeApplication(ctx, cli, app, nil, controllerRequirement); err != nil {
|
||||
return errors.Wrapf(err, "failed to resume application to restart")
|
||||
}
|
||||
cmd.Printf("Application rollback completed.\n")
|
||||
|
||||
@@ -209,9 +209,11 @@ var _ = Describe("Test velaQL rest api", func() {
|
||||
req := apiv1.ApplicationRequest{
|
||||
Components: appWithHelm.Spec.Components,
|
||||
}
|
||||
res := post(fmt.Sprintf("/v1/namespaces/%s/applications/%s", namespace, appWithHelm.Name), req)
|
||||
Expect(res).ShouldNot(BeNil())
|
||||
Expect(res.StatusCode).Should(Equal(200))
|
||||
Eventually(func(g Gomega) {
|
||||
res := post(fmt.Sprintf("/v1/namespaces/%s/applications/%s", namespace, appWithHelm.Name), req)
|
||||
g.Expect(res).ShouldNot(BeNil())
|
||||
g.Expect(res.StatusCode).Should(Equal(200))
|
||||
}, 1*time.Minute).Should(Succeed())
|
||||
|
||||
newApp := new(v1beta1.Application)
|
||||
Eventually(func() error {
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
@@ -447,5 +448,52 @@ var _ = Describe("Test multicluster scenario", func() {
|
||||
g.Expect(k8sClient.Get(workerCtx, types.NamespacedName{Name: "test-busybox", Namespace: prodNamespace}, &appsv1.Deployment{})).Should(Succeed())
|
||||
}, time.Minute).Should(Succeed())
|
||||
})
|
||||
|
||||
It("Test re-deploy application with old revisions", func() {
|
||||
By("apply application")
|
||||
app := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: "test-app-target"},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{{
|
||||
Name: "test-busybox",
|
||||
Type: "webservice",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(`{"image":"busybox","cmd":["sleep","86400"]}`)},
|
||||
}},
|
||||
Policies: []v1beta1.AppPolicy{{
|
||||
Name: "topology-local",
|
||||
Type: "topology",
|
||||
Properties: &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"clusters":["local"],"namespace":"%s"}`, testNamespace))},
|
||||
},
|
||||
}}}
|
||||
oam.SetPublishVersion(app, "alpha")
|
||||
Expect(k8sClient.Create(hubCtx, app)).Should(Succeed())
|
||||
Eventually(func(g Gomega) {
|
||||
g.Expect(k8sClient.Get(hubCtx, types.NamespacedName{Name: "test-busybox", Namespace: testNamespace}, &appsv1.Deployment{})).Should(Succeed())
|
||||
}, time.Minute).Should(Succeed())
|
||||
|
||||
By("update application to new version")
|
||||
appKey := client.ObjectKeyFromObject(app)
|
||||
Eventually(func(g Gomega) {
|
||||
g.Expect(k8sClient.Get(hubCtx, appKey, app)).Should(Succeed())
|
||||
app.Spec.Components[0].Name = "test-busybox-v2"
|
||||
oam.SetPublishVersion(app, "beta")
|
||||
g.Expect(k8sClient.Update(hubCtx, app)).Should(Succeed())
|
||||
}, 15*time.Second).Should(Succeed())
|
||||
Eventually(func(g Gomega) {
|
||||
g.Expect(k8sClient.Get(hubCtx, types.NamespacedName{Name: "test-busybox-v2", Namespace: testNamespace}, &appsv1.Deployment{})).Should(Succeed())
|
||||
err := k8sClient.Get(hubCtx, types.NamespacedName{Name: "test-busybox", Namespace: testNamespace}, &appsv1.Deployment{})
|
||||
g.Expect(kerrors.IsNotFound(err)).Should(BeTrue())
|
||||
}, time.Minute).Should(Succeed())
|
||||
|
||||
By("Re-publish application to v1")
|
||||
_, err := execCommand("up", appKey.Name, "-n", appKey.Namespace, "--revision", appKey.Name+"-v1", "--publish-version", "v1.0")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
g.Expect(k8sClient.Get(hubCtx, types.NamespacedName{Name: "test-busybox", Namespace: testNamespace}, &appsv1.Deployment{})).Should(Succeed())
|
||||
err := k8sClient.Get(hubCtx, types.NamespacedName{Name: "test-busybox-v2", Namespace: testNamespace}, &appsv1.Deployment{})
|
||||
g.Expect(kerrors.IsNotFound(err)).Should(BeTrue())
|
||||
}, 2*time.Minute).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user