github.com/leowmjw/otto@v0.2.1-0.20160126165905-6400716cf085/helper/terraform/foundation.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "path/filepath" 6 7 "github.com/hashicorp/otto/directory" 8 "github.com/hashicorp/otto/foundation" 9 ) 10 11 // Foundation is a helper for various operations a foundation must 12 // perform with Terraform. 13 type Foundation struct { 14 // Dir is the directory where Terraform is run. If this isn't set, it'll 15 // default to "#{ctx.Dir}/deploy". 16 Dir string 17 } 18 19 // Infra manages a foundation using Terraform. 20 // 21 // This will verify the infrastruction is created and use that information 22 // to execute Terraform with the options given. 23 // 24 // This function implements foundation.Foundation.Infra. 25 func (f *Foundation) Infra(ctx *foundation.Context) error { 26 switch ctx.Action { 27 case "": 28 if err := f.execute(ctx, "get", "."); err != nil { 29 return err 30 } 31 32 return f.execute(ctx, "apply") 33 case "destroy": 34 if err := f.execute(ctx, "get", "."); err != nil { 35 return err 36 } 37 38 return f.execute(ctx, "destroy", "-force") 39 default: 40 return fmt.Errorf("unknown action: %s", ctx.Action) 41 } 42 } 43 44 func (f *Foundation) execute(ctx *foundation.Context, args ...string) error { 45 project, err := Project(&ctx.Shared) 46 if err != nil { 47 return err 48 } 49 50 appInfra := ctx.Appfile.ActiveInfrastructure() 51 52 // Foundations themselves are represented as infrastructure in the 53 // backend. Let's look that up. If it doesn't exist, we have to create 54 // it in order to get our UUID for storing state. 55 lookup := directory.Lookup{Infra: appInfra.Type, Foundation: ctx.Tuple.Type} 56 foundationInfra, err := ctx.Directory.GetInfra(&directory.Infra{Lookup: lookup}) 57 if err != nil { 58 return fmt.Errorf( 59 "Error looking up existing infrastructure data: %s\n\n"+ 60 "These errors are usually transient and can be fixed by retrying\n"+ 61 "the command. Additional causes of errors are networking or disk\n"+ 62 "issues that can be resolved external to Otto.", 63 err) 64 } 65 if foundationInfra == nil { 66 // If we don't have an infra, create one 67 foundationInfra = &directory.Infra{Lookup: lookup} 68 foundationInfra.State = directory.InfraStatePartial 69 70 // Put the infrastructure so we can get the UUID to use for our state 71 if err := ctx.Directory.PutInfra(foundationInfra); err != nil { 72 return fmt.Errorf( 73 "Error preparing infrastructure: %s\n\n"+ 74 "These errors are usually transient and can be fixed by retrying\n"+ 75 "the command. Additional causes of errors are networking or disk\n"+ 76 "issues that can be resolved external to Otto.", 77 err) 78 } 79 } 80 81 // Get the infrastructure state. The infrastructure must be 82 // created for us to deploy to it. 83 infra, err := ctx.Directory.GetInfra(&directory.Infra{ 84 Lookup: directory.Lookup{Infra: appInfra.Name}}) 85 if err != nil { 86 return err 87 } 88 if infra == nil || infra.State != directory.InfraStateReady { 89 return fmt.Errorf( 90 "Infrastructure for this application hasn't been built yet.\n" + 91 "Building a foundation requires a target infrastruction to\n" + 92 "be built. Please run `otta infra` to build the underlying\n" + 93 "infrastructure.") 94 } 95 96 // Construct the variables for Terraform from our queried infra 97 vars := make(map[string]string) 98 for k, v := range infra.Outputs { 99 vars[k] = v 100 } 101 for k, v := range ctx.InfraCreds { 102 vars[k] = v 103 } 104 105 // Get the directory 106 tfDir := f.Dir 107 if tfDir == "" { 108 tfDir = filepath.Join(ctx.Dir, "deploy") 109 } 110 111 // Run Terraform! 112 tf := &Terraform{ 113 Path: project.Path(), 114 Dir: tfDir, 115 Ui: ctx.Ui, 116 Variables: vars, 117 Directory: ctx.Directory, 118 StateId: foundationInfra.ID, 119 } 120 err = tf.Execute(args...) 121 if err != nil { 122 return fmt.Errorf( 123 "Error running Terraform: %s\n\n"+ 124 "Terraform usually has helpful error messages. Please read the error\n"+ 125 "messages above and resolve them. Sometimes simply re-running the\n"+ 126 "command again will work.", 127 err) 128 } 129 130 ctx.Ui.Header("Terraform execution complete. Saving results...") 131 if err == nil { 132 if ctx.Action == "destroy" { 133 // If we just destroyed successfully, the infra is now empty. 134 foundationInfra.State = directory.InfraStateInvalid 135 foundationInfra.Outputs = map[string]string{} 136 } else { 137 // If an apply was successful, populate the state and outputs. 138 foundationInfra.State = directory.InfraStateReady 139 foundationInfra.Outputs, err = tf.Outputs() 140 if err != nil { 141 err = fmt.Errorf("Error reading Terraform outputs: %s", err) 142 foundationInfra.State = directory.InfraStatePartial 143 } 144 } 145 } 146 147 // Save the infrastructure information 148 if err := ctx.Directory.PutInfra(foundationInfra); err != nil { 149 return fmt.Errorf( 150 "Error storing infrastructure data: %s\n\n"+ 151 "This means that Otto won't be able to know that your infrastructure\n"+ 152 "was successfully created. Otto tries a few times to save the\n"+ 153 "infrastructure. At this point in time, Otto doesn't support gracefully\n"+ 154 "recovering from this error. Your infrastructure is now orphaned from\n"+ 155 "Otto's management. Please reference the community for help.\n\n"+ 156 "A future version of Otto will resolve this.", 157 err) 158 } 159 160 return err 161 }