├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── box.json
├── composer.json
├── doc
├── AwsSdk
│ └── index.rst
├── Blueprints
│ ├── Images
│ │ └── overview.png
│ └── index.rst
├── Contributing
│ └── index.rst
├── FilePaths
│ └── index.rst
├── Functions
│ └── index.rst
├── GettingStarted
│ ├── Images
│ │ ├── kickstart_demo_deploy.png
│ │ ├── kickstart_demo_ec2list.png
│ │ └── kickstart_demo_stacklist.png
│ └── index.rst
├── Images
│ ├── StackFormation.svg
│ └── stackformation_200px.png
├── Makefile
├── Misc
│ └── index.rst
├── ShellCommands
│ └── index.rst
├── Stack
│ └── index.rst
├── StackPolicies
│ └── index.rst
├── StackReferences
│ └── index.rst
├── Templates
│ └── index.rst
├── UserData
│ └── index.rst
├── conf.py
├── index.rst
└── make.bat
├── docker
├── README.md
├── _base.sh
├── build.sh
├── golang
│ └── Dockerfile
├── php-fpm.conf
├── push.sh
└── slim
│ └── Dockerfile
├── misc
└── convert_fn_join_to_inline_ref.php
├── phpcs.xml
├── phpunit.xml.dist
├── src
├── AwsInspector
│ ├── Command
│ │ ├── Agent
│ │ │ └── AddIdentityCommand.php
│ │ ├── Clean
│ │ │ └── CleanSnapshotsCommand.php
│ │ ├── CloudwatchLogs
│ │ │ ├── AddLambdaTriggerCommand.php
│ │ │ ├── DeleteLambdaTriggerCommand.php
│ │ │ ├── DeleteLogGroupCommand.php
│ │ │ ├── SetRetentionCommand.php
│ │ │ ├── ShowLogGroupsCommand.php
│ │ │ └── TailCommand.php
│ │ ├── Ec2
│ │ │ ├── AbstractCommand.php
│ │ │ ├── ListCommand.php
│ │ │ ├── SshCommand.php
│ │ │ └── TerminateCommand.php
│ │ └── Profile
│ │ │ ├── DisableCommand.php
│ │ │ ├── EnableCommand.php
│ │ │ └── ListCommand.php
│ ├── CommandRegistry.php
│ ├── Helper
│ │ └── Curl.php
│ ├── Model
│ │ ├── AbstractResource.php
│ │ ├── AutoScaling
│ │ │ ├── AutoScalingGroup.php
│ │ │ ├── LaunchConfiguration.php
│ │ │ └── Repository.php
│ │ ├── CloudWatchLogs
│ │ │ ├── LogGroup.php
│ │ │ ├── LogStream.php
│ │ │ └── Repository.php
│ │ ├── Collection.php
│ │ ├── Ebs
│ │ │ ├── Repository.php
│ │ │ └── Volume.php
│ │ ├── Ec2
│ │ │ ├── CollectionRegistry.php
│ │ │ ├── Factory.php
│ │ │ ├── Instance.php
│ │ │ └── Repository.php
│ │ ├── ElastiCache
│ │ │ ├── CacheCluster.php
│ │ │ └── Repository.php
│ │ ├── Elb
│ │ │ ├── Elb.php
│ │ │ └── Repository.php
│ │ ├── Iam
│ │ │ ├── Repository.php
│ │ │ └── User.php
│ │ ├── Rds
│ │ │ ├── Database.php
│ │ │ └── Repository.php
│ │ ├── Route53
│ │ │ └── Repository.php
│ │ └── SecurityGroup
│ │ │ ├── Repository.php
│ │ │ └── SecurityGroup.php
│ ├── Registry.php
│ ├── SdkFactory.php
│ └── Ssh
│ │ ├── Agent.php
│ │ ├── Command.php
│ │ ├── Connection.php
│ │ ├── Identity.php
│ │ ├── LocalConnection.php
│ │ └── PrivateKey.php
├── StackFormation
│ ├── Blueprint.php
│ ├── BlueprintAction.php
│ ├── BlueprintFactory.php
│ ├── Command
│ │ ├── AbstractCommand.php
│ │ ├── Blueprint
│ │ │ ├── AbstractBlueprintCommand.php
│ │ │ ├── DeployCommand.php
│ │ │ ├── DeployPolicyCommand.php
│ │ │ ├── Show
│ │ │ │ ├── ChangesetCommand.php
│ │ │ │ ├── DependenciesCommand.php
│ │ │ │ ├── ParametersCommand.php
│ │ │ │ ├── StacknameCommand.php
│ │ │ │ └── TemplateCommand.php
│ │ │ └── ValidateCommand.php
│ │ ├── SetupCommand.php
│ │ └── Stack
│ │ │ ├── AbstractStackCommand.php
│ │ │ ├── CompareAllCommand.php
│ │ │ ├── DeleteCommand.php
│ │ │ ├── DiffCommand.php
│ │ │ ├── ListCommand.php
│ │ │ ├── ObserveCommand.php
│ │ │ ├── Show
│ │ │ ├── AbstractShowCommand.php
│ │ │ ├── DependantsCommand.php
│ │ │ ├── OutputsCommand.php
│ │ │ ├── ParametersCommand.php
│ │ │ └── ResourcesCommand.php
│ │ │ ├── TimelineCommand.php
│ │ │ └── TreeCommand.php
│ ├── CommandRegistry.php
│ ├── Config.php
│ ├── ConfigTreeBuilder.php
│ ├── DependencyTracker.php
│ ├── Diff.php
│ ├── Exception
│ │ ├── BlueprintNotFoundException.php
│ │ ├── BlueprintReferenceNotFoundException.php
│ │ ├── CleanCloudFormationException.php
│ │ ├── InvalidStackNameException.php
│ │ ├── MissingEnvVarException.php
│ │ ├── NoBlueprintsFoundException.php
│ │ ├── OperationAbortedException.php
│ │ ├── OutputNotFoundException.php
│ │ ├── ParameterNotFoundException.php
│ │ ├── ResourceNotFoundException.php
│ │ ├── StackCannotBeUpdatedException.php
│ │ ├── StackNoUpdatesToBePerformedException.php
│ │ ├── StackNotFoundException.php
│ │ ├── TagNotFoundException.php
│ │ └── ValueResolverException.php
│ ├── Helper
│ │ ├── Cache.php
│ │ ├── ChangeSetTable.php
│ │ ├── Decorator.php
│ │ ├── Div.php
│ │ ├── Exception.php
│ │ ├── Finder.php
│ │ ├── Pipeline.php
│ │ ├── StackEventsTable.php
│ │ ├── StaticCache.php
│ │ └── Validator.php
│ ├── Observer.php
│ ├── Poller.php
│ ├── PrefixedTemplate.php
│ ├── Preprocessor.php
│ ├── Profile
│ │ ├── Manager.php
│ │ └── YamlCredentialProvider.php
│ ├── Stack.php
│ ├── StackFactory.php
│ ├── Template.php
│ ├── TemplateDecodeException.php
│ ├── TemplateInvalidException.php
│ ├── TemplateMerger.php
│ ├── Terminator.php
│ └── ValueResolver
│ │ ├── Stage
│ │ ├── AbstractValueResolverStage.php
│ │ ├── Clean.php
│ │ ├── ConditionalValue.php
│ │ ├── EnvironmentVariable.php
│ │ ├── EnvironmentVariableWithFallback.php
│ │ ├── Md5.php
│ │ ├── ProfileSwitcher.php
│ │ ├── StackOutput.php
│ │ ├── StackParameter.php
│ │ ├── StackResource.php
│ │ ├── Tstamp.php
│ │ └── Variable.php
│ │ └── ValueResolver.php
├── dotenv.php
└── stackformation.php
└── tests
├── AwsInspector
├── Helper
│ └── CurlTest.php
├── Model
│ ├── AbstractResourceTest.php
│ ├── AutoScaling
│ │ ├── AutoScalingGroupTest.php
│ │ └── RepositoryTest.php
│ ├── CollectionTest.php
│ └── Ec2
│ │ └── InstanceTest.php
├── RegistryTest.php
└── Ssh
│ ├── CommandTest.php
│ └── ConnectionTest.php
├── StackFormation
├── BlueprintActionTest.php
├── BlueprintGetParameterTest.php
├── BlueprintTest.php
├── CacheTest.php
├── ConfigTest.php
├── HelperTest.php
├── PollerTest.php
├── PreprocessorTest.php
├── ProfileManagerTest.php
├── StackTest.php
├── ValidateTagsTest.php
├── ValueResolver
│ └── Stage
│ │ └── ConditionalValueTest.php
└── ValueResolverTest.php
├── fixtures
├── Config
│ ├── blueprint.1.yml
│ ├── blueprint.conditional_value.yml
│ ├── blueprint.conditional_vars.yml
│ ├── blueprint.duplicate_stackname.yml
│ ├── blueprint.duplicateglobalvar.yml
│ ├── blueprint.invalid_attribute.yml
│ ├── blueprint.reference.yml
│ ├── blueprint.select_profile.yml
│ ├── blueprint.switch_profile.yml
│ ├── blueprint.template_missing.yml
│ ├── blueprint.templatefile_missing.yml
│ ├── dummy.template
│ └── dummy_policy.json
├── Preprocessor
│ ├── injectFileContentFnFileContentFromParentDirectory
│ │ ├── blueprint
│ │ │ ├── expected.template
│ │ │ └── input.template
│ │ └── userdata.sh
│ ├── injectFileContentFnFileContentFromSameDirectory
│ │ └── blueprint
│ │ │ ├── expected.template
│ │ │ ├── input.template
│ │ │ └── userdata.sh
│ └── injectFileContentFnFileContentFromSubDirectory
│ │ └── blueprint
│ │ ├── expected.template
│ │ ├── input.template
│ │ └── userdata
│ │ └── userdata.sh
├── ProfileManager
│ ├── fixture_basic
│ │ └── profiles.yml
│ ├── fixture_before_scripts
│ │ └── profiles.yml
│ ├── fixture_encrpyted
│ │ └── profiles.yml.encrypted
│ ├── fixture_encrpyted_mix
│ │ ├── profiles.personal.yml
│ │ └── profiles.yml.encrypted
│ └── fixture_merge_profiles
│ │ ├── profiles.personal.yml
│ │ └── profiles.yml
├── RunBeforeScript
│ └── foo.txt
├── Stack
│ ├── test-stack1.describeStackResources.json
│ └── test-stack1.json
├── foo.pem
└── resolve_md5.txt
└── phpunit.bootstrap.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | /composer.lock
3 | /vendor/
4 | *.phar
5 | doc/_build
6 | docker/golang/php-fpm.conf
7 | docker/slim/php-fpm.conf
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: php
3 | php:
4 | - 5.5
5 | - 5.6
6 | - 7.0
7 | matrix:
8 | allow_failures:
9 | - php: 7.0
10 |
11 | before_script:
12 | - composer install --no-progress --no-interaction
13 | script:
14 | - phpunit --debug --coverage-clover build/logs/clover.xml --log-junit build/logs/junit.xml
15 | after_script:
16 | - vendor/bin/test-reporter
17 |
18 | notifications:
19 | email:
20 | recipients:
21 | - travisci@fabrizio-branca.de
22 | on_success: always
23 | on_failure: always
24 |
25 | addons:
26 | code_climate:
27 | repo_token:
28 | secure: Qhy+BuyLPC7D87NFvKJC47CLUyJmRzIcxQSY1VIZoKWerxD42THMLlwU4DwIbIdHjTMDVV6F+fYvX10XddXksjMSt7iCrlCU8ICF9VmT2QbGFgn910FBydSQCB5yrqgEASsZnAzxnO34Hcea8545gMhCNfFnpCcWjnMjNSKU8JgwYuowb+9aneLibWuMsOBixAS9ziBmxrw34Uozn6nLE2y3i9DVxdl4J3D9qslzc+qp76U5zGx14Jl4aZ0IrR8oQZZnSmiJqSGtk2WJK/R8lOFp/v/+a76Jhu/OJA0Xfx7hGSwvk8luF0K/uuIdHfOO91T0DvK7lBuMEPL1eytN+mIzuplDk58+DMORMTKsriqHLI6l+gblwMg3k/rvI29mwjhvYkdunP5WwckdZYEGMcAa3J475C9vtkjntc+8amGQ3+FeeU9V+mff852raYWs4KYCfvuMF5Gmy7aixpqZAHxn3tXenfv8HIFP67P2qbE5w+ErtbVJMl7HVY2t4pWVS+GMitbBvjqnunbIvAK9fThqlDkAD3PZ5nQR3Ad6LvLlMR2KIEnNdjaA9+XxsLpsqNfG2WbrKFmQBPOfXDFguiB2wEqtpB1YLtSZIk+aGmLHOqOC9f6ZklrtuxijIA0G46I9wJI9tASjRTdXtxzuMeiqJJEKzNRFfjxMIZaWuX0=
29 |
30 | before_deploy:
31 | - curl -LSs https://box-project.github.io/box2/installer.php | php
32 | - env
33 | - php box.phar --no-interaction build
34 | deploy:
35 | provider: releases
36 | api_key:
37 | secure: PRTuXlZ9Y83g22H+nukMCpK2HmchmpNNJIS7aT3ywxXOV8qbtmGITEBKe0RgkFo4odANGy4P762RpmOxWY+e081q/4gVztr+fjTmLjzvRSGvD5dPs8j298uedJ25zdo57Raq5E0WC3FEkar91SIk37S73IvDhtzKFMJAkKPSmZ5vYFMRu0CUIQ2wN/T/gMnMytjQbcgVn1vGai3xilN/YNukuv4iuj1aBIEZXnJNKUkz6gpo3nNqHb8PHoYkv7SfxMwEda7joYNFu4p6eU5mlwHoY1gv1iUNKHLRYvFOQAXthbbp2NxImHehtxOy1UFHiz+onL1nTciBgQbjysQgKpZz0ByEB7OKGu/59x1P2F2g9cxUSpqjx4i4M9bI7ALweE7BnyfZj4vHW57kFOmsGlAwaEsJIdJwZS8PXHDy575x3HGY2HdXVBySBRHBbyjUdlKnhp42W8R9+wA56wBK836DBlFFJgxRBdViuZsu08mcR4VnsPMU8oykhfhI8QNCnSV8A5/Hcz2NGfXJi5UV9MMMXfiPLYfe4K2nABBiWu7P6gxQwjPbz4N3CipzFD0cOakGanWnjDIsshTqA/Tn+pl7BGbqfkXciZe5hUuEbhGGk/Jj8i5IsU7ZipArK1sRCMwSSpSEqKQ9EvTxlgceiNw0LBVWrsBxGOvUN88GV3I=
38 | file: stackformation.phar
39 | on:
40 | repo: AOEpeople/StackFormation
41 | branch: master
42 | tags: true
43 | php: "5.6"
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StackFormation
2 |
3 | **Lightweight AWS CloudFormation Stack, Template and Parameter Manager and Preprocessor**
4 |
5 |
6 | [](https://travis-ci.org/AOEpeople/StackFormation)
7 | [](https://codeclimate.com/github/AOEpeople/StackFormation)
8 | [](https://codeclimate.com/github/AOEpeople/StackFormation/coverage)
9 | [](http://stackformation.readthedocs.io)
10 |
11 | If you want to use StackFormation, please have a look at the [StackFormation documentation](http://stackformation.readthedocs.org)
12 |
13 | ### License
14 | [Open Software License v. 3.0 (OSL-3.0)](https://github.com/AOEpeople/StackFormation/blob/master/LICENSE.md)
15 |
16 |
--------------------------------------------------------------------------------
/box.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "src/stackformation.php",
3 | "output": "stackformation.phar",
4 | "chmod": "0775",
5 | "compression": "GZ",
6 | "extract": false,
7 | "files": [ "vendor/autoload.php" ],
8 | "directories": [ "src" ],
9 | "finder": [{ "name": "*.php", "in": "vendor" }],
10 | "git-version": "package_version",
11 | "stub": true
12 | }
13 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aoepeople/stackformation",
3 | "description": "Lightweight AWS CloudFormation Stack Manager",
4 | "license": "OSL-3.0",
5 | "authors": [{
6 | "name": "Fabrizio Branca",
7 | "email": "firstname.lastname@aoe.com",
8 | "homepage": "https://github.com/fbrnc",
9 | "role": "Author"
10 | }, {
11 | "name": "Lee Saferite",
12 | "email": "lee.saferite@aoe.com",
13 | "homepage": "https://github.com/LeeSaferite",
14 | "role": "Contributor"
15 | }, {
16 | "name": "Daniel Niedergesäß",
17 | "email": "dn@smart-devs.rocks",
18 | "homepage": "https://github.com/smart-devs",
19 | "role": "Contributor"
20 | }],
21 | "require": {
22 | "symfony/console": "^3.0.0",
23 | "symfony/yaml": "^3.0.0",
24 | "symfony/config": "^3.0.0",
25 | "symfony/finder": "^3.1",
26 | "aws/aws-sdk-php": "^3.52",
27 | "tedivm/jshrink": "^1.1",
28 | "vlucas/phpdotenv": "~2.1",
29 | "monolog/monolog": "~1",
30 | "php": ">=5.5.0"
31 | },
32 | "suggest": {
33 | "aoepeople/vault": "Allows encrypting/decrypting ssh keys and profiles on the fly"
34 | },
35 | "replace": {
36 | "aoepeople/awsinspector": "*"
37 | },
38 | "require-dev": {
39 | "squizlabs/php_codesniffer": "^2.4",
40 | "codeclimate/php-test-reporter": "0.3.2",
41 | "phpunit/phpunit": "~4.0"
42 | },
43 | "autoload": {
44 | "psr-4": {
45 | "StackFormation\\": "src/StackFormation/",
46 | "AwsInspector\\": "src/AwsInspector/",
47 | "StackFormation\\Tests\\": "tests/StackFormation/",
48 | "AwsInspector\\Tests\\": "tests/AwsInspector/"
49 | },
50 | "files": ["src/dotenv.php"]
51 | },
52 | "bin": [ "src/stackformation.php" ]
53 | }
54 |
--------------------------------------------------------------------------------
/doc/AwsSdk/index.rst:
--------------------------------------------------------------------------------
1 | *********
2 | AWS Sdk
3 | *********
4 |
5 | StackFormation uses the AWS SDK for PHP. You should configure your keys in env vars:
6 |
7 | .. code-block:: shell
8 |
9 | $ export AWS_ACCESS_KEY_ID=INSERT_YOUR_ACCESS_KEY
10 | $ export AWS_SECRET_ACCESS_KEY=INSERT_YOUR_PRIVATE_KEY
11 | $ export AWS_DEFAULT_REGION=eu-west-1
--------------------------------------------------------------------------------
/doc/Blueprints/Images/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AOEpeople/StackFormation/5332dbbe54653e50d610cbaf75fb865c68aa2f1e/doc/Blueprints/Images/overview.png
--------------------------------------------------------------------------------
/doc/Contributing/index.rst:
--------------------------------------------------------------------------------
1 | ************
2 | Contributing
3 | ************
4 |
5 | Your contributions are always welcome!
6 | Please feel free to fork this repository and submit pull request as many you want! If you have any questions please feel free to contact us.
7 |
8 | Contributors
9 | ============
10 |
11 | - `Fabrizio Branca `__
12 | - `Julian Kleinhans `__
13 | - `Lee Saferite `__
14 | - `Daniel Niedergesäß `__
--------------------------------------------------------------------------------
/doc/FilePaths/index.rst:
--------------------------------------------------------------------------------
1 | **********
2 | File paths
3 | **********
4 |
5 | Relative file paths
6 | ===================
7 |
8 | Please note that all files paths in the ``template`` section of a ``blueprints.yml`` are relative to the current ``blueprints.yml`` file and all files included via ``Fn::FileContent``/ ``Fn:FileContentTrimLines`` or ``Fn:FileContentMinify`` are relative to the CloudFormation template file.
9 |
10 | Example:
11 |
12 | .. code-block:: text
13 |
14 | blueprints/
15 | stack1/
16 | userdata/
17 | provisioning.sh
18 | blueprints.yml
19 | my.template
20 |
21 | blueprints.yml:
22 |
23 | .. code-block:: yaml
24 |
25 | blueprints:
26 | - stackname: test
27 | template: my.template
28 |
29 | my.template
30 |
31 | .. code-block:: json
32 |
33 | { [...]
34 | "Ec2Instance": {
35 | "Type": "AWS::AutoScaling::LaunchConfiguration",
36 | "Properties": {
37 | "UserData": {"Fn::Base64": {"Fn::FileContent": "userdata/provisioning.sh"}}
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/doc/Functions/index.rst:
--------------------------------------------------------------------------------
1 | *********
2 | Functions
3 | *********
4 |
5 | Fn::FileContent
6 | ===============
7 |
8 | Before uploading CloudFormation template to the API there's some pre-processing going on: I've introduced a new function "FileContent" that accepts a path to a file. This file will be read, converted into JSON (using ``Fn::Join``). The path is relative to the path of the current CloudFormation template file.
9 |
10 | Usage Example:
11 |
12 | .. code-block:: json
13 |
14 | [...]
15 | "UserData": {"Fn::Base64": {"Fn::FileContent":"../scripts/setup.sh"}},
16 | [...]
17 |
18 | Fn::FileContentTrimLines
19 | ========================
20 |
21 | These function are similar to ``Fn::FileContent`` but additional it trim whitespace. This comes in handy when deploying Lambda function where the content can't be larger than 2048kb if you want to directly embed the source code via CloudFormation (instead of deploying a zip file).
22 |
23 | Fn::FileContentMinify
24 | ========================
25 |
26 | These function are similar to ``Fn::FileContent`` but additional it minify the code. This comes in handy when deploying Lambda function where the content can't be larger than 2048kb if you want to directly embed the source code via CloudFormation (instead of deploying a zip file).
27 |
28 | Fn::FileContentUnpretty
29 | =======================
30 |
31 | This function is the same as ``Fn::FileContent`` expect it will return the resulting JSON without formatting it, which will reduce the file size significantly due to the missing whitespace in the JSON structure (not inside the file content!) This is useful if you're seeing the "...at 'templateBody' failed to satisfy constraint: Member must have length less than or equal to 51200" error message.
32 |
33 | Fn::Split
34 | =========
35 |
36 | Sometimes you have a dynamic number of array items. ``Fn::Split`` allows you to configure them as a single string and transforms them into an array:
37 |
38 | .. code-block:: json
39 |
40 | "Aliases": { "Fn::Split": [",", "www.example.com,cdn.example.com"]}
41 |
42 | results in:
43 |
44 | .. code-block:: json
45 |
46 | "Aliases": ["www.example.com", "cdn.example.com"]
47 |
--------------------------------------------------------------------------------
/doc/GettingStarted/Images/kickstart_demo_deploy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AOEpeople/StackFormation/5332dbbe54653e50d610cbaf75fb865c68aa2f1e/doc/GettingStarted/Images/kickstart_demo_deploy.png
--------------------------------------------------------------------------------
/doc/GettingStarted/Images/kickstart_demo_ec2list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AOEpeople/StackFormation/5332dbbe54653e50d610cbaf75fb865c68aa2f1e/doc/GettingStarted/Images/kickstart_demo_ec2list.png
--------------------------------------------------------------------------------
/doc/GettingStarted/Images/kickstart_demo_stacklist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AOEpeople/StackFormation/5332dbbe54653e50d610cbaf75fb865c68aa2f1e/doc/GettingStarted/Images/kickstart_demo_stacklist.png
--------------------------------------------------------------------------------
/doc/Images/stackformation_200px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AOEpeople/StackFormation/5332dbbe54653e50d610cbaf75fb865c68aa2f1e/doc/Images/stackformation_200px.png
--------------------------------------------------------------------------------
/doc/Misc/index.rst:
--------------------------------------------------------------------------------
1 | ****
2 | Misc
3 | ****
4 |
5 | Use the ``jq`` tool to create a simple list of all parameters (almost) ready to paste it in the blueprints.yml
6 |
7 | .. code-block:: shell
8 |
9 | $ cat my.template | jq '.Parameters | keys' | sed 's/",/: \'\'/g' | sed 's/"//g'
10 |
--------------------------------------------------------------------------------
/doc/Stack/index.rst:
--------------------------------------------------------------------------------
1 | *****
2 | Stack
3 | *****
4 |
5 | Stackname filter
6 | ================
7 |
8 | You can configure a regular expression in the ``STACKFORMATION_NAME_FILTER`` environment variable (e.g. via ``.env.default``) which will filter all your stack lists to the stacks matching this pattern. This is useful if you have a naming convention in place and you don't want to see other team's stacks in your list.
9 |
10 | Example:
11 |
12 | .. code-block:: text
13 |
14 | STACKFORMATION_NAME_FILTER=/^myproject-(a|b)-/
15 |
--------------------------------------------------------------------------------
/doc/StackPolicies/index.rst:
--------------------------------------------------------------------------------
1 | **************
2 | Stack policies
3 | **************
4 |
5 | Using stack policies
6 | ====================
7 |
8 | To prevent stack resources from being unintentionally updated or deleted during a stack update you can use `stack policies `__. Stack policies apply only during stack updates and should be used only as a fail-safe mechanism to prevent accidental updates to certain stack resources.
9 |
10 | It's suggested to create a stack\_policies directory below the corresponding stack directory:
11 |
12 | .. code-block:: yaml
13 |
14 | blueprints/
15 | stack1/
16 | stack_policies/
17 | blueprints.yml
18 | ...
19 | stack2/
20 | stack_policies/
21 | blueprints.yml
22 | ...
23 | ...
24 |
25 | You have to tell StackFormation where it could find the stack policy.
26 |
27 | Example:
28 |
29 | .. code-block:: yaml
30 |
31 | blueprints:
32 | - stackname: 'my-stack'
33 | template: 'templates/my-stack.template'
34 | stackPolicy: 'stack_policies/my-stack.json'
35 |
--------------------------------------------------------------------------------
/doc/StackReferences/index.rst:
--------------------------------------------------------------------------------
1 | ****************
2 | Stack references
3 | ****************
4 |
5 | Referencing outputs/resources/parameters from other stacks
6 |
7 | TODO
8 |
--------------------------------------------------------------------------------
/doc/UserData/index.rst:
--------------------------------------------------------------------------------
1 | *********
2 | User data
3 | *********
4 |
5 | Inject user data
6 | ================
7 |
8 | TODO
9 |
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 | ######################################
2 | StackFormation |version| Documentation
3 | ######################################
4 |
5 | .. image:: Images/stackformation_200px.png
6 | :align: right
7 |
8 | |Build Status| |Code Climate| |Test Coverage|
9 |
10 | **Lightweight AWS CloudFormation Stack, Template and Parameter Manager and Preprocessor**
11 |
12 | Deploying CloudFormation stacks to AWS can be done using the AWS Console, AWS Cli or any SDK. While this is perfectly ok it can be a challenge to keep track of what template is used for what stack and manage the input parameters.
13 | This is where "StackFormation" comes in.
14 |
15 | StackFormation (note the wordplay: CloudFormation / Stacks) will read `blueprint.yml` files that contains information about stacks, the templates they use and their input parameters. It also allows you to query values for input parameters from other stack's resources or outputs.
16 | In addition to that StackFormation makes it easy to embed scripts into UserData.
17 |
18 | .. image:: Images/StackFormation.svg
19 |
20 | If you have any questions please feel free to contact us:
21 |
22 | - `Fabrizio Branca `__
23 | - `Julian Kleinhans `__
24 |
25 | This version of the documentation covering StackFormation |release| has been rendered at: |today|
26 |
27 | .. toctree::
28 | :maxdepth: 3
29 | :caption: Getting started Documentation
30 |
31 | GettingStarted/index
32 |
33 | .. toctree::
34 | :maxdepth: 3
35 | :caption: StackFormation Documentation
36 |
37 | Blueprints/index
38 | Templates/index
39 | StackReferences/index
40 | UserData/index
41 | Stack/index
42 | StackPolicies/index
43 | Functions/index
44 | FilePaths/index
45 | ShellCommands/index
46 | AwsSdk/index
47 | Misc/index
48 |
49 | .. toctree::
50 | :maxdepth: 3
51 | :caption: User Documentation
52 |
53 | Contributing/index
54 |
55 | **License**
56 |
57 | `Open Software License v. 3.0 (OSL-3.0) `__
58 |
59 | .. |Build Status| image:: https://travis-ci.org/AOEpeople/StackFormation.svg?branch=master
60 | :target: https://travis-ci.org/AOEpeople/StackFormation
61 | .. |Code Climate| image:: https://codeclimate.com/github/AOEpeople/StackFormation/badges/gpa.svg
62 | :target: https://codeclimate.com/github/AOEpeople/StackFormation
63 | .. |Test Coverage| image:: https://codeclimate.com/github/AOEpeople/StackFormation/badges/coverage.svg
64 | :target: https://codeclimate.com/github/AOEpeople/StackFormation/coverage
65 |
--------------------------------------------------------------------------------
/docker/README.md:
--------------------------------------------------------------------------------
1 |
2 | # AWS StackFormation with Docker
3 | **Lightweight AWS CloudFormation Stack, Template and Parameter Manager and Preprocessor**
4 | - **Github:** https://github.com/AOEpeople/StackFormation
5 | - **Documentation:** http://stackformation.readthedocs.io/en/latest/
6 | - **DockerHub:** https://hub.docker.com/r/kj187/stackformation/
7 |
8 |
9 | # Available tags
10 |
11 | - `4.3.6`, `latest` (includes PHP 7.0)
12 | - `4.3.6-golang`, `latest-golang` (includes PHP 7.0, Golang 1.6.2 linux/amd64)
13 |
14 | ### Golang version
15 | The Golang version includes the slim version as basis and on top it supports golang.
16 | Golang could be used for AWS Lambda for instance
17 | (see https://github.com/kj187/aws_stackformation_templates/tree/master/blueprints/lambda/golang)
18 |
19 | ## Usage
20 |
21 | ```
22 | $ docker run --rm -it -v $(pwd):/app -w /app kj187/stackformation:latest
23 | ```
24 |
25 | Or if you want to use lambda with golang
26 | ```
27 | $ docker run --rm -it -v $(pwd):/app -w /app kj187/stackformation:latest-golang
28 | ```
29 |
30 | ## Build a new image version
31 |
32 | ```
33 | $ git clone git@github.com:AOEpeople/StackFormation.git
34 | $ ./StackFormation/docker/build.sh STACKFORMATION_GITHUB_RELEASE_VERSION
35 | ```
36 |
37 | STACKFORMATION_GITHUB_RELEASE_VERSION
38 | Github release version which includes an automated generated phar file
39 |
40 | Push to docker hub
41 |
42 | ```
43 | $ ./StackFormation/docker/push.sh STACKFORMATION_GITHUB_RELEASE_VERSION
44 | ```
45 |
--------------------------------------------------------------------------------
/docker/_base.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | function echoerr {
4 | echo "============================================" 1>&2;
5 | echo "ERROR: $@" 1>&2;
6 | echo "============================================" 1>&2;
7 | }
8 | function error_exit { echoerr "$1"; exit 1; }
9 |
10 | if [ -z "$1" ] ; then error_exit "Version number not set!"; fi
11 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
12 |
13 | clean() {
14 | rm -rf $DIR/slim/php-fpm.conf
15 | rm -rf $DIR/golang/php-fpm.conf
16 | }
17 |
18 | cp php-fpm.conf ./slim
19 | cp php-fpm.conf ./golang
20 |
--------------------------------------------------------------------------------
/docker/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | . $PWD/_base.sh
4 |
5 | build() {
6 | docker_repo=$1
7 | sf_release=$2
8 | img_tag=$3
9 | img_latest_tag=$4
10 |
11 | echo ">>>> Build $docker_repo:$img_tag and $docker_repo:$img_latest_tag"
12 | echo ">>>>>> docker build -t $docker_repo:$img_tag -t $docker_repo:$img_latest_tag --build-arg STACKFORMATION_VERSION=$sf_release ."
13 | docker build -t $docker_repo:$img_tag -t $docker_repo:$img_latest_tag --build-arg STACKFORMATION_VERSION=$sf_release . || error_exit "Cant build image"
14 | }
15 |
16 | echo '>> Start building process'
17 |
18 | cd $DIR/slim
19 | echo ">>> Building Slim version"
20 | build kj187/stackformation $1 $1 latest
21 |
22 | cd $DIR/golang
23 | echo ">>> Building Golang version"
24 | build kj187/stackformation $1 $1-golang latest-golang
25 |
26 | echo '>> Building finished'
27 |
28 | clean
29 |
--------------------------------------------------------------------------------
/docker/golang/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:16.04
2 |
3 | MAINTAINER Julian Kleinhans
4 |
5 | ARG STACKFORMATION_VERSION
6 |
7 | RUN locale-gen en_US.UTF-8
8 | ENV LANG en_US.UTF-8
9 | ENV LANGUAGE en_US:en
10 | ENV LC_ALL en_US.UTF-8
11 |
12 | ## Common
13 | RUN apt-get update \
14 | && apt-get install -y sudo wget curl zip unzip git software-properties-common
15 |
16 |
17 | ## AWS cli
18 | RUN apt-get install -y python3
19 | RUN curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip" \
20 | && unzip awscli-bundle.zip \
21 | && /usr/bin/python3 awscli-bundle/install -i /usr/local/aws -b /usr/bin/aws
22 |
23 |
24 | ## PHP
25 | RUN add-apt-repository -y ppa:ondrej/php \
26 | && apt-get update \
27 | && apt-get install -y php7.0-fpm php7.0-cli php7.0-mcrypt php7.0-gd php7.0-mysql \
28 | php7.0-pgsql php7.0-imap php-memcached php7.0-mbstring php7.0-xml php7.0-curl \
29 | php7.0-sqlite3 php7.0-xdebug \
30 | && php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
31 | && mkdir /run/php
32 | COPY php-fpm.conf /etc/php/7.0/fpm/php-fpm.conf
33 |
34 |
35 | ## Stackformation
36 | RUN wget -q https://github.com/AOEpeople/StackFormation/releases/download/${STACKFORMATION_VERSION}/stackformation.phar \
37 | && mv stackformation.phar /usr/bin/stackformation \
38 | && chmod ugo+x /usr/bin/stackformation
39 |
40 |
41 | ## Golang
42 | RUN apt-get update \
43 | && apt-get install -y git software-properties-common \
44 | && add-apt-repository -y ppa:ubuntu-lxc/lxd-stable \
45 | && apt-get update \
46 | && apt-get install -y golang
47 |
48 |
49 | ## Cleanup
50 | RUN apt-get remove -y --purge software-properties-common \
51 | && apt-get clean
52 | RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
53 |
54 | ENTRYPOINT ["stackformation"]
55 |
--------------------------------------------------------------------------------
/docker/push.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | . $PWD/_base.sh
4 |
5 | push() {
6 | echo ">>>> Push $1:$2 to dockerhub"
7 | docker push $1:$2
8 | }
9 |
10 | echo '>> Start docker hub push process'
11 | docker login
12 |
13 | cd $DIR/slim
14 | push kj187/stackformation $1
15 | push kj187/stackformation latest
16 |
17 | cd $DIR/golang
18 | push kj187/stackformation $1-golang
19 | push kj187/stackformation latest-golang
20 |
21 | echo '>> Pushing to docker hub finished'
22 |
--------------------------------------------------------------------------------
/docker/slim/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:16.04
2 |
3 | MAINTAINER Julian Kleinhans
4 |
5 | ARG STACKFORMATION_VERSION
6 |
7 | RUN locale-gen en_US.UTF-8
8 |
9 | ENV LANG en_US.UTF-8
10 | ENV LANGUAGE en_US:en
11 | ENV LC_ALL en_US.UTF-8
12 |
13 | RUN apt-get update \
14 | && apt-get install -y wget curl zip unzip git software-properties-common \
15 | && add-apt-repository -y ppa:ondrej/php \
16 | && apt-get update \
17 | && apt-get install -y php7.0-fpm php7.0-cli php7.0-mcrypt php7.0-gd php7.0-mysql \
18 | php7.0-pgsql php7.0-imap php-memcached php7.0-mbstring php7.0-xml php7.0-curl \
19 | php7.0-sqlite3 php7.0-xdebug \
20 | && php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
21 | && mkdir /run/php \
22 | && apt-get remove -y --purge software-properties-common \
23 | && apt-get -y autoremove \
24 | && apt-get clean
25 |
26 | RUN wget -q https://github.com/AOEpeople/StackFormation/releases/download/${STACKFORMATION_VERSION}/stackformation.phar \
27 | && mv stackformation.phar /usr/bin/stackformation \
28 | && chmod ugo+x /usr/bin/stackformation
29 |
30 | RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
31 |
32 | COPY php-fpm.conf /etc/php/7.0/fpm/php-fpm.conf
33 |
34 | ENTRYPOINT ["stackformation"]
35 |
--------------------------------------------------------------------------------
/misc/convert_fn_join_to_inline_ref.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
10 |
11 | ./tests
12 |
13 |
14 |
15 |
16 |
17 | src
18 |
19 | src/stackformation.php
20 | src/dotenv.php
21 | src/StackFormation/CommandRegistry.php
22 | src/AwsInspector/CommandRegistry.php
23 | src/StackFormation/Command
24 | src/AwsInspector/Command
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/AwsInspector/Command/Agent/AddIdentityCommand.php:
--------------------------------------------------------------------------------
1 | setName('agent:add-identity')
17 | ->setDescription('Add identity')
18 | ->addArgument(
19 | 'key',
20 | InputArgument::REQUIRED,
21 | 'private key'
22 | );
23 | }
24 |
25 | protected function execute(InputInterface $input, OutputInterface $output)
26 | {
27 | $key = $input->getArgument('key');
28 | $identity = new \AwsInspector\Ssh\Identity(\AwsInspector\Ssh\PrivateKey::get($key), true);
29 | $identity->loadIdentity();
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Command/Clean/CleanSnapshotsCommand.php:
--------------------------------------------------------------------------------
1 | setName('clean:orphaned-snapshots-from-createimage')
18 | ->setDescription('Delete snapshots created via CreateImage where the AMI does not exist anymore');
19 | }
20 |
21 | protected function execute(InputInterface $input, OutputInterface $output)
22 | {
23 |
24 | $iam = new \AwsInspector\Model\Iam\Repository();
25 | $accountId = $iam->findCurrentUser()->getAccountId();
26 |
27 | $output->writeln('Owner: ' . $accountId);
28 |
29 | $ec2Client = SdkFactory::getClient('EC2'); /* @var $ec2Client \Aws\Ec2\Ec2Client */
30 |
31 | $res = $ec2Client->describeImages([
32 | 'Owners' => [$accountId]
33 | ]);
34 |
35 | $activeImageIds = array_flip($res->search('Images[].ImageId'));
36 |
37 | $res = $ec2Client->describeSnapshots([
38 | // 'Filters' => [['Name' => 'owner-id','Values' => [$accountId]]]
39 | 'OwnerIds' => [$accountId]
40 | ]);
41 |
42 | $orphanSnapshots = [];
43 |
44 | foreach ($res->get('Snapshots') as $snapshotData) {
45 | $description = $snapshotData["Description"];
46 | // Created by CreateImage(i-ee0c7564) for ami-9945d0ea from vol-e4b6ff16
47 | // if (preg_match('/^Created by CreateImage\(i-.*\) for \(ami-.*\) from \(vol-.*\)$/', $description)) {
48 | if (preg_match('/^Created by CreateImage\(i-.*\) for (ami-.+) from vol-.+/', $description, $matches)) {
49 | $amiId = $matches[1];
50 | if (isset($activeImageIds[$amiId])) {
51 | $output->writeln('Found active AMI: ' . $amiId);
52 | } else {
53 | $output->writeln('AMI not found: ' . $amiId);
54 | $orphanSnapshots[] = $snapshotData['SnapshotId'];
55 | }
56 | }
57 | }
58 |
59 | foreach ($orphanSnapshots as $snapshotId) {
60 | $output->writeln('Deleting ' . $snapshotId);
61 | $result = $ec2Client->deleteSnapshot([
62 | 'SnapshotId' => $snapshotId
63 | ]);
64 | }
65 |
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/AwsInspector/Command/CloudwatchLogs/DeleteLambdaTriggerCommand.php:
--------------------------------------------------------------------------------
1 | setName('cloudwatchlogs:delete-lambda-trigger')
16 | ->setDescription('Deletes a subscription')
17 | ->addArgument(
18 | 'group',
19 | InputArgument::REQUIRED,
20 | 'Log group name pattern'
21 | )->addArgument(
22 | 'lambdaArn',
23 | InputArgument::REQUIRED,
24 | 'The ARN of the Lambda destination to delete permissions'
25 | )->addArgument(
26 | 'filterName',
27 | InputArgument::REQUIRED,
28 | 'A name for the subscription filter'
29 | );
30 | }
31 |
32 | protected function execute(InputInterface $input, OutputInterface $output)
33 | {
34 | // TODO: refactor this to use \AwsInspector\Model\CloudWatchLogs\Repository
35 |
36 | $groupPattern = $input->getArgument('group');
37 | $lambdaArn = $input->getArgument('lambdaArn');
38 | $filterName = $input->getArgument('filterName');
39 |
40 | /* @var $cloudwatchLogsClient \Aws\CloudWatchLogs\CloudWatchLogsClient */
41 | $cloudwatchLogsClient = \AwsInspector\SdkFactory::getClient('cloudwatchlogs');
42 | $lambdaClient = \AwsInspector\SdkFactory::getClient('lambda');
43 |
44 | $nextToken = null;
45 |
46 | do {
47 | $params = ['limit' => 50];
48 | if ($nextToken) {
49 | $params['nextToken'] = $nextToken;
50 | }
51 |
52 | $result = $cloudwatchLogsClient->describeLogGroups($params);
53 | foreach ($result->get('logGroups') as $logGroup) {
54 | $name = $logGroup['logGroupName'];
55 | if (preg_match('/'.$groupPattern.'/', $name)) {
56 | try {
57 | $subscriptionFilters = $cloudwatchLogsClient->describeSubscriptionFilters(['logGroupName' => $logGroup['logGroupName']]);
58 | if (empty($subscriptionFilters->get('subscriptionFilters'))) {
59 | continue;
60 | }
61 |
62 | $cloudwatchLogsClient->deleteSubscriptionFilter([
63 | 'filterName' => $filterName,
64 | 'logGroupName' => $logGroup['logGroupName']
65 | ]);
66 |
67 | $lambdaClient->removePermission([
68 | 'FunctionName' => $lambdaArn,
69 | 'StatementId' => (string) md5($logGroup['logGroupName']),
70 | ]);
71 |
72 | $output->writeln('Delete lambda trigger for ' . $logGroup['logGroupName']);
73 | } catch (\Aws\CloudWatchLogs\Exception\CloudWatchLogsException $e) {
74 | if ($e->getAwsErrorCode() != 'ResourceNotFoundException') {
75 | throw $e;
76 | }
77 | }
78 | }
79 | }
80 | $nextToken = $result->get("nextToken");
81 | } while ($nextToken);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/AwsInspector/Command/CloudwatchLogs/DeleteLogGroupCommand.php:
--------------------------------------------------------------------------------
1 | setName('cloudwatchlogs:delete-log-group')
19 | ->setDescription('Delete log group')
20 | ->addArgument(
21 | 'group',
22 | InputArgument::REQUIRED,
23 | 'Log group name pattern'
24 | );
25 | }
26 |
27 | protected function execute(InputInterface $input, OutputInterface $output)
28 | {
29 | $groupPattern = $input->getArgument('group');
30 | $repository = new Repository();
31 | foreach ($repository->findLogGroups($groupPattern) as $logGroup) { /* @var $logGroup LogGroup */
32 | $output->writeln('Deleting ' . $logGroup->getLogGroupName());
33 | $repository->deleteLogGroup($logGroup->getLogGroupName());
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/AwsInspector/Command/CloudwatchLogs/SetRetentionCommand.php:
--------------------------------------------------------------------------------
1 | setName('cloudwatchlogs:set-retention')
17 | ->setDescription('Set retention')
18 | ->addArgument(
19 | 'days',
20 | InputArgument::REQUIRED,
21 | 'retention in days'
22 | )
23 | ->addArgument(
24 | 'group',
25 | InputArgument::REQUIRED,
26 | 'Log group name pattern'
27 | );
28 | }
29 |
30 | protected function execute(InputInterface $input, OutputInterface $output)
31 | {
32 | // TODO: refactor this to use \AwsInspector\Model\CloudWatchLogs\Repository
33 |
34 | $days = $input->getArgument('days');
35 | $days = intval($days);
36 | if ($days == 0) {
37 | throw new \Exception('Invalid retention period');
38 | }
39 | $groupPattern = $input->getArgument('group');
40 |
41 | $cloudwatchLogsClient = \AwsInspector\SdkFactory::getClient('cloudwatchlogs'); /* @var $cloudwatchLogsClient \Aws\CloudWatchLogs\CloudWatchLogsClient */
42 |
43 | $totalBytes = 0;
44 | $nextToken = null;
45 | do {
46 | $params = ['limit' => 50];
47 | if ($nextToken) {
48 | $params['nextToken'] = $nextToken;
49 | }
50 | $result = $cloudwatchLogsClient->describeLogGroups($params);
51 | foreach ($result->get('logGroups') as $logGroup) {
52 | $name = $logGroup['logGroupName'];
53 | if (preg_match('/'.$groupPattern.'/', $name)) {
54 | $retention = isset($logGroup['retentionInDays']) ? $logGroup['retentionInDays'] : 'never';
55 | if ($retention != $days) {
56 | $output->writeln('Updating ' . $logGroup['logGroupName']);
57 | $cloudwatchLogsClient->putRetentionPolicy([
58 | 'logGroupName' => $name,
59 | 'retentionInDays' => $days
60 | ]);
61 | } else {
62 | $output->writeln('Skipping ' . $logGroup['logGroupName']);
63 | }
64 | } else {
65 | $output->writeln('Does not match pattern: ' . $logGroup['logGroupName']);
66 | }
67 | }
68 | $nextToken = $result->get("nextToken");
69 | } while ($nextToken);
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Command/CloudwatchLogs/ShowLogGroupsCommand.php:
--------------------------------------------------------------------------------
1 | setName('cloudwatchlogs:show-log-groups')
18 | ->setDescription('Show Log Groups')
19 | ->addArgument(
20 | 'group',
21 | InputArgument::OPTIONAL,
22 | 'Log group name pattern'
23 | );
24 | }
25 |
26 | protected function execute(InputInterface $input, OutputInterface $output)
27 | {
28 | $groupPattern = $input->getArgument('group');
29 | if (empty($groupPattern)) {
30 | $groupPattern = '.*';
31 | }
32 |
33 | $cloudwatchLogsClient = \AwsInspector\SdkFactory::getClient('cloudwatchlogs'); /* @var $cloudwatchLogsClient \Aws\CloudWatchLogs\CloudWatchLogsClient */
34 |
35 |
36 | $table = new Table($output);
37 | $table->setHeaders(['Name', 'Retention [days]', 'Size [MB]']);
38 |
39 | $totalBytes = 0;
40 | $nextToken = null;
41 | do {
42 | $params = ['limit' => 50];
43 | if ($nextToken) {
44 | $params['nextToken'] = $nextToken;
45 | }
46 | $result = $cloudwatchLogsClient->describeLogGroups($params);
47 | foreach ($result->get('logGroups') as $logGroup) {
48 | $name = $logGroup['logGroupName'];
49 | if (preg_match('/'.$groupPattern.'/', $name)) {
50 | $table->addRow([
51 | $logGroup['logGroupName'],
52 | isset($logGroup['retentionInDays']) ? $logGroup['retentionInDays'] : 'Never',
53 | round($logGroup['storedBytes'] / (1024*1024))
54 | ]);
55 | $totalBytes += $logGroup['storedBytes'];
56 | }
57 | }
58 | $nextToken = $result->get("nextToken");
59 | } while ($nextToken);
60 |
61 |
62 | $table->render();
63 |
64 | $output->writeln('Total size: ' . $this->formatBytes($totalBytes));
65 | }
66 |
67 | protected function formatBytes($bytes, $precision = 2) {
68 | $units = array('B', 'KB', 'MB', 'GB', 'TB');
69 | $bytes = max($bytes, 0);
70 | $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
71 | $pow = min($pow, count($units) - 1);
72 | $bytes /= (1 << (10 * $pow));
73 | return round($bytes, $precision) . ' ' . $units[$pow];
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/AwsInspector/Command/Ec2/AbstractCommand.php:
--------------------------------------------------------------------------------
1 | setName('ec2:list')
19 | ->setDescription('List all instances')
20 | ->addOption(
21 | 'tag',
22 | 't',
23 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
24 | 'tag (Example: "Environment:Deploy")'
25 | )
26 | ->addOption(
27 | 'column',
28 | 'c',
29 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
30 | 'Extra column (tag)'
31 | )
32 | ->addOption(
33 | 'sort',
34 | 's',
35 | InputOption::VALUE_OPTIONAL,
36 | 'Sort by column'
37 | );
38 | }
39 |
40 | protected function execute(InputInterface $input, OutputInterface $output)
41 | {
42 | $tags = $input->getOption('tag');
43 | $tags = $this->convertTags($tags);
44 |
45 | $mapping = [
46 | 'InstanceId' => 'InstanceId',
47 | 'ImageId' => 'ImageId',
48 | 'State' => 'State.Name',
49 | 'SubnetId' => 'SubnetId',
50 | 'AZ' => 'Placement.AvailabilityZone',
51 | 'PublicIp' => 'PublicIpAddress',
52 | 'PrivateIp' => 'PrivateIpAddress',
53 | 'KeyName' => 'KeyName'
54 | ];
55 |
56 | // dynamically add current tags
57 | foreach (array_keys($tags) as $tagName) {
58 | $mapping[$tagName] = 'Tags[?Key==`'.$tagName.'`].Value | [0]';
59 | }
60 |
61 | foreach ($input->getOption('column') as $tagName) {
62 | $mapping[$tagName] = 'Tags[?Key==`'.$tagName.'`].Value | [0]';
63 | }
64 |
65 | $repository = new Repository();
66 | $instanceCollection = $repository->findEc2InstancesByTags($tags);
67 |
68 | $rows = [];
69 | foreach ($instanceCollection as $instance) { /* @var $instance Instance */
70 | $rows[] = $instance->extractData($mapping);
71 | }
72 |
73 | $this->sortColumn = $input->getOption('sort');
74 | if ($this->sortColumn !== null) {
75 | usort($rows, [$this, 'sortByColumn']);
76 | }
77 |
78 | if (count($rows)) {
79 | $table = new \Symfony\Component\Console\Helper\Table($output);
80 | $table
81 | ->setHeaders(array_keys(end($rows)))
82 | ->setRows($rows);
83 | $table->render();
84 | } else {
85 | $output->writeln('No matching instances found.');
86 | }
87 | }
88 |
89 | private function sortByColumn($a, $b)
90 | {
91 | if (!isset($a[$this->sortColumn])) {
92 | return 0;
93 | }
94 | return strcmp($a[$this->sortColumn], $b[$this->sortColumn]);
95 | }
96 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Command/Profile/DisableCommand.php:
--------------------------------------------------------------------------------
1 | setName('profile:disable')
16 | ->setDescription('Disable current profile');
17 | }
18 |
19 | protected function execute(InputInterface $input, OutputInterface $output)
20 | {
21 | if (!is_file('.env')) {
22 | $output->writeln('No .env file found');
23 | return;
24 | }
25 | if (!unlink('.env')) {
26 | throw new \Exception('Error deleting .env file');
27 | }
28 | $output->writeln('Deleted file .env');
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Command/Profile/EnableCommand.php:
--------------------------------------------------------------------------------
1 | setName('profile:enable')
19 | ->setDescription('Enable profile')
20 | ->addArgument(
21 | 'profile',
22 | InputArgument::REQUIRED,
23 | 'Profile'
24 | );
25 | }
26 |
27 | protected function interact(InputInterface $input, OutputInterface $output)
28 | {
29 | $profile = $input->getArgument('profile');
30 | if (empty($profile)) {
31 |
32 | $profileManager = new Manager();
33 |
34 | $helper = $this->getHelper('question');
35 | $question = new ChoiceQuestion(
36 | 'Please select the profile you want to use',
37 | $profileManager->listAllProfiles()
38 | );
39 |
40 | $question->setErrorMessage('Profile %s is invalid.');
41 |
42 | $profile = $helper->ask($input, $output, $question);
43 | $output->writeln('Selected Profile: '.$profile);
44 |
45 | $input->setArgument('profile', $profile);
46 | }
47 | }
48 |
49 | protected function execute(InputInterface $input, OutputInterface $output)
50 | {
51 | $profileManager = new Manager();
52 | $file = $profileManager->writeProfileToDotEnv($input->getArgument('profile'));
53 | $output->writeln('File written: ' . $file);
54 | }
55 |
56 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Command/Profile/ListCommand.php:
--------------------------------------------------------------------------------
1 | setName('profile:list')
17 | ->setDescription('List all AWS profiles found in configuration');
18 | }
19 |
20 | protected function execute(InputInterface $input, OutputInterface $output)
21 | {
22 | $profileManager = new Manager();
23 |
24 | $rows=[];
25 | foreach($profileManager->listAllProfiles() as $profileName) {
26 | $rows[] = [$profileName];
27 | }
28 |
29 | $table = new \Symfony\Component\Console\Helper\Table($output);
30 | $table
31 | ->setHeaders(array('Profile Name'))
32 | ->setRows($rows)
33 | ;
34 | $table->render();
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/src/AwsInspector/CommandRegistry.php:
--------------------------------------------------------------------------------
1 | apiData = $apiData;
24 | $this->profileManager = is_null($profileManager) ? new \StackFormation\Profile\Manager() : $profileManager;
25 | }
26 |
27 | /**
28 | * @return array
29 | */
30 | public function getApiData()
31 | {
32 | return $this->apiData;
33 | }
34 |
35 | /**
36 | * @param array $mapping
37 | * @return array
38 | */
39 | public function extractData(array $mapping)
40 | {
41 | $result = [];
42 | foreach ($mapping as $fieldName => $expression) {
43 | $result[$fieldName] = \JmesPath\Env::search($expression, $this->apiData);
44 | }
45 | return $result;
46 | }
47 |
48 | /**
49 | * @param string $method
50 | * @param $args
51 | * @return mixed|null
52 | * @throws \Exception
53 | */
54 | public function __call($method, $args)
55 | {
56 | if (substr($method, 0, 3) != 'get') {
57 | $class = get_class($this);
58 | throw new \Exception("Invalid method '$method' (class: $class)");
59 | }
60 |
61 | $field = substr($method, 3);
62 | if (isset($this->apiData[$field])) {
63 | return $this->apiData[$field];
64 | }
65 |
66 | return null;
67 | }
68 |
69 | /**
70 | * @return array
71 | * @throws \Exception
72 | */
73 | public function getAssocTags()
74 | {
75 | //if (!isset($this->apiData['Tags']) && !method_exists($this, 'getTags')) {
76 | // throw new \Exception('Tags are not supported');
77 | //}
78 | return $this->convertToAssocArray($this->getTags() ?: []);
79 | }
80 |
81 | /**
82 | * @param string $key
83 | * @return sring|null
84 | */
85 | public function getTag($key)
86 | {
87 | $tags = $this->getAssocTags();
88 | return isset($tags[$key]) ? $tags[$key] : null;
89 | }
90 |
91 | /**
92 | * @param array $filter
93 | * @return bool
94 | * @throws \Exception
95 | */
96 | public function matchesTags(array $filter)
97 | {
98 | $tags = $this->getAssocTags();
99 | $patched = array_replace_recursive($tags, $filter);
100 | return $tags == $patched;
101 | }
102 |
103 | /**
104 | * @param array $tags
105 | * @return array
106 | */
107 | protected function convertToAssocArray(array $tags)
108 | {
109 | $assocTags = [];
110 | foreach ($tags as $data) {
111 | $assocTags[$data['Key']] = $data['Value'];
112 | }
113 | return $assocTags;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/AutoScaling/LaunchConfiguration.php:
--------------------------------------------------------------------------------
1 | profileManager = is_null($profileManager) ? new \StackFormation\Profile\Manager() : $profileManager;
19 | $this->asgClient = $this->profileManager->getClient('AutoScaling', $profile);
20 | }
21 |
22 | /**
23 | * @return \AwsInspector\Model\Collection
24 | */
25 | public function findAutoScalingGroups()
26 | {
27 | $result = $this->asgClient->describeAutoScalingGroups();
28 |
29 | $rows = $result->search('AutoScalingGroups[]');
30 |
31 | $collection = new \AwsInspector\Model\Collection();
32 | foreach ($rows as $row) {
33 | $collection->attach(new AutoScalingGroup($row));
34 | }
35 |
36 | return $collection;
37 | }
38 |
39 | /**
40 | * @param array $tags
41 | * @return \AwsInspector\Model\Collection
42 | */
43 | public function findAutoScalingGroupsByTags(array $tags = array())
44 | {
45 | $collection = new \AwsInspector\Model\Collection();
46 | foreach ($this->findAutoScalingGroups() as $autoScalingGroup) { /* @var $autoScalingGroup AutoScalingGroup */
47 | if ($autoScalingGroup->matchesTags($tags)) {
48 | $collection->attach($autoScalingGroup);
49 | }
50 | }
51 | return $collection;
52 | }
53 |
54 | /**
55 | * @param string $regex
56 | * @return \AwsInspector\Model\Collection
57 | */
58 | public function findByAutoScalingGroupName($regex)
59 | {
60 | $collection = new \AwsInspector\Model\Collection();
61 | foreach ($this->findAutoScalingGroups() as $autoScalingGroup) { /* @var $autoScalingGroup AutoScalingGroup */
62 | if (preg_match($regex, $autoScalingGroup->getAutoScalingGroupName())) {
63 | $collection->attach($autoScalingGroup);
64 | }
65 | }
66 |
67 | return $collection;
68 | }
69 |
70 | /**
71 | * @return \AwsInspector\Model\Collection
72 | */
73 | public function findLaunchConfigurations()
74 | {
75 | $result = $this->asgClient->describeLaunchConfigurations();
76 |
77 | $rows = $result->search('LaunchConfigurations[]');
78 |
79 | $collection = new \AwsInspector\Model\Collection();
80 | foreach ($rows as $row) {
81 | $collection->attach(new LaunchConfiguration($row));
82 | }
83 |
84 | return $collection;
85 | }
86 |
87 | /**
88 | * @return array
89 | */
90 | public function findLaunchConfigurationsGroupedByImageId()
91 | {
92 | $imageIds = [];
93 | foreach ($this->findLaunchConfigurations() as $launchConfiguration) { /* @var $launchConfiguration LaunchConfiguration */
94 | $imageId = $launchConfiguration->getImageId();
95 | if (!isset($imageIds[$imageId])) {
96 | $imageIds[$imageId] = [];
97 | }
98 | $imageIds[$imageId][] = $launchConfiguration;
99 | }
100 |
101 | return $imageIds;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/CloudWatchLogs/LogGroup.php:
--------------------------------------------------------------------------------
1 | $value) {
20 | $normalizedApiData[ucfirst($key)] = $value;
21 | }
22 | parent::__construct($normalizedApiData, $profileManager);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/CloudWatchLogs/LogStream.php:
--------------------------------------------------------------------------------
1 | $value) {
23 | $normalizedApiData[ucfirst($key)] = $value;
24 | }
25 | parent::__construct($normalizedApiData, $profileManager);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/CloudWatchLogs/Repository.php:
--------------------------------------------------------------------------------
1 | profileManager = $profileManager ?: new \StackFormation\Profile\Manager();
21 | $this->cloudWatchLogsClient = $this->profileManager->getClient('CloudWatchLogs', $profile);
22 | }
23 |
24 | /**
25 | * @param string $logGroupNameFilter
26 | * @return \AwsInspector\Model\Collection
27 | */
28 | public function findLogGroups($logGroupNameFilter=null)
29 | {
30 | $collection = new \AwsInspector\Model\Collection();
31 | $nextToken = null;
32 | do {
33 | $params = ['limit' => 50];
34 | if ($nextToken) {
35 | $params['nextToken'] = $nextToken;
36 | }
37 | $result = $this->cloudWatchLogsClient->describeLogGroups($params);
38 | foreach ($result->get('logGroups') as $row) {
39 | if (!$logGroupNameFilter || Finder::matchWildcard($logGroupNameFilter, $row['logGroupName'])) {
40 | $collection->attach(new LogGroup($row));
41 | }
42 | }
43 | $nextToken = $result->get("nextToken");
44 | } while ($nextToken);
45 | return $collection;
46 | }
47 |
48 | /**
49 | * @param $logGroupName
50 | * @param string $logStreamNameFilter
51 | * @return \AwsInspector\Model\Collection
52 | */
53 | public function findLogStreams($logGroupName, $logStreamNameFilter=null)
54 | {
55 | if (empty($logGroupName)) {
56 | throw new \InvalidArgumentException('LogGroupName cannot be empty');
57 | }
58 | $result = $this->cloudWatchLogsClient->describeLogStreams([
59 | 'logGroupName' => $logGroupName,
60 | 'orderBy' => 'LastEventTime',
61 | 'descending' => true
62 | ]);
63 | $rows = $result->search('logStreams[]');
64 | $collection = new \AwsInspector\Model\Collection();
65 | foreach ($rows as $row) {
66 | if (!$logStreamNameFilter || Finder::matchWildcard($logStreamNameFilter, $row['logStreamName'])) {
67 | $collection->attach(new LogStream($row));
68 | }
69 | }
70 | return $collection;
71 | }
72 |
73 | public function findLogEvents($logGroupName, $logStreamName, &$nextForwardToken)
74 | {
75 | $params = [
76 | 'limit' => 50,
77 | 'logGroupName' => $logGroupName,
78 | 'logStreamName' => $logStreamName
79 | ];
80 | if ($nextForwardToken) {
81 | $params['nextToken'] = $nextForwardToken;
82 | }
83 | $res = $this->cloudWatchLogsClient->getLogEvents($params);
84 | $nextForwardToken = $res->get('nextForwardToken');
85 | return $res->search('events[].message');
86 | }
87 |
88 | public function deleteLogGroup($logGroupName)
89 | {
90 | $this->cloudWatchLogsClient->deleteLogGroup(['logGroupName' => $logGroupName]);
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Collection.php:
--------------------------------------------------------------------------------
1 | rewind();
12 | return $this->current();
13 | }
14 |
15 | public function flatten($method) {
16 | $flattenedObjects = [];
17 | foreach ($this as $item) {
18 | $flattenedObjects[] = $item->$method();
19 | }
20 | return $flattenedObjects;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Ebs/Repository.php:
--------------------------------------------------------------------------------
1 | findEbsVolumes([[
13 | 'Name' => 'attachment.instance-id',
14 | 'Values' => [$instanceId]
15 | ]]);
16 | }
17 |
18 | /**
19 | * @param array $filters
20 | * @return \AwsInspector\Model\Collection
21 | * @throws \Exception
22 | */
23 | public function findEbsVolumes(array $filters=[]) {
24 | $ec2Client = \AwsInspector\SdkFactory::getClient('ec2'); /* @var $ec2Client \Aws\Ec2\Ec2Client */
25 | $result = $ec2Client->describeVolumes(['Filters' => $filters]);
26 | $rows = $result->search('Volumes[]');
27 |
28 | $collection = new \AwsInspector\Model\Collection();
29 | foreach ($rows as $row) {
30 | $collection->attach(new Volume($row));
31 | }
32 | return $collection;
33 | }
34 |
35 | /**
36 | * @param array $tags
37 | * @return \AwsInspector\Model\Collection
38 | */
39 | public function findEbsVolumesByTags(array $tags=array()) {
40 | foreach ($tags as $tagName => $tagValue) {
41 | $filters[] = ['Name' => 'tag:'.$tagName, "Values" => [$tagValue]];
42 | }
43 | return $this->findEbsVolumes($filters);
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Ebs/Volume.php:
--------------------------------------------------------------------------------
1 | findEc2InstancesByTags($tags);
18 | }
19 | return self::$registry[$hash];
20 | }
21 |
22 |
23 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Ec2/Factory.php:
--------------------------------------------------------------------------------
1 | resourceName)) {
40 |
41 | // TODO: this should be changed!
42 | $region = getenv('HURRICANE_TEST_REGION');
43 | if (empty($region)) {
44 | throw new \Exception('Region missing');
45 | }
46 |
47 | // get account id from current user
48 | $iam = new \AwsInspector\Model\Iam\Repository();
49 | $accountId = $iam->findCurrentUser()->getAccountId();
50 |
51 | $parts = [];
52 | $parts['prefix'] = 'arn:aws:elasticache';
53 | $parts['region'] = $region;
54 | $parts['AccountId'] = $accountId;
55 | $parts['resourcetype'] = 'cluster';
56 | $parts['name'] = $this->getCacheClusterId();
57 | $this->resourceName = implode(':', $parts);
58 | }
59 | return $this->resourceName;
60 | }
61 |
62 | public function getTags()
63 | {
64 | if (is_null($this->tags)) {
65 | $elastiCacheClient = \AwsInspector\SdkFactory::getClient('ElastiCache');
66 | /* @var $elastiCacheClient \Aws\ElastiCache\ElastiCacheClient */
67 | $result = $elastiCacheClient->listTagsForResource(['ResourceName' => $this->getResourceName()]);
68 | $this->tags = $result->get('TagList');
69 | }
70 | return $this->tags;
71 | }
72 | }
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/ElastiCache/Repository.php:
--------------------------------------------------------------------------------
1 | elastiCacheClient = \AwsInspector\SdkFactory::getClient('ElastiCache');
18 | }
19 |
20 | public function findCacheClusters()
21 | {
22 | $result = $this->elastiCacheClient->describeCacheClusters(['ShowCacheNodeInfo' => true]);
23 | $rows = $result->search('CacheClusters[]');
24 |
25 | $collection = new \AwsInspector\Model\Collection();
26 | foreach ($rows as $row) {
27 | $collection->attach(new CacheCluster($row));
28 | }
29 | return $collection;
30 | }
31 |
32 | /**
33 | * @param array $tags
34 | * @return \AwsInspector\Model\Collection
35 | */
36 | public function findCacheClustersByTags(array $tags = array())
37 | {
38 | $cacheClusters = $this->findCacheClusters();
39 | $matchingCacheClusters = new Collection();
40 | foreach ($cacheClusters as $cacheCluster) { /* @var $cacheCluster CacheCluster */
41 | if ($cacheCluster->matchesTags($tags)) {
42 | $matchingCacheClusters->attach($cacheCluster);
43 | }
44 | }
45 | return $matchingCacheClusters;
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Elb/Elb.php:
--------------------------------------------------------------------------------
1 | tags)) {
31 | $elbClient = \AwsInspector\SdkFactory::getClient('ElasticLoadBalancing');
32 | /* @var $elbClient \Aws\ElasticLoadBalancing\ElasticLoadBalancingClient */
33 | $result = $elbClient->describeTags(['LoadBalancerNames' => [$this->getLoadBalancerName()]]);
34 | $this->tags = $result->search('TagDescriptions[0].Tags');
35 | }
36 | return $this->tags;
37 | }
38 |
39 | public function getInstanceStates()
40 | {
41 | $elbClient = \AwsInspector\SdkFactory::getClient('ElasticLoadBalancing');
42 | /* @var $elbClient \Aws\ElasticLoadBalancing\ElasticLoadBalancingClient */
43 | $res = $elbClient->describeInstanceHealth(['LoadBalancerName' => $this->getLoadBalancerName()]);
44 | $instances = [];
45 | foreach ($res->search('InstanceStates[]') as $instanceState) {
46 | $instances[$instanceState['InstanceId']] = $instanceState;
47 | }
48 | return $instances;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Elb/Repository.php:
--------------------------------------------------------------------------------
1 | elbClient = \AwsInspector\SdkFactory::getClient('ElasticLoadBalancing');
18 | }
19 |
20 | public function findElbByName($name)
21 | {
22 | $result = $this->elbClient->describeLoadBalancers([ 'LoadBalancerNames' => [$name] ]);
23 | return new Elb($result->search('LoadBalancerDescriptions[0]'));
24 | }
25 |
26 | public function findElbs()
27 | {
28 | $result = $this->elbClient->describeLoadBalancers();
29 | $rows = $result->search('LoadBalancerDescriptions[]');
30 |
31 | $collection = new \AwsInspector\Model\Collection();
32 | foreach ($rows as $row) {
33 | $collection->attach(new Elb($row));
34 | }
35 | return $collection;
36 | }
37 |
38 | /**
39 | * @param array $tags
40 | * @return \AwsInspector\Model\Collection
41 | */
42 | public function findElbsByTags(array $tags = array())
43 | {
44 | $elbs = $this->findElbs();
45 | $matchingElbs = new Collection();
46 | foreach ($elbs as $elb) {
47 | /* @var $elb Elb */
48 | if ($elb->matchesTags($tags)) {
49 | $matchingElbs->attach($elb);
50 | }
51 | }
52 | return $matchingElbs;
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Iam/Repository.php:
--------------------------------------------------------------------------------
1 | iamClient = \AwsInspector\SdkFactory::getClient('Iam');
18 | }
19 |
20 | /**
21 | * @return User
22 | */
23 | public function findCurrentUser()
24 | {
25 | $result = $this->iamClient->getUser();
26 | return new User($result->get('User'));
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Iam/User.php:
--------------------------------------------------------------------------------
1 | getArn());
19 | return $parts[4];
20 | }
21 |
22 | }
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Rds/Database.php:
--------------------------------------------------------------------------------
1 | resourceName)) {
51 |
52 | // get account id from current user
53 | $iam = new \AwsInspector\Model\Iam\Repository();
54 | $accountId = $iam->findCurrentUser()->getAccountId();
55 |
56 | $parts = [];
57 | $parts['prefix'] = 'arn:aws:rds';
58 | $parts['region'] = substr($this->getAvailabilityZone(), 0, -1);
59 | $parts['AccountId'] = $accountId;
60 | $parts['resourcetype'] = 'db';
61 | $parts['name'] = $this->getDBInstanceIdentifier();
62 | $this->resourceName = implode(':', $parts);
63 | }
64 | return $this->resourceName;
65 | }
66 |
67 | public function getTags()
68 | {
69 | if (is_null($this->tags)) {
70 | $rdsClient = \AwsInspector\SdkFactory::getClient('Rds');
71 | /* @var $rdsClient \Aws\Rds\RdsClient */
72 | $result = $rdsClient->listTagsForResource(['ResourceName' => $this->getResourceName()]);
73 | $this->tags = $result->get('TagList');
74 | }
75 | return $this->tags;
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Rds/Repository.php:
--------------------------------------------------------------------------------
1 | rdsClient = \AwsInspector\SdkFactory::getClient('Rds');
18 | }
19 |
20 | public function findDatabases()
21 | {
22 | $result = $this->rdsClient->describeDBInstances();
23 | $rows = $result->search('DBInstances[]');
24 |
25 | $collection = new \AwsInspector\Model\Collection();
26 | foreach ($rows as $row) {
27 | $collection->attach(new Database($row));
28 | }
29 | return $collection;
30 | }
31 |
32 | /**
33 | * @param array $tags
34 | * @return \AwsInspector\Model\Collection
35 | */
36 | public function findDatabasesByTags(array $tags = array())
37 | {
38 | $databases = $this->findDatabases();
39 | $matchingDatabases = new Collection();
40 | foreach ($databases as $database) {
41 | /* @var $database Database */
42 | try {
43 | if ($database->matchesTags($tags)) {
44 | $matchingDatabases->attach($database);
45 | }
46 | } catch (\Aws\Rds\Exception\RdsException $e) {
47 | if ($e->getAwsErrorCode() != 'AccessDenied') {
48 | throw $e;
49 | }
50 | }
51 | }
52 | return $matchingDatabases;
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Model/Route53/Repository.php:
--------------------------------------------------------------------------------
1 | listResourceRecordSets([
22 | 'HostedZoneId' => $hostedZoneId,
23 | 'StartRecordName' => $nextRecordName
24 | ]);
25 | foreach ($res->search('ResourceRecordSets') as $recordSet) {
26 | $name = $recordSet['Name'];
27 | $type = $recordSet['Type'];
28 | unset($recordSet['Name']);
29 | unset($recordSet['Type']);
30 | $this->recordSets[$name][$type] = $recordSet;
31 | }
32 | $nextRecordName = $res->get('NextRecordName');
33 | } while ($res->get('IsTruncated'));
34 | }
35 |
36 | public function getAllRecordSets()
37 | {
38 | return $this->recordSets;
39 | }
40 |
41 | public function findByRecordSet($regex)
42 | {
43 | foreach ($this->recordSets as $name => $data) {
44 | if (preg_match($regex, $name)) {
45 | return $data;
46 | }
47 | }
48 | return [];
49 | }
50 |
51 | public function findByRecordSetNameAndType($recordSetName, $type)
52 | {
53 | $recordSetsForName = $this->findByRecordSet($recordSetName);
54 | if (count($recordSetName) == 0) {
55 | return [];
56 | } elseif (!isset($recordSetsForName[$type])) {
57 | return [];
58 | } else {
59 | return $recordSetsForName[$type];
60 | }
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Model/SecurityGroup/Repository.php:
--------------------------------------------------------------------------------
1 | findSecurityGroups([['Name' => 'group-id', 'Values' => [$groupId]]])->getFirst();
13 | return $group;
14 | }
15 |
16 | /**
17 | * @param array $filters
18 | * @return \AwsInspector\Model\Collection
19 | * @throws \Exception
20 | */
21 | public function findSecurityGroups(array $filters=[]) {
22 | $ec2Client = \AwsInspector\SdkFactory::getClient('ec2'); /* @var $ec2Client \Aws\Ec2\Ec2Client */
23 | $result = $ec2Client->describeSecurityGroups(['Filters' => $filters]);
24 | $rows = $result->search('SecurityGroups[]');
25 |
26 | $collection = new \AwsInspector\Model\Collection();
27 | foreach ($rows as $row) {
28 | $securityGroup = new SecurityGroup($row);
29 | if ($securityGroup !== false) {
30 | $collection->attach($securityGroup);
31 | }
32 | }
33 | return $collection;
34 | }
35 |
36 | /**
37 | * @param array $tags
38 | * @return \AwsInspector\Model\Collection
39 | */
40 | public function findSecurityGroupsByTags(array $tags=array()) {
41 | foreach ($tags as $tagName => $tagValue) {
42 | $filters[] = ['Name' => 'tag-key', "Values" => [$tagName]];
43 | $filters[] = ['Name' => 'tag-value', "Values" => [$tagValue]];
44 | }
45 | return $this->findSecurityGroups($filters);
46 | }
47 |
48 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Model/SecurityGroup/SecurityGroup.php:
--------------------------------------------------------------------------------
1 | getIpPermissions() as $permission) {
28 | if ($permission['IpProtocol'] != $protocol || $permission['FromPort'] != $port) {
29 | continue;
30 | }
31 | if ($origin instanceof SecurityGroup) {
32 | foreach ($permission['UserIdGroupPairs'] as $idGroupPair) {
33 | if ($idGroupPair['GroupId'] == $origin->getGroupId()) {
34 | return true;
35 | }
36 | }
37 | } else {
38 | $isRange = (strpos($origin, '/') !== false);
39 | foreach ($permission['IpRanges'] as $ipRange) {
40 | if ($isRange) {
41 | if ($origin == $ipRange['CidrIp']) {
42 | return true;
43 | }
44 | } else {
45 | if ($this->ipMatchesCidr($origin, $ipRange['CidrIp'])) {
46 | return true;
47 | }
48 | }
49 | }
50 | }
51 | }
52 | return false;
53 | }
54 |
55 | protected function ipMatchesCidr($ip, $range)
56 | {
57 | list ($subnet, $bits) = explode('/', $range);
58 | $ip = ip2long($ip);
59 | $subnet = ip2long($subnet);
60 | $mask = -1 << (32 - $bits);
61 | $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
62 | return ($ip & $mask) == $subnet;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/AwsInspector/Registry.php:
--------------------------------------------------------------------------------
1 | getClient($client, $profile, $args);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/AwsInspector/Ssh/Agent.php:
--------------------------------------------------------------------------------
1 | /dev/null 2>&1', $output);
16 | exec('ssh-add ' . $privateKeyFile, $output);
17 | var_dump($output);
18 | }
19 |
20 | /**
21 | * Check if the current private key is already loaded
22 | *
23 | * @param $privateKeyFile
24 | * @return bool
25 | */
26 | public static function identityLoaded($privateKeyFile) {
27 | $returnVar = null;
28 | exec('ssh-add -l | grep '. $privateKeyFile, $output, $returnVar);
29 | return ($returnVar == 0);
30 | }
31 |
32 | /**
33 | * Since deleting an entity requires the PUBLIC key instead of the private key (that's used for adding)
34 | * we're going to extract the public key first and write it into a temp file
35 | *
36 | * @param $privateKeyFile
37 | * @throws \Exception
38 | */
39 | public static function deleteIdentity($privateKeyFile) {
40 | if (!is_file($privateKeyFile)) {
41 | throw new \Exception('Private key file not found.');
42 | }
43 | exec('ssh-add -L | grep '. $privateKeyFile, $output);
44 | $tmpFile = tempnam(sys_get_temp_dir(), 'publickey_');
45 | file_put_contents($tmpFile, end($output));
46 | exec('ssh-add -d ' . $tmpFile . ' >/dev/null 2>&1', $output);
47 | unlink($tmpFile);
48 | }
49 |
50 | public static function deleteAllIdentities() {
51 | exec('ssh-add -D', $output);
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Ssh/Command.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
21 | if (!is_string($command)) {
22 | throw new \InvalidArgumentException("Command must be a string.");
23 | }
24 | $this->command = $command;
25 | $this->asUser = $asUser;
26 | }
27 |
28 | protected function getCommandString()
29 | {
30 | $command = $this->command;
31 | if ($this->asUser) {
32 | $command = sprintf('sudo -u %s bash -c %s',
33 | escapeshellarg($this->asUser),
34 | escapeshellarg($command)
35 | );
36 | }
37 | return $command;
38 | }
39 |
40 | public function __toString()
41 | {
42 | if (empty($this->connection->__toString())) {
43 | return $this->getCommandString();
44 | }
45 | return sprintf(
46 | "%s %s",
47 | $this->connection,
48 | escapeshellarg($this->getCommandString())
49 | );
50 | }
51 |
52 | public function exec()
53 | {
54 | // file_put_contents('/tmp/exec.log', $this->__toString() . "\n\n", FILE_APPEND);
55 | $returnVar = null;
56 | exec($this->__toString(), $output, $returnVar);
57 | return [
58 | 'output' => $output,
59 | 'returnVar' => $returnVar
60 | ];
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Ssh/Identity.php:
--------------------------------------------------------------------------------
1 | privateKey = $privateKey;
15 | $this->keepIdentity = $keepIdentity;
16 | }
17 |
18 | public function getPrivateKeyFile() {
19 | return $this->privateKey->getPrivateKeyFile();
20 | }
21 |
22 | public function loadIdentity()
23 | {
24 | if (!Agent::identityLoaded($this->getPrivateKeyFile())) {
25 | Agent::addIdentity($this->getPrivateKeyFile());
26 | }
27 | return $this;
28 | }
29 |
30 | public function removeIdentity()
31 | {
32 | if (!empty($this->getPrivateKeyFile()) && Agent::identityLoaded($this->getPrivateKeyFile())) {
33 | // echo "Removing identity {$this->unlockedPrivateKeyFile}\n";
34 | Agent::deleteIdentity($this->getPrivateKeyFile());
35 | }
36 | return $this;
37 | }
38 |
39 | public function __destruct()
40 | {
41 | if (!$this->keepIdentity) {
42 | $this->removeIdentity();
43 | // Remove control paths for ssh multiplexing. See \AwsInspector\Ssh->__toString()
44 | // TODO: deleting these files isn't enough. The mux needs to be closed.
45 | exec('rm ~/mux* 2> /dev/null');
46 | }
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/src/AwsInspector/Ssh/LocalConnection.php:
--------------------------------------------------------------------------------
1 | privateKeyFile = $privateKeyFile;
27 | } else {
28 | $encryptedPrivateKeyFile = $privateKeyFile . '.encrypted';
29 | $this->privateKeyFile = $privateKeyFile . '.unlocked';
30 | if (is_file($encryptedPrivateKeyFile)) {
31 | if (class_exists('\Vault\Vault')) {
32 | $vault = new Vault();
33 | $vault->decryptFile($encryptedPrivateKeyFile, $this->privateKeyFile);
34 | chmod($this->privateKeyFile, 0600);
35 | $this->unlocked = true;
36 | } else {
37 | throw new \Exception('Please install aoepeople/vault');
38 | }
39 | } else {
40 | throw new FileNotFoundException('Could not find private key file ' . $privateKeyFile);
41 | }
42 | }
43 | $this->privateKeyFile = realpath($this->privateKeyFile);
44 | }
45 |
46 | public function getPrivateKeyFile() {
47 | return $this->privateKeyFile;
48 | }
49 |
50 | public function __destruct()
51 | {
52 | if ($this->unlocked) {
53 | unlink($this->privateKeyFile);
54 | $this->unlocked = null;
55 | }
56 | }
57 |
58 |
59 |
60 | }
--------------------------------------------------------------------------------
/src/StackFormation/Command/Blueprint/AbstractBlueprintCommand.php:
--------------------------------------------------------------------------------
1 | addArgument('blueprint', InputArgument::REQUIRED, 'Blueprint');
16 | }
17 |
18 | protected function interact(InputInterface $input, OutputInterface $output)
19 | {
20 | $this->interactAskForBlueprint($input, $output);
21 | }
22 |
23 | abstract protected function executeWithBlueprint(\StackFormation\Blueprint $blueprint, InputInterface $input, OutputInterface $output);
24 |
25 | protected final function execute(InputInterface $input, OutputInterface $output)
26 | {
27 | $blueprintName = $input->getArgument('blueprint');
28 | $blueprint = $this->blueprintFactory->getBlueprint($blueprintName);
29 | return $this->executeWithBlueprint($blueprint, $input, $output);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Blueprint/DeployPolicyCommand.php:
--------------------------------------------------------------------------------
1 | setName('blueprint:deploy-policy')
30 | ->setDescription('Deploy stack policy');
31 | }
32 |
33 | protected function executeWithBlueprint(Blueprint $blueprint, InputInterface $input, OutputInterface $output)
34 | {
35 | $blueprintAction = new BlueprintAction($blueprint, $this->profileManager, $output);
36 | $blueprintAction->updateStackPolicy();
37 | $output->writeln('Updated stack policy.');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Blueprint/Show/ChangesetCommand.php:
--------------------------------------------------------------------------------
1 | setName('blueprint:show:changeset')
15 | ->setDescription('Preview changeset');
16 | }
17 |
18 | protected function executeWithBlueprint(\StackFormation\Blueprint $blueprint, InputInterface $input, OutputInterface $output)
19 | {
20 | $blueprintAction = new \StackFormation\BlueprintAction($blueprint, $this->profileManager, $output);
21 | $changeSetResult = $blueprintAction->getChangeSet();
22 | $table = new \StackFormation\Helper\ChangeSetTable($output);
23 | $table->render($changeSetResult);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Blueprint/Show/DependenciesCommand.php:
--------------------------------------------------------------------------------
1 | setName('blueprint:show:dependencies')
17 | ->setDescription('Show (incoming) dependencies to stacks and environment variables');
18 | }
19 |
20 | protected function executeWithBlueprint(Blueprint $blueprint, InputInterface $input, OutputInterface $output)
21 | {
22 | // trigger resolving all placeholders
23 | $this->dependencyTracker->reset();
24 | $blueprint->getPreprocessedTemplate();
25 |
26 | $output->writeln("Blueprint '{$blueprint->getName()} depends on following stack's resources/parameters/outputs:");
27 |
28 | $table = new Table($output);
29 | $table->setHeaders(['Origin ('.$blueprint->getName().')', 'Source Stack', 'Field'])
30 | ->setRows($this->dependencyTracker->getStackDependenciesAsFlatList())
31 | ->render();
32 |
33 | $output->writeln("Blueprint '{$blueprint->getName()} depends on following environment variables:");
34 | $table = new Table($output);
35 | $table->setHeaders(['Var', 'Current Value', 'Type', 'Origin (within "'.$blueprint->getName().'")'])
36 | ->setRows($this->dependencyTracker->getEnvDependenciesAsFlatList())
37 | ->render();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Blueprint/Show/ParametersCommand.php:
--------------------------------------------------------------------------------
1 | setName('blueprint:show:parameters')
19 | ->setDescription('Preview parameters and tags')
20 | ->addOption(
21 | 'unresolved',
22 | null,
23 | InputOption::VALUE_NONE,
24 | 'Do not resolve placeholders'
25 | );
26 | }
27 |
28 | protected function executeWithBlueprint(Blueprint $blueprint, InputInterface $input, OutputInterface $output)
29 | {
30 | $unresolved = $input->getOption('unresolved');
31 |
32 | $output->writeln("Blueprint '{$blueprint->getName()}':");
33 |
34 | $parameters = $blueprint->getParameters(!$unresolved);
35 |
36 | $output->writeln('== PARAMETERS ==');
37 | $table = new Table($output);
38 | $table
39 | ->setHeaders(['Key', 'Value'])
40 | ->setRows($parameters);
41 | $table->render();
42 |
43 | $output->writeln('== TAGS ==');
44 | $table = new Table($output);
45 | $table
46 | ->setHeaders(['Key', 'Value'])
47 | ->setRows($blueprint->getTags(!$unresolved));
48 | $table->render();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Blueprint/Show/StacknameCommand.php:
--------------------------------------------------------------------------------
1 | setName('blueprint:show:stackname')
16 | ->setDescription('Return stack name for given blueprint name (resolving placeholders)');
17 | }
18 |
19 | protected function executeWithBlueprint(Blueprint $blueprint, InputInterface $input, OutputInterface $output)
20 | {
21 | $output->writeln($blueprint->getStackName());
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Blueprint/Show/TemplateCommand.php:
--------------------------------------------------------------------------------
1 | setName('blueprint:show:template')
17 | ->setDescription('Preview preprocessed template');
18 | }
19 |
20 | protected function executeWithBlueprint(Blueprint $blueprint, InputInterface $input, OutputInterface $output)
21 | {
22 | $output->writeln($blueprint->getPreprocessedTemplate());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Blueprint/ValidateCommand.php:
--------------------------------------------------------------------------------
1 | setName('blueprint:validate')
19 | ->setDescription('Validate a blueprint\'s template');
20 | }
21 |
22 | protected function executeWithBlueprint(Blueprint $blueprint, InputInterface $input, OutputInterface $output)
23 | {
24 | $blueprintAction = new BlueprintAction($blueprint, $this->profileManager, $output);
25 | $blueprintAction->validateTemplate(); // will throw an exception if there's a problem
26 |
27 | $formatter = new FormatterHelper();
28 | $formattedBlock = $formatter->formatBlock(['No validation errors found.'], 'info', true);
29 |
30 | $output->writeln("\n\n$formattedBlock\n\n");
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/SetupCommand.php:
--------------------------------------------------------------------------------
1 | setName('setup')
20 | ->setDescription('Setup stackformation');
21 | }
22 |
23 | protected function execute(InputInterface $input, OutputInterface $output)
24 | {
25 | $fs = new Filesystem();
26 |
27 | if ($fs->exists(self::ENV_FILE)) {
28 | $output->writeln(self::ENV_FILE . ' already exist!');
29 | return;
30 | }
31 |
32 | $helper = $this->getHelper('question');
33 | $data = [];
34 |
35 | $awsAccessKeyIdQuestion= new Question('Please enter your AWS_ACCESS_KEY_ID: ');
36 | $data['AWS_ACCESS_KEY_ID'] = $helper->ask($input, $output, $awsAccessKeyIdQuestion);
37 |
38 | $awsSecretAccessKeyIdQuestion= new Question('Please enter your AWS_SECRET_ACCESS_KEY: ');
39 | $data['AWS_SECRET_ACCESS_KEY'] = $helper->ask($input, $output, $awsSecretAccessKeyIdQuestion);
40 |
41 | $regionQuestion = new Question('Please enter the name of your region [eu-west-1]: ', 'eu-west-1');
42 | $data['AWS_DEFAULT_REGION'] = $helper->ask($input, $output, $regionQuestion);
43 |
44 | try {
45 | $fs->dumpFile(self::ENV_FILE, $this->parseContent($data));
46 | $output->writeln('');
47 | $output->writeln(self::ENV_FILE . ' file was successfully created.');
48 | $output->writeln('You should also add it to your .gitignore with:');
49 | $output->writeln('echo .env.default >> .gitignore');
50 | } catch (IOExceptionInterface $e) {
51 | echo 'An error occurred while creating ' . self::ENV_FILE . ' file at ' . $e->getPath();
52 | }
53 | }
54 |
55 | /**
56 | * @param array $$data
57 | * @return string
58 | */
59 | protected function parseContent(array $data)
60 | {
61 | $content = '';
62 | foreach ($data as $key => $value) {
63 | $content .= $key . '=' . $value . PHP_EOL;
64 | }
65 |
66 | return $content;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Stack/AbstractStackCommand.php:
--------------------------------------------------------------------------------
1 | addArgument('stack', InputArgument::REQUIRED, 'Stack');
17 | $this->afterConfigure();
18 | }
19 |
20 | protected function afterConfigure()
21 | {
22 | // overwrite this in your inheriting class (e.g. for adding optional arguments)
23 | }
24 |
25 | protected function interact(InputInterface $input, OutputInterface $output)
26 | {
27 | $this->interactAskForStack($input, $output);
28 | }
29 |
30 | abstract protected function executeWithStack(\StackFormation\Stack $stack, InputInterface $input, OutputInterface $output);
31 |
32 | protected final function execute(InputInterface $input, OutputInterface $output)
33 | {
34 | $stackName = $input->getArgument('stack');
35 | Validator::validateStackname($stackName);
36 | $stack = $this->getStackFactory()->getStack($stackName);
37 | return $this->executeWithStack($stack, $input, $output);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Stack/DeleteCommand.php:
--------------------------------------------------------------------------------
1 | setName('stack:delete')
20 | ->setDescription('Delete Stack')
21 | ->addArgument(
22 | 'stack',
23 | InputArgument::REQUIRED | InputArgument::IS_ARRAY,
24 | 'Stack'
25 | )
26 | ->addOption(
27 | 'except',
28 | null,
29 | InputOption::VALUE_OPTIONAL,
30 | 'Stack that should NOT be deleted'
31 | )
32 | ->addOption(
33 | 'force',
34 | null,
35 | InputOption::VALUE_NONE,
36 | 'Skip asking for confirmation'
37 | );
38 | }
39 |
40 | protected function interact(InputInterface $input, OutputInterface $output)
41 | {
42 | $this->interactAskForStack($input, $output);
43 |
44 | if (!$input->getOption('force')) {
45 | $stacks = $this->getResolvedStacks($input);
46 | $stacks = "\n - " . implode("\n - ", $stacks) . "\n";
47 | $helper = $this->getHelper('question');
48 | $question = new ConfirmationQuestion("Are you sure you want to delete following stacks? $stacks [y/N] ", false);
49 | if (!$helper->ask($input, $output, $question)) {
50 | throw new \Exception('Operation aborted');
51 | }
52 | $input->setOption('force', true);
53 | }
54 | }
55 |
56 | protected function getResolvedStacks(InputInterface $input)
57 | {
58 | $stacks = Finder::find(
59 | (array)$input->getArgument('stack'),
60 | $this->getStacks()
61 | );
62 |
63 | $except = $input->getOption('except');
64 | if (!empty($except)) {
65 | if (($key = array_search($except, $stacks)) !== false) {
66 | unset($stacks[$key]);
67 | }
68 | }
69 |
70 | return $stacks;
71 | }
72 |
73 | protected function execute(InputInterface $input, OutputInterface $output)
74 | {
75 | if (!$input->getOption('force')) {
76 | throw new \Exception('Operation aborted (use --force)');
77 | }
78 |
79 | $stacks = $this->getResolvedStacks($input);
80 |
81 | if (count($stacks) == 0) {
82 | $output->writeln("No stacks deleted.");
83 | }
84 |
85 | foreach ($stacks as $stackName) {
86 | $this->getStackFactory()->getStack($stackName)->delete();
87 | $output->writeln("Triggered deletion of stack '$stackName'.");
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Stack/DiffCommand.php:
--------------------------------------------------------------------------------
1 | setName('stack:diff')
20 | ->setDescription('Compare a stack\'s template and input parameters with its blueprint');
21 | }
22 |
23 | protected function executeWithStack(Stack $stack, InputInterface $input, OutputInterface $output)
24 | {
25 | $blueprint = $this->blueprintFactory->getBlueprintByStack($stack);
26 |
27 | $diff = new Diff($output);
28 | $diff->setStack($stack)->setBlueprint($blueprint);
29 |
30 | $formatter = new FormatterHelper();
31 | $output->writeln("\n" . $formatter->formatBlock(['Parameters:'], 'error', true) . "\n");
32 | $diff->diffParameters();
33 |
34 | $output->writeln("\n" . $formatter->formatBlock(['Template:'], 'error', true) . "\n");
35 | $diff->diffTemplates();
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Stack/ListCommand.php:
--------------------------------------------------------------------------------
1 | setName('stack:list')
19 | ->setDescription('List all stacks')
20 | ->addOption(
21 | 'nameFilter',
22 | null,
23 | InputOption::VALUE_OPTIONAL,
24 | 'Name Filter (regex). Example --nameFilter \'/^foo/\''
25 | )
26 | ->addOption(
27 | 'statusFilter',
28 | null,
29 | InputOption::VALUE_OPTIONAL,
30 | 'Name Filter (regex). Example --statusFilter \'/IN_PROGRESS/\''
31 | );
32 | }
33 |
34 | protected function execute(InputInterface $input, OutputInterface $output)
35 | {
36 | $nameFilter = $input->getOption('nameFilter');
37 | $statusFilter = $input->getOption('statusFilter');
38 |
39 | $stacks = $this->getStackFactory()->getStacksFromApi(false, $nameFilter, $statusFilter);
40 |
41 | $rows = [];
42 | foreach ($stacks as $stackName => $stack) { /* @var $stack Stack */
43 | $rows[] = [$stackName, Decorator::decorateStatus($stack->getStatus())];
44 | }
45 |
46 | $table = new Table($output);
47 | $table
48 | ->setHeaders(['Name', 'Status'])
49 | ->setRows($rows);
50 | $table->render();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Stack/ObserveCommand.php:
--------------------------------------------------------------------------------
1 | setName('stack:observe')
19 | ->setDescription('Observe stack progress')
20 | ->addOption(
21 | 'deleteOnTerminate',
22 | null,
23 | InputOption::VALUE_NONE,
24 | 'Delete current stack if StackFormation received SIGTERM (e.g. Jenkins job abort) or SIGINT (e.g. CTRL+C)'
25 | );
26 | }
27 |
28 | protected function executeWithStack(Stack $stack, InputInterface $input, OutputInterface $output)
29 | {
30 | $observer = new Observer($stack, $this->getStackFactory(), $output);
31 | if ($input->getOption('deleteOnTerminate')) {
32 | $observer->deleteOnSignal();
33 | }
34 | return $observer->observeStackActivity();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Stack/Show/AbstractShowCommand.php:
--------------------------------------------------------------------------------
1 | property, ['resources', 'outputs', 'parameters'])) {
20 | throw new \Exception('Invalid property');
21 | }
22 |
23 | $this
24 | ->setName('stack:show:'.$this->property)
25 | ->setDescription('Show a live stack\'s '.$this->property);
26 | }
27 |
28 | protected function afterConfigure()
29 | {
30 | $this->addArgument('key', InputArgument::OPTIONAL, 'key');
31 | }
32 |
33 | protected function executeWithStack(Stack $stack, InputInterface $input, OutputInterface $output)
34 | {
35 | $methodName = 'get'.ucfirst($this->property);
36 |
37 | $key = $input->getArgument('key');
38 | if ($key) {
39 | $methodName = substr($methodName, 0, -1);
40 | $output->writeln($stack->$methodName($key));
41 | return;
42 | }
43 |
44 | $data = $stack->$methodName();
45 |
46 | $rows = [];
47 | foreach ($data as $k => $v) {
48 | $v = strlen($v) > 100 ? substr($v, 0, 100) . "..." : $v;
49 | $rows[] = [$k, $v];
50 | }
51 |
52 | $table = new Table($output);
53 | $table->setHeaders(['Key', 'Value'])
54 | ->setRows($rows)
55 | ->render();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Stack/Show/DependantsCommand.php:
--------------------------------------------------------------------------------
1 | setName('stack:show:dependants')
20 | ->setDescription('Show (outgoing) dependencies to blueprints');
21 | }
22 |
23 | protected function executeWithStack(Stack $stack, InputInterface $input, OutputInterface $output)
24 | {
25 | $this->dependencyTracker->reset();
26 | foreach ($this->blueprintFactory->getAllBlueprints() as $blueprint) {
27 | $blueprint->getPreprocessedTemplate();
28 | }
29 |
30 | $dependants = $this->dependencyTracker->findDependantsForStack($stack->getName());
31 |
32 | $rows = [];
33 | foreach ($dependants as $dependant) {
34 | $rows[] = [
35 | $dependant['targetType'] . ':' . $dependant['targetResource'],
36 | $dependant['type'] . ':' . $dependant['blueprint'] . ':' . $dependant['key'],
37 | ];
38 | }
39 |
40 | $output->writeln("Following blueprints depend on stack '{$stack->getName()}':");
41 |
42 | $table = new Table($output);
43 | $table->setHeaders(['Origin (Stack: '.$stack->getName() . ')', 'Blueprint'])
44 | ->setRows($rows)
45 | ->render();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/StackFormation/Command/Stack/Show/OutputsCommand.php:
--------------------------------------------------------------------------------
1 | setName('stack:tree')
19 | ->setDescription('List all stacks as tree')
20 | ->addOption(
21 | 'nameFilter',
22 | null,
23 | InputOption::VALUE_OPTIONAL,
24 | 'Name Filter (regex). Example --nameFilter \'/^foo/\''
25 | )
26 | ->addOption(
27 | 'statusFilter',
28 | null,
29 | InputOption::VALUE_OPTIONAL,
30 | 'Name Filter (regex). Example --statusFilter \'/IN_PROGRESS/\''
31 | );
32 | }
33 |
34 | protected function execute(InputInterface $input, OutputInterface $output)
35 | {
36 | $nameFilter = $input->getOption('nameFilter');
37 | $statusFilter = $input->getOption('statusFilter');
38 |
39 | $stacks = $this->getStackFactory()->getStacksFromApi(false, $nameFilter, $statusFilter);
40 | $dataTree = $this->prepareTree($stacks);
41 | $dataTree = $this->flatternTree($dataTree);
42 | $this->renderNode($dataTree);
43 | }
44 |
45 | /**
46 | * prepare stack list
47 | *
48 | * @param StackFormation\Stack[]
49 | * @return array
50 | */
51 | protected function prepareTree(array $arr) {
52 | $tree = [];
53 | foreach ($arr as $a) {
54 | $name = $a->getName();
55 | $cur = &$tree;
56 | foreach (explode("-", $name) as $e) {
57 | if (empty($cur[$e])) $cur[$e] = [];
58 | $cur = &$cur[$e];
59 | }
60 | }
61 | return $tree;
62 | }
63 |
64 | /**
65 | * flattern tree to gain a better overview
66 | *
67 | * @param array $treeIn
68 | * @return array
69 | */
70 | protected function flatternTree(array $treeIn)
71 | {
72 | $treeOut = [];
73 | foreach ($treeIn as $name => $children) {
74 | if (count($children) === 0) {
75 | $treeOut[$name] = [];
76 | } elseif (count($children) === 1) {
77 | $name = sprintf('%s-%s', $name, key($children));
78 | if (count($children[key($children)]) > 0) {
79 | $treeOut[$name] = $this->flatternTree($children[key($children)]);
80 | }else{
81 | $treeOut[$name] = [];
82 | }
83 | } else {
84 | $treeOut[$name] = $this->flatternTree($children);
85 | }
86 |
87 | }
88 | return $treeOut;
89 | }
90 |
91 | /**
92 | * render tree node
93 | *
94 | * @param string $tree
95 | * @param int $depth
96 | * @param int $cap
97 | */
98 | protected function renderNode($tree, $depth = 0, $cap = 0) {
99 |
100 | $n = count($tree);
101 | foreach ($tree as $k => $next) {
102 | for ($pre = "", $i = $depth - 1; $i >= 0; $i--){
103 | $pre.= $cap >> $i & 1 ? "│ " : " ";
104 | }
105 | echo $pre, --$n > 0 ? '├──' : '└──', $k, PHP_EOL;
106 | if (false === empty($next)){
107 | $this->renderNode($next, $depth + 1, ($cap << 1) | ($n > 0));
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/StackFormation/CommandRegistry.php:
--------------------------------------------------------------------------------
1 | blueprintName = $blueprintname;
13 | $message = "Blueprint '$blueprintname' not found.";
14 | parent::__construct($message, $code, $previous);
15 | }
16 |
17 | public function getBlueprintName()
18 | {
19 | return $this->blueprintName;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/StackFormation/Exception/BlueprintReferenceNotFoundException.php:
--------------------------------------------------------------------------------
1 | stackName = $stackName;
13 | $message = "Invalid stack name '$stackName'";
14 | parent::__construct($message, 0, $previous=null);
15 | }
16 |
17 | /**
18 | * @return string
19 | */
20 | public function getStackName()
21 | {
22 | return $this->stackName;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/StackFormation/Exception/MissingEnvVarException.php:
--------------------------------------------------------------------------------
1 | envVar = $varName;
13 | $message = "Environment variable '$varName' not found";
14 | parent::__construct($message, $code, $previous);
15 | }
16 |
17 | public function getEnvVar()
18 | {
19 | return $this->envVar;
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/StackFormation/Exception/NoBlueprintsFoundException.php:
--------------------------------------------------------------------------------
1 | stackName = $stackName;
14 | $this->state = $state;
15 | parent::__construct("Stack '$stackName' not found (state: $state)", 0, $previous);
16 | }
17 |
18 | public function getStackName() {
19 | return $this->stackName;
20 | }
21 |
22 | public function getState() {
23 | return $this->state;
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/StackFormation/Exception/StackNoUpdatesToBePerformedException.php:
--------------------------------------------------------------------------------
1 | stackName = $stackName;
14 | parent::__construct("No updates to be performened on stack $stackName", 0, $previous);
15 | }
16 |
17 | public function getStackName() {
18 | return $this->stackName;
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/StackFormation/Exception/StackNotFoundException.php:
--------------------------------------------------------------------------------
1 | sourceBlueprint = $sourceBlueprint;
17 | $this->sourceType = $sourceType;
18 | $this->sourceKey = $sourceKey;
19 |
20 | if (!is_scalar($value)) {
21 | $value = var_export($value, true);
22 | }
23 |
24 | $message = "Error resolving value '$value'" . $this->getExceptionMessageAppendix();
25 | parent::__construct($message, 0, $previous);
26 | }
27 |
28 | /**
29 | * Craft exception message appendix
30 | *
31 | * @return string
32 | */
33 | protected function getExceptionMessageAppendix()
34 | {
35 | $tmp = [];
36 | if ($this->sourceBlueprint) { $tmp[] = 'Blueprint: ' . $this->sourceBlueprint->getName(); }
37 | if ($this->sourceType) { $tmp[] = 'Type:' . $this->sourceType; }
38 | if ($this->sourceKey) { $tmp[] = 'Key:' . $this->sourceKey; }
39 | if (count($tmp)) {
40 | return ' (' . implode(', ', $tmp) . ')';
41 | }
42 | return '';
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/StackFormation/Helper/Cache.php:
--------------------------------------------------------------------------------
1 | get('message', function() { return 'Hello World'; });
12 | *
13 | * Will return the message from cache if present and will generate it
14 | * by executing the callback and store it in the cache before returning the value.
15 | *
16 | * @author Fabrizio Branca
17 | */
18 | class Cache
19 | {
20 |
21 | protected $cache = [];
22 |
23 | /**
24 | * Get (and generate)
25 | *
26 | * @param $key
27 | * @param callable|null $callback
28 | * @param bool $fresh
29 | * @return mixed
30 | * @throws \Exception
31 | */
32 | public function get($key, callable $callback = null, $fresh = false)
33 | {
34 | if ($fresh || !$this->has($key)) {
35 | if (!is_null($callback)) {
36 | $this->set($key, $callback());
37 | } else {
38 | throw new \Exception(sprintf("Cache key '%s' not found.", $key));
39 | }
40 | } /* else {
41 | echo "HIT!";
42 | } */
43 | return $this->cache[$key];
44 | }
45 |
46 | /**
47 | * Has
48 | *
49 | * @param $key
50 | * @return bool
51 | */
52 | public function has($key)
53 | {
54 | return isset($this->cache[$key]);
55 | }
56 |
57 | /**
58 | * Set
59 | *
60 | * @param $key
61 | * @param $value
62 | */
63 | public function set($key, $value)
64 | {
65 | $this->cache[$key] = $value;
66 | }
67 |
68 | /**
69 | * Delete
70 | *
71 | * @param $key
72 | */
73 | public function delete($key)
74 | {
75 | unset($this->cache[$key]);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/StackFormation/Helper/ChangeSetTable.php:
--------------------------------------------------------------------------------
1 | setHeaders(['Action', 'LogicalResourceId', 'PhysicalResourceId', 'ResourceType', 'Replacement']);
11 | $this->setRows($this->getRows($changeSetResult));
12 | parent::render();
13 | }
14 |
15 | protected function getRows(\Aws\Result $changeSetResult) {
16 | $rows = [];
17 | foreach ($changeSetResult->search('Changes[]') as $change) {
18 | $resourceChange = $change['ResourceChange'];
19 | $rows[] = [
20 | // $change['Type'], // would this ever show anything other than 'Resource'?
21 | $this->decorateChangesetAction($resourceChange['Action']),
22 | $resourceChange['LogicalResourceId'],
23 | isset($resourceChange['PhysicalResourceId']) ? $resourceChange['PhysicalResourceId'] : '',
24 | $resourceChange['ResourceType'],
25 | isset($resourceChange['Replacement']) ? Decorator::decorateChangesetReplacement($resourceChange['Replacement']) : '',
26 | ];
27 | }
28 | return $rows;
29 | }
30 |
31 | protected function decorateChangesetAction($changeSetAction) {
32 | switch ($changeSetAction) {
33 | case 'Modify': return "$changeSetAction>";
34 | case 'Add': return "$changeSetAction>";
35 | case 'Remove': return "$changeSetAction>";
36 | }
37 | return $changeSetAction;
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/src/StackFormation/Helper/Decorator.php:
--------------------------------------------------------------------------------
1 | UPDATE>_ROLLBACK_>COMPLETE>";
14 | }
15 | if (strpos($status, 'IN_PROGRESS') !== false) {
16 | return "$status>";
17 | }
18 | if (strpos($status, 'COMPLETE') !== false) {
19 | return "$status>";
20 | }
21 | if (strpos($status, 'FAILED') !== false) {
22 | return "$status>";
23 | }
24 | return $status;
25 | }
26 |
27 | public static function decorateChangesetReplacement($changeSetReplacement)
28 | {
29 | if ($changeSetReplacement == 'Conditional') {
30 | return "$changeSetReplacement>";
31 | }
32 | if ($changeSetReplacement == 'False') {
33 | return "$changeSetReplacement>";
34 | }
35 | if ($changeSetReplacement == 'True') {
36 | return "$changeSetReplacement>";
37 | }
38 | return $changeSetReplacement;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/StackFormation/Helper/Div.php:
--------------------------------------------------------------------------------
1 | $value) {
21 | $tmp[] = "$key$keyValueSeparator$value";
22 | }
23 | return implode($itemSeparator, $tmp);
24 | }
25 |
26 | /**
27 | * @param string $program
28 | * @return bool
29 | * @see n98-magerun/src/N98/Util/OperatingSystem.php
30 | */
31 | public static function isProgramInstalled($program)
32 | {
33 | $out = null;
34 | $return = null;
35 | @exec('which ' . $program, $out, $return);
36 | return $return === 0;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/StackFormation/Helper/Exception.php:
--------------------------------------------------------------------------------
1 | getResponse()->getBody();
11 | $xml = simplexml_load_string($message);
12 | if ($xml !== false && $xml->Error->Message) {
13 | return $xml->Error->Message;
14 | }
15 | return $exception->getMessage();
16 | }
17 |
18 | public static function refineException(\Aws\CloudFormation\Exception\CloudFormationException $exception)
19 | {
20 | $message = self::extractMessage($exception);
21 | $matches = [];
22 | if (preg_match('/^Stack \[(.+)\] does not exist$/', $message, $matches)) {
23 | return new \StackFormation\Exception\StackNotFoundException($matches[1], $exception);
24 | }
25 | if (preg_match('/.+stack\/(.+)\/.+is in ([A-Z_]+) state and can not be updated./', $message, $matches)) {
26 | return new \StackFormation\Exception\StackCannotBeUpdatedException($matches[1], $matches[2], $exception);
27 | }
28 | if (strpos($message, 'No updates are to be performed.') !== false) {
29 | return new \StackFormation\Exception\StackNoUpdatesToBePerformedException('TBD');
30 | }
31 | return new \StackFormation\Exception\CleanCloudFormationException($message, 0, $exception);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/StackFormation/Helper/Finder.php:
--------------------------------------------------------------------------------
1 | describeLogGroups($params);
55 | foreach ($resGroups->search('logGroups[].logGroupName') as $logGroupName) {
56 | $streamsNextToken = null;
57 | do {
58 | $streamsParams = [
59 | 'logGroupName' => $logGroupName,
60 | 'orderBy' => 'LastEventTime'
61 | ];
62 | if ($streamsNextToken) {
63 | $streamsParams['nextToken'] = $streamsNextToken;
64 | }
65 | $resStreams = $cloudWatchLogClient->describeLogStreams($streamsParams);
66 | foreach ($resStreams->search('logStreams[].logStreamName') as $logStreamName) {
67 | if ($stream == $logStreamName) {
68 | return $logGroupName;
69 | }
70 | }
71 | $streamsNextToken = $resStreams->get("nextToken");
72 | } while ($streamsNextToken);
73 | }
74 | $groupsNextToken = $resGroups->get("nextToken");
75 | } while ($groupsNextToken);
76 | return null;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/StackFormation/Helper/Pipeline.php:
--------------------------------------------------------------------------------
1 | stages[] = $stage;
24 | return $this;
25 | }
26 |
27 | /**
28 | * Process the payload.
29 | *
30 | * @param $payload
31 | * @return mixed
32 | */
33 | public function process($payload)
34 | {
35 | foreach ($this->stages as $stage) {
36 | $payload = call_user_func($stage, $payload);
37 | }
38 | return $payload;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/StackFormation/Helper/StaticCache.php:
--------------------------------------------------------------------------------
1 | get($key, $callback, $fresh);
34 | }
35 |
36 | /**
37 | * Has
38 | *
39 | * @param $key
40 | * @return bool
41 | */
42 | public function has($key)
43 | {
44 | return self::getCache()->has($key);
45 | }
46 |
47 | /**
48 | * Set
49 | *
50 | * @param $key
51 | * @param $value
52 | */
53 | public function set($key, $value)
54 | {
55 | return self::getCache()->set($key, $value);
56 | }
57 |
58 | /**
59 | * Delete
60 | *
61 | * @param $key
62 | */
63 | public function delete($key)
64 | {
65 | return self::getCache()->delete($key);
66 | }
67 |
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/StackFormation/Helper/Validator.php:
--------------------------------------------------------------------------------
1 | 10) {
27 | throw new \Exception('No more than 10 tags are allowed');
28 | }
29 | foreach ($tags as $tag) {
30 | // key
31 | if (!isset($tag['Key'])) {
32 | throw new \Exception('Tag key is missing');
33 | }
34 | $key = $tag['Key'];
35 | if (strlen($key) > 127) {
36 | throw new \Exception('Keys cannot be longer than 127 characters');
37 | }
38 | if (!preg_match('/^[a-zA-Z0-9_\-+=:\/@\.]{1,127}$/', $key)) {
39 | throw new \Exception("Invalid characters in key '$key'");
40 | }
41 | if (strpos($key, 'aws:') === 0) {
42 | throw new \Exception('The aws: prefix cannot be used for keys');
43 | }
44 |
45 | // value
46 | if (!isset($tag['Value'])) {
47 | throw new \Exception('Tag value is missing');
48 | }
49 | $value = $tag['Value'];
50 | if (strlen($value) > 255) {
51 | throw new \Exception('Values cannot be longer than 255 characters');
52 | }
53 | if (!preg_match('/^[a-zA-Z0-9_\-+=:\/@\.]{1,255}$/', $value)) {
54 | throw new \Exception("Invalid characters in value '$value' (key: $key)");
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/StackFormation/Poller.php:
--------------------------------------------------------------------------------
1 | filepath = $filePath;
18 | $this->cache = new \StackFormation\Helper\Cache();
19 | $this->preProcessor = $preprocessor ? $preprocessor : new Preprocessor();
20 | }
21 |
22 | public function getFilePath()
23 | {
24 | return $this->filepath;
25 | }
26 |
27 | public function getFileContent()
28 | {
29 | return $this->cache->get(
30 | __METHOD__,
31 | function () {
32 | return file_get_contents($this->filepath);
33 | }
34 | );
35 | }
36 |
37 | public function getProcessedTemplate()
38 | {
39 | return $this->cache->get(
40 | __METHOD__,
41 | function () {
42 | return $this->preProcessor->processJson($this->getFileContent(), $this->getBasePath());
43 | }
44 | );
45 | }
46 |
47 | public function getDecodedJson()
48 | {
49 | if (!$this->cache->has(__METHOD__)) {
50 | $templateBody = $this->getProcessedTemplate();
51 | $array = json_decode($templateBody, true);
52 | if (!is_array($array)) {
53 | throw new TemplateDecodeException($this->getFilePath(), sprintf("Error decoding file '%s'", $this->getFilePath()));
54 | }
55 | if ($array['AWSTemplateFormatVersion'] != '2010-09-09') {
56 | throw new TemplateInvalidException($this->getFilePath(), 'Invalid AWSTemplateFormatVersion');
57 | }
58 |
59 | $this->cache->set(__METHOD__, $array);
60 | }
61 |
62 | return $this->cache->get(__METHOD__);
63 | }
64 |
65 | public function getDescription()
66 | {
67 | $data = $this->getDecodedJson();
68 |
69 | return isset($data['Description']) ? $data['Description'] : '';
70 | }
71 |
72 | public function getBasePath()
73 | {
74 | return dirname($this->getFilePath());
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/StackFormation/TemplateDecodeException.php:
--------------------------------------------------------------------------------
1 | templateFile = $templateFile;
13 | }
14 |
15 | public function getTemplateFile()
16 | {
17 | return $this->templateFile;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/StackFormation/TemplateInvalidException.php:
--------------------------------------------------------------------------------
1 | templateFile = $templateFile;
13 | }
14 |
15 | public function getTemplateFile()
16 | {
17 | return $this->templateFile;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/StackFormation/Terminator.php:
--------------------------------------------------------------------------------
1 | currentStack = $stack;
13 | $this->output = $output;
14 | }
15 |
16 | public function setupSignalHandler()
17 | {
18 | $this->output->writeln('Handling signals SIGTERM and SIGINT for stack ' . $this->currentStack->getName());
19 | declare(ticks = 1);
20 | pcntl_signal(SIGTERM, array($this, 'signalHandler')); // Jenkins: aborting a job
21 | pcntl_signal(SIGINT, array($this, 'signalHandler')); // CTRL+C on command line
22 | }
23 |
24 | public function signalHandler($signo)
25 | {
26 | switch ($signo) {
27 | case SIGINT: $this->output->writeln("Caught SIGINT"); break;
28 | case SIGTERM: $this->output->writeln("Caught SIGTERM"); break;
29 | default: $this->output->writeln("Caught $signo"); break;
30 | }
31 | $this->output->writeln("Deleting stack {$this->currentStack->getName()}");
32 | $this->currentStack->delete();
33 | exit;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/AbstractValueResolverStage.php:
--------------------------------------------------------------------------------
1 | valueResolver = $valueResolver;
20 | $this->sourceBlueprint = $sourceBlueprint;
21 | $this->sourceType = $sourceType;
22 | $this->sourceKey = $sourceKey;
23 | }
24 |
25 | public function __invoke($string)
26 | {
27 | try {
28 | return $this->invoke($string);
29 | } catch (\Exception $e) {
30 | throw new \StackFormation\Exception\ValueResolverException($string, $this->sourceBlueprint, $this->sourceType, $this->sourceKey, $e);
31 | }
32 | }
33 |
34 | abstract function invoke($string);
35 |
36 | /**
37 | * Convenience method
38 | *
39 | * @return \StackFormation\StackFactory
40 | */
41 | protected function getStackFactory()
42 | {
43 | return $this->valueResolver->getStackFactory($this->sourceBlueprint);
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/Clean.php:
--------------------------------------------------------------------------------
1 | $value) {
16 | if ($this->isTrue($condition, $this->sourceBlueprint)) {
17 | return $value;
18 | }
19 | }
20 | return '';
21 | }
22 |
23 | /**
24 | * Evaluate is key 'is true'
25 | *
26 | * @param $condition
27 | * @param Blueprint|null $sourceBlueprint
28 | * @return bool
29 | * @throws \Exception
30 | */
31 | public function isTrue($condition, Blueprint $sourceBlueprint=null)
32 | {
33 | // resolve placeholders
34 | $condition = $this->valueResolver->resolvePlaceholders($condition, $sourceBlueprint, 'conditional_value', $condition);
35 |
36 | if ($condition == 'default') {
37 | return true;
38 | }
39 | if (strpos($condition, '==') !== false) {
40 | list($left, $right) = explode('==', $condition, 2);
41 | $left = trim($left);
42 | $right = trim($right);
43 | return ($left == $right);
44 | } elseif (strpos($condition, '!=') !== false) {
45 | list($left, $right) = explode('!=', $condition, 2);
46 | $left = trim($left);
47 | $right = trim($right);
48 | return ($left != $right);
49 | } elseif (strpos($condition, '~=') !== false) {
50 | list($subject, $pattern) = explode('~=', $condition, 2);
51 | $subject = trim($subject);
52 | $pattern = trim($pattern);
53 | return preg_match($pattern, $subject);
54 | }
55 | throw new \Exception('Invalid condition: ' . $condition);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/EnvironmentVariable.php:
--------------------------------------------------------------------------------
1 | valueResolver->getDependencyTracker()->trackEnvUsage($matches[1], false, $value, $this->sourceBlueprint, $this->sourceType, $this->sourceKey);
20 | return getenv($matches[1]);
21 | },
22 | $string
23 | );
24 | return $string;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/EnvironmentVariableWithFallback.php:
--------------------------------------------------------------------------------
1 | valueResolver->getDependencyTracker()->trackEnvUsage($matches[1], true, $value, $this->sourceBlueprint, $this->sourceType, $this->sourceKey);
16 | return $value;
17 | },
18 | $string
19 | );
20 | return $string;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/Md5.php:
--------------------------------------------------------------------------------
1 | sourceBlueprint) {
18 | chdir($this->sourceBlueprint->getBasePath());
19 | }
20 | if (!is_file($file)) {
21 | throw new FileNotFoundException("File '$file' not found.");
22 | }
23 | $md5 = md5_file($file);
24 | chdir($cwd);
25 | return $md5;
26 | },
27 | $string
28 | );
29 | return $string;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/ProfileSwitcher.php:
--------------------------------------------------------------------------------
1 | valueResolver->getDependencyTracker(),
21 | $this->valueResolver->getProfileManager(),
22 | $this->valueResolver->getConfig(),
23 | $matches[1]
24 | );
25 | $value = $subValueResolver->resolvePlaceholders($matches[2], $this->sourceBlueprint, $this->sourceType, $this->sourceKey);
26 |
27 | // restoring AWS_UNSET_PROFILE value if it was set before
28 | if ($unsetProfileBackup) {
29 | putenv('AWS_UNSET_PROFILE=1');
30 | }
31 | return $value;
32 | },
33 | $string
34 | );
35 | return $string;
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/StackOutput.php:
--------------------------------------------------------------------------------
1 | valueResolver->getDependencyTracker()->trackStackDependency('output', $matches[1], $matches[2], $this->sourceBlueprint, $this->sourceType, $this->sourceKey);
16 | return $this->getStackFactory()->getStackOutput($matches[1], $matches[2]);
17 | },
18 | $string
19 | );
20 | return $string;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/StackParameter.php:
--------------------------------------------------------------------------------
1 | valueResolver->getDependencyTracker()->trackStackDependency('parameter', $matches[1], $matches[2], $this->sourceBlueprint, $this->sourceType, $this->sourceKey);
16 | return $this->getStackFactory()->getStackParameter($matches[1], $matches[2]);
17 | },
18 | $string
19 | );
20 | return $string;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/StackResource.php:
--------------------------------------------------------------------------------
1 | valueResolver->getDependencyTracker()->trackStackDependency('resource', $matches[1], $matches[2], $this->sourceBlueprint, $this->sourceType, $this->sourceKey);
16 | return $this->getStackFactory()->getStackResource($matches[1], $matches[2]);
17 | },
18 | $string
19 | );
20 | return $string;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/StackFormation/ValueResolver/Stage/Tstamp.php:
--------------------------------------------------------------------------------
1 | valueResolver->getConfig()->getGlobalVars();
15 | if ($this->sourceBlueprint) {
16 | $vars = array_merge($vars, $this->sourceBlueprint->getVars());
17 | }
18 | if (!isset($vars[$matches[1]])) {
19 | throw new \Exception("Variable '{$matches[1]}' not found");
20 | }
21 | $value = $vars[$matches[1]];
22 | if (is_array($value)) {
23 | $value = $this->valueResolver->resolvePlaceholders($value, $this->sourceBlueprint);
24 | }
25 | if ($value == $matches[0]) {
26 | throw new \Exception('Direct circular reference detected');
27 | }
28 | return $value;
29 | },
30 | $string
31 | );
32 | return $string;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/dotenv.php:
--------------------------------------------------------------------------------
1 | overload();
9 | }
10 | if (is_readable(CWD . DIRECTORY_SEPARATOR . '.env')) {
11 | $dotenv = new Dotenv\Dotenv(CWD);
12 | $dotenv->overload();
13 | }
14 | }
15 |
16 | register_shutdown_function(function() {
17 | \AwsInspector\Ssh\Connection::closeMuxConnections();
18 | });
19 |
--------------------------------------------------------------------------------
/src/stackformation.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | add($command);
17 | }
18 | foreach (\AwsInspector\CommandRegistry::getCommands() as $command) {
19 | $app->add($command);
20 | }
21 |
22 | $app->run();
23 |
--------------------------------------------------------------------------------
/tests/AwsInspector/Model/CollectionTest.php:
--------------------------------------------------------------------------------
1 | title = 'StackFormation';
16 | $collection->attach($a);
17 |
18 | $b = new \stdClass();
19 | $b->title = 'WhateverElse';
20 | $collection->attach($b);
21 |
22 | $collection->next();
23 |
24 | $this->assertSame($a, $collection->getFirst());
25 | $this->assertSame('StackFormation', $collection->getFirst()->title);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/AwsInspector/Model/Ec2/InstanceTest.php:
--------------------------------------------------------------------------------
1 | [],
17 | 'PrivateIpAddress' => '4.5.7.6'
18 | ]);
19 | $connection = $instance->getSshConnection();
20 | $this->assertEquals('4.5.7.6', $connection->getHost());
21 | $this->assertEquals('ec2-user', $connection->getUsername());
22 | }
23 |
24 | /**
25 | * @test
26 | */
27 | public function user()
28 | {
29 | $instance = new Instance([
30 | 'Tags' => [['Key' => 'inspector', 'Value' => 'User:Foo']],
31 | 'PrivateIpAddress' => '4.5.7.6',
32 | ]);
33 | $this->assertEquals('User:Foo', $instance->getTag('inspector'));
34 | $connection = $instance->getSshConnection();
35 | $this->assertEquals('4.5.7.6', $connection->getHost());
36 | $this->assertEquals('Foo', $connection->getUsername());
37 | }
38 |
39 | /**
40 | * @test
41 | */
42 | public function sshConnectionTestViaBastion()
43 | {
44 | $instance = $this->getMock('AwsInspector\Model\Ec2\Instance', ['getJumpHost'], [[
45 | 'Tags' => [
46 | ['Key' => 'inspector', 'Value' => 'User:Foo,Type:Bastion,Environment:dpl']
47 | ],
48 | 'PrivateIpAddress' => '4.5.7.6',
49 | ]]);
50 | $instance->method('getJumpHost')->willReturn(new Instance([
51 | 'Tags' => [['Key' => 'inspector', 'Value' => 'User:Bar']],
52 | 'PrivateIpAddress' => '1.2.3.4',
53 | ]));
54 |
55 | /* @var $instance Instance */
56 | $this->assertEquals('User:Foo,Type:Bastion,Environment:dpl', $instance->getTag('inspector'));
57 |
58 | $connection = $instance->getSshConnection();
59 | $this->assertEquals('4.5.7.6', $connection->getHost());
60 | $this->assertEquals('Foo', $connection->getUsername());
61 |
62 | $proxyCommand = "ssh -o ConnectTimeout=5 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null Bar@1.2.3.4 'nc %h %p'";
63 | $command = 'ssh -o ProxyCommand="'.$proxyCommand.'" -o ConnectTimeout=5 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null Foo@4.5.7.6';
64 |
65 | $this->assertEquals($command, $connection->__toString());
66 | }
67 |
68 | public function exec()
69 | {
70 | $instance = new Instance([
71 | 'Tags' => [],
72 | 'PrivateIpAddress' => '1.2.3.4',
73 | ]);
74 |
75 | $instance->exec();
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/tests/AwsInspector/RegistryTest.php:
--------------------------------------------------------------------------------
1 | assertSame($expectedValue, $value);
16 | }
17 |
18 | /**
19 | * @test
20 | */
21 | public function getReturnsExpectedObject()
22 | {
23 | $object = new \stdClass();
24 | $object->value = 'My String';
25 | \AwsInspector\Registry::set('key_phpunit_getReturnsExpectedObject', $object);
26 | $value = \AwsInspector\Registry::get('key_phpunit_getReturnsExpectedObject');
27 | $this->assertTrue($value instanceof \stdClass);
28 | $this->assertSame($object->value, $value->value);
29 | }
30 |
31 | /**
32 | * @test
33 | */
34 | public function getReturnsFalseIfExpectedKeyIsNotAvailable()
35 | {
36 | $value = \AwsInspector\Registry::get('key_phpunit_getReturnsFalseIfExpectedKeyIsNotAvailable');
37 | $this->assertFalse($value);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/AwsInspector/Ssh/CommandTest.php:
--------------------------------------------------------------------------------
1 | getMock('\AwsInspector\Ssh\Connection', [], [], '', false);
17 | $connectionMock->method('__toString')->willReturn('connection');
18 | $command = new Command($connectionMock, 'command');
19 | $this->assertEquals("connection 'command'", $command->__toString());
20 | }
21 |
22 | /**
23 | * @test
24 | */
25 | public function commandWithArguments()
26 | {
27 | $connectionMock = $this->getMock('\AwsInspector\Ssh\Connection', [], [], '', false);
28 | $connectionMock->method('__toString')->willReturn('connection');
29 | $command = new Command($connectionMock, 'echo '.escapeshellarg('Hello World'));
30 | $this->assertEquals("connection 'echo '\''Hello World'\'''", $command->__toString());
31 | }
32 |
33 | /**
34 | * @test
35 | */
36 | public function runLocalCommand()
37 | {
38 | $testfile = tempnam(sys_get_temp_dir(), __FUNCTION__);
39 | $command = new Command(new LocalConnection(), 'echo -n '.escapeshellarg('Hello World') . ' > ' . $testfile);
40 | $this->assertEquals("echo -n 'Hello World' > $testfile", $command->__toString());
41 | $command->exec();
42 | $this->assertEquals('Hello World', file_get_contents($testfile));
43 | unlink($testfile);
44 | }
45 |
46 | /**
47 | * @test
48 | */
49 | public function asUser()
50 | {
51 | $connectionMock = $this->getMock('\AwsInspector\Ssh\Connection', [], [], '', false);
52 | $connectionMock->method('__toString')->willReturn('connection');
53 | $command = new Command($connectionMock, 'whoami', 'www-data');
54 | $this->assertEquals(
55 | "connection 'sudo -u '\''www-data'\'' bash -c '\''whoami'\'''",
56 | $command->__toString()
57 | );
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function runLocalCommandasUser()
64 | {
65 | try {
66 | $testfile = tempnam(sys_get_temp_dir(), __FUNCTION__);
67 | $command = new Command(new LocalConnection(), 'whoami > ' . $testfile, 'root');
68 | $command->exec();
69 | $this->assertEquals('root', trim(file_get_contents($testfile)));
70 | unlink($testfile);
71 | } catch (\Exception $e) {
72 | $this->markTestSkipped('It is ok if this test fails');
73 | }
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/tests/AwsInspector/Ssh/ConnectionTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(
19 | 'ssh -o ConnectTimeout=5 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null TestUsername@1.2.3.4',
20 | $connection->__toString()
21 | );
22 | }
23 |
24 | /**
25 | * @test
26 | */
27 | public function withPrivateKey()
28 | {
29 | $connection = new Connection('TestUsername', '1.2.3.4', PrivateKey::get(FIXTURE_ROOT . 'foo.pem'));
30 | $this->assertEquals(
31 | 'ssh -i '.FIXTURE_ROOT.'foo.pem -o ConnectTimeout=5 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null TestUsername@1.2.3.4',
32 | $connection->__toString()
33 | );
34 | }
35 |
36 | /**
37 | * @test
38 | */
39 | public function withJumpHost()
40 | {
41 | $jumpHost = new Instance([
42 | 'Tags' => [],
43 | 'PrivateIpAddress' => '4.5.7.6'
44 | ]);
45 | $connection = new Connection('TestUsername', '1.2.3.4', null, $jumpHost);
46 | $this->assertEquals(
47 | 'ssh -o ProxyCommand="ssh -o ConnectTimeout=5 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ec2-user@4.5.7.6 \'nc %h %p\'" -o ConnectTimeout=5 -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null TestUsername@1.2.3.4',
48 | $connection->__toString()
49 | );
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/tests/StackFormation/CacheTest.php:
--------------------------------------------------------------------------------
1 | cache = new \StackFormation\Helper\Cache();
13 | }
14 |
15 | /**
16 | * @throws Exception
17 | * @test
18 | */
19 | public function storeInCache()
20 | {
21 | $result = $this->cache->has('test');
22 | $this->assertFalse($result);
23 |
24 | $this->cache->set('test', '42');
25 | $result = $this->cache->has('test');
26 | $this->assertTrue($result);
27 |
28 | $result = $this->cache->get('test');
29 | $this->assertEquals('42', $result);
30 |
31 | $this->cache->delete('test');
32 | $result = $this->cache->has('test');
33 |
34 | $this->assertFalse($result);
35 | }
36 |
37 | /**
38 | * @throws Exception
39 | * @test
40 | */
41 | public function storeInCacheWithCallback()
42 | {
43 | $result = $this->cache->has('test');
44 | $this->assertFalse($result);
45 |
46 | $result = $this->cache->get('test', function() {
47 | return '42';
48 | });
49 | $this->assertEquals('42', $result);
50 |
51 | $result = $this->cache->get('test');
52 | $this->assertEquals('42', $result);
53 |
54 | $this->cache->delete('test');
55 | $result = $this->cache->has('test');
56 |
57 | $this->assertFalse($result);
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function keyNotFound()
64 | {
65 | $this->setExpectedException('Exception', "Cache key 'doesnotexist' not found.");
66 | $this->cache->get('doesnotexist');
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/StackFormation/ConfigTest.php:
--------------------------------------------------------------------------------
1 | setExpectedException('Exception', "Stackname 'a' does not specify a template.");
13 | $config = new \StackFormation\Config([FIXTURE_ROOT . '/Config/blueprint.template_missing.yml']);
14 | }
15 |
16 | /**
17 | * @test
18 | */
19 | public function duplicateStackName()
20 | {
21 | $this->setExpectedException('Exception', "Stackname 'a' was declared more than once.");
22 | $config = new \StackFormation\Config([FIXTURE_ROOT . '/Config/blueprint.duplicate_stackname.yml']);
23 | }
24 |
25 | /**
26 | * @test
27 | */
28 | public function globalVariable()
29 | {
30 | $config = new \StackFormation\Config([FIXTURE_ROOT . '/Config/blueprint.1.yml']);
31 | $globalVars = $config->getGlobalVars();
32 | $this->assertArrayHasKey('GlobalFoo', $globalVars);
33 | $this->assertEquals('GlobalBar', $globalVars['GlobalFoo']);
34 | }
35 |
36 | /**
37 | * @test
38 | */
39 | public function getBlueprints()
40 | {
41 | $config = new \StackFormation\Config([FIXTURE_ROOT . '/Config/blueprint.1.yml']);
42 | $this->assertTrue($config->blueprintExists('fixture1'));
43 | $this->assertTrue($config->blueprintExists('fixture2'));
44 | $names = $config->getBlueprintNames();
45 | $this->assertTrue(in_array('fixture1', $names));
46 | $this->assertTrue(in_array('fixture2', $names));
47 | }
48 |
49 | /**
50 | * @test
51 | */
52 | public function invalidAttribute()
53 | {
54 | $this->setExpectedException(
55 | '\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException',
56 | 'Unrecognized option "parameter" under "root.blueprints.invalid_attribute"'
57 | );
58 | $config = new \StackFormation\Config([FIXTURE_ROOT . '/Config/blueprint.invalid_attribute.yml']);
59 | }
60 |
61 | //public function testDuplicateGlobalVar()
62 | //{
63 | // $this->markTestSkipped('This is not so trivial :)');
64 | // $this->setExpectedException('Exception');
65 | // $config = new \StackFormation\Config([FIXTURE_ROOT . '/Config/blueprint.duplicateglobalvar.yml']);
66 | // $vars = $config->getGlobalVars();
67 | //}
68 | }
69 |
--------------------------------------------------------------------------------
/tests/StackFormation/HelperTest.php:
--------------------------------------------------------------------------------
1 | setExpectedException('Exception');
42 | Validator::validateStackname($stackName);
43 | }
44 |
45 | /**
46 | * @return array
47 | */
48 | public function invalidStackNameProvider()
49 | {
50 | return [
51 | [new \stdClass()],
52 | [ []],
53 | [ null ],
54 | [ 42 ],
55 | ['ecom_t_stack'],
56 | [''],
57 | ['stackname with whitespace'],
58 | ['123ecom'],
59 | [str_repeat('a', 129)],
60 | ];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/StackFormation/PollerTest.php:
--------------------------------------------------------------------------------
1 | counters[__METHOD__])) { $this->counters[__METHOD__] = 0; }
19 | return $this->counters[__METHOD__]++ > 5;
20 | };
21 | \StackFormation\Poller::poll($function, 0);
22 | }
23 |
24 | /**
25 | * @test
26 | */
27 | public function pollExceed()
28 | {
29 | $this->setExpectedException('Exception', 'Max polls exceeded.');
30 | $function = function() {
31 | if (!isset($this->counters[__METHOD__])) { $this->counters[__METHOD__] = 0; }
32 | return $this->counters[__METHOD__]++ > 51;
33 | };
34 | \StackFormation\Poller::poll($function, 0);
35 | }
36 |
37 | /**
38 | * @test
39 | */
40 | public function returnValueFromCallback()
41 | {
42 | $function = function() {
43 | if (!isset($this->counters[__METHOD__])) { $this->counters[__METHOD__] = 0; }
44 | return ($this->counters[__METHOD__]++ > 5) ? "HELLO WORLD" : false;
45 | };
46 | $result = \StackFormation\Poller::poll($function, 0);
47 | $this->assertEquals("HELLO WORLD", $result);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/StackFormation/PreprocessorTest.php:
--------------------------------------------------------------------------------
1 | preprocessor = new \StackFormation\Preprocessor();
16 | }
17 |
18 | /**
19 | * @param string $fixtureDirectory
20 | * @throws \Exception
21 | * @test
22 | * @dataProvider processFileDataProvider
23 | */
24 | public function processFile($fixtureDirectory)
25 | {
26 | $prefix = FIXTURE_ROOT . 'Preprocessor/';
27 | $prefix .= $fixtureDirectory . '/';
28 | $templatePath = $prefix . 'blueprint/input.template';
29 | $fileContent = file_get_contents($templatePath);
30 | $this->assertEquals(
31 | $this->preprocessor->processJson($fileContent, dirname($templatePath)),
32 | file_get_contents($prefix. 'blueprint/expected.template')
33 | );
34 | }
35 |
36 | public function processFileDataProvider()
37 | {
38 | $prefix = FIXTURE_ROOT . 'Preprocessor/';
39 | $directories = glob($prefix.'*', GLOB_ONLYDIR);
40 | array_walk($directories, function(&$directory) use ($prefix) {
41 | $directory = [ str_replace($prefix, '', $directory)];
42 | });
43 | return $directories;
44 | }
45 |
46 | /**
47 | * @param string $inputJson
48 | * @param string $expectedJson
49 | * @throws \Exception
50 | * @test
51 | * @dataProvider processJsonDataProvider
52 | */
53 | public function processJson($inputJson, $expectedJson)
54 | {
55 | $this->assertEquals(
56 | $this->preprocessor->processJson($inputJson, sys_get_temp_dir()),
57 | $expectedJson
58 | );
59 | }
60 |
61 | /**
62 | * @return array
63 | */
64 | public function processJsonDataProvider()
65 | {
66 | return [
67 | // strip comments
68 | ['Hello World /* Comment */', 'Hello World '],
69 | ['Hello World /* Comment */ Hello World', 'Hello World Hello World'],
70 | ['/* Comment */ Hello World', ' Hello World'],
71 | // support single quotes
72 | ["Hello World /* 'Comment' */ Hello World", 'Hello World Hello World'],
73 | // ignore double quotes
74 | ['Hello World /* "Comment" */', 'Hello World /* "Comment" */'],
75 | ['Hello World /* "Comment */', 'Hello World /* "Comment */'],
76 | // multi-line
77 | ["Hello World /* Multiline\nComment */ Hello World", 'Hello World Hello World'],
78 | // parseRefInDoubleQuotedStrings
79 | ['"Key": "Name", "Value": "magento-{Ref:Environment}-{Ref:Build}-instance"', '"Key": "Name", "Value": {"Fn::Join": ["", ["magento-", {"Ref":"Environment"}, "-", {"Ref":"Build"}, "-instance"]]}'],
80 | // expandPort
81 | ['{"IpProtocol": "tcp", "Port": "80", "CidrIp": "1.2.3.4/32"},', '{"IpProtocol": "tcp", "FromPort": "80", "ToPort": "80", "CidrIp": "1.2.3.4/32"},'],
82 | // replace ref
83 | ["WAIT_CONDITION_HANDLE='{Ref:WaitConditionHandle}'", "WAIT_CONDITION_HANDLE='\", {\"Ref\": \"WaitConditionHandle\"}, \"'"],
84 | ["REGION='{Ref:AWS::Region}'", "REGION='\", {\"Ref\": \"AWS::Region\"}, \"'"],
85 | ['"Aliases": { "Fn::Split": [",", "a,b,c"] }', '"Aliases": ["a", "b", "c"]'],
86 | ['"Aliases": { "Fn::Split": ["+", "a,b,c"] }', '"Aliases": ["a,b,c"]'],
87 | ['"Aliases": { "Fn::Split": ["+", "a+b+c"] }', '"Aliases": ["a", "b", "c"]'],
88 | ];
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/tests/StackFormation/StackTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | protected $cfnClientMock;
16 |
17 | public function setUp()
18 | {
19 | $describeStackResourcesResult = new \Aws\Result(json_decode(file_get_contents(FIXTURE_ROOT.'Stack/test-stack1.describeStackResources.json'), true));
20 |
21 | $this->cfnClientMock = $this->getMock('\Aws\CloudFormation\CloudFormationClient', ['describeStackResources'], [], '', false);
22 | $this->cfnClientMock
23 | ->method('describeStackResources')
24 | ->with(['StackName' => 'test-stack1'])
25 | ->willReturn($describeStackResourcesResult);
26 |
27 | $data = file_get_contents(FIXTURE_ROOT.'Stack/test-stack1.json');
28 | $data = json_decode($data, true);
29 | $this->stack = new \StackFormation\Stack($data, $this->cfnClientMock);
30 | }
31 |
32 | public function testName()
33 | {
34 | $this->assertEquals('test-stack1', $this->stack->getName());
35 | }
36 |
37 | public function testDescription()
38 | {
39 | $this->assertEquals('Test Description', $this->stack->getDescription());
40 | }
41 |
42 | public function testStatus()
43 | {
44 | $this->assertEquals('UPDATE_COMPLETE', $this->stack->getStatus());
45 | }
46 |
47 | public function testParameter()
48 | {
49 | $this->assertEquals('Bar', $this->stack->getParameter('Foo'));
50 | }
51 |
52 | public function testParameterThatDoesntExist()
53 | {
54 | $this->setExpectedException('Exception', "Parameter 'DoesNotExist' not found in stack 'test-stack1'");
55 | $this->stack->getParameter('DoesNotExist');
56 | }
57 |
58 | public function testTag()
59 | {
60 | $this->assertEquals('BarTag', $this->stack->getTag('FooTag'));
61 | }
62 |
63 | public function testTagThatDoesntExist()
64 | {
65 | $this->setExpectedException('Exception', "Tag 'DoesNotExist' not found in stack 'test-stack1'");
66 | $this->stack->getTag('DoesNotExist');
67 | }
68 |
69 | public function testOutput()
70 | {
71 | $this->assertEquals('OutputBar', $this->stack->getOutput('OutputFoo'));
72 | }
73 |
74 | public function testOutputThatDoesntExist()
75 | {
76 | $this->setExpectedException('Exception', "Output 'DoesNotExist' not found in stack 'test-stack1'");
77 | $this->stack->getOutput('DoesNotExist');
78 | }
79 |
80 | public function testResource()
81 | {
82 | $this->assertEquals('sg-de4494b8', $this->stack->getResource('InstanceSg'));
83 | }
84 |
85 | public function testResourceThatDoesntExist()
86 | {
87 | $this->setExpectedException('Exception', "Resource 'DoesNotExist' not found in stack 'test-stack1'");
88 | $this->stack->getResource('DoesNotExist');
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/tests/StackFormation/ValidateTagsTest.php:
--------------------------------------------------------------------------------
1 | 'Name', 'Value' => 'Bar']],
27 | [['Key' => str_repeat('A', 127), 'Value' => 'Bar']],
28 | [['Key' => 'Name', 'Value' => str_repeat('A', 255)]],
29 | [['Key' => 'Name', 'Value' => base64_encode('&*#,!')]],
30 | ];
31 | }
32 |
33 | /**
34 | * @param array $tag
35 | * @throws \Exception
36 | * @test
37 | * @dataProvider invalidTagsProvider
38 | */
39 | public function invalidTag(array $tag)
40 | {
41 | $this->setExpectedException('Exception');
42 | Validator::validateTags([$tag]);
43 | }
44 |
45 | /**
46 | * @return array
47 | */
48 | public function invalidTagsProvider()
49 | {
50 | return [
51 | [['Key' => 'aws:Name', 'Value' => 'Bar']],
52 | [['Name' => 'Foo', 'Value' => 'Bar']],
53 | [['Key' => 'Na,me', 'Value' => 'Bar']],
54 | [['Key' => 'Name', 'Value' => 'Ba,r']],
55 | [['Key' => str_repeat('A', 128), 'Value' => 'Bar']],
56 | [['Key' => 'Name', 'Value' => str_repeat('A', 256)]],
57 | ];
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function valueIsMissing()
64 | {
65 | $this->setExpectedException('Exception', 'Tag value is missing');
66 | Validator::validateTags([['Key' => 'foo']]);
67 | }
68 |
69 | /**
70 | * @test
71 | */
72 | public function keyIsMissing()
73 | {
74 | $this->setExpectedException('Exception', 'Tag key is missing');
75 | Validator::validateTags([['Value' => 'foo']]);
76 | }
77 |
78 | /**
79 | * @test
80 | */
81 | public function moreThanTenTags()
82 | {
83 | $this->setExpectedException('Exception', 'No more than 10 tags are allowed');
84 | $tags = [];
85 | for ($i=0; $i<11; $i++) {
86 | $tags[] = ['Key' => "Key$i", 'Value' => "Value$i"];
87 | }
88 | Validator::validateTags($tags);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.1.yml:
--------------------------------------------------------------------------------
1 | vars:
2 | GlobalFoo: GlobalBar
3 |
4 | blueprints:
5 |
6 | - stackname: 'fixture1'
7 | template: 'dummy.template'
8 | vars:
9 | BlueprintFoo: 'BlueprintBar'
10 | parameters:
11 | ParamFoo: ParamBar
12 | tags:
13 | TagFoo: TagBar
14 |
15 | - stackname: 'fixture2'
16 | template: 'dummy.template'
17 |
18 | - stackname: 'fixture3'
19 | template: 'dummy.template'
20 | tags:
21 | TagFoo: '{env:Foo}'
22 |
23 | - stackname: 'fixture4'
24 | template: 'dummy.template'
25 | Capabilities: 'FOO'
26 |
27 | - stackname: 'fixture5'
28 | template: 'dummy.template'
29 | Capabilities: 'FOO,BAR'
30 |
31 | - stackname: 'fixture6'
32 | template: 'dummy.template'
33 | before:
34 | - 'echo -n "HELLO WORLD" > {env:TESTFILE}'
35 |
36 | - stackname: 'fixture7'
37 | template: 'dummy.template'
38 | stackPolicy: 'dummy_policy.json'
39 |
40 | - stackname: 'fixture8'
41 | template: 'dummy.template'
42 | profile: 'before_scripts_profile'
43 | before:
44 | - 'echo -n "${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}" > {env:TESTFILE}'
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.conditional_value.yml:
--------------------------------------------------------------------------------
1 | blueprints:
2 |
3 | - stackname: 'conditional_value'
4 | template: 'dummy.template'
5 | parameters:
6 | CondValue:
7 | '{env:Foo}==Val1': a
8 | '{env:Foo}==Val2': b
9 | 'default': c
10 |
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.conditional_vars.yml:
--------------------------------------------------------------------------------
1 | vars:
2 |
3 | FooVar:
4 | '{env:Foo}==Val1': a
5 | '{env:Foo}==Val2': b
6 | 'default': c
7 |
8 | blueprints:
9 |
10 | - stackname: 'fixture_var_conditional_global'
11 | template: 'dummy.template'
12 | parameters:
13 | Parameter1: '{var:FooVar}'
14 |
15 | - stackname: 'fixture_var_conditional_local'
16 | template: 'dummy.template'
17 | vars:
18 | LocalFooVar:
19 | '{env:Foo}==Val1': a
20 | '{env:Foo}==Val2': b
21 | 'default': c
22 | parameters:
23 | Parameter1: '{var:LocalFooVar}'
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.duplicate_stackname.yml:
--------------------------------------------------------------------------------
1 | blueprints:
2 |
3 | - stackname: 'a'
4 | template: 'dummy.template'
5 | - stackname: 'b'
6 | template: 'dummy.template'
7 | - stackname: 'a'
8 | template: 'dummy.template'
9 |
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.duplicateglobalvar.yml:
--------------------------------------------------------------------------------
1 | vars:
2 | GlobalFoo: GlobalBar
3 | GlobalFoo: GlobalBar2
4 |
5 | blueprints:
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.invalid_attribute.yml:
--------------------------------------------------------------------------------
1 | blueprints:
2 |
3 | - stackname: 'invalid_attribute'
4 | template: 'dummy.template'
5 | # this is 'parameters' not 'parameter'
6 | parameter:
7 | Foo: Bar
8 |
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.reference.yml:
--------------------------------------------------------------------------------
1 | blueprints:
2 |
3 | - stackname: 'reference-fixture-{env:FOO1}'
4 | template: 'dummy.template'
5 | vars:
6 | MyVar: '{env:FOO2}'
7 | parameters:
8 | MyParam: '{env:FOO3}'
9 | MyParam2: 'prefix{var:MyVar}'
10 |
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.select_profile.yml:
--------------------------------------------------------------------------------
1 | blueprints:
2 |
3 | - stackname: 'fixture_selectprofile'
4 | template: 'dummy.template'
5 | profile: 'myprofile'
6 |
7 | - stackname: 'fixture_selectprofile_conditional'
8 | template: 'dummy.template'
9 | profile:
10 | '{env:Foo}==Val1': a
11 | '{env:Foo}==Val2': b
12 | 'default': c
13 |
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.switch_profile.yml:
--------------------------------------------------------------------------------
1 | blueprints:
2 |
3 | - stackname: 'switch_profile'
4 | template: 'dummy.template'
5 | profile: 'myprofile1'
6 | parameters:
7 | Foo1: 'Bar1'
8 | Foo2: '[profile:myprofile2:{output:remoteStack:fooField}]'
9 | Foo3: '{output:localStack:fooField}'
10 |
11 | - stackname: 'switch_profile_complex'
12 | template: 'dummy.template'
13 | profile: 'myprofile1'
14 | parameters:
15 | VarnishAmi: '[profile:t.deploy-ecom:{output:ecom-{env:ACCOUNT}-all-ami-types-{env:BASE_TYPE_VERSION}-stack:VarnishAmi}]'
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.template_missing.yml:
--------------------------------------------------------------------------------
1 | blueprints:
2 |
3 | - stackname: 'a'
4 |
5 |
--------------------------------------------------------------------------------
/tests/fixtures/Config/blueprint.templatefile_missing.yml:
--------------------------------------------------------------------------------
1 | blueprints:
2 |
3 | - stackname: 'a'
4 | template: 'doesnotexist.template'
5 |
6 |
--------------------------------------------------------------------------------
/tests/fixtures/Config/dummy.template:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "",
4 | "Resources": {
5 | "MyResource": {
6 | "Type": "AWS::CloudFormation::WaitConditionHandle"
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/tests/fixtures/Config/dummy_policy.json:
--------------------------------------------------------------------------------
1 | {
2 | "Statement" : [
3 | {
4 | "Effect" : "Allow",
5 | "Action" : "Update:Delete",
6 | "Principal": "*",
7 | "Resource" : "*"
8 | }
9 | ]
10 | }
--------------------------------------------------------------------------------
/tests/fixtures/Preprocessor/injectFileContentFnFileContentFromParentDirectory/blueprint/expected.template:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "LaunchConfiguration": {
4 | "Properties": {
5 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [
6 | "#!\/usr\/bin\/env bash\n",
7 | "echo \"Hello World\"\n"
8 | ]]}}
9 | }
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/tests/fixtures/Preprocessor/injectFileContentFnFileContentFromParentDirectory/blueprint/input.template:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "LaunchConfiguration": {
4 | "Properties": {
5 | "UserData": {"Fn::Base64": {"Fn::FileContent": "../userdata.sh"}}
6 | }
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/tests/fixtures/Preprocessor/injectFileContentFnFileContentFromParentDirectory/userdata.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "Hello World"
--------------------------------------------------------------------------------
/tests/fixtures/Preprocessor/injectFileContentFnFileContentFromSameDirectory/blueprint/expected.template:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "LaunchConfiguration": {
4 | "Properties": {
5 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [
6 | "#!\/usr\/bin\/env bash\n",
7 | "echo \"Hello World\"\n"
8 | ]]}}
9 | }
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/tests/fixtures/Preprocessor/injectFileContentFnFileContentFromSameDirectory/blueprint/input.template:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "LaunchConfiguration": {
4 | "Properties": {
5 | "UserData": {"Fn::Base64": {"Fn::FileContent": "userdata.sh"}}
6 | }
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/tests/fixtures/Preprocessor/injectFileContentFnFileContentFromSameDirectory/blueprint/userdata.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "Hello World"
--------------------------------------------------------------------------------
/tests/fixtures/Preprocessor/injectFileContentFnFileContentFromSubDirectory/blueprint/expected.template:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "LaunchConfiguration": {
4 | "Properties": {
5 | "UserData": {"Fn::Base64": {"Fn::Join": ["", [
6 | "#!\/usr\/bin\/env bash\n",
7 | "echo \"Hello World\"\n"
8 | ]]}}
9 | }
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/tests/fixtures/Preprocessor/injectFileContentFnFileContentFromSubDirectory/blueprint/input.template:
--------------------------------------------------------------------------------
1 | {
2 | "Resources": {
3 | "LaunchConfiguration": {
4 | "Properties": {
5 | "UserData": {"Fn::Base64": {"Fn::FileContent": "userdata/userdata.sh"}}
6 | }
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/tests/fixtures/Preprocessor/injectFileContentFnFileContentFromSubDirectory/blueprint/userdata/userdata.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo "Hello World"
--------------------------------------------------------------------------------
/tests/fixtures/ProfileManager/fixture_basic/profiles.yml:
--------------------------------------------------------------------------------
1 | profiles:
2 | test1:
3 | region: 'us-east-1'
4 | access_key: 'TESTACCESSKEY1'
5 | secret_key: 'TESTSECRETKEY1'
6 | test2:
7 | region: 'us-east-1'
8 | access_key: 'TESTACCESSKEY2'
9 | secret_key: 'TESTSECRETKEY2'
10 | test3:
11 | region: 'us-east-1'
12 | access_key: 'TESTACCESSKEY3'
13 | secret_key: 'TESTSECRETKEY3'
14 | assume_role: 'arn:aws:iam::0123456789012:role/p-magento-iam-DeployRole'
15 |
--------------------------------------------------------------------------------
/tests/fixtures/ProfileManager/fixture_before_scripts/profiles.yml:
--------------------------------------------------------------------------------
1 | profiles:
2 | before_scripts_profile:
3 | region: 'us-east-1'
4 | access_key: 'TESTACCESSKEY1'
5 | secret_key: 'TESTSECRETKEY1'
--------------------------------------------------------------------------------
/tests/fixtures/ProfileManager/fixture_encrpyted/profiles.yml.encrypted:
--------------------------------------------------------------------------------
1 | MHxuL0t6ZEVJV3NHdlhhZEFhVTBMM3lDZmQ2QWJuaEtqZHBUVEVJTksxQjZzL2J2TEhFMXJ2SnZyUytmTkh0ZXJoRjVEQzI2TmtQUmZDcURkQXhrbk9oaXBIVzdoTnZEcm13TmFBUHlLUTZDL1dlWlZKWlMxVHgrSmpZUmlIT2JVckZsR2xzNnl4b3F0R2VCQlAxY0dwSUxlVmJ3QUczSDVxek5ZNlBCSHRTbWJTaXBBcjJXYldoNHNOSHpEWkk2WXQyRWhYdWJ5aTBFekhMM0RsR3daUzRZd3h2MmJhQXZHbTNob1g2Mk5xaGMvTEV4RjlZUURIU3BEMUJrbWlJcHhDS3oydEV0YXE0dmc2VjlFVkMrQjBYMDd2elVYa0RZZFVuMmpkb0Z1Yzg3My8xbFZkTDFjSHp0OXFvTE1VNFJ3NTR5QVRtOW01TVBnOVh4Uk16OWFKSnVHQzJGSjhnK3hDeWFTQUFSMkEzMGpaZzE1aEhwYzN5NnNWbnJ0MWJoZTZwRjJFV203dzJnMkZva0hUMDZBR1BWTkQxZEVLanhnaTRHQnNxa2pHWDBlNm5HUFZFb3ptc0VqTHAzMlhsWFNFb2tSMmlRSjN3bkZQa0QvbzVYVGRwOFFLTFdxdlNtcEc2Rm4wRDFUSlVFekVYNU53ZDdpWEs0V3RRT3dad3Y0a3Q0cERSTEVLN3U5ZjJtR3BpMkI3V01GQVdvazlGWFU9fDl3TmJPdWkwY01HQm50TmRGZ0Y1NVE9PQ==
--------------------------------------------------------------------------------
/tests/fixtures/ProfileManager/fixture_encrpyted_mix/profiles.personal.yml:
--------------------------------------------------------------------------------
1 | profiles:
2 | test1-personal:
3 | region: 'us-east-1'
4 | access_key: 'TESTPERSONALACCESSKEY1'
5 | secret_key: 'TESTPERSONALSECRETKEY1'
6 | test2-personal:
7 | region: 'us-east-1'
8 | access_key: 'TESTPERSONALACCESSKEY2'
9 | secret_key: 'TESTPERSONALSECRETKEY2'
--------------------------------------------------------------------------------
/tests/fixtures/ProfileManager/fixture_encrpyted_mix/profiles.yml.encrypted:
--------------------------------------------------------------------------------
1 | MHxuL0t6ZEVJV3NHdlhhZEFhVTBMM3lDZmQ2QWJuaEtqZHBUVEVJTksxQjZzL2J2TEhFMXJ2SnZyUytmTkh0ZXJoRjVEQzI2TmtQUmZDcURkQXhrbk9oaXBIVzdoTnZEcm13TmFBUHlLUTZDL1dlWlZKWlMxVHgrSmpZUmlIT2JVckZsR2xzNnl4b3F0R2VCQlAxY0dwSUxlVmJ3QUczSDVxek5ZNlBCSHRTbWJTaXBBcjJXYldoNHNOSHpEWkk2WXQyRWhYdWJ5aTBFekhMM0RsR3daUzRZd3h2MmJhQXZHbTNob1g2Mk5xaGMvTEV4RjlZUURIU3BEMUJrbWlJcHhDS3oydEV0YXE0dmc2VjlFVkMrQjBYMDd2elVYa0RZZFVuMmpkb0Z1Yzg3My8xbFZkTDFjSHp0OXFvTE1VNFJ3NTR5QVRtOW01TVBnOVh4Uk16OWFKSnVHQzJGSjhnK3hDeWFTQUFSMkEzMGpaZzE1aEhwYzN5NnNWbnJ0MWJoZTZwRjJFV203dzJnMkZva0hUMDZBR1BWTkQxZEVLanhnaTRHQnNxa2pHWDBlNm5HUFZFb3ptc0VqTHAzMlhsWFNFb2tSMmlRSjN3bkZQa0QvbzVYVGRwOFFLTFdxdlNtcEc2Rm4wRDFUSlVFekVYNU53ZDdpWEs0V3RRT3dad3Y0a3Q0cERSTEVLN3U5ZjJtR3BpMkI3V01GQVdvazlGWFU9fDl3TmJPdWkwY01HQm50TmRGZ0Y1NVE9PQ==
--------------------------------------------------------------------------------
/tests/fixtures/ProfileManager/fixture_merge_profiles/profiles.personal.yml:
--------------------------------------------------------------------------------
1 | profiles:
2 | test1-personal:
3 | region: 'us-east-1'
4 | access_key: 'TESTPERSONALACCESSKEY1'
5 | secret_key: 'TESTPERSONALSECRETKEY1'
6 | test2-personal:
7 | region: 'us-east-1'
8 | access_key: 'TESTPERSONALACCESSKEY2'
9 | secret_key: 'TESTPERSONALSECRETKEY2'
--------------------------------------------------------------------------------
/tests/fixtures/ProfileManager/fixture_merge_profiles/profiles.yml:
--------------------------------------------------------------------------------
1 | profiles:
2 | test1:
3 | region: 'us-east-1'
4 | access_key: 'TESTACCESSKEY1'
5 | secret_key: 'TESTSECRETKEY1'
6 | test2:
7 | region: 'us-east-1'
8 | access_key: 'TESTACCESSKEY2'
9 | secret_key: 'TESTSECRETKEY2'
10 | test3:
11 | region: 'us-east-1'
12 | access_key: 'TESTACCESSKEY3'
13 | secret_key: 'TESTSECRETKEY3'
14 | assume_role: 'arn:aws:iam::0123456789012:role/p-magento-iam-DeployRole'
15 |
--------------------------------------------------------------------------------
/tests/fixtures/RunBeforeScript/foo.txt:
--------------------------------------------------------------------------------
1 | HELLO WORLD FROM FILE
--------------------------------------------------------------------------------
/tests/fixtures/Stack/test-stack1.describeStackResources.json:
--------------------------------------------------------------------------------
1 | {
2 | "StackResources": [
3 | {
4 | "StackName": "test-stack1",
5 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack\/test-stack1\/037063f1-2dfa-11e6-a4dd-50a686be73f2",
6 | "LogicalResourceId": "AutoScalingGroup",
7 | "PhysicalResourceId": "test-stack1-AutoScalingGroup-ELHRZ3CQ5KTD",
8 | "ResourceType": "AWS::AutoScaling::AutoScalingGroup",
9 | "Timestamp": "2016-06-19T06:26:50+00:00",
10 | "ResourceStatus": "UPDATE_COMPLETE"
11 | }, {
12 | "StackName": "test-stack1",
13 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack\/test-stack1\/037063f1-2dfa-11e6-a4dd-50a686be73f2",
14 | "LogicalResourceId": "Dns",
15 | "PhysicalResourceId": "jenkins.example.net",
16 | "ResourceType": "AWS::Route53::RecordSet",
17 | "Timestamp": "2016-06-09T04:25:13+00:00",
18 | "ResourceStatus": "CREATE_COMPLETE"
19 | }, {
20 | "StackName": "test-stack1",
21 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack\/test-stack1\/037063f1-2dfa-11e6-a4dd-50a686be73f2",
22 | "LogicalResourceId": "InstanceSg",
23 | "PhysicalResourceId": "sg-de4494b8",
24 | "ResourceType": "AWS::EC2::SecurityGroup",
25 | "Timestamp": "2016-06-11T04:43:44+00:00",
26 | "ResourceStatus": "UPDATE_COMPLETE"
27 | }, {
28 | "StackName": "test-stack1",
29 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack\/test-stack1\/037063f1-2dfa-11e6-a4dd-50a686be73f2",
30 | "LogicalResourceId": "LaunchConfiguration",
31 | "PhysicalResourceId": "test-stack1-LaunchConfiguration-22VBM2XN6V32",
32 | "ResourceType": "AWS::AutoScaling::LaunchConfiguration",
33 | "Timestamp": "2016-06-19T06:23:45+00:00",
34 | "ResourceStatus": "UPDATE_COMPLETE"
35 | }, {
36 | "StackName": "test-stack1",
37 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack\/test-stack1\/037063f1-2dfa-11e6-a4dd-50a686be73f2",
38 | "LogicalResourceId": "LoadBalancer",
39 | "PhysicalResourceId": "demo-jenk-LoadBala-11B79X246A9EJ",
40 | "ResourceType": "AWS::ElasticLoadBalancing::LoadBalancer",
41 | "Timestamp": "2016-06-11T04:43:29+00:00",
42 | "ResourceStatus": "UPDATE_COMPLETE"
43 | }, {
44 | "StackName": "test-stack1",
45 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack\/test-stack1\/037063f1-2dfa-11e6-a4dd-50a686be73f2",
46 | "LogicalResourceId": "LoadBalancerSg",
47 | "PhysicalResourceId": "sg-e3449485",
48 | "ResourceType": "AWS::EC2::SecurityGroup",
49 | "Timestamp": "2016-06-11T04:43:25+00:00",
50 | "ResourceStatus": "UPDATE_COMPLETE"
51 | }
52 | ]
53 | }
--------------------------------------------------------------------------------
/tests/fixtures/Stack/test-stack1.json:
--------------------------------------------------------------------------------
1 | {
2 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack\/test-stack1\/037063f1-2dfa-11e6-a4dd-50a686be73f2",
3 | "StackName": "test-stack1",
4 | "Description": "Test Description",
5 | "Parameters": [
6 | {"ParameterKey": "Foo", "ParameterValue": "Bar"},
7 | {"ParameterKey": "Foo2","ParameterValue": "Bar2"}
8 | ],
9 | "CreationTime": "2016-06-09T04:24:10+00:00",
10 | "LastUpdatedTime": "2016-06-19T06:23:36+00:00",
11 | "StackStatus": "UPDATE_COMPLETE",
12 | "DisableRollback": true,
13 | "NotificationARNs": [],
14 | "Outputs": [
15 | {"OutputKey": "OutputFoo", "OutputValue": "OutputBar"},
16 | {"OutputKey": "OutputFoo2","OutputValue": "OutputBar2"}
17 | ],
18 | "Tags": [
19 | {"Key": "FooTag", "Value": "BarTag"},
20 | {"Key": "stackformation:blueprint", "Value": "TmFtZT1kZW1vLWplbmtpbnM="}
21 | ]
22 | }
--------------------------------------------------------------------------------
/tests/fixtures/foo.pem:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AOEpeople/StackFormation/5332dbbe54653e50d610cbaf75fb865c68aa2f1e/tests/fixtures/foo.pem
--------------------------------------------------------------------------------
/tests/fixtures/resolve_md5.txt:
--------------------------------------------------------------------------------
1 | ASDS SFsd df dg zg aerg g ag sdfgfdgstesrgrg
--------------------------------------------------------------------------------
/tests/phpunit.bootstrap.php:
--------------------------------------------------------------------------------
1 | setUseIncludePath(true);
11 | define('FIXTURE_ROOT', __DIR__ . '/fixtures/');
--------------------------------------------------------------------------------