diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000000..917e5ce2cdb7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,121 @@ +name: deploy + +on: + workflow_dispatch: + push: + branches: + - lab + - master + - published + +# these permissions are needed to interact with GitHub's OIDC Token endpoint. +permissions: + id-token: write + contents: read + +jobs: + publish: + runs-on: ubuntu-20.04 + steps: + - + name: Prepare + run: | + JEKYLL_ENV=development + DOCS_AWS_REGION=us-east-1 + if [ "${{ github.ref }}" = "refs/heads/master" ]; then + DOCS_URL="https://docs-stage2.docker.com" # TODO: change this to https://docs-stage.docker.com when new env switched and ready + DOCS_AWS_IAM_ROLE="arn:aws:iam::710015040892:role/stage-docs-docker.github.io-20220816140248629900000003" + DOCS_S3_BUCKET="stage-docs-docker.github.io" + DOCS_S3_CONFIG="s3-config.json" + DOCS_CLOUDFRONT_ID="E1R7CSW3F0X4H8" + DOCS_LAMBDA_FUNCTION_REDIRECTS="DockerDocsRedirectFunction-stage" + DOCS_SLACK_MSG="Successfully deployed docs-stage2 from master branch. $DOCS_URL" # TODO: change to "deployed docs-stage" when new env switched and ready + elif [ "${{ github.ref }}" = "refs/heads/published" ]; then + #JEKYLL_ENV=production # TODO: uncomment when new env switched and ready + DOCS_URL="https://docs2.docker.com" # TODO: change this to https://docs.docker.com when new env switched and ready + DOCS_AWS_IAM_ROLE="arn:aws:iam::710015040892:role/prod-docs-docker.github.io-20220816161549883800000001" + DOCS_S3_BUCKET="prod-docs-docker.github.io" + DOCS_S3_CONFIG="s3-config.json" + DOCS_CLOUDFRONT_ID="E228TTN20HNU8F" + DOCS_LAMBDA_FUNCTION_REDIRECTS="DockerDocsRedirectFunction-prod" + DOCS_SLACK_MSG="Successfully deployed docs2 from published branch. $DOCS_URL" # TODO: change to "deployed docs" when new env switched and ready + elif [ "${{ github.ref }}" = "refs/heads/lab" ]; then + DOCS_URL="https://docs-labs.docker.com" + DOCS_AWS_IAM_ROLE="arn:aws:iam::710015040892:role/labs-docs-docker.github.io-20220728143917865600000003" + DOCS_S3_BUCKET="labs-docs-docker.github.io" + DOCS_S3_CONFIG="s3-config.json" + DOCS_CLOUDFRONT_ID="E1MYDYF65FW3HG" + DOCS_LAMBDA_FUNCTION_REDIRECTS="DockerDocsRedirectFunction-labs" + DOCS_SLACK_MSG="Successfully deployed docs-labs from lab branch. $DOCS_URL" + else + echo >&2 "ERROR: unknown branch ${{ github.ref }}" + exit 1 + fi + SEND_SLACK_MSG="true" + if [ -z "$DOCS_AWS_IAM_ROLE" ] || [ -z "$DOCS_S3_BUCKET" ] || [ -z "$DOCS_CLOUDFRONT_ID" ] || [ -z "$DOCS_SLACK_MSG" ]; then + SEND_SLACK_MSG="false" + fi + echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + echo "JEKYLL_ENV=$JEKYLL_ENV" >> $GITHUB_ENV + echo "DOCS_URL=$DOCS_URL" >> $GITHUB_ENV + echo "DOCS_AWS_REGION=$DOCS_AWS_REGION" >> $GITHUB_ENV + echo "DOCS_AWS_IAM_ROLE=$DOCS_AWS_IAM_ROLE" >> $GITHUB_ENV + echo "DOCS_S3_BUCKET=$DOCS_S3_BUCKET" >> $GITHUB_ENV + echo "DOCS_S3_CONFIG=$DOCS_S3_CONFIG" >> $GITHUB_ENV + echo "DOCS_CLOUDFRONT_ID=$DOCS_CLOUDFRONT_ID" >> $GITHUB_ENV + echo "DOCS_LAMBDA_FUNCTION_REDIRECTS=$DOCS_LAMBDA_FUNCTION_REDIRECTS" >> $GITHUB_ENV + echo "DOCS_SLACK_MSG=$DOCS_SLACK_MSG" >> $GITHUB_ENV + echo "SEND_SLACK_MSG=$SEND_SLACK_MSG" >> $GITHUB_ENV + - + name: Checkout + uses: actions/checkout@v3 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Build website + uses: docker/bake-action@v2 + with: + targets: release + set: | + *.cache-from=type=gha,scope=deploy-${{ env.BRANCH_NAME }} + *.cache-to=type=gha,scope=deploy-${{ env.BRANCH_NAME }},mode=max + - + name: Configure AWS Credentials + if: ${{ env.DOCS_AWS_IAM_ROLE != '' }} + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ env.DOCS_AWS_IAM_ROLE }} + aws-region: ${{ env.DOCS_AWS_REGION }} + - + name: Upload files to S3 bucket + if: ${{ env.DOCS_S3_BUCKET != '' }} + run: | + aws --region ${{ env.DOCS_AWS_REGION }} s3 sync --acl public-read _site s3://${{ env.DOCS_S3_BUCKET }}/ --delete + - + name: Update S3 config + if: ${{ env.DOCS_S3_BUCKET != '' && env.DOCS_S3_CONFIG != '' }} + uses: docker/bake-action@v2 + with: + targets: aws-s3-update-config + set: | + *.cache-from=type=gha,scope=releaser + env: + AWS_REGION: ${{ env.DOCS_AWS_REGION }} + AWS_S3_BUCKET: ${{ env.DOCS_S3_BUCKET }} + AWS_S3_CONFIG: ${{ env.DOCS_S3_CONFIG }} + - + name: Update Cloudfront config + if: ${{ env.DOCS_CLOUDFRONT_ID != '' }} + uses: docker/bake-action@v2 + with: + targets: aws-cloudfront-update + env: + AWS_REGION: us-east-1 # cloudfront and lambda edge functions are only available in us-east-1 region + AWS_CLOUDFRONT_ID: ${{ env.DOCS_CLOUDFRONT_ID }} + AWS_LAMBDA_FUNCTION: ${{ env.DOCS_LAMBDA_FUNCTION_REDIRECTS }} + - + name: Send Slack notification + if: ${{ env.SEND_SLACK_MSG == 'true' }} + run: | + curl -X POST -H 'Content-type: application/json' --data '{"text":"${{ env.DOCS_SLACK_MSG }}"}' ${{ secrets.SLACK_WEBHOOK }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 020e0fae1bda..d249323ccf10 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,6 @@ on: workflow_dispatch: push: branches: - - lab - master - published @@ -30,15 +29,13 @@ jobs: DOCS_S3_CONFIG="_website-config-docs.json" DOCS_LAMBDA_FUNCTION_CACHE="arn:aws:lambda:us-east-1:710015040892:function:docs-cache-invalidator" DOCS_SLACK_MSG="Successfully published docs. https://docs.docker.com/" - elif [ "${{ github.ref }}" = "refs/heads/lab" ]; then - DOCS_URL="https://docs-lab.docker.com" - DOCS_S3_BUCKET="" - DOCS_LAMBDA_FUNCTION_CACHE="" - DOCS_S3_CONFIG="_website-config-docs-lab.json" else echo >&2 "ERROR: unknown branch ${{ github.ref }}" exit 1 fi + echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" >> $GITHUB_ENV + echo "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" >> $GITHUB_ENV echo "JEKYLL_ENV=$JEKYLL_ENV" >> $GITHUB_ENV echo "DOCS_URL=$DOCS_URL" >> $GITHUB_ENV echo "DOCS_AWS_REGION=$DOCS_AWS_REGION" >> $GITHUB_ENV @@ -58,15 +55,12 @@ jobs: with: targets: release set: | - *.cache-from=type=gha,scope=publish - *.cache-to=type=gha,scope=publish,mode=max + *.cache-from=type=gha,scope=publish-${{ env.BRANCH_NAME }} + *.cache-to=type=gha,scope=publish-${{ env.BRANCH_NAME }},mode=max - name: Upload files to S3 bucket run: | aws --region ${{ env.DOCS_AWS_REGION }} s3 sync --acl public-read _site s3://${{ env.DOCS_S3_BUCKET }}/ --delete - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: Update S3 config uses: docker/bake-action@v2 @@ -75,8 +69,6 @@ jobs: set: | *.cache-from=type=gha,scope=releaser env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ env.DOCS_AWS_REGION }} AWS_S3_BUCKET: ${{ env.DOCS_S3_BUCKET }} AWS_S3_CONFIG: ${{ env.DOCS_S3_CONFIG }} @@ -89,8 +81,6 @@ jobs: set: | *.cache-from=type=gha,scope=releaser env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ env.DOCS_AWS_REGION }} AWS_LAMBDA_FUNCTION: ${{ env.DOCS_LAMBDA_FUNCTION_CACHE }} - diff --git a/_releaser/Dockerfile b/_releaser/Dockerfile index 54be23c84f53..cdd4e5edc459 100644 --- a/_releaser/Dockerfile +++ b/_releaser/Dockerfile @@ -8,15 +8,13 @@ FROM golang:${GO_VERSION}-alpine AS base RUN apk add --no-cache jq openssl ENV CGO_ENABLED=0 WORKDIR /src - -FROM base AS vendor -COPY go.mod go.sum *.go ./ +COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ - go mod tidy && go mod download + go mod download -FROM vendor AS releaser -COPY go.mod go.sum *.go ./ -RUN --mount=type=cache,target=/go/pkg/mod \ +FROM base AS releaser +RUN --mount=type=bind,target=. \ + --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ go build -o /out/releaser . @@ -42,6 +40,7 @@ RUN --mount=type=bind,target=. \ --mount=type=bind,from=releaser,source=/out/releaser,target=/usr/bin/releaser \ --mount=type=secret,id=AWS_ACCESS_KEY_ID \ --mount=type=secret,id=AWS_SECRET_ACCESS_KEY \ + --mount=type=secret,id=AWS_SESSION_TOKEN \ releaser aws s3-update-config FROM base AS aws-lambda-invoke @@ -50,4 +49,20 @@ ARG AWS_LAMBDA_FUNCTION RUN --mount=type=bind,from=releaser,source=/out/releaser,target=/usr/bin/releaser \ --mount=type=secret,id=AWS_ACCESS_KEY_ID \ --mount=type=secret,id=AWS_SECRET_ACCESS_KEY \ + --mount=type=secret,id=AWS_SESSION_TOKEN \ releaser aws lambda-invoke + +FROM base AS aws-cloudfront-update +ARG AWS_REGION +ARG AWS_LAMBDA_FUNCTION +ARG AWS_CLOUDFRONT_ID +RUN --mount=type=bind,target=. \ + --mount=type=bind,from=sitedir,target=/site \ + --mount=type=bind,from=releaser,source=/out/releaser,target=/usr/bin/releaser \ + --mount=type=secret,id=AWS_ACCESS_KEY_ID \ + --mount=type=secret,id=AWS_SECRET_ACCESS_KEY \ + --mount=type=secret,id=AWS_SESSION_TOKEN \ + AWS_LAMBDA_FUNCTION_FILE=cloudfront-lambda-redirects.js \ + REDIRECTS_JSON=$(jq -c '.' /site/redirects.json) \ + REDIRECTS_PREFIXES_JSON=$(jq -c '.' redirects-prefixes.json) \ + releaser aws cloudfront-update diff --git a/_releaser/_website-config-docs-lab.json b/_releaser/_website-config-docs-lab.json deleted file mode 100644 index 85fc6e8f1df1..000000000000 --- a/_releaser/_website-config-docs-lab.json +++ /dev/null @@ -1,388 +0,0 @@ -{ - "ErrorDocument": { - "Key": "404.html" - }, - "IndexDocument": { - "Suffix": "index.html" - }, - "RedirectAllRequestsTo": null, - "RoutingRules": [ - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.4/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.5/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.6/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.7/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.8/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.9/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.10/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.11/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.12/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v1.13/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v17.03/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v17.09/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v17.12/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "ee/licensing/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "ee/get-support/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "ee/cluster/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "ee/supported-platforms/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "ee/ucp/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "ee/dtr/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "compliance/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "datacenter/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v18.09/ee/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v18.03/ee/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v17.06/enterprise/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "ee/docker-ee/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "ee/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": null, - "ReplaceKeyWith": "" - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v17.06/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v18.03/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - }, - { - "Condition": { - "HttpErrorCodeReturnedEquals": null, - "KeyPrefixEquals": "v18.09/" - }, - "Redirect": { - "HostName": "docs-lab.docker.com", - "HttpRedirectCode": null, - "Protocol": "https", - "ReplaceKeyPrefixWith": "", - "ReplaceKeyWith": null - } - } - ] -} diff --git a/_releaser/aws.go b/_releaser/aws.go index 7469b26c5b2b..1c04f8f71001 100644 --- a/_releaser/aws.go +++ b/_releaser/aws.go @@ -1,21 +1,29 @@ package main import ( + "archive/zip" + "bytes" "encoding/json" "fmt" - "io/ioutil" "log" + "os" + "path" + "text/template" + "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/cloudfront" "github.com/aws/aws-sdk-go/service/lambda" "github.com/aws/aws-sdk-go/service/s3" ) type AwsCmd struct { - S3UpdateConfig AwsS3UpdateConfigCmd `kong:"cmd,name=s3-update-config"` - LambdaInvoke AwsLambdaInvokeCmd `kong:"cmd,name=lambda-invoke"` + S3UpdateConfig AwsS3UpdateConfigCmd `kong:"cmd,name=s3-update-config"` + LambdaInvoke AwsLambdaInvokeCmd `kong:"cmd,name=lambda-invoke"` + CloudfrontUpdate AwsCloudfrontUpdateCmd `kong:"cmd,name=cloudfront-update"` } type AwsS3UpdateConfigCmd struct { @@ -25,7 +33,7 @@ type AwsS3UpdateConfigCmd struct { } func (s *AwsS3UpdateConfigCmd) Run() error { - file, err := ioutil.ReadFile(s.S3Config) + file, err := os.ReadFile(s.S3Config) if err != nil { return fmt.Errorf("failed to read s3 config file %s: %w", s.S3Config, err) } @@ -40,6 +48,9 @@ func (s *AwsS3UpdateConfigCmd) Run() error { Credentials: awsCredentials(), Region: aws.String(s.Region), }) + if err != nil { + return fmt.Errorf("failed to create session: %w", err) + } svc := s3.New(sess) @@ -84,6 +95,189 @@ func (s *AwsLambdaInvokeCmd) Run() error { return nil } +type AwsCloudfrontUpdateCmd struct { + Region string `kong:"name='region',env='AWS_REGION'"` + Function string `kong:"name='lambda-function',env='AWS_LAMBDA_FUNCTION'"` + FunctionFile string `kong:"name='lambda-function-file',env='AWS_LAMBDA_FUNCTION_FILE'"` + CloudfrontID string `kong:"name='cloudfront-id',env='AWS_CLOUDFRONT_ID'"` + RedirectsJSON string `kong:"name='redirects-json',env='REDIRECTS_JSON'"` + RedirectsPrefixesJSON string `kong:"name='redirects-prefixes-json',env='REDIRECTS_PREFIXES_JSON'"` +} + +func (s *AwsCloudfrontUpdateCmd) Run() error { + var err error + ver := time.Now().UTC().Format(time.RFC3339) + + zipdt, err := getLambdaFunctionZip(s.FunctionFile, s.RedirectsJSON, s.RedirectsPrefixesJSON) + if err != nil { + return fmt.Errorf("cannot create lambda function zip: %w", err) + } + + svc := lambda.New(session.Must(session.NewSessionWithOptions(session.Options{ + SharedConfigState: session.SharedConfigEnable, + })), &aws.Config{ + Credentials: awsCredentials(), + Region: aws.String(s.Region), + }) + + function, err := svc.GetFunction(&lambda.GetFunctionInput{ + FunctionName: aws.String(s.Function), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok && aerr.Code() != lambda.ErrCodeResourceNotFoundException { + return fmt.Errorf("cannot find lambda function %q: %w", s.Function, err) + } + _, err = svc.CreateFunction(&lambda.CreateFunctionInput{ + FunctionName: aws.String(s.Function), + }) + if aerr, ok := err.(awserr.Error); ok && aerr.Code() != lambda.ErrCodeResourceConflictException { + return err + } + } + codeSha256 := *function.Configuration.CodeSha256 + log.Printf("INFO: updating lambda function %q\n", s.Function) + + updateConfig, err := svc.UpdateFunctionCode(&lambda.UpdateFunctionCodeInput{ + FunctionName: aws.String(s.Function), + ZipFile: zipdt, + }) + if err != nil { + return fmt.Errorf("failed to update lambda function code: %s", err) + } + log.Printf("INFO: lambda function updated successfully (%s)\n", *updateConfig.FunctionArn) + + if codeSha256 == *updateConfig.CodeSha256 { + log.Printf("INFO: lambda function code has not changed. skipping publication...") + return nil + } + + log.Printf("INFO: waiting for lambda function to be processed\n") + // the lambda function code image is never ready right away, AWS has to + // process it, so we wait 3 seconds before trying to publish the version. + time.Sleep(3 * time.Second) + + publishConfig, err := svc.PublishVersion(&lambda.PublishVersionInput{ + FunctionName: aws.String(s.Function), + CodeSha256: aws.String(*updateConfig.CodeSha256), + Description: aws.String(ver), + }) + if err != nil { + return fmt.Errorf("failed to publish lambda function version %q for %q: %w", ver, s.Function, err) + } + log.Printf("INFO: lambda function version %q published successfully (%s)\n", ver, *publishConfig.FunctionArn) + + sess, err := session.NewSession(&aws.Config{ + Credentials: awsCredentials(), + Region: aws.String(s.Region)}, + ) + if err != nil { + return fmt.Errorf("failed to create session: %w", err) + } + + cfrt := cloudfront.New(sess) + cfrtDistrib, err := cfrt.GetDistribution(&cloudfront.GetDistributionInput{ + Id: aws.String(s.CloudfrontID), + }) + if err != nil { + return fmt.Errorf("cannot find cloudfront distribution %q: %w", s.CloudfrontID, err) + } + log.Printf("INFO: cloudfront distribution %q loaded\n", *cfrtDistrib.Distribution.Id) + + cfrtDistribConfig, err := cfrt.GetDistributionConfig(&cloudfront.GetDistributionConfigInput{ + Id: aws.String(s.CloudfrontID), + }) + if err != nil { + return fmt.Errorf("cannot load cloudfront distribution config: %w", err) + } + log.Printf("INFO: cloudfront distribution configuration loaded\n") + + distribConfig := cfrtDistribConfig.DistributionConfig + if distribConfig.DefaultCacheBehavior == nil { + log.Printf("INFO: cloudfront distribution default cache behavior not found. skipping...") + return nil + } + + for _, funcAssoc := range distribConfig.DefaultCacheBehavior.LambdaFunctionAssociations.Items { + if *funcAssoc.EventType != cloudfront.EventTypeViewerRequest { + continue + } + log.Printf("INFO: cloudfront distribution viewer request function ARN found: %q\n", *funcAssoc.LambdaFunctionARN) + } + + log.Printf("INFO: updating cloudfront config with viewer request function ARN %q", *publishConfig.FunctionArn) + distribConfig.DefaultCacheBehavior.LambdaFunctionAssociations = &cloudfront.LambdaFunctionAssociations{ + Quantity: aws.Int64(1), + Items: []*cloudfront.LambdaFunctionAssociation{ + { + EventType: aws.String(cloudfront.EventTypeViewerRequest), + IncludeBody: aws.Bool(false), + LambdaFunctionARN: publishConfig.FunctionArn, + }, + }, + } + _, err = cfrt.UpdateDistribution(&cloudfront.UpdateDistributionInput{ + Id: aws.String(s.CloudfrontID), + IfMatch: cfrtDistrib.ETag, + DistributionConfig: distribConfig, + }) + if err != nil { + return err + } + + log.Printf("INFO: cloudfront config updated successfully\n") + return nil +} + +func getLambdaFunctionZip(funcFilename string, redirectsJSON string, redirectsPrefixesJSON string) ([]byte, error) { + funcdt, err := os.ReadFile(funcFilename) + if err != nil { + return nil, fmt.Errorf("failed to read lambda function file %q: %w", funcFilename, err) + } + + var funcbuf bytes.Buffer + functpl := template.Must(template.New("").Parse(string(funcdt))) + if err = functpl.Execute(&funcbuf, struct { + RedirectsJSON string + RedirectsPrefixesJSON string + }{ + redirectsJSON, + redirectsPrefixesJSON, + }); err != nil { + return nil, err + } + + tmpdir, err := os.MkdirTemp("", "lambda-zip") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpdir) + + zipfile, err := os.Create(path.Join(tmpdir, "lambda-function.zip")) + if err != nil { + return nil, err + } + defer zipfile.Close() + + zipwrite := zip.NewWriter(zipfile) + zipindex, err := zipwrite.Create("index.js") + if err != nil { + return nil, err + } + if _, err = zipindex.Write(funcbuf.Bytes()); err != nil { + return nil, err + } + if err = zipwrite.Close(); err != nil { + return nil, err + } + + zipdt, err := os.ReadFile(zipfile.Name()) + if err != nil { + return nil, err + } + + return zipdt, nil +} + func awsCredentials() *credentials.Credentials { return credentials.NewChainCredentials( []credentials.Provider{ diff --git a/_releaser/cloudfront-lambda-redirects.js b/_releaser/cloudfront-lambda-redirects.js new file mode 100644 index 000000000000..face10fe003c --- /dev/null +++ b/_releaser/cloudfront-lambda-redirects.js @@ -0,0 +1,48 @@ +'use strict'; + +exports.handler = (event, context, callback) => { + //console.log("event", JSON.stringify(event)); + const request = event.Records[0].cf.request; + + const redirects = JSON.parse(`{{.RedirectsJSON}}`); + for (let key in redirects) { + if (key !== request.uri) { + continue; + } + //console.log(`redirect: ${request.uri} to ${redirects[key]}`); + const response = { + status: '301', + statusDescription: 'Moved Permanently', + headers: { + location: [{ + key: 'Location', + value: redirects[key], + }], + }, + } + callback(null, response); + return + } + + const redirectsPrefixes = JSON.parse(`{{.RedirectsPrefixesJSON}}`); + for (let key in redirectsPrefixes) { + if (!request.uri.startsWith(key)) { + continue; + } + //console.log(`redirect: ${request.uri} to ${redirectsPrefixes[key]}`); + const response = { + status: '301', + statusDescription: 'Moved Permanently', + headers: { + location: [{ + key: 'Location', + value: redirectsPrefixes[key], + }], + }, + } + callback(null, response); + return + } + + callback(null, request); +}; diff --git a/_releaser/redirects-prefixes.json b/_releaser/redirects-prefixes.json new file mode 100644 index 000000000000..a032ff2de9ef --- /dev/null +++ b/_releaser/redirects-prefixes.json @@ -0,0 +1,21 @@ +{ + "/compliance/": "/", + "/datacenter/": "/", + "/ee/": "/", + "/v1.4/": "/", + "/v1.5/": "/", + "/v1.6/": "/", + "/v1.7/": "/", + "/v1.8/": "/", + "/v1.9/": "/", + "/v1.10/": "/", + "/v1.11/": "/", + "/v1.12/": "/", + "/v1.13/": "/", + "/v17.03/": "/", + "/v17.06/": "/", + "/v17.09/": "/", + "/v17.12/": "/", + "/v18.03/": "/", + "/v18.09/": "/" +} diff --git a/_releaser/s3-config.json b/_releaser/s3-config.json new file mode 100644 index 000000000000..1040162edffc --- /dev/null +++ b/_releaser/s3-config.json @@ -0,0 +1,9 @@ +{ + "ErrorDocument": { + "Key": "404.html" + }, + "IndexDocument": { + "Suffix": "index.html" + }, + "RedirectAllRequestsTo": null +} diff --git a/docker-bake.hcl b/docker-bake.hcl index bd37880d3e75..4f19155b3b73 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -112,7 +112,10 @@ variable "AWS_S3_BUCKET" { default = "" } variable "AWS_S3_CONFIG" { - default = "_website-config-docs-stage.json" + default = "" +} +variable "AWS_CLOUDFRONT_ID" { + default = "" } variable "AWS_LAMBDA_FUNCTION" { default = "" @@ -123,11 +126,13 @@ target "_common-aws" { AWS_REGION = AWS_REGION AWS_S3_BUCKET = AWS_S3_BUCKET AWS_S3_CONFIG = AWS_S3_CONFIG + AWS_CLOUDFRONT_ID = AWS_CLOUDFRONT_ID AWS_LAMBDA_FUNCTION = AWS_LAMBDA_FUNCTION } secret = [ "id=AWS_ACCESS_KEY_ID,env=AWS_ACCESS_KEY_ID", - "id=AWS_SECRET_ACCESS_KEY,env=AWS_SECRET_ACCESS_KEY" + "id=AWS_SECRET_ACCESS_KEY,env=AWS_SECRET_ACCESS_KEY", + "id=AWS_SESSION_TOKEN,env=AWS_SESSION_TOKEN" ] } @@ -146,3 +151,14 @@ target "aws-lambda-invoke" { no-cache-filter = ["aws-lambda-invoke"] output = ["type=cacheonly"] } + +target "aws-cloudfront-update" { + inherits = ["_common-aws"] + context = "_releaser" + target = "aws-cloudfront-update" + contexts = { + sitedir = DOCS_SITE_DIR + } + no-cache-filter = ["aws-cloudfront-update"] + output = ["type=cacheonly"] +}