| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You 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 ( |
| "fmt" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "strings" |
| |
| "github.com/AlecAivazis/survey/v2" |
| "github.com/apache/dubbo-kubernetes/cli/pkg/cli" |
| "github.com/apache/dubbo-kubernetes/cli/pkg/hub/builder/dockerfile" |
| "github.com/apache/dubbo-kubernetes/cli/pkg/hub/builder/pack" |
| "github.com/apache/dubbo-kubernetes/cli/pkg/sdk" |
| "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo" |
| "github.com/apache/dubbo-kubernetes/cli/pkg/util" |
| "github.com/ory/viper" |
| "github.com/spf13/cobra" |
| ) |
| |
| type imageArgs struct { |
| // dockerfile Defines the required Dockerfile files. |
| dockerfile bool |
| // builder Defines the path used by the builder. |
| builder bool |
| // output Defines the generated dubbo deploy yaml file. |
| output string |
| // destroy Defines the deletion of deploy yaml file. |
| destroy bool |
| // imageInfo specifies the image used for the build process. |
| imageInfo string |
| // namespace specifies the Kubernetes namespace for the deployment. |
| namespace string |
| // port defines the service port used in the Kubernetes configuration. |
| port string |
| } |
| |
| func addHubFlags(cmd *cobra.Command, iArgs *imageArgs) { |
| cmd.PersistentFlags().BoolVarP(&iArgs.dockerfile, "file", "f", false, "Specify the file as a dockerfile") |
| cmd.PersistentFlags().BoolVarP(&iArgs.builder, "builder", "b", false, "The builder generates the image") |
| cmd.PersistentFlags().StringVar(&iArgs.imageInfo, "imageInfo", "", "Specifies the image used for building or deploying the application.") |
| } |
| |
| func addDeployFlags(cmd *cobra.Command, iArgs *imageArgs) { |
| cmd.PersistentFlags().StringVarP(&iArgs.output, "output", "o", "dubbo-deploy.yaml", "The output generates k8s yaml file") |
| cmd.PersistentFlags().BoolVarP(&iArgs.destroy, "delete", "d", false, "deletion k8s yaml file") |
| cmd.PersistentFlags().StringVar(&iArgs.namespace, "namespace", "", "Kubernetes namespace where the resources will be deployed.") |
| cmd.PersistentFlags().StringVar(&iArgs.imageInfo, "imageInfo", "", "Container image to use for building or deploying the application.") |
| cmd.PersistentFlags().StringVar(&iArgs.port, "port", "81", "Port number exposed by the Kubernetes service.") |
| } |
| |
| func ImageCmd(ctx cli.Context, cmd *cobra.Command, clientFactory ClientFactory) *cobra.Command { |
| ihc := imageHubCmd(cmd, clientFactory) |
| idc := imageDeployCmd(cmd, clientFactory) |
| ic := &cobra.Command{ |
| Use: "image", |
| Short: "Used to build and push images, apply to cluster", |
| } |
| ic.AddCommand(ihc) |
| ic.AddCommand(idc) |
| return ic |
| } |
| |
| type hubConfig struct { |
| // Dockerfile Defines the required files. |
| Dockerfile bool |
| // Builder Defines the path used by the builder. |
| Builder bool |
| // Image information required by image. |
| Image string |
| // BuilderImage is the image (name or mapping) to use for building. Usually |
| // set automatically. |
| BuilderImage string |
| // Path of the application implementation on local disk. Defaults to current |
| // working directory of the process. |
| Path string |
| } |
| |
| type deployConfig struct { |
| *hubConfig |
| Output string |
| Destroy bool |
| Namespace string |
| Port int |
| Path string |
| } |
| |
| func newHubConfig(cmd *cobra.Command) *hubConfig { |
| hc := &hubConfig{ |
| Dockerfile: viper.GetBool("file"), |
| Builder: viper.GetBool("builder"), |
| Image: viper.GetString("imageInfo"), |
| } |
| return hc |
| } |
| |
| func newDeployConfig(cmd *cobra.Command) *deployConfig { |
| dc := &deployConfig{ |
| hubConfig: newHubConfig(cmd), |
| Output: viper.GetString("output"), |
| Destroy: viper.GetBool("delete"), |
| Namespace: viper.GetString("namespace"), |
| Port: viper.GetInt("port"), |
| } |
| return dc |
| } |
| |
| func (hc hubConfig) imageClientOptions() ([]sdk.Option, error) { |
| var do []sdk.Option |
| if hc.Dockerfile { |
| do = append(do, sdk.WithBuilder(dockerfile.NewBuilder())) |
| } else { |
| do = append(do, sdk.WithBuilder(pack.NewBuilder())) |
| } |
| return do, nil |
| } |
| |
| func (d deployConfig) deployClientOptions() ([]sdk.Option, error) { |
| i, err := d.imageClientOptions() |
| if err != nil { |
| return i, err |
| } |
| |
| return i, nil |
| } |
| |
| func imageHubCmd(cmd *cobra.Command, clientFactory ClientFactory) *cobra.Command { |
| iArgs := &imageArgs{} |
| hc := &cobra.Command{ |
| Use: "hub", |
| Short: "Build and Push to images", |
| Long: "The hub subcommand used to build and push images", |
| Example: ` # Build an image using a Dockerfile. |
| dubboctl image hub -f Dockerfile |
| |
| # Build an image using a builder. |
| dubboctl image hub -b |
| `, |
| Args: func(cmd *cobra.Command, args []string) error { |
| if !iArgs.dockerfile && !iArgs.builder { |
| return fmt.Errorf("at least one of the -b or -f flags must be set") |
| } |
| |
| if cmd.Flags().Changed("file") { |
| if len(args) != 1 { |
| return fmt.Errorf("you must provide exactly one argument when using the -f flag: the path to the Dockerfile") |
| } |
| |
| df := args[0] |
| if !strings.HasSuffix(df, "Dockerfile") { |
| return fmt.Errorf("the provided file must be a Dockerfile when using the -f flag") |
| } |
| } |
| return nil |
| }, |
| PreRunE: bindEnv("file", "builder", "imageInfo"), |
| RunE: func(cmd *cobra.Command, args []string) error { |
| return runHub(cmd, args, clientFactory) |
| }, |
| } |
| addHubFlags(hc, iArgs) |
| return hc |
| } |
| |
| func (hc *hubConfig) checkHubConfig(dc *dubbo.DubboConfig) { |
| if hc.Path == "" { |
| root, err := os.Getwd() |
| if err != nil { |
| return |
| } |
| dc.Root = root |
| } else { |
| dc.Root = hc.Path |
| } |
| if hc.BuilderImage != "" { |
| dc.Build.BuilderImages["pack"] = hc.BuilderImage |
| } |
| if hc.Image != "" { |
| dc.Image = hc.Image |
| } |
| } |
| |
| func runHub(cmd *cobra.Command, args []string, clientFactory ClientFactory) error { |
| if err := util.GetCreatePath(); err != nil { |
| return err |
| } |
| hubCfg := newHubConfig(cmd) |
| filePath, err := dubbo.NewDubboConfig(hubCfg.Path) |
| if err != nil { |
| return err |
| } |
| |
| hubCfg, err = hubCfg.hubPrompt(filePath) |
| if err != nil { |
| return err |
| } |
| |
| if !filePath.Initialized() { |
| return util.NewErrNotInitialized(filePath.Root) |
| } |
| |
| hubCfg.checkHubConfig(filePath) |
| |
| clientOptions, err := hubCfg.imageClientOptions() |
| if err != nil { |
| return err |
| } |
| |
| client, done := clientFactory(clientOptions...) |
| defer done() |
| |
| filePath.Built() |
| |
| if filePath, err = client.Build(cmd.Context(), filePath); err != nil { |
| return err |
| } |
| |
| if filePath, err = client.Push(cmd.Context(), filePath); err != nil { |
| return err |
| } |
| |
| err = filePath.WriteFile() |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func imageDeployCmd(cmd *cobra.Command, clientFactory ClientFactory) *cobra.Command { |
| iArgs := &imageArgs{} |
| hc := &cobra.Command{ |
| Use: "deploy", |
| Short: "Deploy to cluster", |
| Long: "The deploy subcommand used to deploy to cluster", |
| Example: ` # Deploy the application to the cluster. |
| dubboctl image deploy |
| |
| # Delete the deployed application. |
| dubboctl image deploy -d |
| `, |
| PreRunE: bindEnv("output", "delete", "namespace", "imageInfo", "port"), |
| RunE: func(cmd *cobra.Command, args []string) error { |
| return runDeploy(cmd, args, clientFactory) |
| }, |
| } |
| addDeployFlags(hc, iArgs) |
| return hc |
| } |
| |
| func (d deployConfig) checkDeployConfig(dc *dubbo.DubboConfig) { |
| d.checkHubConfig(dc) |
| if d.Output != "" { |
| dc.Deploy.Output = d.Output |
| } |
| if d.Namespace != "" { |
| dc.Deploy.Namespace = d.Namespace |
| } |
| if d.Port != 0 { |
| dc.Deploy.Port = d.Port |
| } |
| } |
| |
| func runDeploy(cmd *cobra.Command, args []string, clientFactory ClientFactory) error { |
| if err := util.GetCreatePath(); err != nil { |
| return err |
| } |
| deployCfg := newDeployConfig(cmd) |
| |
| filePath, err := dubbo.NewDubboConfig(deployCfg.Path) |
| if err != nil { |
| return err |
| } |
| |
| deployCfg, err = deployCfg.deployPrompt(filePath) |
| if err != nil { |
| return err |
| } |
| |
| deployCfg.checkDeployConfig(filePath) |
| |
| clientOptions, err := deployCfg.deployClientOptions() |
| if err != nil { |
| return err |
| } |
| |
| client, done := clientFactory(clientOptions...) |
| defer done() |
| if filePath, err = client.Deploy(cmd.Context(), filePath); err != nil { |
| return err |
| } |
| |
| if !deployCfg.Destroy { |
| if err := apply(cmd, filePath); err != nil { |
| return err |
| } |
| } |
| if deployCfg.Destroy { |
| if err := remove(cmd, filePath); err != nil { |
| return err |
| } |
| } |
| |
| err = filePath.WriteFile() |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func apply(cmd *cobra.Command, dc *dubbo.DubboConfig) error { |
| file := filepath.Join(dc.Root, dc.Deploy.Output) |
| ec := exec.CommandContext(cmd.Context(), "kubectl", "apply", "-f", file) |
| ec.Stdout, ec.Stderr = os.Stdout, os.Stderr |
| if err := ec.Run(); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func remove(cmd *cobra.Command, dc *dubbo.DubboConfig) error { |
| file := filepath.Join(dc.Root, dc.Deploy.Output) |
| ec := exec.CommandContext(cmd.Context(), "kubectl", "delete", "-f", file) |
| ec.Stdout, ec.Stderr = os.Stdout, os.Stderr |
| if err := ec.Run(); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func (hc *hubConfig) hubPrompt(dc *dubbo.DubboConfig) (*hubConfig, error) { |
| var err error |
| if !util.InteractiveTerminal() { |
| return hc, nil |
| } |
| |
| if hc.Image == "" && dc.Image == "" { |
| qs := []*survey.Question{ |
| { |
| Name: "imageInfo", |
| Validate: survey.Required, |
| Prompt: &survey.Input{ |
| Message: "Please enter the image tag ([REGISTRY]/[USERNAME]/[IMAGENAME]:tag)\n Image: ", |
| Default: hc.Image, |
| }, |
| }, |
| } |
| if err = survey.Ask(qs, hc); err != nil { |
| return hc, err |
| } |
| } |
| return hc, err |
| } |
| |
| func (d *deployConfig) deployPrompt(dc2 *dubbo.DubboConfig) (*deployConfig, error) { |
| var err error |
| if !util.InteractiveTerminal() { |
| return d, nil |
| } |
| if dc2.Deploy.Namespace == "" { |
| qs := []*survey.Question{ |
| { |
| Name: "namespace", |
| Validate: survey.Required, |
| Prompt: &survey.Input{ |
| Message: "Namespace", |
| }, |
| }, |
| } |
| if err = survey.Ask(qs, d); err != nil { |
| return d, err |
| } |
| } |
| |
| buildconfig, err := d.hubPrompt(dc2) |
| if err != nil { |
| return d, err |
| } |
| |
| d.hubConfig = buildconfig |
| |
| if dc2.Deploy.Port == 0 && d.Port == 0 { |
| qs := []*survey.Question{ |
| { |
| Name: "port", |
| Validate: survey.Required, |
| Prompt: &survey.Input{ |
| Message: "Port", |
| }, |
| }, |
| } |
| if err = survey.Ask(qs, d); err != nil { |
| return d, err |
| } |
| } |
| |
| return d, err |
| } |