dubboctl/cmd/image.go (325 lines of code) (raw):
/*
* 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"
"github.com/AlecAivazis/survey/v2"
"github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
"github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/builder/dockerfile"
"github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/builder/pack"
"github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk"
"github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
"github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
"github.com/ory/viper"
"github.com/spf13/cobra"
"os"
"os/exec"
"path/filepath"
"strings"
)
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
}
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")
}
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")
}
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"),
}
return hc
}
func newDeployConfig(cmd *cobra.Command) *deployConfig {
dc := &deployConfig{
hubConfig: newHubConfig(cmd),
Output: viper.GetString("output"),
Destroy: viper.GetBool("delete"),
}
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"),
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"),
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: "image",
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.hubConfig.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
}