Esempio n. 1
0
func (opts *DeployOptions) actionDeploy(rctx router.Context) error {
	ctx := rctx.(*app.Context)
	project, err := Project(&ctx.Shared)
	if err != nil {
		return err
	}
	vars := make(map[string]string)

	infra, infraVars, err := opts.lookupInfraVars(ctx)
	if err != nil {
		return err
	}
	if infra == nil {
		return fmt.Errorf(
			"Infrastructure for this application hasn't been built yet.\n" +
				"The deploy step requires this because the target infrastructure\n" +
				"as well as its final properties can affect the deploy process.\n" +
				"Please run `otto infra` to build the underlying infrastructure,\n" +
				"then run `otto deploy` again.")
	}
	for k, v := range infraVars {
		vars[k] = v
	}

	if !opts.DisableBuild {
		buildVars, err := opts.lookupBuildVars(ctx, infra)
		if err != nil {
			return err
		}
		if buildVars == nil {
			return fmt.Errorf(
				"This application hasn't been built yet. Please run `otto build`\n" +
					"first so that the deploy step has an artifact to deploy.")
		}
		for k, v := range buildVars {
			vars[k] = v
		}
	}

	// Setup the vars
	if err := foundation.WriteVars(&ctx.Shared); err != nil {
		return fmt.Errorf("Error preparing deploy: %s", err)
	}

	// Get our old deploy to populate the old state data if we have it.
	// This step is critical to make sure that Terraform remains idempotent
	// and that it handles migrations properly.
	deploy, err := opts.lookupDeploy(ctx)
	if err != nil {
		return err
	}

	// Run Terraform!
	tf := &Terraform{
		Path:      project.Path(),
		Dir:       opts.tfDir(ctx),
		Ui:        ctx.Ui,
		Variables: vars,
		Directory: ctx.Directory,
		StateId:   deploy.ID,
	}
	if err := tf.Execute("apply"); err != nil {
		deploy.MarkFailed()
		if putErr := ctx.Directory.PutDeploy(deploy); putErr != nil {
			return fmt.Errorf("The deploy failed with err: %s\n\n"+
				"And then there was an error storing it in the directory: %s\n"+
				"This second error is a bug and should be reported.", err, putErr)
		}

		return terraformError(err)
	}

	deploy.MarkSuccessful()
	if err := ctx.Directory.PutDeploy(deploy); err != nil {
		return err
	}
	return nil
}
Esempio n. 2
0
// Build can be used to build an artifact with Packer and parse the
// artifact out into a Build properly.
//
// This function automatically knows how to parse various built-in
// artifacts of Packer. For the exact functionality of the parse
// functions, see the documentation of the various parse functions.
//
// This function implements the app.App.Build function.
// TODO: Test
func Build(ctx *app.Context, opts *BuildOptions) error {
	project := Project(&ctx.Shared)
	if err := project.InstallIfNeeded(); err != nil {
		return err
	}

	ctx.Ui.Header("Querying infrastructure data for build...")

	// Get the infrastructure, since it needs to be ready for building
	// to occur. We'll copy the outputs and the credentials as variables
	// to Packer.
	infra, err := ctx.Directory.GetInfra(&directory.Infra{
		Lookup: directory.Lookup{
			Infra: ctx.Appfile.ActiveInfrastructure().Name}})
	if err != nil {
		return err
	}

	// If the infra isn't ready then we can't build
	if infra == nil || infra.State != directory.InfraStateReady {
		return fmt.Errorf(
			"Infrastructure for this application hasn't been built yet.\n" +
				"The build step requires this because the target infrastructure\n" +
				"as well as its final properties can affect the build process.\n" +
				"Please run `otto infra` to build the underlying infrastructure,\n" +
				"then run `otto build` again.")
	}

	// Construct the variables for Packer from the infra. We copy them as-is.
	vars := make(map[string]string)
	for k, v := range infra.Outputs {
		if opts.InfraOutputMap != nil {
			if nk, ok := opts.InfraOutputMap[k]; ok {
				k = nk
			}
		}

		vars[k] = v
	}
	for k, v := range ctx.InfraCreds {
		vars[k] = v
	}

	// Setup the vars
	if err := foundation.WriteVars(&ctx.Shared); err != nil {
		return fmt.Errorf("Error preparing build: %s", err)
	}

	ctx.Ui.Header("Building deployment archive...")
	slugPath, err := createAppSlug(filepath.Dir(ctx.Appfile.Path))
	if err != nil {
		return err
	}
	vars["slug_path"] = slugPath

	// Start building the resulting build
	build := &directory.Build{
		Lookup: directory.Lookup{
			AppID:       ctx.Appfile.ID,
			Infra:       ctx.Tuple.Infra,
			InfraFlavor: ctx.Tuple.InfraFlavor,
		},

		Artifact: make(map[string]string),
	}

	// Get the paths for Packer execution
	packerDir := opts.Dir
	templatePath := opts.TemplatePath
	if opts.Dir == "" {
		packerDir = filepath.Join(ctx.Dir, "build")
	}
	if opts.TemplatePath == "" {
		templatePath = filepath.Join(packerDir, "template.json")
	}

	ctx.Ui.Header("Building deployment artifact with Packer...")
	ctx.Ui.Message(
		"Raw Packer output will begin streaming in below. Otto\n" +
			"does not create this output. It is mirrored directly from\n" +
			"Packer while the build is being run.\n\n")

	// Build and execute Packer
	p := &Packer{
		Path:      project.Path(),
		Dir:       packerDir,
		Ui:        ctx.Ui,
		Variables: vars,
		Callbacks: map[string]OutputCallback{
			"artifact": ParseArtifactAmazon(build.Artifact),
		},
	}
	if err := p.Execute("build", templatePath); err != nil {
		return err
	}

	// Store the build!
	ctx.Ui.Header("Storing build data in directory...")
	if err := ctx.Directory.PutBuild(build); err != nil {
		return fmt.Errorf(
			"Error storing the build in the directory service: %s\n\n"+
				"Despite the build itself completing successfully, Otto must\n"+
				"also successfully store the results in the directory service\n"+
				"to be able to deploy this build. Please fix the above error and\n"+
				"rebuild.",
			err)
	}

	ctx.Ui.Header("[green]Build success!")
	ctx.Ui.Message(
		"[green]The build was completed successfully and stored within\n" +
			"the directory service, meaning other members of your team\n" +
			"don't need to rebuild this same version and can deploy it\n" +
			"immediately.")

	return nil
}