Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Elastic Beanstalk deploy action for CodePipeline #2516

Closed
kianris opened this issue May 10, 2019 · 39 comments · Fixed by #22135
Closed

Add Elastic Beanstalk deploy action for CodePipeline #2516

kianris opened this issue May 10, 2019 · 39 comments · Fixed by #22135
Assignees
Labels
@aws-cdk/aws-codepipeline Related to AWS CodePipeline effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p1

Comments

@kianris
Copy link

kianris commented May 10, 2019

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

  • Input Artifact
  • Application Name
  • Environment Name
@kianris kianris added the gap label May 10, 2019
@skinny85 skinny85 added the @aws-cdk/aws-codepipeline Related to AWS CodePipeline label May 13, 2019
@skinny85
Copy link
Contributor

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
Copy link

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

@skinny85
Copy link
Contributor

skinny85 commented Jul 24, 2019

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');
    }
}

@skinny85 skinny85 self-assigned this Aug 12, 2019
@SomayaB SomayaB added feature-request A feature should be added or improved. and removed gap labels Nov 11, 2019
@michaelday008
Copy link

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

@dhirajkhodade
Copy link

dhirajkhodade commented Jan 27, 2020

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
Copy link
Contributor

skinny85 commented Jan 27, 2020

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:

options.role.addToPolicy(new iam.PolicyStatement({
resources: [this.deploymentGroup.application.applicationArn],
actions: ['codedeploy:GetApplicationRevision', 'codedeploy:RegisterApplicationRevision']
}));

@dhirajkhodade
Copy link

dhirajkhodade commented Jan 29, 2020

Thanks @skinny85 this helped me resolve access issue.

@skinny85 skinny85 added effort/large Large work item – several weeks of effort gap labels Feb 6, 2020
@SomayaB SomayaB removed the gap label Feb 25, 2020
@Jaimin-Patel30
Copy link

Jaimin-Patel30 commented Jul 18, 2020

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:*'
            ],
          }));

@skinny85 skinny85 added effort/medium Medium work item – several days of effort and removed effort/large Large work item – several weeks of effort labels Jul 20, 2020
@skinny85 skinny85 added the p2 label Sep 11, 2020
@FernandoCaletti
Copy link

FernandoCaletti commented Sep 22, 2020

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
Copy link

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

@FernandoCaletti
Copy link

@nsquires413 I try translate, but doesn't work

@nsquires413
Copy link

Yeah I get the same

@brianhhq
Copy link

require this feature +1

@cudba
Copy link

cudba commented Oct 30, 2020

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
Copy link

rangat commented Oct 30, 2020

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
Copy link
Contributor

skinny85 commented Nov 2, 2020

Not really @rangat , sorry.

What was the problem that you encountered?

@kafka399
Copy link

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
Copy link
Contributor

@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.

@skinny85
Copy link
Contributor

skinny85 commented Jun 2, 2021

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: aws/jsii#2870, let's see what they say.

@Nemanjalj66
Copy link

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
Copy link
Contributor

skinny85 commented Jun 3, 2021

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

@skinny85
Copy link
Contributor

skinny85 commented Jun 4, 2021

Apparently, you need to extend the Amazon.JSII.Runtime.Deputy.DeputyBase class to implement an interface in JSII: aws/jsii#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
Copy link

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
Copy link

fbouteruche commented Jul 7, 2021

@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
Copy link
Contributor

RomainMuller commented Jul 8, 2021

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
Copy link
Contributor

skinny85 commented Jul 9, 2021

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
Copy link

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
Copy link

A-ndy-git commented Nov 15, 2021

+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
Copy link
Contributor

@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
Copy link

A-ndy-git commented Nov 15, 2021

@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
Copy link

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 github-actions bot added p1 and removed p2 labels Apr 10, 2022
@TheRealAmazonKendra TheRealAmazonKendra self-assigned this Jun 15, 2022
@mergify mergify bot closed this as completed in #22135 Oct 4, 2022
mergify bot pushed a commit that referenced this issue Oct 4, 2022
Add  ElasticBeanstalk Deploy action for CodePipeline.

closes #2516


----

### All Submissions:

* [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@github-actions
Copy link

github-actions bot commented Oct 4, 2022

⚠️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.

arewa pushed a commit to arewa/aws-cdk that referenced this issue Oct 8, 2022
…22135)

Add  ElasticBeanstalk Deploy action for CodePipeline.

closes aws#2516


----

### All Submissions:

* [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
homakk pushed a commit to homakk/aws-cdk that referenced this issue Dec 1, 2022
…22135)

Add  ElasticBeanstalk Deploy action for CodePipeline.

closes aws#2516


----

### All Submissions:

* [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-codepipeline Related to AWS CodePipeline effort/medium Medium work item – several days of effort feature-request A feature should be added or improved. p1
Projects
None yet
Development

Successfully merging a pull request may close this issue.