├── settings.gradle ├── test ├── .DS_Store ├── tw │ ├── .DS_Store │ └── com │ │ ├── integration │ │ ├── TestGetCurrentIpProvider.java │ │ ├── TestIdentityProvider.java │ │ ├── TestHaveValidTemplateFiles.java │ │ └── TestPictureGeneration.java │ │ ├── unit │ │ ├── TestCidr.java │ │ ├── TestInstanceSummary.java │ │ ├── TestCFNAssistNotification.java │ │ ├── TestDiagramFactory.java │ │ ├── TestSavesFile.java │ │ ├── TestStackCache.java │ │ ├── TestDiagramCreator.java │ │ ├── TestStackEntry.java │ │ ├── TestDeletionsPending.java │ │ └── TestEnvVarParams.java │ │ ├── FilesForTesting.java │ │ ├── acceptance │ │ ├── TestCommandLineVPCoperations.java │ │ └── TestKeyPairCreationAndSave.java │ │ └── UpdateStackExpectations.java └── resources │ └── logback-test.xml ├── src ├── tw │ └── com │ │ ├── pictures │ │ ├── dot │ │ │ ├── Colour.java │ │ │ ├── Renders.java │ │ │ ├── Shape.java │ │ │ ├── CommonElements.java │ │ │ ├── Recorder.java │ │ │ ├── SubGraph.java │ │ │ ├── CommonDiagramElements.java │ │ │ ├── Graph.java │ │ │ ├── Node.java │ │ │ ├── Edge.java │ │ │ ├── NodesAndEdges.java │ │ │ ├── SubGraphFacade.java │ │ │ ├── FileRecorder.java │ │ │ └── HasAttributes.java │ │ ├── HasDiagramId.java │ │ ├── NetworkChildDiagram.java │ │ ├── SecurityChildDiagram.java │ │ ├── ChildDiagram.java │ │ ├── DiagramBuilder.java │ │ ├── DiagramCreator.java │ │ ├── DiagramFactory.java │ │ ├── SubnetDiagrams.java │ │ ├── TemplatedChildDiagram.java │ │ └── Diagram.java │ │ ├── providers │ │ ├── ProvidesNow.java │ │ ├── NotificationSender.java │ │ ├── IdentityProvider.java │ │ ├── RDSClient.java │ │ ├── ProvidesCurrentIp.java │ │ ├── SavesFile.java │ │ └── SNSNotificationSender.java │ │ ├── repository │ │ ├── CloudFormRepository.java │ │ ├── CheckStackExists.java │ │ ├── ResourceRepository.java │ │ ├── SetDeltaIndexForProjectAndEnv.java │ │ ├── LogStreamInterleaver.java │ │ └── StackRepository.java │ │ ├── SetsDeltaIndex.java │ │ ├── ProvidesMonitoringARN.java │ │ ├── commandline │ │ ├── CommandLineException.java │ │ ├── CommandExecutor.java │ │ ├── CommandLineAction.java │ │ ├── actions │ │ │ ├── ElbAction.java │ │ │ ├── ResetAction.java │ │ │ ├── BackAction.java │ │ │ ├── TagLogAction.java │ │ │ ├── PurgeAction.java │ │ │ ├── FileAction.java │ │ │ ├── InitAction.java │ │ │ ├── CreateDiagramAction.java │ │ │ ├── RemoveLogsAction.java │ │ │ ├── TidyOldStacksAction.java │ │ │ ├── AddTagAction.java │ │ │ ├── DeleteByNameAction.java │ │ │ ├── UpdateTargetGroupAction.java │ │ │ ├── InstancesAction.java │ │ │ ├── ListAction.java │ │ │ ├── BlockCurrentIPAction.java │ │ │ ├── DirAction.java │ │ │ ├── DeleteAction.java │ │ │ ├── FetchLogsAction.java │ │ │ ├── AllowCurrentIPAction.java │ │ │ ├── AllowHostAction.java │ │ │ ├── SSHCommandAction.java │ │ │ ├── BlockHostAction.java │ │ │ ├── ListDriftAction.java │ │ │ ├── CreateKeyPairAction.java │ │ │ └── SharedAction.java │ │ └── Actions.java │ │ ├── exceptions │ │ ├── MissingCapabilities.java │ │ ├── NotReadyException.java │ │ ├── MustHaveBuildNumber.java │ │ ├── BadVPCDeltaIndexException.java │ │ ├── TagsAlreadyInit.java │ │ ├── FailedToCreateQueueException.java │ │ ├── InvalidStackParameterException.java │ │ ├── DuplicateStackException.java │ │ ├── TooManyELBException.java │ │ ├── CfnAssistException.java │ │ ├── CannotFindVpcException.java │ │ ├── WrongNumberOfStacksException.java │ │ ├── WrongNumberOfInstancesException.java │ │ └── WrongStackStatus.java │ │ ├── parameters │ │ ├── ProvidesZones.java │ │ ├── CfnBuiltInParams.java │ │ ├── PopulatesParameters.java │ │ └── EnvVarParams.java │ │ ├── TemplateExtensionFilter.java │ │ ├── NotificationProvider.java │ │ ├── ant │ │ ├── ActionElement.java │ │ ├── PurgeElement.java │ │ ├── ELBUpdateElement.java │ │ ├── InitElement.java │ │ ├── SetTagAction.java │ │ ├── BlockCurrentIPElement.java │ │ ├── AllowCurrentIPElement.java │ │ ├── AllowhostElement.java │ │ ├── BlockhostElement.java │ │ ├── TargetGroupUpdateElement.java │ │ ├── TidyStacksElement.java │ │ ├── DeleteElement.java │ │ ├── DiagramsElement.java │ │ └── TemplatesElement.java │ │ ├── entity │ │ ├── EnvironmentTag.java │ │ ├── OutputLogEventDecorator.java │ │ ├── Cidr.java │ │ ├── StackResources.java │ │ ├── DeletionPending.java │ │ ├── StackNameAndId.java │ │ ├── InstanceSummary.java │ │ └── Tagging.java │ │ ├── StackMonitor.java │ │ └── MonitorStackEvents.java ├── cfnScripts │ ├── simpleStack.json │ ├── simpleIAMStack.json │ ├── subnetWithCIDRParam.json │ ├── createS3BucketForTesting.json │ ├── instance.json │ ├── orderedScripts │ │ ├── holding │ │ │ └── 03createRoutes.json │ │ ├── 01createSubnet.json │ │ └── 02createAcls.json │ ├── causesRollBack.json │ ├── subnetWithVPCTagParam.json │ ├── instanceWithTypeTag.json │ ├── subnet.yaml │ ├── simpleStackWithAZ.json │ ├── subnetWithParam.json │ ├── orderedScriptsWithUpdate │ │ ├── 02createSubnet.delta.json │ │ └── 01createSubnet.json │ ├── subnetWithS3Param.json │ ├── subnetWithBuild.json │ ├── acl.json │ ├── targetGroupAndInstance.json │ ├── subnet.json │ ├── subnet.delta.json │ ├── elb.json │ └── elbAndInstance.json └── cfnassist.sh ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .github └── dependabot.yml └── conf └── logback.xml /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "cfnassist" 2 | -------------------------------------------------------------------------------- /test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cartwrightian/cfnassist/HEAD/test/.DS_Store -------------------------------------------------------------------------------- /test/tw/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cartwrightian/cfnassist/HEAD/test/tw/.DS_Store -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/Colour.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | public enum Colour { 4 | Red 5 | 6 | } 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cartwrightian/cfnassist/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/Renders.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | public interface Renders { 4 | void render(Recorder writer); 5 | } 6 | -------------------------------------------------------------------------------- /src/tw/com/pictures/HasDiagramId.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | public interface HasDiagramId { 4 | 5 | String getIdAsString(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/Shape.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | public enum Shape { 4 | Box, Diamond, Octogon, Parallelogram, Box3d, Msquare, InvHouse 5 | } 6 | -------------------------------------------------------------------------------- /src/tw/com/providers/ProvidesNow.java: -------------------------------------------------------------------------------- 1 | package tw.com.providers; 2 | 3 | import java.time.ZonedDateTime; 4 | 5 | public interface ProvidesNow { 6 | ZonedDateTime getUTCNow(); 7 | } 8 | -------------------------------------------------------------------------------- /src/tw/com/repository/CloudFormRepository.java: -------------------------------------------------------------------------------- 1 | package tw.com.repository; 2 | 3 | public interface CloudFormRepository extends CheckStackExists, StackRepository, ResourceRepository { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/tw/com/SetsDeltaIndex.java: -------------------------------------------------------------------------------- 1 | package tw.com; 2 | 3 | import tw.com.exceptions.CannotFindVpcException; 4 | 5 | public interface SetsDeltaIndex { 6 | void setDeltaIndex(Integer newDelta) throws CannotFindVpcException; 7 | } 8 | -------------------------------------------------------------------------------- /src/tw/com/ProvidesMonitoringARN.java: -------------------------------------------------------------------------------- 1 | package tw.com; 2 | 3 | import tw.com.exceptions.NotReadyException; 4 | 5 | public interface ProvidesMonitoringARN { 6 | 7 | String getSNSArn() throws NotReadyException; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/tw/com/repository/CheckStackExists.java: -------------------------------------------------------------------------------- 1 | package tw.com.repository; 2 | 3 | import tw.com.exceptions.WrongNumberOfStacksException; 4 | 5 | public interface CheckStackExists { 6 | boolean stackExists(String stackName) throws WrongNumberOfStacksException; 7 | } 8 | -------------------------------------------------------------------------------- /src/tw/com/commandline/CommandLineException.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline; 2 | 3 | @SuppressWarnings("serial") 4 | public class CommandLineException extends Exception { 5 | 6 | public CommandLineException(String msg) { 7 | super(msg); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/tw/com/pictures/NetworkChildDiagram.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | public class NetworkChildDiagram extends TemplatedChildDiagram { 4 | 5 | public NetworkChildDiagram(ChildDiagram contained) { 6 | super(contained); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/MissingCapabilities.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class MissingCapabilities extends CfnAssistException { 5 | 6 | public MissingCapabilities(String msg) { 7 | super(msg); 8 | 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/NotReadyException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | 4 | @SuppressWarnings("serial") 5 | public class NotReadyException extends CfnAssistException { 6 | 7 | public NotReadyException(String msg) { 8 | super(msg); 9 | 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/tw/com/parameters/ProvidesZones.java: -------------------------------------------------------------------------------- 1 | package tw.com.parameters; 2 | 3 | import software.amazon.awssdk.services.ec2.model.AvailabilityZone; 4 | 5 | import java.util.Map; 6 | 7 | public interface ProvidesZones { 8 | Map getZones(); 9 | } 10 | -------------------------------------------------------------------------------- /src/tw/com/pictures/SecurityChildDiagram.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | public class SecurityChildDiagram extends TemplatedChildDiagram { 4 | 5 | public SecurityChildDiagram(ChildDiagram contained) { 6 | super(contained); 7 | } 8 | 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/MustHaveBuildNumber.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class MustHaveBuildNumber extends CfnAssistException { 5 | 6 | public MustHaveBuildNumber() { 7 | super("Must provide a build number"); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/BadVPCDeltaIndexException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class BadVPCDeltaIndexException extends CfnAssistException { 5 | public BadVPCDeltaIndexException(String tag) { 6 | super("Cannot understand tag:" +tag); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/TagsAlreadyInit.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | 4 | @SuppressWarnings("serial") 5 | public class TagsAlreadyInit extends CfnAssistException { 6 | 7 | public TagsAlreadyInit(String vpcId) { 8 | super("TagsAlreadyInit [vpcId=" + vpcId + "]"); 9 | 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/tw/com/providers/NotificationSender.java: -------------------------------------------------------------------------------- 1 | package tw.com.providers; 2 | 3 | import tw.com.entity.CFNAssistNotification; 4 | import tw.com.exceptions.CfnAssistException; 5 | 6 | public interface NotificationSender { 7 | String sendNotification(CFNAssistNotification notification) throws CfnAssistException; 8 | } 9 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/FailedToCreateQueueException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class FailedToCreateQueueException extends CfnAssistException { 5 | 6 | public FailedToCreateQueueException(String queueId) { 7 | super("Failed to create SQS queue: " + queueId); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/InvalidStackParameterException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class InvalidStackParameterException extends CfnAssistException { 5 | 6 | public InvalidStackParameterException(String parameterName) { 7 | super(parameterName + " is invalid"); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/DuplicateStackException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class DuplicateStackException extends CfnAssistException { 5 | 6 | public DuplicateStackException(String stackname) { 7 | super("Attempted to create a duplicated stack, name was " + stackname); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/TooManyELBException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class TooManyELBException extends CfnAssistException { 5 | 6 | public TooManyELBException(int found, String msg) { 7 | super(String.format("Found too many loadbalancer, expected one and got %s. %s",found,msg)); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.war 5 | *.ear 6 | *.swp 7 | *.log 8 | junit 9 | 10 | .DS_Store 11 | /dist 12 | /out 13 | /build 14 | /bin 15 | network_diagramvpcId.dot 16 | security_diagramvpcId.dot 17 | network_diagramvpc-*.dot 18 | security_diagramvpc*.dot 19 | .gradle 20 | .idea 21 | .env 22 | cfnassist.iml 23 | cfnassist.ipr 24 | cfnassist.iws 25 | .envrc 26 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/CfnAssistException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class CfnAssistException extends Exception { 5 | 6 | public CfnAssistException(String msg) { 7 | super(msg); 8 | } 9 | 10 | public CfnAssistException(String message, Throwable throwable) { 11 | super(message, throwable); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/CommonElements.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | import tw.com.exceptions.CfnAssistException; 4 | 5 | public interface CommonElements { 6 | void addSecurityGroup(String id, String label) throws CfnAssistException; 7 | 8 | void addPortRange(String uniqueId, String label) throws CfnAssistException; 9 | 10 | void connectWithLabel(String uniqueIdA, String uniqueIdB, String label); 11 | } 12 | -------------------------------------------------------------------------------- /src/cfnScripts/simpleStack.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - does nothing much", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | } 11 | 12 | }, 13 | "Resources":{ 14 | "handle":{ 15 | "Type":"AWS::CloudFormation::WaitConditionHandle", 16 | "Properties":{ 17 | 18 | } 19 | 20 | } 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /src/tw/com/exceptions/CannotFindVpcException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | import tw.com.entity.ProjectAndEnv; 4 | 5 | @SuppressWarnings("serial") 6 | public class CannotFindVpcException extends CfnAssistException { 7 | 8 | public CannotFindVpcException(ProjectAndEnv projAndEnv) { 9 | super("CannotFindVpcException " + projAndEnv.toString()); 10 | } 11 | 12 | public CannotFindVpcException(String id) { 13 | super("CannotFindVpcException " + id); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/tw/com/TemplateExtensionFilter.java: -------------------------------------------------------------------------------- 1 | package tw.com; 2 | 3 | import java.io.File; 4 | import java.io.FilenameFilter; 5 | 6 | import org.apache.commons.io.FilenameUtils; 7 | 8 | public class TemplateExtensionFilter implements FilenameFilter { 9 | 10 | @Override 11 | public boolean accept(File dir, String name) { 12 | boolean json = FilenameUtils.getExtension(name).endsWith("json"); 13 | boolean yaml = FilenameUtils.getExtension(name).endsWith("yaml"); 14 | return json || yaml; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/tw/com/commandline/CommandExecutor.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | public class CommandExecutor { 7 | public void execute(List command) throws IOException, InterruptedException { 8 | ProcessBuilder processBuilder = new ProcessBuilder(); 9 | processBuilder.command(command); 10 | processBuilder.inheritIO(); 11 | Process process = processBuilder.start(); 12 | process.waitFor(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/Recorder.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | import java.io.IOException; 4 | 5 | import software.amazon.awssdk.services.ec2.model.Vpc; 6 | 7 | public interface Recorder { 8 | 9 | public abstract void write(String string); 10 | 11 | public abstract void writeline(String string); 12 | 13 | public abstract void writeLabel(String label); 14 | 15 | public abstract void beginFor(Vpc vpc, String prefix) throws IOException; 16 | 17 | public abstract void end() throws IOException; 18 | 19 | } -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/SubGraph.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | 4 | public class SubGraph extends NodesAndEdges implements Renders { 5 | 6 | private String id; 7 | public SubGraph(String id) { 8 | this.id = id; 9 | } 10 | 11 | public String getId() { 12 | return id; 13 | } 14 | 15 | @Override 16 | public void render(Recorder recorder) { 17 | recorder.writeline(String.format("subgraph \"%s\" {",id)); 18 | renderNodesAndEdges(recorder, true); 19 | recorder.writeline("}"); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/WrongNumberOfStacksException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | 4 | @SuppressWarnings("serial") 5 | public class WrongNumberOfStacksException extends CfnAssistException { 6 | 7 | private int actualNumber; 8 | 9 | public WrongNumberOfStacksException(int expectedNumber, int actualNumber) { 10 | super("Expected " + expectedNumber + " stacks, but got " + actualNumber); 11 | this.actualNumber = actualNumber; 12 | } 13 | 14 | public int getNumber() { 15 | return actualNumber; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/cfnScripts/simpleIAMStack.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - added to reproduce issue with aws api where IAM CAPABILITIES must be passed to create call", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | } 11 | 12 | }, 13 | "Resources":{ 14 | "webServerInstanceProfile" : { 15 | "Type" : "AWS::IAM::InstanceProfile", 16 | "Properties": { 17 | "Path": "/", 18 | "Roles": [ "Webserver" ] 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | open-pull-requests-limit: 20 13 | commit-message: 14 | prefix: "[skip ci]" 15 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/WrongNumberOfInstancesException.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class WrongNumberOfInstancesException extends CfnAssistException { 5 | 6 | private String id; 7 | private int number; 8 | 9 | public WrongNumberOfInstancesException(String id, int number) { 10 | super(String.format("Found wrong number of instances (%s) for instance id: %s", number, id)); 11 | this.id = id; 12 | this.number = number; 13 | } 14 | 15 | public String getId() { 16 | return id; 17 | } 18 | 19 | public int getNumber() { 20 | return number; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/tw/com/pictures/ChildDiagram.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | 4 | import tw.com.exceptions.CfnAssistException; 5 | import tw.com.pictures.dot.CommonElements; 6 | import tw.com.pictures.dot.Recorder; 7 | 8 | public interface ChildDiagram extends CommonElements { 9 | 10 | void addInstance(String uniqueId, String label) throws CfnAssistException; 11 | 12 | void render(Recorder recorder); 13 | 14 | void addRouteTable(String uniqueId, String label) throws CfnAssistException; 15 | 16 | public void connectWithLabel(String uniqueAId, String uniqueBId, String label); 17 | 18 | String getId(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /test/tw/com/integration/TestGetCurrentIpProvider.java: -------------------------------------------------------------------------------- 1 | package tw.com.integration; 2 | 3 | import java.net.InetAddress; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import tw.com.exceptions.CfnAssistException; 8 | import tw.com.providers.ProvidesCurrentIp; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertNotNull; 11 | 12 | public class TestGetCurrentIpProvider { 13 | 14 | @Test 15 | public void shouldGetCurrentIp() throws CfnAssistException { 16 | ProvidesCurrentIp provider = new ProvidesCurrentIp(); 17 | InetAddress result = provider.getCurrentIp(); 18 | assertNotNull(result); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/tw/com/NotificationProvider.java: -------------------------------------------------------------------------------- 1 | package tw.com; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.commons.cli.MissingArgumentException; 6 | 7 | import tw.com.entity.StackNotification; 8 | import tw.com.exceptions.FailedToCreateQueueException; 9 | import tw.com.exceptions.NotReadyException; 10 | 11 | public interface NotificationProvider extends ProvidesMonitoringARN { 12 | 13 | public abstract void init() throws MissingArgumentException, FailedToCreateQueueException, InterruptedException; 14 | 15 | public abstract List receiveNotifications() throws NotReadyException; 16 | 17 | public abstract boolean isInit(); 18 | 19 | } -------------------------------------------------------------------------------- /src/tw/com/pictures/DiagramBuilder.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | 4 | import java.io.IOException; 5 | import java.util.LinkedList; 6 | 7 | import tw.com.pictures.dot.Recorder; 8 | 9 | 10 | public class DiagramBuilder { 11 | 12 | private LinkedList vpcDiagrams; 13 | 14 | public DiagramBuilder() { 15 | vpcDiagrams = new LinkedList(); 16 | } 17 | 18 | public void add(VPCDiagramBuilder diagram) { 19 | vpcDiagrams.add(diagram); 20 | } 21 | 22 | public void render(Recorder recorder) throws IOException { 23 | for(VPCDiagramBuilder diagram : vpcDiagrams) { 24 | diagram.render(recorder); 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/tw/com/exceptions/WrongStackStatus.java: -------------------------------------------------------------------------------- 1 | package tw.com.exceptions; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.StackStatus; 4 | import tw.com.entity.StackNameAndId; 5 | 6 | @SuppressWarnings("serial") 7 | public class WrongStackStatus extends CfnAssistException { 8 | 9 | private StackNameAndId stackId; 10 | 11 | public WrongStackStatus(StackNameAndId stackId, StackStatus requiredStatus, StackStatus actual) { 12 | super(String.format("Got an unexpected stack status for %s, expected %s and got %s", stackId, requiredStatus, actual)); 13 | this.stackId = stackId; 14 | } 15 | 16 | public StackNameAndId getStackId() { 17 | return stackId; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/cfnassist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # get the bin directory the script is actually stored in; for details see: 4 | # http://stackoverflow.com/questions/59895/can-a-bash-script-tell-what-directory-its-stored-in 5 | SOURCE="${BASH_SOURCE[0]}" 6 | while [ -h "$SOURCE" ]; do 7 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 8 | SOURCE="$(readlink "$SOURCE")" 9 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" 10 | done 11 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" 12 | 13 | # the home directory is the directory that the bin directory is in 14 | CFNA_HOME=`dirname $DIR` 15 | 16 | # run java 17 | java -cp "$CFNA_HOME/lib/cfnassist.jar:$CFNA_HOME/conf:$CFNA_HOME/lib/*" tw.com.commandline.Main $* 18 | -------------------------------------------------------------------------------- /src/tw/com/ant/ActionElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineException; 7 | import tw.com.entity.ProjectAndEnv; 8 | import tw.com.exceptions.CfnAssistException; 9 | 10 | import java.io.IOException; 11 | import java.util.Collection; 12 | 13 | public interface ActionElement { 14 | 15 | void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, 16 | Collection cfnParams) 17 | throws IOException, InterruptedException, CfnAssistException, CommandLineException, MissingArgumentException; 18 | } -------------------------------------------------------------------------------- /conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | System.err 8 | 9 | 10 | 11 | cfnassist.log 12 | true 13 | 14 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/tw/com/repository/ResourceRepository.java: -------------------------------------------------------------------------------- 1 | package tw.com.repository; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | 7 | import software.amazon.awssdk.services.elasticloadbalancing.model.Instance; 8 | import tw.com.entity.EnvironmentTag; 9 | import tw.com.entity.SearchCriteria; 10 | import tw.com.exceptions.CfnAssistException; 11 | 12 | public interface ResourceRepository { 13 | 14 | String findPhysicalIdByLogicalId(EnvironmentTag envTag, String logicalId); 15 | 16 | List getAllInstancesFor(SearchCriteria criteria) throws CfnAssistException; 17 | 18 | Set getAllInstancesMatchingType(SearchCriteria criteria, String typeTag) throws CfnAssistException; 19 | 20 | List getInstancesFor(String Stackname); 21 | 22 | } -------------------------------------------------------------------------------- /src/tw/com/repository/SetDeltaIndexForProjectAndEnv.java: -------------------------------------------------------------------------------- 1 | package tw.com.repository; 2 | 3 | import tw.com.SetsDeltaIndex; 4 | import tw.com.entity.ProjectAndEnv; 5 | import tw.com.exceptions.CannotFindVpcException; 6 | 7 | public class SetDeltaIndexForProjectAndEnv implements SetsDeltaIndex { 8 | 9 | private ProjectAndEnv projAndEnv; 10 | private VpcRepository vpcRepository; 11 | 12 | public SetDeltaIndexForProjectAndEnv(ProjectAndEnv projAndEnv, VpcRepository vpcRepository) { 13 | this.projAndEnv = projAndEnv; 14 | this.vpcRepository = vpcRepository; 15 | } 16 | 17 | @Override 18 | public void setDeltaIndex(Integer newDelta) throws CannotFindVpcException { 19 | vpcRepository.setVpcIndexTag(projAndEnv, newDelta.toString()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/cfnScripts/subnetWithCIDRParam.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description" : "test template for cnfassist - creates a single subnet with the CIDR passed in as a parameter", 4 | "Parameters" : { 5 | "env" : { "Type" : "String" }, 6 | "vpc" : { "Type" : "String" }, 7 | "cidr" : { "Type" : "String" , "Description" : "cidr to use" }, 8 | "zoneA" : { "Type" : "String" , "Default" : "eu-west-1a" } 9 | }, 10 | "Resources" : { 11 | "testCidrSubnet" : { 12 | "Type" : "AWS::EC2::Subnet", 13 | "Properties" : { 14 | "AvailabilityZone" : { "Ref" : "zoneA" }, 15 | "CidrBlock" : { "Ref" : "cidr" }, 16 | "Tags" : [ { "Key" : "Name", "Value": "testSubnet" } ], 17 | "VpcId" : { "Ref": "vpc" } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/cfnScripts/createS3BucketForTesting.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"create the S3 bucket used as part of the integration tests, BucketName must be unique globally", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | } 11 | 12 | }, 13 | "Resources":{ 14 | "S3TestBucket": { "Type" : "AWS::S3::Bucket", 15 | "Properties" : { 16 | "AccessControl" : "Private", 17 | "BucketName" : "cfnassists3testbucket" 18 | } 19 | } 20 | }, 21 | "Outputs": { 22 | "S3BucketSecureURL" : { 23 | "Value" : { "Fn::Join" : [ "", [ "https://", { "Fn::GetAtt" : [ "S3TestBucket", "DomainName" ] } ] ] }, 24 | "Description" : "Name of S3 bucket to hold website content" 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/cfnScripts/instance.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - creates a simple instance", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | }, 16 | "subnet": { "Type":"String", "Description":"::vpcSubnet" } 17 | }, 18 | "Resources":{ 19 | "simpleInstance":{ 20 | "Type":"AWS::EC2::Instance", 21 | "Properties":{ 22 | "InstanceType":"t2.micro", 23 | "ImageId":"ami-0f3164307ee5d695a", 24 | "SubnetId":{ 25 | "Ref":"subnet" 26 | }, 27 | "Tags": [ 28 | { "Key" : "Name", "Value": "testInstance" } 29 | ] 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/CommonDiagramElements.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | import tw.com.exceptions.CfnAssistException; 4 | 5 | public class CommonDiagramElements implements CommonElements { 6 | 7 | NodesAndEdges graph; 8 | 9 | public CommonDiagramElements(NodesAndEdges graph) { 10 | this.graph = graph; 11 | } 12 | 13 | @Override 14 | public void addSecurityGroup(String id, String label) throws CfnAssistException { 15 | graph.addNode(id).withLabel(label).withShape(Shape.Box); 16 | } 17 | 18 | @Override 19 | public void addPortRange(String uniqueId, String label) throws CfnAssistException { 20 | graph.addNode(uniqueId).withLabel(label); 21 | } 22 | 23 | @Override 24 | public void connectWithLabel(String uniqueIdA, String uniqueIdB, String label) { 25 | graph.addEdge(uniqueIdA, uniqueIdB).withLabel(label); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/cfnScripts/orderedScripts/holding/03createRoutes.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"03 - create route table association", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "webSubnet":{ 12 | "Type":"String", 13 | "Description":"::webSubnet" 14 | }, 15 | "dbSubnet":{ 16 | "Type":"String", 17 | "Description":"::dbSubnet" 18 | }, 19 | "dbRouteTable": { 20 | "Type":"String", 21 | "Description":"::dbRouteTable" 22 | } 23 | }, 24 | "Resources":{ 25 | "dbRouteTableAssociation" : { 26 | "Type" : "AWS::EC2::SubnetRouteTableAssociation", 27 | "Properties" : { 28 | "SubnetId" : { "Ref" : "dbSubnet" }, 29 | "RouteTableId" : { "Ref" : "dbRouteTable" } 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/cfnScripts/causesRollBack.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - DELIBERATELY CAUSE A ROLLBACK", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | } 11 | 12 | }, 13 | "Resources":{ 14 | "loadBalancer":{ 15 | "Type":"AWS::ElasticLoadBalancing::LoadBalancer", 16 | "Properties":{ 17 | "HealthCheck":{ 18 | "HealthyThreshold":"2", 19 | "Interval":"15", 20 | "Target":"HTTP:8080/api/status", 21 | "Timeout":"5", 22 | "UnhealthyThreshold":"2" 23 | }, 24 | "Listeners":[ 25 | { 26 | "InstancePort":"8082", 27 | "LoadBalancerPort":"80", 28 | "Protocol":"HTTP", 29 | "PolicyNames":[ 30 | 31 | ] 32 | 33 | } 34 | 35 | ] 36 | 37 | } 38 | 39 | } 40 | 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/tw/com/pictures/DiagramCreator.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import tw.com.exceptions.CfnAssistException; 7 | import tw.com.pictures.dot.Recorder; 8 | import software.amazon.awssdk.services.ec2.model.Vpc; 9 | 10 | public class DiagramCreator { 11 | 12 | private AmazonVPCFacade facade; 13 | 14 | public DiagramCreator(AmazonVPCFacade facade) { 15 | this.facade = facade; 16 | } 17 | 18 | public void createDiagrams(Recorder recorder) throws IOException, CfnAssistException { 19 | 20 | List vpcs = facade.getVpcs(); 21 | 22 | DiagramBuilder diagrams = new DiagramBuilder(); 23 | DiagramFactory diagramFactory = new DiagramFactory(); 24 | VPCVisitor visitor = new VPCVisitor(diagrams, facade, diagramFactory); 25 | for(Vpc vpc : vpcs) { 26 | visitor.visit(vpc); 27 | } 28 | diagrams.render(recorder); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/cfnScripts/subnetWithVPCTagParam.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - creates a single subnet, ZoneA required as a parameter", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | }, 16 | "testVPCTAG":{ 17 | "Type":"String", 18 | "Description":"::CFN_TAG" 19 | } 20 | }, 21 | "Resources":{ 22 | "testSubnet":{ 23 | "Type":"AWS::EC2::Subnet", 24 | "Properties":{ 25 | "AvailabilityZone":{ 26 | "Ref":"zoneA" 27 | }, 28 | "CidrBlock":"10.0.10.0/24", 29 | "Tags":[ 30 | { 31 | "Key":"expectedTAG", "Value":{ "Ref":"testVPCTAG"} 32 | } 33 | ], 34 | "VpcId":{ 35 | "Ref":"vpc" 36 | } 37 | 38 | } 39 | 40 | } 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /src/cfnScripts/instanceWithTypeTag.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - creates a simple instance with a CFN_ASSIST_TYPE tag set", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | }, 16 | "subnet": { "Type":"String", "Description":"::vpcSubnet" } 17 | }, 18 | "Resources":{ 19 | "simpleInstance":{ 20 | "Type":"AWS::EC2::Instance", 21 | "Properties":{ 22 | "InstanceType":"t2.micro", 23 | "ImageId":"ami-0f3164307ee5d695a", 24 | "SubnetId":{ 25 | "Ref":"subnet" 26 | }, 27 | "Tags": [ 28 | { "Key" : "Name", "Value": "anotherTestInstance" }, 29 | { "Key" : "CFN_ASSIST_TYPE", "Value": "web" } 30 | ] 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/cfnScripts/subnet.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Description: A sample template 3 | Parameters: 4 | env: 5 | Type: "String" 6 | vpc: 7 | Type: "String" 8 | zoneA: 9 | Type: "String" 10 | Default: "eu-west-1a" 11 | Description: "zoneADescription" 12 | zoneB: 13 | Type: "String" 14 | Default: "eu-west-1b" 15 | Resources: 16 | testSubnet: 17 | Type: "AWS::EC2::Subnet" 18 | Properties: 19 | AvailabilityZone: 20 | Ref: zoneA 21 | CidrBlock: "10.0.42.0/24" 22 | Tags: 23 | - 24 | Key: "Name" 25 | Value: "testSubnet" 26 | - 27 | Key: "TagEnv" 28 | Value: 29 | Ref: env 30 | VpcId: 31 | Ref: vpc 32 | Outputs: 33 | SUBNET: 34 | Value: !Ref testSubnet 35 | Description: "::CFN_TAG" 36 | notused: 37 | Value: !Ref testSubnet 38 | Description: "should not get picked up by cfn assist" 39 | -------------------------------------------------------------------------------- /src/tw/com/pictures/DiagramFactory.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | import software.amazon.awssdk.services.ec2.model.Subnet; 4 | import software.amazon.awssdk.services.ec2.model.Vpc; 5 | 6 | import tw.com.exceptions.CfnAssistException; 7 | import tw.com.pictures.dot.GraphFacade; 8 | 9 | public class DiagramFactory { 10 | 11 | public VPCDiagramBuilder createVPCDiagramBuilder(Vpc vpc) { 12 | GraphFacade networkDiagram = new GraphFacade(); 13 | GraphFacade securityDiagram = new GraphFacade(); 14 | return new VPCDiagramBuilder(vpc, networkDiagram, securityDiagram); 15 | } 16 | 17 | public SubnetDiagramBuilder createSubnetDiagramBuilder(VPCDiagramBuilder parentBuilder, Subnet subnet) throws CfnAssistException { 18 | NetworkChildDiagram diagram = parentBuilder.createNetworkDiagramForSubnet(subnet); 19 | SecurityChildDiagram securityDiagram = parentBuilder.createSecurityDiagramForSubnet(subnet); 20 | return new SubnetDiagramBuilder(diagram, securityDiagram, subnet); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/cfnScripts/simpleStackWithAZ.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - does nothing much", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", "Description": "::CFN_ZONE_A" 13 | }, 14 | "zoneB":{ 15 | "Type":"String", "Description": "::CFN_ZONE_B" 16 | } 17 | }, 18 | "Resources":{ 19 | "testSubnetZoneA": { 20 | "Type": "AWS::EC2::Subnet", 21 | "Properties": { 22 | "AvailabilityZone": { 23 | "Ref": "zoneA" 24 | }, 25 | "CidrBlock": "10.0.10.0/24", 26 | "VpcId": { 27 | "Ref": "vpc" 28 | } 29 | } 30 | }, 31 | "testSubnetZoneB": { 32 | "Type": "AWS::EC2::Subnet", 33 | "Properties": { 34 | "AvailabilityZone": { 35 | "Ref": "zoneB" 36 | }, 37 | "CidrBlock": "10.0.11.0/24", 38 | "VpcId": { 39 | "Ref": "vpc" 40 | } 41 | } 42 | } 43 | 44 | } 45 | } -------------------------------------------------------------------------------- /src/cfnScripts/subnetWithParam.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - creates a single subnet, ZoneA required as a parameter", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Description":"zoneADescription" 14 | }, 15 | "zoneB":{ 16 | "Type":"String", 17 | "Default":"eu-west-1b" 18 | } 19 | }, 20 | "Resources":{ 21 | "testSubnet":{ 22 | "Type":"AWS::EC2::Subnet", 23 | "Properties":{ 24 | "AvailabilityZone":{ 25 | "Ref":"zoneA" 26 | }, 27 | "CidrBlock":"10.0.10.0/24", 28 | "Tags":[ 29 | { 30 | "Key":"Name", 31 | "Value":"webSubnetZoneA" 32 | }, 33 | { 34 | "Key":"TagEnv", 35 | "Value":{ 36 | "Ref":"env" 37 | } 38 | 39 | } 40 | ], 41 | "VpcId":{ 42 | "Ref":"vpc" 43 | } 44 | 45 | } 46 | 47 | } 48 | 49 | } 50 | } -------------------------------------------------------------------------------- /src/tw/com/entity/EnvironmentTag.java: -------------------------------------------------------------------------------- 1 | package tw.com.entity; 2 | 3 | public class EnvironmentTag { 4 | 5 | private String env; 6 | 7 | @Override 8 | public String toString() { 9 | return "EnvironmentTag [env=" + env + "]"; 10 | } 11 | 12 | @Override 13 | public int hashCode() { 14 | final int prime = 31; 15 | int result = 1; 16 | result = prime * result + ((env == null) ? 0 : env.hashCode()); 17 | return result; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object obj) { 22 | if (this == obj) 23 | return true; 24 | if (obj == null) 25 | return false; 26 | if (getClass() != obj.getClass()) 27 | return false; 28 | EnvironmentTag other = (EnvironmentTag) obj; 29 | if (env == null) { 30 | if (other.env != null) 31 | return false; 32 | } else if (!env.equals(other.env)) 33 | return false; 34 | return true; 35 | } 36 | 37 | public EnvironmentTag(String env) { 38 | this.env = env; 39 | } 40 | 41 | public String getEnv() { 42 | return env; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/tw/com/commandline/CommandLineAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.apache.commons.cli.Option; 5 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 6 | import tw.com.FacadeFactory; 7 | import tw.com.entity.ProjectAndEnv; 8 | import tw.com.exceptions.CfnAssistException; 9 | 10 | import java.io.IOException; 11 | import java.util.Collection; 12 | 13 | public interface CommandLineAction { 14 | 15 | Option getOption(); 16 | 17 | String getArgName(); 18 | 19 | void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 20 | String... argument) throws 21 | IOException, InterruptedException, CfnAssistException, MissingArgumentException; 22 | 23 | void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 24 | String... argumentForAction) throws CommandLineException; 25 | 26 | boolean usesProject(); 27 | boolean usesComment(); 28 | boolean usesSNS(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/cfnScripts/orderedScriptsWithUpdate/02createSubnet.delta.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"02 - updates the CIDR block for the web subnet", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | }, 16 | "zoneB":{ 17 | "Type":"String", 18 | "Default":"eu-west-1b" 19 | }, 20 | "stackname":{ 21 | "Type": "String", 22 | "Default":"01createSubnet", 23 | "Description":"The name of the stack to apply this update to" 24 | } 25 | }, 26 | "Resources":{ 27 | "webSubnet":{ 28 | "Type":"AWS::EC2::Subnet", 29 | "Properties":{ 30 | "AvailabilityZone":{ 31 | "Ref":"zoneA" 32 | }, 33 | "CidrBlock":"10.0.99.0/24", 34 | "Tags":[ 35 | { 36 | "Key":"Name", 37 | "Value":"webSubnet" 38 | } 39 | ], 40 | "VpcId":{ 41 | "Ref":"vpc" 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/tw/com/ant/PurgeElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineAction; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.commandline.actions.PurgeAction; 9 | import tw.com.entity.ProjectAndEnv; 10 | import tw.com.exceptions.CfnAssistException; 11 | 12 | import java.io.IOException; 13 | import java.util.Collection; 14 | 15 | public class PurgeElement implements ActionElement { 16 | 17 | public PurgeElement() { 18 | } 19 | 20 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams) 21 | throws IOException, InterruptedException, CfnAssistException, CommandLineException, MissingArgumentException { 22 | 23 | CommandLineAction actionToInvoke = new PurgeAction(); 24 | 25 | actionToInvoke.validate(projectAndEnv, cfnParams); 26 | actionToInvoke.invoke(factory, projectAndEnv, cfnParams); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /test/tw/com/integration/TestIdentityProvider.java: -------------------------------------------------------------------------------- 1 | package tw.com.integration; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import software.amazon.awssdk.services.iam.IamClient; 6 | import software.amazon.awssdk.services.iam.model.User; 7 | import tw.com.EnvironmentSetupForTests; 8 | import tw.com.providers.IdentityProvider; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | import static org.junit.jupiter.api.Assertions.assertNotNull; 12 | 13 | public class TestIdentityProvider { 14 | 15 | private IdentityProvider identityProvider; 16 | private IamClient iamClient; 17 | 18 | @BeforeEach 19 | public void shouldRunBeforeEachTest() { 20 | iamClient = EnvironmentSetupForTests.createIamClient(); 21 | identityProvider = new IdentityProvider(iamClient); 22 | } 23 | 24 | @Test 25 | public void shouldGetUserId() { 26 | User result = identityProvider.getUserId(); 27 | assertNotNull(result); 28 | assertFalse(result.userName().isEmpty()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/tw/com/unit/TestCidr.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import tw.com.entity.Cidr; 7 | import tw.com.exceptions.CfnAssistException; 8 | 9 | class TestCidr { 10 | 11 | @Test 12 | void shouldHaveDefaultCidr() { 13 | Cidr cidr = Cidr.Default(); 14 | Assertions.assertTrue(cidr.isDefault()); 15 | } 16 | 17 | @Test 18 | void shouldParseDefaultCidr() throws CfnAssistException { 19 | Cidr cidr = Cidr.parse("0.0.0.0/0"); 20 | 21 | Assertions.assertTrue(cidr.isDefault()); 22 | } 23 | 24 | @Test 25 | void shouldParseCidr() throws CfnAssistException { 26 | Cidr cidr = Cidr.parse("192.168.0.2/32"); 27 | 28 | Assertions.assertFalse(cidr.isDefault()); 29 | Assertions.assertEquals("192.168.0.2/32", cidr.toString()); 30 | } 31 | 32 | @Test 33 | void shouldThrowOnBadFormat() { 34 | try { 35 | Cidr.parse("notvalid/xx"); 36 | Assertions.fail("Should have thrown"); 37 | } catch (CfnAssistException expected) { 38 | // no op 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/cfnScripts/orderedScripts/01createSubnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"01 - creates subnets", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | }, 16 | "zoneB":{ 17 | "Type":"String", 18 | "Default":"eu-west-1b" 19 | } 20 | 21 | }, 22 | "Resources":{ 23 | "webSubnet":{ 24 | "Type":"AWS::EC2::Subnet", 25 | "Properties":{ 26 | "AvailabilityZone":{ 27 | "Ref":"zoneA" 28 | }, 29 | "CidrBlock":"10.0.14.0/24", 30 | "Tags":[ 31 | { 32 | "Key":"Name", 33 | "Value":"webSubnet" 34 | } 35 | ], 36 | "VpcId":{ 37 | "Ref":"vpc" 38 | } 39 | } 40 | }, 41 | "dbSubnet":{ 42 | "Type":"AWS::EC2::Subnet", 43 | "Properties":{ 44 | "AvailabilityZone":{ 45 | "Ref":"zoneA" 46 | }, 47 | "CidrBlock":"10.0.60.0/24", 48 | "VpcId":{ 49 | "Ref":"vpc" 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/cfnScripts/orderedScriptsWithUpdate/01createSubnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"01 - creates subnets", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | }, 16 | "zoneB":{ 17 | "Type":"String", 18 | "Default":"eu-west-1b" 19 | } 20 | }, 21 | "Resources":{ 22 | "webSubnet":{ 23 | "Type":"AWS::EC2::Subnet", 24 | "Properties":{ 25 | "AvailabilityZone":{ 26 | "Ref":"zoneA" 27 | }, 28 | "CidrBlock":"10.0.14.0/24", 29 | "Tags":[ 30 | { 31 | "Key":"Name", 32 | "Value":"webSubnet" 33 | } 34 | ], 35 | "VpcId":{ 36 | "Ref":"vpc" 37 | } 38 | } 39 | }, 40 | "dbSubnet":{ 41 | "Type":"AWS::EC2::Subnet", 42 | "Properties":{ 43 | "AvailabilityZone":{ 44 | "Ref":"zoneA" 45 | }, 46 | "CidrBlock":"10.0.60.0/24", 47 | "VpcId":{ 48 | "Ref":"vpc" 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/tw/com/providers/IdentityProvider.java: -------------------------------------------------------------------------------- 1 | package tw.com.providers; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import software.amazon.awssdk.services.iam.IamClient; 7 | import software.amazon.awssdk.services.iam.model.GetUserResponse; 8 | import software.amazon.awssdk.services.iam.model.IamException; 9 | import software.amazon.awssdk.services.iam.model.User; 10 | 11 | public class IdentityProvider { 12 | private static final Logger logger = LoggerFactory.getLogger(IdentityProvider.class); 13 | 14 | private IamClient iamClient; 15 | 16 | public IdentityProvider(IamClient iamClient) { 17 | this.iamClient = iamClient; 18 | } 19 | 20 | // needs iam:GetUser 21 | public User getUserId() { 22 | logger.debug("Get current user"); 23 | try { 24 | GetUserResponse result = iamClient.getUser(); 25 | User user = result.user(); 26 | logger.info("Fetched current user: " + user); 27 | return user; 28 | } 29 | catch(IamException exception) { 30 | logger.warn("Unable to fetch current user: " + exception.toString()); 31 | return null; 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/tw/com/entity/OutputLogEventDecorator.java: -------------------------------------------------------------------------------- 1 | package tw.com.entity; 2 | 3 | 4 | import software.amazon.awssdk.services.cloudwatchlogs.model.OutputLogEvent; 5 | 6 | public class OutputLogEventDecorator implements Comparable { 7 | 8 | private final OutputLogEvent outputLogEvent; 9 | private final String groupName; 10 | private final String streamName; 11 | 12 | public OutputLogEventDecorator(OutputLogEvent outputLogEvent, String groupName, String streamName) { 13 | this.outputLogEvent = outputLogEvent; 14 | this.groupName = groupName; 15 | this.streamName = streamName; 16 | } 17 | 18 | public String getGroupName() { 19 | return groupName; 20 | } 21 | public String toString() { 22 | return String.format("%s %s", streamName, outputLogEvent.message()); 23 | } 24 | 25 | public Long getTimestamp() { 26 | return outputLogEvent.timestamp(); 27 | } 28 | 29 | @Override 30 | public int compareTo(OutputLogEventDecorator o) { 31 | return getTimestamp().compareTo(o.getTimestamp()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/tw/com/providers/RDSClient.java: -------------------------------------------------------------------------------- 1 | package tw.com.providers; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | import software.amazon.awssdk.services.rds.RdsClient; 7 | import software.amazon.awssdk.services.rds.model.DBInstance; 8 | import software.amazon.awssdk.services.rds.model.DBSubnetGroup; 9 | import software.amazon.awssdk.services.rds.model.DescribeDbInstancesResponse; 10 | 11 | public class RDSClient { 12 | RdsClient rdsClient; 13 | 14 | public RDSClient(RdsClient rdsClient) { 15 | this.rdsClient = rdsClient; 16 | } 17 | 18 | public List getDBInstancesForVpc(String vpcId) { 19 | DescribeDbInstancesResponse result = rdsClient.describeDBInstances(); 20 | List dbInstances = result.dbInstances(); 21 | 22 | List filtered = new LinkedList<>(); 23 | for(DBInstance dbInstance : dbInstances) { 24 | DBSubnetGroup dbSubnetGroup = dbInstance.dbSubnetGroup(); 25 | if (dbSubnetGroup!=null) { 26 | String groupVpcId = dbSubnetGroup.vpcId(); 27 | if (groupVpcId.equals(vpcId)) { 28 | filtered.add(dbInstance); 29 | } 30 | } 31 | } 32 | return filtered; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/tw/com/entity/Cidr.java: -------------------------------------------------------------------------------- 1 | package tw.com.entity; 2 | 3 | import org.apache.commons.net.util.SubnetUtils; 4 | 5 | import tw.com.exceptions.CfnAssistException; 6 | 7 | public class Cidr { 8 | private static final String DEFAULT_CIDR = "0.0.0.0/0"; 9 | private SubnetUtils subnet; 10 | private boolean isDefault; 11 | 12 | private Cidr(String string) { 13 | subnet = new SubnetUtils(string); 14 | } 15 | 16 | private Cidr(boolean isDefault) { 17 | this.isDefault = isDefault; 18 | } 19 | 20 | public boolean isDefault() { 21 | return isDefault; 22 | } 23 | 24 | public static Cidr Default() { 25 | return new Cidr(true); 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | if (isDefault()) { 31 | return DEFAULT_CIDR; 32 | } 33 | return subnet.getInfo().getCidrSignature(); 34 | } 35 | 36 | public static Cidr parse(String string) throws CfnAssistException { 37 | if (DEFAULT_CIDR.equals(string)) { 38 | return Default(); 39 | } 40 | try { 41 | return new Cidr(string); 42 | } 43 | catch(IllegalArgumentException exception) { 44 | throw new CfnAssistException("Could not parse cidr " + string); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/tw/com/pictures/SubnetDiagrams.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import software.amazon.awssdk.services.ec2.model.Subnet; 7 | import tw.com.exceptions.CfnAssistException; 8 | 9 | public class SubnetDiagrams { 10 | 11 | private Diagram parentDiagram; 12 | private Map childDiagrams = new HashMap(); // subnetId -> diagram 13 | 14 | public ChildDiagram addDiagramFor(Subnet subnet) throws CfnAssistException { 15 | String subnetName = AmazonVPCFacade.getNameFromTags(subnet.tags()); 16 | String subnetLabel = formSubnetLabel(subnet, subnetName); 17 | String subnetId = subnet.subnetId(); 18 | 19 | ChildDiagram childDiagram = parentDiagram.createSubDiagram(subnetId, subnetLabel); 20 | childDiagrams.put(subnetId,childDiagram); 21 | 22 | return childDiagram; 23 | } 24 | 25 | public static String formSubnetLabel(Subnet subnet, String tagName) { 26 | String name = subnet.subnetId(); 27 | if (!tagName.isEmpty()) { 28 | name = tagName; 29 | } 30 | return String.format("%s [%s]\n(%s)", name, subnet.subnetId(), subnet.cidrBlock()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/Graph.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | public class Graph extends NodesAndEdges implements Renders { 7 | private static final int DIAGRAM_TITLE_FONT_SIZE = 36; 8 | 9 | private List subs = new LinkedList(); 10 | 11 | public Graph() { 12 | addCompound(); 13 | } 14 | 15 | public SubGraph createSubGraph(String id) { 16 | SubGraph subGraph = new SubGraph(id); 17 | subs.add(subGraph); 18 | return subGraph; 19 | } 20 | 21 | public SubGraph createDiagramCluster(String id, String label, int fontSize) { 22 | SubGraph subDiagram = this.createSubGraph("cluster_"+id); 23 | subDiagram.addLabel(label); 24 | subDiagram.fontSize(fontSize); 25 | return subDiagram; 26 | } 27 | 28 | @Override 29 | public void render(Recorder recorder) { 30 | recorder.writeline("digraph G { "); 31 | renderNodesAndEdges(recorder, true); 32 | for(SubGraph sub : subs) { 33 | sub.render(recorder); 34 | } 35 | recorder.write("}"); 36 | } 37 | 38 | public void addTitle(String title) { 39 | addLabel(title); 40 | fontSize(DIAGRAM_TITLE_FONT_SIZE); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/cfnScripts/subnetWithS3Param.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - creates a single subnet, urlA and urlB required as a parameters", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | }, 16 | "urlA":{ 17 | "Type":"String" 18 | }, 19 | "urlB":{ 20 | "Type":"String" 21 | } 22 | 23 | }, 24 | "Resources":{ 25 | "testSubnet":{ 26 | "Type":"AWS::EC2::Subnet", 27 | "Properties":{ 28 | "AvailabilityZone":{ 29 | "Ref":"zoneA" 30 | }, 31 | "CidrBlock":"10.0.43.0/24", 32 | "Tags":[ 33 | { 34 | "Key":"Name", 35 | "Value":"webSubnetZoneA" 36 | }, 37 | { 38 | "Key":"TagEnv", "Value":{ "Ref":"env"} 39 | }, 40 | { 41 | "Key":"urlATag", "Value":{ "Ref":"urlA"} 42 | }, 43 | { 44 | "Key":"urlBTag", "Value":{ "Ref":"urlB"} 45 | } 46 | ], 47 | "VpcId":{ 48 | "Ref":"vpc" 49 | } 50 | 51 | } 52 | 53 | } 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/Node.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | import tw.com.exceptions.CfnAssistException; 4 | 5 | public class Node extends HasAttributes { 6 | 7 | private String name; 8 | private String target = ""; 9 | private String edgeLabel = ""; 10 | public Node(String name) { 11 | this.name = name; 12 | } 13 | 14 | public void write(Recorder writer) { 15 | writer.write(String.format("\"%s\"",name)); 16 | writeAttributes(writer, false); 17 | writer.writeline(";"); 18 | writeTarget(writer); 19 | } 20 | 21 | private void writeTarget(Recorder writer) { 22 | if (target.isEmpty()) { 23 | return; 24 | } 25 | 26 | writer.write(String.format("\"%s\"->\"%s\" ",name, target)); 27 | if (!edgeLabel.isEmpty()) { 28 | writer.write(String.format(" [ label=\"%s\" ]", edgeLabel)); 29 | } 30 | writer.writeline(";"); 31 | } 32 | 33 | public Node withShape(Shape shape) throws CfnAssistException { 34 | addShape(shape); 35 | return this; 36 | } 37 | 38 | public Node withLabel(String label) { 39 | addLabel(label); 40 | return this; 41 | } 42 | 43 | public Node makeInvisible() { 44 | setStyleInvisible(); 45 | return this; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/tw/com/ant/ELBUpdateElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineException; 7 | import tw.com.commandline.actions.ElbAction; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.io.IOException; 12 | import java.util.Collection; 13 | 14 | public class ELBUpdateElement implements ActionElement { 15 | 16 | private String typeTag; 17 | 18 | public void setTypeTag(String typeTag) { 19 | this.typeTag = typeTag; 20 | } 21 | 22 | public ELBUpdateElement() { 23 | } 24 | 25 | @Override 26 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, 27 | Collection cfnParams) 28 | throws IOException, 29 | InterruptedException, 30 | CfnAssistException, CommandLineException, MissingArgumentException { 31 | ElbAction actionToInvoke = new ElbAction(); 32 | 33 | actionToInvoke.validate(projectAndEnv, cfnParams, typeTag); 34 | actionToInvoke.invoke(factory, projectAndEnv, cfnParams, typeTag); 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/tw/com/ant/InitElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import org.apache.commons.cli.MissingArgumentException; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineAction; 8 | import tw.com.commandline.CommandLineException; 9 | import tw.com.commandline.actions.InitAction; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.io.IOException; 14 | import java.util.Collection; 15 | 16 | public class InitElement implements ActionElement { 17 | private String vpcId; 18 | 19 | public InitElement() { 20 | 21 | } 22 | 23 | @Override 24 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams) throws IOException, InterruptedException, CfnAssistException, CommandLineException, MissingArgumentException { 25 | 26 | CommandLineAction actionToInvoke = new InitAction(); 27 | 28 | actionToInvoke.validate(projectAndEnv, cfnParams, vpcId); 29 | actionToInvoke.invoke(factory, projectAndEnv, cfnParams, vpcId); 30 | } 31 | 32 | public void setVpcId(String vpcId) { 33 | this.vpcId = vpcId; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/tw/com/ant/SetTagAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineAction; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.io.IOException; 12 | import java.util.Collection; 13 | 14 | public class SetTagAction implements ActionElement { 15 | private String name; 16 | private String value; 17 | 18 | @Override 19 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams) throws IOException, InterruptedException, CfnAssistException, CommandLineException, MissingArgumentException { 20 | CommandLineAction action = new tw.com.commandline.actions.AddTagAction(); 21 | 22 | action.validate(projectAndEnv, cfnParams, name, value); 23 | action.invoke(factory, projectAndEnv, cfnParams, name, value); 24 | } 25 | 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | public void setValue(String value) { 31 | this.value = value; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/tw/com/providers/ProvidesCurrentIp.java: -------------------------------------------------------------------------------- 1 | package tw.com.providers; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.net.Inet4Address; 7 | import java.net.InetAddress; 8 | import java.net.URL; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import tw.com.exceptions.CfnAssistException; 14 | 15 | public class ProvidesCurrentIp { 16 | private static final String GETIP_URL = "http://checkip.amazonaws.com"; 17 | private static final Logger logger = LoggerFactory.getLogger(ProvidesCurrentIp.class); 18 | 19 | 20 | public InetAddress getCurrentIp() throws CfnAssistException { 21 | try { 22 | URL whatismyip = new URL(GETIP_URL); 23 | BufferedReader in; 24 | logger.debug("Attempt to fetch public IP from " + GETIP_URL); 25 | in = new BufferedReader(new InputStreamReader(whatismyip.openStream())); 26 | String ip = in.readLine(); 27 | logger.info("Got public IP as " + ip); 28 | in.close(); 29 | return Inet4Address.getByName(ip); 30 | } catch (IOException e) { 31 | throw new CfnAssistException("Unable to get currnet public ip " + e.getMessage()); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/tw/com/unit/TestInstanceSummary.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import tw.com.EnvironmentSetupForTests; 9 | import tw.com.entity.InstanceSummary; 10 | 11 | import software.amazon.awssdk.services.ec2.model.Tag; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | 15 | public class TestInstanceSummary { 16 | 17 | @Test 18 | public void testShouldSummariseTags() { 19 | List tags = new LinkedList<>(); 20 | 21 | tags.add(EnvironmentSetupForTests.createEc2Tag("tag1", "valueA")); 22 | tags.add(EnvironmentSetupForTests.createEc2Tag("tag2", "valueB")); 23 | tags.add(EnvironmentSetupForTests.createEc2Tag("tag3", "valueC")); 24 | 25 | InstanceSummary summary = new InstanceSummary("id", "10.0.0.99", tags); 26 | 27 | String results = summary.getTags(); 28 | 29 | assertEquals("tag1=valueA,tag2=valueB,tag3=valueC", results); 30 | } 31 | 32 | @Test 33 | public void shouldReturnCorrectValues() { 34 | List tags = new LinkedList<>(); 35 | InstanceSummary summary = new InstanceSummary("id", "10.0.0.99", tags); 36 | 37 | assertEquals("id", summary.getInstance()); 38 | assertEquals("10.0.0.99", summary.getPrivateIP()); 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/tw/com/ant/BlockCurrentIPElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineException; 7 | import tw.com.commandline.actions.BlockCurrentIPAction; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.io.IOException; 12 | import java.util.Collection; 13 | 14 | public class BlockCurrentIPElement implements ActionElement { 15 | private String tag; 16 | private String port; 17 | 18 | public BlockCurrentIPElement() { 19 | 20 | } 21 | 22 | public void setTag(String tag) { 23 | this.tag = tag; 24 | } 25 | 26 | public void setPort(String port) { 27 | this.port = port; 28 | } 29 | 30 | @Override 31 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, 32 | Collection cfnParams) 33 | throws IOException, InterruptedException, 34 | CfnAssistException, CommandLineException, MissingArgumentException { 35 | 36 | BlockCurrentIPAction action = new BlockCurrentIPAction(); 37 | 38 | action.validate(projectAndEnv, cfnParams, tag, port); 39 | action.invoke(factory, projectAndEnv, cfnParams, tag, port); 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/tw/com/entity/StackResources.java: -------------------------------------------------------------------------------- 1 | package tw.com.entity; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import software.amazon.awssdk.services.cloudformation.model.StackResource; 11 | 12 | public class StackResources { 13 | private static final Logger logger = LoggerFactory.getLogger(StackResources.class); 14 | 15 | // (StackName) -> [Stack Resources] 16 | private final Map> theResources; 17 | 18 | public StackResources() { 19 | theResources = new HashMap<>(); 20 | } 21 | 22 | public boolean containsStack(String stackName) { 23 | return theResources.containsKey(stackName); 24 | } 25 | 26 | public List getStackResources(String stackName) { 27 | return theResources.get(stackName); 28 | } 29 | 30 | public void addStackResources(String stackName, 31 | List resources) { 32 | logger.debug("Adding resources for stack: " + stackName); 33 | theResources.put(stackName, resources); 34 | } 35 | 36 | public void removeResources(String stackName) { 37 | if (theResources.containsKey(stackName)) { 38 | logger.info("Removing resources for stack: " + stackName); 39 | theResources.remove(stackName); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/tw/com/ant/AllowCurrentIPElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineException; 7 | import tw.com.commandline.actions.AllowCurrentIPAction; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.io.IOException; 12 | import java.util.Collection; 13 | 14 | public class AllowCurrentIPElement implements ActionElement { 15 | private String tag; 16 | private String port; 17 | 18 | public AllowCurrentIPElement() { 19 | 20 | } 21 | 22 | public void setTag(String tag) { 23 | this.tag = tag; 24 | } 25 | 26 | public void setPort(String port) { 27 | this.port = port; 28 | } 29 | 30 | @Override 31 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, 32 | Collection cfnParams) 33 | throws IOException, 34 | InterruptedException, 35 | CfnAssistException, CommandLineException, MissingArgumentException { 36 | 37 | AllowCurrentIPAction action = new AllowCurrentIPAction(); 38 | 39 | action.validate(projectAndEnv, cfnParams, tag, port); 40 | action.invoke(factory, projectAndEnv, cfnParams, tag, port); 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/cfnScripts/subnetWithBuild.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "test template for cnfassist - creates a single subnet, required build parameter", 4 | "Parameters": { 5 | "env": { 6 | "Type": "String" 7 | }, 8 | "vpc": { 9 | "Type": "String" 10 | }, 11 | "zoneA": { 12 | "Type": "String", 13 | "Default": "eu-west-1a", 14 | "Description": "zoneADescription" 15 | }, 16 | "build": { 17 | "Type": "String", 18 | "Description": "Should get populated with build" 19 | } 20 | }, 21 | "Resources": { 22 | "testSubnet": { 23 | "Type": "AWS::EC2::Subnet", 24 | "Properties": { 25 | "AvailabilityZone": { 26 | "Ref": "zoneA" 27 | }, 28 | "CidrBlock": "10.0.10.0/24", 29 | "Tags": [ 30 | { 31 | "Key": "Name", 32 | "Value": "testSubnet" 33 | }, 34 | { 35 | "Key": "TagBuild", 36 | "Value": { 37 | "Ref": "build" 38 | } 39 | }, 40 | { 41 | "Key": "TagEnv", 42 | "Value": { 43 | "Ref": "env" 44 | } 45 | } 46 | ], 47 | "VpcId": { 48 | "Ref": "vpc" 49 | } 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/tw/com/parameters/CfnBuiltInParams.java: -------------------------------------------------------------------------------- 1 | package tw.com.parameters; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 6 | import software.amazon.awssdk.services.cloudformation.model.TemplateParameter; 7 | import tw.com.entity.ProjectAndEnv; 8 | 9 | import java.util.Collection; 10 | import java.util.List; 11 | 12 | public class CfnBuiltInParams extends PopulatesParameters { 13 | private static final Logger logger = LoggerFactory.getLogger(CfnBuiltInParams.class); 14 | 15 | private String vpcId; 16 | 17 | public CfnBuiltInParams(String vpcId) { 18 | this.vpcId = vpcId; 19 | } 20 | 21 | @Override 22 | public void addParameters(Collection result, List declaredParameters, 23 | ProjectAndEnv projAndEnv, ProvidesZones zoneProvider) { 24 | logger.info("Populate built-in parameters"); 25 | addParameterTo(result, declaredParameters, PopulatesParameters.PARAMETER_ENV, projAndEnv.getEnv()); 26 | addParameterTo(result, declaredParameters, PopulatesParameters.PARAMETER_VPC, vpcId); 27 | if (projAndEnv.hasBuildNumber()) { 28 | addParameterTo(result, declaredParameters, PopulatesParameters.PARAMETER_BUILD_NUMBER, 29 | projAndEnv.getBuildNumber().toString()); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/cfnScripts/acl.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - creates a single acl", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "subnet":{ 12 | "Type":"String", 13 | "Description":"::testSubnet" 14 | } 15 | 16 | }, 17 | "Resources":{ 18 | "assocAclWebSubnet":{ 19 | "Type":"AWS::EC2::SubnetNetworkAclAssociation", 20 | "Properties":{ 21 | "SubnetId":{ 22 | "Ref":"subnet" 23 | }, 24 | "NetworkAclId":{ 25 | "Ref":"testAcl" 26 | } 27 | } 28 | }, 29 | "testAcl":{ 30 | "Type":"AWS::EC2::NetworkAcl", 31 | "Properties":{ 32 | "Tags":[ 33 | { 34 | "Key":"Name", 35 | "Value":"testAcl" 36 | } 37 | 38 | ], 39 | "VpcId":{ 40 | "Ref":"vpc" 41 | } 42 | 43 | } 44 | 45 | }, 46 | "networkAclEntryWebE100":{ 47 | "Type":"AWS::EC2::NetworkAclEntry", 48 | "Properties":{ 49 | "NetworkAclId":{ 50 | "Ref":"testAcl" 51 | }, 52 | "RuleNumber":"100", 53 | "RuleAction":"allow", 54 | "Egress":"true", 55 | "CidrBlock":"0.0.0.0/0", 56 | "Protocol":"6", 57 | "PortRange":{ 58 | "From":"1024", 59 | "To":"65535" 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/tw/com/ant/AllowhostElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineException; 7 | import tw.com.commandline.actions.AllowHostAction; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.util.Collection; 12 | 13 | public class AllowhostElement implements ActionElement { 14 | private String tag; 15 | private String port; 16 | private String host; 17 | 18 | public AllowhostElement() { 19 | 20 | } 21 | 22 | public void setHost(String host) { this.host = host; } 23 | 24 | public void setTag(String tag) { 25 | this.tag = tag; 26 | } 27 | 28 | public void setPort(String port) { 29 | this.port = port; 30 | } 31 | 32 | @Override 33 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, 34 | Collection cfnParams) 35 | throws 36 | InterruptedException, 37 | CfnAssistException, CommandLineException, MissingArgumentException { 38 | 39 | AllowHostAction action = new AllowHostAction(); 40 | 41 | action.validate(projectAndEnv, cfnParams, tag, host, port); 42 | action.invoke(factory, projectAndEnv, cfnParams, tag, host, port); 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/tw/com/ant/BlockhostElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineException; 7 | import tw.com.commandline.actions.BlockHostAction; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.util.Collection; 12 | 13 | public class BlockhostElement implements ActionElement { 14 | private String tag; 15 | private String port; 16 | private String host; 17 | 18 | public BlockhostElement() { 19 | 20 | } 21 | 22 | public void setHost(String host) { this.host = host; } 23 | 24 | public void setTag(String tag) { 25 | this.tag = tag; 26 | } 27 | 28 | public void setPort(String port) { 29 | this.port = port; 30 | } 31 | 32 | @Override 33 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, 34 | Collection cfnParams) 35 | throws 36 | InterruptedException, 37 | CfnAssistException, CommandLineException, MissingArgumentException { 38 | 39 | BlockHostAction action = new BlockHostAction(); 40 | 41 | action.validate(projectAndEnv, cfnParams, tag, host, port); 42 | action.invoke(factory, projectAndEnv, cfnParams, tag, host, port); 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/tw/com/ant/TargetGroupUpdateElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineException; 7 | import tw.com.commandline.actions.UpdateTargetGroupAction; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.io.IOException; 12 | import java.util.Collection; 13 | 14 | public class TargetGroupUpdateElement implements ActionElement { 15 | 16 | private String typeTag; 17 | private String port; 18 | 19 | public void setTypeTag(String typeTag) { 20 | this.typeTag = typeTag; 21 | } 22 | 23 | public void setPort(String port) { 24 | this.port = port; 25 | } 26 | 27 | public TargetGroupUpdateElement() { 28 | } 29 | 30 | @Override 31 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, 32 | Collection cfnParams) 33 | throws IOException, 34 | InterruptedException, 35 | CfnAssistException, CommandLineException, MissingArgumentException { 36 | UpdateTargetGroupAction actionToInvoke = new UpdateTargetGroupAction(); 37 | 38 | actionToInvoke.validate(projectAndEnv, cfnParams, typeTag, port); 39 | actionToInvoke.invoke(factory, projectAndEnv, cfnParams, typeTag, port); 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/Edge.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | public class Edge extends HasAttributes { 4 | 5 | @Override 6 | public boolean equals(Object obj) { 7 | if (obj.getClass() != Edge.class) { 8 | return false; 9 | } 10 | Edge other = (Edge)obj; 11 | return (other.begin.equals(this.begin)) && (other.end.equals(this.end)); 12 | } 13 | 14 | private String begin; 15 | private String end; 16 | 17 | public Edge(String begin, String end) { 18 | this.begin = begin; 19 | this.end = end; 20 | } 21 | 22 | public void write(Recorder recorder) { 23 | recorder.write(String.format("\"%s\"->\"%s\" ", begin, end)); 24 | writeAttributes(recorder, false); 25 | recorder.writeline(";"); 26 | } 27 | 28 | public Edge withLabel(String label) { 29 | addLabel(label); 30 | return this; 31 | } 32 | 33 | public Edge withNoArrow() { 34 | addNoDirection(); 35 | return this; 36 | } 37 | 38 | public Edge withDot() { 39 | addDot(); 40 | return this; 41 | } 42 | 43 | public Edge withDottedLine() { 44 | addDottedLine(); 45 | return this; 46 | } 47 | 48 | public Edge endsAt(String elementId) { 49 | addEndsAt(elementId); 50 | return this; 51 | } 52 | 53 | public Edge withBox() { 54 | addBox(); 55 | return this; 56 | } 57 | 58 | public Edge beginsAt(String elementId) { 59 | addBeginsAt(elementId); 60 | return this; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/tw/com/entity/DeletionPending.java: -------------------------------------------------------------------------------- 1 | package tw.com.entity; 2 | 3 | 4 | public class DeletionPending implements Comparable { 5 | 6 | 7 | private int delta; 8 | private StackNameAndId stackId; 9 | 10 | public DeletionPending(int delta, StackNameAndId stackId) { 11 | this.delta = delta; 12 | this.stackId = stackId; 13 | } 14 | 15 | public Integer getDelta() { 16 | return delta; 17 | } 18 | 19 | public StackNameAndId getStackId() { 20 | return stackId; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return String.format("DeletionPending(delta=%s,stackId=%s", delta, stackId); 26 | } 27 | 28 | @Override 29 | public int compareTo(DeletionPending o) { 30 | // when sorted we want them ordered by highest delta first 31 | if (o.delta==this.delta) return 0; 32 | if (o.delta>this.delta) return 1; 33 | return -1; 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | final int prime = 31; 39 | int result = 1; 40 | result = prime * result + delta; 41 | return result; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object obj) { 46 | if (this == obj) 47 | return true; 48 | if (obj == null) 49 | return false; 50 | if (getClass() != obj.getClass()) 51 | return false; 52 | DeletionPending other = (DeletionPending) obj; 53 | if (delta != other.delta) 54 | return false; 55 | return true; 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/cfnScripts/targetGroupAndInstance.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - creates a simple target group and instance", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | } 16 | }, 17 | "Resources":{ 18 | "vpcSubnet":{ 19 | "Type":"AWS::EC2::Subnet", 20 | "Properties":{ 21 | "AvailabilityZone":{ 22 | "Ref":"zoneA" 23 | }, 24 | "CidrBlock":"10.0.10.13/24", 25 | "VpcId":{ 26 | "Ref":"vpc" 27 | } 28 | } 29 | }, 30 | "targetGroup" : { 31 | "Type" : "AWS::ElasticLoadBalancingV2::TargetGroup", 32 | "Properties" : { 33 | "HealthCheckEnabled": "True", 34 | "Name": { "Fn::Join": [ "-", ["tramchesterTargetGroup", {"Ref":"env"}]] }, 35 | "Port": "8080", 36 | "Protocol" : "HTTP", 37 | "VpcId": { "Ref": "vpc"} 38 | } 39 | }, 40 | "simpleInstance":{ 41 | "Type":"AWS::EC2::Instance", 42 | "Properties":{ 43 | "InstanceType":"t2.micro", 44 | "ImageId":"ami-0f3164307ee5d695a", 45 | "SubnetId":{ 46 | "Ref":"vpcSubnet" 47 | }, 48 | "Tags": [ 49 | { "Key" : "Name", "Value": "testInstance" }, 50 | { "Key" : "CFN_ASSIST_TYPE", "Value": "web" } 51 | ] 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/NodesAndEdges.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | import tw.com.exceptions.CfnAssistException; 7 | 8 | public class NodesAndEdges extends HasAttributes { 9 | 10 | protected List nodes = new LinkedList(); 11 | protected List edges = new LinkedList(); 12 | 13 | public NodesAndEdges() { 14 | super(); 15 | } 16 | 17 | public Node addNode(String id) throws CfnAssistException { 18 | if (id==null) { 19 | throw new CfnAssistException("name cannot be null"); 20 | } 21 | Node node = new Node(id); 22 | nodes.add(node); 23 | return node; 24 | } 25 | 26 | public void addCompound() { 27 | super.addCompound(); 28 | } 29 | 30 | public Edge addEdge(String begin, String end) { 31 | Edge edge = new Edge(begin, end); 32 | edges.add(edge); 33 | return edge; 34 | } 35 | 36 | public Edge addEdgeIgnoreDup(String begin, String end) { 37 | Edge edge = new Edge(begin, end); 38 | if (edges.contains(edge)) { 39 | int index = edges.indexOf(edge); 40 | return edges.get(index); 41 | } 42 | edges.add(edge); 43 | return edge; 44 | } 45 | 46 | protected void renderNodesAndEdges(Recorder recorder, boolean isGraph) { 47 | writeAttributes(recorder, isGraph); 48 | for(Node node : nodes) { 49 | node.write(recorder); 50 | } 51 | for(Edge edge : edges) { 52 | edge.write(recorder); 53 | } 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /test/tw/com/unit/TestCFNAssistNotification.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | import java.io.IOException; 4 | 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | 10 | import software.amazon.awssdk.services.iam.model.User; 11 | import tw.com.EnvironmentSetupForTests; 12 | import tw.com.entity.CFNAssistNotification; 13 | 14 | class TestCFNAssistNotification { 15 | 16 | private User user; 17 | 18 | @BeforeEach 19 | public void beforeEachTestRuns() { 20 | user = EnvironmentSetupForTests.createUser(); 21 | } 22 | 23 | @Test 24 | void shouldTestEquality() { 25 | CFNAssistNotification notifA = new CFNAssistNotification("stackA", "status1", user); 26 | CFNAssistNotification notifB = new CFNAssistNotification("stackA", "status1", user); 27 | CFNAssistNotification notifC = new CFNAssistNotification("stackX", "status1", user); 28 | 29 | Assertions.assertEquals(notifA, notifB); 30 | Assertions.assertEquals(notifB, notifA); 31 | Assertions.assertFalse(notifA.equals(notifC)); 32 | } 33 | 34 | @Test 35 | void testToAndFromJSON() throws IOException { 36 | CFNAssistNotification notifA = new CFNAssistNotification("stackA", "status1", user); 37 | 38 | String json = CFNAssistNotification.toJSON(notifA); 39 | 40 | CFNAssistNotification result = CFNAssistNotification.fromJSON(json); 41 | 42 | Assertions.assertEquals(notifA, result); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/tw/com/ant/TidyStacksElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineException; 7 | import tw.com.commandline.actions.TidyOldStacksAction; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.util.Collection; 14 | 15 | public class TidyStacksElement implements ActionElement { 16 | private File target; 17 | private String typeTag; 18 | 19 | public void setTarget(File target) { 20 | this.target = target; 21 | } 22 | 23 | public void setTypeTag(String typeTag) { 24 | this.typeTag = typeTag; 25 | } 26 | 27 | public TidyStacksElement() { 28 | } 29 | 30 | @Override 31 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, 32 | Collection cfnParams) 33 | throws IOException, 34 | InterruptedException, 35 | CfnAssistException, CommandLineException, MissingArgumentException { 36 | 37 | TidyOldStacksAction actionToInvoke = new TidyOldStacksAction(); 38 | String absolutePath = target.getAbsolutePath(); 39 | 40 | actionToInvoke.validate(projectAndEnv, cfnParams, absolutePath, typeTag); 41 | actionToInvoke.invoke(factory, projectAndEnv, cfnParams, absolutePath, typeTag); 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/cfnScripts/subnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "test template for cnfassist - creates a single subnet", 4 | "Parameters": { 5 | "env": { 6 | "Type": "String" 7 | }, 8 | "vpc": { 9 | "Type": "String" 10 | }, 11 | "zoneA": { 12 | "Type": "String", 13 | "Default": "eu-west-1a", 14 | "Description": "zoneADescription" 15 | }, 16 | "zoneB": { 17 | "Type": "String", 18 | "Default": "eu-west-1b" 19 | } 20 | }, 21 | "Resources": { 22 | "testSubnet": { 23 | "Type": "AWS::EC2::Subnet", 24 | "Properties": { 25 | "AvailabilityZone": { 26 | "Ref": "zoneA" 27 | }, 28 | "CidrBlock": "10.0.42.0/24", 29 | "Tags": [ 30 | { 31 | "Key": "Name", 32 | "Value": "testSubnet" 33 | }, 34 | { 35 | "Key": "TagEnv", 36 | "Value": { 37 | "Ref": "env" 38 | } 39 | } 40 | ], 41 | "VpcId": { 42 | "Ref": "vpc" 43 | } 44 | } 45 | } 46 | }, 47 | "Outputs": { 48 | "SUBNET": { 49 | "Value": { 50 | "Ref": "testSubnet" 51 | }, 52 | "Description": "::CFN_TAG" 53 | }, 54 | "notused": { 55 | "Value": { 56 | "Ref": "testSubnet" 57 | }, 58 | "Description": "should not get picked up by cfn assist" 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/tw/com/StackMonitor.java: -------------------------------------------------------------------------------- 1 | package tw.com; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import software.amazon.awssdk.services.cloudformation.model.StackEvent; 6 | import software.amazon.awssdk.services.cloudformation.model.StackStatus; 7 | import tw.com.repository.StackRepository; 8 | 9 | import java.util.List; 10 | 11 | public abstract class StackMonitor implements MonitorStackEvents { 12 | private static final Logger logger = LoggerFactory.getLogger(StackMonitor.class); 13 | 14 | protected final StackRepository stackRepository; 15 | 16 | public static final StackStatus[] DELETE_ABORTS = { StackStatus.DELETE_FAILED }; 17 | public static final StackStatus[] CREATE_ABORTS = { StackStatus.CREATE_FAILED, StackStatus.ROLLBACK_IN_PROGRESS }; 18 | public static final StackStatus[] ROLLBACK_ABORTS = { StackStatus.ROLLBACK_FAILED }; 19 | public static final StackStatus[] UPDATE_ABORTS = { StackStatus.UPDATE_ROLLBACK_IN_PROGRESS } ; 20 | 21 | protected StackMonitor(StackRepository stackRepository) { 22 | this.stackRepository = stackRepository; 23 | } 24 | 25 | // TODO StackNameAndId 26 | protected void logStackEvents(String stackName) { 27 | final List stackEvents = stackRepository.getStackEvents(stackName); 28 | if (stackEvents.isEmpty()) { 29 | logger.error("Not stack events for " + stackName); 30 | return; 31 | } 32 | for(StackEvent event : stackEvents) { 33 | logger.info(event.toString()); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/cfnScripts/subnet.delta.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"UPDATE template for cnfassist - updates a single subnet to a new CIDR", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | }, 16 | "zoneB":{ 17 | "Type":"String", 18 | "Default":"eu-west-1b" 19 | }, 20 | "stackname":{ 21 | "Type":"String", 22 | "Description":"The name of the stack we want to update, cfnassist uses this to find the stack", 23 | "Default":"subnet" 24 | } 25 | }, 26 | "Resources":{ 27 | "testSubnet":{ 28 | "Type":"AWS::EC2::Subnet", 29 | "Properties":{ 30 | "AvailabilityZone":{ 31 | "Ref":"zoneA" 32 | }, 33 | "CidrBlock":"10.0.99.0/24", 34 | "Tags":[ 35 | { 36 | "Key":"Name", 37 | "Value":"testSubnet" 38 | }, 39 | { 40 | "Key":"TagEnv", 41 | "Value":{ 42 | "Ref":"env" 43 | } 44 | 45 | } 46 | 47 | ], 48 | "VpcId":{ 49 | "Ref":"vpc" 50 | } 51 | 52 | } 53 | 54 | } 55 | }, 56 | "Outputs" : { 57 | "SUBNET" : { 58 | "Value" : { "Ref":"testSubnet" }, "Description":"::CFN_TAG" 59 | }, 60 | "notused" : { 61 | "Value" : { "Ref":"testSubnet" }, "Description":"should not get picked up by cfn assist" 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/tw/com/entity/StackNameAndId.java: -------------------------------------------------------------------------------- 1 | package tw.com.entity; 2 | 3 | public class StackNameAndId { 4 | 5 | private String stackName; 6 | private String stackId; 7 | 8 | public StackNameAndId(String stackName, String stackId) { 9 | this.stackName = stackName; 10 | this.stackId = stackId; 11 | } 12 | 13 | @Override 14 | public int hashCode() { 15 | final int prime = 31; 16 | int result = 1; 17 | result = prime * result + ((stackId == null) ? 0 : stackId.hashCode()); 18 | result = prime * result 19 | + ((stackName == null) ? 0 : stackName.hashCode()); 20 | return result; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object obj) { 25 | if (this == obj) 26 | return true; 27 | if (obj == null) 28 | return false; 29 | if (getClass() != obj.getClass()) 30 | return false; 31 | StackNameAndId other = (StackNameAndId) obj; 32 | if (stackId == null) { 33 | if (other.stackId != null) 34 | return false; 35 | } else if (!stackId.equals(other.stackId)) 36 | return false; 37 | if (stackName == null) { 38 | if (other.stackName != null) 39 | return false; 40 | } else if (!stackName.equals(other.stackName)) 41 | return false; 42 | return true; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "StackId [stackName=" + stackName + ", stackId=" + stackId + "]"; 48 | } 49 | 50 | public String getStackName() { 51 | return stackName; 52 | } 53 | 54 | public String getStackId() { 55 | return stackId; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/tw/com/ant/DeleteElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import org.apache.tools.ant.BuildException; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineAction; 8 | import tw.com.commandline.CommandLineException; 9 | import tw.com.commandline.actions.DeleteAction; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.util.Collection; 16 | 17 | public class DeleteElement implements ActionElement { 18 | private File target; 19 | 20 | public DeleteElement() { 21 | } 22 | 23 | public void setTarget(File target) { 24 | this.target = target; 25 | } 26 | 27 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams) 28 | throws IOException, InterruptedException, CfnAssistException, CommandLineException, MissingArgumentException { 29 | String absolutePath = target.getAbsolutePath(); 30 | 31 | if (!target.isFile()) { 32 | throw new BuildException("Cannot invoke delete for a directory, use Rollbackif you want to roll back all deltas in a folder"); 33 | } 34 | 35 | CommandLineAction actionToInvoke = new DeleteAction(); 36 | 37 | actionToInvoke.validate(projectAndEnv, cfnParams, absolutePath); 38 | actionToInvoke.invoke(factory, projectAndEnv, cfnParams, absolutePath); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/tw/com/ant/DiagramsElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.apache.tools.ant.BuildException; 5 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.commandline.actions.CreateDiagramAction; 9 | import tw.com.entity.ProjectAndEnv; 10 | import tw.com.exceptions.CfnAssistException; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.util.Collection; 15 | 16 | public class DiagramsElement implements ActionElement { 17 | private File target; 18 | 19 | public DiagramsElement() { 20 | 21 | } 22 | 23 | public void setTarget(File target) { 24 | this.target = target; 25 | } 26 | 27 | @Override 28 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, 29 | Collection cfnParams) 30 | throws IOException, InterruptedException, 31 | CfnAssistException, CommandLineException, MissingArgumentException { 32 | String path = target.getAbsolutePath(); 33 | 34 | if (!target.isDirectory()) { 35 | throw new BuildException( 36 | String.format("Target %s was not a directory, please provide a directory for the diagrams to be saved into", path)); 37 | } 38 | 39 | CreateDiagramAction action = new CreateDiagramAction(); 40 | 41 | action.validate(projectAndEnv, cfnParams, path); 42 | action.invoke(factory, projectAndEnv, cfnParams, path); 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/SubGraphFacade.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | import tw.com.exceptions.CfnAssistException; 4 | import tw.com.pictures.ChildDiagram; 5 | 6 | public class SubGraphFacade implements ChildDiagram { 7 | 8 | private SubGraph subGraph; 9 | private CommonElements commonElements; 10 | 11 | public SubGraphFacade(SubGraph subGraph) { 12 | this.subGraph = subGraph ; 13 | commonElements = new CommonDiagramElements(subGraph); 14 | } 15 | 16 | @Override 17 | public void addInstance(String instanceId, String label) throws CfnAssistException { 18 | subGraph.addNode(instanceId).withShape(Shape.Box).withLabel(label); 19 | } 20 | 21 | @Override 22 | public void render(Recorder recorder) { 23 | subGraph.render(recorder); 24 | } 25 | 26 | @Override 27 | public void addRouteTable(String routeTableId, String label) throws CfnAssistException { 28 | subGraph.addNode(routeTableId).addLabel(label); 29 | } 30 | 31 | @Override 32 | public String getId() { 33 | return subGraph.getId(); 34 | } 35 | 36 | @Override 37 | public void addPortRange(String uniqueId, String label) throws CfnAssistException { 38 | commonElements.addPortRange(uniqueId, label); 39 | } 40 | 41 | @Override 42 | public void addSecurityGroup(String uniqueId, String label) throws CfnAssistException { 43 | commonElements.addSecurityGroup(uniqueId, label); 44 | } 45 | 46 | @Override 47 | public void connectWithLabel(String uniqueAId, String uniqueBId, String label) { 48 | commonElements.connectWithLabel(uniqueAId, uniqueBId, label); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/tw/com/pictures/TemplatedChildDiagram.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | import tw.com.exceptions.CfnAssistException; 4 | import tw.com.pictures.ChildDiagram; 5 | import tw.com.pictures.dot.Recorder; 6 | 7 | public class TemplatedChildDiagram implements ChildDiagram { 8 | 9 | private T contained; 10 | 11 | public ChildDiagram getContained() { 12 | return contained; 13 | } 14 | 15 | public TemplatedChildDiagram(T contained) { 16 | this.contained = contained; 17 | } 18 | 19 | @Override 20 | public void addInstance(String instanceId, String label) throws CfnAssistException { 21 | contained.addInstance(instanceId, label); 22 | } 23 | 24 | @Override 25 | public void render(Recorder recorder) { 26 | contained.render(recorder); 27 | } 28 | 29 | @Override 30 | public void addRouteTable(String routeTableId, String label) 31 | throws CfnAssistException { 32 | contained.addRouteTable(routeTableId, label); 33 | } 34 | 35 | @Override 36 | public String getId() { 37 | return contained.getId(); 38 | } 39 | 40 | @Override 41 | public void addSecurityGroup(String id, String label) throws CfnAssistException { 42 | contained.addSecurityGroup(id, label); 43 | } 44 | 45 | @Override 46 | public void addPortRange(String uniqueId, String label) throws CfnAssistException { 47 | contained.addPortRange(uniqueId, label); 48 | } 49 | 50 | @Override 51 | public void connectWithLabel(String uniqueAId, String uniqueBId, String label) { 52 | contained.connectWithLabel(uniqueAId, uniqueBId, label); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/cfnScripts/elb.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - creates an ELB", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | } 16 | }, 17 | "Resources":{ 18 | "internetGateway":{ 19 | "Type":"AWS::EC2::InternetGateway" 20 | }, 21 | "AttachGateway":{ 22 | "Type":"AWS::EC2::VPCGatewayAttachment", 23 | "Properties":{ 24 | "VpcId":{ 25 | "Ref":"vpc" 26 | }, 27 | "InternetGatewayId":{ 28 | "Ref":"internetGateway" 29 | } 30 | } 31 | }, 32 | "vpcSubnet":{ 33 | "Type":"AWS::EC2::Subnet", 34 | "Properties":{ 35 | "AvailabilityZone":{ 36 | "Ref":"zoneA" 37 | }, 38 | "CidrBlock":"10.0.10.13/24", 39 | "VpcId":{ 40 | "Ref":"vpc" 41 | } 42 | } 43 | }, 44 | "loadBalancer":{ 45 | "Type":"AWS::ElasticLoadBalancing::LoadBalancer", 46 | "DependsOn" : "AttachGateway", 47 | "Properties":{ 48 | "HealthCheck":{ 49 | "HealthyThreshold":"2", 50 | "Interval":"15", 51 | "Target":"HTTP:8080/api/status", 52 | "Timeout":"5", 53 | "UnhealthyThreshold":"2" 54 | }, 55 | "Subnets":[ 56 | { 57 | "Ref":"vpcSubnet" 58 | } 59 | ], 60 | "Listeners":[ 61 | { 62 | "InstancePort":"8082", 63 | "LoadBalancerPort":"80", 64 | "Protocol":"HTTP", 65 | "PolicyNames":[ 66 | 67 | ] 68 | 69 | } 70 | ] 71 | } 72 | 73 | } 74 | 75 | } 76 | } -------------------------------------------------------------------------------- /src/tw/com/repository/LogStreamInterleaver.java: -------------------------------------------------------------------------------- 1 | package tw.com.repository; 2 | 3 | import tw.com.entity.OutputLogEventDecorator; 4 | 5 | import java.util.*; 6 | import java.util.stream.Stream; 7 | 8 | public class LogStreamInterleaver implements Iterable { 9 | 10 | private final List> iteratorList; 11 | private int streamIndex; 12 | 13 | // NOPE :-( 14 | // ASSUME: streams are ordered earliest first and each stream has earlier events first 15 | public LogStreamInterleaver(List> rawStreams) { 16 | this.iteratorList = new LinkedList<>(); 17 | rawStreams.forEach(rawStream -> iteratorList.add(rawStream.iterator())); 18 | streamIndex = 0; 19 | } 20 | 21 | private boolean hasAnyLeft() { 22 | if (iteratorList.get(streamIndex).hasNext()) { 23 | return true; 24 | } 25 | streamIndex++; 26 | if (streamIndex>=iteratorList.size()) { 27 | return false; 28 | } 29 | return iteratorList.get(streamIndex).hasNext(); 30 | } 31 | 32 | public OutputLogEventDecorator getNext() { 33 | return iteratorList.get(streamIndex).next(); 34 | } 35 | 36 | @Override 37 | public Iterator iterator() { 38 | return new Iterator() { 39 | @Override 40 | public boolean hasNext() { 41 | return hasAnyLeft() ; 42 | } 43 | 44 | @Override 45 | public OutputLogEventDecorator next() { 46 | return getNext(); 47 | } 48 | }; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /test/tw/com/unit/TestDiagramFactory.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | import org.easymock.EasyMock; 4 | import org.easymock.EasyMockSupport; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import tw.com.exceptions.CfnAssistException; 10 | import tw.com.pictures.*; 11 | 12 | import software.amazon.awssdk.services.ec2.model.Subnet; 13 | 14 | class TestDiagramFactory extends EasyMockSupport { 15 | 16 | private DiagramFactory factory; 17 | private VPCDiagramBuilder parentDiagramBuilder; 18 | private NetworkChildDiagram childNetworkDiagram; 19 | private tw.com.pictures.SecurityChildDiagram childSecurityDiagram; 20 | 21 | @BeforeEach 22 | public void beforeEachTestRuns() { 23 | parentDiagramBuilder = createStrictMock(VPCDiagramBuilder.class); 24 | childNetworkDiagram = createStrictMock(NetworkChildDiagram.class); 25 | childSecurityDiagram = createStrictMock(tw.com.pictures.SecurityChildDiagram.class); 26 | 27 | factory = new DiagramFactory(); 28 | } 29 | 30 | @Test 31 | void shouldAddSubnetDiagrm() throws CfnAssistException { 32 | 33 | Subnet subnet = Subnet.builder().subnetId("subnetId").cidrBlock("cidrBlock").build(); 34 | EasyMock.expect(parentDiagramBuilder.createNetworkDiagramForSubnet(subnet)).andReturn(childNetworkDiagram); 35 | EasyMock.expect(parentDiagramBuilder.createSecurityDiagramForSubnet(subnet)).andReturn(childSecurityDiagram); 36 | 37 | replayAll(); 38 | SubnetDiagramBuilder result = factory.createSubnetDiagramBuilder(parentDiagramBuilder, subnet); 39 | verifyAll(); 40 | 41 | Assertions.assertSame(childNetworkDiagram, result.getNetworkDiagram()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/tw/com/ant/TemplatesElement.java: -------------------------------------------------------------------------------- 1 | package tw.com.ant; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import org.apache.tools.ant.BuildException; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineAction; 8 | import tw.com.commandline.CommandLineException; 9 | import tw.com.commandline.actions.DirAction; 10 | import tw.com.commandline.actions.FileAction; 11 | import tw.com.entity.ProjectAndEnv; 12 | import tw.com.exceptions.CfnAssistException; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.util.Collection; 17 | 18 | public class TemplatesElement implements ActionElement { 19 | 20 | private File target; 21 | 22 | public TemplatesElement() { 23 | 24 | } 25 | 26 | public void setTarget(File target) { 27 | this.target = target; 28 | } 29 | 30 | 31 | @Override 32 | public void execute(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams) 33 | throws IOException, InterruptedException, CfnAssistException, 34 | CommandLineException, MissingArgumentException { 35 | String absolutePath = target.getAbsolutePath(); 36 | CommandLineAction actionToInvoke = null; 37 | if (target.isDirectory()) { 38 | actionToInvoke = new DirAction(); 39 | } else if (target.isFile()) { 40 | actionToInvoke = new FileAction(); 41 | } 42 | 43 | if (actionToInvoke==null) { 44 | throw new BuildException("Unable to action on path, expect file or folder, path was: " + absolutePath); 45 | } 46 | actionToInvoke.validate(projectAndEnv, cfnParams, absolutePath); 47 | actionToInvoke.invoke(factory, projectAndEnv, cfnParams, absolutePath); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/ElbAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.io.IOException; 12 | import java.util.Collection; 13 | 14 | public class ElbAction extends SharedAction { 15 | 16 | @SuppressWarnings("static-access") 17 | public ElbAction() { 18 | createOptionWithArg("elbUpdate", "Update elb to point at instances tagged with build"); 19 | } 20 | 21 | @Override 22 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 23 | String... args) 24 | throws IOException, InterruptedException, 25 | CfnAssistException, MissingArgumentException { 26 | 27 | AwsFacade facade = factory.createFacade(); 28 | facade.updateELBToInstancesMatchingBuild(projectAndEnv, args[0]); 29 | } 30 | 31 | @Override 32 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 33 | String... argumentForAction) throws CommandLineException { 34 | guardForProjectAndEnv(projectAndEnv); 35 | if (!projectAndEnv.hasBuildNumber()) { 36 | throw new CommandLineException("You must provide the build parameter"); 37 | } 38 | } 39 | 40 | @Override 41 | public boolean usesProject() { 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean usesComment() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean usesSNS() { 52 | return false; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/tw/com/MonitorStackEvents.java: -------------------------------------------------------------------------------- 1 | package tw.com; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.commons.cli.MissingArgumentException; 6 | 7 | import software.amazon.awssdk.services.cloudformation.model.CreateStackRequest; 8 | import software.amazon.awssdk.services.cloudformation.model.StackStatus; 9 | import software.amazon.awssdk.services.cloudformation.model.UpdateStackRequest; 10 | import tw.com.entity.DeletionsPending; 11 | import tw.com.entity.StackNameAndId; 12 | import tw.com.exceptions.CfnAssistException; 13 | import tw.com.exceptions.NotReadyException; 14 | import tw.com.exceptions.WrongNumberOfStacksException; 15 | import tw.com.exceptions.WrongStackStatus; 16 | 17 | public interface MonitorStackEvents { 18 | StackStatus waitForCreateFinished(StackNameAndId id) throws WrongNumberOfStacksException, InterruptedException, NotReadyException, WrongStackStatus; 19 | StackStatus waitForDeleteFinished(StackNameAndId id) throws WrongNumberOfStacksException, InterruptedException, NotReadyException, WrongStackStatus; 20 | StackStatus waitForRollbackComplete(StackNameAndId id) throws NotReadyException, WrongNumberOfStacksException, WrongStackStatus, InterruptedException; 21 | StackStatus waitForUpdateFinished(StackNameAndId id) throws WrongNumberOfStacksException, InterruptedException, WrongStackStatus, NotReadyException; 22 | List waitForDeleteFinished(DeletionsPending pending, SetsDeltaIndex setsDeltaIndex) throws CfnAssistException; 23 | 24 | void init() throws MissingArgumentException, CfnAssistException, InterruptedException; 25 | 26 | void addMonitoringTo(CreateStackRequest.Builder createStackRequest) throws NotReadyException; 27 | void addMonitoringTo(UpdateStackRequest.Builder updateStackRequest) throws NotReadyException; 28 | } 29 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/ResetAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.util.Collection; 14 | 15 | public class ResetAction extends SharedAction { 16 | private static final Logger logger = LoggerFactory.getLogger(ResetAction.class); 17 | 18 | @SuppressWarnings("static-access") 19 | public ResetAction() { 20 | createOption("reset", "Warning: Resets the Delta Tag "+AwsFacade.INDEX_TAG+ " to zero"); 21 | } 22 | 23 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection unusedParams, String... unusedArg) throws MissingArgumentException, CfnAssistException, InterruptedException { 24 | logger.info("Reseting index for " + projectAndEnv); 25 | AwsFacade aws = factory.createFacade(); 26 | aws.resetDeltaIndex(projectAndEnv); 27 | } 28 | 29 | @Override 30 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 31 | String... argumentForAction) 32 | throws CommandLineException { 33 | guardForProjectAndEnv(projectAndEnv); 34 | guardForNoBuildNumber(projectAndEnv); 35 | } 36 | 37 | @Override 38 | public boolean usesProject() { 39 | return true; 40 | } 41 | 42 | @Override 43 | public boolean usesComment() { 44 | return false; 45 | } 46 | 47 | @Override 48 | public boolean usesSNS() { 49 | return false; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/BackAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.util.Collection; 14 | 15 | public class BackAction extends SharedAction { 16 | private static final Logger logger = LoggerFactory.getLogger(BackAction.class); 17 | 18 | @SuppressWarnings("static-access") 19 | public BackAction() { 20 | createOption("back", 21 | "Warning: Remove last delta and reset index accordingly."); 22 | } 23 | 24 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection unused, 25 | String... args) throws CfnAssistException, MissingArgumentException, InterruptedException { 26 | logger.info("Invoking stepback for " + projectAndEnv); 27 | AwsFacade aws = factory.createFacade(); 28 | aws.stepbackLastChange(projectAndEnv); 29 | } 30 | 31 | @Override 32 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 33 | String... argumentForAction) 34 | throws CommandLineException { 35 | guardForProjectAndEnv(projectAndEnv); 36 | guardForNoBuildNumber(projectAndEnv); 37 | } 38 | 39 | @Override 40 | public boolean usesProject() { 41 | return true; 42 | } 43 | 44 | @Override 45 | public boolean usesComment() { 46 | return false; 47 | } 48 | 49 | @Override 50 | public boolean usesSNS() { 51 | return true; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/TagLogAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.util.Collection; 14 | 15 | public class TagLogAction extends SharedAction { 16 | private static final Logger logger = LoggerFactory.getLogger(TagLogAction.class); 17 | 18 | @SuppressWarnings("static-access") 19 | public TagLogAction() { 20 | createOptionWithArgs("tagLog", "Tags the named group with current project and env", 1); 21 | } 22 | 23 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection unused, 24 | String... args) throws CfnAssistException, MissingArgumentException, InterruptedException { 25 | logger.info("Invoking removeLogs for " + projectAndEnv + " and " + args[0]); 26 | AwsFacade aws = factory.createFacade(); 27 | String groupName = args[0]; 28 | aws.tagCloudWatchLog(projectAndEnv, groupName); 29 | } 30 | 31 | @Override 32 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 33 | String... argumentForAction) 34 | throws CommandLineException { 35 | guardForProjectAndEnv(projectAndEnv); 36 | } 37 | 38 | @Override 39 | public boolean usesProject() { 40 | return true; 41 | } 42 | 43 | @Override 44 | public boolean usesComment() { 45 | return false; 46 | } 47 | 48 | @Override 49 | public boolean usesSNS() { 50 | return true; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/PurgeAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.util.Collection; 14 | 15 | public class PurgeAction extends SharedAction { 16 | private static final Logger logger = LoggerFactory.getLogger(PurgeAction.class); 17 | 18 | @SuppressWarnings("static-access") 19 | public PurgeAction() { 20 | createOption("purge", 21 | "Warning: Rollback all current deltas and reset index accordingly. DEPRECATED: use purge"); 22 | } 23 | 24 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection unused, 25 | String... args) throws CfnAssistException, MissingArgumentException, InterruptedException { 26 | logger.info("Invoking rollback for " + projectAndEnv); 27 | AwsFacade aws = factory.createFacade(); 28 | aws.rollbackTemplatesByIndexTag(projectAndEnv); 29 | } 30 | 31 | @Override 32 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 33 | String... argumentForAction) 34 | throws CommandLineException { 35 | guardForProjectAndEnv(projectAndEnv); 36 | guardForNoBuildNumber(projectAndEnv); 37 | } 38 | 39 | @Override 40 | public boolean usesProject() { 41 | return true; 42 | } 43 | 44 | @Override 45 | public boolean usesComment() { 46 | return false; 47 | } 48 | 49 | @Override 50 | public boolean usesSNS() { 51 | return true; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/cfnScripts/orderedScripts/02createAcls.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"02 - creates acls", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "webSubnet":{ 12 | "Type":"String", 13 | "Description":"::webSubnet" 14 | }, 15 | "dbSubnet":{ 16 | "Type":"String", 17 | "Description":"::dbSubnet" 18 | } 19 | }, 20 | "Resources":{ 21 | "assocAclWebSubnet":{ 22 | "Type":"AWS::EC2::SubnetNetworkAclAssociation", 23 | "Properties":{ 24 | "SubnetId":{ 25 | "Ref":"webSubnet" 26 | }, 27 | "NetworkAclId":{ 28 | "Ref":"webAcl" 29 | } 30 | } 31 | }, 32 | "assocAclDbSubnet":{ 33 | "Type":"AWS::EC2::SubnetNetworkAclAssociation", 34 | "Properties":{ 35 | "SubnetId":{ 36 | "Ref":"dbSubnet" 37 | }, 38 | "NetworkAclId":{ 39 | "Ref":"dbAcl" 40 | } 41 | } 42 | }, 43 | "webAcl":{ 44 | "Type":"AWS::EC2::NetworkAcl", 45 | "Properties":{ 46 | "VpcId":{ 47 | "Ref":"vpc" 48 | } 49 | } 50 | }, 51 | "dbAcl":{ 52 | "Type":"AWS::EC2::NetworkAcl", 53 | "Properties":{ 54 | "VpcId":{ 55 | "Ref":"vpc" 56 | } 57 | } 58 | }, 59 | "networkAclEntryWebE100":{ 60 | "Type":"AWS::EC2::NetworkAclEntry", 61 | "Properties":{ 62 | "NetworkAclId":{ 63 | "Ref":"webAcl" 64 | }, 65 | "RuleNumber":"100", 66 | "RuleAction":"allow", 67 | "Egress":"true", 68 | "CidrBlock":"0.0.0.0/0", 69 | "Protocol":"6", 70 | "PortRange":{ 71 | "From":"1024", 72 | "To":"65535" 73 | } 74 | } 75 | }, 76 | "dbRouteTable" : { 77 | "Type" : "AWS::EC2::RouteTable", 78 | "Properties" : { 79 | "VpcId" : { "Ref" : "vpc" } 80 | } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/FileAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.entity.StackNameAndId; 12 | import tw.com.exceptions.CfnAssistException; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.util.Collection; 17 | 18 | public class FileAction extends SharedAction { 19 | static final Logger logger = LoggerFactory.getLogger(FileAction.class); 20 | 21 | @SuppressWarnings("static-access") 22 | public FileAction() { 23 | createOptionWithArg("file", "The single template file to apply"); 24 | } 25 | 26 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 27 | String... args) throws IOException, CfnAssistException, InterruptedException, MissingArgumentException { 28 | File templateFile = new File(args[0]); 29 | AwsFacade aws = factory.createFacade(); 30 | StackNameAndId stackId = aws.applyTemplate(templateFile, projectAndEnv, cfnParams); 31 | logger.info("Created stack name "+stackId); 32 | } 33 | 34 | @Override 35 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 36 | String... argumentForAction) throws CommandLineException { 37 | guardForProjectAndEnv(projectAndEnv); 38 | } 39 | 40 | @Override 41 | public boolean usesProject() { 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean usesComment() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean usesSNS() { 52 | return true; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | INFO 6 | 7 | 8 | %d{HH:mm:ss.SSS} %thread %X{test} %-5level %logger{36} - %msg%n 9 | 10 | 11 | 12 | 13 | System.err 14 | 15 | WARN 16 | 17 | 18 | %d{HH:mm:ss.SSS} %thread %X{test} %-5level %logger{36} - %msg%n 19 | 20 | 21 | 22 | 23 | 24 | INFO 25 | 26 | build/reports/tests/testing.log 27 | 28 | 29 | build/reports/tests/testing.%d{yyyy-MM-dd}.log 30 | 31 | 5 32 | 50MB 33 | 34 | 35 | 36 | %d{HH:mm:ss.SSS} %thread %X{test} %-5level %logger{36} - %msg%n 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/InitAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.io.IOException; 14 | import java.util.Collection; 15 | 16 | public class InitAction extends SharedAction { 17 | private static final Logger logger = LoggerFactory.getLogger(InitAction.class); 18 | 19 | @SuppressWarnings("static-access") 20 | public InitAction() { 21 | createOptionWithArg("init", "Warning: Initialise a VPC to set up tags, provide VPC Id"); 22 | } 23 | 24 | @Override 25 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection unused, 26 | String... args) throws 27 | IOException, InterruptedException, CfnAssistException, MissingArgumentException { 28 | String vpcId = args[0]; 29 | logger.info("Invoke init of tags for VPC: " + vpcId); 30 | AwsFacade aws = factory.createFacade(); 31 | aws.initEnvAndProjectForVPC(vpcId, projectAndEnv); 32 | } 33 | 34 | @Override 35 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 36 | String... argumentForAction) 37 | throws CommandLineException { 38 | guardForProjectAndEnv(projectAndEnv); 39 | guardForNoBuildNumber(projectAndEnv); 40 | } 41 | 42 | @Override 43 | public boolean usesProject() { 44 | return true; 45 | } 46 | 47 | @Override 48 | public boolean usesComment() { 49 | return false; 50 | } 51 | 52 | @Override 53 | public boolean usesSNS() { 54 | return false; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/tw/com/pictures/Diagram.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures; 2 | 3 | import tw.com.exceptions.CfnAssistException; 4 | import tw.com.pictures.dot.CommonElements; 5 | import tw.com.pictures.dot.Recorder; 6 | 7 | public interface Diagram extends CommonElements { 8 | 9 | ChildDiagram createSubDiagram(String uniqueId, String label) throws CfnAssistException; 10 | 11 | void addTitle(String title); 12 | 13 | void render(Recorder recorder); 14 | 15 | void addRouteTable(String uniqueId, String label) throws CfnAssistException; 16 | 17 | void addConnectionBetween(String uniqueIdA, String uniqueIdB); 18 | 19 | void addPublicIPAddress(String unqiueId, String label) throws CfnAssistException; 20 | 21 | void addLoadBalancer(String unqiueId, String label) throws CfnAssistException; 22 | 23 | void associateWithSubDiagram(String unqiueId, String clusterId, HasDiagramId childDiagram); 24 | 25 | void addConnectionFromSubDiagram(String start, String end, HasDiagramId childDigram, String label) throws CfnAssistException; 26 | 27 | void addConnectionToSubDiagram(String start, String end, HasDiagramId childDigram, String label) throws CfnAssistException; 28 | 29 | void addBlockedConnectionFromSubDiagram(String start, String end, HasDiagramId childDigram, String label) throws CfnAssistException; 30 | 31 | void addBlockedConnectionToSubDiagram(String start, String end, HasDiagramId childDigram, String label) throws CfnAssistException; 32 | 33 | void addDBInstance(String rdsId, String label) throws CfnAssistException; 34 | 35 | void addACL(String uniqueId, String label) throws CfnAssistException; 36 | 37 | void addCidr(String uniqueId, String label) throws CfnAssistException; 38 | 39 | void associate(String uniqueIdA, String uniqueIdB); 40 | 41 | void addRouteToInstance(String instanceId, String routeTableId, SubnetDiagramBuilder subnetDiagramBuilder, String cidr) throws CfnAssistException; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /test/tw/com/FilesForTesting.java: -------------------------------------------------------------------------------- 1 | package tw.com; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.Paths; 5 | 6 | public class FilesForTesting { 7 | // eclipse on windows may need you to set working directory to basedir and not workspace 8 | private static Path DIR = Paths.get("src","cfnScripts"); 9 | 10 | public static final String SUBNET_STACK_JSON = DIR.resolve("subnet.json").toString(); 11 | public static final String SUBNET_STACK_YAML = DIR.resolve("subnet.yaml").toString(); 12 | 13 | public static final String SUBNET_STACK_DELTA = DIR.resolve("subnet.delta.json").toString(); 14 | public static final String SIMPLE_STACK = DIR.resolve("simpleStack.json").toString(); 15 | public static final String SIMPLE_STACK_WITH_AZ = DIR.resolve("simpleStackWithAZ.json").toString(); 16 | public static final String ACL = DIR.resolve("acl.json").toString(); 17 | public static final String SUBNET_CIDR_PARAM = DIR.resolve("subnetWithCIDRParam.json").toString(); 18 | public static final String SUBNET_WITH_PARAM = DIR.resolve("subnetWithParam.json").toString(); 19 | public static final String SUBNET_WITH_S3_PARAM = DIR.resolve("subnetWithS3Param.json").toString(); 20 | public static final String INSTANCE = DIR.resolve("instance.json").toString(); 21 | public static final String ELB_AND_INSTANCE = DIR.resolve("elbAndInstance.json").toString(); 22 | public static final String TARGET_GROUP_AND_INSTANCE = DIR.resolve("targetGroupAndInstance.json").toString(); 23 | 24 | public static final String ORDERED_SCRIPTS_FOLDER = DIR.resolve("orderedScripts").toString(); 25 | public static final Path ORDERED_SCRIPTS_WITH_UPDATES_FOLDER = DIR.resolve("orderedScriptsWithUpdate"); 26 | public static final String STACK_UPDATE = ORDERED_SCRIPTS_WITH_UPDATES_FOLDER.resolve("02createSubnet.update.json").toString(); 27 | 28 | public static final String STACK_IAM_CAP = DIR.resolve("simpleIAMStack.json").toString(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/CreateDiagramAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.FacadeFactory; 6 | import tw.com.commandline.CommandLineException; 7 | import tw.com.entity.ProjectAndEnv; 8 | import tw.com.exceptions.CfnAssistException; 9 | import tw.com.pictures.DiagramCreator; 10 | import tw.com.pictures.dot.FileRecorder; 11 | import tw.com.pictures.dot.Recorder; 12 | 13 | import java.io.IOException; 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | import java.util.Collection; 17 | 18 | public class CreateDiagramAction extends SharedAction { 19 | 20 | @SuppressWarnings("static-access") 21 | public CreateDiagramAction() { 22 | createOptionWithArg("diagrams", "Create diagrams for VPCs in given folder"); 23 | } 24 | 25 | @Override 26 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, 27 | Collection cfnParams, 28 | String... argument) throws 29 | IOException, 30 | InterruptedException, CfnAssistException, MissingArgumentException { 31 | DiagramCreator diagramCreator = factory.createDiagramCreator(); 32 | Path folder = Paths.get(argument[0]); 33 | Recorder recorder = new FileRecorder(folder); 34 | diagramCreator.createDiagrams(recorder); 35 | } 36 | 37 | @Override 38 | public void validate(ProjectAndEnv projectAndEnv, 39 | Collection cfnParams, 40 | String... argumentForAction) throws CommandLineException { 41 | guardForNoBuildNumber(projectAndEnv); 42 | guardForSNSNotSet(projectAndEnv); 43 | } 44 | 45 | @Override 46 | public boolean usesProject() { 47 | return false; 48 | } 49 | 50 | @Override 51 | public boolean usesComment() { 52 | return false; 53 | } 54 | 55 | @Override 56 | public boolean usesSNS() { 57 | return false; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /test/tw/com/integration/TestHaveValidTemplateFiles.java: -------------------------------------------------------------------------------- 1 | package tw.com.integration; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import software.amazon.awssdk.services.cloudformation.model.CloudFormationException; 7 | import tw.com.EnvironmentSetupForTests; 8 | import tw.com.providers.CFNClient; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.nio.charset.Charset; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | import static org.junit.jupiter.api.Assertions.fail; 16 | 17 | public class TestHaveValidTemplateFiles { 18 | 19 | @BeforeEach 20 | public void beforeTestsRun() { 21 | } 22 | 23 | @Test 24 | public void testAllTestCfnFilesAreValid() throws InterruptedException { 25 | software.amazon.awssdk.services.cloudformation.CloudFormationClient cfnClient = EnvironmentSetupForTests.createCFNClient(); 26 | CFNClient cloudClient = new CFNClient(cfnClient); 27 | File folder = new File("src/cfnScripts"); 28 | 29 | assertTrue(folder.exists()); 30 | validateFolder(cloudClient, folder); 31 | } 32 | 33 | private void validateFolder(CFNClient cloudClient, File folder) throws InterruptedException 34 | { 35 | File[] files = folder.listFiles(); 36 | for(File file : files) { 37 | if (file.isDirectory()) { 38 | validateFolder(cloudClient, file); 39 | } else 40 | { 41 | if (!file.isHidden()) { 42 | try { 43 | String contents = FileUtils.readFileToString(file, Charset.defaultCharset()); 44 | cloudClient.validateTemplate(contents); 45 | } catch (IOException | CloudFormationException e) { 46 | fail(file.getAbsolutePath() + ": " + e); 47 | } 48 | } 49 | 50 | Thread.sleep(200); // to avoid rate limit errors on AWS api 51 | } 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/RemoveLogsAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.util.Collection; 14 | 15 | public class RemoveLogsAction extends SharedAction { 16 | private static final Logger logger = LoggerFactory.getLogger(RemoveLogsAction.class); 17 | 18 | @SuppressWarnings("static-access") 19 | public RemoveLogsAction() { 20 | createOptionWithArgs("removeLogs", "Warning: Deletes cloudwatch logs older than N days", 1); 21 | } 22 | 23 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection unused, 24 | String... args) throws CfnAssistException, MissingArgumentException, InterruptedException { 25 | logger.info("Invoking removeLogs for " + projectAndEnv + " and " + args[0]); 26 | AwsFacade aws = factory.createFacade(); 27 | try { 28 | int days = Integer.parseInt(args[0]); 29 | aws.removeCloudWatchLogsOlderThan(projectAndEnv, days); 30 | } 31 | catch (NumberFormatException parseFailed) { 32 | throw new CfnAssistException("Unable to parse number of weeks " + args[0]); 33 | } 34 | } 35 | 36 | @Override 37 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 38 | String... argumentForAction) 39 | throws CommandLineException { 40 | guardForProjectAndEnv(projectAndEnv); 41 | } 42 | 43 | @Override 44 | public boolean usesProject() { 45 | return true; 46 | } 47 | 48 | @Override 49 | public boolean usesComment() { 50 | return false; 51 | } 52 | 53 | @Override 54 | public boolean usesSNS() { 55 | return true; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/TidyOldStacksAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.util.Collection; 14 | 15 | public class TidyOldStacksAction extends SharedAction { 16 | 17 | @SuppressWarnings("static-access") 18 | public TidyOldStacksAction() { 19 | createOptionWithArgs("tidyOldStacks","Delete stacks matching given template no longer associated to the LB via an instance."+ 20 | "Pass template filename and type tag",2); 21 | } 22 | 23 | @Override 24 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, 25 | Collection cfnParams, 26 | String... args) throws 27 | IOException, 28 | InterruptedException, CfnAssistException, MissingArgumentException { 29 | AwsFacade facade = factory.createFacade(); 30 | File file = new File(args[0]); 31 | facade.tidyNonLBAssocStacks(file, projectAndEnv, args[1]); 32 | } 33 | 34 | @Override 35 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 36 | String... args) 37 | throws CommandLineException { 38 | if (args.length!=2) { 39 | throw new CommandLineException("Missing arguments for command"); 40 | } 41 | if ((args[0]==null) || (args[1]==null)) { 42 | throw new CommandLineException("Missing arguments for command"); 43 | } 44 | super.guardForNoBuildNumber(projectAndEnv); 45 | } 46 | 47 | @Override 48 | public boolean usesProject() { 49 | return true; 50 | } 51 | 52 | @Override 53 | public boolean usesComment() { 54 | return false; 55 | } 56 | 57 | @Override 58 | public boolean usesSNS() { 59 | return true; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/AddTagAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.io.IOException; 14 | import java.util.Collection; 15 | 16 | public class AddTagAction extends SharedAction { 17 | private static final Logger logger = LoggerFactory.getLogger(AddTagAction.class); 18 | 19 | @SuppressWarnings("static-access") 20 | public AddTagAction() { 21 | createOptionWithArgs("tag", "Set a tag on the vpc, provide tag name and tag value", 2); 22 | } 23 | 24 | @Override 25 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 26 | String... args) throws IOException, InterruptedException, CfnAssistException, MissingArgumentException { 27 | String tagName = args[0]; 28 | String tagValue = args[1]; 29 | logger.info(String.format("set tag %s=%s for %s", tagName, tagValue, projectAndEnv)); 30 | AwsFacade aws = factory.createFacade(); 31 | aws.setTagForVpc(projectAndEnv, tagName, tagValue); 32 | } 33 | 34 | @Override 35 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, String... argumentForAction) throws CommandLineException { 36 | guardForProjectAndEnv(projectAndEnv); 37 | guardForNoBuildNumber(projectAndEnv); 38 | } 39 | 40 | @Override 41 | public boolean usesProject() { 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean usesComment() { 47 | return false; 48 | } 49 | 50 | @Override 51 | public boolean usesSNS() { 52 | return false; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/DeleteByNameAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.io.IOException; 14 | import java.util.Collection; 15 | 16 | public class DeleteByNameAction extends SharedAction { 17 | private static final Logger logger = LoggerFactory.getLogger(DeleteAction.class); 18 | 19 | @SuppressWarnings("static-access") 20 | public DeleteByNameAction() { 21 | createOptionWithArg("rm", "The base name (i.e. excluding project and env) of the stack to delete"); 22 | } 23 | 24 | @Override 25 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 26 | String... args) throws 27 | IOException, 28 | InterruptedException, CfnAssistException, MissingArgumentException { 29 | String name = args[0]; 30 | logger.info(String.format("Attempting to delete corresponding to name: %s and %s", name, projectAndEnv)); 31 | AwsFacade aws = factory.createFacade(); 32 | aws.deleteStackByName(name, projectAndEnv); 33 | } 34 | 35 | @Override 36 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 37 | String... argumentForAction) throws CommandLineException { 38 | guardForProjectAndEnv(projectAndEnv); 39 | } 40 | 41 | @Override 42 | public boolean usesProject() { 43 | return true; 44 | } 45 | 46 | @Override 47 | public boolean usesComment() { 48 | return false; 49 | } 50 | 51 | @Override 52 | public boolean usesSNS() { 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/UpdateTargetGroupAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.io.IOException; 12 | import java.util.Collection; 13 | 14 | public class UpdateTargetGroupAction extends SharedAction { 15 | 16 | @SuppressWarnings("static-access") 17 | public UpdateTargetGroupAction() { 18 | createOptionWithArgs("targetGroupUpdate", "Update target group to point at instances tagged with build", 2); 19 | } 20 | 21 | @Override 22 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 23 | String... args) 24 | throws IOException, InterruptedException, 25 | CfnAssistException, MissingArgumentException { 26 | 27 | AwsFacade facade = factory.createFacade(); 28 | int port = Integer.parseInt(args[1]); 29 | facade.updateTargetGroupToInstancesMatchingBuild(projectAndEnv, args[0], port); 30 | } 31 | 32 | @Override 33 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 34 | String... argumentForAction) throws CommandLineException { 35 | guardForProjectAndEnv(projectAndEnv); 36 | if (!projectAndEnv.hasBuildNumber()) { 37 | throw new CommandLineException("You must provide the build parameter"); 38 | } 39 | 40 | try { 41 | Integer.parseInt(argumentForAction[1]); 42 | } 43 | catch (NumberFormatException exception) { 44 | throw new CommandLineException(exception.getMessage()); 45 | } 46 | } 47 | 48 | @Override 49 | public boolean usesProject() { 50 | return true; 51 | } 52 | 53 | @Override 54 | public boolean usesComment() { 55 | return true; 56 | } 57 | 58 | @Override 59 | public boolean usesSNS() { 60 | return false; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/InstancesAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.InstanceSummary; 9 | import tw.com.entity.ProjectAndEnv; 10 | import tw.com.entity.SearchCriteria; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.util.Collection; 14 | import java.util.List; 15 | 16 | public class InstancesAction extends SharedAction { 17 | 18 | @SuppressWarnings("static-access") 19 | public InstancesAction() { 20 | createOption("instances", "List out sumamry of instances"); 21 | } 22 | 23 | @Override 24 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, 25 | Collection cfnParams, String... argument) throws 26 | InterruptedException, CfnAssistException, MissingArgumentException { 27 | AwsFacade aws = factory.createFacade(); 28 | SearchCriteria criteria = new SearchCriteria(projectAndEnv); 29 | List results = aws.listInstances(criteria); 30 | render(results); 31 | } 32 | 33 | @Override 34 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 35 | String... argumentForAction) throws CommandLineException { 36 | guardForProject(projectAndEnv); 37 | guardForNoBuildNumber(projectAndEnv); 38 | } 39 | 40 | private void render(List results) { 41 | System.out.println("id\tPrivate IP\tTags"); 42 | for(InstanceSummary entry : results) { 43 | System.out.println(String.format("%s\t%s\t%s", 44 | entry.getInstance(), 45 | entry.getPrivateIP(), 46 | entry.getTags())); 47 | } 48 | } 49 | 50 | @Override 51 | public boolean usesProject() { 52 | return true; 53 | } 54 | 55 | @Override 56 | public boolean usesComment() { 57 | return false; 58 | } 59 | 60 | @Override 61 | public boolean usesSNS() { 62 | return false; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/ListAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.entity.StackEntry; 10 | import tw.com.exceptions.CfnAssistException; 11 | 12 | import java.io.IOException; 13 | import java.util.Collection; 14 | import java.util.List; 15 | 16 | public class ListAction extends SharedAction { 17 | 18 | @SuppressWarnings("static-access") 19 | public ListAction() { 20 | createOption("ls", "List out the stacks"); 21 | } 22 | 23 | @Override 24 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, 25 | Collection cfnParams, String... argument) throws 26 | IOException, 27 | InterruptedException, CfnAssistException, MissingArgumentException { 28 | AwsFacade aws = factory.createFacade(); 29 | List results = aws.listStacks(projectAndEnv); 30 | render(results); 31 | } 32 | 33 | @Override 34 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 35 | String... argumentForAction) throws CommandLineException { 36 | guardForProject(projectAndEnv); 37 | guardForNoBuildNumber(projectAndEnv); 38 | } 39 | 40 | private void render(List results) { 41 | System.out.println("Stackname\tProject\tEnvironment"); 42 | for(StackEntry entry : results) { 43 | System.out.println(String.format("%s\t%s\t%s\t%s", 44 | entry.getStackName(), 45 | entry.getProject(), 46 | entry.getEnvTag().getEnv(), 47 | entry.getStack().stackStatus())); 48 | } 49 | } 50 | 51 | @Override 52 | public boolean usesProject() { 53 | return true; 54 | } 55 | 56 | @Override 57 | public boolean usesComment() { 58 | return false; 59 | } 60 | 61 | @Override 62 | public boolean usesSNS() { 63 | return false; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/tw/com/entity/InstanceSummary.java: -------------------------------------------------------------------------------- 1 | package tw.com.entity; 2 | 3 | import java.util.List; 4 | 5 | import software.amazon.awssdk.services.ec2.model.Tag; 6 | 7 | public class InstanceSummary { 8 | 9 | private String instanceId; 10 | private String privateIP; 11 | private List tags; 12 | 13 | @Override 14 | public int hashCode() { 15 | final int prime = 31; 16 | int result = 1; 17 | result = prime * result 18 | + ((instanceId == null) ? 0 : instanceId.hashCode()); 19 | result = prime * result 20 | + ((privateIP == null) ? 0 : privateIP.hashCode()); 21 | result = prime * result + ((tags == null) ? 0 : tags.hashCode()); 22 | return result; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object obj) { 27 | if (this == obj) 28 | return true; 29 | if (obj == null) 30 | return false; 31 | if (getClass() != obj.getClass()) 32 | return false; 33 | InstanceSummary other = (InstanceSummary) obj; 34 | if (instanceId == null) { 35 | if (other.instanceId != null) 36 | return false; 37 | } else if (!instanceId.equals(other.instanceId)) 38 | return false; 39 | if (privateIP == null) { 40 | if (other.privateIP != null) 41 | return false; 42 | } else if (!privateIP.equals(other.privateIP)) 43 | return false; 44 | if (tags == null) { 45 | if (other.tags != null) 46 | return false; 47 | } else if (!tags.equals(other.tags)) 48 | return false; 49 | return true; 50 | } 51 | 52 | public InstanceSummary(String instanceId, String privateIP, List tags) { 53 | this.instanceId = instanceId; 54 | this.privateIP = privateIP; 55 | this.tags = tags; 56 | } 57 | 58 | public String getInstance() { 59 | return instanceId; 60 | } 61 | 62 | public String getPrivateIP() { 63 | return privateIP; 64 | } 65 | 66 | public String getTags() { 67 | StringBuilder builder = new StringBuilder(); 68 | for(Tag tag : tags) { 69 | if (builder.length()!=0) { 70 | builder.append(","); 71 | } 72 | builder.append(String.format("%s=%s",tag.key(),tag.value())); 73 | } 74 | return builder.toString(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/BlockCurrentIPAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | import tw.com.providers.ProvidesCurrentIp; 11 | 12 | import java.util.Collection; 13 | 14 | public class BlockCurrentIPAction extends SharedAction { 15 | 16 | @SuppressWarnings("static-access") 17 | public BlockCurrentIPAction() { 18 | createOptionWithArgs("blockCurrentIP", 19 | "Block (i.e remove from security group) current ip for ELB tagged with type tag for port", 2); 20 | } 21 | 22 | @Override 23 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, 24 | Collection cfnParams, 25 | String... argument) throws 26 | InterruptedException, 27 | CfnAssistException, MissingArgumentException { 28 | 29 | AwsFacade facade = factory.createFacade(); 30 | ProvidesCurrentIp hasCurrentIp = factory.getCurrentIpProvider(); 31 | 32 | Integer port = Integer.parseInt(argument[1]); 33 | facade.removeCurrentIPAndPortFromELB(projectAndEnv, argument[0], hasCurrentIp, port); 34 | } 35 | 36 | @Override 37 | public void validate(ProjectAndEnv projectAndEnv, 38 | Collection cfnParams, 39 | String... argumentForAction) throws CommandLineException { 40 | guardForProjectAndEnv(projectAndEnv); 41 | 42 | try { 43 | Integer.parseInt(argumentForAction[1]); 44 | } 45 | catch (NumberFormatException exception) { 46 | throw new CommandLineException(exception.getLocalizedMessage()); 47 | } 48 | 49 | } 50 | 51 | @Override 52 | public boolean usesProject() { 53 | return true; 54 | } 55 | 56 | @Override 57 | public boolean usesComment() { 58 | return false; 59 | } 60 | 61 | @Override 62 | public boolean usesSNS() { 63 | return false; 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/DirAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.entity.StackNameAndId; 12 | import tw.com.exceptions.CfnAssistException; 13 | 14 | import java.io.IOException; 15 | import java.util.ArrayList; 16 | import java.util.Collection; 17 | 18 | public class DirAction extends SharedAction { 19 | private static final Logger logger = LoggerFactory.getLogger(DirAction.class); 20 | 21 | @SuppressWarnings("static-access") 22 | public DirAction() { 23 | createOptionWithArg("dir","The directory/folder containing delta templates to apply"); 24 | } 25 | 26 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 27 | String... args) throws IOException, CfnAssistException, InterruptedException, MissingArgumentException { 28 | AwsFacade aws = factory.createFacade(); 29 | 30 | String folderPath = args[0]; 31 | ArrayList stackIds = aws.applyTemplatesFromFolder(folderPath , projectAndEnv, cfnParams); 32 | logger.info(String.format("Created %s stacks", stackIds.size())); 33 | for(StackNameAndId name : stackIds) { 34 | logger.info("Created stack " +name); 35 | } 36 | } 37 | 38 | @Override 39 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 40 | String... argumentForAction) 41 | throws CommandLineException { 42 | guardForProjectAndEnv(projectAndEnv); 43 | guardForNoBuildNumber(projectAndEnv); 44 | } 45 | 46 | @Override 47 | public boolean usesProject() { 48 | return true; 49 | } 50 | 51 | @Override 52 | public boolean usesComment() { 53 | return true; 54 | } 55 | 56 | @Override 57 | public boolean usesSNS() { 58 | return true; 59 | } 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/DeleteAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.util.Collection; 16 | 17 | public class DeleteAction extends SharedAction { 18 | private static final Logger logger = LoggerFactory.getLogger(DeleteAction.class); 19 | 20 | @SuppressWarnings("static-access") 21 | public DeleteAction() { 22 | createOptionWithArg("delete", "The template file corresponding to stack to delete"); 23 | } 24 | 25 | @Override 26 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 27 | String... args) throws 28 | IOException, 29 | InterruptedException, CfnAssistException, MissingArgumentException { 30 | String filename = args[0]; 31 | logger.info(String.format("Attempting to delete corresponding to %s and %s", filename, projectAndEnv)); 32 | File templateFile = new File(filename); 33 | AwsFacade aws = factory.createFacade(); 34 | aws.deleteStackFrom(templateFile, projectAndEnv); 35 | } 36 | 37 | @Override 38 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 39 | String... argumentForAction) throws CommandLineException { 40 | guardForProjectAndEnv(projectAndEnv); 41 | // caused nested deleted in ant task to fail, passing artifacts causes no harm/action so comment out for now 42 | //guardForNoArtifacts(artifacts); 43 | } 44 | 45 | @Override 46 | public boolean usesProject() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean usesComment() { 52 | return false; 53 | } 54 | 55 | @Override 56 | public boolean usesSNS() { 57 | return true; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/FetchLogsAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 7 | import tw.com.AwsFacade; 8 | import tw.com.FacadeFactory; 9 | import tw.com.commandline.CommandLineException; 10 | import tw.com.entity.ProjectAndEnv; 11 | import tw.com.exceptions.CfnAssistException; 12 | 13 | import java.nio.file.Path; 14 | import java.util.Collection; 15 | import java.util.List; 16 | 17 | public class FetchLogsAction extends SharedAction { 18 | private static final Logger logger = LoggerFactory.getLogger(FetchLogsAction.class); 19 | 20 | @SuppressWarnings("static-access") 21 | public FetchLogsAction() { 22 | createOptionWithArgs("logs", "Fetchs logs for project for last N hours", 1); 23 | } 24 | 25 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection unused, 26 | String... args) throws CfnAssistException, MissingArgumentException, InterruptedException { 27 | logger.info("Invoking get logs for " + projectAndEnv + " and " + args[0]); 28 | AwsFacade aws = factory.createFacade(); 29 | int hours = Integer.parseInt(args[0]); 30 | 31 | List filenames = aws.fetchLogs(projectAndEnv, hours); 32 | System.out.println("Logs for " + projectAndEnv); 33 | filenames.forEach(filename -> { 34 | System.out.println(String.format("Saved file '%s'", filename.toAbsolutePath().toString()));; 35 | }); 36 | System.out.flush(); 37 | } 38 | 39 | @Override 40 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 41 | String... argumentForAction) 42 | throws CommandLineException { 43 | guardForProjectAndEnv(projectAndEnv); 44 | } 45 | 46 | @Override 47 | public boolean usesProject() { 48 | return true; 49 | } 50 | 51 | @Override 52 | public boolean usesComment() { 53 | return false; 54 | } 55 | 56 | @Override 57 | public boolean usesSNS() { 58 | return true; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /test/tw/com/acceptance/TestCommandLineVPCoperations.java: -------------------------------------------------------------------------------- 1 | package tw.com.acceptance; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; 7 | import software.amazon.awssdk.services.ec2.Ec2Client; 8 | import software.amazon.awssdk.services.ec2.model.Vpc; 9 | import tw.com.CLIArgBuilder; 10 | import tw.com.EnvironmentSetupForTests; 11 | import tw.com.commandline.Main; 12 | import tw.com.providers.CloudClient; 13 | import tw.com.repository.VpcRepository; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | 17 | public class TestCommandLineVPCoperations { 18 | 19 | private static Ec2Client ec2Client; 20 | private Vpc altEnvVPC; 21 | 22 | @BeforeEach 23 | public void beforeEveryTestRun() { 24 | VpcRepository vpcRepository = new VpcRepository(new CloudClient(ec2Client, new DefaultAwsRegionProviderChain())); 25 | altEnvVPC = EnvironmentSetupForTests.findAltVpc(vpcRepository); 26 | } 27 | 28 | @BeforeAll 29 | public static void beforeAllTestsOnce() { 30 | ec2Client = EnvironmentSetupForTests.createEC2Client(); 31 | } 32 | 33 | @Test 34 | public void testInvokeInitViaCommandLine() throws InterruptedException { 35 | EnvironmentSetupForTests.clearVpcTags(ec2Client, altEnvVPC); 36 | String[] args = CLIArgBuilder.initVPC(EnvironmentSetupForTests.ALT_ENV, EnvironmentSetupForTests.PROJECT, altEnvVPC.vpcId()); 37 | 38 | Main main = new Main(args); 39 | int result = main.parse(); 40 | 41 | assertEquals(0,result); 42 | } 43 | 44 | @Test 45 | public void testInvokeResetViaCommandLine() { 46 | String[] args = { 47 | "-env", EnvironmentSetupForTests.ENV, 48 | "-project", EnvironmentSetupForTests.PROJECT, 49 | "-reset" 50 | }; 51 | Main main = new Main(args); 52 | assertEquals(0,main.parse()); 53 | } 54 | 55 | @Test 56 | public void testTagVPCViaCommandLine() { 57 | String[] args = CLIArgBuilder.tagVPC("TEST_TAG_NAME", "Test Tag Value"); 58 | Main main = new Main(args); 59 | assertEquals(0,main.parse()); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/tw/com/parameters/PopulatesParameters.java: -------------------------------------------------------------------------------- 1 | package tw.com.parameters; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 6 | import software.amazon.awssdk.services.cloudformation.model.TemplateParameter; 7 | import tw.com.entity.ProjectAndEnv; 8 | import tw.com.exceptions.CannotFindVpcException; 9 | import tw.com.exceptions.InvalidStackParameterException; 10 | 11 | import java.io.IOException; 12 | import java.util.Collection; 13 | import java.util.List; 14 | 15 | public abstract class PopulatesParameters { 16 | private static final Logger logger = LoggerFactory.getLogger(PopulatesParameters.class); 17 | 18 | public static final String PARAMETER_ENV = "env"; 19 | public static final String PARAMETER_VPC = "vpc"; 20 | public static final String PARAMETER_BUILD_NUMBER = "build"; 21 | 22 | public static final String PARAM_PREFIX = "::"; 23 | public static final String CFN_TAG_ON_OUTPUT = PARAM_PREFIX+"CFN_TAG"; 24 | public static final String ENV_TAG = PARAM_PREFIX+"ENV"; 25 | public static final String CFN_TAG_ZONE = PARAM_PREFIX+"CFN_ZONE_"; 26 | 27 | abstract void addParameters(Collection result, 28 | List declaredParameters, ProjectAndEnv projAndEnv, ProvidesZones providesZones) throws CannotFindVpcException, IOException, InvalidStackParameterException; 29 | 30 | protected void addParameterTo(Collection parameters, List declared, String parameterName, String parameterValue) { 31 | boolean isDeclared = false; 32 | for(TemplateParameter declaration : declared) { 33 | isDeclared = (declaration.parameterKey().equals(parameterName)); 34 | if (isDeclared) break; 35 | } 36 | if (!isDeclared) { 37 | logger.info(String.format("Not populating parameter %s as it is not declared in the json file", parameterName)); 38 | } else { 39 | logger.info(String.format("Setting %s parameter to %s", parameterName, parameterValue)); 40 | Parameter parameter = Parameter.builder().parameterKey(parameterName).parameterValue(parameterValue).build(); 41 | parameters.add(parameter); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/tw/com/parameters/EnvVarParams.java: -------------------------------------------------------------------------------- 1 | package tw.com.parameters; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 6 | import software.amazon.awssdk.services.cloudformation.model.TemplateParameter; 7 | import tw.com.entity.ProjectAndEnv; 8 | import tw.com.exceptions.InvalidStackParameterException; 9 | 10 | import java.util.Collection; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | 14 | public class EnvVarParams extends PopulatesParameters { 15 | private static final Logger logger = LoggerFactory.getLogger(EnvVarParams.class); 16 | 17 | @Override 18 | public void addParameters(Collection result, 19 | List declaredParameters, ProjectAndEnv projAndEnv, ProvidesZones providesZones) 20 | throws InvalidStackParameterException { 21 | List toPopulate = findParametersToFill(declaredParameters); 22 | populateFromEnv(result, toPopulate); 23 | } 24 | 25 | private List findParametersToFill(List declaredParameters) { 26 | List results = new LinkedList<>(); 27 | for(TemplateParameter candidate : declaredParameters) { 28 | if (shouldPopulateFor(candidate)) { 29 | results.add(candidate.parameterKey()); 30 | } 31 | } 32 | return results; 33 | } 34 | 35 | private boolean shouldPopulateFor(TemplateParameter candidate) { 36 | if (candidate.description()==null) { 37 | return false; 38 | } 39 | return candidate.description().equals(PopulatesParameters.ENV_TAG); 40 | } 41 | 42 | private void populateFromEnv(Collection result, 43 | List toPopulate) throws InvalidStackParameterException { 44 | for(String name : toPopulate) { 45 | logger.info("Attempt to populate parameters from environmental variable: " + name); 46 | String value = System.getenv(name); 47 | if (value==null) { 48 | logger.error("Environment variable not set, name was " + name); 49 | throw new InvalidStackParameterException(name); 50 | } 51 | result.add(Parameter.builder().parameterKey(name).parameterValue(value).build()); 52 | } 53 | 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /test/tw/com/unit/TestSavesFile.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | 4 | import org.apache.commons.io.FileUtils; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import tw.com.EnvironmentSetupForTests; 9 | import tw.com.providers.SavesFile; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.nio.charset.Charset; 14 | import java.nio.file.Files; 15 | import java.nio.file.LinkOption; 16 | import java.nio.file.Path; 17 | import java.nio.file.Paths; 18 | import java.nio.file.attribute.PosixFilePermission; 19 | import java.util.Set; 20 | 21 | import static org.junit.jupiter.api.Assertions.*; 22 | 23 | public class TestSavesFile { 24 | private File testFile = new File("testFile"); 25 | private SavesFile savesFile; 26 | private Path filename; 27 | 28 | @BeforeEach 29 | public void beforeEachTestRuns() { 30 | FileUtils.deleteQuietly(testFile); 31 | savesFile = new SavesFile(); 32 | filename = Paths.get(testFile.getAbsolutePath()); 33 | } 34 | 35 | @AfterEach 36 | public void afterEachTestRuns() { 37 | FileUtils.deleteQuietly(testFile); 38 | } 39 | 40 | @Test 41 | public void shouldCheckIfFileExists() throws IOException { 42 | FileUtils.touch(testFile); 43 | assertTrue(savesFile.exists(filename)); 44 | 45 | testFile.delete(); 46 | assertFalse(savesFile.exists(filename)); 47 | } 48 | 49 | @Test 50 | public void shouldSaveStringInFile() throws IOException { 51 | savesFile.save(filename, "someText"); 52 | 53 | String result = FileUtils.readFileToString(testFile, Charset.defaultCharset()); 54 | 55 | assertEquals("someText", result); 56 | } 57 | 58 | @Test 59 | public void shouldChangePermissions() throws IOException { 60 | savesFile.save(filename, "someText"); 61 | savesFile.ownerOnlyPermisssion(filename); 62 | 63 | Set permissions = Files.getPosixFilePermissions(filename, LinkOption.NOFOLLOW_LINKS); 64 | 65 | EnvironmentSetupForTests.checkKeyPairFilePermissions(permissions); 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/tw/com/repository/StackRepository.java: -------------------------------------------------------------------------------- 1 | package tw.com.repository; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.*; 4 | import tw.com.MonitorStackEvents; 5 | import tw.com.entity.*; 6 | import tw.com.exceptions.CfnAssistException; 7 | import tw.com.exceptions.WrongNumberOfStacksException; 8 | import tw.com.providers.CFNClient; 9 | 10 | import java.util.Collection; 11 | import java.util.List; 12 | 13 | public interface StackRepository { 14 | 15 | List getStacks(); 16 | List getStacks(EnvironmentTag envTag); 17 | 18 | List getStacks(ProjectAndEnv projectAndEnv); 19 | 20 | List getStacksMatching(EnvironmentTag envTag, String name); 21 | StackEntry getStacknameByIndex(EnvironmentTag envTag, Integer index) throws WrongNumberOfStacksException; 22 | 23 | StackStatus waitForStatusToChangeFrom(String stackName, 24 | StackStatus currentStatus, List aborts) 25 | throws WrongNumberOfStacksException, InterruptedException; 26 | 27 | List getStackEvents(String stackName); 28 | StackStatus getStackStatus(String stackName) throws WrongNumberOfStacksException; 29 | 30 | StackNameAndId getStackNameAndId(String stackName) throws WrongNumberOfStacksException; 31 | Stack getStack(String stackName) throws WrongNumberOfStacksException; 32 | 33 | void createFail(StackNameAndId id) throws WrongNumberOfStacksException; 34 | Stack createSuccess(StackNameAndId id) throws WrongNumberOfStacksException; 35 | void updateFail(StackNameAndId id) throws WrongNumberOfStacksException; 36 | Stack updateSuccess(StackNameAndId id) throws WrongNumberOfStacksException; 37 | 38 | StackNameAndId updateStack(String contents, Collection parameters, MonitorStackEvents monitor, 39 | String stackName) throws CfnAssistException; 40 | 41 | StackNameAndId createStack(ProjectAndEnv projAndEnv, String contents, String stackName, 42 | Collection parameters, MonitorStackEvents monitor, Tagging tagging) throws CfnAssistException; 43 | 44 | void deleteStack(String stackName); 45 | 46 | List validateStackTemplate(String templateContents); 47 | 48 | List getStackDrifts(ProjectAndEnv projectAndEnv); 49 | } -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/AllowCurrentIPAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | import tw.com.providers.ProvidesCurrentIp; 11 | 12 | import java.io.IOException; 13 | import java.util.Collection; 14 | 15 | public class AllowCurrentIPAction extends SharedAction { 16 | 17 | private static final int INDEX_OF_PORT_ARG = 1; 18 | 19 | @SuppressWarnings("static-access") 20 | public AllowCurrentIPAction() { 21 | createOptionWithArgs("allowCurrentIP", 22 | "Allow current ip (i.e. add to the security group) for ELB tagged with type tag, supply tag & port", 23 | 2); 24 | } 25 | 26 | @Override 27 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, 28 | Collection cfnParams, 29 | String... argument) throws 30 | IOException, InterruptedException, 31 | CfnAssistException, MissingArgumentException { 32 | 33 | AwsFacade facade = factory.createFacade(); 34 | ProvidesCurrentIp hasCurrentIp = factory.getCurrentIpProvider(); 35 | 36 | Integer port = Integer.parseInt(argument[INDEX_OF_PORT_ARG]); 37 | facade.addCurrentIPWithPortToELB(projectAndEnv, argument[0], hasCurrentIp, port); 38 | } 39 | 40 | @Override 41 | public void validate(ProjectAndEnv projectAndEnv, 42 | Collection cfnParams, 43 | String... argumentForAction) throws CommandLineException { 44 | guardForProjectAndEnv(projectAndEnv); 45 | try { 46 | Integer.parseInt(argumentForAction[INDEX_OF_PORT_ARG]); 47 | } 48 | catch (NumberFormatException exception) { 49 | throw new CommandLineException(exception.getLocalizedMessage()); 50 | } 51 | 52 | } 53 | 54 | @Override 55 | public boolean usesProject() { 56 | return true; 57 | } 58 | 59 | @Override 60 | public boolean usesComment() { 61 | return false; 62 | } 63 | 64 | @Override 65 | public boolean usesSNS() { 66 | return false; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/FileRecorder.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | import java.io.FileWriter; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import software.amazon.awssdk.services.ec2.model.Vpc; 13 | 14 | public class FileRecorder implements Recorder { 15 | private static final Logger logger = LoggerFactory.getLogger(FileRecorder.class); 16 | 17 | StringBuilder builder; 18 | private FileWriter fw; 19 | private Path folder; 20 | 21 | public FileRecorder(Path folder) { 22 | this.folder = folder; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object obj) { 27 | if (this == obj) 28 | return true; 29 | if (obj == null) 30 | return false; 31 | if (getClass() != obj.getClass()) 32 | return false; 33 | FileRecorder other = (FileRecorder) obj; 34 | if (folder == null) { 35 | if (other.folder != null) 36 | return false; 37 | } else if (!folder.equals(other.folder)) 38 | return false; 39 | return true; 40 | } 41 | 42 | @Override 43 | public void write(String string) { 44 | builder.append(string); 45 | } 46 | 47 | @Override 48 | public void writeline(String string) { 49 | builder.append(string); 50 | builder.append("\n"); 51 | } 52 | 53 | @Override 54 | public void writeLabel(String label) { 55 | builder.append(String.format("label = \"%s\";", label)); 56 | builder.append("\n"); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return builder.toString(); 62 | } 63 | 64 | @Override 65 | public void beginFor(Vpc vpc, String prefix) throws IOException { 66 | builder = new StringBuilder(); 67 | String diagramFilename = prefix+vpc.vpcId()+".dot"; 68 | Path target = Paths.get(folder.toString(), diagramFilename); 69 | logger.info(">>>>> Saving to " + target.toAbsolutePath()); 70 | if (!Files.exists(folder)) { 71 | logger.warn("Target folder is missing, creating " + folder); 72 | Files.createDirectory(folder); 73 | } 74 | fw = new FileWriter(target.toFile()); 75 | } 76 | 77 | @Override 78 | public void end() throws IOException { 79 | fw.append(builder.toString()); 80 | fw.close(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/AllowHostAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.net.UnknownHostException; 12 | import java.util.Collection; 13 | 14 | public class AllowHostAction extends SharedAction { 15 | 16 | private static final int INDEX_OF_PORT_ARG = 2; 17 | 18 | @SuppressWarnings("static-access") 19 | public AllowHostAction() { 20 | createOptionWithArgs("allowhost", 21 | "Allow given host ip's (i.e. add to the security group) for ELB tagged with type tag, supply tag, hostname & port", 22 | 3); 23 | } 24 | 25 | @Override 26 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, 27 | Collection cfnParams, 28 | String... argument) throws InterruptedException, MissingArgumentException, CfnAssistException { 29 | 30 | AwsFacade facade = factory.createFacade(); 31 | 32 | Integer port = Integer.parseInt(argument[INDEX_OF_PORT_ARG]); 33 | String hostName = argument[1]; 34 | try { 35 | facade.addHostAndPortToELB(projectAndEnv, argument[0], hostName, port); 36 | } catch (UnknownHostException unknownHost) { 37 | throw new CfnAssistException("Unable to resolve host "+ hostName, unknownHost); 38 | } 39 | } 40 | 41 | @Override 42 | public void validate(ProjectAndEnv projectAndEnv, 43 | Collection cfnParams, 44 | String... argumentForAction) throws CommandLineException { 45 | guardForProjectAndEnv(projectAndEnv); 46 | try { 47 | Integer.parseInt(argumentForAction[INDEX_OF_PORT_ARG]); 48 | } 49 | catch (NumberFormatException exception) { 50 | throw new CommandLineException(exception.getLocalizedMessage()); 51 | } 52 | } 53 | 54 | @Override 55 | public boolean usesProject() { 56 | return true; 57 | } 58 | 59 | @Override 60 | public boolean usesComment() { 61 | return false; 62 | } 63 | 64 | @Override 65 | public boolean usesSNS() { 66 | return false; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/SSHCommandAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 8 | import tw.com.AwsFacade; 9 | import tw.com.FacadeFactory; 10 | import tw.com.commandline.CommandExecutor; 11 | import tw.com.commandline.CommandLineException; 12 | import tw.com.entity.ProjectAndEnv; 13 | import tw.com.exceptions.CfnAssistException; 14 | 15 | import java.io.IOException; 16 | import java.util.Collection; 17 | import java.util.List; 18 | 19 | public class SSHCommandAction extends SharedAction { 20 | private static final Logger logger = LoggerFactory.getLogger(SSHCommandAction.class); 21 | 22 | @SuppressWarnings("static-access") 23 | public SSHCommandAction() { 24 | createOptionalWithOptionalArg("ssh", "Create ssh command for the project/env combination"); 25 | } 26 | 27 | 28 | @Override 29 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 30 | String... argument) throws IOException, InterruptedException, CfnAssistException, MissingArgumentException { 31 | 32 | AwsFacade aws = factory.createFacade(); 33 | CommandExecutor commandExecutor = factory.getCommandExecutor(); 34 | String user = (argument==null) ? "ec2-user" : argument[0]; 35 | List sshCommand = aws.createSSHCommand(projectAndEnv, user); 36 | logger.info("About to execute " + sshCommand); 37 | commandExecutor.execute(sshCommand); 38 | } 39 | 40 | @Override 41 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 42 | String... argumentForAction) throws CommandLineException { 43 | guardForProjectAndEnv(projectAndEnv); 44 | guardForNoBuildNumber(projectAndEnv); 45 | } 46 | 47 | @Override 48 | public boolean usesProject() { 49 | return true; 50 | } 51 | 52 | @Override 53 | public boolean usesComment() { 54 | return false; 55 | } 56 | 57 | @Override 58 | public boolean usesSNS() { 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/BlockHostAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import org.apache.commons.cli.MissingArgumentException; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import java.net.UnknownHostException; 12 | import java.util.Collection; 13 | 14 | public class BlockHostAction extends SharedAction { 15 | 16 | private static final int INDEX_OF_PORT_ARG = 2; 17 | 18 | @SuppressWarnings("static-access") 19 | public BlockHostAction() { 20 | createOptionWithArgs("blockhost", 21 | "Block given host ip's (i.e. remove from the security group) for ELB tagged with type tag, supply tag, hostname & port", 22 | 3); 23 | } 24 | 25 | @Override 26 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, 27 | Collection cfnParams, 28 | String... argument) throws 29 | InterruptedException, 30 | CfnAssistException, MissingArgumentException { 31 | 32 | AwsFacade facade = factory.createFacade(); 33 | 34 | Integer port = Integer.parseInt(argument[INDEX_OF_PORT_ARG]); 35 | String hostName = argument[1]; 36 | try { 37 | facade.removeHostAndPortFromELB(projectAndEnv, argument[0], hostName, port); 38 | } catch (UnknownHostException unknownHost) { 39 | throw new CfnAssistException("Unable to resolve host "+ hostName, unknownHost); 40 | } 41 | } 42 | 43 | @Override 44 | public void validate(ProjectAndEnv projectAndEnv, 45 | Collection cfnParams, 46 | String... argumentForAction) throws CommandLineException { 47 | guardForProjectAndEnv(projectAndEnv); 48 | try { 49 | Integer.parseInt(argumentForAction[INDEX_OF_PORT_ARG]); 50 | } 51 | catch (NumberFormatException exception) { 52 | throw new CommandLineException(exception.getLocalizedMessage()); 53 | } 54 | } 55 | 56 | @Override 57 | public boolean usesProject() { 58 | return true; 59 | } 60 | 61 | @Override 62 | public boolean usesComment() { 63 | return false; 64 | } 65 | 66 | @Override 67 | public boolean usesSNS() { 68 | return false; 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/ListDriftAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.entity.StackEntry; 10 | import tw.com.exceptions.CfnAssistException; 11 | import tw.com.providers.CFNClient; 12 | 13 | import java.io.IOException; 14 | import java.util.Collection; 15 | import java.util.List; 16 | 17 | public class ListDriftAction extends SharedAction { 18 | 19 | @SuppressWarnings("static-access") 20 | public ListDriftAction() { 21 | createOption("drift", "List out the stack drift status"); 22 | } 23 | 24 | @Override 25 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, 26 | Collection cfnParams, String... argument) throws 27 | IOException, 28 | InterruptedException, CfnAssistException, MissingArgumentException { 29 | AwsFacade aws = factory.createFacade(); 30 | 31 | List results = aws.listStackDrift(projectAndEnv); 32 | render(results); 33 | } 34 | 35 | @Override 36 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, 37 | String... argumentForAction) throws CommandLineException { 38 | guardForProject(projectAndEnv); 39 | guardForNoBuildNumber(projectAndEnv); 40 | } 41 | 42 | private void render(List results) { 43 | System.out.println("Stackname\tProject\tEnvironment\tDrift\tCount"); 44 | for(StackEntry entry : results) { 45 | CFNClient.DriftStatus driftStatus = entry.getDriftStatus(); 46 | System.out.println(String.format("%s\t%s\t%s\t%s\t%s", 47 | entry.getStackName(), 48 | entry.getProject(), 49 | entry.getEnvTag().getEnv(), 50 | driftStatus.getStackDriftStatus(), 51 | driftStatus.getDriftedStackResourceCount())); 52 | } 53 | } 54 | 55 | @Override 56 | public boolean usesProject() { 57 | return true; 58 | } 59 | 60 | @Override 61 | public boolean usesComment() { 62 | return false; 63 | } 64 | 65 | @Override 66 | public boolean usesSNS() { 67 | return false; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /test/tw/com/unit/TestStackCache.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | 4 | import org.easymock.EasyMock; 5 | import org.easymock.EasyMockSupport; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | import software.amazon.awssdk.services.cloudformation.model.Stack; 9 | import tw.com.EnvironmentSetupForTests; 10 | import tw.com.StackCache; 11 | import tw.com.entity.StackEntry; 12 | import tw.com.providers.CFNClient; 13 | 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | import static tw.com.EnvironmentSetupForTests.createCfnStackTAG; 19 | 20 | public class TestStackCache extends EasyMockSupport { 21 | 22 | @Test 23 | public void shouldGetStackEntry() { 24 | 25 | CFNClient formationClient = createMock(CFNClient.class); 26 | List stacks = new LinkedList<>(); 27 | stacks.add(Stack.builder().tags( 28 | createCfnStackTAG("CFN_ASSIST_PROJECT",EnvironmentSetupForTests.PROJECT), 29 | createCfnStackTAG("CFN_ASSIST_ENV", EnvironmentSetupForTests.ENV), 30 | createCfnStackTAG("CFN_ASSIST_BUILD_NUMBER", "42"), 31 | createCfnStackTAG("CFN_ASSIST_DELTA", "8"), 32 | createCfnStackTAG("CFN_ASSIST_UPDATE", "9,10,11")).build()); 33 | EasyMock.expect(formationClient.describeAllStacks()).andReturn(stacks); 34 | 35 | StackCache stackCache = new StackCache(formationClient, EnvironmentSetupForTests.PROJECT); 36 | 37 | replayAll(); 38 | List result = stackCache.getEntries(); 39 | verifyAll(); 40 | 41 | Assertions.assertEquals(1, result.size()); 42 | StackEntry stack = result.get(0); 43 | Assertions.assertEquals(EnvironmentSetupForTests.PROJECT, stack.getProject()); 44 | Assertions.assertEquals(EnvironmentSetupForTests.ENV, stack.getEnvTag().getEnv()); 45 | Assertions.assertEquals(42, stack.getBuildNumber()); 46 | Assertions.assertEquals(8, stack.getIndex()); 47 | Set updateIndexs = stack.getUpdateIndex(); 48 | Assertions.assertEquals(3, updateIndexs.size()); 49 | Assertions.assertTrue(updateIndexs.contains(9)); 50 | Assertions.assertTrue(updateIndexs.contains(10)); 51 | Assertions.assertTrue(updateIndexs.contains(11)); 52 | 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/CreateKeyPairAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.MissingArgumentException; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import tw.com.AwsFacade; 6 | import tw.com.FacadeFactory; 7 | import tw.com.commandline.CommandLineException; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CfnAssistException; 10 | import tw.com.providers.CloudClient; 11 | 12 | import java.io.IOException; 13 | import java.nio.file.Paths; 14 | import java.util.Collection; 15 | 16 | import static java.lang.String.format; 17 | 18 | public class CreateKeyPairAction extends SharedAction { 19 | 20 | @SuppressWarnings("static-access") 21 | public CreateKeyPairAction() { 22 | createOptionalWithOptionalArg("keypair", "Create a new keypair for the given project and environment"); 23 | } 24 | 25 | @Override 26 | public void invoke(FacadeFactory factory, ProjectAndEnv projectAndEnv, Collection cfnParams, 27 | String... argument) throws IOException, InterruptedException, 28 | CfnAssistException, MissingArgumentException { 29 | AwsFacade facade = factory.createFacade(); 30 | 31 | String filename; 32 | if (argument==null) { 33 | String home = System.getenv("HOME"); 34 | String keypairName = format("%s_%s", projectAndEnv.getProject(), projectAndEnv.getEnv()); 35 | filename = format("%s/.ssh/%s.pem", home, keypairName); 36 | } else { 37 | filename = argument[0]; 38 | } 39 | 40 | CloudClient.AWSPrivateKey keyPair = facade.createKeyPair(projectAndEnv, factory.getSavesFile(), Paths.get(filename)); 41 | System.out.println(format("Created key %s", keyPair.getKeyName())); 42 | } 43 | 44 | @Override 45 | public void validate(ProjectAndEnv projectAndEnv, Collection cfnParams, String... argumentForAction) throws CommandLineException { 46 | guardForProjectAndEnv(projectAndEnv); 47 | } 48 | 49 | @Override 50 | public boolean usesProject() { 51 | return true; 52 | } 53 | 54 | @Override 55 | public boolean usesComment() { 56 | return false; 57 | } 58 | 59 | @Override 60 | public boolean usesSNS() { 61 | return false; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/tw/com/unit/TestDiagramCreator.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Paths; 6 | 7 | import org.easymock.EasyMock; 8 | import org.easymock.EasyMockSupport; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import tw.com.VpcTestBuilder; 13 | import tw.com.exceptions.CfnAssistException; 14 | import tw.com.pictures.AmazonVPCFacade; 15 | import tw.com.pictures.DiagramCreator; 16 | import tw.com.pictures.dot.FileRecorder; 17 | import tw.com.pictures.dot.Recorder; 18 | 19 | class TestDiagramCreator extends EasyMockSupport { 20 | 21 | private AmazonVPCFacade awsFacade; 22 | private Recorder recorder; 23 | private VpcTestBuilder vpcTestBuilder; 24 | 25 | @BeforeEach 26 | public void beforeEachTestRuns() throws CfnAssistException { 27 | awsFacade = createStrictMock(AmazonVPCFacade.class); 28 | recorder = createMock(Recorder.class); 29 | 30 | vpcTestBuilder = new VpcTestBuilder("vpcId"); 31 | vpcTestBuilder.setGetVpcsExpectations(awsFacade); 32 | vpcTestBuilder.setFacadeVisitExpections(awsFacade); 33 | } 34 | 35 | @Test 36 | void invokeDiagramCreationWithMockedRecorder() throws IOException, CfnAssistException { 37 | 38 | recorder.beginFor(vpcTestBuilder.getVpc(), "network_diagram"); 39 | EasyMock.expectLastCall(); 40 | recorder.writeline(EasyMock.anyString()); 41 | EasyMock.expectLastCall().atLeastOnce(); 42 | recorder.write(EasyMock.anyString()); 43 | EasyMock.expectLastCall().atLeastOnce(); 44 | recorder.end(); 45 | EasyMock.expectLastCall(); 46 | recorder.beginFor(vpcTestBuilder.getVpc(), "security_diagram"); 47 | EasyMock.expectLastCall(); 48 | recorder.end(); 49 | EasyMock.expectLastCall(); 50 | 51 | DiagramCreator creator = new DiagramCreator(awsFacade); 52 | 53 | replayAll(); 54 | creator.createDiagrams(recorder); 55 | verifyAll(); 56 | } 57 | 58 | @Test 59 | void invokeDiagramCreationWithRealRecorder() throws IOException, CfnAssistException { 60 | 61 | Recorder realRecorder = new FileRecorder(Paths.get(".")); 62 | DiagramCreator creator = new DiagramCreator(awsFacade); 63 | 64 | replayAll(); 65 | creator.createDiagrams(realRecorder); 66 | verifyAll(); 67 | 68 | assert(new File("./network_diagramvpcId.dot").exists()); 69 | assert(new File("./security_diagramvpcId.dot").exists()); 70 | 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/cfnScripts/elbAndInstance.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"test template for cnfassist - creates an ELB and an Instance", 4 | "Parameters":{ 5 | "env":{ 6 | "Type":"String" 7 | }, 8 | "vpc":{ 9 | "Type":"String" 10 | }, 11 | "zoneA":{ 12 | "Type":"String", 13 | "Default":"eu-west-1a", 14 | "Description":"zoneADescription" 15 | } 16 | }, 17 | "Resources":{ 18 | 19 | "vpcSubnet":{ 20 | "Type":"AWS::EC2::Subnet", 21 | "Properties":{ 22 | "AvailabilityZone":{ 23 | "Ref":"zoneA" 24 | }, 25 | "CidrBlock":"10.0.10.13/24", 26 | "VpcId":{ 27 | "Ref":"vpc" 28 | } 29 | } 30 | }, 31 | "sgLoadBalance": { 32 | "Type": "AWS::EC2::SecurityGroup", 33 | "Properties": { 34 | "GroupDescription": "Load Balancer Security Group", 35 | "VpcId" : { "Ref" : "vpc" }, 36 | "SecurityGroupIngress": [ 37 | { "IpProtocol": "tcp", "FromPort": "80", "ToPort": "80", "CidrIp": "10.0.0.0/16" } 38 | ], 39 | "SecurityGroupEgress": [ 40 | { "IpProtocol": "tcp", "FromPort": "80", "ToPort": "80", "CidrIp": "10.0.0.0/16" } 41 | ] 42 | } 43 | }, 44 | "loadBalancer":{ 45 | "Type":"AWS::ElasticLoadBalancing::LoadBalancer", 46 | "Properties":{ 47 | "HealthCheck":{ 48 | "HealthyThreshold":"2", 49 | "Interval":"15", 50 | "Target":"HTTP:8080/api/status", 51 | "Timeout":"5", 52 | "UnhealthyThreshold":"2" 53 | }, 54 | "Subnets":[ 55 | { 56 | "Ref":"vpcSubnet" 57 | } 58 | ], 59 | "Listeners":[ 60 | { 61 | "InstancePort":"8082", 62 | "LoadBalancerPort":"80", 63 | "Protocol":"HTTP", 64 | "PolicyNames":[ 65 | 66 | ] 67 | 68 | } 69 | ], 70 | "SecurityGroups" : [ { "Ref" : "sgLoadBalance" } ] 71 | } 72 | }, 73 | "simpleInstance":{ 74 | "Type":"AWS::EC2::Instance", 75 | "Properties":{ 76 | "InstanceType":"t2.micro", 77 | "ImageId":"ami-0f3164307ee5d695a", 78 | "SubnetId":{ 79 | "Ref":"vpcSubnet" 80 | }, 81 | "Tags": [ 82 | { "Key" : "Name", "Value": "aTestInstance" }, 83 | { "Key" : "CFN_ASSIST_TYPE", "Value": "web" } 84 | ] 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /test/tw/com/unit/TestStackEntry.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import software.amazon.awssdk.services.cloudformation.model.Stack; 5 | import tw.com.entity.EnvironmentTag; 6 | import tw.com.entity.StackEntry; 7 | 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | public class TestStackEntry { 15 | 16 | @Test 17 | public void shouldExtractBasenameFromEntryWithNoBuildNumber() { 18 | Stack stack = createAStack("ProjectEnvTheBaseName"); 19 | StackEntry entry = new StackEntry("Project", new EnvironmentTag("Env"), stack); 20 | 21 | assertEquals("TheBaseName", entry.getBaseName()); 22 | } 23 | 24 | @Test 25 | public void shouldExtractBasenameFromEntryWithBuildNumber() { 26 | Stack stack = createAStack("Project42EnvTheBaseName"); 27 | StackEntry entry = new StackEntry("Project", new EnvironmentTag("Env"), stack); 28 | entry.setBuildNumber(42); 29 | 30 | assertEquals("TheBaseName",entry.getBaseName()); 31 | } 32 | 33 | private Stack createAStack(String name) { 34 | return Stack.builder().stackName(name).build(); 35 | } 36 | 37 | @Test 38 | public void shouldHaveIndex() { 39 | StackEntry entry = new StackEntry("Project", new EnvironmentTag("Env"), createAStack("theStackName")); 40 | entry.setIndex(56); 41 | 42 | assertTrue(entry.hasIndex()); 43 | assertEquals(56, entry.getIndex()); 44 | } 45 | 46 | @Test 47 | public void shouldHaveUpdateIndex() { 48 | StackEntry entry = new StackEntry("Project", new EnvironmentTag("Env"), createAStack("theStackName")); 49 | Set updates = new HashSet<>(List.of(42)); 50 | entry.setUpdateIndex(updates); 51 | 52 | assertTrue(entry.hasUpdateIndex()); 53 | assertEquals(new HashSet<>(List.of(42)), entry.getUpdateIndex()); 54 | } 55 | 56 | @Test 57 | public void shouldHaveEquality() { 58 | StackEntry entryA = new StackEntry("project", new EnvironmentTag("Env1"), createAStack("")); 59 | StackEntry entryB = new StackEntry("project", new EnvironmentTag("Env1"), createAStack("")); 60 | StackEntry entryC = new StackEntry("project", new EnvironmentTag("Env2"), createAStack("")); 61 | 62 | assertEquals(entryA, entryB); 63 | assertEquals(entryB, entryA); 64 | assertNotEquals(entryC, entryA); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /test/tw/com/unit/TestDeletionsPending.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import tw.com.SetsDeltaIndex; 7 | import tw.com.entity.DeletionPending; 8 | import tw.com.entity.DeletionsPending; 9 | import tw.com.entity.StackNameAndId; 10 | import tw.com.exceptions.CannotFindVpcException; 11 | 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | 15 | class TestDeletionsPending implements SetsDeltaIndex { 16 | 17 | private DeletionsPending pending; 18 | private Integer setDelta; 19 | 20 | @BeforeEach 21 | public void beforeEachTestRuns() { 22 | pending = new DeletionsPending(); 23 | 24 | pending.add(2, new StackNameAndId("nameA", "112")); 25 | pending.add(1, new StackNameAndId("nameB", "113")); 26 | pending.add(3, new StackNameAndId("nameC", "114")); 27 | 28 | setDelta = -100; 29 | } 30 | 31 | @Test 32 | void shouldIterateOverPendingsInCorrectOrder() { 33 | 34 | LinkedList results = new LinkedList<>(); 35 | for(DeletionPending item : pending) { 36 | results.add(item.getDelta()); 37 | } 38 | 39 | Assertions.assertEquals(3, results.size()); 40 | Assertions.assertEquals(3, (int)results.get(0)); 41 | Assertions.assertEquals(2, (int)results.get(1)); 42 | Assertions.assertEquals(1, (int)results.get(2)); 43 | } 44 | 45 | @Test 46 | void shouldMarkItemsAsDeleted() { 47 | pending.markIdAsDeleted("114"); 48 | List result = pending.getNamesOfDeleted(); 49 | Assertions.assertEquals(1, result.size()); 50 | Assertions.assertEquals("nameC", result.get(0)); 51 | Assertions.assertTrue(pending.hasMore()); 52 | 53 | pending.markIdAsDeleted("112"); 54 | result = pending.getNamesOfDeleted(); 55 | Assertions.assertEquals(2, result.size()); 56 | Assertions.assertEquals("nameA", result.get(1)); 57 | Assertions.assertTrue(pending.hasMore()); 58 | 59 | pending.markIdAsDeleted("113"); 60 | result = pending.getNamesOfDeleted(); 61 | Assertions.assertEquals(3, result.size()); 62 | Assertions.assertEquals("nameB", result.get(2)); 63 | Assertions.assertFalse(pending.hasMore()); 64 | } 65 | 66 | @Test 67 | void shouldUpdateDeltaIndexCorrectly() throws CannotFindVpcException { 68 | pending.markIdAsDeleted("114"); 69 | pending.markIdAsDeleted("112"); 70 | 71 | pending.updateDeltaIndex(this); 72 | Assertions.assertEquals(1, setDelta); 73 | } 74 | 75 | @Override 76 | public void setDeltaIndex(Integer setDelta) throws CannotFindVpcException { 77 | this.setDelta = setDelta; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /test/tw/com/integration/TestPictureGeneration.java: -------------------------------------------------------------------------------- 1 | package tw.com.integration; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; 6 | import software.amazon.awssdk.services.ec2.Ec2Client; 7 | import software.amazon.awssdk.services.elasticloadbalancing.ElasticLoadBalancingClient; 8 | import software.amazon.awssdk.services.rds.RdsClient; 9 | import tw.com.EnvironmentSetupForTests; 10 | import tw.com.exceptions.CfnAssistException; 11 | import tw.com.pictures.AmazonVPCFacade; 12 | import tw.com.pictures.DiagramCreator; 13 | import tw.com.pictures.dot.FileRecorder; 14 | import tw.com.pictures.dot.Recorder; 15 | import tw.com.providers.CloudClient; 16 | import tw.com.providers.CFNClient; 17 | import tw.com.providers.LoadBalancerClassicClient; 18 | import tw.com.providers.RDSClient; 19 | import tw.com.repository.*; 20 | 21 | import java.io.IOException; 22 | import java.nio.file.Path; 23 | import java.nio.file.Paths; 24 | 25 | public class TestPictureGeneration { 26 | 27 | private RDSClient rdsClient; 28 | private CloudRepository cloudRepository; 29 | private ELBRepository elbRepository; 30 | 31 | @BeforeEach 32 | public void beforeEachTestRuns() { 33 | Ec2Client ec2Client = EnvironmentSetupForTests.createEC2Client(); 34 | ElasticLoadBalancingClient awsElbClient = EnvironmentSetupForTests.createELBClient(); 35 | software.amazon.awssdk.services.cloudformation.CloudFormationClient cfnClient = EnvironmentSetupForTests.createCFNClient(); 36 | RdsClient awsRdsClient = EnvironmentSetupForTests.createRDSClient(); 37 | 38 | CloudClient cloudClient = new CloudClient(ec2Client, new DefaultAwsRegionProviderChain()); 39 | LoadBalancerClassicClient elbClient = new LoadBalancerClassicClient(awsElbClient); 40 | VpcRepository vpcRepository = new VpcRepository(cloudClient); 41 | 42 | CFNClient CFNClient = new CFNClient(cfnClient); 43 | cloudRepository = new CloudRepository(cloudClient); 44 | ResourceRepository cfnRepository = new CfnRepository(CFNClient, cloudRepository, "CfnAssist"); 45 | 46 | elbRepository = new ELBRepository(elbClient, vpcRepository, cfnRepository); 47 | rdsClient = new RDSClient(awsRdsClient); 48 | } 49 | 50 | @Test 51 | public void shouldGenerateDiagramFromCurrentAccountVPCs() throws IOException, CfnAssistException { 52 | Path folder = Paths.get(".").toAbsolutePath(); 53 | Recorder recorder = new FileRecorder(folder); 54 | 55 | AmazonVPCFacade awsFacade = new AmazonVPCFacade(cloudRepository, elbRepository, rdsClient); 56 | DiagramCreator createsDiagrams = new DiagramCreator(awsFacade); 57 | createsDiagrams.createDiagrams(recorder); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/tw/com/providers/SavesFile.java: -------------------------------------------------------------------------------- 1 | package tw.com.providers; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import tw.com.entity.OutputLogEventDecorator; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.PrintWriter; 11 | import java.nio.charset.Charset; 12 | import java.nio.file.Files; 13 | import java.nio.file.OpenOption; 14 | import java.nio.file.Path; 15 | import java.nio.file.StandardOpenOption; 16 | import java.nio.file.attribute.PosixFilePermission; 17 | import java.util.HashSet; 18 | import java.util.List; 19 | import java.util.Set; 20 | import java.util.stream.Stream; 21 | 22 | import static java.lang.String.format; 23 | 24 | public class SavesFile { 25 | private static final Logger logger = LoggerFactory.getLogger(SavesFile.class); 26 | private Set permissionSet = new HashSet<>(); 27 | private OpenOption[] options = new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.APPEND, 28 | StandardOpenOption.WRITE}; 29 | 30 | public SavesFile() { 31 | permissionSet.add(PosixFilePermission.OWNER_READ); 32 | permissionSet.add(PosixFilePermission.OWNER_WRITE); 33 | } 34 | 35 | public boolean save(Path destination, String contents) { 36 | File file = destination.toFile(); 37 | try { 38 | FileUtils.write(file, contents, Charset.defaultCharset()); 39 | return true; 40 | } catch (IOException e) { 41 | logger.error("Unable to save to file " + file.getAbsolutePath(), e); 42 | return false; 43 | } 44 | } 45 | 46 | public boolean exists(Path savesFile) { 47 | return savesFile.toFile().exists(); 48 | } 49 | 50 | public void ownerOnlyPermisssion(Path filename) throws IOException { 51 | Files.setPosixFilePermissions(filename, permissionSet); 52 | } 53 | 54 | public boolean save(Path path, List> fetchLogs) { 55 | if (Files.exists(path)) { 56 | logger.warn(format("File '%s' already exists", path.toAbsolutePath().toString())); 57 | } 58 | try { 59 | logger.info("Writing events to " + path.toAbsolutePath()); 60 | PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(path, options), true); 61 | fetchLogs.forEach(stream -> stream.forEach(item -> printWriter.println(item.toString()))); 62 | printWriter.close(); 63 | } catch (IOException e) { 64 | logger.error("Unable to save to file " + path.toAbsolutePath(), e); 65 | return false; 66 | } 67 | return true; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/tw/com/unit/TestEnvVarParams.java: -------------------------------------------------------------------------------- 1 | package tw.com.unit; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 5 | import software.amazon.awssdk.services.cloudformation.model.TemplateParameter; 6 | import software.amazon.awssdk.services.ec2.model.AvailabilityZone; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import tw.com.entity.ProjectAndEnv; 9 | import tw.com.exceptions.CannotFindVpcException; 10 | import tw.com.exceptions.InvalidStackParameterException; 11 | import tw.com.parameters.EnvVarParams; 12 | import tw.com.parameters.ProvidesZones; 13 | 14 | import java.io.IOException; 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.fail; 21 | 22 | public class TestEnvVarParams implements ProvidesZones { 23 | 24 | private EnvVarParams envVarParams; 25 | private ProjectAndEnv projAndEnv; 26 | 27 | @BeforeEach 28 | public void beforeEveryTestRuns() { 29 | envVarParams = new EnvVarParams(); 30 | projAndEnv = new ProjectAndEnv("cfnassist", "test"); 31 | } 32 | 33 | /////// 34 | // needs environmental variable set to testEnvVar set to testValue 35 | /////// 36 | 37 | @Test 38 | public void shouldPickUpVariableFromEnvironmentIfDeclaredInTemplate() throws CannotFindVpcException, IOException, InvalidStackParameterException { 39 | 40 | List results = new LinkedList<>(); 41 | List declaredParameters = new LinkedList<>(); 42 | declaredParameters.add(TemplateParameter.builder(). 43 | description("::ENV"). 44 | parameterKey("testEnvVar").build()); 45 | 46 | envVarParams.addParameters(results , declaredParameters, projAndEnv, this); 47 | 48 | assertEquals(1, results.size()); 49 | Parameter firstResult = results.get(0); 50 | assertEquals("testValue", firstResult.parameterValue()); 51 | assertEquals("testEnvVar", firstResult.parameterKey()); 52 | } 53 | 54 | @Test 55 | public void shouldThrowIfEnvVariableIsNotFound() throws CannotFindVpcException, IOException { 56 | 57 | List results = new LinkedList<>(); 58 | List declaredParameters = new LinkedList<>(); 59 | declaredParameters.add(TemplateParameter.builder(). 60 | description("::ENV"). 61 | parameterKey("envVarShouldNotExist").build()); 62 | try { 63 | envVarParams.addParameters(results , declaredParameters, projAndEnv, this); 64 | fail("should have thrown"); 65 | } 66 | catch(InvalidStackParameterException expected) { 67 | // expected 68 | } 69 | } 70 | 71 | @Override 72 | public Map getZones() { 73 | return null; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/tw/com/UpdateStackExpectations.java: -------------------------------------------------------------------------------- 1 | package tw.com; 2 | 3 | import software.amazon.awssdk.services.cloudformation.model.Parameter; 4 | import software.amazon.awssdk.services.cloudformation.model.Stack; 5 | import software.amazon.awssdk.services.cloudformation.model.StackStatus; 6 | import software.amazon.awssdk.services.cloudformation.model.TemplateParameter; 7 | import software.amazon.awssdk.services.ec2.model.AvailabilityZone; 8 | import software.amazon.awssdk.services.ec2.model.Vpc; 9 | import org.easymock.EasyMock; 10 | import org.easymock.EasyMockSupport; 11 | import tw.com.entity.ProjectAndEnv; 12 | import tw.com.entity.StackNameAndId; 13 | import tw.com.exceptions.CfnAssistException; 14 | import tw.com.repository.CloudFormRepository; 15 | import tw.com.repository.CloudRepository; 16 | import tw.com.repository.VpcRepository; 17 | 18 | import java.io.IOException; 19 | import java.util.Collection; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | 25 | public class UpdateStackExpectations extends EasyMockSupport { 26 | protected static final String VPC_ID = "vpcId"; 27 | 28 | protected CloudFormRepository cfnRepository; 29 | protected VpcRepository vpcRepository; 30 | protected MonitorStackEvents monitor; 31 | protected CloudRepository cloudRepository; 32 | protected ProjectAndEnv projectAndEnv = EnvironmentSetupForTests.getMainProjectAndEnv(); 33 | private Map zones = new HashMap<>(); 34 | 35 | protected StackNameAndId setUpdateExpectations(String stackName, String filename, 36 | List templateParameters, 37 | Collection parameters) 38 | throws CfnAssistException, InterruptedException, IOException { 39 | String stackId = "stackId"; 40 | Stack stack = Stack.builder().stackId(stackId).build(); 41 | StackNameAndId stackNameAndId = new StackNameAndId(stackName, stackId); 42 | 43 | String contents = EnvironmentSetupForTests.loadFile(filename); 44 | EasyMock.expect(vpcRepository.getCopyOfVpc(projectAndEnv)).andReturn(Vpc.builder().vpcId(VPC_ID).build()); 45 | EasyMock.expect(cfnRepository.validateStackTemplate(contents)).andReturn(templateParameters); 46 | 47 | EasyMock.expect(cfnRepository.updateStack(contents, parameters, monitor, stackName)).andReturn(stackNameAndId); 48 | EasyMock.expect(monitor.waitForUpdateFinished(stackNameAndId)).andReturn(StackStatus.UPDATE_COMPLETE); 49 | EasyMock.expect(cfnRepository.updateSuccess(stackNameAndId)).andReturn(stack); 50 | EasyMock.expect(cloudRepository.getZones()).andReturn(zones); 51 | return stackNameAndId; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/tw/com/commandline/actions/SharedAction.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline.actions; 2 | 3 | import org.apache.commons.cli.Option; 4 | import tw.com.commandline.CommandLineAction; 5 | import tw.com.commandline.CommandLineException; 6 | import tw.com.entity.ProjectAndEnv; 7 | 8 | public abstract class SharedAction implements CommandLineAction { 9 | 10 | protected Option option; 11 | 12 | @Override 13 | public Option getOption() { 14 | return option; 15 | } 16 | 17 | @Override 18 | public String getArgName() { 19 | return option.getArgName(); 20 | } 21 | 22 | protected void createOption(String optName, String description) { 23 | option = Option.builder(optName).argName(optName).desc(description).build(); 24 | } 25 | 26 | protected void createOptionWithArg(String name, String description) { 27 | option = Option.builder(name).argName(name).desc(description).hasArg().build(); 28 | } 29 | 30 | protected void createOptionWithArgs(String name, String description, int numberOfArgs) { 31 | option = Option.builder(name).argName(name).desc(description).hasArgs().numberOfArgs(numberOfArgs).build(); 32 | } 33 | 34 | protected void createOptionalWithOptionalArg(String name, String description) { 35 | option = Option.builder(name).argName(name).desc(description).hasArg().optionalArg(true).build(); 36 | } 37 | 38 | protected void guardForProjectAndEnv(ProjectAndEnv projectAndEnv) 39 | throws CommandLineException { 40 | guardForProject(projectAndEnv); 41 | guardForEnv(projectAndEnv); 42 | } 43 | 44 | private void guardForEnv(ProjectAndEnv projectAndEnv) 45 | throws CommandLineException { 46 | if (!projectAndEnv.hasEnv()) { 47 | throw new CommandLineException("Must provide env"); 48 | } 49 | } 50 | 51 | protected void guardForProject(ProjectAndEnv projectAndEnv) 52 | throws CommandLineException { 53 | if (!projectAndEnv.hasProject()) { 54 | throw new CommandLineException("Must provide project"); 55 | } 56 | } 57 | 58 | protected void guardForNoBuildNumber(ProjectAndEnv projectAndEnv) 59 | throws CommandLineException { 60 | if (projectAndEnv.hasBuildNumber()) { 61 | throw new CommandLineException( 62 | "Build number parameter is not valid with action: " 63 | + getArgName()); 64 | } 65 | } 66 | 67 | protected void guardForBuildNumber(ProjectAndEnv projectAndEnv) 68 | throws CommandLineException { 69 | if (!projectAndEnv.hasBuildNumber()) { 70 | throw new CommandLineException( 71 | "You must provide build number if you specify artitacts"); 72 | } 73 | } 74 | 75 | protected void guardForSNSNotSet(ProjectAndEnv projectAndEnv) throws CommandLineException { 76 | if (projectAndEnv.useSNS()) { 77 | throw new CommandLineException("Setting sns does not work with this action"); 78 | } 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/tw/com/commandline/Actions.java: -------------------------------------------------------------------------------- 1 | package tw.com.commandline; 2 | 3 | import org.apache.commons.cli.CommandLine; 4 | import org.apache.commons.cli.HelpFormatter; 5 | import org.apache.commons.cli.MissingArgumentException; 6 | import org.apache.commons.cli.Options; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import tw.com.commandline.actions.*; 10 | 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | 14 | public class Actions { 15 | private static final Logger logger = LoggerFactory.getLogger(Actions.class); 16 | 17 | private final List actions; 18 | 19 | public Actions() { 20 | actions = new LinkedList<>(); 21 | createActions(); 22 | } 23 | 24 | public void addActionsTo(Options cliOptions) { 25 | for(CommandLineAction action : actions) { 26 | cliOptions.addOption(action.getOption()); 27 | } 28 | } 29 | 30 | public CommandLineAction selectCorrectActionFromArgs(CommandLine cmd, HelpFormatter formatter, String executableName, Options commandLineOptions) throws MissingArgumentException { 31 | int count = 0; 32 | CommandLineAction matchingAction = null; 33 | StringBuilder names = new StringBuilder(); 34 | for(CommandLineAction action : actions) { 35 | names.append(action.getArgName()).append(" "); 36 | if (cmd.hasOption(action.getArgName())) { 37 | matchingAction = action; 38 | count++; 39 | } 40 | } 41 | 42 | if (count!=1) { 43 | String msg = "Please supply only one of " + names.toString(); 44 | logger.error(msg); 45 | formatter.printHelp(executableName, commandLineOptions); 46 | throw new MissingArgumentException(msg); 47 | } 48 | return matchingAction; 49 | } 50 | 51 | private void createActions() { 52 | actions.add(new FileAction()); 53 | actions.add(new DirAction()); 54 | actions.add(new ResetAction()); 55 | actions.add(new InitAction()); 56 | actions.add(new AddTagAction()); 57 | actions.add(new ElbAction()); 58 | actions.add(new UpdateTargetGroupAction()); 59 | actions.add(new DeleteAction()); 60 | actions.add(new DeleteByNameAction()); 61 | actions.add(new ListAction()); 62 | actions.add(new ListDriftAction()); 63 | actions.add(new TidyOldStacksAction()); 64 | actions.add(new CreateDiagramAction()); 65 | actions.add(new AllowCurrentIPAction()); 66 | actions.add(new BlockCurrentIPAction()); 67 | actions.add(new InstancesAction()); 68 | actions.add(new CreateKeyPairAction()); 69 | actions.add(new SSHCommandAction()); 70 | actions.add(new BackAction()); 71 | actions.add(new PurgeAction()); 72 | actions.add(new AllowHostAction()); 73 | actions.add(new BlockHostAction()); 74 | actions.add(new RemoveLogsAction()); 75 | actions.add(new TagLogAction()); 76 | actions.add(new FetchLogsAction()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/tw/com/entity/Tagging.java: -------------------------------------------------------------------------------- 1 | package tw.com.entity; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import software.amazon.awssdk.services.cloudformation.model.Tag; 6 | import tw.com.AwsFacade; 7 | 8 | import java.util.Collection; 9 | import java.util.Optional; 10 | 11 | public class Tagging { 12 | private static final Logger logger = LoggerFactory.getLogger(Tagging.class); 13 | 14 | public static final String COMMENT_TAG = "CFN_COMMENT"; 15 | 16 | private String commentTag = ""; 17 | private Optional indexTag = Optional.empty(); 18 | private Optional updateIndex = Optional.empty(); 19 | 20 | public void addTagsTo(Collection tagCollection) { 21 | if (!commentTag.isEmpty()) { 22 | addTag(tagCollection, COMMENT_TAG, commentTag); 23 | } 24 | if (indexTag.isPresent()) { 25 | addTag(tagCollection, AwsFacade.INDEX_TAG, indexTag.get().toString()); 26 | } 27 | } 28 | 29 | private void addTag(Collection tagCollection, String tag, String value) { 30 | logger.info(String.format("Adding %s: %s", tag, value)); 31 | tagCollection.add(createTag(tag, value)); 32 | } 33 | 34 | public void setCommentTag(String commentTag) { 35 | this.commentTag = commentTag; 36 | } 37 | 38 | public void setIndexTag(Integer indexTag) { 39 | this.indexTag = Optional.of(indexTag); 40 | } 41 | 42 | private Tag createTag(String key, String value) { 43 | Tag tag = Tag.builder().key(key).value(value).build(); 44 | return tag; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "Tagging{" + 50 | "commentTag='" + commentTag + '\'' + 51 | ", indexTag=" + indexTag + 52 | ", updateIndex=" + updateIndex + 53 | '}'; 54 | } 55 | 56 | @Override 57 | public boolean equals(Object o) { 58 | if (this == o) return true; 59 | if (o == null || getClass() != o.getClass()) return false; 60 | 61 | Tagging tagging = (Tagging) o; 62 | 63 | if (commentTag != null ? !commentTag.equals(tagging.commentTag) : tagging.commentTag != null) return false; 64 | if (indexTag != null ? !indexTag.equals(tagging.indexTag) : tagging.indexTag != null) return false; 65 | return !(updateIndex != null ? !updateIndex.equals(tagging.updateIndex) : tagging.updateIndex != null); 66 | 67 | } 68 | 69 | @Override 70 | public int hashCode() { 71 | int result = commentTag != null ? commentTag.hashCode() : 0; 72 | result = 31 * result + (indexTag != null ? indexTag.hashCode() : 0); 73 | result = 31 * result + (updateIndex != null ? updateIndex.hashCode() : 0); 74 | return result; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/tw/com/acceptance/TestKeyPairCreationAndSave.java: -------------------------------------------------------------------------------- 1 | package tw.com.acceptance; 2 | 3 | import software.amazon.awssdk.services.ec2.Ec2Client; 4 | import software.amazon.awssdk.services.ec2.model.*; 5 | import org.junit.jupiter.api.BeforeAll; 6 | import org.junit.jupiter.api.Test; 7 | import tw.com.CLIArgBuilder; 8 | import tw.com.EnvironmentSetupForTests; 9 | import tw.com.commandline.Main; 10 | 11 | import java.io.IOException; 12 | import java.nio.file.Files; 13 | import java.nio.file.LinkOption; 14 | import java.nio.file.Path; 15 | import java.nio.file.Paths; 16 | import java.nio.file.attribute.PosixFilePermission; 17 | import java.util.LinkedList; 18 | import java.util.List; 19 | import java.util.Set; 20 | 21 | import static org.junit.jupiter.api.Assertions.assertEquals; 22 | import static org.junit.jupiter.api.Assertions.assertTrue; 23 | 24 | public class TestKeyPairCreationAndSave { 25 | 26 | private static Ec2Client ec2Client; 27 | 28 | @BeforeAll 29 | public static void beforeAllTestsRun() { 30 | ec2Client = EnvironmentSetupForTests.createEC2Client(); 31 | } 32 | 33 | @Test 34 | public void shouldCreateKeyPairWithFilename() throws IOException { 35 | String keypairName = "CfnAssist_Test"; 36 | 37 | deleteKeyPair(keypairName); 38 | 39 | String filename = "testFilenameForPem.tmp"; 40 | Path path = Paths.get(filename); 41 | Files.deleteIfExists(path); 42 | 43 | String[] args = CLIArgBuilder.createKeyPair(filename); 44 | Main main = new Main(args); 45 | int commandResult = main.parse(); 46 | 47 | List keys = deleteKeyPair(keypairName); 48 | 49 | // now do the asserts 50 | assertEquals(0, commandResult); 51 | assertEquals(1, keys.size()); 52 | assertEquals(keypairName, keys.get(0).keyName()); 53 | 54 | assertTrue(Files.exists(path)); 55 | 56 | Set permissions = Files.getPosixFilePermissions(Paths.get(filename), LinkOption.NOFOLLOW_LINKS); 57 | EnvironmentSetupForTests.checkKeyPairFilePermissions(permissions); 58 | 59 | Files.deleteIfExists(path); 60 | } 61 | 62 | private List deleteKeyPair(String keypairName) { 63 | List keys; 64 | try { 65 | DescribeKeyPairsRequest query = DescribeKeyPairsRequest.builder().keyNames(keypairName).build(); 66 | DescribeKeyPairsResponse keysFound = ec2Client.describeKeyPairs(query); 67 | keys = keysFound.keyPairs(); 68 | } catch (Ec2Exception exception) { 69 | keys = new LinkedList<>(); 70 | } 71 | 72 | if (keys.size() > 0) { 73 | DeleteKeyPairRequest deleteRequest = DeleteKeyPairRequest.builder().keyName(keypairName).build(); 74 | ec2Client.deleteKeyPair(deleteRequest); 75 | } 76 | return keys; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/tw/com/pictures/dot/HasAttributes.java: -------------------------------------------------------------------------------- 1 | package tw.com.pictures.dot; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | import tw.com.exceptions.CfnAssistException; 7 | 8 | public class HasAttributes { 9 | 10 | private List attributes = new LinkedList(); 11 | 12 | public HasAttributes() { 13 | super(); 14 | } 15 | 16 | protected void writeAttributes(Recorder recorder, boolean isGraph) { 17 | if (attributes.isEmpty()) { 18 | return; 19 | } 20 | StringBuilder output = new StringBuilder(); 21 | output.append(" "); 22 | for (String attrib : attributes) { 23 | output.append(attrib).append(" "); 24 | if (isGraph) { 25 | output.append("\n"); 26 | } 27 | } 28 | if (isGraph) { 29 | recorder.write(output.toString()); 30 | } else { 31 | recorder.write(String.format(" [ %s ] ", output)); 32 | } 33 | } 34 | 35 | public void addLabel(String label) { 36 | attributes.add(String.format("label=\"%s\"", label)); 37 | } 38 | 39 | public void fontSize(int size) { 40 | attributes.add(String.format("fontsize = %s", size)); 41 | } 42 | 43 | protected void addNoDirection() { 44 | attributes.add("dir=none"); 45 | } 46 | 47 | 48 | protected void withColour(Colour col) throws CfnAssistException { 49 | switch(col) { 50 | case Red: 51 | attributes.add("color=red"); 52 | break; 53 | default: 54 | throw new CfnAssistException("Unknown colour: " + col.toString()); 55 | } 56 | } 57 | 58 | protected void addShape(Shape shape) throws CfnAssistException { 59 | switch (shape) { 60 | case Box: 61 | attributes.add("shape=box"); 62 | break; 63 | case Diamond: 64 | attributes.add("shape=diamond"); 65 | break; 66 | case Octogon: 67 | attributes.add("shape=octagon"); 68 | break; 69 | case Parallelogram: 70 | attributes.add("shape=parallelogram"); 71 | break; 72 | case Box3d: 73 | attributes.add("share=Box3d"); 74 | break; 75 | case Msquare: 76 | attributes.add("shape=Msquare"); 77 | break; 78 | case InvHouse: 79 | attributes.add("shape=invhouse"); 80 | break; 81 | default: 82 | throw new CfnAssistException("Unknown shape: " + shape.toString()); 83 | } 84 | } 85 | 86 | protected void addDottedLine() { 87 | attributes.add("style=dotted"); 88 | } 89 | 90 | protected void addDot() { 91 | attributes.add("arrowhead=dot"); 92 | } 93 | 94 | protected void addBox() { 95 | attributes.add("arrowhead=box"); 96 | } 97 | 98 | protected void addEndsAt(String elementId) { 99 | attributes.add(String.format("lhead=\"%s\"", elementId)); 100 | } 101 | 102 | protected void addBeginsAt(String elementId) { 103 | attributes.add(String.format("ltail=\"%s\"", elementId)); 104 | } 105 | 106 | protected void addCompound() { 107 | attributes.add("compound=true"); 108 | } 109 | 110 | public void setStyleInvisible() { 111 | attributes.add("style=invis"); 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /src/tw/com/providers/SNSNotificationSender.java: -------------------------------------------------------------------------------- 1 | package tw.com.providers; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import software.amazon.awssdk.services.sns.SnsClient; 7 | import software.amazon.awssdk.services.sns.model.*; 8 | import tw.com.entity.CFNAssistNotification; 9 | import tw.com.exceptions.CfnAssistException; 10 | 11 | import com.fasterxml.jackson.core.JsonProcessingException; 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | 14 | public class SNSNotificationSender implements NotificationSender { 15 | private static final Logger logger = LoggerFactory.getLogger(SNSNotificationSender.class); 16 | 17 | public static final String TOPIC_NAME = "CFN_ASSIST_NOTIFICATIONS"; 18 | 19 | private SnsClient snsClient; 20 | 21 | // stateful to limit number of AWS API calls we make 22 | private String topicANR =""; 23 | 24 | public SNSNotificationSender(SnsClient snsClient) { 25 | this.snsClient = snsClient; 26 | } 27 | 28 | public String getTopicARN() { 29 | try { 30 | if (topicANR.isEmpty()) { 31 | ListTopicsResponse topics = snsClient.listTopics(); 32 | for(Topic topic : topics.topics()) { 33 | String foundArn = topic.topicArn(); 34 | if (foundArn.contains(TOPIC_NAME)) { 35 | logger.info("Found notification topic for SNS, ARN is: " + foundArn); 36 | topicANR = foundArn; 37 | break; 38 | } 39 | } 40 | } 41 | if (topicANR.isEmpty()) { 42 | logger.info("Did not find notification topic for SNS, to receive updates create topic: " + TOPIC_NAME); 43 | } 44 | return topicANR; 45 | } 46 | catch (AuthorizationErrorException authException) { 47 | logger.error("Did not send SNS notification. You may need to update permissions for user via IAM. Exception was " + authException); 48 | return ""; 49 | } 50 | } 51 | 52 | public String sendNotification(CFNAssistNotification notification) throws CfnAssistException { 53 | String topicArn = getTopicARN(); 54 | if (topicArn.isEmpty()) { 55 | logger.info("Will not send notification as sns topic not found, topic is: " + TOPIC_NAME); 56 | return ""; 57 | } 58 | 59 | ObjectMapper objectMapper = new ObjectMapper(); 60 | try { 61 | logger.info("Send notification: " + notification); 62 | String json = objectMapper.writeValueAsString(notification); 63 | 64 | PublishRequest request = PublishRequest.builder().topicArn(topicArn).message(json).build(); 65 | PublishResponse result = snsClient.publish(request); 66 | logger.info(String.format("Send message on topic %s with id %s", TOPIC_NAME, result.messageId())); 67 | return result.messageId(); 68 | } 69 | catch (JsonProcessingException jsonException) { 70 | throw new CfnAssistException("Unable to create notification JSON " + jsonException.toString()); 71 | } 72 | catch (AuthorizationErrorException authException) { 73 | logger.error("Did not send SNS notification. You may need to update permissions for user via IAM. Exception was " 74 | + authException); 75 | return ""; 76 | } 77 | } 78 | 79 | } 80 | --------------------------------------------------------------------------------