/************************************************************************* * Copyright 2009-2015 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.cloudformation.resources.standard.actions; import com.amazonaws.HttpMethod; import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.s3.model.BucketVersioningConfiguration; import com.amazonaws.services.s3.model.S3VersionSummary; import com.amazonaws.services.s3.model.SetBucketVersioningConfigurationRequest; import com.amazonaws.services.s3.model.VersionListing; import com.eucalyptus.cloudformation.bootstrap.CloudFormationAWSCredentialsProvider; import com.eucalyptus.cloudformation.resources.ResourceAction; import com.eucalyptus.cloudformation.resources.ResourceInfo; import com.eucalyptus.cloudformation.resources.ResourceProperties; import com.eucalyptus.cloudformation.resources.standard.info.AWSCloudFormationWaitConditionHandleResourceInfo; import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSCloudFormationWaitConditionHandleProperties; import com.eucalyptus.cloudformation.template.JsonHelper; import com.eucalyptus.cloudformation.workflow.steps.Step; import com.eucalyptus.cloudformation.workflow.steps.StepBasedResourceAction; import com.eucalyptus.cloudformation.workflow.updateinfo.UpdateType; import com.eucalyptus.configurable.ConfigurableClass; import com.eucalyptus.configurable.ConfigurableField; import com.eucalyptus.crypto.Crypto; import com.eucalyptus.objectstorage.client.EucaS3Client; import com.eucalyptus.objectstorage.client.EucaS3ClientFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; import javax.annotation.Nullable; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; /** * Created by ethomas on 2/3/14. */ @ConfigurableClass( root = "cloudformation", description = "Parameters controlling cloud formation") public class AWSCloudFormationWaitConditionHandleResourceAction extends StepBasedResourceAction { @ConfigurableField(initial = "cf-waitcondition", description = "The prefix of the bucket used for wait condition handles") public static volatile String WAIT_CONDITION_BUCKET_PREFIX = "cf-waitcondition"; private AWSCloudFormationWaitConditionHandleProperties properties = new AWSCloudFormationWaitConditionHandleProperties(); private AWSCloudFormationWaitConditionHandleResourceInfo info = new AWSCloudFormationWaitConditionHandleResourceInfo(); @Override public UpdateType getUpdateType(ResourceAction resourceAction, boolean stackTagsChanged) { return UpdateType.UNSUPPORTED; } public AWSCloudFormationWaitConditionHandleResourceAction() { super(fromEnum(CreateSteps.class), fromEnum(DeleteSteps.class), null, null); } private enum CreateSteps implements Step { GET_BUCKET_AND_CREATE_OBJECT { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { final AWSCloudFormationWaitConditionHandleResourceAction action = (AWSCloudFormationWaitConditionHandleResourceAction) resourceAction; try ( final EucaS3Client s3c = EucaS3ClientFactory.getEucaS3Client( new CloudFormationAWSCredentialsProvider( (int)TimeUnit.HOURS.toSeconds( 12 ) ) ) ) { // look for an existing bucket... String bucketName = null; ObjectNode objectNode = null; List<Bucket> buckets = s3c.listBuckets(); for ( Bucket bucket : buckets ) { if ( bucket.getName().toLowerCase().startsWith( WAIT_CONDITION_BUCKET_PREFIX.toLowerCase() ) ) { // TODO: what happens if versioning is off? bucketName = bucket.getName(); } } if ( bucketName == null ) { // TODO: check prefix length bucketName = ( WAIT_CONDITION_BUCKET_PREFIX + "-" + Crypto.generateAlphanumericId( 13 ) ).toLowerCase(); } s3c.createBucket( bucketName ); String keyName = action.getStackEntity().getStackId() + "/" + action.info.getLogicalResourceId() + "/WaitHandle"; objectNode = JsonHelper.createObjectNode(); objectNode.put( "version", "1.0" ); objectNode.put( "bucket", bucketName ); objectNode.put( "key", keyName ); action.info.setEucaParts( JsonHelper.getStringFromJsonNode( objectNode ) ); s3c.refreshEndpoint( true ); String url = s3c.generatePresignedUrl( bucketName, keyName, action.in12Hours(), HttpMethod.PUT ).toString(); action.info.setPhysicalResourceId( url ); action.info.setCreatedEnoughToDelete(true); } return action; } }, VERSION_BUCKET { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSCloudFormationWaitConditionHandleResourceAction action = (AWSCloudFormationWaitConditionHandleResourceAction) resourceAction; try ( final EucaS3Client s3c = EucaS3ClientFactory.getEucaS3Client(new CloudFormationAWSCredentialsProvider()) ) { ObjectNode objectNode = (ObjectNode) JsonHelper.getJsonNodeFromString(action.info.getEucaParts()); if (!"1.0".equals(objectNode.get("version").asText())) throw new Exception("Invalid version for eucaParts"); String bucketName = objectNode.get("bucket").asText(); s3c.setBucketVersioningConfiguration(new SetBucketVersioningConfigurationRequest(bucketName, new BucketVersioningConfiguration(BucketVersioningConfiguration.ENABLED))); } action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId()))); return action; } }; @Nullable @Override public Integer getTimeout() { return null; } } private enum DeleteSteps implements Step { DELETE_VERSIONS { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSCloudFormationWaitConditionHandleResourceAction action = (AWSCloudFormationWaitConditionHandleResourceAction) resourceAction; if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action; try ( final EucaS3Client s3c = EucaS3ClientFactory.getEucaS3Client(new CloudFormationAWSCredentialsProvider()) ) { ObjectNode objectNode = (ObjectNode) JsonHelper.getJsonNodeFromString(action.info.getEucaParts()); if (!"1.0".equals(objectNode.get("version").asText())) throw new Exception("Invalid version for eucaParts"); String bucketName = objectNode.get("bucket").asText(); String keyName = objectNode.get("key").asText(); if (!s3c.doesBucketExist(bucketName)) { return action; } VersionListing versionListing = s3c.listVersions(bucketName,""); // delete all items under the buckey/key for (S3VersionSummary versionSummary: versionListing.getVersionSummaries()) { if (versionSummary.getKey().equals(keyName)) { s3c.deleteVersion(versionSummary.getBucketName(), versionSummary.getKey(), versionSummary.getVersionId()); } } } return action; } }; @Nullable @Override public Integer getTimeout() { return null; } } @Override public ResourceProperties getResourceProperties() { return properties; } @Override public void setResourceProperties(ResourceProperties resourceProperties) { properties = (AWSCloudFormationWaitConditionHandleProperties) resourceProperties; } @Override public ResourceInfo getResourceInfo() { return info; } @Override public void setResourceInfo(ResourceInfo resourceInfo) { info = (AWSCloudFormationWaitConditionHandleResourceInfo) resourceInfo; } private Date in12Hours() { return new Date(System.currentTimeMillis() + 12 * 60 * 60 * 1000L); // max timeout for handle } }