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 }
// 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 }