github.com/docker/app@v0.9.1-beta3.0.20210611140623-a48f773ab002/internal/commands/run.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  
     7  	"github.com/docker/app/internal/packager"
     8  
     9  	"github.com/docker/app/internal/image"
    10  
    11  	"github.com/deislabs/cnab-go/driver"
    12  	"github.com/docker/app/internal/cliopts"
    13  
    14  	"github.com/deislabs/cnab-go/action"
    15  	"github.com/deislabs/cnab-go/credentials"
    16  	bdl "github.com/docker/app/internal/bundle"
    17  	"github.com/docker/app/internal/cnab"
    18  	"github.com/docker/app/internal/store"
    19  	"github.com/docker/cli/cli"
    20  	"github.com/docker/cli/cli/command"
    21  	"github.com/docker/docker/pkg/namesgenerator"
    22  	"github.com/pkg/errors"
    23  	"github.com/sirupsen/logrus"
    24  	"github.com/spf13/cobra"
    25  )
    26  
    27  type runOptions struct {
    28  	cliopts.ParametersOptions
    29  	credentialOptions
    30  	orchestrator  string
    31  	kubeNamespace string
    32  	stackName     string
    33  	cnabBundle    string
    34  	labels        []string
    35  }
    36  
    37  const longDescription = `Run an App from an App image.`
    38  
    39  const runExample = `- $ docker app run --name myrunningapp myrepo/myapp:mytag
    40  - $ docker app run 34be4a0c5f50 --name myrunningapp`
    41  
    42  func runCmd(dockerCli command.Cli, installerContext *cliopts.InstallerContextOptions) *cobra.Command {
    43  	var opts runOptions
    44  
    45  	cmd := &cobra.Command{
    46  		Use:     "run [OPTIONS] APP_IMAGE",
    47  		Aliases: []string{"deploy"},
    48  		Short:   "Run an App from an App image",
    49  		Long:    longDescription,
    50  		Example: runExample,
    51  		RunE: func(cmd *cobra.Command, args []string) error {
    52  			if opts.cnabBundle != "" && len(args) != 0 {
    53  				return errors.Errorf(
    54  					"%q cannot run a bundle and an App image",
    55  					cmd.CommandPath(),
    56  				)
    57  			}
    58  			if opts.cnabBundle == "" {
    59  				if err := cli.ExactArgs(1)(cmd, args); err != nil {
    60  					return err
    61  				}
    62  				return runDockerApp(dockerCli, args[0], opts, installerContext)
    63  			}
    64  			return runCnab(dockerCli, opts, installerContext)
    65  		},
    66  	}
    67  	opts.ParametersOptions.AddFlags(cmd.Flags())
    68  	opts.credentialOptions.addFlags(cmd.Flags())
    69  	cmd.Flags().StringVar(&opts.orchestrator, "orchestrator", "", "Orchestrator to install on (swarm, kubernetes)")
    70  	cmd.Flags().StringVar(&opts.kubeNamespace, "namespace", "default", "Kubernetes namespace to install into")
    71  	cmd.Flags().StringVar(&opts.stackName, "name", "", "Assign a name to the installation")
    72  	cmd.Flags().StringVar(&opts.cnabBundle, "cnab-bundle-json", "", "Run a CNAB bundle instead of a Docker App")
    73  	cmd.Flags().StringArrayVar(&opts.labels, "label", nil, "Label to add to services")
    74  
    75  	//nolint:errcheck
    76  	cmd.Flags().SetAnnotation("cnab-bundle-json", "experimentalCLI", []string{"true"})
    77  
    78  	return cmd
    79  }
    80  
    81  func runCnab(dockerCli command.Cli, opts runOptions, installerContext *cliopts.InstallerContextOptions) error {
    82  	bndl, err := image.FromFile(opts.cnabBundle)
    83  	if err != nil {
    84  		return errors.Wrapf(err, "failed to read bundle %q", opts.cnabBundle)
    85  	}
    86  	return runBundle(dockerCli, bndl, opts, installerContext, "")
    87  }
    88  
    89  func runDockerApp(dockerCli command.Cli, appname string, opts runOptions, installerContext *cliopts.InstallerContextOptions) error {
    90  	imageStore, err := prepareImageStore()
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	bndl, ref, err := cnab.GetBundle(dockerCli, imageStore, appname)
    96  	if err != nil {
    97  		return errors.Wrapf(err, "Unable to find App %q", appname)
    98  	}
    99  	return runBundle(dockerCli, bndl, opts, installerContext, ref.String())
   100  }
   101  
   102  func runBundle(dockerCli command.Cli, bndl *image.AppImage, opts runOptions, installerContext *cliopts.InstallerContextOptions, ref string) (err error) {
   103  	if err := packager.CheckAppVersion(dockerCli.Err(), bndl.Bundle); err != nil {
   104  		return err
   105  	}
   106  	_, installationStore, credentialStore, err := prepareStores(dockerCli.CurrentContext())
   107  	if err != nil {
   108  		return err
   109  	}
   110  	if err := bndl.Validate(); err != nil {
   111  		return err
   112  	}
   113  	installationName := opts.stackName
   114  	if installationName == "" {
   115  		installationName = namesgenerator.GetRandomName(0)
   116  	}
   117  	logrus.Debugf(`Looking for a previous installation "%q"`, installationName)
   118  	if installation, err := installationStore.Read(installationName); err == nil {
   119  		// A failed installation can be overridden, but with a warning
   120  		if IsInstallationFailed(installation) {
   121  			fmt.Fprintf(dockerCli.Err(), "WARNING: installing over previously failed installation %q\n", installationName)
   122  		} else {
   123  			// Return an error in case of successful installation, or even failed upgrade, which means
   124  			// their was already a successful installation.
   125  			return fmt.Errorf("Installation %q already exists, use 'docker app update' instead", installationName)
   126  		}
   127  	} else {
   128  		logrus.Debug(err)
   129  	}
   130  	installation, err := store.NewInstallation(installationName, ref, bndl)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	driverImpl, errBuf, err := cnab.SetupDriver(installation, dockerCli, installerContext, os.Stdout)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	if err := bdl.MergeBundleParameters(installation,
   141  		bdl.WithFileParameters(opts.ParametersFiles),
   142  		bdl.WithCommandLineParameters(opts.Overrides),
   143  		bdl.WithLabels(opts.labels),
   144  		bdl.WithOrchestratorParameters(opts.orchestrator, opts.kubeNamespace),
   145  		bdl.WithSendRegistryAuth(opts.sendRegistryAuth),
   146  	); err != nil {
   147  		return err
   148  	}
   149  	creds, err := prepareCredentialSet(bndl.Bundle, opts.CredentialSetOpts(dockerCli, credentialStore)...)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	if err := credentials.Validate(creds, bndl.Credentials); err != nil {
   154  		return err
   155  	}
   156  
   157  	inst := &action.Install{
   158  		Driver: driverImpl,
   159  	}
   160  	{
   161  		defer muteDockerCli(dockerCli)()
   162  		cfgFunc := func(op *driver.Operation) error {
   163  			op.Out = dockerCli.Out()
   164  			return nil
   165  		}
   166  		err = inst.Run(&installation.Claim, creds, cfgFunc, cnab.WithRelocationMap(installation))
   167  	}
   168  	// Even if the installation failed, the installation is persisted with its failure status,
   169  	// so any installation needs a clean uninstallation.
   170  	err2 := installationStore.Store(installation)
   171  	if err != nil {
   172  		return fmt.Errorf("Failed to run App: %s\n%s", err, errBuf)
   173  	}
   174  	if err2 != nil {
   175  		return err2
   176  	}
   177  
   178  	fmt.Fprintf(os.Stdout, "App %q running on context %q\n", installationName, dockerCli.CurrentContext())
   179  	return nil
   180  }