├── 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 |
--------------------------------------------------------------------------------