github.com/oam-dev/kubevela@v1.9.11/references/cli/up.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cli
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"time"
    24  
    25  	"github.com/kubevela/pkg/controller/sharding"
    26  	"github.com/pkg/errors"
    27  	"github.com/spf13/cobra"
    28  	apitypes "k8s.io/apimachinery/pkg/types"
    29  	"k8s.io/client-go/util/retry"
    30  	"k8s.io/kubectl/pkg/util/i18n"
    31  	"k8s.io/kubectl/pkg/util/templates"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  	"sigs.k8s.io/yaml"
    34  
    35  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    36  	"github.com/oam-dev/kubevela/apis/types"
    37  	velacmd "github.com/oam-dev/kubevela/pkg/cmd"
    38  	cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util"
    39  	"github.com/oam-dev/kubevela/pkg/oam"
    40  	pkgUtils "github.com/oam-dev/kubevela/pkg/utils"
    41  	utilapp "github.com/oam-dev/kubevela/pkg/utils/app"
    42  	utilcommon "github.com/oam-dev/kubevela/pkg/utils/common"
    43  	"github.com/oam-dev/kubevela/pkg/utils/util"
    44  	"github.com/oam-dev/kubevela/references/common"
    45  )
    46  
    47  // UpCommandOptions command args for vela up
    48  type UpCommandOptions struct {
    49  	AppName        string
    50  	Namespace      string
    51  	File           string
    52  	PublishVersion string
    53  	RevisionName   string
    54  	ShardID        string
    55  	Debug          bool
    56  	Wait           bool
    57  	WaitTimeout    string
    58  }
    59  
    60  // Complete fill the args for vela up
    61  func (opt *UpCommandOptions) Complete(f velacmd.Factory, cmd *cobra.Command, args []string) {
    62  	if len(args) > 0 {
    63  		opt.AppName = args[0]
    64  	}
    65  	opt.Namespace = velacmd.GetNamespace(f, cmd)
    66  }
    67  
    68  // Validate if vela up args is valid, interrupt the command
    69  func (opt *UpCommandOptions) Validate() error {
    70  	if opt.AppName != "" && opt.File != "" {
    71  		return errors.Errorf("cannot use app name and file at the same time")
    72  	}
    73  	if opt.AppName == "" && opt.File == "" {
    74  		return errors.Errorf("either app name or file should be set")
    75  	}
    76  	if opt.AppName != "" && opt.PublishVersion == "" && opt.ShardID == "" {
    77  		return errors.Errorf("publish-version must be set if you want to force existing application to re-run")
    78  	}
    79  	if opt.AppName == "" && opt.RevisionName != "" {
    80  		return errors.Errorf("revision name must be used with application name")
    81  	}
    82  	if opt.RevisionName != "" && opt.ShardID != "" {
    83  		return errors.Errorf("revision name must be used with shard id")
    84  	}
    85  	return nil
    86  }
    87  
    88  // Run execute the vela up command
    89  func (opt *UpCommandOptions) Run(f velacmd.Factory, cmd *cobra.Command) error {
    90  	if opt.File != "" {
    91  		return opt.deployApplicationFromFile(f, cmd)
    92  	}
    93  	if opt.RevisionName == "" {
    94  		return opt.deployExistingApp(f, cmd)
    95  	}
    96  	return opt.deployExistingAppUsingRevision(f, cmd)
    97  }
    98  
    99  func (opt *UpCommandOptions) deployExistingAppUsingRevision(f velacmd.Factory, cmd *cobra.Command) error {
   100  	ctx, cli := cmd.Context(), f.Client()
   101  	_, _, err := utilapp.RollbackApplicationWithRevision(ctx, cli, opt.AppName, opt.Namespace, opt.RevisionName, opt.PublishVersion)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	cmd.Printf("Application updated with new PublishVersion %s using revision %s\n", opt.PublishVersion, opt.RevisionName)
   106  	return nil
   107  }
   108  
   109  func (opt *UpCommandOptions) deployExistingApp(f velacmd.Factory, cmd *cobra.Command) error {
   110  	ctx, cli := cmd.Context(), f.Client()
   111  	app := &v1beta1.Application{}
   112  	if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   113  		if err := cli.Get(ctx, apitypes.NamespacedName{Name: opt.AppName, Namespace: opt.Namespace}, app); err != nil {
   114  			return err
   115  		}
   116  		if publishVersion := oam.GetPublishVersion(app); publishVersion == opt.PublishVersion && opt.ShardID == "" {
   117  			return errors.Errorf("current PublishVersion is %s", publishVersion)
   118  		}
   119  		if opt.PublishVersion != "" {
   120  			oam.SetPublishVersion(app, opt.PublishVersion)
   121  		}
   122  		if opt.Debug {
   123  			addDebugPolicy(app)
   124  		}
   125  		if err := reschedule(ctx, cli, app, opt.ShardID); err != nil {
   126  			return err
   127  		}
   128  		return cli.Update(ctx, app)
   129  	}); err != nil {
   130  		return err
   131  	}
   132  	if opt.PublishVersion != "" {
   133  		cmd.Printf("Application updated with new PublishVersion %s\n", opt.PublishVersion)
   134  	}
   135  	if opt.ShardID != "" {
   136  		cmd.Printf("Application scheduled to %s\n", opt.ShardID)
   137  	}
   138  	return nil
   139  }
   140  
   141  func addDebugPolicy(app *v1beta1.Application) {
   142  	for _, policy := range app.Spec.Policies {
   143  		if policy.Type == "debug" {
   144  			return
   145  		}
   146  	}
   147  	app.Spec.Policies = append(app.Spec.Policies, v1beta1.AppPolicy{
   148  		Name: "debug",
   149  		Type: "debug",
   150  	})
   151  }
   152  
   153  func reschedule(ctx context.Context, cli client.Client, app *v1beta1.Application, shardID string) error {
   154  	if shardID != "" {
   155  		sharding.SetScheduledShardID(app, shardID)
   156  		return utilapp.RescheduleAppRevAndRT(ctx, cli, app, shardID)
   157  	}
   158  	return nil
   159  }
   160  
   161  func (opt *UpCommandOptions) deployApplicationFromFile(f velacmd.Factory, cmd *cobra.Command) error {
   162  	cli := f.Client()
   163  	body, err := pkgUtils.ReadRemoteOrLocalPath(opt.File, true)
   164  	if err != nil {
   165  		return err
   166  	}
   167  	ioStream := util.IOStreams{
   168  		In:     cmd.InOrStdin(),
   169  		Out:    cmd.OutOrStdout(),
   170  		ErrOut: cmd.ErrOrStderr(),
   171  	}
   172  	if common.IsAppfile(body) { // legacy compatibility
   173  		o := &common.AppfileOptions{Kubecli: cli, IO: ioStream, Namespace: opt.Namespace}
   174  		if err = o.Run(opt.File, o.Namespace, utilcommon.Args{Schema: utilcommon.Scheme}); err != nil {
   175  			return err
   176  		}
   177  		opt.AppName = o.Name
   178  	} else {
   179  		var app v1beta1.Application
   180  		err = yaml.Unmarshal(body, &app)
   181  		if err != nil {
   182  			return errors.Wrap(err, "File format is illegal, only support vela appfile format or OAM Application object yaml")
   183  		}
   184  
   185  		// Override namespace if namespace flag is set. We should check if namespace is `default` or not
   186  		// since GetFlagNamespaceOrEnv returns default namespace when failed to get current env.
   187  		if opt.Namespace != "" && opt.Namespace != types.DefaultAppNamespace {
   188  			app.SetNamespace(opt.Namespace)
   189  		}
   190  		if opt.PublishVersion != "" {
   191  			oam.SetPublishVersion(&app, opt.PublishVersion)
   192  		}
   193  		opt.AppName = app.Name
   194  		if opt.Debug {
   195  			addDebugPolicy(&app)
   196  		}
   197  		if err = reschedule(cmd.Context(), cli, &app, opt.ShardID); err != nil {
   198  			return err
   199  		}
   200  		err = common.ApplyApplication(app, ioStream, cli)
   201  		if err != nil {
   202  			return err
   203  		}
   204  		cmd.Printf("Application %s applied.\n", green.Sprintf("%s/%s", app.Namespace, app.Name))
   205  	}
   206  	return nil
   207  }
   208  
   209  var (
   210  	upLong = templates.LongDesc(i18n.T(`
   211  		Deploy one application
   212  
   213  		Deploy one application based on local files or re-deploy an existing application.
   214  		With the -n/--namespace flag, you can choose the location of the target application.
   215  
   216  		To apply application from file, use the -f/--file flag to specify the application 
   217  		file location.
   218  
   219  		To give a particular version to this deploy, use the -v/--publish-version flag. When
   220  		you are deploying an existing application, the version name must be different from
   221  		the current name. You can also use a history revision for the deploy and override the
   222  		current application by using the -r/--revision flag.`))
   223  
   224  	upExample = templates.Examples(i18n.T(`
   225  		# Deploy an application from file
   226  		vela up -f ./app.yaml
   227  
   228  		# Deploy an application with a version name
   229  		vela up example-app -n example-ns --publish-version beta
   230  
   231  		# Deploy an application using existing revision
   232  		vela up example-app -n example-ns --publish-version beta --revision example-app-v2
   233  
   234  		# Deploy an application with specified shard-id assigned. This can be used to manually re-schedule application.
   235  		vela up example-app --shard-id shard-1
   236  
   237  		# Deploy an application from stdin
   238  		cat <<EOF | vela up -f -
   239          ... <app.yaml here> ...
   240          EOF
   241  `))
   242  )
   243  
   244  // NewUpCommand will create command for applying an AppFile
   245  func NewUpCommand(f velacmd.Factory, order string, c utilcommon.Args, ioStream util.IOStreams) *cobra.Command {
   246  	o := &UpCommandOptions{
   247  		WaitTimeout: "300s",
   248  	}
   249  	cmd := &cobra.Command{
   250  		Use:                   "up",
   251  		DisableFlagsInUseLine: true,
   252  		Short:                 i18n.T("Deploy one application."),
   253  		Long:                  upLong,
   254  		Example:               upExample,
   255  		Annotations: map[string]string{
   256  			types.TagCommandOrder: order,
   257  			types.TagCommandType:  types.TypeStart,
   258  		},
   259  		Args: cobra.RangeArgs(0, 1),
   260  		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   261  			o.Complete(f, cmd, args)
   262  			if o.File == "" {
   263  				return velacmd.GetApplicationsForCompletion(cmd.Context(), f, o.Namespace, toComplete)
   264  			}
   265  			return nil, cobra.ShellCompDirectiveDefault
   266  		},
   267  		Run: func(cmd *cobra.Command, args []string) {
   268  			o.Complete(f, cmd, args)
   269  			cmdutil.CheckErr(o.Validate())
   270  			cmdutil.CheckErr(o.Run(f, cmd))
   271  			if o.Debug {
   272  				dOpts := &debugOpts{}
   273  				wargs := &WorkflowArgs{Args: c}
   274  				ctx := context.Background()
   275  				cmdutil.CheckErr(wargs.getWorkflowInstance(ctx, cmd, []string{o.AppName}))
   276  				if wargs.Type == instanceTypeWorkflowRun {
   277  					cmdutil.CheckErr(fmt.Errorf("please use `vela workflow debug <name>` instead"))
   278  				}
   279  				if wargs.App == nil {
   280  					cmdutil.CheckErr(fmt.Errorf("application %s not found", args[0]))
   281  				}
   282  				cmdutil.CheckErr(dOpts.debugApplication(ctx, wargs, c, ioStream))
   283  			}
   284  			if o.Wait {
   285  				dur, err := time.ParseDuration(o.WaitTimeout)
   286  				if err != nil {
   287  					cmdutil.CheckErr(fmt.Errorf("parse timeout duration err: %w", err))
   288  				}
   289  				status, err := printTrackingDeployStatus(c, ioStream, o.AppName, o.Namespace, dur)
   290  				if err != nil {
   291  					cmdutil.CheckErr(err)
   292  				}
   293  				if status != appDeployedHealthy {
   294  					os.Exit(1)
   295  				}
   296  			}
   297  		},
   298  	}
   299  	cmd.Flags().StringVarP(&o.File, "file", "f", o.File, "The file path for appfile or application. It could be a remote url.")
   300  	cmd.Flags().StringVarP(&o.PublishVersion, "publish-version", "v", o.PublishVersion, "The publish version for deploying application.")
   301  	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.")
   302  	cmd.Flags().StringVarP(&o.ShardID, "shard-id", "s", o.ShardID, "The shard id assigned to the application. If empty, it will not be used.")
   303  	cmd.Flags().BoolVarP(&o.Debug, "debug", "", o.Debug, "Enable debug mode for application")
   304  	cmd.Flags().BoolVarP(&o.Wait, "wait", "w", o.Wait, "Wait app to be healthy until timout, if no timeout specified, the default duration is 300s.")
   305  	cmd.Flags().StringVarP(&o.WaitTimeout, "timeout", "", o.WaitTimeout, "Set the timout for wait app to be healthy, if not specified, the default duration is 300s.")
   306  	cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
   307  		"revision",
   308  		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   309  			var appName string
   310  			if len(args) > 0 {
   311  				appName = args[0]
   312  			}
   313  			namespace := velacmd.GetNamespace(f, cmd)
   314  			return velacmd.GetRevisionForCompletion(cmd.Context(), f, appName, namespace, toComplete)
   315  		}))
   316  
   317  	return velacmd.NewCommandBuilder(f, cmd).
   318  		WithNamespaceFlag().
   319  		WithResponsiveWriter().
   320  		Build()
   321  }