1 /* 2 * Copyright (c) 2016 ingenieux Labs 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package br.com.ingenieux.mojo.beanstalk.env; 18 19 import com.amazonaws.services.elasticbeanstalk.model.ConfigurationOptionSetting; 20 import com.amazonaws.services.elasticbeanstalk.model.CreateEnvironmentResult; 21 import com.amazonaws.services.elasticbeanstalk.model.OptionSpecification; 22 23 import org.apache.commons.lang.Validate; 24 import org.apache.maven.plugin.AbstractMojoExecutionException; 25 import org.apache.maven.plugins.annotations.Mojo; 26 import org.apache.maven.plugins.annotations.Parameter; 27 28 import java.util.Arrays; 29 import java.util.Collections; 30 import java.util.LinkedHashMap; 31 import java.util.Map; 32 import java.util.Properties; 33 34 import br.com.ingenieux.mojo.beanstalk.AbstractNeedsEnvironmentMojo; 35 import br.com.ingenieux.mojo.beanstalk.cmd.env.create.CreateEnvironmentCommand; 36 import br.com.ingenieux.mojo.beanstalk.cmd.env.create.CreateEnvironmentContext; 37 import br.com.ingenieux.mojo.beanstalk.cmd.env.create.CreateEnvironmentContextBuilder; 38 import br.com.ingenieux.mojo.beanstalk.cmd.env.waitfor.WaitForEnvironmentCommand; 39 import br.com.ingenieux.mojo.beanstalk.cmd.env.waitfor.WaitForEnvironmentContext; 40 import br.com.ingenieux.mojo.beanstalk.cmd.env.waitfor.WaitForEnvironmentContextBuilder; 41 42 import static java.lang.String.format; 43 import static org.apache.commons.lang.StringUtils.isBlank; 44 import static org.apache.commons.lang.StringUtils.isNotBlank; 45 import static org.codehaus.plexus.util.StringUtils.defaultString; 46 47 /** 48 * Creates and Launches an Elastic Beanstalk Environment <p> See the docs for the <a href= 49 * "http://docs.amazonwebservices.com/elasticbeanstalk/latest/api/API_CreateEnvironment.html" 50 * >CreateEnvironment API</a> call. 51 * 52 * @since 0.1.0 53 */ 54 @Mojo(name = "create-environment") 55 public class CreateEnvironmentMojo extends AbstractNeedsEnvironmentMojo { 56 57 public static final String BEANSTALK_TAG_PREFIX = "beanstalk.tag."; 58 59 /** 60 * environmentName. Takes precedence over environmentRef. 61 */ 62 @Parameter(property = "beanstalk.environmentName", required = true) 63 protected String environmentName; 64 /** 65 * Application Description 66 */ 67 @Parameter(property = "beanstalk.applicationDescription", defaultValue = "${project.name}") 68 String applicationDescription; 69 /** 70 * <p> Configuration Option Settings. Will evaluate as such: </p> <p> <p> If empty, will lookup 71 * for beanstalk.env.aws.x.y variable in the context, and it will map this variable to namespace 72 * [aws.x], under option name y, unless there's an alias set. </p> <p> <p>A Property might be 73 * aliased. Current aliases include:</p> <p> <ul> <li>beanstalk.scalingAvailabilityZones, to 74 * aws:autoscaling:asg/Availability Zones</li> <li>beanstalk.scalingCooldown, to 75 * aws:autoscaling:asg/Cooldown</li> <li>beanstalk.scalingCustomAvailabilityZones, to 76 * aws:autoscaling:asg/Custom Availability Zones</li> <li>beanstalk.scalingMinSize, to 77 * aws:autoscaling:asg/MinSize</li> <li>beanstalk.scalingMaxSize, to 78 * aws:autoscaling:asg/MaxSize</li> <p> <li>beanstalk.keyName, to 79 * aws:autoscaling:launchconfiguration/EC2KeyName (EC2 Instance Key)</li> 80 * <li>beanstalk.iamInstanceProfile, to aws:autoscaling:launchconfiguration/IamInstanceProfile 81 * (IAM Instance Profile Role Name)</li> <li>beanstalk.imageId, to 82 * aws:autoscaling:launchconfiguration/ImageId</li> <li>beanstalk.instanceType, to 83 * aws:autoscaling:launchconfiguration/InstanceType (EC2 Instance Type to Use)</li> 84 * <li>beanstalk.monitoringInterval, to aws:autoscaling:launchconfiguration/MonitoringInterval</li> 85 * <li>beanstalk.securityGroups, to aws:autoscaling:launchconfiguration/SecurityGroups</li> 86 * <li>beanstalk.sshSourceRestriction, to aws:autoscaling:launchconfiguration/SSHSourceRestriction</li> 87 * <li>beanstalk.blockDeviceMappings, to aws:autoscaling:launchconfiguration/BlockDeviceMappings</li> 88 * <li>beanstalk.rootVolumeType, to aws:autoscaling:launchconfiguration/RootVolumeType</li> 89 * <li>beanstalk.rootVolumeSize, to aws:autoscaling:launchconfiguration/RootVolumeSize</li> 90 * <li>beanstalk.rootVolumeIOPS, to aws:autoscaling:launchconfiguration/RootVolumeIOPS</li> <p> 91 * <li>beanstalk.triggerBreachDuration, to aws:autoscaling:trigger/BreachDuration</li> 92 * <li>beanstalk.triggerLowerBreachScaleIncrement, to aws:autoscaling:trigger/LowerBreachScaleIncrement</li> 93 * <li>beanstalk.triggerLowerThreshold, to aws:autoscaling:trigger/LowerThreshold</li> 94 * <li>beanstalk.triggerMeasureName, to aws:autoscaling:trigger/MeasureName</li> 95 * <li>beanstalk.triggerPeriod, to aws:autoscaling:trigger/Period</li> 96 * <li>beanstalk.triggerStatistic, to aws:autoscaling:trigger/Statistic</li> 97 * <li>beanstalk.triggerUnit, to aws:autoscaling:trigger/Unit</li> 98 * <li>beanstalk.triggerUpperBreachScaleIncrement, to aws:autoscaling:trigger/UpperBreachScaleIncrement</li> 99 * <li>beanstalk.triggerUpperThreshold, to aws:autoscaling:trigger/UpperThreshold</li> <p> 100 * <li>beanstalk.rollingupdateMaxBatchSize, to aws:autoscaling:updatepolicy:rollingupdate/MaxBatchSize</li> 101 * <li>beanstalk.rollingupdateMinInstancesInService, to aws:autoscaling:updatepolicy:rollingupdate/MinInstancesInService</li> 102 * <li>beanstalk.rollingupdatePauseTime, to aws:autoscaling:updatepolicy:rollingupdate/PauseTime</li> 103 * <li>beanstalk.rollingupdateEnabled, to aws:autoscaling:updatepolicy:rollingupdate/RollingUpdateEnabled</li> 104 * <p> <li>beanstalk.vpcId, to aws:ec2:vpc/VPCId</li> <li>beanstalk.vpcSubnets, to 105 * aws:ec2:vpc/Subnets</li> <li>beanstalk.vpcELBSubnets, to aws:ec2:vpc/ELBSubnets</li> 106 * <li>beanstalk.vpcELBScheme, to aws:ec2:vpc/ELBScheme</li> <li>beanstalk.vpcDBSubnets, to 107 * aws:ec2:vpc/DBSubnets</li> <li>beanstalk.vpcAssociatePublicIpAddress, to 108 * aws:ec2:vpc/AssociatePublicIpAddress</li> <p> <li>beanstalk.applicationHealthCheckURL, to 109 * aws:elasticbeanstalk:application/Application Healthcheck URL (Application Healthcheck 110 * URL)</li> <p> <li>beanstalk.timeout, to aws:elasticbeanstalk:command/Timeout</li> <p> 111 * <li>beanstalk.environmentType, to aws:elasticbeanstalk:environment/EnvironmentType 112 * (SingleInstance or ELB-bound Environment)</li> <p> <li>beanstalk.automaticallyTerminateUnhealthyInstances, 113 * to aws:elasticbeanstalk:monitoring/Automatically Terminate Unhealthy Instances (true if 114 * should automatically terminate instances)</li> <p> <li>beanstalk.notificationEndpoint, to 115 * aws:elasticbeanstalk:sns:topics/Notification Endpoint</li> <li>beanstalk.notificationProtocol, 116 * to aws:elasticbeanstalk:sns:topics/Notification Protocol</li> <li>beanstalk.notificationTopicARN, 117 * to aws:elasticbeanstalk:sns:topics/Notification Topic ARN</li> <li>beanstalk.notificationTopicName, 118 * to aws:elasticbeanstalk:sns:topics/Notification Topic Name</li> <p> 119 * <li>beanstalk.sqsdWorkerQueueUrl, to aws:elasticbeanstalk:sqsd/WorkerQueueURL</li> 120 * <li>beanstalk.sqsdHttpPath, to aws:elasticbeanstalk:sqsd/HttpPath</li> 121 * <li>beanstalk.sqsdMimeType, to aws:elasticbeanstalk:sqsd/MimeType</li> 122 * <li>beanstalk.sqsdHttpConnections, to aws:elasticbeanstalk:sqsd/HttpConnections</li> 123 * <li>beanstalk.sqsdConnectTimeout, to aws:elasticbeanstalk:sqsd/ConnectTimeout</li> 124 * <li>beanstalk.sqsdInactivityTimeout, to aws:elasticbeanstalk:sqsd/InactivityTimeout</li> 125 * <li>beanstalk.sqsdVisibilityTimeout, to aws:elasticbeanstalk:sqsd/VisibilityTimeout</li> 126 * <li>beanstalk.sqsdRetentionPeriod, to aws:elasticbeanstalk:sqsd/RetentionPeriod</li> 127 * <li>beanstalk.sqsdMaxRetries, to aws:elasticbeanstalk:sqsd/MaxRetries</li> <p> 128 * <li>beanstalk.healthcheckHealthyThreshold, to aws:elb:healthcheck/HealthyThreshold</li> 129 * <li>beanstalk.healthcheckInterval, to aws:elb:healthcheck/Interval</li> 130 * <li>beanstalk.healthcheckTimeout, to aws:elb:healthcheck/Timeout</li> 131 * <li>beanstalk.healthcheckUnhealthyThreshold, to aws:elb:healthcheck/UnhealthyThreshold</li> 132 * <p> <li>beanstalk.loadBalancerHTTPPort, to aws:elb:loadbalancer/LoadBalancerHTTPPort</li> 133 * <li>beanstalk.loadBalancerPortProtocol, to aws:elb:loadbalancer/LoadBalancerPortProtocol</li> 134 * <li>beanstalk.loadBalancerHTTPSPort, to aws:elb:loadbalancer/LoadBalancerHTTPSPort</li> 135 * <li>beanstalk.loadBalancerSSLPortProtocol, to aws:elb:loadbalancer/LoadBalancerSSLPortProtocol</li> 136 * <li>beanstalk.loadBalancerSSLCertificateId, to aws:elb:loadbalancer/SSLCertificateId</li> 137 * <p> <li>beanstalk.stickinessCookieExpiration, to aws:elb:policies/Stickiness Cookie 138 * Expiration (Stickiness Cookie Expiration Timeout)</li> <li>beanstalk.stickinessPolicy, to 139 * aws:elb:policies/Stickiness Policy (ELB Stickiness Policy)</li> <p> 140 * <li>beanstalk.dbAllocatedStorage, to aws:rds:dbinstance/DBAllocatedStorage</li> 141 * <li>beanstalk.dbDeletionPolicy, to aws:rds:dbinstance/DBDeletionPolicy</li> 142 * <li>beanstalk.dbEngine, to aws:rds:dbinstance/DBEngine</li> <li>beanstalk.dbEngineVersion, to 143 * aws:rds:dbinstance/DBEngineVersion</li> <li>beanstalk.dbInstanceClass, to 144 * aws:rds:dbinstance/DBInstanceClass</li> <li>beanstalk.dbPassword, to 145 * aws:rds:dbinstance/DBPassword</li> <li>beanstalk.dbSnapshotIdentifier, to 146 * aws:rds:dbinstance/DBSnapshotIdentifier</li> <li>beanstalk.dbUser, to 147 * aws:rds:dbinstance/DBUser</li> <li>beanstalk.dbMultiAZDatabase, to 148 * aws:rds:dbinstance/MultiAZDatabase</li> <p> <li>beanstalk.environmentAwsSecretKey, to 149 * aws:elasticbeanstalk:application:environment/AWS_SECRET_KEY</li> 150 * <li>beanstalk.environmentAwsAccessKeyId, to aws:elasticbeanstalk:application:environment/AWS_ACCESS_KEY_ID</li> 151 * <li>beanstalk.environmentJdbcConnectionString, to aws:elasticbeanstalk:application:environment/JDBC_CONNECTION_STRING</li> 152 * <li>beanstalk.environmentParam1, to aws:elasticbeanstalk:application:environment/PARAM1</li> 153 * <li>beanstalk.environmentParam2, to aws:elasticbeanstalk:application:environment/PARAM2</li> 154 * <li>beanstalk.environmentParam3, to aws:elasticbeanstalk:application:environment/PARAM3</li> 155 * <li>beanstalk.environmentParam4, to aws:elasticbeanstalk:application:environment/PARAM4</li> 156 * <li>beanstalk.environmentParam5, to aws:elasticbeanstalk:application:environment/PARAM5</li> 157 * <p> <li>beanstalk.logPublicationControl, to aws:elasticbeanstalk:hostmanager/LogPublicationControl</li> 158 * <p> <li>beanstalk.jvmOptions, to aws:elasticbeanstalk:container:tomcat:jvmoptions/JVM 159 * Options</li> <li>beanstalk.jvmXmx, to aws:elasticbeanstalk:container:tomcat:jvmoptions/Xmx</li> 160 * <li>beanstalk.jvmMaxPermSize, to aws:elasticbeanstalk:container:tomcat:jvmoptions/XX:MaxPermSize</li> 161 * <li>beanstalk.jvmXms, to aws:elasticbeanstalk:container:tomcat:jvmoptions/Xms</li> <p> 162 * <li>beanstalk.phpDocumentRoot, to aws:elasticbeanstalk:container:php:phpini/document_root</li> 163 * <li>beanstalk.phpMemoryLimit, to aws:elasticbeanstalk:container:php:phpini/memory_limit</li> 164 * <li>beanstalk.phpZlibOutputCompression, to aws:elasticbeanstalk:container:php:phpini/zlib.output_compression</li> 165 * <li>beanstalk.phpAllowUrlFopen, to aws:elasticbeanstalk:container:php:phpini/allow_url_fopen</li> 166 * <li>beanstalk.phpDisplayErrors, to aws:elasticbeanstalk:container:php:phpini/display_errors</li> 167 * <li>beanstalk.phpMaxExecutionTime, to aws:elasticbeanstalk:container:php:phpini/max_execution_time</li> 168 * <li>beanstalk.phpComposerOptions, to aws:elasticbeanstalk:container:php:phpini/composer_options</li> 169 * </ul> <p> The reason for most of those aliases if the need to address space and ':' inside 170 * Maven Properties and XML Files. 171 */ 172 @Parameter ConfigurationOptionSetting[] optionSettings; 173 174 @Parameter OptionSpecification[] optionsToRemove; 175 176 /** 177 * Version Label to use 178 */ 179 @Parameter(property = "beanstalk.versionLabel") 180 String versionLabel; 181 182 /** 183 * Solution Stack Name 184 */ 185 @Parameter(property = "beanstalk.solutionStack", defaultValue = "32bit Amazon Linux running Tomcat 7") 186 String solutionStack; 187 188 /** 189 * <p>If waiting for the environment to become ready, this indicates wether or not we require 190 * the Green health checks to pass.</p> 191 * 192 * <p>One reason to avoid such a requirement is if we are frantically replacing one failed 193 * environment with another one trying to get a ring of interdependant services to operate.</p> 194 * 195 * <p>Optional, but usually true. Under normal circumstances we *only* want to swap in a healthy 196 * service.</p> 197 */ 198 @Parameter(property = "beanstalk.mustBeHealthy", defaultValue = "true") 199 boolean mustBeHealthy; 200 201 /** 202 * <p>Template Name.</p> <p> 203 * <p>Could be either literal or a glob, like, <pre>ingenieux-services-prod-*</pre>. If a glob, 204 * there will be a lookup involved, and the first one in reverse ASCIIbetical order will be 205 * picked upon. </p> 206 */ 207 @Parameter(property = "beanstalk.templateName") 208 String templateName; 209 210 /** 211 * <p>Status to Wait For</p> <p> <p>Optional. If set, will block until app status is set eg 212 * "Ready"</p> 213 */ 214 @Parameter(property = "beanstalk.waitForReady", defaultValue = "true") 215 boolean waitForReady; 216 217 /** 218 * <p>Environment Tier Name (defaults to "WebServer")</p> 219 */ 220 @Parameter(property = "beanstalk.environmentTierName", defaultValue = "WebServer") 221 String environmentTierName; 222 223 /** 224 * <p>Environment Tier Type</p> 225 */ 226 @Parameter(property = "beanstalk.environmentTierType") 227 String environmentTierType; 228 229 /** 230 * <p>Environment Tier Version</p> 231 */ 232 @Parameter(property = "beanstalk.environmentTierVersion") 233 String environmentTierVersion; 234 235 /** 236 * <p>CNAME Prefix</p> 237 */ 238 @Parameter(property = "beanstalk.cnamePrefix") 239 String cnamePrefix; 240 241 /** 242 * <p>Environment Tag List</p> <p> To set up environment tags put this under plugin 243 * configuration tag 244 * <pre> 245 * {@code 246 * <tags> 247 * <tagName>tagValue</tagName> 248 * </tags> 249 * } 250 * </pre> 251 * or use system property in the following format: beanstalk.tag.TAG_NAME=TAG_VALUE 252 */ 253 @Parameter Map<String, String> tags = Collections.emptyMap(); 254 255 /** 256 * Final Computed Tags 257 */ 258 Map<String, String> tagsToUse = Collections.emptyMap(); 259 260 /** 261 * Overrides parent in order to avoid a thrown exception as there's not an environment to 262 * lookup 263 */ 264 @Override 265 protected void configure() { 266 projectTags(); 267 } 268 269 /** 270 * Compute the Tags to USe 271 * 272 * TODO: Do we need system.properties? 273 */ 274 protected Map<String, String> projectTags() { 275 tagsToUse = new LinkedHashMap<String, String>(this.tags); 276 277 /** 278 * First: Cleanup 279 */ 280 for (Map.Entry<String, String> e : tagsToUse.entrySet()) { 281 String k = e.getKey(); 282 283 String v = e.getValue(); 284 285 if (isBlank(v)) { 286 getLog().info(format("Excluding tag: '%s'", k)); 287 tagsToUse.remove(k); 288 } 289 } 290 291 // Step #2: Context 292 293 Properties contextProperties = new Properties(); 294 295 for (Map.Entry<Object, Object> e : context.getContextData().entrySet()) { 296 if (!("" + e.getKey()).startsWith(BEANSTALK_TAG_PREFIX)) continue; 297 298 contextProperties.put(e.getKey(), e.getValue()); 299 } 300 301 // Now consolidate all three 302 303 for (Properties props : Arrays.asList(project.getProperties(), contextProperties, System.getProperties())) { 304 for (Map.Entry<Object, Object> e : project.getProperties().entrySet()) { 305 String k = "" + e.getKey(); 306 307 if (!k.startsWith(BEANSTALK_TAG_PREFIX)) continue; 308 309 k = k.substring(BEANSTALK_TAG_PREFIX.length()); 310 311 String v = defaultString(e.getValue()); 312 313 if (isBlank(v)) { 314 getLog().info(format("Excluding tag: '%s'", k)); 315 tagsToUse.remove(k); 316 } else { 317 getLog().info(format("Assigning Tag: '%s'='%s'", k, v)); 318 tagsToUse.put(k, v); 319 } 320 } 321 } 322 323 return tagsToUse; 324 } 325 326 @Override 327 protected Object executeInternal() throws Exception { 328 versionLabel = lookupVersionLabel(applicationName, versionLabel); 329 330 return createEnvironment(cnamePrefix, environmentName); 331 } 332 333 protected CreateEnvironmentResult createEnvironment(String cnameToCreate, String newEnvironmentName) throws AbstractMojoExecutionException { 334 /* 335 * Hey Aldrin, have you ever noticed we're getting pedantic on those validations? 336 */ 337 Validate.isTrue(isNotBlank(newEnvironmentName), "No New Environment Name Supplied"); 338 339 if (null == optionSettings) { 340 optionSettings = introspectOptionSettings(); 341 } 342 343 versionLabel = lookupVersionLabel(applicationName, versionLabel); 344 345 CreateEnvironmentContextBuilder builder = 346 CreateEnvironmentContextBuilder.createEnvironmentContext() // 347 .withApplicationName(applicationName) // 348 .withApplicationDescription(applicationDescription) // 349 .withCnamePrefix(cnameToCreate) // 350 .withSolutionStack(lookupSolutionStack(solutionStack)) // 351 .withTemplateName(templateName) // 352 .withEnvironmentName(newEnvironmentName) // 353 .withOptionSettings(optionSettings) // 354 .withOptionsToRemove(optionsToRemove) // 355 .withEnvironmentTierName(environmentTierName) // 356 .withEnvironmentTierType(environmentTierType) // 357 .withEnvironmentTierVersion(environmentTierVersion) // 358 .withVersionLabel(versionLabel) // 359 .withTags(tagsToUse); // 360 361 CreateEnvironmentContext context = builder.build(); 362 363 CreateEnvironmentCommandreate/CreateEnvironmentCommand.html#CreateEnvironmentCommand">CreateEnvironmentCommand command = new CreateEnvironmentCommand(this); 364 365 CreateEnvironmentResult result = command.execute(context); 366 367 if (waitForReady) { 368 WaitForEnvironmentContext ctx = 369 new WaitForEnvironmentContextBuilder() // 370 .withEnvironmentRef(result.getEnvironmentId()) // 371 .withApplicationName(result.getApplicationName()) // 372 .withHealth(mustBeHealthy ? "Green" : null) // 373 .withStatusToWaitFor("Ready") // 374 .build(); 375 376 new WaitForEnvironmentCommand(this).execute(ctx); 377 } 378 379 return result; 380 } 381 }