├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── PR-Automation ├── README.md ├── action.yml ├── aws-client └── session.go ├── cloudfront └── invalidation.go ├── entrypoint.sh ├── examples ├── build-and-deploy-react-app.yml ├── cleanup-s3-on-pr-merge.yml ├── deploy-on-pr.yml └── deploy-pre-build-site.yml ├── github └── comment.go ├── go.mod ├── go.sum ├── logger └── logger.go ├── main.go ├── s3 ├── attach_policy.go ├── create_s3_bucket.go ├── delete_s3.go ├── deploy.go ├── deploy_and_commit.go └── enable_static_site.go ├── secret_manager └── set_env.go └── utils ├── fails.go ├── files.go ├── get_urls.go ├── parse_github_event.go ├── repo_info.go └── shell.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | action.yml 4 | Dockerfile 5 | LICENSE.md 6 | README.md 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | make-env.sh 3 | Dockertest 4 | data.json 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | # Set necessary environmet variables needed for our image 3 | ENV GO111MODULE=on \ 4 | CGO_ENABLED=0 \ 5 | GOOS=linux \ 6 | GOARCH=amd64 7 | 8 | # Move to working directory /build 9 | WORKDIR /build 10 | 11 | # Copy and download dependency using go mod 12 | COPY go.mod go.sum ./ 13 | RUN go mod download 14 | 15 | # Copy the code into the container 16 | COPY . . 17 | 18 | # Build the application 19 | RUN go build -o /s3 . 20 | 21 | ## copy only build file 22 | FROM node:14-alpine 23 | 24 | LABEL maintainer="razzkumar " 25 | LABEL version="1.0.1" 26 | LABEL repository="https://github.com/razzkumar/pr-automation-s3-utils" 27 | 28 | LABEL "com.github.actions.name"="PR Automation" 29 | LABEL "com.github.actions.description"="Deploy each PR to s3 bucket by create \ 30 | new s3 bucket and comment url to the PR and delete s3 after merge" 31 | LABEL "com.github.actions.icon"="upload-cloud" 32 | LABEL "com.github.actions.color"="green" 33 | 34 | COPY --from=builder /s3 / 35 | COPY ./entrypoint.sh / 36 | 37 | # Command to run when starting the container 38 | CMD ["/entrypoint.sh"] 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present razzkumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PR-Automation: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/razzkumar/pr-automation-with-s3/de9b7f8f3e1766b360a6731091de21d1a8ec6180/PR-Automation -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Static Site Automation 2 | 3 | ### What it is? 4 | This Gihub action that uses the [golang aws sdk](https://aws.amazon.com/sdk-for-go/) to build s3 bucket and attach policy 5 | for static website deploy the static file to that newly created s3 bucket and 6 | comment the url to the PR. To deploy static file it uses either from your 7 | repository or build during your workflow. There is [self hosted](https://github.com/razzkumar/frontend-PR-automation) tool, if Github 8 | action is not feasible. 9 | 10 | 11 | ### Best for? 12 | - Immediate feedback visually to developers or anyone interested in changes. 13 | - Reduce burden of having to build application for QA and verify the changes. 14 | - Faster iterations. 15 | 16 | ### How to use? 17 | 18 | Add `.yml` file/s such as given examples in your `.github/workflows` folder. [Refer to the documentation on workflow YAML syntax here.](https://help.github.com/en/articles/workflow-syntax-for-github-actions) 19 | 20 | ##### The following example will: 21 | - Create s3 bucket and attach policy for static site 22 | - Build the javascript/typescript frontend application with the help of 23 | given command (ex: `BUILD_COMMAND="yarn build"`) 24 | - Upload build file (static site) to s3 25 | - Comment the URL of the static site to the Pull Request 26 | - Delete the aws S3 bucket after PR is merged 27 | 28 | ##### Config file: `.github/workflows/deploy-existing.yml` 29 | 30 | ```yaml 31 | name: Next js frontend dev 32 | 33 | on: 34 | push: 35 | branches: 36 | - dev 37 | 38 | jobs: 39 | deploy: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@master 43 | - name: Build and deploy next app 44 | uses: razzkumar/pr-automation-with-s3@v1.0.2 45 | env: 46 | AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} 47 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 48 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 49 | AWS_REGION: "us-east-1" 50 | SRC_FOLDER: "out" 51 | ACTION: 'deploy' 52 | BUILD_COMMAND: "yarn build && yarn export" 53 | CLOUDFRONT_ID: ${{ secrets.CLOUDFRONT_ID }} 54 | SECRETS_MANAGER: ${{ secrets.SECRETS_MANAGER }} // name of secrets on secret manager 55 | ``` 56 | 57 | 58 | ##### Config file: `.github/workflows/deploy-on-pr.yml` 59 | 60 | ```yaml 61 | name: Deploy site to S3 And add comment to PR and delete after merge 62 | 63 | on: 64 | pull_request: 65 | branches: 66 | - master 67 | 68 | jobs: 69 | deploy: 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@master 73 | - name: Static site deploy to s3 and comment on PR 74 | uses: razzkumar/pr-automation-with-s3@v1.0.2 75 | env: 76 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 77 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 78 | GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN}} 79 | AWS_REGION: 'us-east-2' # optional: defaults to us-east-2 80 | SRC_FOLDER: 'build' # optional: defaults to build (react app) 81 | IS_BUILD: 'true' # optional: defaults to true 82 | ACTION: "create" # optional: defaults to create (option:create,delete and deploy) 83 | BUILD_COMMAND: "yarn build" # optional: defaults to `yarn build` 84 | ``` 85 | 86 | 87 | ##### Config file: `.github/workflows/cleanup-on-pr-merge.yml` 88 | 89 | ```yaml 90 | name: Delete S3 bucket after PR merge 91 | 92 | on: 93 | pull_request: 94 | types: [closed] 95 | 96 | jobs: 97 | delete: 98 | runs-on: ubuntu-latest 99 | steps: 100 | - name: Clean up temperory bucket 101 | if: github.event.pull_request.merged == true 102 | uses: razzkumar/pr-automation-with-s3@v1.0.2 103 | env: 104 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 105 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 106 | AWS_REGION: 'us-east-2' # optional: defaults to us-east-2 107 | ACTION: "delete" # Action must be delete to delete 108 | 109 | ``` 110 | 111 | - It can be used for many other purpose also,some of the [examples](https://github.com/razzkumar/pr-automation-with-s3/blob/master/examples/deploy-pre-build-site.yml) are: 112 | - Deploy prebuild app [config link](https://github.com/razzkumar/pr-automation-with-s3/blob/master/examples) 113 | - Build react app and deploy it. [config link](https://github.com/razzkumar/pr-automation-with-s3/blob/master/examples/build-and-deploy-react-app.yml) 114 | 115 | ### Configuration 116 | 117 | The following settings must be passed as environment variables as shown in the example. Sensitive information, especially `GH_ACCESS_TOKEN`,`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, should be [set as encrypted secrets](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables) — otherwise, they'll be public to anyone browsing your repository's source code and CI logs. 118 | 119 | | Key | Suggested Type | Value | Required | Default | 120 | | ------------- | ------------- | ------------- | ------------- | ------------- | 121 | | `GH_ACCESS_TOKEN` | `secrect env` | Your Github access token used while commenting PR | **YES/NO** If `ACTION: create` then it's required,otherwise it's optional | NA | 122 | | `AWS_ACCESS_KEY_ID` | `secret env` | Your AWS Access Key. [More info here.](https://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html) | **Yes** | N/A | 123 | | `AWS_SECRET_ACCESS_KEY` | `secret env` | Your AWS Secret Access Key. [More info here.](https://docs.aws.amazon.com/general/latest/gr/managing-aws-access-keys.html) | **Yes** | N/A | 124 | | `AWS_S3_BUCKET` | `secret env` | The name of the bucket you're syncing to. For example, `jarv.is` or `my-app-releases`. | **YES/NO** | - If running on PR it will genereat by tool `PR-Branch`.pr`PR-number`.auto-deploy - In the case of depoyment it required | 125 | | `AWS_REGION` | `env` | The region where you created your bucket. Set to `us-east-2` by default. [Full list of regions here.](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions) | No | `us-east-2` | 126 | | `SRC_FOLDER` | `env` | The local directory (or file) you wish to deploy to S3. For example, `public`. Defaults to `build`. | No | `build` (based on react app) | 127 | | `IS_BUILD` | `env` | This is the flag that indicate that build a project or not | No | `true` (It will run `yarn && yarn build` by default) | 128 | | `ACTION` | `env` | This is also a flag that indicate what to do (`create`:-create s3 (if not exist) bucket,build react and comment on PR,`deploy`:helps to deploy to s3,`delete`: delete the s3 bucket) | No | `create` (It will create s3 (if not exist),built the app, deploy to s3 and comment URL to PR`) | 129 | | `BUILD_COMMAND` | `env` | How to build the react app if its `npm run build` then it will run `npm install && npm run build` | No | `yarn build` (It will run `yarn && yarn build` by default) | 130 | | `CLOUDFRONT_ID` | `secret env` | id of cloudfront for invalidation | No | | 131 | | `SECRETS_MANAGER` | `env` | name of the aws secres manager key | No | | 132 | 133 | 134 | #### Note for S3 Bucket creation 135 | - It only create a s3 bucket if not `exist` 136 | - While Creating bucket for the pull_request S3 bucket name will be: `PR-Branch`.pr`PR-number`.auto-deploy 137 | - For Eg.: 138 | - if base branch is `SIG-1000` and PR number is `23` the the bucket name will be `sig-100.pr23.auto-deploy` 139 | - If we deploy app on push or (not on pull requst) like prebuild app deployment, app build and deploy then the bucket name will be `$AWS_S3_BUCKET.auto-deploy` 140 | - For Eg. 141 | - if `AWS_S3_BUCKET=dev-test-deployment` then bucket will be `dev-test-deployment.auto-deploy` 142 | 143 | ## TODO 144 | - [ ] Add tests 145 | - [ ] Add option to deploy on aws cloudfront 146 | - [ ] Design PR comment done by tool 147 | - [ ] Maintain code quality 148 | 149 | ## Contributing 150 | Feel free to send pull requests 151 | 152 | ## License 153 | This project is distributed under the [MIT license](LICENSE.md) 154 | 155 | [![HitCount](http://hits.dwyl.com/razzkumar/pr-automation-with-s3.svg)](http://hits.dwyl.com/razzkumar/pr-automation-with-s3) 156 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Static Site Automation" 2 | description: "It simple Build the aws S3 bucket, build the applicaton and deploy static site to s3 and comment the url to the PR" 3 | author: razzkumar 4 | runs: 5 | image: docker://razzkumar/pr-automation:v1.0.2 6 | using: docker 7 | branding: 8 | icon: upload-cloud 9 | color: green 10 | -------------------------------------------------------------------------------- /aws-client/session.go: -------------------------------------------------------------------------------- 1 | package awsclient 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/session" 8 | "github.com/razzkumar/PR-Automation/logger" 9 | ) 10 | 11 | // GetSession returns Session for AWS. 12 | func GetSession() *session.Session { 13 | 14 | region := os.Getenv("AWS_REGION") 15 | //accssKey := utils.LoadEnv("AWS_ACCESS_KEY_ID") 16 | //secrectKey := utils.LoadEnv("AWS_SECRET_ACCESS_KEY") 17 | 18 | sess, err := session.NewSessionWithOptions(session.Options{ 19 | Config: aws.Config{ 20 | Region: aws.String(region), 21 | }, 22 | }) 23 | 24 | if err != nil { 25 | logger.FailOnError(err, "Unable to connect connect to AWS.") 26 | } 27 | 28 | return sess 29 | } 30 | -------------------------------------------------------------------------------- /cloudfront/invalidation.go: -------------------------------------------------------------------------------- 1 | package cloudfront 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/session" 9 | "github.com/aws/aws-sdk-go/service/cloudfront" 10 | ) 11 | 12 | func Invalidation(did string, sess *session.Session) error { 13 | 14 | if did == "" { 15 | return nil 16 | } 17 | 18 | client := cloudfront.New(sess) 19 | 20 | now := time.Now() 21 | 22 | invalidaitonInput := cloudfront.CreateInvalidationInput{ 23 | DistributionId: aws.String(did), 24 | InvalidationBatch: &cloudfront.InvalidationBatch{ 25 | CallerReference: aws.String( 26 | fmt.Sprintf("goinvali%s", now.Format("2006/01/02,15:04:05"))), 27 | Paths: &cloudfront.Paths{ 28 | Quantity: aws.Int64(1), 29 | Items: []*string{ 30 | aws.String("/*"), 31 | }, 32 | }, 33 | }, 34 | } 35 | 36 | resp, err := client.CreateInvalidation(&invalidaitonInput) 37 | 38 | if err != nil { 39 | return fmt.Errorf("Invalidation failed. err:%v", err.Error()) 40 | } 41 | 42 | fmt.Printf("Invalidation:%v\n", resp) 43 | 44 | return nil 45 | 46 | } 47 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | if [ -z "$AWS_ACCESS_KEY_ID" ]; then 6 | echo "AWS_ACCESS_KEY_ID is not set. Quitting." 7 | exit 1 8 | fi 9 | 10 | if [ -z "$AWS_SECRET_ACCESS_KEY" ]; then 11 | echo "AWS_SECRET_ACCESS_KEY is not set. Quitting." 12 | exit 1 13 | fi 14 | 15 | # Check checkoperation 16 | 17 | if [ -z "$ACTION" ]; then 18 | ACTION="create" 19 | fi 20 | 21 | #listing all available eviroment variables 22 | 23 | case "$ACTION" in 24 | # Create action will create new s3 static site and deploy on it 25 | create) 26 | if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]];then 27 | # check GH_ACCESS_TOKEN is set or not for the commit_ 28 | if [ -z "$GH_ACCESS_TOKEN" ]; then 29 | echo "GH_ACCESS_TOKEN is not set. Quitting." 30 | exit 1 31 | fi 32 | 33 | # Running prcomment command to commit 34 | /s3 -action $ACTION 35 | 36 | ## Fail action if /s3 command throw error 37 | if [ $? -ne 0 ];then 38 | echo "::error::Failed to deploy to the s3" 39 | exit 1 40 | fi 41 | 42 | else 43 | echo "::error::Unable to build " 44 | exit 1 45 | fi 46 | ;; 47 | #Simpy delet the static site from s3 48 | delete) 49 | /s3 -action $ACTION 50 | 51 | if [ $? -ne 0 ]; then 52 | echo "::error::Unable to Unable to delete " 53 | exit 1 54 | fi 55 | ;; 56 | deploy) 57 | if [ -z "$AWS_S3_BUCKET" ]; then 58 | echo "$AWS_S3_BUCKET is not set. Quitting." 59 | exit 1 60 | fi 61 | 62 | /s3 -action $ACTION 63 | 64 | if [ $? -ne 0 ]; then 65 | echo "::error::Unable to Build static file " 66 | exit 1 67 | fi 68 | ;; 69 | *) 70 | echo "::error:: Can't perform any task" 71 | exit 1 72 | ;; 73 | esac 74 | -------------------------------------------------------------------------------- /examples/build-and-deploy-react-app.yml: -------------------------------------------------------------------------------- 1 | 2 | # It will create `$AWS_S3_BUCKET.auto-deploy` s3 bucket if not exist and build 3 | #the react app and Deploy it 4 | 5 | name: Create s3,Build and deploy react app 6 | 7 | on: 8 | push: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@master 17 | - name: Build and deploy react app 18 | uses: razzkumar/pr-automation-with-s3@v1.0.0 19 | env: 20 | AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} # attach auto-deploy on name while creating s3 bucket 21 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 22 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 23 | ACTION: 'deploy' # action must be deploy 24 | -------------------------------------------------------------------------------- /examples/cleanup-s3-on-pr-merge.yml: -------------------------------------------------------------------------------- 1 | # - It simply delete the s3 bucket on PR merge 2 | 3 | name: Delete S3 bucket after PR merge 4 | 5 | on: 6 | pull_request: 7 | types: [closed] 8 | 9 | jobs: 10 | delete: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Clean up temperory bucket 14 | if: github.event.pull_request.merged == true 15 | uses: razzkumar/pr-automation-with-s3@v1.0.0 16 | env: 17 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 18 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 19 | AWS_REGION: 'us-east-2' # optional: defaults to us-east-2 must be save as "deploy-on-pr" 20 | ACTION: "delete" # optional: defaule to create (option: create,delete and deploy) 21 | -------------------------------------------------------------------------------- /examples/deploy-on-pr.yml: -------------------------------------------------------------------------------- 1 | name: Deploy site to S3 And add comment to PR 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - none 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: Static site deploy to s3 and comment on PR 14 | uses: razzkumar/pr-automation-with-s3@v1.0.0 15 | env: 16 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 17 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 18 | GH_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN}} 19 | AWS_REGION: 'us-east-2' # optional: defaults to us-east-2 20 | SRC_FOLDER: 'build' # optional: defaults to build (react app) 21 | IS_BUILD: 'true' # optional: defaults to true 22 | ACTION: "create" # optional: defaule to create (option: create,delet and deploy) 23 | BUILD_COMMAND: "yarn build" # required if IS_BUILD is true 24 | 25 | -------------------------------------------------------------------------------- /examples/deploy-pre-build-site.yml: -------------------------------------------------------------------------------- 1 | 2 | # It will create `$AWS_S3_BUCKET.auto-deploy` s3 bucket and build the react app 3 | # and Deploy it 4 | 5 | name: Deploy prebuild static site 6 | 7 | on: 8 | push: 9 | branches: 10 | - dev 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@master 17 | - name: Deploy prebuild static app 18 | uses: razzkumar/pr-automation-with-s3@v1.0.0 19 | env: 20 | AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} # attach auto-deploy on name while creating s3 bucket 21 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 22 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 23 | IS_BUILD: 'false' # It doesn't run "yarn build" 24 | SRC_FOLDER: "public" # Optional: It will take "build" folder by default 25 | ACTION: 'deploy' # action must be deploy 26 | -------------------------------------------------------------------------------- /github/comment.go: -------------------------------------------------------------------------------- 1 | package gh 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | 8 | "github.com/google/go-github/v30/github" 9 | "github.com/razzkumar/PR-Automation/utils" 10 | "golang.org/x/oauth2" 11 | ) 12 | 13 | func GithubClient(ctx context.Context) *github.Client { 14 | 15 | ts := oauth2.StaticTokenSource( 16 | &oauth2.Token{AccessToken: os.Getenv("GH_ACCESS_TOKEN")}, 17 | ) 18 | 19 | tc := oauth2.NewClient(ctx, ts) 20 | 21 | client := github.NewClient(tc) 22 | 23 | return client 24 | } 25 | 26 | func Comment(url string, repo utils.ProjectInfo) error { 27 | 28 | comment := "Visit: " + url 29 | 30 | ctx := context.Background() 31 | 32 | client := GithubClient(ctx) 33 | 34 | pullRequestReviewRequest := &github.PullRequestReviewRequest{Body: &comment, Event: github.String("COMMENT")} 35 | 36 | //client.PullRequests.CreateComment(ctx, owner, repo, num, pullRequestReviewRequest) 37 | pullRequestReview, _, err := client.PullRequests.CreateReview(ctx, repo.RepoOwner, repo.RepoName, repo.PrNumber, pullRequestReviewRequest) 38 | 39 | if err != nil { 40 | return err 41 | } 42 | 43 | log.Println("github-Commit: Created GitHub PR Review comment", pullRequestReview.ID) 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/razzkumar/PR-Automation 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.40.48 7 | github.com/go-sql-driver/mysql v1.5.0 // indirect 8 | github.com/google/go-github v17.0.0+incompatible // indirect 9 | github.com/google/go-github/v30 v30.1.0 10 | github.com/stretchr/testify v1.4.0 // indirect 11 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/aws/aws-sdk-go v1.29.34 h1:yrzwfDaZFe9oT4AmQeNNunSQA7c0m2chz0B43+bJ1ok= 4 | github.com/aws/aws-sdk-go v1.29.34/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= 5 | github.com/aws/aws-sdk-go v1.40.48 h1:9lKz7AoFl2vYuVwWB7el9SmMBvOj83NixEvfNrojLEo= 6 | github.com/aws/aws-sdk-go v1.40.48/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= 7 | github.com/catchplay/scaffold v0.0.0-20190110040009-8500479f1f1a/go.mod h1:LgjiNnM6ZQ5attHxHoTrK0detPgipDCnEP4R+fVcMzM= 8 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 9 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 13 | github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= 14 | github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= 15 | github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= 16 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 17 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 18 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 19 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 20 | github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= 21 | github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= 22 | github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= 23 | github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= 24 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 25 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 26 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 27 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 28 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 29 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 30 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 31 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 32 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 33 | github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= 34 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 35 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 36 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 37 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 38 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 39 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 40 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 41 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 42 | github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 43 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 44 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 45 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 46 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 47 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 48 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 49 | github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 50 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 51 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 52 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 53 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 54 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 55 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= 56 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 57 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= 58 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 59 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 60 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= 61 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 62 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 63 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 64 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 65 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 66 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 67 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 68 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 69 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 70 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 71 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 72 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 73 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 74 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 75 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 76 | gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= 77 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 78 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 79 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | 9 | func FailOnNoFlag(msg string) { 10 | panic(fmt.Sprintf("%s", msg)) 11 | } 12 | 13 | func Info(msg string) { 14 | log.Panic(msg) 15 | } 16 | 17 | func FailOnError(err error, msg string) { 18 | if err != nil { 19 | panic(fmt.Sprintf("%s: %s", msg, err)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/razzkumar/PR-Automation/aws-client" 9 | "github.com/razzkumar/PR-Automation/cloudfront" 10 | "github.com/razzkumar/PR-Automation/logger" 11 | "github.com/razzkumar/PR-Automation/s3" 12 | "github.com/razzkumar/PR-Automation/utils" 13 | ) 14 | 15 | func main() { 16 | 17 | // Setting env variable 18 | awsRegion := os.Getenv("AWS_REGION") 19 | 20 | if awsRegion == "" { 21 | err := os.Setenv("AWS_REGION", "us-east-2") 22 | if err != nil { 23 | logger.FailOnError(err, "Fail to set AWS_REGION") 24 | } 25 | } 26 | 27 | var action string 28 | // Getting action wether delete or create 29 | var repo utils.ProjectInfo 30 | 31 | flag.StringVar(&action, "action", "", "It's create or delete s3 bucket") 32 | flag.Parse() 33 | 34 | if action == "" { 35 | logger.FailOnNoFlag("Please provide action what to do [deploy,delete,create]") 36 | } 37 | 38 | if os.Getenv("GITHUB_EVENT_NAME") == "pull_request" && (action == "create" || action == "delete") { 39 | repo = utils.GetPRInfo(repo) 40 | } else { 41 | repo = utils.GetInfo(repo, action) 42 | } 43 | 44 | // Getting session of aws 45 | sess := awsclient.GetSession() 46 | 47 | switch action { 48 | case "deploy": 49 | err := s3.Deploy(repo, sess) 50 | 51 | invErr := cloudfront.Invalidation(repo.CloudfrontId, sess) 52 | logger.FailOnError(invErr, "Fail to invalitate") 53 | 54 | logger.FailOnError(err, "Error on Deployment") 55 | case "create": 56 | err := s3.DeployAndComment(repo, sess) 57 | logger.FailOnError(err, "Error on Deployment and commit") 58 | case "delete": 59 | err := s3.Delete(repo.Bucket, sess) 60 | logger.FailOnError(err, "Error while Delete") 61 | default: 62 | err := fmt.Errorf("Nothing to do") 63 | logger.FailOnError(err, "Default case") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /s3/attach_policy.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/aws/aws-sdk-go/aws/awserr" 10 | "github.com/aws/aws-sdk-go/service/s3" 11 | ) 12 | 13 | func AttachPolicy(bucket string, svc *s3.S3) error { 14 | 15 | publicRead := map[string]interface{}{ 16 | "Version": "2012-10-17", 17 | "Statement": []map[string]interface{}{ 18 | { 19 | "Sid": "PublicReadGetObject", 20 | "Effect": "Allow", 21 | "Principal": "*", 22 | "Action": []string{ 23 | "s3:GetObject", 24 | }, 25 | "Resource": []string{ 26 | fmt.Sprintf("arn:aws:s3:::%s/*", bucket), 27 | }, 28 | }, 29 | }, 30 | } 31 | 32 | policy, err := json.Marshal(publicRead) 33 | if err != nil { 34 | return err 35 | } 36 | _, err = svc.PutBucketPolicy(&s3.PutBucketPolicyInput{ 37 | Bucket: aws.String(bucket), 38 | Policy: aws.String(string(policy)), 39 | }) 40 | 41 | if err != nil { 42 | return err 43 | } 44 | 45 | fmt.Printf("Successfully set bucket %q's policy\n", bucket) 46 | return nil 47 | } 48 | 49 | func GetPolicy(bucket string, svc *s3.S3) (bool, error) { 50 | result, err := svc.GetBucketPolicy(&s3.GetBucketPolicyInput{ 51 | Bucket: aws.String(bucket), 52 | }) 53 | if err != nil { 54 | // Special error handling for the when the bucket doesn't 55 | // exists so we can give a more direct error message from the CLI. 56 | if aerr, ok := err.(awserr.Error); ok { 57 | switch aerr.Code() { 58 | case s3.ErrCodeNoSuchBucket: 59 | return false, fmt.Errorf("Bucket %q does not exist.", bucket) 60 | 61 | case "NoSuchBucketPolicy": 62 | return false, fmt.Errorf("Bucket %q does not have a policy.", bucket) 63 | } 64 | } 65 | return false, fmt.Errorf("Unable to get bucket %q policy, %v.", bucket, err) 66 | } 67 | 68 | isPublic := strings.Contains(result.String(), "PublicReadGetObject") 69 | fmt.Println("isPublic", isPublic) 70 | if !isPublic { 71 | return false, fmt.Errorf("Bucket is not Public") 72 | } 73 | return true, nil 74 | } 75 | -------------------------------------------------------------------------------- /s3/create_s3_bucket.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/awserr" 8 | "github.com/aws/aws-sdk-go/service/s3" 9 | ) 10 | 11 | func GetStaticSiteStatus(bucket string, svc *s3.S3) (*s3.GetBucketWebsiteOutput, error) { 12 | status, err := svc.GetBucketWebsite(&s3.GetBucketWebsiteInput{ 13 | Bucket: aws.String(bucket), 14 | }) 15 | 16 | if err != nil { 17 | // Check for the NoSuchWebsiteConfiguration error code telling us 18 | // that the bucket does not have a website configured. 19 | if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoSuchWebsiteConfiguration" { 20 | EnableStaticHosting(bucket, svc) 21 | } else { 22 | return nil, fmt.Errorf("Unable to get bucket website config, %v", err) 23 | } 24 | } 25 | fmt.Println("Bucket Status", bucket) 26 | return status, nil 27 | } 28 | 29 | func CreateBucket(bucket string, svc *s3.S3) error { 30 | _, err := svc.CreateBucket(&s3.CreateBucketInput{ 31 | Bucket: aws.String(bucket), 32 | }) 33 | if err != nil { 34 | if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "BucketAlreadyOwnedByYou" { 35 | fmt.Printf("bucket %s is Already Exist\n", bucket) 36 | } else { 37 | return err 38 | } 39 | } 40 | GetStaticSiteStatus(bucket, svc) 41 | isPublic, _ := GetPolicy(bucket, svc) 42 | if !isPublic { 43 | fmt.Println("Attaching policy") 44 | AttachPolicy(bucket, svc) 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /s3/delete_s3.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/session" 8 | "github.com/aws/aws-sdk-go/service/s3" 9 | "github.com/aws/aws-sdk-go/service/s3/s3manager" 10 | ) 11 | 12 | func Delete(bucket string, sess *session.Session) error { 13 | 14 | svc := s3.New(sess) 15 | // Setup BatchDeleteIterator to iterate through a list of objects. 16 | iter := s3manager.NewDeleteListIterator(svc, &s3.ListObjectsInput{ 17 | Bucket: aws.String(bucket), 18 | }) 19 | 20 | // Traverse iterator deleting each object 21 | if err := s3manager.NewBatchDeleteWithClient(svc).Delete(aws.BackgroundContext(), iter); err != nil { 22 | return err 23 | } 24 | 25 | fmt.Printf("\nDeleted object(s) from bucket: %s\n", bucket) 26 | 27 | _, err := svc.DeleteBucket(&s3.DeleteBucketInput{ 28 | Bucket: aws.String(bucket), 29 | }) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | fmt.Printf("\n%s s3 Bucket Deleted\n", bucket) 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /s3/deploy.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/aws/aws-sdk-go/aws" 11 | "github.com/aws/aws-sdk-go/aws/session" 12 | "github.com/aws/aws-sdk-go/service/s3" 13 | "github.com/aws/aws-sdk-go/service/s3/s3manager" 14 | "github.com/razzkumar/PR-Automation/logger" 15 | "github.com/razzkumar/PR-Automation/secret_manager" 16 | "github.com/razzkumar/PR-Automation/utils" 17 | ) 18 | 19 | // Deploy to S3 bucket 20 | func Deploy(repo utils.ProjectInfo, sess *session.Session) error { 21 | 22 | dir := "./" + repo.DistFolder 23 | 24 | svc := s3.New(sess) 25 | 26 | err := CreateBucket(repo.Bucket, svc) 27 | 28 | if err != nil { 29 | logger.FailOnError(err, "Error while creating S3 bucket") 30 | } 31 | 32 | if repo.IsBuild { 33 | 34 | secret_manager.SetEnv(sess) 35 | 36 | //Running build 37 | build() 38 | 39 | } 40 | 41 | uploader := s3manager.NewUploader(sess) 42 | 43 | fileList := []string{} 44 | 45 | filepath.Walk(dir, 46 | func(path string, info os.FileInfo, err error) error { 47 | if err != nil { 48 | return err 49 | } 50 | 51 | if info.IsDir() { 52 | return nil 53 | } 54 | 55 | fileList = append(fileList, path) 56 | 57 | return nil 58 | }) 59 | 60 | // Loop through every file and uplaod to s3 61 | for _, file := range fileList { 62 | f, _ := os.Open(file) 63 | 64 | key := strings.TrimPrefix(file, dir) 65 | key = strings.Replace(key, repo.DistFolder, "", -1) 66 | fileContentType := utils.GetFileType(file) 67 | 68 | _, err := uploader.Upload(&s3manager.UploadInput{ 69 | Bucket: aws.String(repo.Bucket), 70 | Key: aws.String(key), 71 | ContentType: aws.String(fileContentType), 72 | Body: f, 73 | }) 74 | 75 | if err != nil { 76 | return err 77 | } 78 | fmt.Println("Uploading... " + key) 79 | } 80 | 81 | fmt.Println("\n\n" + strconv.Itoa(len(fileList)) + " Files Uploaded Successfully. 🎉 🎉 🎉") 82 | 83 | // cloudfrontDistributionID := os.Getenv("CLOUDFRONT_ID") 84 | 85 | // if cloudfrontDistributionID=="" 86 | 87 | url := utils.GetURL(repo.Bucket) 88 | fmt.Println("URL : ", url) 89 | 90 | fmt.Println("removing dist files") 91 | os.RemoveAll(dir) 92 | 93 | return nil 94 | } 95 | 96 | func build() { 97 | 98 | buildCmd := os.Getenv("BUILD_COMMAND") 99 | 100 | if strings.Contains(buildCmd, "npm") { 101 | 102 | fmt.Println("npm install ....") 103 | utils.RunCommand("npm install") 104 | 105 | fmt.Printf("------------ %s ----------", buildCmd) 106 | utils.RunCommand(buildCmd) 107 | 108 | } else { 109 | 110 | fmt.Println("yarn install ....") 111 | utils.RunCommand("yarn") 112 | 113 | if buildCmd == "" { 114 | fmt.Println("----------- yarn build ----------") 115 | utils.RunCommand("yarn build") 116 | } else { 117 | fmt.Printf("----------- %s -----------", buildCmd) 118 | utils.RunCommand(buildCmd) 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /s3/deploy_and_commit.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws/session" 5 | "github.com/razzkumar/PR-Automation/github" 6 | "github.com/razzkumar/PR-Automation/logger" 7 | "github.com/razzkumar/PR-Automation/utils" 8 | ) 9 | 10 | // Deploy to S3 bucket 11 | func DeployAndComment(repo utils.ProjectInfo, sess *session.Session) error { 12 | // TODO notity to slack 13 | err := Deploy(repo, sess) 14 | 15 | if err != nil { 16 | logger.FailOnError(err, "Error While Deploying to s3") 17 | } 18 | 19 | url := utils.GetURL(repo.Bucket) 20 | 21 | err = gh.Comment(url, repo) 22 | 23 | if err != nil { 24 | return err 25 | } 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /s3/enable_static_site.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/service/s3" 8 | "github.com/razzkumar/PR-Automation/logger" 9 | ) 10 | 11 | const ( 12 | indexPage = "index.html" 13 | errorPage = "index.html" // If error accure redirect to index.html 14 | ) 15 | 16 | func EnableStaticHosting(bucket string, svc *s3.S3) { 17 | params := s3.PutBucketWebsiteInput{ 18 | Bucket: aws.String(bucket), 19 | WebsiteConfiguration: &s3.WebsiteConfiguration{ 20 | IndexDocument: &s3.IndexDocument{ 21 | Suffix: aws.String(indexPage), 22 | }, 23 | }, 24 | } 25 | 26 | // Add the error page if set on CLI 27 | if len(errorPage) > 0 { 28 | params.WebsiteConfiguration.ErrorDocument = &s3.ErrorDocument{ 29 | Key: aws.String(errorPage), 30 | } 31 | } 32 | 33 | _, err := svc.PutBucketWebsite(¶ms) 34 | if err != nil { 35 | logger.Info(fmt.Sprintf("Unable to set bucket %q website configuration, %v", bucket, err)) 36 | } 37 | 38 | fmt.Printf("Successfully set bucket %q website configuration\n", bucket) 39 | } 40 | -------------------------------------------------------------------------------- /secret_manager/set_env.go: -------------------------------------------------------------------------------- 1 | package secret_manager 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/awserr" 11 | "github.com/aws/aws-sdk-go/aws/session" 12 | "github.com/aws/aws-sdk-go/service/secretsmanager" 13 | ) 14 | 15 | func SetEnv(sess *session.Session) error { 16 | 17 | secretName := os.Getenv("SECRETS_MANAGER") 18 | if secretName == "" { 19 | return nil 20 | } 21 | 22 | //Create a Secrets Manager client 23 | 24 | svc := secretsmanager.New(sess) 25 | 26 | input := &secretsmanager.GetSecretValueInput{ 27 | SecretId: aws.String(secretName), 28 | VersionStage: aws.String("AWSCURRENT"), // VersionStage defaults to AWSCURRENT if unspecified 29 | } 30 | 31 | // In this sample we only handle the specific exceptions for the 'GetSecretValue' API. 32 | // See https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html 33 | 34 | result, err := svc.GetSecretValue(input) 35 | if err != nil { 36 | if aerr, ok := err.(awserr.Error); ok { 37 | switch aerr.Code() { 38 | case secretsmanager.ErrCodeDecryptionFailure: 39 | // Secrets Manager can't decrypt the protected secret text using the provided KMS key. 40 | fmt.Println(secretsmanager.ErrCodeDecryptionFailure, aerr.Error()) 41 | 42 | case secretsmanager.ErrCodeInternalServiceError: 43 | // An error occurred on the server side. 44 | fmt.Println(secretsmanager.ErrCodeInternalServiceError, aerr.Error()) 45 | 46 | case secretsmanager.ErrCodeInvalidParameterException: 47 | // You provided an invalid value for a parameter. 48 | fmt.Println(secretsmanager.ErrCodeInvalidParameterException, aerr.Error()) 49 | 50 | case secretsmanager.ErrCodeInvalidRequestException: 51 | // You provided a parameter value that is not valid for the current state of the resource. 52 | fmt.Println(secretsmanager.ErrCodeInvalidRequestException, aerr.Error()) 53 | 54 | case secretsmanager.ErrCodeResourceNotFoundException: 55 | // We can't find the resource that you asked for. 56 | fmt.Println(secretsmanager.ErrCodeResourceNotFoundException, aerr.Error()) 57 | } 58 | } else { 59 | // Print the error, cast err to awserr.Error to get the Code and 60 | // Message from an error. 61 | fmt.Println(err.Error()) 62 | } 63 | return nil 64 | } 65 | 66 | // Decrypts secret using the associated KMS CMK. 67 | // Depending on whether the secret is a string or binary, one of these fields will be populated. 68 | var secretString, decodedBinarySecret string 69 | if result.SecretString != nil { 70 | secretString = *result.SecretString 71 | } else { 72 | decodedBinarySecretBytes := make([]byte, base64.StdEncoding.DecodedLen(len(result.SecretBinary))) 73 | len, err := base64.StdEncoding.Decode(decodedBinarySecretBytes, result.SecretBinary) 74 | if err != nil { 75 | fmt.Println("Base64 Decode Error:", err) 76 | return nil 77 | } 78 | decodedBinarySecret = string(decodedBinarySecretBytes[:len]) 79 | 80 | } 81 | 82 | if decodedBinarySecret != "" { 83 | djsonMap := make(map[string]string) 84 | err = json.Unmarshal([]byte(decodedBinarySecret), &djsonMap) 85 | 86 | if err != nil { 87 | panic(err) 88 | } 89 | for name, secret := range djsonMap { 90 | if err := os.Setenv(name, secret); err != nil { 91 | return fmt.Errorf("failed to set environment variable: '%s': %s", name, err) 92 | } 93 | } 94 | } 95 | 96 | jsonMap := make(map[string]string) 97 | err = json.Unmarshal([]byte(secretString), &jsonMap) 98 | if err != nil { 99 | panic(err) 100 | } 101 | for name, secret := range jsonMap { 102 | if err := os.Setenv(name, secret); err != nil { 103 | return fmt.Errorf("failed to set environment variable: '%s': %s", name, err) 104 | } 105 | } 106 | // Your code goes here. 107 | return nil 108 | } 109 | -------------------------------------------------------------------------------- /utils/fails.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/razzkumar/PR-Automation/logger" 4 | 5 | func EnvLoadError(env string, envName string) { 6 | 7 | if env == "" { 8 | logger.FailOnNoFlag("Unbale to load " + envName) 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /utils/files.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "mime" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | // GetFileType returns the content type of the file. 10 | func GetFileType(filename string) string { 11 | split := strings.Split(filename, ".") 12 | 13 | if len(split) == 0 { 14 | return "binary/octet-stream" 15 | } 16 | 17 | ext := split[len(split)-1] 18 | 19 | return mime.TypeByExtension("." + ext) 20 | } 21 | 22 | // Exists check existence of file 23 | func isFileExists(filepath string) bool { 24 | if _, err := os.Stat(filepath); err == nil { 25 | return true 26 | } 27 | return false 28 | } 29 | -------------------------------------------------------------------------------- /utils/get_urls.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "os" 4 | 5 | func GetURL(bucket string) string { 6 | region := os.Getenv("AWS_REGION") 7 | url := "http://" + bucket + ".s3-website." + region + ".amazonaws.com/" 8 | return url 9 | } 10 | -------------------------------------------------------------------------------- /utils/parse_github_event.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | 8 | "github.com/google/go-github/v30/github" 9 | "github.com/razzkumar/PR-Automation/logger" 10 | ) 11 | 12 | func ParseGithubEvent() interface{} { 13 | 14 | event := os.Getenv("GITHUB_EVENT_NAME") 15 | filePath := os.Getenv("GITHUB_EVENT_PATH") 16 | // Open our jsonFile 17 | jsonFile, err := os.Open(filePath) 18 | 19 | // if we os.Open returns an error then handle it 20 | if err != nil { 21 | logger.FailOnError(err, "Fail to read event file") 22 | } 23 | 24 | // defer the closing of our jsonFile so that we can parse it later on 25 | defer jsonFile.Close() 26 | 27 | byteValue, _ := ioutil.ReadAll(jsonFile) 28 | 29 | parsedData, err := github.ParseWebHook(event, byteValue) 30 | 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | return parsedData 36 | 37 | } 38 | 39 | func GetPREvent() *github.PullRequestEvent { 40 | parsedData := ParseGithubEvent() 41 | return parsedData.(*github.PullRequestEvent) 42 | } 43 | 44 | func GetPushEvent() *github.PushEvent { 45 | parsedData := ParseGithubEvent() 46 | return parsedData.(*github.PushEvent) 47 | } 48 | -------------------------------------------------------------------------------- /utils/repo_info.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/razzkumar/PR-Automation/logger" 9 | ) 10 | 11 | type ProjectInfo struct { 12 | PrNumber int 13 | RepoOwner string 14 | Branch string 15 | RepoName string 16 | DistFolder string 17 | Bucket string 18 | IsBuild bool 19 | CloudfrontId string // cloud front distribution id 20 | } 21 | 22 | func getDistFolder() string { 23 | 24 | assestFolder := os.Getenv("SRC_FOLDER") 25 | //setting build folder default 26 | if assestFolder == "" { 27 | assestFolder = "build" 28 | } 29 | 30 | distDir := strings.Replace(assestFolder, "./", "", -1) 31 | 32 | return distDir 33 | } 34 | 35 | func GetPRInfo(repo ProjectInfo) ProjectInfo { 36 | 37 | prEvent := GetPREvent() 38 | repo.Branch = prEvent.PullRequest.Head.GetRef() 39 | 40 | repo.PrNumber = prEvent.GetNumber() 41 | 42 | prNumInit := strconv.Itoa(repo.PrNumber) 43 | 44 | bucket := strings.ToLower(repo.Branch + ".PR" + prNumInit + ".auto-deploy") 45 | 46 | repo.Bucket = bucket 47 | 48 | repo.RepoOwner = prEvent.Repo.Owner.GetLogin() 49 | repo.RepoName = prEvent.Repo.GetName() 50 | 51 | repo.DistFolder = getDistFolder() 52 | 53 | isBuild := os.Getenv("IS_BUILD") 54 | 55 | if isBuild == "" || isBuild == "true" { 56 | repo.IsBuild = true 57 | } 58 | 59 | return repo 60 | } 61 | 62 | func GetInfo(repo ProjectInfo, action string) ProjectInfo { 63 | 64 | // setting bucket 65 | bucket := os.Getenv("AWS_S3_BUCKET") 66 | if action == "deploy" && bucket == "" { 67 | logger.FailOnNoFlag("AWS_S3_BUCKET is not set:") 68 | } 69 | repo.Bucket = bucket 70 | 71 | // setting dist folder 72 | repo.DistFolder = getDistFolder() 73 | 74 | isBuild := os.Getenv("IS_BUILD") 75 | 76 | if isBuild == "" || isBuild == "true" { 77 | repo.IsBuild = true 78 | } 79 | 80 | repo.CloudfrontId = os.Getenv("CLOUDFRONT_ID") 81 | 82 | return repo 83 | } 84 | -------------------------------------------------------------------------------- /utils/shell.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | ) 10 | 11 | // Execute runs the process with the supplied environment. 12 | func RunCommand(command string) error { 13 | cmd := exec.Command("sh", "-c", command) 14 | 15 | var out bytes.Buffer 16 | var stderr bytes.Buffer 17 | 18 | cmd.Stderr = &stderr 19 | cmd.Stdout = &out 20 | 21 | cmd.Stdout = os.Stdout 22 | 23 | err := cmd.Run() 24 | if err != nil { 25 | fmt.Println(stderr.String()) 26 | 27 | return errors.New(err.Error()) 28 | } 29 | 30 | return nil 31 | } 32 | --------------------------------------------------------------------------------