github.com/mweagle/Sparta@v1.15.0/docs_source/content/reference/application/environments.md (about) 1 --- 2 date: 2016-03-09T19:56:50+01:00 3 title: Managing Environments 4 weight: 10 5 --- 6 7 It's common for a single Sparta application to target multiple *environments*. For example: 8 9 * Development 10 * Staging 11 * Production 12 13 Each environment is largely similar, but the application may need slightly different configuration in each context. 14 15 To support this, Sparta uses Go's [conditional compilation](http://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool) support to ensure that configuration information is validated at build time. Conditional compilation is supported via the `--tags/-t` command line argument. 16 17 This example will work through the [SpartaConfig](https://github.com/mweagle/SpartaConfig) sample. The requirement is that each environment declare it's `Name` and also add that value as a Stack [Output](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html). 18 19 ## Default Configuration 20 21 To start with, create the _default_ configuration. This is the configuration that Sparta uses when provisioning your Stack and defines the environment configuration contract. 22 23 ```go 24 // +build !staging,!production 25 // file: environments/default.go 26 27 package environments 28 29 import ( 30 "fmt" 31 "github.com/Sirupsen/logrus" 32 "github.com/aws/aws-sdk-go/aws/session" 33 gocf "github.com/crewjam/go-cloudformation" 34 sparta "github.com/mweagle/Sparta" 35 ) 36 37 // Name is the default configuration 38 const Name = "" 39 ``` 40 41 The important part is the set of excluded tags at the top of the file: 42 43 ```go 44 // +build !staging,!production 45 ``` 46 47 This ensures that the configuration is only eligible for compilation when Sparta goes to `provision` the service. 48 49 ## Environment Configuration 50 51 The next steps are to define the environment-specific configuration files: 52 53 ```go 54 // +build staging 55 // file: environments/staging.go 56 57 package environments 58 59 import ( 60 "github.com/Sirupsen/logrus" 61 "github.com/aws/aws-sdk-go/aws/session" 62 gocf "github.com/crewjam/go-cloudformation" 63 sparta "github.com/mweagle/Sparta" 64 ) 65 66 // Name is the production configuration 67 const Name = "staging" 68 69 ``` 70 71 72 ```go 73 // +build production 74 // file: environments/production.go 75 76 package environments 77 78 import ( 79 "github.com/Sirupsen/logrus" 80 "github.com/aws/aws-sdk-go/aws/session" 81 gocf "github.com/crewjam/go-cloudformation" 82 sparta "github.com/mweagle/Sparta" 83 ) 84 85 // Name is the production configuration 86 const Name = "production" 87 88 ``` 89 90 These three files define the set of compile-time mutually-exclusive sources that represent environment targets. 91 92 ## Segregating Services 93 94 The `serviceName` argument supplied to [sparta.Main](https://godoc.org/github.com/mweagle/Sparta#Main) defines the AWS CloudFormation stack that supports your application. While the previous files represent different environments, they will collide at `provision` time since they share the same service name. 95 96 The `serviceName` can be specialized by using the `buildTags` in the service name definition as in: 97 98 ```go 99 fmt.Sprintf("SpartaHelloWorld-%s", sparta.OptionsGlobal.BuildTags), 100 ``` 101 102 Each time you run `provision` with a unique `--tags` value, a new CloudFormation stack will be created. 103 104 *NOTE*: This isn't something suitable for production use as there could be multiple `BuildTags` values. 105 106 ## Enforcing Environments 107 108 The final requirement is to add the environment name as a Stack Output. To annotate the stack with the output value, we'll register a [ServiceDecorator](https://godoc.org/github.com/mweagle/Sparta#ServiceDecoratorHook) and use the same conditional compilation support to compile the environment-specific version. 109 110 The _main.go_ source file registers the workflow hook via: 111 112 ```go 113 hooks := &sparta.WorkflowHooks{ 114 Context: map[string]interface{}{}, 115 ServiceDecorator: environments.ServiceDecoratorHook(sparta.OptionsGlobal.BuildTags), 116 } 117 ``` 118 119 Both _environments/staging.go_ and _environments/production.go_ define the same hook function: 120 121 ```go 122 func ServiceDecoratorHook(buildTags string) sparta.ServiceDecoratorHook { 123 return func(context map[string]interface{}, 124 serviceName string, 125 template *gocf.Template, 126 S3Bucket string, 127 S3Key string, 128 buildID string, 129 awsSession *session.Session, 130 noop bool, 131 logger *logrus.Logger) error { 132 template.Outputs["Environment"] = &gocf.Output{ 133 Description: "Sparta Config target environment", 134 Value: Name, 135 } 136 return nil 137 } 138 } 139 ``` 140 141 The _environments/default.go_ definition is slightly different. The "default" environment isn't one that our service should actually deploy to. It simply exists to make the initial Sparta build (the one that cross compiles the AWS Lambda binary) compile. Build tags are applied to the *AWS Lambda* compiled binary that Sparta generates. 142 143 To prevent users from accidentally deploying to the "default" environment, the `BuildTags` are validated in the hook definition: 144 145 ```go 146 func ServiceDecoratorHook(buildTags string) sparta.ServiceDecoratorHook { 147 return func(context map[string]interface{}, 148 serviceName string, 149 template *gocf.Template, 150 S3Bucket string, 151 S3Key string, 152 buildID string, 153 awsSession *session.Session, 154 noop bool, 155 logger *logrus.Logger) error { 156 if len(buildTags) <= 0 { 157 return fmt.Errorf("Please provide a --tags value for environment target") 158 } 159 return nil 160 } 161 } 162 ``` 163 164 ## Provisioning 165 166 Putting everything together, the `SpartaConfig` service can deploy to either environment: 167 168 **staging** 169 170 go run main.go provision --level info --s3Bucket $(S3_BUCKET) --noop --tags staging 171 172 **production** 173 174 go run main.go provision --level info --s3Bucket $(S3_BUCKET) --noop --tags production 175 176 Attempting to deploy to "default" generates an error: 177 178 ```bash 179 INFO[0000] Welcome to SpartaConfig- Go=go1.7.1 Option=provision SpartaVersion=0.9.2 UTC=2016-10-12T04:07:35Z 180 INFO[0000] Provisioning service BuildID=550c9e360426f48201c885c0abeb078dfc000a0a NOOP=true Tags= 181 INFO[0000] Verifying IAM Lambda execution roles 182 INFO[0000] IAM roles verified Count=1 183 INFO[0000] Running `go generate` 184 INFO[0000] Compiling binary Name=SpartaConfig_.lambda.amd64 185 INFO[0008] Executable binary size KB=15309 MB=14 186 INFO[0008] Creating ZIP archive for upload TempName=/Users/mweagle/Documents/gopath/src/github.com/mweagle/SpartaConfig/SpartaConfig_104207098 187 INFO[0009] Registering Sparta function FunctionName=main.helloWorld 188 INFO[0009] Lambda function deployment package size KB=4262 MB=4 189 INFO[0009] Bypassing bucket expiration policy check due to -n/-noop command line argument BucketName=weagle 190 INFO[0009] Bypassing S3 upload due to -n/-noop command line argument Bucket=weagle Key=SpartaConfig-/SpartaConfig_104207098 191 INFO[0009] Calling WorkflowHook WorkflowHook=github.com/mweagle/SpartaConfig/environments.ServiceDecoratorHook.func1 WorkflowHookContext=map[] 192 INFO[0009] Invoking rollback functions RollbackCount=0 193 ERRO[0009] Please provide a --tags value for environment target 194 Error: Please provide a --tags value for environment target 195 Usage: 196 main provision [flags] 197 198 Flags: 199 -i, --buildID string Optional BuildID to use 200 -s, --s3Bucket string S3 Bucket to use for Lambda source 201 -t, --tags string Optional build tags to use for compilation 202 203 Global Flags: 204 -l, --level string Log level [panic, fatal, error, warn, info, debug] (default "info") 205 -n, --noop Dry-run behavior only (do not perform mutations) 206 207 ERRO[0009] Please provide a --tags value for environment target 208 exit status 1 209 ``` 210 211 ## Notes 212 213 - Call [ParseOptions](https://godoc.org/github.com/mweagle/Sparta#ParseOptions) to initialize `sparta.OptionsGlobal.BuildTags` field for use in a service name definition. 214 - An alternative approach is to define a custom [ArchiveHook](https://godoc.org/github.com/mweagle/Sparta#ArchiveHook) and inject custom configuration into the ZIP archive. This data is available at `Path.Join(env.LAMBDA_TASK_ROOT, ZIP_ARCHIVE_PATH)` 215 - See [discfg](https://github.com/tmaiaroto/discfg), [etcd](https://github.com/coreos/etcd), [Consul](https://www.consul.io/) (among others) for alternative, more dynamic discovery services.