github.com/Azure/draft-classic@v0.16.0/cmd/draft/up.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/Azure/go-autorest/autorest"
    11  	azurecli "github.com/Azure/go-autorest/autorest/azure/cli"
    12  	"github.com/docker/cli/cli/command"
    13  	cliconfig "github.com/docker/cli/cli/config"
    14  	dockerdebug "github.com/docker/cli/cli/debug"
    15  	dockerflags "github.com/docker/cli/cli/flags"
    16  	"github.com/docker/cli/opts"
    17  	"github.com/docker/go-connections/tlsconfig"
    18  	"github.com/spf13/cobra"
    19  	"github.com/spf13/pflag"
    20  	"golang.org/x/net/context"
    21  	"k8s.io/client-go/rest"
    22  
    23  	"github.com/Azure/draft/pkg/azure/containerregistry"
    24  	"github.com/Azure/draft/pkg/azure/iam"
    25  	"github.com/Azure/draft/pkg/builder"
    26  	azurecontainerbuilder "github.com/Azure/draft/pkg/builder/azure"
    27  	dockercontainerbuilder "github.com/Azure/draft/pkg/builder/docker"
    28  	"github.com/Azure/draft/pkg/cmdline"
    29  	"github.com/Azure/draft/pkg/draft/draftpath"
    30  	"github.com/Azure/draft/pkg/local"
    31  	"github.com/Azure/draft/pkg/storage/kube/configmap"
    32  	"github.com/Azure/draft/pkg/tasks"
    33  )
    34  
    35  const upDesc = `
    36  This command builds a container image using Docker, pushes it to a container registry
    37  and then instructs helm to install the chart, referencing the image just built.
    38  `
    39  
    40  const (
    41  	ignoreFileName        = ".draftignore"
    42  	dockerTLSEnvVar       = "DOCKER_TLS"
    43  	dockerTLSVerifyEnvVar = "DOCKER_TLS_VERIFY"
    44  	tasksTOMLFile         = ".draft-tasks.toml"
    45  )
    46  
    47  var (
    48  	dockerCertPath = os.Getenv("DOCKER_CERT_PATH")
    49  	autoConnect    bool
    50  )
    51  
    52  type upCmd struct {
    53  	out  io.Writer
    54  	src  string
    55  	home draftpath.Home
    56  	// storage engine draft should use for storing builds, logs, etc.
    57  	storageEngine string
    58  	// options common to the docker client and the daemon.
    59  	dockerClientOptions *dockerflags.ClientOptions
    60  }
    61  
    62  func defaultDockerTLS() bool {
    63  	return os.Getenv(dockerTLSEnvVar) != ""
    64  }
    65  
    66  func defaultDockerTLSVerify() bool {
    67  	return os.Getenv(dockerTLSVerifyEnvVar) != ""
    68  }
    69  
    70  func dockerPreRun(opts *dockerflags.ClientOptions) {
    71  	dockerflags.SetLogLevel(opts.Common.LogLevel)
    72  
    73  	if opts.ConfigDir != "" {
    74  		cliconfig.SetDir(opts.ConfigDir)
    75  	}
    76  
    77  	if opts.Common.Debug {
    78  		dockerdebug.Enable()
    79  	}
    80  }
    81  
    82  func newUpCmd(out io.Writer) *cobra.Command {
    83  	var (
    84  		up = &upCmd{
    85  			out:                 out,
    86  			dockerClientOptions: dockerflags.NewClientOptions(),
    87  		}
    88  		runningEnvironment string
    89  		f                  *pflag.FlagSet
    90  	)
    91  
    92  	cmd := &cobra.Command{
    93  		Use:   "up [path]",
    94  		Short: "build and push Docker image, then install the Helm chart, referencing the image just built",
    95  		Long:  upDesc,
    96  		PersistentPreRun: func(c *cobra.Command, args []string) {
    97  			rootCmd.PersistentPreRunE(c, args)
    98  			up.dockerClientOptions.Common.SetDefaultOptions(f)
    99  			dockerPreRun(up.dockerClientOptions)
   100  		},
   101  		RunE: func(_ *cobra.Command, args []string) (err error) {
   102  			if len(args) > 0 {
   103  				up.src = args[0]
   104  			}
   105  			if up.src == "" || up.src == "." {
   106  				if up.src, err = os.Getwd(); err != nil {
   107  					return err
   108  				}
   109  			}
   110  			up.home = draftpath.Home(homePath())
   111  			return up.run(runningEnvironment)
   112  		},
   113  	}
   114  
   115  	f = cmd.Flags()
   116  	f.StringVarP(&runningEnvironment, environmentFlagName, environmentFlagShorthand, defaultDraftEnvironment(), environmentFlagUsage)
   117  	f.BoolVar(&up.dockerClientOptions.Common.Debug, "docker-debug", false, "Enable debug mode")
   118  	f.StringVar(&up.dockerClientOptions.Common.LogLevel, "docker-log-level", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`)
   119  	f.BoolVar(&up.dockerClientOptions.Common.TLS, "docker-tls", defaultDockerTLS(), "Use TLS; implied by --tlsverify")
   120  	f.BoolVar(&up.dockerClientOptions.Common.TLSVerify, fmt.Sprintf("docker-%s", dockerflags.FlagTLSVerify), defaultDockerTLSVerify(), "Use TLS and verify the remote")
   121  	f.StringVar(&up.dockerClientOptions.ConfigDir, "docker-config", cliconfig.Dir(), "Location of client config files")
   122  	f.BoolVarP(&autoConnect, "auto-connect", "", false, "specifies if draft up should automatically connect to the application")
   123  
   124  	up.dockerClientOptions.Common.TLSOptions = &tlsconfig.Options{
   125  		CAFile:   filepath.Join(dockerCertPath, dockerflags.DefaultCaFile),
   126  		CertFile: filepath.Join(dockerCertPath, dockerflags.DefaultCertFile),
   127  		KeyFile:  filepath.Join(dockerCertPath, dockerflags.DefaultKeyFile),
   128  	}
   129  
   130  	tlsOptions := up.dockerClientOptions.Common.TLSOptions
   131  	f.Var(opts.NewQuotedString(&tlsOptions.CAFile), "docker-tlscacert", "Trust certs signed only by this CA")
   132  	f.Var(opts.NewQuotedString(&tlsOptions.CertFile), "docker-tlscert", "Path to TLS certificate file")
   133  	f.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "docker-tlskey", "Path to TLS key file")
   134  
   135  	hostOpt := opts.NewNamedListOptsRef("docker-hosts", &up.dockerClientOptions.Common.Hosts, opts.ValidateHost)
   136  	f.Var(hostOpt, "docker-host", "Daemon socket(s) to connect to")
   137  
   138  	return cmd
   139  }
   140  
   141  func (u *upCmd) run(environment string) (err error) {
   142  	var (
   143  		buildctx   *builder.Context
   144  		kubeConfig *rest.Config
   145  		ctx        = context.Background()
   146  		bldr       = builder.New()
   147  	)
   148  	bldr.LogsDir = u.home.Logs()
   149  
   150  	taskList, err := tasks.Load(tasksTOMLFile)
   151  	if err != nil {
   152  		if err == tasks.ErrNoTaskFile {
   153  			debug(err.Error())
   154  		} else {
   155  			return err
   156  		}
   157  	} else {
   158  		if _, err = taskList.Run(tasks.DefaultRunner, tasks.PreUp, ""); err != nil {
   159  			return err
   160  		}
   161  	}
   162  
   163  	if buildctx, err = builder.LoadWithEnv(u.src, environment); err != nil {
   164  		return fmt.Errorf("failed loading build context with env %q: %v", environment, err)
   165  	}
   166  
   167  	if configuredBuilder, ok := globalConfig[containerBuilder.name]; ok {
   168  		buildctx.Env.ContainerBuilder = configuredBuilder
   169  	}
   170  
   171  	// if a registry has been set in their global config but nothing was in draft.toml, use that instead
   172  	if reg, ok := globalConfig[registry.name]; ok {
   173  		buildctx.Env.Registry = reg
   174  	}
   175  
   176  	if configuredResourceGroup, ok := globalConfig[resourceGroupName.name]; ok {
   177  		buildctx.Env.ResourceGroupName = configuredResourceGroup
   178  	}
   179  
   180  	if buildctx.Env.Registry == "" {
   181  		// give a way for minikube users (and users who understand what they're doing) a way to opt out
   182  		if _, ok := globalConfig[disablePushWarning.name]; !ok {
   183  			fmt.Fprintln(u.out, "WARNING: no registry has been set, therefore Draft will not push to a container registry. This can be fixed by running `draft config set registry docker.io/myusername`")
   184  			fmt.Fprintln(u.out, "Hint: this warning can be disabled by running `draft config set disable-push-warning 1`")
   185  		}
   186  	}
   187  
   188  	var cb builder.ContainerBuilder
   189  	switch buildctx.Env.ContainerBuilder {
   190  	case "acrbuild":
   191  		subscription, err := getSubscriptionFromProfile()
   192  		if err != nil {
   193  			return fmt.Errorf("Could not retrieve azure profile information: %v", err)
   194  		}
   195  		token, err := iam.GetToken(iam.AuthGrantType())
   196  		if err != nil {
   197  			return fmt.Errorf("Could not retrieve adal token: %v", err)
   198  		}
   199  		auth := autorest.NewBearerAuthorizer(&token)
   200  		registriesClient := containerregistry.NewRegistriesClient(subscription.ID)
   201  		registriesClient.Authorizer = auth
   202  		registriesClient.AddToUserAgent(containerregistry.UserAgent())
   203  		buildsClient := containerregistry.NewBuildsClient(subscription.ID)
   204  		buildsClient.Authorizer = auth
   205  		buildsClient.AddToUserAgent(containerregistry.UserAgent())
   206  		cb = &azurecontainerbuilder.Builder{
   207  			RegistryClient: registriesClient,
   208  			BuildsClient:   buildsClient,
   209  			AdalToken:      token,
   210  			Subscription:   subscription,
   211  		}
   212  	default:
   213  		// setup docker
   214  		cli := &command.DockerCli{}
   215  		if err := cli.Initialize(u.dockerClientOptions); err != nil {
   216  			return fmt.Errorf("failed to create docker client: %v", err)
   217  		}
   218  		cb = &dockercontainerbuilder.Builder{
   219  			DockerClient: cli,
   220  		}
   221  	}
   222  	bldr.ContainerBuilder = cb
   223  
   224  	// setup kube
   225  	bldr.Kube, kubeConfig, err = getKubeClient(kubeContext)
   226  	if err != nil {
   227  		return fmt.Errorf("Could not get a kube client: %s", err)
   228  	}
   229  	bldr.Helm, err = setupHelm(bldr.Kube, kubeConfig, tillerNamespace)
   230  	if err != nil {
   231  		return fmt.Errorf("Could not get a helm client: %s", err)
   232  	}
   233  
   234  	// setup the storage engine
   235  	bldr.Storage = configmap.NewConfigMaps(bldr.Kube.CoreV1().ConfigMaps(tillerNamespace))
   236  	progressC := bldr.Up(ctx, buildctx)
   237  	cmdline.Display(ctx, buildctx.Env.Name, progressC, cmdline.WithBuildID(bldr.ID))
   238  
   239  	if buildctx.Env.AutoConnect || autoConnect {
   240  		c := newConnectCmd(u.out)
   241  		return c.RunE(c, []string{})
   242  	}
   243  
   244  	if err := runPostDeployTasks(taskList, bldr.ID); err != nil {
   245  		debug(err.Error())
   246  		return nil
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  func runPostDeployTasks(taskList *tasks.Tasks, buildID string) error {
   253  	if taskList == nil || len(taskList.PostDeploy) == 0 {
   254  		return errors.New("No post deploy tasks to run")
   255  	}
   256  
   257  	app, err := local.DeployedApplication(draftToml, runningEnvironment)
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	client, _, err := getKubeClient(kubeContext)
   263  	if err != nil {
   264  		return err
   265  	}
   266  
   267  	names, err := app.GetPodNames(buildID, client)
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	for _, name := range names {
   273  		_, err := taskList.Run(tasks.DefaultRunner, tasks.PostDeploy, name)
   274  		if err != nil {
   275  			debug("error running task: %v", err)
   276  		}
   277  	}
   278  
   279  	return nil
   280  }
   281  
   282  func getSubscriptionFromProfile() (azurecli.Subscription, error) {
   283  	profilePath, err := azurecli.ProfilePath()
   284  	if err != nil {
   285  		return azurecli.Subscription{}, err
   286  	}
   287  	profile, err := azurecli.LoadProfile(profilePath)
   288  	if err != nil {
   289  		return azurecli.Subscription{}, err
   290  	}
   291  	for _, sub := range profile.Subscriptions {
   292  		if sub.IsDefault {
   293  			return sub, nil
   294  		}
   295  	}
   296  	return azurecli.Subscription{}, fmt.Errorf("could not find a default subscription ID from %s", profilePath)
   297  }