/************************************************************************* * Copyright 2009-2013 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.eucalyptus.auth.euare.AddUserToGroupResponseType; import com.eucalyptus.auth.euare.AddUserToGroupType; import com.eucalyptus.auth.euare.AttachUserPolicyType; import com.eucalyptus.auth.euare.AttachedPolicyType; import com.eucalyptus.auth.euare.CreateLoginProfileResponseType; import com.eucalyptus.auth.euare.CreateLoginProfileType; import com.eucalyptus.auth.euare.CreateUserResponseType; import com.eucalyptus.auth.euare.CreateUserType; import com.eucalyptus.auth.euare.DeleteLoginProfileResponseType; import com.eucalyptus.auth.euare.DeleteLoginProfileType; import com.eucalyptus.auth.euare.DeleteUserPolicyResponseType; import com.eucalyptus.auth.euare.DeleteUserPolicyType; import com.eucalyptus.auth.euare.DeleteUserResponseType; import com.eucalyptus.auth.euare.DeleteUserType; import com.eucalyptus.auth.euare.DetachUserPolicyType; import com.eucalyptus.auth.euare.GroupType; import com.eucalyptus.auth.euare.ListAttachedUserPoliciesResponseType; import com.eucalyptus.auth.euare.ListAttachedUserPoliciesType; import com.eucalyptus.auth.euare.ListGroupsForUserResponseType; import com.eucalyptus.auth.euare.ListGroupsForUserType; import com.eucalyptus.auth.euare.PutUserPolicyResponseType; import com.eucalyptus.auth.euare.PutUserPolicyType; import com.eucalyptus.auth.euare.RemoveUserFromGroupResponseType; import com.eucalyptus.auth.euare.RemoveUserFromGroupType; import com.eucalyptus.auth.euare.UpdateLoginProfileResponseType; import com.eucalyptus.auth.euare.UpdateLoginProfileType; import com.eucalyptus.auth.euare.UpdateUserResponseType; import com.eucalyptus.auth.euare.UpdateUserType; import com.eucalyptus.auth.euare.UserType; import com.eucalyptus.cloudformation.ValidationErrorException; import com.eucalyptus.cloudformation.resources.IAMHelper; 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.AWSIAMUserResourceInfo; import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSIAMUserProperties; import com.eucalyptus.cloudformation.resources.standard.propertytypes.EmbeddedIAMPolicy; import com.eucalyptus.cloudformation.template.JsonHelper; import com.eucalyptus.cloudformation.util.MessageHelper; import com.eucalyptus.cloudformation.workflow.steps.Step; import com.eucalyptus.cloudformation.workflow.steps.StepBasedResourceAction; import com.eucalyptus.cloudformation.workflow.steps.UpdateStep; import com.eucalyptus.cloudformation.workflow.updateinfo.UpdateType; import com.eucalyptus.cloudformation.workflow.updateinfo.UpdateTypeAndDirection; import com.eucalyptus.component.ServiceConfiguration; import com.eucalyptus.component.Topology; import com.eucalyptus.component.id.Euare; import com.eucalyptus.util.async.AsyncExceptions; import com.eucalyptus.util.async.AsyncRequests; import com.fasterxml.jackson.databind.node.TextNode; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.log4j.Logger; import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; /** * Created by ethomas on 2/3/14. */ public class AWSIAMUserResourceAction extends StepBasedResourceAction { private static final Logger LOG = Logger.getLogger(AWSIAMUserResourceAction.class); private AWSIAMUserProperties properties = new AWSIAMUserProperties(); private AWSIAMUserResourceInfo info = new AWSIAMUserResourceInfo(); public AWSIAMUserResourceAction() { super(fromEnum(CreateSteps.class), fromEnum(DeleteSteps.class), fromUpdateEnum(UpdateNoInterruptionSteps.class), null); // In this case, update with replacement has a precondition check before essentially the same steps as "create". We add both. Map<String, UpdateStep> updateWithReplacementMap = Maps.newLinkedHashMap(); updateWithReplacementMap.putAll(fromUpdateEnum(UpdateWithReplacementPreCreateSteps.class)); updateWithReplacementMap.putAll(createStepsToUpdateWithReplacementSteps(fromEnum(CreateSteps.class))); setUpdateSteps(UpdateTypeAndDirection.UPDATE_WITH_REPLACEMENT, updateWithReplacementMap); } private static final String DEFAULT_PATH = "/"; @Override public UpdateType getUpdateType(ResourceAction resourceAction, boolean stackTagsChanged) { UpdateType updateType = info.supportsTags() && stackTagsChanged ? UpdateType.NO_INTERRUPTION : UpdateType.NONE; AWSIAMUserResourceAction otherAction = (AWSIAMUserResourceAction) resourceAction; if (!Objects.equals(properties.getPath(), otherAction.properties.getPath())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } if (!Objects.equals(properties.getGroups(), otherAction.properties.getGroups())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } if (!Objects.equals(properties.getLoginProfile(), otherAction.properties.getLoginProfile())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } if (!Objects.equals(properties.getManagedPolicyArns(), otherAction.properties.getManagedPolicyArns())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } if (!Objects.equals(properties.getPolicies(), otherAction.properties.getPolicies())) { updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION); } if (!Objects.equals(properties.getUserName(), otherAction.properties.getUserName())) { updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT); } return updateType; } private enum CreateSteps implements Step { CREATE_USER { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSIAMUserResourceAction action = (AWSIAMUserResourceAction) resourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); String userName = action.properties.getUserName() != null ? action.properties.getUserName() : action.getDefaultPhysicalResourceId(); CreateUserType createUserType = MessageHelper.createMessage(CreateUserType.class, action.info.getEffectiveUserId()); createUserType.setUserName(userName); createUserType.setPath(MoreObjects.firstNonNull(action.properties.getPath(), DEFAULT_PATH)); CreateUserResponseType createUserResponseType = AsyncRequests.<CreateUserType,CreateUserResponseType> sendSync(configuration, createUserType); String arn = createUserResponseType.getCreateUserResult().getUser().getArn(); action.info.setPhysicalResourceId(userName); action.info.setCreatedEnoughToDelete(true); action.info.setArn(JsonHelper.getStringFromJsonNode(new TextNode(arn))); action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId()))); return action; } }, ADD_LOGIN_PROFILE { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSIAMUserResourceAction action = (AWSIAMUserResourceAction) resourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); if (action.properties.getLoginProfile() != null) { CreateLoginProfileType createLoginProfileType = MessageHelper.createMessage(CreateLoginProfileType.class, action.info.getEffectiveUserId()); createLoginProfileType.setPassword(action.properties.getLoginProfile().getPassword()); createLoginProfileType.setUserName(action.info.getPhysicalResourceId()); AsyncRequests.<CreateLoginProfileType,CreateLoginProfileResponseType> sendSync(configuration, createLoginProfileType); } return action; } }, ADD_POLICIES { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSIAMUserResourceAction action = (AWSIAMUserResourceAction) resourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); if (action.properties.getPolicies() != null) { for (EmbeddedIAMPolicy policy: action.properties.getPolicies()) { PutUserPolicyType putUserPolicyType = MessageHelper.createMessage(PutUserPolicyType.class, action.info.getEffectiveUserId()); putUserPolicyType.setUserName(action.info.getPhysicalResourceId()); putUserPolicyType.setPolicyName(policy.getPolicyName()); putUserPolicyType.setPolicyDocument(policy.getPolicyDocument().toString()); AsyncRequests.<PutUserPolicyType,PutUserPolicyResponseType> sendSync(configuration, putUserPolicyType); } } return action; } }, ADD_MANAGED_POLICIES { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSIAMUserResourceAction action = (AWSIAMUserResourceAction) resourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); if (action.properties.getPolicies() != null) { for (String managedPolicyArn: action.properties.getManagedPolicyArns()) { AttachUserPolicyType attachUserPolicyType = MessageHelper.createMessage(AttachUserPolicyType.class, action.info.getEffectiveUserId()); attachUserPolicyType.setUserName(action.info.getPhysicalResourceId()); attachUserPolicyType.setPolicyArn(managedPolicyArn); AsyncRequests.sendSync(configuration, attachUserPolicyType); } } return action; } }, ADD_GROUPS { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSIAMUserResourceAction action = (AWSIAMUserResourceAction) resourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); if (action.properties.getGroups() != null) { for (String groupName: action.properties.getGroups()) { AddUserToGroupType addUserToGroupType = MessageHelper.createMessage(AddUserToGroupType.class, action.info.getEffectiveUserId()); addUserToGroupType.setGroupName(groupName); addUserToGroupType.setUserName(action.info.getPhysicalResourceId()); AsyncRequests.<AddUserToGroupType,AddUserToGroupResponseType> sendSync(configuration, addUserToGroupType); } } return action; } }; @Nullable @Override public Integer getTimeout() { return null; } } private enum DeleteSteps implements Step { DELETE_USER { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSIAMUserResourceAction action = (AWSIAMUserResourceAction) resourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action; // if no user, bye... if (!IAMHelper.userExists(configuration, action.info.getPhysicalResourceId(), action.info.getEffectiveUserId())) return action; // remove user from groups we added (if there) if (action.properties.getGroups() != null) { boolean seenAllGroups = false; List<String> currentGroups = Lists.newArrayList(); String groupMarker = null; while (!seenAllGroups) { ListGroupsForUserType listGroupsForUserType = MessageHelper.createMessage(ListGroupsForUserType.class, action.info.getEffectiveUserId()); listGroupsForUserType.setUserName(action.info.getPhysicalResourceId()); if (groupMarker != null) { listGroupsForUserType.setMarker(groupMarker); } ListGroupsForUserResponseType listGroupsForUserResponseType = AsyncRequests.<ListGroupsForUserType,ListGroupsForUserResponseType> sendSync(configuration, listGroupsForUserType); if (Boolean.TRUE.equals(listGroupsForUserResponseType.getListGroupsForUserResult().getIsTruncated())) { groupMarker = listGroupsForUserResponseType.getListGroupsForUserResult().getMarker(); } else { seenAllGroups = true; } if (listGroupsForUserResponseType.getListGroupsForUserResult().getGroups() != null && listGroupsForUserResponseType.getListGroupsForUserResult().getGroups().getMemberList() != null) { for (GroupType groupType: listGroupsForUserResponseType.getListGroupsForUserResult().getGroups().getMemberList()) { currentGroups.add(groupType.getGroupName()); } } } for (String groupName: action.properties.getGroups()) { if (currentGroups.contains(groupName)) { RemoveUserFromGroupType removeUserFromGroupType = MessageHelper.createMessage(RemoveUserFromGroupType.class, action.info.getEffectiveUserId()); removeUserFromGroupType.setGroupName(groupName); removeUserFromGroupType.setUserName(action.info.getPhysicalResourceId()); AsyncRequests.<RemoveUserFromGroupType,RemoveUserFromGroupResponseType> sendSync(configuration, removeUserFromGroupType); } } // Note: the above will not add externally added groups, but this is by design... } // remove all policies added by us. (Note: this could cause issues if an admin added some, but we delete what we create) // Note: deleting a non-existing policy doesn't do anything so we just delete them all... if (action.properties.getPolicies() != null) { for (EmbeddedIAMPolicy policy: action.properties.getPolicies()) { DeleteUserPolicyType deleteUserPolicyType = MessageHelper.createMessage(DeleteUserPolicyType.class, action.info.getEffectiveUserId()); deleteUserPolicyType.setUserName(action.info.getPhysicalResourceId()); deleteUserPolicyType.setPolicyName(policy.getPolicyName()); AsyncRequests.<DeleteUserPolicyType,DeleteUserPolicyResponseType> sendSync(configuration, deleteUserPolicyType); } } DeleteUserType deleteUserType = MessageHelper.createMessage(DeleteUserType.class, action.info.getEffectiveUserId()); deleteUserType.setUserName(action.info.getPhysicalResourceId()); AsyncRequests.<DeleteUserType,DeleteUserResponseType> sendSync(configuration, deleteUserType); return action; } }; @Nullable @Override public Integer getTimeout() { return null; } } @Override public ResourceProperties getResourceProperties() { return properties; } @Override public void setResourceProperties(ResourceProperties resourceProperties) { properties = (AWSIAMUserProperties) resourceProperties; } @Override public ResourceInfo getResourceInfo() { return info; } @Override public void setResourceInfo(ResourceInfo resourceInfo) { info = (AWSIAMUserResourceInfo) resourceInfo; } private static Set<String> getPolicyNames(AWSIAMUserResourceAction action) { Set<String> policyNames = Sets.newLinkedHashSet(); if (action.properties.getPolicies() != null) { for (EmbeddedIAMPolicy policy : action.properties.getPolicies()) { policyNames.add(policy.getPolicyName()); } } return policyNames; } private static Set<String> getManagedPolicyArns(AWSIAMUserResourceAction action) { Set<String> managedPolicyArns = Sets.newHashSet(); if (action != null && action.properties != null && action.properties.getManagedPolicyArns() != null) { managedPolicyArns.addAll(action.properties.getManagedPolicyArns()); } return managedPolicyArns; } private static Set<String> getExistingManagedPolicyArns(AWSIAMUserResourceAction newAction) throws Exception { ServiceConfiguration configuration = Topology.lookup(Euare.class); ListAttachedUserPoliciesType listAttachedUserPoliciesType = MessageHelper.createMessage(ListAttachedUserPoliciesType.class, newAction.info.getEffectiveUserId()); listAttachedUserPoliciesType.setUserName(newAction.info.getPhysicalResourceId()); ListAttachedUserPoliciesResponseType listAttachedUserPoliciesResponseType = AsyncRequests.sendSync(configuration, listAttachedUserPoliciesType); Set<String> result = Sets.newHashSet(); if (listAttachedUserPoliciesResponseType != null && listAttachedUserPoliciesResponseType.getListAttachedUserPoliciesResult() != null && listAttachedUserPoliciesResponseType.getListAttachedUserPoliciesResult().getAttachedPolicies() != null) { for (AttachedPolicyType attachedPolicyType : listAttachedUserPoliciesResponseType.getListAttachedUserPoliciesResult().getAttachedPolicies()) { result.add(attachedPolicyType.getPolicyArn()); } } return result; } private enum UpdateNoInterruptionSteps implements UpdateStep { UPDATE_USER { @Override public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception { AWSIAMUserResourceAction oldAction = (AWSIAMUserResourceAction) oldResourceAction; AWSIAMUserResourceAction newAction = (AWSIAMUserResourceAction) newResourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); String userName = newAction.info.getPhysicalResourceId(); UpdateUserType updateUserType = MessageHelper.createMessage(UpdateUserType.class, newAction.info.getEffectiveUserId()); updateUserType.setUserName(userName); updateUserType.setNewPath(MoreObjects.firstNonNull(newAction.properties.getPath(), DEFAULT_PATH)); UpdateUserResponseType updateUserResponseType = AsyncRequests.<UpdateUserType,UpdateUserResponseType> sendSync(configuration, updateUserType); return newAction; } }, UPDATE_LOGIN_PROFILE { @Override public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception { AWSIAMUserResourceAction oldAction = (AWSIAMUserResourceAction) oldResourceAction; AWSIAMUserResourceAction newAction = (AWSIAMUserResourceAction) newResourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); if (newAction.properties.getLoginProfile() != null) { UpdateLoginProfileType updateLoginProfileType = MessageHelper.createMessage(UpdateLoginProfileType.class, newAction.info.getEffectiveUserId()); updateLoginProfileType.setPassword(newAction.properties.getLoginProfile().getPassword()); updateLoginProfileType.setUserName(newAction.info.getPhysicalResourceId()); try { AsyncRequests.<UpdateLoginProfileType, UpdateLoginProfileResponseType>sendSync(configuration, updateLoginProfileType); } catch ( final Exception e ) { final Optional<AsyncExceptions.AsyncWebServiceError> error = AsyncExceptions.asWebServiceError(e); if (error.isPresent() && Strings.nullToEmpty(error.get().getCode()).equals("NoSuchEntity")) { CreateLoginProfileType createLoginProfileType = MessageHelper.createMessage(CreateLoginProfileType.class, newAction.info.getEffectiveUserId()); createLoginProfileType.setPassword(newAction.properties.getLoginProfile().getPassword()); createLoginProfileType.setUserName(newAction.info.getPhysicalResourceId()); AsyncRequests.<CreateLoginProfileType,CreateLoginProfileResponseType> sendSync(configuration, createLoginProfileType); } else throw e; } } else { DeleteLoginProfileType deleteLoginProfileType = MessageHelper.createMessage(DeleteLoginProfileType.class, newAction.info.getEffectiveUserId()); deleteLoginProfileType.setUserName(newAction.info.getPhysicalResourceId()); try { AsyncRequests.<DeleteLoginProfileType,DeleteLoginProfileResponseType> sendSync(configuration, deleteLoginProfileType); } catch ( final Exception e ) { final Optional<AsyncExceptions.AsyncWebServiceError> error = AsyncExceptions.asWebServiceError(e); if (error.isPresent() && Strings.nullToEmpty(error.get().getCode()).equals("NoSuchEntity")) { // we don't care. (already deleted or never there) } else throw e; } } return newAction; } }, UPDATE_POLICIES { public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception { AWSIAMUserResourceAction oldAction = (AWSIAMUserResourceAction) oldResourceAction; AWSIAMUserResourceAction newAction = (AWSIAMUserResourceAction) newResourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); Set<String> oldPolicyNames = getPolicyNames(oldAction); Set<String> newPolicyNames = getPolicyNames(newAction); if (newAction.properties.getPolicies() != null) { for (EmbeddedIAMPolicy policy: newAction.properties.getPolicies()) { PutUserPolicyType putUserPolicyType = MessageHelper.createMessage(PutUserPolicyType.class, newAction.info.getEffectiveUserId()); putUserPolicyType.setUserName(newAction.info.getPhysicalResourceId()); putUserPolicyType.setPolicyName(policy.getPolicyName()); putUserPolicyType.setPolicyDocument(policy.getPolicyDocument().toString()); AsyncRequests.<PutUserPolicyType,PutUserPolicyResponseType> sendSync(configuration, putUserPolicyType); } } // delete all the old policies not in the new set (deleting policies is idempotent thankfully) for (String oldPolicyName : Sets.difference(oldPolicyNames, newPolicyNames)) { DeleteUserPolicyType deleteUserPolicyType = MessageHelper.createMessage(DeleteUserPolicyType.class, newAction.info.getEffectiveUserId()); deleteUserPolicyType.setUserName(newAction.info.getPhysicalResourceId()); deleteUserPolicyType.setPolicyName(oldPolicyName); AsyncRequests.<DeleteUserPolicyType,DeleteUserPolicyResponseType> sendSync(configuration, deleteUserPolicyType); } return newAction; } }, UPDATE_MANAGED_POLICIES { public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception { AWSIAMUserResourceAction oldAction = (AWSIAMUserResourceAction) oldResourceAction; AWSIAMUserResourceAction newAction = (AWSIAMUserResourceAction) newResourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); Set<String> oldManagedPolicyArns = getManagedPolicyArns(oldAction); Set<String> newManagedPolicyArns = getManagedPolicyArns(newAction); Set<String> existingManagedPolicyArns = getExistingManagedPolicyArns(newAction); // the policies to add are the new policies that are not old and not existing Set<String> managedPolicyArnsToAdd = Sets.difference(newManagedPolicyArns, Sets.union(oldManagedPolicyArns, existingManagedPolicyArns)); for (String managedPolicyArn: managedPolicyArnsToAdd) { AttachUserPolicyType attachUserPolicyType = MessageHelper.createMessage(AttachUserPolicyType.class, newAction.info.getEffectiveUserId()); attachUserPolicyType.setUserName(newAction.info.getPhysicalResourceId()); attachUserPolicyType.setPolicyArn(managedPolicyArn); AsyncRequests.sendSync(configuration, attachUserPolicyType); } // the policies to remove from the resource are the old policies that are not new, but they must also exist. Set<String> managedPolicyArnsToRemove = Sets.difference(Sets.intersection(existingManagedPolicyArns, oldManagedPolicyArns), newManagedPolicyArns); for (String managedPolicyArn: managedPolicyArnsToRemove) { DetachUserPolicyType detachUserPolicyType = MessageHelper.createMessage(DetachUserPolicyType.class, newAction.info.getEffectiveUserId()); detachUserPolicyType.setUserName(newAction.info.getPhysicalResourceId()); detachUserPolicyType.setPolicyArn(managedPolicyArn); try { AsyncRequests.sendSync(configuration, detachUserPolicyType); } catch ( final Exception e ) { final Optional<AsyncExceptions.AsyncWebServiceError> error = AsyncExceptions.asWebServiceError(e); if (error.isPresent() && Strings.nullToEmpty(error.get().getCode()).equals("NoSuchEntity")) { // we don't care. (already deleted or never there) } else throw e; } } return newAction; } }, UPDATE_GROUPS { @Override public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception { AWSIAMUserResourceAction oldAction = (AWSIAMUserResourceAction) oldResourceAction; AWSIAMUserResourceAction newAction = (AWSIAMUserResourceAction) newResourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); Set<String> oldGroupNames = IAMHelper.collectionToSetAndNullToEmpty(oldAction.properties.getGroups()); Set<String> newGroupNames = IAMHelper.collectionToSetAndNullToEmpty(newAction.properties.getGroups()); // only add groups not already added (due to conflict error if double adding) Set<String> existingGroupsForUser = IAMHelper.getGroupNamesForUser(configuration, newAction.info.getPhysicalResourceId(), newAction.info.getEffectiveUserId()); for (String groupName: Sets.difference(newGroupNames, existingGroupsForUser)) { AddUserToGroupType addUserToGroupType = MessageHelper.createMessage(AddUserToGroupType.class, newAction.info.getEffectiveUserId()); addUserToGroupType.setGroupName(groupName); addUserToGroupType.setUserName(newAction.info.getPhysicalResourceId()); AsyncRequests.<AddUserToGroupType,AddUserToGroupResponseType> sendSync(configuration, addUserToGroupType); } for (String groupName: Sets.difference(oldGroupNames, newGroupNames)) { RemoveUserFromGroupType removeUserFromGroupType = MessageHelper.createMessage(RemoveUserFromGroupType.class, oldAction.info.getEffectiveUserId()); removeUserFromGroupType.setGroupName(groupName); removeUserFromGroupType.setUserName(oldAction.info.getPhysicalResourceId()); try { AsyncRequests.<RemoveUserFromGroupType,RemoveUserFromGroupResponseType> sendSync(configuration, removeUserFromGroupType); } catch ( final Exception e ) { final Optional<AsyncExceptions.AsyncWebServiceError> error = AsyncExceptions.asWebServiceError(e); if (error.isPresent() && Strings.nullToEmpty(error.get().getCode()).equals("NoSuchEntity")) { // we don't care. (already deleted or never there) } else throw e; } } return newAction; } }, UPDATE_ARN { @Override public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception { AWSIAMUserResourceAction oldAction = (AWSIAMUserResourceAction) oldResourceAction; AWSIAMUserResourceAction newAction = (AWSIAMUserResourceAction) newResourceAction; ServiceConfiguration configuration = Topology.lookup(Euare.class); String userName = newAction.info.getPhysicalResourceId(); UserType user = IAMHelper.getUser(configuration, userName, newAction.info.getEffectiveUserId()); newAction.info.setArn(JsonHelper.getStringFromJsonNode(new TextNode(user.getArn()))); return newAction; } }; @Nullable @Override public Integer getTimeout() { return null; } } private enum UpdateWithReplacementPreCreateSteps implements UpdateStep { CHECK_CHANGED_USER_NAME { @Override public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception { AWSIAMUserResourceAction oldAction = (AWSIAMUserResourceAction) oldResourceAction; AWSIAMUserResourceAction newAction = (AWSIAMUserResourceAction) newResourceAction; if (Objects.equals(oldAction.properties.getUserName(), newAction.properties.getUserName()) && oldAction.properties.getUserName() != null) { throw new ValidationErrorException("CloudFormation cannot update a stack when a custom-named resource requires replacing. Rename "+oldAction.properties.getUserName()+" and update the stack again."); } return newAction; } @Nullable @Override public Integer getTimeout() { return null; } } } }