diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f55b6cdde9..56f191de0e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,4 +1,10 @@ version: 2 +registries: + ghcr: # Define access for a private registry + type: docker-registry + url: ghcr.io + username: PAT + password: ${{secrets.CONTAINER_BUILDER_TOKEN}} updates: - package-ecosystem: npm directory: '/' @@ -23,11 +29,18 @@ updates: - dependency-name: '*' update-types: ['version-update:semver-patch', 'version-update:semver-minor'] + - dependency-name: 'github/internal-actions' - package-ecosystem: 'docker' + registries: + - ghcr directory: '/' schedule: interval: weekly day: thursday + groups: + baseImages: + patterns: + - '*' ignore: - dependency-name: 'node' diff --git a/.github/workflows/moda-ci.yaml b/.github/workflows/moda-ci.yaml new file mode 100644 index 0000000000..0545be426c --- /dev/null +++ b/.github/workflows/moda-ci.yaml @@ -0,0 +1,65 @@ +name: docs-internal Moda CI + +# More info on CI actions setup can be found here: +# https://github.com/github/ops/blob/master/docs/playbooks/build-systems/moving-moda-apps-from-bp-to-actions.md + +on: + workflow_dispatch: + push: + branches-ignore: + - 'gh-readonly-queue/**' + merge_group: + types: [checks_requested] + +jobs: + moda-config-bundle: + if: ${{ github.repository == 'github/docs-internal' }} + name: ${{ matrix.ci_job.job }} + strategy: + fail-fast: false + matrix: + ci_job: [{ 'job': 'docs-internal-moda-config-bundle' }] + uses: github/internal-actions/.github/workflows/moda.yml@main + with: + ci-formatted-job-name: ${{ matrix.ci_job.job }} + vault-keys: ${{ vars.VAULT_KEYS }} + secrets: + dx-bot-token: ${{ secrets.INTERNAL_ACTIONS_DX_BOT_ACCOUNT_TOKEN }} + datadog-api-key: ${{ secrets.DATADOG_API_KEY }} + + docker-image: + if: ${{ github.repository == 'github/docs-internal' }} + name: ${{ matrix.ci_job.job }} + strategy: + fail-fast: false + matrix: + ci_job: [{ 'job': 'docs-internal-docker-image' }] + uses: github/internal-actions/.github/workflows/kube.yml@main + with: + ci-formatted-job-name: ${{ matrix.ci_job.job }} + vault-keys: ${{ vars.VAULT_KEYS }} + secrets: + dx-bot-token: ${{ secrets.INTERNAL_ACTIONS_DX_BOT_ACCOUNT_TOKEN }} + datadog-api-key: ${{ secrets.DATADOG_API_KEY }} + + docker-security: + if: ${{ github.repository == 'github/docs-internal' }} + name: ${{ matrix.ci_job.job }} + strategy: + fail-fast: false + matrix: + ci_job: [{ 'job': 'docs-internal-docker-security' }] + uses: github/internal-actions/.github/workflows/docker_security.yml@main + with: + ci-formatted-job-name: ${{ matrix.ci_job.job }} + vault-keys: ${{ vars.VAULT_KEYS }} + secrets: + dx-bot-token: ${{ secrets.INTERNAL_ACTIONS_DX_BOT_ACCOUNT_TOKEN }} + datadog-api-key: ${{ secrets.DATADOG_API_KEY }} + +permissions: + actions: read + checks: read + contents: read + statuses: read + id-token: write diff --git a/Dockerfile b/Dockerfile index dd9033bb37..eadf79b32d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,9 @@ FROM node:22-alpine@sha256:c13b26e7e602ef2f1074aef304ce6e9b7dd284c419b35d89fcf3c # This directory is owned by the node user ARG APP_HOME=/home/node/app +# Make sure there's a translations directory available to not error the COPY command +RUN mkdir -p translations && chown -R node:node translations + # Make sure we don't run anything as the root user USER node @@ -109,4 +112,4 @@ FROM preview AS production ENV ENABLED_LANGUAGES "en,zh,es,pt,ru,ja,fr,de,ko" # Copy in all translations -COPY --chown=node:node translations ./translations +COPY --chown=node:node --from=base translations ./translations diff --git a/config/kubernetes/production/deployments/webapp.yaml b/config/kubernetes/production/deployments/webapp.yaml new file mode 100644 index 0000000000..fba6734ca3 --- /dev/null +++ b/config/kubernetes/production/deployments/webapp.yaml @@ -0,0 +1,53 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webapp +spec: + replicas: 2 + selector: + matchLabels: + app: webapp + template: + metadata: + labels: + app: webapp + annotations: + # If you emit structured logs, you can specify a parser to use so your logs are parsed + # properly and are much nicer to query in splunk. For more details, see + # https://thehub.github.com/engineering/development-and-ops/observability/logging/fluent-bit/ + # fluentbit.io/parser: logfmt + spec: + dnsPolicy: Default + containers: + - name: webapp + image: docs-internal + resources: + requests: + cpu: 4000m + memory: 5Gi + limits: + cpu: 4000m + memory: 14Gi + ports: + - name: http + containerPort: 4000 + protocol: TCP + envFrom: + - secretRef: + name: vault-secrets + - configMapRef: + name: kube-cluster-metadata + # Zero-downtime deploys + # https://thehub.github.com/engineering/products-and-services/internal/moda/feature-documentation/pod-lifecycle/#required-prestop-hook + # https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + lifecycle: + preStop: + exec: + command: ['sleep', '5'] + readinessProbe: + initialDelaySeconds: 5 + httpGet: + # WARNING: This should be updated to a meaningful endpoint for your application which will return a 200 once the app is fully started. + # See: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes + path: /healthz + port: http diff --git a/config/kubernetes/production/services/webapp.yaml b/config/kubernetes/production/services/webapp.yaml new file mode 100644 index 0000000000..4c96ca5be8 --- /dev/null +++ b/config/kubernetes/production/services/webapp.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Service +metadata: + name: webapp + labels: + service: webapp + annotations: + moda.github.net/domain-name: 'docs-internal.github.com' + moda.github.net/dns-registration-enabled: 'false' + moda.github.net/load-balancer-type: + public-external-http + # moda.github.net/allowed-ips: '23.235.32.0/20,43.249.72.0/22,103.244.50.0/24,103.245.222.0/23,103.245.224.0/24,104.156.80.0/20,140.248.64.0/18,140.248.128.0/17,146.75.0.0/17,151.101.0.0/16,157.52.64.0/18,167.82.0.0/17,167.82.128.0/20,167.82.160.0/20,167.82.224.0/20,172.111.64.0/18,185.31.16.0/22,199.27.72.0/21,199.232.0.0/1' + # ipv6 addresses not included + # curl -i "https://api.fastly.com/public-ip-list" +spec: + ports: + - name: http + port: 4000 + protocol: TCP + targetPort: http + selector: + app: webapp + type: LoadBalancer diff --git a/config/moda/deployment.yaml b/config/moda/deployment.yaml new file mode 100644 index 0000000000..a8edafe099 --- /dev/null +++ b/config/moda/deployment.yaml @@ -0,0 +1,13 @@ +required_builds: + - docs-internal-moda-config-bundle / docs-internal-moda-config-bundle + - docs-internal-docker-image / docs-internal-docker-image + - docs-internal-docker-security / docs-internal-docker-security +environments: + - name: production + auto_deploy: true + cluster_selector: + profile: general + region: iad +notifications: + slack_channels: + - '#docs-ops' diff --git a/ownership.yaml b/ownership.yaml new file mode 100644 index 0000000000..fd23b50c4c --- /dev/null +++ b/ownership.yaml @@ -0,0 +1,22 @@ +--- +version: 1 +ownership: + - team: github/docs-engineering + repo: https://github.com/github/docs-internal + name: docs-internal + kind: moda + long_name: Docs on Moda + description: Please use instead. + exec_sponsor: nerdneha + product_manager: docs-bot + qos: best_effort + tier: 2 + sev1: + pagerduty: https://github.pagerduty.com/escalation_policies#PN57VQ1 + tta: 30m + sev2: + issue: https://github.com/github/docs-engineering/issues + tta: 1d + sev3: + issue: https://github.com/github/docs-engineering/issues + tta: 1w diff --git a/src/workflows/tests/actions-workflows.ts b/src/workflows/tests/actions-workflows.ts index 6f2a231dfb..df1461e96f 100644 --- a/src/workflows/tests/actions-workflows.ts +++ b/src/workflows/tests/actions-workflows.ts @@ -27,6 +27,7 @@ const workflowsDir = path.join(__dirname, '../../../.github/workflows') const workflows: WorkflowMeta[] = fs .readdirSync(workflowsDir) .filter((filename) => filename.endsWith('.yml') || filename.endsWith('.yaml')) + .filter((filename) => filename !== 'moda-ci.yaml') // Skip moda-ci .map((filename) => { const fullpath = path.join(workflowsDir, filename) const data = yaml.load(fs.readFileSync(fullpath, 'utf8')) as WorkflowMeta['data']