Skip to content

Commit 031ce5b

Browse files
authored
Merge pull request #149 from richford/policy-arn
Policy arn
2 parents 8c7f3ba + 9f26df1 commit 031ce5b

File tree

3 files changed

+81
-16
lines changed

3 files changed

+81
-16
lines changed

cloudknot/cloudknot.py

+65-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import absolute_import, division, print_function
22

3+
import botocore
34
import configparser
45
import ipaddress
56
import logging
@@ -225,6 +226,70 @@ def validated_name(role_name, fallback_suffix):
225226
spot_fleet_role_name = validated_name(spot_fleet_role_name,
226227
'spot-fleet-role')
227228

229+
# Check the user supplied policies. Remove redundant entries
230+
if isinstance(policies, six.string_types):
231+
input_policies = {policies}
232+
else:
233+
try:
234+
if all(isinstance(x, six.string_types) for x in policies):
235+
input_policies = set(list(policies))
236+
else:
237+
raise aws.CloudknotInputError(
238+
'policies must be a string or a '
239+
'sequence of strings.'
240+
)
241+
except TypeError:
242+
raise aws.CloudknotInputError('policies must be a string '
243+
'or a sequence of strings')
244+
245+
# Validate policies against the available policies
246+
policy_arns = []
247+
policy_names = []
248+
for policy in input_policies:
249+
try:
250+
aws.clients['iam'].get_policy(PolicyArn=policy)
251+
policy_arns.append(policy)
252+
except (
253+
aws.clients['iam'].exceptions.InvalidInputException,
254+
aws.clients['iam'].exceptions.NoSuchEntityException,
255+
botocore.exceptions.ParamValidationError,
256+
):
257+
policy_names.append(policy)
258+
259+
if policy_names:
260+
# Get all AWS policies
261+
response = aws.clients['iam'].list_policies()
262+
aws_policies = {d['PolicyName']: d['Arn']
263+
for d in response.get('Policies')}
264+
265+
# If results are paginated, continue appending to aws_policies,
266+
# using `Marker` to tell next call where to start
267+
while response['IsTruncated']:
268+
response = aws.clients['iam'].list_policies(
269+
Marker=response['Marker']
270+
)
271+
aws_policies.update(
272+
{d['PolicyName']: d['Arn']
273+
for d in response.get('Policies')}
274+
)
275+
276+
# If input policies are not subset of aws_policies, throw error
277+
if not (set(policy_names) < set(aws_policies.keys())):
278+
bad_policies = set(policy_names) - set(aws_policies.keys())
279+
raise aws.CloudknotInputError(
280+
'Could not find the policies {bad_policies!s} on '
281+
'AWS.'.format(bad_policies=bad_policies)
282+
)
283+
284+
policy_arns += [aws_policies[policy]
285+
for policy in policy_names]
286+
287+
s3_params = aws.get_s3_params()
288+
policy_list = [s3_params.policy_arn] + [
289+
policy for policy in policy_arns
290+
]
291+
policies = ','.join(policy_list)
292+
228293
if use_default_vpc:
229294
if any([ipv4_cidr, instance_tenancy]):
230295
raise aws.CloudknotInputError(
@@ -276,12 +341,6 @@ def validated_name(role_name, fallback_suffix):
276341
with open(template_path, 'r') as fp:
277342
template_body = fp.read()
278343

279-
s3_params = aws.get_s3_params()
280-
policy_list = [s3_params.policy_arn] + [
281-
policy for policy in policies
282-
]
283-
policies = ','.join(policy_list)
284-
285344
response = aws.clients['cloudformation'].create_stack(
286345
StackName=self.name + '-pars',
287346
TemplateBody=template_body,
@@ -403,12 +462,6 @@ def validated_name(role_name, fallback_suffix):
403462
with open(template_path, 'r') as fp:
404463
template_body = fp.read()
405464

406-
s3_params = aws.get_s3_params()
407-
policy_list = [s3_params.policy_arn] + [
408-
policy for policy in policies
409-
]
410-
policies = ','.join(policy_list)
411-
412465
response = aws.clients['cloudformation'].create_stack(
413466
StackName=self.name + '-pars',
414467
TemplateBody=template_body,

cloudknot/tests/test_pars.py

+9
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ def test_pars_errors(cleanup):
8484
with pytest.raises(ck.aws.CloudknotInputError):
8585
ck.Pars(name=name, use_default_vpc=False, instance_tenancy=42)
8686

87+
with pytest.raises(ck.aws.CloudknotInputError):
88+
ck.Pars(name=name, use_default_vpc=False, policies=42)
89+
90+
with pytest.raises(ck.aws.CloudknotInputError):
91+
ck.Pars(name=name, use_default_vpc=False, policies=[42, 42])
92+
93+
with pytest.raises(ck.aws.CloudknotInputError):
94+
ck.Pars(name=name, use_default_vpc=False, policies=['foo'])
95+
8796

8897
def test_pars_with_default_vpc(cleanup):
8998
name = get_testing_name()

cloudknot/version.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
# Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z"
66
_version_major = 0
7-
_version_minor = 3
7+
_version_minor = 4
88
_version_micro = '' # use '' for first of series, number for 1 and above
9-
# _version_extra = 'dev'
10-
_version_extra = '' # Uncomment this for full releases
9+
_version_extra = 'dev'
10+
# _version_extra = '' # Uncomment this for full releases
1111

1212
# Construct full version string from these.
1313
_ver = [_version_major, _version_minor]
@@ -76,5 +76,8 @@
7676
REQUIRES = ["awscli", "boto3>=1.5.21", "botocore>=1.8.36", "cloudpickle",
7777
"docker>=2.0.0, <3.0.0", "pipreqs", "six", "tenacity",
7878
'configparser;python_version<"3.0"', ]
79-
EXTRAS_REQUIRE = {':python_version < "3.0"': ["configparser"]}
79+
EXTRAS_REQUIRE = {
80+
':python_version < "3.0"': ["configparser"],
81+
'dev': ['pytest', 'pytest-cov', 'flake8',],
82+
}
8083
ENTRY_POINTS = {'console_scripts': ['cloudknot=cloudknot.cli:main']}

0 commit comments

Comments
 (0)