├── LICENSE.txt ├── README.md ├── requirements-dev.txt ├── setup.cfg ├── setup.py ├── ssmx-logo.png ├── ssmx.py └── test_ssm.py /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright for portions of ssmx are held by [HelloFresh, 2017] as part of project [ssm-cli](https://github.com/hellofresh/ssm-cli). 4 | All other copyright for project ssmx are held by [Johny Georges, 2018]. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # ssmx 6 | 7 | `ssmx` is a CLI tool for injecting parameters stored in AWS SSM into executables. 8 | 9 | It also provides commands to retrieve and set parameters in AWS SSM. 10 | 11 | ## Installation 12 | 13 | ``` 14 | pip install ssmx 15 | ``` 16 | 17 | ## Usage 18 | 19 | Running: 20 | 21 | ``` 22 | ssmx --help 23 | ``` 24 | 25 | ## Configuring Credentials 26 | 27 | The Following authentication methods are supported: 28 | 29 | - Environment variables 30 | - Shared credential file (~/.aws/credentials) 31 | - AWS config file (~/.aws/config) 32 | - AWS profiles 33 | 34 | For more details please see [here](http://boto3.readthedocs.io/en/latest/guide/configuration.html). 35 | 36 | ### List parameters 37 | 38 | List all parameters: 39 | 40 | ``` 41 | ssmx list 42 | ``` 43 | 44 | Output: 45 | 46 | ``` 47 | +------------------------------------+---------------+ 48 | | Name | Description | 49 | +====================================+===============+ 50 | | /platform/infra/testing | Test param | 51 | +------------------------------------+---------------+ 52 | | MY_KEY | MY TEST KEY | 53 | +------------------------------------+---------------+ 54 | ``` 55 | 56 | Filter parameters by name: 57 | 58 | ``` 59 | ssmx list --name my-app 60 | ``` 61 | 62 | Will list parameters starting with `my-app` 63 | 64 | ``` 65 | +---------------------+----------------------+ 66 | | Name | Description | 67 | +=====================+======================+ 68 | | my-app.hostname | my app hostname | 69 | | my-app.secret-key | hush puppy | 70 | +---------------------+----------------------+ 71 | ``` 72 | 73 | ### Delete Parameters 74 | 75 | ``` 76 | ssmx delete --name 77 | ``` 78 | 79 | Will delete the parameter `MY_KEY`. Invalid parameters are ignored and printed on stdout. 80 | 81 | Output: 82 | 83 | ``` 84 | +----------------------+ 85 | | Deleted Parameters | 86 | +======================+ 87 | | MY_KEY | 88 | +----------------------+ 89 | ``` 90 | 91 | ### Get parameters 92 | 93 | ```bash 94 | ssmx get --name 95 | ``` 96 | 97 | Will retrieve and decrypt the param MY_KEY 98 | Output: 99 | 100 | ``` 101 | +--------+---------+ 102 | | Name | Value | 103 | +========+=========+ 104 | | MY_KEY | MY_VAL | 105 | +--------+---------+ 106 | ``` 107 | 108 | ### Put parameters 109 | 110 | ```bash 111 | ssmx put 112 | --name 113 | --value 114 | --description # optional 115 | --encrypt # optional 116 | --key-id # required only when --encrypt is specified 117 | ``` 118 | 119 | **Important Note:** `put` behaves like an upsert, meaning if no entry exists with the name provided, it will create a new entry, and if an entry already exists with the name provided, it will overwrite the current value with the value provided. 120 | 121 | ### Provide env variables to an executable 122 | 123 | ``` 124 | ssmx exec --env-file -- 125 | ``` 126 | 127 | Using the `exec` command, you can specify an `env` file that contains plain and secret values. Secret values need to be provided in the following format: 128 | 129 | ```bash 130 | PLAIN_ENV_VAR=hello world 131 | SECRET_ENV_VAR=ssm: 132 | ``` 133 | 134 | #### Example 135 | 136 | Let's assume we are working with a node.js application that requires specific secret envrionment variables for specific environments. In other words, our application depends on `.env` files to contain the environment variables it needs to function correctly for each environment it's deployed in. 137 | 138 | Suppose our `dev.env` file looks like the following 139 | 140 | ```bash 141 | THIRD_PARTY_HOSTNAME=https://api.third-party.com 142 | THIRD_PARTY_ACCESS_TOKEN=ssm:my-app.dev-third-party-access-token 143 | ``` 144 | 145 | We now need to pass this `dev.env` file to `ssmx` to fetch and decrypt the value for `THIRD_PARTY_ACCESS_TOKEN` and then inject the two env. variables into the process that will run our node.js application. 146 | 147 | ```bash 148 | $ ssmx exec --env-file ./env/dev.env -- npm start 149 | ``` 150 | 151 | #### Example with --name parameter 152 | 153 | Let's simplify our example from above and let's assume we store all our plain and secret env. variables in AWS SSM and we don't use `.env` files. 154 | 155 | We also prefix our keys in AWS SSM with `-my-app`, i.e. 156 | 157 | ``` 158 | +--------------------------------------+------------------------------+ 159 | | Name | Value | 160 | +======================================+==============================+ 161 | | /dev-my-app/third-party-hostname | https://api.third-party.com | 162 | | /dev-my-app/third-party-access-token | shhhh-my-access-token | 163 | +--------------------------------------+------------------------------+ 164 | ``` 165 | 166 | We can then acheive the same result in the previous example with the following command 167 | 168 | ```bash 169 | $ ssmx exec --name dev-my-app -- npm start 170 | ``` 171 | 172 | Now this feature is really handy because if you're using docker to containerize your applications and AWS ECS to host your containers, you can simply provide an environment variable in your container definition, (i.e. `APP_NAME` ) to differentiate between each environment. 173 | 174 | For example, in our Dockerfile we can do the following 175 | 176 | ```docker 177 | ENTRYPOINT [ "./run-app.sh" ] 178 | ``` 179 | 180 | ./run-app.sh 181 | 182 | ```bash 183 | #!/usr/bin/env bash 184 | set -e 185 | echo "Starting up..." 186 | 187 | # $APP_NAME is exposed by the container definition 188 | ssmx exec --name $APP_NAME -- npm start 189 | ``` 190 | 191 | #### Important Note 192 | 193 | If you plan to use the `--name` parameter with `ssmx exec`, you need to follow a specific format for the keys you create in AWS SSM. The keys need to follow the `path` format which works as follows: 194 | 195 | ```bash 196 | ///.../ 197 | 198 | # examples 199 | /my-app/third-party-hostname 200 | /my-app/dev/third-party-hostname 201 | ``` 202 | 203 | You can read in more detail about paths [here](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_GetParametersByPath.html#systemsmanager-GetParametersByPath-request-Path) 204 | 205 | ### License 206 | 207 | `ssmx` is released under [MIT](./LICENSE) 208 | 209 | ### Inspirations 210 | 211 | This project is a fork from HelloFresh's [ssm-cli](https://github.com/hellofresh/ssm-cli) and drew inspiration from the following projects: 212 | 213 | - [Chamber](https://github.com/segmentio/chamber) 214 | - [kms-env](https://github.com/ukayani/kms-env) 215 | - [dotenv](https://github.com/theskumar/python-dotenv) 216 | 217 | --- 218 | 219 | ### Used By 220 |

221 | 222 | 223 | 224 |

225 | 226 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | git+git://github.com/spulec/moto.git 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = '\n' + fh.read() 5 | 6 | setup( 7 | name='ssmx', 8 | author="Johny Georges", 9 | author_email="jgeorges371@gmail.com", 10 | url="https://github.com/JetJet13/ssmx", 11 | python_requires=">=2.7", 12 | description="CLI tool for injecting parameters stored in AWS SSM into executables.", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | version='v1.0.3', 16 | py_modules=['ssmx'], 17 | download_url="https://github.com/JetJet13/ssmx/archive/v1.0.2.tar.gz", 18 | include_package_data=True, 19 | install_requires=[ 20 | 'click', 21 | 'boto3', 22 | 'tabulate', 23 | 'subprocess32' 24 | ], 25 | entry_points=''' 26 | [console_scripts] 27 | ssmx=ssmx:cli 28 | ''' 29 | ) 30 | -------------------------------------------------------------------------------- /ssmx-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetJet13/ssmx/bbc8d3c7ac1315f919336d66ccaafe14a49b7533/ssmx-logo.png -------------------------------------------------------------------------------- /ssmx.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import click 3 | import re 4 | from os import environ 5 | from tabulate import tabulate 6 | from subprocess32 import Popen 7 | 8 | @click.group() 9 | @click.version_option() 10 | def cli(): 11 | """ 12 | ssmx is a CLI tool for injecting parameters stored in AWS SSM into executables. 13 | It also provides commands to retrieve and set parameters in AWS SSM. 14 | """ 15 | 16 | 17 | 18 | 19 | def list_params(names, profile, region): 20 | session = boto3.Session(profile_name=profile, region_name=region) 21 | client = session.client('ssm') 22 | if names: 23 | filters = [{"Key": "Name", "Values": names}] 24 | # The whole list needs to be empty 25 | else: 26 | filters = [] 27 | try: 28 | paginator = client.get_paginator('describe_parameters') 29 | response_iterator = paginator.paginate(Filters=filters) 30 | except Exception as e: 31 | click.echo("Error listing parameters:") 32 | click.echo(e.message) 33 | exit(1) 34 | 35 | output = [] 36 | for page in response_iterator: 37 | for param in page['Parameters']: 38 | if 'Description' not in param: 39 | param['Description'] = '' 40 | output.append(dict(Name=param.get('Name'), Description=param.get('Description'))) 41 | return output 42 | 43 | @cli.command(name="list") 44 | @click.option('--name', '-n', metavar='', multiple=True, help='Show parameters starting with ') 45 | @click.option('--profile', '-p', metavar='', required=False, help='an aws profile') 46 | @click.option('--region', '-r', metavar='', required=False, help='aws Region, i.e. us-east-1') 47 | def list(name, profile, region): 48 | """List available parameters.""" 49 | output = list_params(name, profile, region) 50 | if output: 51 | click.echo(tabulate({'Name': [item['Name'] for item in output], 52 | 'Description': [item['Description'] for item in output]}, 53 | headers='keys', tablefmt='grid')) 54 | else: 55 | click.echo("No parameters found.") 56 | 57 | 58 | 59 | 60 | 61 | def delete_params(names, profile, region): 62 | session = boto3.Session(profile_name=profile, region_name=region) 63 | client = boto3.client('ssm') 64 | try: 65 | response = client.delete_parameters(Names=names) 66 | # TODO: catch exceptions 67 | except Exception as e: 68 | click.echo("Error deleting parameters:") 69 | click.echo(e.message) 70 | exit(1) 71 | return response.get('DeletedParameters'), response.get('InvalidParameters') 72 | 73 | @cli.command(name="delete") 74 | @click.option('--name', '-n', metavar='', multiple=True, required=True, help='Name of the parameter to delete') 75 | @click.option('--profile', '-p', metavar='', required=False, help='an aws profile') 76 | @click.option('--region', '-r', metavar='', required=False, help='aws Region, i.e. us-east-1') 77 | def delete(name, profile, region): 78 | """Delete parameters with .""" 79 | output, err = delete_params(name, profile, region) 80 | # Print if specified otherwise return output 81 | if output: 82 | click.echo(tabulate({'Deleted Parameters': output}, headers='keys', tablefmt='grid')) 83 | if err: 84 | click.echo(tabulate({'Invalid Parameters': err}, headers='keys', tablefmt='grid')) 85 | 86 | 87 | 88 | 89 | 90 | 91 | def get_params(names, profile, region): 92 | session = boto3.Session(profile_name=profile, region_name=region) 93 | client = session.client('ssm') 94 | try: 95 | response = client.get_parameters(Names=names, WithDecryption=True) 96 | except Exception as e: 97 | click.echo("Error getting parameters") 98 | click.echo(e.message) 99 | exit(1) 100 | return response.get('Parameters'), response.get('InvalidParameters') 101 | 102 | @cli.command(name="get") 103 | @click.option('--name', '-n', metavar='', multiple=True, required=True, help='Name of the parameter to retrieve') 104 | @click.option('--profile', '-p', metavar='', required=False, help='an aws profile') 105 | @click.option('--region', '-r', metavar='', required=False, help='aws Region, i.e. us-east-1') 106 | def get(name, profile, region, print_output=True): 107 | """Retrieve values of parameters with .""" 108 | output, err = get_params(name, profile, region) 109 | if output: 110 | click.echo(tabulate({'Name': [param['Name'] for param in output], 111 | 'Value': [param['Value']for param in output]}, 112 | headers='keys', tablefmt='grid')) 113 | if err: 114 | click.echo(tabulate({'Invalid Parameters': [param for param in err]}, 115 | headers='keys', tablefmt='grid')) 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | def put_param(name, value, encrypt, key_id, profile, region, description): 124 | session = boto3.Session(profile_name=profile, region_name=region) 125 | client = session.client('ssm') 126 | if encrypt: 127 | if key_id is None: 128 | click.echo("Heads Up! I'm unable to encrypt without specifying a KMS Key ID; Retry with --key-id ") 129 | exit(1) 130 | param_type = 'SecureString' 131 | else: 132 | if key_id: 133 | click.echo("Heads Up! --key-id is required only when --encrypt is specified.") 134 | exit(1) 135 | param_type = 'String' 136 | try: 137 | if key_id: 138 | client.put_parameter(Name=name, Value=value, Description=description, 139 | Overwrite=True, Type=param_type, KeyId=key_id) 140 | else: 141 | client.put_parameter(Name=name, Value=value, Overwrite=True, Description=description, Type=param_type) 142 | except Exception as e: 143 | click.echo("Error putting parameters") 144 | click.echo(e.message) 145 | exit(1) 146 | # return the name of the variable in case of success 147 | return name 148 | 149 | @cli.command(name="put", help='Upsert parameters') 150 | @click.option('--name', '-n', metavar='', required=True, help='Name of the parameter') 151 | @click.option('--value', '-v', metavar='', required=True, help='Value of the parameter') 152 | @click.option('--description', '-d', metavar='', default="", help='Description for the parameter') 153 | @click.option('--encrypt', '-e', is_flag=True, default=False, help='Encrypt the parameter') 154 | @click.option('--key-id', '-k', metavar='', required=False, help='KMS Key ID to encrypt. Required option if --encrypt is used.') 155 | @click.option('--profile', '-p', metavar='', required=False, help='an aws profile') 156 | @click.option('--region', '-r', metavar='', required=False, help='aws Region, i.e. us-east-1') 157 | def put(name, value, encrypt, description, key_id, profile, region): 158 | """Put parameter with name , value and description . Optionally encrypt the parameter.""" 159 | output = put_param(name=name, value=value, description=description, encrypt=encrypt, key_id=key_id, profile=profile, region=region) 160 | if output: 161 | click.echo(tabulate({'Created Parameters': [output]}, headers='keys', tablefmt='grid')) 162 | 163 | 164 | 165 | 166 | 167 | 168 | def get_param(name, profile, region): 169 | session = boto3.Session(profile_name=profile, region_name=region) 170 | client = session.client('ssm') 171 | try: 172 | response = client.get_parameter(Name=name, WithDecryption=True) 173 | # TODO: catch exceptions 174 | except Exception as e: 175 | click.echo("Error getting parameters") 176 | click.echo(e.message) 177 | click.echo('Parameter Name: %s' % name) 178 | exit(1) 179 | return response.get('Parameter') 180 | 181 | def get_parameters_by_path(path, profile, region): 182 | session = boto3.Session(profile_name=profile, region_name=region) 183 | client = session.client('ssm') 184 | try: 185 | paginator = client.get_paginator('get_parameters_by_path') 186 | response_iterator = paginator.paginate(Path=path, Recursive=True, WithDecryption=True) 187 | except Exception as e: 188 | click.echo("Error listing parameters:") 189 | click.echo(e.message) 190 | exit(1) 191 | 192 | output = [] 193 | for page in response_iterator: 194 | for param in page['Parameters']: 195 | if 'Description' not in param: 196 | param['Description'] = '' 197 | output.append(dict(Name=param.get('Name'), Value=param.get('Value'))) 198 | return output 199 | 200 | 201 | def formatKey(input): 202 | """ 203 | formatKey converts the parameter key stored in ssm into 204 | the traditional env. variable key format 205 | 206 | examples: 207 | /my-app/foo.bar -> FOO_BAR 208 | /my-app/dev/hello-world -> HELLO_WORLD 209 | /my-app/sbx/alpha/job_one -> JOB_ONE 210 | """ 211 | paths = input.split('/') 212 | keyName = paths[-1] # last item is key name 213 | formattedKey = re.sub(r'[\.\-\_]', '_', keyName).upper() 214 | return formattedKey 215 | 216 | @cli.command(name="exec", help='Inject env variables into an executable') 217 | @click.argument('command', nargs=-1, required=False, type=click.UNPROCESSED) 218 | @click.option('--env-file', '-f', metavar='', required=False, help='filepath for .env file') 219 | @click.option('--name', '-n', metavar='', required=False, help='prefix-name of parameters, i.e. //hello-world') 220 | @click.option('--profile', '-p', metavar='', required=False, help='an aws profile') 221 | @click.option('--region', '-r', metavar='', required=False, help='aws Region, i.e. us-east-1') 222 | def execute(command, env_file, name, profile, region): 223 | """Inject env. variables into an executable via and/or """ 224 | 225 | # command is a tuple 226 | if len(command) == 0: 227 | click.echo("nothing to execute") 228 | return 229 | 230 | env_dict = {} 231 | if env_file: 232 | env_vars = [] 233 | f = open(env_file, 'r') 234 | for line in f: 235 | if line.startswith('#'): 236 | continue 237 | key, value = line.strip().split('=', 1) 238 | env_vars.append({'name': key, 'value': value}) 239 | for env_var in env_vars: 240 | key = env_var['name'] 241 | value = env_var['value'] 242 | if value.startswith('ssm:'): 243 | secretKey = value[4:] 244 | out = get_param(secretKey, profile, region) 245 | env_var['value'] = out['Value'] 246 | 247 | for env_var in env_vars: 248 | key = env_var['name'] 249 | value = env_var['value'] 250 | env_dict[key] = value 251 | click.echo("injected %s" % key) 252 | 253 | if name: 254 | env_vars = [] 255 | if name[0] == '/': 256 | path = name 257 | else: 258 | path = '/' + name 259 | params = get_parameters_by_path(path, profile, region) 260 | for param in params: 261 | key = formatKey(param['Name']) 262 | formatKey(key) 263 | value = param['Value'] 264 | env_dict[key] = value 265 | click.echo("injected %s" % key) 266 | 267 | cmd_env = environ.copy() 268 | cmd_env.update(env_dict) 269 | 270 | p = Popen(command, 271 | universal_newlines=True, 272 | bufsize=0, 273 | shell=False, 274 | env=cmd_env) 275 | _, _ = p.communicate() 276 | 277 | return p.returncode -------------------------------------------------------------------------------- /test_ssm.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import ssm 3 | import yaml 4 | import tempfile 5 | from click.testing import CliRunner 6 | from moto import mock_ssm 7 | 8 | 9 | @mock_ssm 10 | def test_list_no_arg(): 11 | conn = boto3.client('ssm') 12 | # Create two parameters and check both are returned 13 | conn.put_parameter(Name='test', Value='testing123', Type='SecureString') 14 | conn.put_parameter(Name='test1', Value='testing123', Type='SecureString') 15 | 16 | # Call list without any parameters 17 | out = ssm.list_params([]) 18 | 19 | names = [param['Name'] for param in out] 20 | assert len(out) == 2 21 | assert 'test1' in names 22 | assert 'test' in names 23 | 24 | 25 | @mock_ssm 26 | def test_list_starts_with(): 27 | conn = boto3.client('ssm') 28 | # Create two parameters and check both are returned 29 | conn.put_parameter(Name='test1', Value='testing123', Type='SecureString') 30 | conn.put_parameter(Name='anotherValue', Value='testing123', Type='SecureString') 31 | 32 | out = ssm.list_params(["test"]) 33 | 34 | names = [param['Name'] for param in out] 35 | assert len(out) == 1 36 | assert 'test1' in names 37 | assert 'anotherValue' not in names 38 | 39 | 40 | @mock_ssm 41 | def test_delete_correct(): 42 | conn = boto3.client('ssm') 43 | # Create a parameter and check if it is deleted. 44 | conn.put_parameter(Name='test1', Value='testing123', Type='SecureString') 45 | 46 | out, err = ssm.delete_params(['test1']) 47 | 48 | assert len(err) == 0 49 | assert len(out) == 1 50 | assert 'test1' in out 51 | # Check if parameter is actually deleted 52 | out = ssm.list_params(['test1']) 53 | assert len(out) == 0 54 | 55 | 56 | @mock_ssm 57 | def test_delete_invalid(): 58 | # Delete an invalid parameter 59 | out, err = ssm.delete_params(['InvalidParam']) 60 | 61 | assert len(out) == 0 62 | assert len(err) == 1 63 | assert 'InvalidParam' in err 64 | 65 | 66 | @mock_ssm 67 | def test_get_param_correct(): 68 | conn = boto3.client('ssm') 69 | 70 | # Put 2 parameters 71 | conn.put_parameter(Name='test1', Value='testing1', Type='SecureString') 72 | conn.put_parameter(Name='test1', Value='testing2', Type='SecureString') 73 | 74 | # get the parameter 75 | 76 | out, err = ssm.get_params(['test1']) 77 | # Check output 78 | assert len(out) == 1 79 | assert len(err) == 0 80 | 81 | assert out[0]['Name'] == 'test1' 82 | assert out[0]['Value'] == 'testing1' 83 | 84 | 85 | @mock_ssm 86 | def test_get_param_incorrect(): 87 | conn = boto3.client('ssm') 88 | 89 | # Put 2 parameters 90 | conn.put_parameter(Name='test1', Value='testing1', Type='SecureString') 91 | conn.put_parameter(Name='test2', Value='testing2', Type='SecureString') 92 | 93 | # get the parameter 94 | 95 | out, err = ssm.get_params(['test1', 'test3']) 96 | # Check output 97 | assert len(out) == 1 98 | assert len(err) == 1 99 | 100 | assert out[0]['Name'] == 'test1' 101 | assert out[0]['Value'] == 'testing1' 102 | # Check error 103 | assert err[0] == 'test3' 104 | 105 | 106 | @mock_ssm 107 | def test_put_param(): 108 | # Put 1 parameter 109 | ssm.put_param(name="test1", value="value1", description='my var', encrypt=True) 110 | # get the parameter 111 | out, err = ssm.get_params(['test1']) 112 | # Check parameter is returned 113 | assert len(out) == 1 114 | assert len(err) == 0 115 | 116 | assert out[0]['Name'] == 'test1' 117 | assert out[0]['Value'] == 'value1' 118 | 119 | 120 | @mock_ssm 121 | def test_cli_list_no_arg(): 122 | conn = boto3.client('ssm') 123 | # Create two parameters and check both are returned 124 | conn.put_parameter(Name='test1', Value='testing1', Type='SecureString') 125 | conn.put_parameter(Name='test2', Value='testing2', Type='SecureString') 126 | 127 | # Call list without any parameters 128 | runner = CliRunner() 129 | result = runner.invoke(ssm.list) 130 | 131 | assert "No parameters found" not in result.output 132 | assert 'test1' in result.output 133 | assert 'test2' in result.output 134 | 135 | 136 | @mock_ssm 137 | def test_cli_list_starts_with(): 138 | conn = boto3.client('ssm') 139 | # Create two parameters and check both are returned 140 | conn.put_parameter(Name='test1', Value='testing123', Type='SecureString') 141 | conn.put_parameter(Name='anotherValue', Value='testing123', Type='SecureString') 142 | 143 | runner = CliRunner() 144 | result = runner.invoke(ssm.list, ['--name', 'test']) 145 | 146 | assert 'test1' in result.output 147 | assert 'anotherValue' not in result.output 148 | 149 | 150 | @mock_ssm 151 | def test_cli_get_param(): 152 | conn = boto3.client('ssm') 153 | 154 | # Create two parameters and check both are returned 155 | conn.put_parameter(Name='test1', Value='value1', Type='SecureString') 156 | conn.put_parameter(Name='test2', Value='value2', Type='SecureString') 157 | 158 | runner = CliRunner() 159 | result = runner.invoke(ssm.get, ['--name', 'test1']) 160 | 161 | assert 'Invalid Parameters' not in result.output 162 | assert 'value1' in result.output 163 | assert 'test1' in result.output 164 | 165 | assert 'test2' not in result.output 166 | assert 'value2' not in result.output 167 | 168 | 169 | @mock_ssm 170 | def test_cli_delete_correct(): 171 | conn = boto3.client('ssm') 172 | # Create a parameter and check if it is deleted. 173 | conn.put_parameter(Name='test1', Value='testing123', Type='SecureString') 174 | 175 | runner = CliRunner() 176 | result = runner.invoke(ssm.delete, ['--name', 'test1']) 177 | assert 'Deleted Parameters' in result.output 178 | assert 'test1' in result.output 179 | # Check if parameter is actually deleted 180 | runner = CliRunner() 181 | result = runner.invoke(ssm.list, ['--name', 'test1']) 182 | assert 'No parameters found' in result.output 183 | 184 | 185 | @mock_ssm 186 | def test_cli_delete_invalid(): 187 | # Delete an invalid parameter 188 | runner = CliRunner() 189 | result = runner.invoke(ssm.delete, ['--name', 'invalid_param']) 190 | 191 | assert 'Invalid Parameters' in result.output 192 | assert 'invalid_param' in result.output 193 | assert 'Deleted Parameters' not in result.output 194 | 195 | 196 | @mock_ssm 197 | def test_cli_put_param(): 198 | # Create a parameter 199 | runner = CliRunner() 200 | result = runner.invoke(ssm.put, ['--name', 'test1', '--value', 'value1', '--encrypt']) 201 | 202 | assert 'Created Parameters' in result.output 203 | assert 'test' in result.output 204 | 205 | 206 | @mock_ssm 207 | def test_cli_file_list(): 208 | # Create a temp file 209 | test_file = tempfile.NamedTemporaryFile(delete=False, mode='w') 210 | 211 | conn = boto3.client('ssm') 212 | conn.put_parameter(Name='test1', Value='val', Type='SecureString') 213 | conn.put_parameter(Name='test2', Value='val', Type='SecureString') 214 | 215 | test_file.write(''' 216 | list: 217 | - test''') 218 | test_file.close() 219 | 220 | runner = CliRunner() 221 | result = runner.invoke(ssm.from_file, ['--path', test_file.name]) 222 | output_yaml = yaml.load(result.output) 223 | 224 | assert 'list' in output_yaml.keys() 225 | assert len(output_yaml['list']) == 2 226 | assert 'test1' in [param['Name'] for param in output_yaml['list']] 227 | assert 'test2' in [param['Name'] for param in output_yaml['list']] 228 | 229 | 230 | @mock_ssm 231 | def test_file_delete(): 232 | # Create a temp file 233 | test_file = tempfile.NamedTemporaryFile(delete=False, mode='w') 234 | 235 | conn = boto3.client('ssm') 236 | conn.put_parameter(Name='test1', Value='val', Type='SecureString') 237 | 238 | test_file.write(''' 239 | delete: 240 | - test1 241 | - invalid''') 242 | test_file.close() 243 | 244 | runner = CliRunner() 245 | result = runner.invoke(ssm.from_file, ['--path', test_file.name]) 246 | 247 | output_yaml = yaml.load(result.output) 248 | 249 | assert 'delete' in output_yaml.keys() 250 | 251 | assert len(output_yaml['delete']['DeletedParameters']) == 1 252 | assert output_yaml['delete']['DeletedParameters'][0] == 'test1' 253 | 254 | assert len(output_yaml['delete']['InvalidParameters']) == 1 255 | assert output_yaml['delete']['InvalidParameters'][0] == 'invalid' 256 | 257 | 258 | @mock_ssm 259 | def test_file_get(): 260 | # Create a temp file 261 | test_file = tempfile.NamedTemporaryFile(delete=False, mode='w') 262 | 263 | conn = boto3.client('ssm') 264 | conn.put_parameter(Name='test1', Value='val', Type='SecureString') 265 | 266 | test_file.write(''' 267 | get: 268 | - test1 269 | - invalid''') 270 | test_file.close() 271 | 272 | runner = CliRunner() 273 | result = runner.invoke(ssm.from_file, ['--path', test_file.name]) 274 | 275 | output_yaml = yaml.load(result.output) 276 | 277 | assert 'get' in output_yaml.keys() 278 | 279 | assert len(output_yaml['get']['GetParameters']) == 1 280 | assert output_yaml['get']['GetParameters'][0]['Name'] == 'test1' 281 | 282 | assert len(output_yaml['get']['InvalidParameters']) == 1 283 | assert output_yaml['get']['InvalidParameters'][0] == 'invalid' 284 | 285 | 286 | @mock_ssm 287 | def test_file_put(): 288 | # Create a temp file 289 | test_file = tempfile.NamedTemporaryFile(delete=False, mode='w') 290 | test_file.write(''' 291 | put: 292 | - name: test 293 | value: val 294 | encrypt: True''') 295 | test_file.close() 296 | 297 | runner = CliRunner() 298 | result = runner.invoke(ssm.from_file, ['--path', test_file.name]) 299 | 300 | output_yaml = yaml.load(result.output) 301 | 302 | assert 'put' in output_yaml.keys() 303 | 304 | assert len(output_yaml['put']['CreatedParameters']) == 1 305 | assert output_yaml['put']['CreatedParameters'][0] == 'test' 306 | 307 | # Check parameter actually got created 308 | conn = boto3.client('ssm') 309 | response = conn.describe_parameters() 310 | assert response['Parameters'][0]['Name'] == 'test' 311 | --------------------------------------------------------------------------------