github.com/leowmjw/otto@v0.2.1-0.20160126165905-6400716cf085/builtin/app/custom/app.go (about) 1 package custom 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/hashicorp/otto/app" 11 "github.com/hashicorp/otto/appfile" 12 "github.com/hashicorp/otto/foundation" 13 "github.com/hashicorp/otto/helper/bindata" 14 "github.com/hashicorp/otto/helper/compile" 15 "github.com/hashicorp/otto/helper/oneline" 16 "github.com/hashicorp/otto/helper/packer" 17 "github.com/hashicorp/otto/helper/schema" 18 "github.com/hashicorp/otto/helper/terraform" 19 "github.com/hashicorp/otto/helper/vagrant" 20 ) 21 22 //go:generate go-bindata -pkg=custom -nomemcopy -nometadata ./data/... 23 24 // App is an implementation of app.App 25 type App struct{} 26 27 func (a *App) Meta() (*app.Meta, error) { 28 return Meta, nil 29 } 30 31 func (a *App) Implicit(ctx *app.Context) (*appfile.File, error) { 32 return nil, nil 33 } 34 35 func (a *App) Compile(ctx *app.Context) (*app.CompileResult, error) { 36 fragmentPath := filepath.Join(ctx.Dir, "dev-dep", "Vagrantfile.fragment") 37 38 var opts compile.AppOptions 39 custom := &customizations{Opts: &opts} 40 opts = compile.AppOptions{ 41 Ctx: ctx, 42 Bindata: &bindata.Data{ 43 Asset: Asset, 44 AssetDir: AssetDir, 45 Context: map[string]interface{}{ 46 "fragment_path": fragmentPath, 47 }, 48 }, 49 FoundationConfig: foundation.Config{ 50 ServiceName: ctx.Application.Name, 51 }, 52 Customization: &compile.Customization{ 53 Callback: custom.process, 54 Schema: map[string]*schema.FieldSchema{ 55 "dev_vagrantfile": &schema.FieldSchema{ 56 Type: schema.TypeString, 57 Description: "Path to Vagrantfile", 58 }, 59 60 "dep_vagrantfile": &schema.FieldSchema{ 61 Type: schema.TypeString, 62 Description: "Path to Vagrantfile template", 63 }, 64 65 "packer": &schema.FieldSchema{ 66 Type: schema.TypeString, 67 Description: "Path to Packer template", 68 }, 69 70 "terraform": &schema.FieldSchema{ 71 Type: schema.TypeString, 72 Description: "Path to a Terraform module", 73 }, 74 }, 75 }, 76 } 77 78 return compile.App(&opts) 79 } 80 81 func (a *App) Build(ctx *app.Context) error { 82 // Determine if we set a Packer path. 83 path := filepath.Join(ctx.Dir, "build", "packer_path") 84 if _, err := os.Stat(path); err != nil { 85 if os.IsNotExist(err) { 86 return errors.New(strings.TrimSpace(errPackerNotSet)) 87 } 88 89 return err 90 } 91 92 // Read the actual Packer dir 93 packerPath, err := oneline.Read(path) 94 if err != nil { 95 return fmt.Errorf( 96 "Error reading the Packer template path: %s\n\n"+ 97 "An Otto recompile with `otto compile` usually fixes this.", 98 err) 99 } 100 101 return packer.Build(ctx, &packer.BuildOptions{ 102 Dir: filepath.Dir(packerPath), 103 TemplatePath: packerPath, 104 }) 105 } 106 107 func (a *App) Deploy(ctx *app.Context) error { 108 // Determine if we set a Terraform path. If we didn't, then 109 // tell the user we can't deploy. 110 path := filepath.Join(ctx.Dir, "deploy", "terraform_path") 111 if _, err := os.Stat(path); err != nil { 112 if os.IsNotExist(err) { 113 return errors.New(strings.TrimSpace(errTerraformNotSet)) 114 } 115 116 return err 117 } 118 119 // Read the actual TF dir 120 tfdir, err := oneline.Read(path) 121 if err != nil { 122 return fmt.Errorf( 123 "Error reading the Terraform module directory: %s\n\n"+ 124 "An Otto recompile with `otto compile` usually fixes this.", 125 err) 126 } 127 128 // Determine if we set a Packer path. If we didn't, we disable 129 // the build loading for deploys. 130 disableBuild := true 131 path = filepath.Join(ctx.Dir, "build", "packer_path") 132 if _, err := os.Stat(path); err == nil { 133 disableBuild = false 134 } 135 136 // But if we did, then deploy using Terraform 137 return terraform.Deploy(&terraform.DeployOptions{ 138 Dir: tfdir, 139 DisableBuild: disableBuild, 140 }).Route(ctx) 141 } 142 143 func (a *App) Dev(ctx *app.Context) error { 144 // Determine if we have a Vagrant path set... 145 instructions := devInstructionsCustom 146 path := filepath.Join(ctx.Dir, "dev", "vagrant_path") 147 if _, err := os.Stat(path); err != nil { 148 if !os.IsNotExist(err) { 149 return err 150 } 151 152 path = "" 153 } 154 if path != "" { 155 var err error 156 path, err = oneline.Read(path) 157 if err != nil { 158 return fmt.Errorf( 159 "Error reading the Vagrant directory: %s\n\n"+ 160 "An Otto recompile with `otto compile` usually fixes this.", 161 err) 162 } 163 } 164 165 if path == "" { 166 // Determine if we have our own Vagrantfile 167 path = filepath.Join(ctx.Dir, "dev", "Vagrantfile") 168 if _, err := os.Stat(path); err != nil { 169 if os.IsNotExist(err) { 170 return errors.New(strings.TrimSpace(errVagrantNotSet)) 171 } 172 173 return err 174 } 175 176 instructions = devInstructionsDevDep 177 } 178 179 return vagrant.Dev(&vagrant.DevOptions{ 180 Dir: filepath.Dir(path), 181 Instructions: strings.TrimSpace(instructions), 182 }).Route(ctx) 183 } 184 185 func (a *App) DevDep(dst, src *app.Context) (*app.DevDep, error) { 186 // Determine if we have a Vagrantfile. This is a sentinel that 187 // we set this setting. 188 path := filepath.Join(src.Dir, "dev", "Vagrantfile") 189 if _, err := os.Stat(path); err != nil { 190 if os.IsNotExist(err) { 191 return nil, errors.New(strings.TrimSpace(errVagrantNotSet)) 192 } 193 194 return nil, err 195 } 196 197 // We purposely return nil here. We don't need to do anything. It 198 // is all already setup from the compilation step. 199 return nil, nil 200 } 201 202 const devInstructionsCustom = ` 203 Vagrant was executed in the directory specified by the "dev" 204 customization. 205 ` 206 207 const devInstructionsDevDep = ` 208 A development has been created. 209 210 Note that this development environment is just an example of what a 211 consumer of this application might see as a development dependency. 212 "Custom" types are not meant to be mutably developed like normal 213 applications. 214 ` 215 216 const errPackerNotSet = ` 217 Otto can't build this application because the "packer" setting 218 isn't set in the "build" customization. 219 220 For the "custom" application type, the "build" customization must 221 set the "packer" setting to point to a Packer template to execute. 222 Otto will execute this for the build. 223 224 Example: 225 226 customization "build" { 227 packer = "path/to/template.json" 228 } 229 230 ` 231 232 const errTerraformNotSet = ` 233 Otto can't deploy this application because the "terraform" setting 234 hasn't been set in the "deploy" customization. 235 236 For the "custom" application type, the "deploy" customization must 237 set the "terraform" setting to point to a Terraform module to execute. 238 Otto will execute this module for the deploy. 239 240 Example: 241 242 customization "deploy" { 243 terraform = "path/to/module" 244 } 245 246 ` 247 248 const errVagrantNotSet = ` 249 Otto can't build a development environment for this because the 250 "vagrant" setting hasn't been set in the "dev" or "dev-dep" customization. 251 252 For the "custom" application type, customizations must be used to 253 tell Otto what to do. For the dev command, Otto requires either 254 the "dev" or "dev-dep" customization to be set with the "vagrant" setting. 255 The "vagrant" setting depends on which customization is being set. Please 256 refer to the documentation for more details. 257 258 Example: 259 260 customization "dev" { 261 vagrantfile = "path/to/Vagrantfile" 262 } 263 264 `