Skip to content

Commit

Permalink
Add "aws servicecatalog generate product/provisioning-artifact" custo…
Browse files Browse the repository at this point in the history
…mization to AWS CLI
  • Loading branch information
Wang authored and joguSD committed Sep 29, 2017
1 parent b36f406 commit 52f732c
Show file tree
Hide file tree
Showing 22 changed files with 1,547 additions and 26 deletions.
7 changes: 0 additions & 7 deletions awscli/customizations/cloudformation/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ class InvalidTemplatePathError(CloudFormationCommandError):
fmt = "Invalid template path {template_path}"


class NoSuchBucketError(CloudFormationCommandError):
fmt = ("S3 Bucket does not exist. "
"Execute the command to create a new bucket"
"\n"
"aws s3 mb s3://{bucket_name}")


class ChangeEmptyError(CloudFormationCommandError):
fmt = "No changes to deploy. Stack {stack_name} is up to date"

Expand Down
2 changes: 1 addition & 1 deletion awscli/customizations/cloudformation/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@

from botocore.client import Config

from awscli.customizations.cloudformation.s3uploader import S3Uploader
from awscli.customizations.cloudformation.artifact_exporter import Template
from awscli.customizations.cloudformation.yamlhelper import yaml_dump
from awscli.customizations.cloudformation import exceptions
from awscli.customizations.commands import BasicCommand
from awscli.customizations.s3uploader import S3Uploader

LOG = logging.getLogger(__name__)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,22 @@
from s3transfer.manager import TransferManager
from s3transfer.subscribers import BaseSubscriber

from awscli.customizations.cloudformation import exceptions

LOG = logging.getLogger(__name__)


class NoSuchBucketError(Exception):
def __init__(self, **kwargs):
msg = self.fmt.format(**kwargs)
Exception.__init__(self, msg)
self.kwargs = kwargs


fmt = ("S3 Bucket does not exist. "
"Execute the command to create a new bucket"
"\n"
"aws s3 mb s3://{bucket_name}")


class S3Uploader(object):
"""
Class to upload objects to S3 bucket that use versioning. If bucket
Expand Down Expand Up @@ -94,8 +105,7 @@ def upload(self, file_name, remote_path):
except botocore.exceptions.ClientError as ex:
error_code = ex.response["Error"]["Code"]
if error_code == "NoSuchBucket":
raise exceptions.NoSuchBucketError(
bucket_name=self.bucket_name)
raise NoSuchBucketError(bucket_name=self.bucket_name)
raise ex

def upload_with_dedup(self, file_name, extension=None):
Expand Down
24 changes: 24 additions & 0 deletions awscli/customizations/servicecatalog/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from awscli.customizations.servicecatalog.generate \
import GenerateCommand


def register_servicecatalog_commands(event_emitter):
event_emitter.register('building-command-table.servicecatalog',
inject_commands)


def inject_commands(command_table, session, **kwargs):
command_table['generate'] = GenerateCommand(session)
25 changes: 25 additions & 0 deletions awscli/customizations/servicecatalog/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.


class ServiceCatalogCommandError(Exception):
fmt = 'An unspecified error occurred'

def __init__(self, **kwargs):
msg = self.fmt.format(**kwargs)
Exception.__init__(self, msg)
self.kwargs = kwargs


class InvalidParametersException(ServiceCatalogCommandError):
fmt = "An error occurred (InvalidParametersException) : {message}"
35 changes: 35 additions & 0 deletions awscli/customizations/servicecatalog/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from awscli.customizations.commands import BasicCommand
from awscli.customizations.servicecatalog import helptext
from awscli.customizations.servicecatalog.generateproduct \
import GenerateProductCommand
from awscli.customizations.servicecatalog.generateprovisioningartifact \
import GenerateProvisioningArtifactCommand


class GenerateCommand(BasicCommand):
NAME = "generate"
DESCRIPTION = helptext.GENERATE_COMMAND
SUBCOMMANDS = [
{'name': 'product',
'command_class': GenerateProductCommand},
{'name': 'provisioning-artifact',
'command_class': GenerateProvisioningArtifactCommand}
]

def _run_main(self, parsed_args, parsed_globals):
if parsed_args.subcommand is None:
raise ValueError("usage: aws [options] <command> <subcommand> "
"[parameters]\naws: error: too few arguments")
51 changes: 51 additions & 0 deletions awscli/customizations/servicecatalog/generatebase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

from awscli.customizations.commands import BasicCommand
from awscli.customizations.servicecatalog.utils \
import make_url, get_s3_path
from awscli.customizations.s3uploader import S3Uploader
from awscli.customizations.servicecatalog import exceptions


class GenerateBaseCommand(BasicCommand):

def _run_main(self, parsed_args, parsed_globals):
self.region = self.get_and_validate_region(parsed_globals)
self.s3_client = self._session.create_client(
's3',
region_name=self.region,
endpoint_url=parsed_globals.endpoint_url,
verify=parsed_globals.verify_ssl
)
self.s3_uploader = S3Uploader(self.s3_client,
parsed_args.bucket_name,
self.region,
force_upload=True)
self.s3_uploader.upload(parsed_args.file_path,
get_s3_path(parsed_args.file_path))

def get_and_validate_region(self, parsed_globals):
region = parsed_globals.region
if region is None:
region = self._session.get_config_variable('region')
if region not in self._session.get_available_regions('servicecatalog'):
raise exceptions.InvalidParametersException(
message="Region {0} is not supported".format(
parsed_globals.region))
return region

def create_s3_url(self, bucket_name, file_path):
return make_url(self.region,
bucket_name,
get_s3_path(file_path))
165 changes: 165 additions & 0 deletions awscli/customizations/servicecatalog/generateproduct.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

import sys

from awscli.customizations.servicecatalog import helptext
from awscli.customizations.servicecatalog.generatebase \
import GenerateBaseCommand
from botocore.compat import json


class GenerateProductCommand(GenerateBaseCommand):
NAME = "product"
DESCRIPTION = helptext.PRODUCT_COMMAND_DESCRIPTION
ARG_TABLE = [
{
'name': 'product-name',
'required': True,
'help_text': helptext.PRODUCT_NAME
},
{
'name': 'product-owner',
'required': True,
'help_text': helptext.OWNER
},
{
'name': 'product-type',
'required': True,
'help_text': helptext.PRODUCT_TYPE,
'choices': ['CLOUD_FORMATION_TEMPLATE', 'MARKETPLACE']
},
{
'name': 'product-description',
'required': False,
'help_text': helptext.PRODUCT_DESCRIPTION
},
{
'name': 'product-distributor',
'required': False,
'help_text': helptext.DISTRIBUTOR
},
{
'name': 'tags',
'required': False,
'schema': {
'type': 'array',
'items': {
'type': 'string'
}
},
'default': [],
'synopsis': '--tags Key=key1,Value=value1 Key=key2,Value=value2',
'help_text': helptext.TAGS
},
{
'name': 'file-path',
'required': True,
'help_text': helptext.FILE_PATH
},
{
'name': 'bucket-name',
'required': True,
'help_text': helptext.BUCKET_NAME
},
{
'name': 'support-description',
'required': False,
'help_text': helptext.SUPPORT_DESCRIPTION
},
{
'name': 'support-email',
'required': False,
'help_text': helptext.SUPPORT_EMAIL
},
{
'name': 'provisioning-artifact-name',
'required': True,
'help_text': helptext.PA_NAME
},
{
'name': 'provisioning-artifact-description',
'required': True,
'help_text': helptext.PA_DESCRIPTION
},
{
'name': 'provisioning-artifact-type',
'required': True,
'help_text': helptext.PA_TYPE,
'choices': [
'CLOUD_FORMATION_TEMPLATE',
'MARKETPLACE_AMI',
'MARKETPLACE_CAR'
]
}
]

def _run_main(self, parsed_args, parsed_globals):
super(GenerateProductCommand, self)._run_main(parsed_args,
parsed_globals)
self.region = self.get_and_validate_region(parsed_globals)

self.s3_url = self.create_s3_url(parsed_args.bucket_name,
parsed_args.file_path)
self.scs_client = self._session.create_client(
'servicecatalog', region_name=self.region,
endpoint_url=parsed_globals.endpoint_url,
verify=parsed_globals.verify_ssl
)

response = self.create_product(self.build_args(parsed_args,
self.s3_url),
parsed_globals)
sys.stdout.write(json.dumps(response, indent=2, ensure_ascii=False))

return 0

def create_product(self, args, parsed_globals):
response = self.scs_client.create_product(**args)
if 'ResponseMetadata' in response:
del response['ResponseMetadata']
return response

def _extract_tags(self, args_tags):
tags = []
for tag in args_tags:
tags.append(dict(t.split('=') for t in tag.split(',')))
return tags

def build_args(self, parsed_args, s3_url):
args = {
"Name": parsed_args.product_name,
"Owner": parsed_args.product_owner,
"ProductType": parsed_args.product_type,
"Tags": self._extract_tags(parsed_args.tags),
"ProvisioningArtifactParameters": {
'Name': parsed_args.provisioning_artifact_name,
'Description': parsed_args.provisioning_artifact_description,
'Info': {
'LoadTemplateFromURL': s3_url
},
'Type': parsed_args.provisioning_artifact_type
}
}

# Non-required args
if parsed_args.support_description:
args["SupportDescription"] = parsed_args.support_description
if parsed_args.product_description:
args["Description"] = parsed_args.product_description
if parsed_args.support_email:
args["SupportEmail"] = parsed_args.support_email
if parsed_args.product_distributor:
args["Distributor"] = parsed_args.product_distributor

return args
Loading

0 comments on commit 52f732c

Please sign in to comment.