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 }