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.59k stars 3.89k forks source link

Add Elastic Beanstalk deploy action for CodePipeline #2516

Closed kianris closed 2 years ago

kianris commented 5 years ago

A new software.amazon.awscdk.services.codepipeline.Action for deploys to Elastic Beanstalk. Requires:

skinny85 commented 5 years ago

Thanks for bringing this to our attention @kianris . We're not planning to work on Elastic BeanStalk in the very near future, but it's good to have this on our backlog.

Thanks, Adam

yogiraj07 commented 5 years ago

+1 for this feature. Is there any workaround? How to achieve Elastic Beanstalk as deployment provider using CDK?

skinny85 commented 5 years ago

To unblock yourself, you can create your own implementation of IAction in the meantime:

import codepipeline = require('@aws-cdk/aws-codepipeline');
import events = require('@aws-cdk/aws-events');
import { Construct } from '@aws-cdk/core';

export interface ElasticBeanStalkDeployActionProps extends codepipeline.CommonAwsActionProps {
    applicationName: string;

    environmentName: string;
}

export class ElasticBeanStalkDeployAction implements codepipeline.IAction {
    public readonly actionProperties: codepipeline.ActionProperties;
    private readonly props: ElasticBeanStalkDeployActionProps;

    constructor(props: ElasticBeanStalkDeployActionProps) {
        this.actionProperties = {
            ...props,
            provider: 'ElasticBeanstalk',
            category: codepipeline.ActionCategory.DEPLOY,
            artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 },
        };
        this.props = props;
    }

    public bind(_scope: Construct, _stage: codepipeline.IStage, _options: codepipeline.ActionBindOptions):
            codepipeline.ActionConfig {
        return {
            configuration: {
                ApplicationName: this.props.applicationName,
                EnvironmentName: this.props.environmentName,
            },
        };
    }

    public onStateChange(_name: string, _target?: events.IRuleTarget, _options?: events.RuleProps): events.Rule {
        throw new Error('unsupported');
    }
}

EDIT: actually, since the Action has an input, this needs to be something like:

import codepipeline = require('@aws-cdk/aws-codepipeline');
import events = require('@aws-cdk/aws-events');
import { Construct } from '@aws-cdk/core';

export interface ElasticBeanStalkDeployActionProps extends codepipeline.CommonAwsActionProps {
    applicationName: string;

    environmentName: string;

    input: codepipeline.Artifact;
}

export class ElasticBeanStalkDeployAction implements codepipeline.IAction {
    public readonly actionProperties: codepipeline.ActionProperties;
    private readonly props: ElasticBeanStalkDeployActionProps;

    constructor(props: ElasticBeanStalkDeployActionProps) {
        this.actionProperties = {
            ...props,
            provider: 'ElasticBeanstalk',
            category: codepipeline.ActionCategory.DEPLOY,
            artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 },
            inputs: [props.input],
        };
        this.props = props;
    }

    public bind(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
            codepipeline.ActionConfig {
        options.bucket.grantRead(options.role);

        return {
            configuration: {
                ApplicationName: this.props.applicationName,
                EnvironmentName: this.props.environmentName,
            },
        };
    }

    public onStateChange(_name: string, _target?: events.IRuleTarget, _options?: events.RuleProps): events.Rule {
        throw new Error('unsupported');
    }
}
michaelday008 commented 4 years ago

I don't see an option to vote, so +1 for this feature.

dhirajkhodade commented 4 years ago

after adding own implementation of IAction. I am getting below error in deploy step of my pipeline

Insufficient permissions The provided role does not have the elasticbeanstalk:CreateApplicationVersion permission

I am trying to add new role with assume role but my aws account user is not authorized to assume role. Is there any other way I can achieve this?

skinny85 commented 4 years ago

after adding own implementation of IAction. I am getting below error in deploy step of my pipeline

Insufficient permissions The provided role does not have the elasticbeanstalk:CreateApplicationVersion permission

I am trying to add new role with assume role but my aws account user is not authorized to assume role. Is there any other way I can achieve this?

What you're probably missing is, in your bind method, some code like:

  public bind(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
      codepipeline.ActionConfig {
    options.role.addToPolicy(new iam.PolicyStatement({
      resources: ['*'],
      actions: ['elasticbeanstalk:CreateApplicationVersion'],
    }));
  }

, similarly to what happens in different actions, example: https://github.com/aws/aws-cdk/blob/b90905d8da3c6fa6fd369f84de4fb2645b948c87/packages/%40aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts#L44-L47

dhirajkhodade commented 4 years ago

Thanks @skinny85 this helped me resolve access issue.

Jaimin-Patel30 commented 4 years ago

Yeah. Adding permission solved my problem as well. I needed to add these premission as codepipeline deploy stage needed it to deploy to Elastic Beanstalk.

This worked for me.

options.role.addToPolicy(new iam.PolicyStatement({
            resources: ['*'],
            actions: [
                'elasticbeanstalk:*',
                'autoscaling:*',
                'elasticloadbalancing:*',
                'rds:*',
                's3:*',
                'cloudwatch:*',
                'cloudformation:*'
            ],
          }));
FernandoCaletti commented 4 years ago

I tried implement in .net, but doesn't work.

Unhandled exception. Amazon.JSII.Runtime.JsiiException: Validation failed with the following errors:
  [CdkCicdStack/cdkcicdstackPipeline] Stage 'Deploy' must have at least one action
using Amazon.CDK;
using Amazon.CDK.AWS.CodePipeline;
using Amazon.CDK.AWS.Events;
using Amazon.CDK.AWS.IAM;

namespace CdkCicd
{
    public class ElasticBeanStalkDeployAction : IAction
    {
        public IActionProperties ActionProperties { get; set; }
        private readonly ElasticBeanStalkDeployActionProps Props;

        public ElasticBeanStalkDeployAction(ElasticBeanStalkDeployActionProps props)
        {
            this.ActionProperties = new ActionProperties()
            {
                Provider = "ElasticBeanstalk",
                Category = ActionCategory.DEPLOY,
                ArtifactBounds = new ActionArtifactBounds()
                {
                    MinInputs = 1,
                    MaxInputs = 1,
                    MinOutputs = 0,
                    MaxOutputs = 0
                },
                Inputs = new[] { props.Input }
            };

            this.Props = props;
        }

        public IActionConfig Bind(Construct scope, IStage stage, IActionBindOptions options)
        {
            options.Bucket.GrantRead(options.Role);

            options.Role.AddToPrincipalPolicy(new PolicyStatement(new PolicyStatementProps() 
            {
                Resources = new [] { "*" },
                Actions = new[] 
                { 
                    "elasticbeanstalk:*",
                    "autoscaling:*",
                    "elasticloadbalancing:*",
                    "rds:*",
                    "s3:*",
                    "cloudwatch:*",
                    "cloudformation:*"
                },
            }));

            return new ActionConfig()
            {
                Configuration = new
                {
                    ApplicationName = this.Props.ApplicationName,
                    EnvironmentName = this.Props.EnvironmentName,
                }
            };
        }

        public Rule OnStateChange(string name, IRuleTarget target = null, IRuleProps options = null)
        {
            throw new System.NotImplementedException();
        }
    }
}

using Amazon.CDK.AWS.CodePipeline;

namespace CdkCicd
{
    public class ElasticBeanStalkDeployActionProps : CommonAwsActionProps
    {
        public string ApplicationName { get; set; }
        public string EnvironmentName { get; set; }
        public Artifact_ Input { get; set; }
    }
}

var pipeline = new Pipeline(this, "cdkcicdstackPipeline", new PipelineProps()
            {
                PipelineName = "cdkcicdstack",
                Stages = new[]
                {
                    new Amazon.CDK.AWS.CodePipeline.StageProps()
                    {
                        StageName = "Source",
                        Actions = new []
                        {
                            new S3SourceAction(new S3SourceActionProps() 
                            {
                                Bucket = bucket,
                                BucketKey = "Teste.zip",
                                Output = new Artifact_("SourceArtifact"),
                                ActionName = "Source"
                            })
                        }
                    },
                    new Amazon.CDK.AWS.CodePipeline.StageProps()
                    {
                        StageName = "Deploy",
                        Actions = new []
                        {
                            new ElasticBeanStalkDeployAction(new ElasticBeanStalkDeployActionProps()
                            {
                                Input = new Artifact_("SourceArtifact"),
                                ActionName = "Deploy",
                                ApplicationName = "AuthService",
                                EnvironmentName = "AuthService-Testing2"
                            })
                        }
                    },
                },
            });
nsquires413 commented 4 years ago

Also would like to see a .net implementation of this workaround if anyone can translate the typescript example to C#

FernandoCaletti commented 4 years ago

@nsquires413 I try translate, but doesn't work

nsquires413 commented 4 years ago

Yeah I get the same

brianhhq commented 4 years ago

require this feature +1

cudba commented 3 years ago
export class ElasticBeanStalkDeployAction implements codepipeline.IAction {
    public readonly actionProperties: codepipeline.ActionProperties;
    private readonly props:

i added also the role prop to actionProperties,

export interface ElasticBeanstalkDeployActionProps {
    ebsApplicationName: string;
    ebsEnvironmentName: string;
    input: Artifact;
    role?: IRole;
}

then you can pass in the role from the pipeline itself

pipeline.addStage({
            stageName: 'Deploy',
            actions: [
                new ElasticBeanstalkDeployAction({
                    ebsEnvironmentName: elasticBeanstalk.environment.environmentName!!,
                    ebsApplicationName: elasticBeanstalk.application.applicationName!!,
                    input: buildOutput,
                    role: pipeline.role,
                }),
            ],
        });

while adding the AWSElasticBeanstalkFullAccess role to the pipeline

pipeline.role.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AWSElasticBeanstalkFullAccess'));

So you dont need to do this @Jaimin-Patel30

options.role.addToPolicy(new iam.PolicyStatement({
            resources: ['*'],
            actions: [
                'elasticbeanstalk:*',
                'autoscaling:*',
                'elasticloadbalancing:*',
                'rds:*',
                's3:*',
                'cloudwatch:*',
                'cloudformation:*'
            ],
          }));

and it will not create another policy in IAM.

nothing too big but might help someone, i initially thought the pipline role would be injected in the bind function, but this is not the case. if you dont provide a role in the actionProperties it will create a new one, so you have to add the policy again in the bind function

rangat commented 3 years ago

I also was trying to implement this in python but didn't have luck. Do you guys have an estimate for when the Elastic Beanstalk Deploy Action might be available?

skinny85 commented 3 years ago

Not really @rangat , sorry.

What was the problem that you encountered?

kafka399 commented 3 years ago

Thanks for bringing this to our attention @kianris . We're not planning to work on Elastic BeanStalk in the very near future, but it's good to have this on our backlog.

Thanks, Adam @skinny85 what is the reason not to provide the implementation? It was more than one year this question is open, people are interested in it and we have AWS examples integrating codepipeline with beanstalk.

skinny85 commented 3 years ago

@kafka399 the main problem is that we don't have an L2 Construct Library for BeanStalk, which makes it challenging to support a good CodePipeline Action abstraction for it.

I'd be glad to help someone with guidance on the matter, if they wanted to submit us a PR adding this feature.

kafka399 commented 3 years ago

Here is a working implementation in Python, thanks @RomainMuller for the support!

from aws_cdk import (
    aws_codepipeline as codepipeline,
    core
)
from  aws_cdk.aws_codepipeline import IAction
import jsii

class ElasticBeanStalkDeployActionProps(codepipeline.CommonAwsActionProps):

    @property
    def environment_name(self):
        return self._environment_name

    @property
    def application_name(self):
        return self._application_name

    @property
    def role(self):
        return self._role

    def __init__(self,  *, environment_name, application_name, role) -> None:
        self._environment_name=environment_name
        self._application_name=application_name
        self._role=role

@jsii.implements(IAction)
class ElasticBeanStalkDeployAction():

    @property
    def props(self) -> ElasticBeanStalkDeployActionProps:
        return self._props

    @property
    def action_properties(self) -> codepipeline.ActionProperties:
        return self._action_properties

    @action_properties.setter
    def action_properties(self, value):
        print("setter of x called")
        self._action_properties = value

    def __init__(self, *args, **kwargs) -> None:
        super().__init__()
        print(kwargs)

        self._props = ElasticBeanStalkDeployActionProps(
            application_name=kwargs['application_name'],
            environment_name=kwargs['environment_name'],
            role=kwargs['role']
        )
        self._action_properties = codepipeline.ActionProperties(
            provider="ElasticBeanstalk",
            category=codepipeline.ActionCategory.DEPLOY,
            inputs=[kwargs['input']],
            action_name=kwargs['action_name'],
            role=kwargs['role'],
            artifact_bounds=codepipeline.ActionArtifactBounds(
                max_inputs=1,
                max_outputs=0,
                min_inputs=1,
                min_outputs=0
            )
        )

    def bind(self, scope: core.Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions) -> codepipeline.ActionConfig:

        return codepipeline.ActionConfig(
            configuration={
                'ApplicationName': self.props.application_name,
                'EnvironmentName': self.props.environment_name

            }
        )

    def on_state_change(self, name, target=None, *, description=None, enabled=None, event_bus=None, event_pattern=None,
                        rule_name=None, schedule=None, targets=None):
        print('on_state_change')

And then you call add this call to your pipeline:

codepipeline.StageProps(stage_name="DeployBean",
                  actions=[
                      ElasticBeanStalkDeployAction(

                          action_name='Deploy',
                          role=pipeline_role,
                          application_name=APP_NAME,
                          run_order=1,
                          input=source_output,
                          environment_name=ENV_NAME

                      )])
thinkOfaNumber commented 3 years ago

I've tried the code supplied by @skinny85 (thanks) and I've tried both suggestions (#1 and #2) to add permissions to the role but I get this error in the deployment action:

Action execution failed Deployment completed, but with errors: During an aborted deployment, some instances may have deployed the new application version. To ensure all instances are running the same version, re-deploy the appropriate application version. Failed to deploy application. Unsuccessful command execution on instance id(s) '...'. Aborting the operation. [Instance: ... ConfigSet: Infra-WriteRuntimeConfig, Infra-EmbeddedPreBuild, Hook-PreAppDeploy, Infra-EmbeddedPostBuild, Hook-EnactAppDeploy, Hook-PostAppDeploy] Command failed on instance. Return code: 1 Output: null. Error occurred during build: Command hooks failed Deployment Failed: Unexpected Exception

can anyone help with that?

Right now I'm just adding any roles I can think of:

            resources: ['*'],
            actions: [
                'elasticbeanstalk:*',
                'autoscaling:*',
                'elasticloadbalancing:*',
                'rds:*',
                's3:*',
                'cloudwatch:*',
                'cloudformation:*',
                'codedeploy:GetApplicationRevision',
                'codedeploy:RegisterApplicationRevision'
            ],
          }));
cudba commented 3 years ago

@thinkOfaNumber

think only the AWSElasticBeanstalkFullAccess policy is required to deploy to ELB

     deployActionRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName('AWSElasticBeanstalkFullAccess'));

this is my deploy action

export interface ElasticBeanstalkDeployActionProps {
    id: string;
    ebsApplicationName: string;
    ebsEnvironmentName: string;
    input: Artifact;
    role?: IRole;
}

export class ElasticBeanstalkDeployAction implements IAction {
    readonly actionProperties: ActionProperties;
    private readonly props: ElasticBeanstalkDeployActionProps;

    constructor(props: ElasticBeanstalkDeployActionProps) {
        this.actionProperties = {
            ...props,
            category: ActionCategory.DEPLOY,
            actionName: `${props.id}-elasticbeanstalk-deploy-action`,
            owner: 'AWS',
            provider: 'ElasticBeanstalk',

            artifactBounds: {
                minInputs: 1,
                maxInputs: 1,
                minOutputs: 0,
                maxOutputs: 0,
            },
            inputs: [props.input],
        };
        this.props = props;
    }
    bind(scope: Construct, stage: IStage, options: ActionBindOptions): ActionConfig {
        options.bucket.grantRead(options.role);
        return {
            configuration: {
                ApplicationName: this.props.ebsApplicationName,
                EnvironmentName: this.props.ebsEnvironmentName,
            },
        };
    }

    onStateChange(name: string, target?: IRuleTarget, options?: RuleProps): Rule {
        throw new Error('not supported');
    }
}

here is how I use it

                new ElasticBeanstalkDeployAction({
                    id: 'my-elb-app',
                    ebsEnvironmentName: config.environment.environmentName!!,
                    ebsApplicationName: config.application.applicationName!!,
                    input: build_output_artifact,
                    role: deployActionRole,
                }),

hope it helps

thinkOfaNumber commented 3 years ago

thanks @cudba I had tried that, but turns out the error was due to something completely different...

I forgot to include the aws-windows-deployment-manifest.json which is required for an IIS app.

For future readers, I placed this inside my artifact at the root level:

{
    "manifestVersion": 1,
    "deployments": {
        "aspNetCoreWeb": [
        {
            "name": "... your app name here ...",
            "parameters": {
                "appBundle": ".",
                "iisPath": "/",
                "iisWebSite": "Default Web Site"
            }
        }
        ]
    }
}

more info on that here.

Nemanjalj66 commented 3 years ago

@skinny85 Do you maybe have an idea why the proposed approach for Typescript doesn't work for C# .net? I have the same issue as @FernandoCaletti: Unhandled exception. Amazon.JSII.Runtime.JsiiException: Validation failed with the following errors: [Pipeline] Stage 'Deploy' must have at least one action

skinny85 commented 3 years ago

@Nemanjalj66 can you show your code? That doesn't seem like an error related to the custom Action implementation...

Nemanjalj66 commented 3 years ago
public class ElasticBeanStalkDeployActionProps : CommonAwsActionProps
    {
        public string ApplicationName;
        public string EnvironmentName;
        public Artifact_ Input;
    }

public class ElasticBeanStalkDeployAction : IAction
    {
        private readonly ElasticBeanStalkDeployActionProps props;

        public ElasticBeanStalkDeployAction(ElasticBeanStalkDeployActionProps props)
        {
            this.props = props ?? throw new ArgumentNullException(nameof(props));
        }

        public IActionProperties ActionProperties => new ActionProperties()
        {
            Provider = "ElasticBeanstalk",
            Category = ActionCategory.DEPLOY,
            ArtifactBounds = new ActionArtifactBounds() { MaxInputs = 1, MinInputs = 1, MinOutputs = 0, MaxOutputs = 0 },
            Inputs = new Artifact_[] { props.Input },
            Role = props.Role,
            ActionName = props.ActionName,
            RunOrder = props.RunOrder,
            VariablesNamespace = props.VariablesNamespace,
            Owner = "Custom"
        };

        public IActionConfig Bind(Construct scope, IStage stage, IActionBindOptions options)
        {
            options.Bucket.GrantRead(options.Role);

            return new ActionConfig() {
                Configuration = new 
                {
                    ApplicationName = props.ApplicationName,
                    EnvironmentName = props.EnvironmentName,
                }
            };
        }

        public Rule OnStateChange(string name, IRuleTarget target = null, IRuleProps options = null)
        {
            throw new Exception("Unsupported");
        }
    }

Pipeline:

public PipelineStack(Construct scope, string id, string applicationName, string environmentName, IStackProps props = null) :
            base(scope, id, props)
        {
            //aBuildOutput is defined as an output of a build stage

            new Pipeline(this, "Pipeline", new PipelineProps
            {
                Stages = new[]
                {
                    new Amazon.CDK.AWS.CodePipeline.StageProps
                    {
                        StageName = "Deploy_Application",
                        Actions = new []
                        {
                            new ElasticBeanStalkDeployAction(new ElasticBeanStalkDeployActionProps() {
                                ActionName = "Deploy",
                                ApplicationName = applicationName,
                                EnvironmentName = environmentName,
                                Input = aBuildOutput
                            })
                        }
                    }
                }
            });
        }
skinny85 commented 3 years ago

Hmm, you're right @Nemanjalj66 . I tried this:

using Amazon.CDK;
using Amazon.CDK.AWS.Cognito;
using Amazon.CDK.AWS.IoT;
using Amazon.CDK.AWS.Route53;
using Amazon.CDK.AWS.CodeBuild;
using Amazon.CDK.AWS.CodeCommit;
using Amazon.CDK.AWS.CodePipeline;
using Amazon.CDK.AWS.CodePipeline.Actions;

namespace CsharpCognitoL1
{
    public class ElasticBeanStalkDeployActionProps : CommonAwsActionProps
    {
        public string ApplicationName;
        public string EnvironmentName;
        public Artifact_ Input;
    }

    public class ElasticBeanStalkDeployAction : Amazon.CDK.AWS.CodePipeline.Actions.Action
    {
        private readonly ElasticBeanStalkDeployActionProps props;

        public ElasticBeanStalkDeployAction(ElasticBeanStalkDeployActionProps props) : base(new ActionProperties
            {
                Provider = "ElasticBeanstalk",
                Category = ActionCategory.DEPLOY,
                ArtifactBounds = new ActionArtifactBounds { MaxInputs = 1, MinInputs = 1, MinOutputs = 0, MaxOutputs = 0 },
                Inputs = new Artifact_[] { props.Input },
                Role = props.Role,
                ActionName = props.ActionName,
                RunOrder = props.RunOrder,
                VariablesNamespace = props.VariablesNamespace,
                Owner = "Custom",
            })
        {
            this.props = props;
        }

        protected override IActionConfig Bound(Construct scope, IStage stage, IActionBindOptions options)
        {
            options.Bucket.GrantRead(options.Role);

            return new ActionConfig {
                Configuration = new
                {
                    ApplicationName = this.props.ApplicationName,
                    EnvironmentName = this.props.EnvironmentName,
                },
            };
        }
    }

    public class CsharpCognitoL1Stack : Stack
    {
        internal CsharpCognitoL1Stack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            var sourceOutput = new Artifact_();
            var buildOutput = new Artifact_();
            new Pipeline(this, "Pipeline", new PipelineProps
            {
                Stages = new Amazon.CDK.AWS.CodePipeline.StageProps[]
                {
                    new Amazon.CDK.AWS.CodePipeline.StageProps
                    {
                        StageName = "Source",
                        Actions = new IAction[]
                        {
                            new CodeCommitSourceAction(new CodeCommitSourceActionProps
                            {
                                ActionName = "Source",
                                Output = sourceOutput,
                                Repository = Repository.FromRepositoryName(this, "Repo", "my-repo"),
                            }),
                        },
                    },
                    new Amazon.CDK.AWS.CodePipeline.StageProps
                    {
                        StageName = "Build",
                        Actions = new IAction[]
                        {
                            new CodeBuildAction(new CodeBuildActionProps
                            {
                                ActionName = "Build",
                                Input = sourceOutput,
                                Outputs = new Artifact_[] { buildOutput },
                                Project = Project.FromProjectName(this, "Project", "my-build"),
                            }),
                        },
                    },
                    new Amazon.CDK.AWS.CodePipeline.StageProps
                    {
                        StageName = "Deploy_Application",
                        Actions = new IAction[]
                        {
                            new ElasticBeanStalkDeployAction(new ElasticBeanStalkDeployActionProps
                            {
                                ActionName = "Deploy",
                                ApplicationName = "applicationName",
                                EnvironmentName = "environmentName",
                                Input = buildOutput,
                            }),
                        },
                    },
                },
            });
        }
    }
}

But it fails with the error:

Unhandled exception. Amazon.JSII.Runtime.JsiiException: Amazon.JSII.Runtime.JsiiException: System.ArgumentException: Could not infer JSII type for .NET type '<>f__AnonymousType0`2' (Parameter 'type')
   at Amazon.JSII.Runtime.Services.Converters.FrameworkToJsiiConverter.InferType(IReferenceMap referenceMap, Type type)
   at Amazon.JSII.Runtime.Services.Converters.FrameworkToJsiiConverter.InferType(IReferenceMap referenceMap, Object value)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.ConvertAny(Type type, IReferenceMap referenceMap, Object value)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.TryConvertPrimitive(Type type, IReferenceMap referenceMap, Object value, Boolean isOptional, PrimitiveType primitiveType, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.TryConvert(IOptionalValue optionalValue, Type type, IReferenceMap referenceMap, Object value, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.FrameworkToJsiiConverter.TryConvertClass(Type type, IReferenceMap referenceMap, Object value, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.TryConvertCustomType(Type type, IReferenceMap referenceMap, Object value, Boolean isOptional, String fullyQualifiedName, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.TryConvert(IOptionalValue optionalValue, Type type, IReferenceMap referenceMap, Object value, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.FrameworkToJsiiConverter.TryConvert(IOptionalValue optionalValue, IReferenceMap referenceMap, Object value, Object& result)
   at Amazon.JSII.Runtime.CallbackExtensions.InvokeCallback(Callback callback, IReferenceMap referenceMap, IFrameworkToJsiiConverter converter, String& error)
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.Send[TRequest,TResponse](TRequest requestObject)
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.Send[TRequest,TResponse](TRequest requestObject)
   at Amazon.JSII.Runtime.Services.Client.Invoke(InvokeRequest request)
   at Amazon.JSII.Runtime.Services.Client.Invoke(ObjectReference objectReference, String method, Object[] arguments)
   at Amazon.JSII.Runtime.Deputy.DeputyBase.<>c__DisplayClass17_0`1.<InvokeInstanceMethod>b__1(IClient client, Object[] args)
   at Amazon.JSII.Runtime.Deputy.DeputyBase.<InvokeMethodCore>g__GetResult|18_0[T](<>c__DisplayClass18_0`1& )
   at Amazon.JSII.Runtime.Deputy.DeputyBase.InvokeMethodCore[T](JsiiMethodAttribute methodAttribute, Object[] arguments, Func`3 beginFunc, Func`3 invokeFunc)
   at Amazon.JSII.Runtime.Deputy.DeputyBase.InvokeInstanceMethod[T](Type[] parameterTypes, Object[] arguments, String methodName)
   at Amazon.CDK.AWS.CodePipeline.Action.Bind(Construct scope, IStage stage, IActionBindOptions options)
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.Send[TRequest,TResponse](TRequest requestObject)
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.Send[TRequest,TResponse](TRequest requestObject)
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.Send[TRequest,TResponse](TRequest requestObject)
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.Send[TRequest,TResponse](TRequest requestObject)
   at Amazon.JSII.Runtime.Services.Client.Create(CreateRequest request)
   at Amazon.JSII.Runtime.Services.Client.Create(String fullyQualifiedName, Object[] arguments, Override[] overrides, String[] interfaces)
   at Amazon.JSII.Runtime.Deputy.DeputyBase..ctor(DeputyProps props)
   at Constructs.Construct..ctor(DeputyProps props)
   at Amazon.CDK.Construct..ctor(DeputyProps props)
   at Amazon.CDK.Resource..ctor(DeputyProps props)
   at Amazon.CDK.AWS.CodePipeline.Pipeline..ctor(Construct scope, String id, IPipelineProps props)
   at CsharpCognitoL1.CsharpCognitoL1Stack..ctor(Construct scope, String id, IStackProps props) in /Users/adamruka/workplace/cdk/on-call/csharp-cognito-l1/src/CsharpCognitoL1/CsharpCognitoL1Stack.cs:line 59
   at CsharpCognitoL1.Program.Main(String[] args) in /Users/adamruka/workplace/cdk/on-call/csharp-cognito-l1/src/CsharpCognitoL1/Program.cs:line 13
Subprocess exited with error 134

I've raised an error to the JSII project: https://github.com/aws/jsii/issues/2870, let's see what they say.

Nemanjalj66 commented 3 years ago

Thank you for your answer @skinny85 ! The reason why you got a different error is that your ElasticBeanStalkDeployAction is derived from the abstract class Action. In the code I posted, you can see that ElasticBeanStalkDeployAction actually implements the IAction interface. I have also tried the approach you did and got the same error, but I've been hoping that implementing the IAction interface would help me overcome the issue. Do you have an idea of what might be a problem with the approach of implementing the IAction interface and do you have any ideas for alternatives?

skinny85 commented 3 years ago

Yeah, you're right @Nemanjalj66, I was able to reproduce that error... this looks like another JSII bug, unfortunately 😕.

skinny85 commented 3 years ago

Apparently, you need to extend the Amazon.JSII.Runtime.Deputy.DeputyBase class to implement an interface in JSII: https://github.com/aws/jsii/issues/1029

However, I have tried it, and I still didn't work:

using System;
using Amazon.JSII.Runtime.Deputy;
using Amazon.CDK;
using Amazon.CDK.AWS.Cognito;
using Amazon.CDK.AWS.IoT;
using Amazon.CDK.AWS.Route53;
using Amazon.CDK.AWS.CodeBuild;
using Amazon.CDK.AWS.CodeCommit;
using Amazon.CDK.AWS.CodePipeline;
using Amazon.CDK.AWS.CodePipeline.Actions;
using Amazon.CDK.AWS.Events;

namespace CsharpCognitoL1
{
    public class ElasticBeanStalkDeployActionProps : CommonAwsActionProps
    {
        public string ApplicationName;
        public string EnvironmentName;
        public Artifact_ Input;
    }

    public class ElasticBeanStalkDeployAction : DeputyBase, IAction
    {
        private readonly ElasticBeanStalkDeployActionProps props;

        public ElasticBeanStalkDeployAction(ElasticBeanStalkDeployActionProps props)
        {
            this.props = props;
        }

        public IActionProperties ActionProperties => new ActionProperties()
        {
            Provider = "ElasticBeanstalk",
            Category = ActionCategory.DEPLOY,
            ArtifactBounds = new ActionArtifactBounds() { MaxInputs = 1, MinInputs = 1, MinOutputs = 0, MaxOutputs = 0 },
            Inputs = new Artifact_[] { props.Input },
            Role = props.Role,
            ActionName = props.ActionName,
            RunOrder = props.RunOrder,
            VariablesNamespace = props.VariablesNamespace,
            Owner = "Custom"
        };

        public IActionConfig Bind(Construct scope, IStage stage, IActionBindOptions options)
        {
            options.Bucket.GrantRead(options.Role);

            return new ActionConfig() {
                Configuration = new
                {
                    ApplicationName = props.ApplicationName,
                    EnvironmentName = props.EnvironmentName,
                }
            };
        }

        public Rule OnStateChange(string name, IRuleTarget target = null, IRuleProps options = null)
        {
            throw new Exception("Unsupported");
        }
    }

    public class CsharpCognitoL1Stack : Stack
    {
        internal CsharpCognitoL1Stack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            var sourceOutput = new Artifact_();
            var buildOutput = new Artifact_();
            new Pipeline(this, "Pipeline", new PipelineProps
            {
                Stages = new Amazon.CDK.AWS.CodePipeline.StageProps[]
                {
                    new Amazon.CDK.AWS.CodePipeline.StageProps
                    {
                        StageName = "Source",
                        Actions = new IAction[]
                        {
                            new CodeCommitSourceAction(new CodeCommitSourceActionProps
                            {
                                ActionName = "Source",
                                Output = sourceOutput,
                                Repository = Repository.FromRepositoryName(this, "Repo", "my-repo"),
                            }),
                        },
                    },
                    new Amazon.CDK.AWS.CodePipeline.StageProps
                    {
                        StageName = "Build",
                        Actions = new IAction[]
                        {
                            new CodeBuildAction(new CodeBuildActionProps
                            {
                                ActionName = "Build",
                                Input = sourceOutput,
                                Outputs = new Artifact_[] { buildOutput },
                                Project = Project.FromProjectName(this, "Project", "my-build"),
                            }),
                        },
                    },
                    new Amazon.CDK.AWS.CodePipeline.StageProps
                    {
                        StageName = "Deploy_Application",
                        Actions = new IAction[]
                        {
                            new ElasticBeanStalkDeployAction(new ElasticBeanStalkDeployActionProps
                            {
                                ActionName = "Deploy",
                                ApplicationName = "applicationName",
                                EnvironmentName = "environmentName",
                                Input = buildOutput,
                            }),
                        },
                    },
                },
            });
        }
    }
}

Error:

$ npx cdk synth 
Unhandled exception. Amazon.JSII.Runtime.JsiiException: System.ArgumentException: Could not infer JSII type for .NET type '<>f__AnonymousType0`2' (Parameter 'type')
   at Amazon.JSII.Runtime.Services.Converters.FrameworkToJsiiConverter.InferType(IReferenceMap referenceMap, Type type)
   at Amazon.JSII.Runtime.Services.Converters.FrameworkToJsiiConverter.InferType(IReferenceMap referenceMap, Object value)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.ConvertAny(Type type, IReferenceMap referenceMap, Object value)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.TryConvertPrimitive(Type type, IReferenceMap referenceMap, Object value, Boolean isOptional, PrimitiveType primitiveType, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.TryConvert(IOptionalValue optionalValue, Type type, IReferenceMap referenceMap, Object value, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.FrameworkToJsiiConverter.TryConvertClass(Type type, IReferenceMap referenceMap, Object value, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.TryConvertCustomType(Type type, IReferenceMap referenceMap, Object value, Boolean isOptional, String fullyQualifiedName, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.ValueConverter.TryConvert(IOptionalValue optionalValue, Type type, IReferenceMap referenceMap, Object value, Object& result)
   at Amazon.JSII.Runtime.Services.Converters.FrameworkToJsiiConverter.TryConvert(IOptionalValue optionalValue, IReferenceMap referenceMap, Object value, Object& result)
   at Amazon.JSII.Runtime.CallbackExtensions.InvokeCallback(Callback callback, IReferenceMap referenceMap, IFrameworkToJsiiConverter converter, String& error)
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.TryDeserialize[TResponse](String responseJson)
   at Amazon.JSII.Runtime.Services.Client.ReceiveResponse[TResponse]()
   at Amazon.JSII.Runtime.Services.Client.Create(CreateRequest request)
   at Amazon.JSII.Runtime.Services.Client.Create(String fullyQualifiedName, Object[] arguments, Override[] overrides, String[] interfaces)
   at Amazon.JSII.Runtime.Deputy.DeputyBase..ctor(DeputyProps props)
   at Constructs.Construct..ctor(DeputyProps props)
   at Amazon.CDK.Construct..ctor(DeputyProps props)
   at Amazon.CDK.Resource..ctor(DeputyProps props)
   at Amazon.CDK.AWS.CodePipeline.Pipeline..ctor(Construct scope, String id, IPipelineProps props)
   at CsharpCognitoL1.CsharpCognitoL1Stack..ctor(Construct scope, String id, IStackProps props) in /Users/adamruka/workplace/cdk/on-call/csharp-cognito-l1/src/CsharpCognitoL1/CsharpCognitoL1Stack.cs:line 142
   at CsharpCognitoL1.Program.Main(String[] args) in /Users/adamruka/workplace/cdk/on-call/csharp-cognito-l1/src/CsharpCognitoL1/Program.cs:line 13
Subprocess exited with error 134
Nemanjalj66 commented 3 years ago

Thank you again for the answer! I have tried it in my implementation and it also fails with the same exception as you mentioned.

fbouteruche commented 3 years ago

@Nemanjalj66

As a workaround for your .NET issue, you can fallback to raw overrides on your pipeline like this

            CfnPipeline cfnPipeline = pipeline.Node.DefaultChild as CfnPipeline;
            cfnPipeline.AddPropertyOverride("Stages.2", new Dictionary<string, object>
            {
                { "Name", "Deploy" },
                { "Actions", new object[]
                    {
                        new Dictionary<string, object>
                        {
                            { "Name", "Deploy" },
                            { "ActionTypeId", new Dictionary<string, object>
                                {
                                    { "Category", "Deploy" },
                                    { "Owner", "AWS" },
                                    { "Provider", "ElasticBeanstalk" },
                                    { "Version", "1" }
                                }
                            },
                            { "Configuration", new Dictionary<string, string>()
                                {
                                    {"ApplicationName", "<YOUR_BEANSTALK_APPLICATION_NAME>" },
                                    {"EnvironmentName", "<YOUR_BEANSTALK_ENVIRONMENT_NAME>" }
                                }
                            },
                            { "InputArtifacts", new object[]
                                {
                                    new Dictionary<string, string>{
                                        { "Name", "<YOUR_INPUT_ARTIFACT_NAME>" }
                                    }
                                }
                            },
                            { "Namespace", "DeployVariables" }
                        }
                    }
                }
            });
RomainMuller commented 3 years ago

The culprit is likely with this type of syntax:

        public IActionConfig Bind(Construct scope, IStage stage, IActionBindOptions options)
        {
            options.Bucket.GrantRead(options.Role);

            return new ActionConfig() {
                Configuration = new // <-- HERE
                {
                    ApplicationName = props.ApplicationName,
                    EnvironmentName = props.EnvironmentName,
                }
            };
        }

Returning an anonymous object here (new { ... }) is probably what causes the problem. This particular value is typed any in TypeScript, and hence I suspect it could be solved by returning a Dictionary<string, object> or a JObject instead.

skinny85 commented 3 years ago

This is brilliant @RomainMuller! I can confirm switching to dictionary works, like in this code:

using System.Collections.Generic;
using Amazon.CDK;
using Amazon.CDK.AWS.CodeBuild;
using Amazon.CDK.AWS.CodeCommit;
using Amazon.CDK.AWS.CodePipeline;
using Amazon.CDK.AWS.CodePipeline.Actions;

public class ElasticBeanStalkDeployActionProps : CommonAwsActionProps
{
    public string ApplicationName;
    public string EnvironmentName;
    public Artifact_ Input;
}

public class ElasticBeanStalkDeployAction : Amazon.CDK.AWS.CodePipeline.Actions.Action
{
    private readonly ElasticBeanStalkDeployActionProps props;

    public ElasticBeanStalkDeployAction(ElasticBeanStalkDeployActionProps props) : base(new ActionProperties
        {
            Provider = "ElasticBeanstalk",
            Category = ActionCategory.DEPLOY,
            ArtifactBounds = new ActionArtifactBounds { MaxInputs = 1, MinInputs = 1, MinOutputs = 0, MaxOutputs = 0 },
            Inputs = new Artifact_[] { props.Input },
            Role = props.Role,
            ActionName = props.ActionName,
            RunOrder = props.RunOrder,
            VariablesNamespace = props.VariablesNamespace,
            Owner = "Custom",
        })
    {
        this.props = props;
    }

    protected override IActionConfig Bound(Construct scope, IStage stage, IActionBindOptions options)
    {
        options.Bucket.GrantRead(options.Role);

        return new ActionConfig {
            Configuration = new Dictionary<string, object>
            {
                { "ApplicationName", this.props.ApplicationName },
                { "EnvironmentName", this.props.EnvironmentName },
            },
        };
    }
}

public class MyStack : Stack
{
    internal MyStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
    {
        var sourceOutput = new Artifact_();
        var buildOutput = new Artifact_();
        new Pipeline(this, "Pipeline", new PipelineProps
        {
            Stages = new Amazon.CDK.AWS.CodePipeline.StageProps[]
            {
                new Amazon.CDK.AWS.CodePipeline.StageProps
                {
                    StageName = "Source",
                    Actions = new IAction[]
                    {
                        new CodeCommitSourceAction(new CodeCommitSourceActionProps
                        {
                            ActionName = "Source",
                            Output = sourceOutput,
                            Repository = Repository.FromRepositoryName(this, "Repo", "my-repo"),
                        }),
                    },
                },
                new Amazon.CDK.AWS.CodePipeline.StageProps
                {
                    StageName = "Build",
                    Actions = new IAction[]
                    {
                        new CodeBuildAction(new CodeBuildActionProps
                        {
                            ActionName = "Build",
                            Input = sourceOutput,
                            Outputs = new Artifact_[] { buildOutput },
                            Project = Project.FromProjectName(this, "Project", "my-build"),
                        }),
                    },
                },
                new Amazon.CDK.AWS.CodePipeline.StageProps
                {
                    StageName = "Deploy_Application",
                    Actions = new IAction[]
                    {
                        new ElasticBeanStalkDeployAction(new ElasticBeanStalkDeployActionProps
                        {
                            ActionName = "Deploy",
                            ApplicationName = "applicationName",
                            EnvironmentName = "environmentName",
                            Input = buildOutput,
                        }),
                    },
                },
            },
        });
    }
}
Darknight471 commented 3 years ago

I also was trying to implement this in python but didn't have luck. Do you guys have an estimate for when the Elastic Beanstalk Deploy Action might be available?

have you implemented now ?

A-ndy-git commented 2 years ago

+1 for this feature.

Hitting a roadblock trying to add a deployment stage to my pipeline.

Is there anyway to do this in vanilla JS?

 const deployAction = {
      actionName: 'DeployAction',
      provider: 'ElasticBeanstalk',
      category: ActionCategory.DEPLOY,
      artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 },
      applicationName: environment.environmentName,
      environmentName: environment.applicationName
    };

deployStage.addAction(deployAction);

Returns error:

/Users/*REDACTED*/Documents/*REDACTED*/TradingBotCdk/node_modules/@aws-cdk/aws-codepipeline/lib/private/rich-action.js:25
        return this.action.bind(scope, stage, options);
                           ^

TypeError: this.action.bind is not a function
skinny85 commented 2 years ago

@A-ndy-git you're missing the bind() method of the IAction interface.

Also... use TS, seriously 😜. It will make your life so much easier.

At least use it until you make this work, and then convert to JS after. But I have a feeling, after you see how much better is CDK in TS than in JS, you won't want to go back 😉.

A-ndy-git commented 2 years ago

@skinny85 thanks for your quick reply :) - as much as I'd love to use TS, this would involve migrating my entire CDK project which I don't want to do (at this stage).

EDIT - got there in the end, heres how its done:

import { ActionBindOptions, ActionCategory, ActionConfig, ActionProperties, Artifact, IAction, IStage } from '@aws-cdk/aws-codepipeline';
import { IRuleTarget, Rule, RuleProps } from '@aws-cdk/aws-events';
import { IRole } from '@aws-cdk/aws-iam';
import { Construct } from '@aws-cdk/core';

export interface ElasticBeanstalkDeployActionProps {
    id: string;
    ebsApplicationName: string;
    ebsEnvironmentName: string;
    input: Artifact;
    role?: IRole;
}

export class ElasticBeanstalkDeployAction implements IAction {
    readonly actionProperties: ActionProperties;
    private readonly props: ElasticBeanstalkDeployActionProps;

    constructor(props: ElasticBeanstalkDeployActionProps) {
        this.actionProperties = {
            ...props,
            category: ActionCategory.DEPLOY,
            actionName: `${props.id}-elasticbeanstalk-deploy-action`,
            owner: 'AWS',
            provider: 'ElasticBeanstalk',

            artifactBounds: {
                minInputs: 1,
                maxInputs: 1,
                minOutputs: 0,
                maxOutputs: 0,
            },
            inputs: [props.input],
        };
        this.props = props;
    }
    bind(scope: Construct, stage: IStage, options: ActionBindOptions): ActionConfig {
        options.bucket.grantRead(options.role);
        return {
            configuration: {
                ApplicationName: this.props.ebsApplicationName,
                EnvironmentName: this.props.ebsEnvironmentName,
            },
        };
    }

    onStateChange(name: string, target?: IRuleTarget, options?: RuleProps): Rule {
        throw new Error('not supported');
    }
}

And use it via:

    const deployAction = new ElasticBeanstalkDeployAction({
      id: 'your-app-name',
      ebsEnvironmentName: environment.environmentName,
      ebsApplicationName: application.applicationName,
      input: sourceOutput,
      role: pipeline.role,
    });

    deployStage.addAction(deployAction);
github-actions[bot] commented 2 years ago

This issue has received a significant amount of attention so we are automatically upgrading its priority. A member of the community will see the re-prioritization and provide an update on the issue.

github-actions[bot] commented 2 years ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.