diff --git a/.github/workflows/deploy-pr-preview.yml b/.github/workflows/deploy-pr-preview.yml new file mode 100644 index 000000000..de7953a4b --- /dev/null +++ b/.github/workflows/deploy-pr-preview.yml @@ -0,0 +1,19 @@ +name: Deploy pr preview + +on: + pull_request: + types: + - opened + - synchronize + - closed + +jobs: + deploy-pr-preview: + uses: grafana/writers-toolkit/.github/workflows/deploy-preview.yml@robbymilo/deploy-preview + with: + sha: ${{ github.event.pull_request.head.sha }} + branch: ${{ github.ref_name }} + event_number: ${{ github.event.number }} + title: ${{ github.event.pull_request.title }} + repo: writers-toolkit + website_directory: content/docs/writers-toolkit diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml new file mode 100644 index 000000000..47339d6c7 --- /dev/null +++ b/.github/workflows/deploy-preview.yml @@ -0,0 +1,89 @@ +name: deploy-preview + +on: + workflow_call: + inputs: + sha: + required: true + type: string + branch: + required: true + type: string + event_number: + required: true + type: string + title: + required: true + type: string + repo: + required: true + type: string + source_directory: + default: docs/sources + type: string + website_directory: + required: true + type: string + +env: + CLOUD_RUN_REGION: us-south1 + +permissions: + contents: read + id-token: write + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + deploy-preview: + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build website + shell: bash + run: | + mkdir -p ${PWD}/dist + docker run -v ${PWD}/dist:/hugo/dist -v "${PWD}/${{ inputs.source_directory }}:/hugo/${{ inputs.website_directory }}" --rm grafana/docs-base:latest /bin/bash -c 'hugo --environment=docs --destination=dist/ --baseURL= --minify' + + - name: Build image + shell: bash + run: | + docker build . -f ./deploy-preview/Dockerfile -t ${{ inputs.repo }}:${{ inputs.sha }} + docker run ${{ inputs.repo }}:${{ inputs.sha }} sh -c 'ls -al /usr/share/nginx/dist/docs/writers-toolkit/' + + - name: Build and push image to GAR + id: push-to-gar + if: github.event.action != 'closed' + uses: grafana/shared-workflows/actions/push-to-gar-docker@main + with: + registry: us-docker.pkg.dev + tags: |- + "${{ inputs.sha }}" + "${{ inputs.event_number }}" + context: . + image_name: ${{ inputs.repo }} + environment: dev + push: true + + - uses: "google-github-actions/auth@v2.1.6" + id: gcloud-auth-cloud-run + with: + workload_identity_provider: "projects/304398677251/locations/global/workloadIdentityPools/github/providers/github-provider" + service_account: "github-docs-cloud-run-dev@grafanalabs-workload-identity.iam.gserviceaccount.com" + + - name: Deploy to Cloud Run + uses: "google-github-actions/deploy-cloudrun@v2.7.2" + if: github.event.action != 'closed' + id: deploy + with: + image: us-docker.pkg.dev/grafanalabs-dev/docker-website-dev/website:4d75d79656f3b200ae94415cd645a63648f9f4cf + service: deploy-preview-website-22384-1 + project_id: grafanalabs-dev + region: us-south1 + flags: --port=80 --ingress=all --allow-unauthenticated diff --git a/deploy-preview/Dockerfile b/deploy-preview/Dockerfile new file mode 100644 index 000000000..ada86076c --- /dev/null +++ b/deploy-preview/Dockerfile @@ -0,0 +1,10 @@ +FROM nginx:1.21.6-alpine + +COPY ./dist /usr/share/nginx/dist + +RUN rm -rf /etc/nginx/sites-enabled && \ + rm -rf /etc/nginx/nginx.conf + +COPY ./deploy-preview/nginx.conf /etc/nginx/nginx.conf + +RUN nginx -t \ No newline at end of file diff --git a/deploy-preview/nginx.conf b/deploy-preview/nginx.conf new file mode 100644 index 000000000..bd65f295a --- /dev/null +++ b/deploy-preview/nginx.conf @@ -0,0 +1,109 @@ +worker_processes 5; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; +worker_rlimit_nofile 8192; + +events { + worker_connections 4096; +} + +http { + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE + ssl_prefer_server_ciphers on; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + gzip on; + + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + + server { + absolute_redirect off; + + listen 80; + + add_header "X-UA-Compatible" "IE=Edge,chrome=1"; + add_header "Strict-Transport-Security" "max-age=31536000; includeSubDomains; preload"; + add_header "Referrer-Policy" "origin-when-cross-origin"; + + # add_header "Content-Security-Policy-Report-Only" "object-src 'none'; script-src 'report-sample' 'self' 'unsafe-eval' 'unsafe-inline' 'sha256-HNwGiG3xFKuDX/zKYWVB/Lykh85DfcqKEJgCwk+t9gM=' https://js.intercomcdn.com https://widget.intercom.io/widget/ https://go2.grafana.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com https://x.clearbitjs.com https://app.clearbit.com https://munchkin.marketo.net https://connect.facebook.net https://snap.licdn.com https://www.google-analytics.com/ https://px.ads.linkedin.com https://www.linkedin.com https://fresnel.vimeocdn.com https://f.vimeocdn.com https://vimeo.com https://player.vimeo.com https://platform.twitter.com https://syndication.twitter.com https://api.twitter.com https://twitter.com https://static.hotjar.com https://in.hotjar.com https://script.hotjar.com https://www.googletagmanager.com/gtag/ *.googleadservices.com https://googleads.g.doubleclick.net/pagead/ https://static.doubleclick.net https://www.youtube.com https://www.eventbrite.com http://rsdk.grafana.com http://rsdk2.grafana.com https://heypal.chat https://www.heypal.chat https://pal-api-production.up.railway.app https://faro-collector-prod-us-central-0.grafana.net https://*.fullstory.com https://rsi.grafana.com; report-uri /api/csp-reports"; + add_header "Content-Security-Policy" "object-src 'none'; script-src 'self' 'unsafe-eval' 'unsafe-inline' https://go2.grafana.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://unpkg.com https://x.clearbitjs.com https://app.clearbit.com https://munchkin.marketo.net https://connect.facebook.net https://snap.licdn.com https://www.google-analytics.com/ https://px.ads.linkedin.com https://www.linkedin.com https://fresnel.vimeocdn.com https://f.vimeocdn.com https://vimeo.com https://player.vimeo.com https://platform.twitter.com https://syndication.twitter.com https://api.twitter.com https://twitter.com https://static.hotjar.com https://in.hotjar.com https://script.hotjar.com https://www.googletagmanager.com/gtag/ *.googleadservices.com https://googleads.g.doubleclick.net/pagead/ https://static.doubleclick.net https://www.youtube.com https://www.eventbrite.com http://rsdk.grafana.com http://rsdk2.grafana.com https://heypal.chat https://www.heypal.chat https://pal-api-production.up.railway.app https://faro-collector-prod-us-central-0.grafana.net https://*.fullstory.com https://rsi.grafana.com https://cdn.mouseflow.com https://widget.intercom.io https://js.intercomcdn.com https://*.qualtrics.com https://js.zi-scripts.com https://tags.clickagy.com https://widget.kapa.ai https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/"; + + add_header Cache-Control "public, max-age=0, must-revalidate"; + add_header X-Frame-Options "DENY"; + + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; + + error_page 404 /404.html; + error_page 403 /404.html; # show 404 instead of 403 when directory exists but index.html does not + location = /404.html { + ssi on; + ssi_last_modified on; + root /usr/share/nginx/dist/; + internal; + } + + # move assets to a different layer to prevent docker cache invalidation + # https://regex101.com/r/M7HrFY/1 + location ~* ^/static/(app.min.css|app.css.map|shared.min.css|shared.css.map|.*.json|.*.html|.*.js|lastmod.csv)$ { + alias /usr/share/nginx/assets/$1; + } + + location / { + ssi on; + ssi_last_modified on; + alias /usr/share/nginx/dist/; + } + + location /static { + proxy_pass https://grafana.com/static; + } + + location /media { + proxy_pass https://grafana.com/media; + } + + location /api { + proxy_pass https://grafana.com/api; + } + + location ~ ^/connect(.*)$ { + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; + + proxy_ssl_server_name on; + resolver 8.8.8.8 8.8.4.4 ipv6=off; + resolver_timeout 5s; + set $faro "faro-collector-prod-us-central-0.grafana.net"; + + proxy_pass https://$faro/collect$1; + } + + + location /healthz { + return 200 'ok'; + add_header Content-Type text/plain; + } + } +} diff --git a/deploy-preview/redirects.sh b/deploy-preview/redirects.sh new file mode 100755 index 000000000..4c3d9665b --- /dev/null +++ b/deploy-preview/redirects.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +if ! command -v rg &>/dev/null; then + echo "ERROR: required binary 'rg' is not in PATH. For installation, refer to https://github.com/BurntSushi/ripgrep#installation." >&2 + exit 2 +fi + +# find all files that have a meta refresh +rg -e 'http-equiv=refresh content="0; url=/' --glob '*.html' --no-heading --no-ignore --no-line-number --with-filename --fixed-strings -- dist | +# ensure that any lines not matched by sed script are treated as comments by nginx +sed -E 's/^/# /' | +# change into nginx rewrite format +sed -E 's/# dist([^:]+)(\/index.html|(\/[^\/]+.html)):.+<\/noscript><\/head><\/html>/rewrite "^\1\3\/\?\$" "\4" permanent;/' | +# output to file for nginx include +cat >dist/redirects.conf