aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.57k stars 3.88k forks source link

Service Catalog Deploy Action for CodePipeline #3744

Closed khornberg closed 4 years ago

khornberg commented 5 years ago

:question: General Issue

The Question

What am I doing wrong when trying to implement the IAction interface since CDK lacks a Service Catalog deploy action?

I am trying to work from the instructions for Python and the code in #2516.

Is this not possible in Python?

Some of my code was taken from the classes implemented in the python package.

In raw CloudFormation yaml what I am looking for looks like:

Actions:
- Name: PublishToServiceCatalog
  RunOrder: 1
  ActionTypeId:
    Category: Deploy
    Owner: AWS
    Provider: ServiceCatalog
    Version: '1'
  InputArtifacts:
    - Name: Artifact
  Configuration:
    ConfigurationFilePath: 'service-config.json'
    ProductId:
      Ref: ServiceCatalogProduct

Thus the base question would be, how do I get that into my synthesized stack when the Pipeline StageProps expect an IAction object?

Perhaps I am thinking about all of this wrongly or something.

Environment

Other information

With

import jsii

from aws_cdk.aws_codepipeline_actions import Action

from aws_cdk.aws_codepipeline import IAction
from aws_cdk.aws_codepipeline import ActionArtifactBounds
from aws_cdk.aws_codepipeline import ActionBindOptions
from aws_cdk.aws_codepipeline import ActionCategory
from aws_cdk.aws_codepipeline import ActionProperties
from aws_cdk.aws_codepipeline import CommonAwsActionProps

class ServiceCatalogApprovalActionProps(CommonAwsActionProps):
    def __init__(self, *args, **kwargs):
        self._values = {"action_name": kwargs["action_name"]}

    @property
    def action_name(self):
        return self._values.get("action_name")

    def __eq__(self, rhs) -> bool:
        return isinstance(rhs, self.__class__) and rhs._values == self._values

    def __ne__(self, rhs) -> bool:
        return not (rhs == self)

    def __repr__(self) -> str:
        return "ServiceCatalogActionProps(%s)" % ", ".join(k + "=" + repr(v) for k, v in self._values.items())

@jsii.implements(IAction)
class ServiceCatalogAction(Action):
    def __init__(self, action_name, *args, run_order=None, **kwargs):
        artifact_bounds = ActionArtifactBounds(max_inputs=1, max_outputs=0, min_inputs=1, min_outputs=0)
        category = ActionCategory.DEPLOY
        provider = "ServiceCatalog"
        self.action_properties = ActionProperties(
            action_name=action_name,
            artifact_bounds=artifact_bounds,
            category=category,
            provider=provider
        )
        props = ServiceCatalogApprovalActionProps(action_name=action_name)
        jsii.create(ServiceCatalogAction, self, [props])

    @jsii.member(jsii_name="bound")
    def _bound(self, scope, _stage, *, bucket, role):
        options = ActionBindOptions(bucket=bucket, role=role)
        return jsii.invoke(self, "bound", [scope, _stage, options])

I get

β””β”€β”€βž€ cdk synth --path-metadata false --version-reporting false
Traceback (most recent call last):
  File "resources/app.py", line 8, in <module>
    Stack(app, REPO)
  File "/Users/kyle/.pyenv/versions/python-api-example/lib/python3.7/site-packages/jsii/_runtime.py", line 66, in __call__
    inst = super().__call__(*args, **kwargs)
  File "/Users/kyle/projects/python-api-example/resources/stack.py", line 36, in __init__
    self.create()
  File "/Users/kyle/projects/python-api-example/resources/stack.py", line 102, in create
    action_name="Publish-Service", run_order=1, input=deploy_artifact, configuration={}
  File "/Users/kyle/.pyenv/versions/python-api-example/lib/python3.7/site-packages/jsii/_runtime.py", line 66, in __call__
    inst = super().__call__(*args, **kwargs)
  File "/Users/kyle/projects/python-api-example/resources/custom_cdk.py", line 57, in __init__
    provider=provider
AttributeError: can't set attribute

With

class ServiceCatalogApprovalActionProps(CommonAwsActionProps):
    def __init__(self, *args, **kwargs):
        self._values = {"action_name": kwargs["action_name"]}
        self._values['artifact_bounds'] = ActionArtifactBounds(max_inputs=1, max_outputs=0, min_inputs=1, min_outputs=0)
        self._values['category'] = ActionCategory.DEPLOY
        self._values['provider'] = "ServiceCatalog"

    @property
    def action_name(self):
        return self._values.get("action_name")

    def __eq__(self, rhs) -> bool:
        return isinstance(rhs, self.__class__) and rhs._values == self._values

    def __ne__(self, rhs) -> bool:
        return not (rhs == self)

    def __repr__(self) -> str:
        return "ServiceCatalogActionProps(%s)" % ", ".join(k + "=" + repr(v) for k, v in self._values.items())

@jsii.implements(IAction)
class ServiceCatalogAction(Action):
    def __init__(self, action_name, *args, run_order=None, **kwargs):
        props = ServiceCatalogApprovalActionProps(action_name=action_name)
        jsii.create(ServiceCatalogAction, self, [props])

    @jsii.member(jsii_name="bound")
    def _bound(self, scope, _stage, *, bucket, role):
        options = ActionBindOptions(bucket=bucket, role=role)
        return jsii.invoke(self, "bound", [scope, _stage, options])

Stacktrace

β””β”€β”€βž€ cdk synth --path-metadata false --version-reporting false
jsii.errors.JavaScriptError:
  Error: Missing required properties for @aws-cdk/aws-codepipeline.ActionProperties: artifactBounds,category,provider
      at validateRequiredProps (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:12544:15)
      at Object.deserialize (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_em[21/1842]ii/jsii-runtime.js:12212:21)
      at Kernel._toSandbox (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7031:61)
      at /Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7084:33
      at Array.map (<anonymous>)
      at Kernel._boxUnboxParameters (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7084:19)
      at Kernel._toSandboxValues (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7069:21)
      at /Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6666:66
      at Kernel._wrapSandboxCode (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:7134:19)
      at Kernel._create (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6666:26)
      at Kernel.create (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6419:21)
      at KernelHost.processRequest (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6191:28)
      at KernelHost.run (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6137:14)
      at /Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6137:45
      at KernelHost.processRequest (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6233:16)
      at KernelHost.run (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6137:14)
      at /Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6137:45
      at KernelHost.processRequest (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6233:16)
      at KernelHost.run (/Users/kyle/.pyenv/versions/3.7.3/envs/python-api-example/lib/python3.7/site-packages/jsii/_embedded/jsii/jsii-runtime.js:6137:14)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "resources/app.py", line 8, in <module>
    Stack(app, REPO)
  File "/Users/kyle/.pyenv/versions/python-api-example/lib/python3.7/site-packages/jsii/_runtime.py", line 66, in __call__
    inst = super().__call__(*args, **kwargs)
  File "/Users/kyle/projects/python-api-example/resources/stack.py", line 36, in __init__
    self.create()
  File "/Users/kyle/projects/python-api-example/resources/stack.py", line 102, in create
    action_name="Publish-Service", run_order=1, input=deploy_artifact, configuration={}
  File "/Users/kyle/.pyenv/versions/python-api-example/lib/python3.7/site-packages/jsii/_runtime.py", line 66, in __call__
    inst = super().__call__(*args, **kwargs)
  File "/Users/kyle/projects/python-api-example/resources/custom_cdk.py", line 64, in __init__
    jsii.create(ServiceCatalogAction, self, [props])
  File "/Users/kyle/.pyenv/versions/python-api-example/lib/python3.7/site-packages/jsii/_kernel/__init__.py", line 208, in create
    overrides=overrides,
  File "/Users/kyle/.pyenv/versions/python-api-example/lib/python3.7/site-packages/jsii/_kernel/providers/process.py", line 331, in
create
    return self._process.send(request, CreateResponse)
  File "/Users/kyle/.pyenv/versions/python-api-example/lib/python3.7/site-packages/jsii/_kernel/providers/process.py", line 316, in
send
    raise JSIIError(resp.error) from JavaScriptError(resp.stack)
jsii.errors.JSIIError: Missing required properties for @aws-cdk/aws-codepipeline.ActionProperties: artifactBounds,category,provider
Subprocess exited with error 1

Pip list

aws-cdk.assets                      1.5.0
aws-cdk.aws-apigateway              1.5.0
aws-cdk.aws-applicationautoscaling  1.5.0
aws-cdk.aws-autoscaling             1.5.0
aws-cdk.aws-autoscaling-common      1.5.0
aws-cdk.aws-autoscaling-hooktargets 1.5.0
aws-cdk.aws-certificatemanager      1.5.0
aws-cdk.aws-cloudformation          1.5.0
aws-cdk.aws-cloudfront              1.5.0
aws-cdk.aws-cloudwatch              1.5.0
aws-cdk.aws-codebuild               1.5.0
aws-cdk.aws-codecommit              1.5.0
aws-cdk.aws-codedeploy              1.5.0
aws-cdk.aws-codepipeline            1.5.0
aws-cdk.aws-codepipeline-actions    1.5.0
aws-cdk.aws-ec2                     1.5.0
aws-cdk.aws-ecr                     1.5.0
aws-cdk.aws-ecr-assets              1.5.0
aws-cdk.aws-ecs                     1.5.0
aws-cdk.aws-elasticloadbalancing    1.5.0
aws-cdk.aws-elasticloadbalancingv2  1.5.0
aws-cdk.aws-events                  1.5.0
aws-cdk.aws-events-targets          1.5.0
aws-cdk.aws-iam                     1.5.0
aws-cdk.aws-kms                     1.5.0
aws-cdk.aws-lambda                  1.5.0
aws-cdk.aws-logs                    1.5.0
aws-cdk.aws-route53                 1.5.0
aws-cdk.aws-route53-targets         1.5.0
aws-cdk.aws-s3                      1.5.0
aws-cdk.aws-s3-assets               1.5.0
aws-cdk.aws-secretsmanager          1.5.0
aws-cdk.aws-servicediscovery        1.5.0
aws-cdk.aws-sns                     1.5.0
aws-cdk.aws-sns-subscriptions       1.5.0
aws-cdk.aws-sqs                     1.5.0
aws-cdk.aws-ssm                     1.5.0
aws-cdk.aws-stepfunctions           1.5.0
aws-cdk.core                        1.5.0
aws-cdk.custom-resources            1.5.0
aws-cdk.cx-api                      1.5.0
aws-cdk.region-info                 1.5.0
skinny85 commented 5 years ago

Thanks for reporting @khornberg . Just wanted to let you know we're working on this. I hope to have an update soon. Thanks for your patience.

khornberg commented 5 years ago

Very much a hack. I have figured out that I can change the synthesized template from python like so:

application = app.synth()

with open(f"{application.directory}/{repo}.template.json", "r") as f:
    template = json.load(f)
    resources = template["Resources"]
    pipeline = resources[
        [
            resource
            for resource in resources.keys()
            if resource.startswith("Pipeline") and resources[resource]["Type"] == "AWS::CodePipeline::Pipeline"
        ][0]
    ]
    service_catalog_publish = {
        "Name": "ServiceCatalogPublish",
        "Actions": [
            {
                "ActionTypeId": {"Category": "Deploy", "Owner": "AWS", "Provider": "ServiceCatalog", "Version": "1"},
                "Configuration": {
                    "TemplateFilePath": f"{deploy_artifact_path}",
                    "ProductVersionName": f"{pipeline_branch}",
                    "ProductType": "CLOUD_FORMATION_TEMPLATE",
                    "ProductVersionDescription": f"Test service for the {pipeline_branch} branch",
                    "ProductId": {"Ref": "ServiceCatalogProduct"},
                },
                "InputArtifacts": [{"Name": "DeployArtifact"}],
                "Name": "Publish-Test-Service",
                "RunOrder": 1,
            }
        ],
    }

    pipeline["Properties"]["Stages"].append(service_catalog_publish)

with open(f"{application.directory}/{repo}.template.json", "w") as f:
    json.dump(template, f)
skinny85 commented 4 years ago

@khornberg can you update to the latest CDK version, and see if the original code works now?

Thanks, Adam

khornberg commented 4 years ago

Thanks for following up on this I no longer have the original code and shifted focus from what I was using this in

skinny85 commented 4 years ago

OK. Let us know if you need anything else for this issue from our side.