diff --git a/.github/workflows/create-diagram.yml b/.github/workflows/create-diagram.yml index f45245e5..8d4fffc2 100644 --- a/.github/workflows/create-diagram.yml +++ b/.github/workflows/create-diagram.yml @@ -4,7 +4,7 @@ on: tags: - v* branches: - - master + - main pull_request: jobs: repo-visuals: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..d8703934 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,75 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Deploy to k8s + +on: + push: + branches: + - main + workflow_dispatch: + +env: + PROJECT_ID: ${{ secrets.GKE_PROJECT }} # contini-XXX-de5a + GKE_CLUSTER: gke-test-2022 # Add your cluster name here. + GKE_ZONE: us-central1 # Add your cluster zone here. + DEPLOYMENT_NAME: gke-hello-app # Add your deployment name here. + IMAGE: go-hello-world + +jobs: + setup-build-publish-deploy: + name: Setup, Build, Publish, and Deploy + runs-on: ubuntu-latest + environment: production + + steps: + - name: Checkout + uses: actions/checkout@v3 + + # Setup gcloud CLI + - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 + with: + service_account_key: ${{ secrets.GKE_SA_KEY }} + project_id: ${{ secrets.GKE_PROJECT }} + + # Configure Docker to use the gcloud command-line tool as a credential + # helper for authentication + - run: |- + gcloud --quiet auth configure-docker + + # Get the GKE credentials so we can deploy to the cluster + - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e + with: + cluster_name: ${{ env.GKE_CLUSTER }} + location: ${{ env.GKE_ZONE }} + credentials: ${{ secrets.GKE_SA_KEY }} + + # Build the Docker image + - name: Build + run: |- + docker build \ + --tag "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA" \ + --build-arg GITHUB_SHA="$GITHUB_SHA" \ + --build-arg GITHUB_REF="$GITHUB_REF" \ + . + + # Push the Docker image to Google Container Registry + - name: Publish + run: |- + docker push "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA" + + # Set up kustomize + - name: Set up Kustomize + run: |- + curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v3.1.0/kustomize_3.1.0_linux_amd64 + chmod u+x ./kustomize + + # Deploy the Docker image to the GKE cluster + - name: Deploy + run: |- + ./kustomize edit set image gcr.io/$PROJECT_ID/$IMAGE:latest=gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA + ./kustomize build . | kubectl apply -f - + kubectl rollout status deployment/$DEPLOYMENT_NAME + kubectl get services -o wide diff --git a/.github/workflows/google.yml b/.github/workflows/google.yml deleted file mode 100644 index 28e42554..00000000 --- a/.github/workflows/google.yml +++ /dev/null @@ -1,96 +0,0 @@ -# This workflow will build a docker container, publish it to Google Container Registry, and deploy it to GKE when a release is created -# -# To configure this workflow: -# -# 1. Ensure that your repository contains the necessary configuration for your Google Kubernetes Engine cluster, including deployment.yml, kustomization.yml, service.yml, etc. -# -# 2. Set up secrets in your workspace: GKE_PROJECT with the name of the project and GKE_SA_KEY with the Base64 encoded JSON service account key (https://github.com/GoogleCloudPlatform/github-actions/tree/docs/service-account-key/setup-gcloud#inputs). -# -# 3. Change the values for the GKE_ZONE, GKE_CLUSTER, IMAGE, and DEPLOYMENT_NAME environment variables (below). -# -# For more support on how to run the workflow, please visit https://github.com/GoogleCloudPlatform/github-actions/tree/master/example-workflows/gke - -name: Build and Deploy to GKE - -#on: -# release: -# types: [created] -on: - push: - branches: - - master - - -env: - PROJECT_ID: ${{ secrets.GKE_PROJECT }} - GKE_CLUSTER: gsd-stable # using v1.15 (future versions won't allow deployment.yml) https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/ - GKE_ZONE: us-central1-c - DEPLOYMENT_NAME: gke-from-github - IMAGE: hello-world - -jobs: - setup-build-publish-deploy: - name: Setup, Build, Publish, and Deploy - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - # Setup gcloud CLI - - uses: GoogleCloudPlatform/github-actions/setup-gcloud@0.1.3 - with: - service_account_key: ${{ secrets.GKE_SA_KEY }} - project_id: ${{ secrets.GKE_PROJECT }} - - # Configure Docker to use the gcloud command-line tool as a credential - # helper for authentication - - run: |- - gcloud --quiet auth configure-docker - - # Get the GKE credentials so we can deploy to the cluster - - run: |- - gcloud container clusters get-credentials "$GKE_CLUSTER" --zone "$GKE_ZONE" - - # Build the Docker image - - name: Build - run: |- - docker build \ - --tag "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA" \ - --build-arg GITHUB_SHA="$GITHUB_SHA" \ - --build-arg GITHUB_REF="$GITHUB_REF" \ - . - - # Push the Docker image to Google Container Registry - - name: Publish - run: |- - docker push "gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA" - - - - name: Build latest - run: |- - docker build \ - --tag "gcr.io/$PROJECT_ID/$IMAGE:latest" \ - --build-arg GITHUB_SHA="$GITHUB_SHA" \ - --build-arg GITHUB_REF="$GITHUB_REF" \ - . - - - name: Publish latest - run: |- - docker push "gcr.io/$PROJECT_ID/$IMAGE:latest" - - - - # Set up kustomize - - name: Set up Kustomize - run: |- - curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v3.1.0/kustomize_3.1.0_linux_amd64 - chmod u+x ./kustomize - - # Deploy the Docker image to the GKE cluster - - name: Deploy - run: |- - ./kustomize edit set image gcr.io/PROJECT_ID/IMAGE:TAG=gcr.io/$PROJECT_ID/$IMAGE:$GITHUB_SHA - ./kustomize build . | kubectl apply -f - - kubectl rollout status deployment/$DEPLOYMENT_NAME - kubectl get services -o wide diff --git a/.github/workflows/main-test.yml.backup b/.github/workflows/main-test.yml.backup new file mode 100644 index 00000000..4eb753df --- /dev/null +++ b/.github/workflows/main-test.yml.backup @@ -0,0 +1,39 @@ +# Used to test things during PR's that would normally only happen on main branch +# +# eg updates to dynamodb table +# + +name: Main CI (TESTING for PRs) + +on: + pull_request: + types: [opened, synchronize, reopened] + +env: + IMAGE_NAME: go-hello-world + GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }} + +jobs: + + buildtest: + runs-on: ubuntu-latest + + steps: + + - name: checkout repo + uses: actions/checkout@v2 + + - name: Create DynamboDB Table in AWS - To store metadata (one-time) + run: make create_table + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-2 + + # - name: Create tags in DynamboDB Table in AWS - Metadata for this commit + # run: make create_tags + # env: + # PIPELINE_ID: ${GITHUB_RUN_ID}-${GITHUB_RUN_NUMBER} + # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # AWS_DEFAULT_REGION: us-east-2 \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ef9c53b8..cef16f81 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,10 +1,10 @@ -name: CI +name: Main CI on: push: # Publish `master` as Docker `latest` image. branches: - - master + - main # Publish `v1.2.3` tags as releases. tags: @@ -12,120 +12,66 @@ on: # Run tests for any PRs. pull_request: - -env: - IMAGE_NAME: go-hello-world - GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }} # not sure why gh didn't let me create a secret with an underscore in it???????? + types: [opened, synchronize, reopened] jobs: - security: - runs-on: ubuntu-latest - if: github.event_name == 'push' - - steps: - - uses: actions/checkout@v2 - - uses: ynniss/golang-security-action@master - with: - CODE_PATH: "./src/" - - security-gosec: - runs-on: ubuntu-latest - env: - GO111MODULE: on - steps: - - name: Checkout Source - uses: actions/checkout@v2 - - name: Run Gosec Security Scanner - uses: securego/gosec@master - with: - args: ./... build: runs-on: ubuntu-latest - if: github.event_name == 'push' +# if: github.event_name == 'push' steps: - - uses: actions/checkout@v2 - - # this will cause a failure which is only in some demos but annoying in others - #- uses: ynniss/golang-security-action@master - # with: - # #CODE_PATH: "./src/" # <<< we should move our go source into a generic src container so the refernce app is nicely organized and it's easier to build/find source - # CODE_PATH: "./" - - - - - - - - - - - - + - name: checkout code + uses: actions/checkout@v2 - name: Tests run: make test - + - name: Build the Go package run: make build + - name: Security Tests + run: make security + + - name: Archive security results + uses: actions/upload-artifact@v2 + with: + name: security-report + path: security-report + + - name: Run the Go package locally (detached) run: make run - #- name: Create DynamboDB Table in AWS - # run: make create_table - # env: - # FOO: ${{ secrets.FOO }} - # BAR: "BAZ" - # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - # AWS_DEFAULT_REGION: us-east-2 +# This is only used to initially create the table - need a cleaner way to include this, +# and have it do nothing if the table exists. Make currently ignores error + - name: Create DynamboDB Table in AWS - To store metadata (one-time) + run: make create_table + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-2 - - name: Create tags in DynamboDB Table in AWS + - name: Create tags in DynamboDB Table in AWS - Metadata for this commit run: make create_tags env: - FOO: ${{ secrets.FOO }} - BAR: "BAZ" + PIPELINE_ID: ${GITHUB_RUN_ID} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: us-east-2 + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Push image + run: make push - - - - - - - # this was just in here to prove our the publish, should actually edit the make build do write the correct image name then we can remove this step - - name: Build image - run: docker build . --file Dockerfile --tag $IMAGE_NAME - - # this is a bit verbose so probably time we wrapped it in make (it was just a copy/paste from github actions anyway) - - name: Log into registry and Push image - run: | - echo "${{ secrets.GH_PACKAGES }}" | docker login docker.pkg.github.com -u ${{ github.actor }} --password-stdin - - IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME - - # Change all uppercase to lowercase - IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') - - # Strip git ref prefix from version - VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') - - # Strip "v" prefix from tag name - [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') - - # Use Docker `latest` tag convention - [ "$VERSION" == "master" ] && VERSION=latest - - echo IMAGE_ID=$IMAGE_ID - echo VERSION=$VERSION - - docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker push $IMAGE_ID:$VERSION + call-verify: + uses: ./.github/workflows/verify.yml + needs: [build] + secrets: inherit diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml new file mode 100644 index 00000000..f36e864f --- /dev/null +++ b/.github/workflows/playground.yml @@ -0,0 +1,38 @@ +# These are pipeline steps we're testing that aren't GSD-like yet +name: Playground + +on: + push: + # Publish `master` as Docker `latest` image. + branches: + - main + + # Publish `v1.2.3` tags as releases. + tags: + - v* + + # Run tests for any PRs. + pull_request: + +jobs: + + golang-security: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: ynniss/golang-security-action@master + with: + CODE_PATH: "./src/" + + gosec-scanner: + runs-on: ubuntu-latest + env: + GO111MODULE: on + steps: + - name: Checkout Source + uses: actions/checkout@v2 + - name: Run Gosec Security Scanner + uses: securego/gosec@master + with: + args: ./... diff --git a/.github/workflows/lint.yml b/.github/workflows/quality.yml similarity index 50% rename from .github/workflows/lint.yml rename to .github/workflows/quality.yml index f7ce46c6..015113b8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/quality.yml @@ -1,19 +1,21 @@ -name: lint on: push: - tags: - - v* branches: - - master + - main pull_request: + types: [opened, synchronize, reopened] + +name: Code Quality + jobs: + lint: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: lint - uses: golangci/golangci-lint-action@v1 + uses: golangci/golangci-lint-action@v3 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. version: v1.29 @@ -26,3 +28,21 @@ jobs: # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true + + sonarcloud: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + # Disabling shallow clone is recommended for improving relevancy of reporting + fetch-depth: 0 + - name: SonarCloud Scan + uses: sonarsource/sonarcloud-github-action@master + with: + projectBaseDir: src + args: > + -Dsonar.organization=fooooooo + -Dsonar.projectKey=barrrrrrrrr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml deleted file mode 100644 index b6e23b6f..00000000 --- a/.github/workflows/sonar.yml +++ /dev/null @@ -1,31 +0,0 @@ -on: - push: - branches: - - master - pull_request: - types: [opened, synchronize, reopened] -name: Code Quality - -jobs: - sonarcloud: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - # Disabling shallow clone is recommended for improving relevancy of reporting - fetch-depth: 0 - - name: SonarCloud Scan - uses: sonarsource/sonarcloud-github-action@master - with: - projectBaseDir: src -# args: > -# -Dsonar.organization=my-organization -# -Dsonar.projectKey=my-projectkey -# -Dsonar.python.coverage.reportPaths=coverage.xml -# -Dsonar.sources=lib/ -# -Dsonar.test.exclusions=tests/** -# -Dsonar.tests=tests/ -# -Dsonar.verbose=true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/synk.yml b/.github/workflows/synk.yml new file mode 100644 index 00000000..421b73ff --- /dev/null +++ b/.github/workflows/synk.yml @@ -0,0 +1,19 @@ +name: Snyk - Check for vulnerabilities in this Golang project +on: push +jobs: + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Run Snyk to check for vulnerabilities + uses: snyk/actions/golang@master + continue-on-error: true # To make sure that SARIF upload gets called + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + args: --sarif-file-output=snyk.sarif + - name: Upload result to GitHub Code Scanning + uses: github/codeql-action/upload-sarif@v1 + with: + sarif_file: snyk.sarif + diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 00000000..b9871e2e --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,54 @@ +on: + workflow_call: + secrets: + AWS_ACCESS_KEY_ID: + required: true + AWS_SECRET_ACCESS_KEY: + required: true + GH_ARTIFACT_TOKEN: + required: true + DYNAMODB_TABLE: + required: true + +name: Verify +env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + TOKEN: ${{ secrets.GH_ARTIFACT_TOKEN }} + AWS_DEFAULT_REGION: us-east-2 + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - run: | + echo "🎉 The commit ID of the artifact is: $GITCOMMIT" + echo "GITCOMMIT=$GITCOMMIT" >> $GITHUB_ENV + echo "🎉 The dynamodb table is: $DYNAMODB_TABLE" + echo "DYNAMODB_TABLE=$DYNAMODB_TABLE" >> $GITHUB_ENV + env: + GITCOMMIT: ${{ github.sha }} + DYNAMODB_TABLE: ${{ secrets.DYNAMODB_TABLE }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Check out gsd-verification-rules repo + uses: actions/checkout@v2 + with: + repository: contino/gsd-verification-rules + ref: main + path: gsd-verification-rules + ssh-key: "${{ secrets.SSH_PRIVATE_KEY }}" + + - name: Download all workflow run artifacts + uses: actions/download-artifact@v3 + with: + path: gsd-verification-rules + + - name: Run all verification rules + run: cd gsd-verification-rules && env && make verify diff --git a/.gitignore b/.gitignore index a4a78619..a98b8c76 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,9 @@ aws.env # ide specific directories .idea -.vscode \ No newline at end of file +.vscode +Makefile + +# gsd specific +gsd-verification-rules +security-report/* \ No newline at end of file diff --git a/Makefile b/Makefile index c5294026..ffb52450 100644 --- a/Makefile +++ b/Makefile @@ -1,64 +1,103 @@ -DOCKER_TAG ?= go-hello-world -FULL_TAG ?= ${DOCKER_TAG}:${HASH} -DYNAMODB_TABLE ?= ${DOCKER_TAG} -PORT ?= "8080" +3M_IMAGE_NAME ?= flemay/musketeers +REGISTRY_URL := ghcr.io +GITHUB_REPOSITORY ?= contino/gsd-hello-world +IMAGE_NAME ?= go-hello-world +FULL_TAG ?= ${REGISTRY_URL}/${GITHUB_REPOSITORY}/${IMAGE_NAME}:${HASH} +DYNAMODB_TABLE ?= ${IMAGE_NAME}-v2 +PORT ?= 8080 +DOCKER_COMPOSE ?= FULL_TAG=${FULL_TAG} docker-compose GO_TEST_DOCKER_COMPOSE ?= docker-compose run --rm gobase go test -v -cover AWS_CLI_DOCKER_COMPOSE ?= docker-compose run --rm awscli -HASH := $(shell git rev-parse HEAD) -VERACODE_ID?= "someveracodeid" +HASH := $(shell git rev-parse HEAD) +ENVFILE ?= aws.template +PIPELINE_BASE ?= contino/gsd-hello-world -ENVFILE ?= aws.template +.DEFAULT_GOAL := help -envfile: - echo "from envfile" - echo "FOO=${FOO}" - echo "BAR=${BAR}" +.PHONY: help +help: ## List of targets with descriptions + @echo "\n--------------------- Run [TARGET] [ARGS] or "make help" for more information ---------------------\n" + @for MAKEFILENAME in $(MAKEFILE_LIST); do \ + grep -E '[a-zA-Z_-]+:.*?## .*$$' $$MAKEFILENAME | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'; \ + done + @echo "\n---------------------------------------------------------------------------------------------------\n" + +.PHONY: list +list: pull-3m-image ## Get list of all make targets + @echo "list"; \ + $(DOCKER_RUN_3M) $(MAKE) --no-print-directory _list + +.PHONY: _list +_list: + @echo "\n---------------------------------------\nList of available targets:\n---------------------------------------" + @$(MAKE) -pRrq -f $(firstword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' + @echo "\n---------------------------------------\n" + +.PHONY: envfile +envfile: ## Create envfile cp $(ENVFILE) aws.env -.PHONY : build -build: +.PHONY: build +build: ## Build the application docker build -t ${FULL_TAG} . +.PHONY: push +push: dockerlogin ## Push the containerized application + docker push ${FULL_TAG} + +.PHONY: dockerlogin +dockerlogin: ## Login to docker registry + docker login ${REGISTRY_URL} + .PHONY: run -run: - docker run -d -p ${PORT}:${PORT} --name ${DOCKER_TAG} ${FULL_TAG} +run: ## Run the application + $(DOCKER_COMPOSE) up -d gohelloworld + $(DOCKER_COMPOSE) up healthcheck .PHONY: down -down: - docker rm -f ${DOCKER_TAG} +down: ## Stop the application + $(DOCKER_COMPOSE) down .PHONY: test -test: envfile +test: envfile ## Test the application ${GO_TEST_DOCKER_COMPOSE} +.PHONY: verify +verify: + git clone git@github.com:contino/gsd-verification-rules.git || true + cd gsd-verification-rules && git pull && make verify + +.PHONY: security +security: run ## Run security checks against app + rm -rf security-report + mkdir -p security-report + $(DOCKER_COMPOSE) -p security -f security-compose.yml up + $(DOCKER_COMPOSE) -p security -f security-compose.yml down || true + @$(MAKE) --no-print-directory down + .PHONY: create_table create_table: envfile - echo "from create_table" - echo "FOO=${FOO}" - echo "BAR=${BAR}" - ${AWS_CLI_DOCKER_COMPOSE} dynamodb create-table \ - --table-name ${DYNAMODB_TABLE} \ - --attribute-definitions \ - AttributeName=GIT_COMMIT,AttributeType=S \ - AttributeName=VERACODE_ID,AttributeType=S \ - --key-schema \ - AttributeName=GIT_COMMIT,KeyType=HASH \ - AttributeName=VERACODE_ID,KeyType=RANGE \ - --provisioned-throughput \ - ReadCapacityUnits=10,WriteCapacityUnits=5 + -${AWS_CLI_DOCKER_COMPOSE} dynamodb create-table \ + --table-name ${DYNAMODB_TABLE} \ + --attribute-definitions AttributeName=GIT_COMMIT,AttributeType=S \ + --key-schema AttributeName=GIT_COMMIT,KeyType=HASH \ + --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5 \ + --tags Key=Permanent,Value=True +.PHONY: create_tags create_tags: envfile ${AWS_CLI_DOCKER_COMPOSE} dynamodb put-item \ --table-name ${DYNAMODB_TABLE} \ - --item \ - '{"GIT_COMMIT": {"S": "${HASH}"}, "VERACODE_ID":{"S": ${VERACODE_ID}}}' + --item '{ "GIT_COMMIT": {"S": "${HASH}"}, "PIPELINE_BASE":{"S": "${PIPELINE_BASE}"}, "PIPELINE_ID":{"S": "${PIPELINE_ID}"} }' .PHONY: clean -clean: - docker kill ${DOCKER_TAG} - docker rm ${DOCKER_TAG} - +clean: ## Cleanup and remove docker application + docker kill ${IMAGE_NAME} + docker rm ${IMAGE_NAME} +.PHONY: pull-3m-image +pull-3m-image: ## Pull 3M image for local executions + docker pull ${3M_IMAGE_NAME} diff --git a/aws.template b/aws.template index 2e6b841a..fd7d8bbe 100644 --- a/aws.template +++ b/aws.template @@ -2,4 +2,5 @@ FOO BAR AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY +AWS_SESSION_TOKEN AWS_DEFAULT_REGION diff --git a/deployment.yml b/deployment.yml index 399434d5..2ea2eb57 100644 --- a/deployment.yml +++ b/deployment.yml @@ -1,4 +1,4 @@ -# Copyright 2019 Google LLC +# Copyright 2021 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,12 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -apiVersion: apps/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: - name: gke-test + name: gke-hello-app spec: - replicas: 1 + replicas: 2 + selector: + matchLabels: + app: gke-hello-app strategy: rollingUpdate: maxSurge: 1 @@ -26,15 +29,27 @@ spec: template: metadata: labels: - app: gke-test + app: gke-hello-app spec: containers: - - name: gke-test - image: gcr.io/contino-919ebf1dd6da5f73/hello-world:latest + - name: hello-app + image: gcr.io/contini-0fd0de1002f7de5a/go-hello-world:latest ports: - containerPort: 8080 - resources: - requests: - cpu: 100m - limits: - cpu: 100m +# resources: +# requests: +# cpu: 100m +# limits: +# cpu: 100m +--- +apiVersion: v1 +kind: Service +metadata: + name: gke-hello-app-service +spec: + type: LoadBalancer + ports: + - port: 80 + targetPort: 8080 + selector: + app: gke-hello-app diff --git a/docker-compose.yml b/docker-compose.yml index ab618c22..aa103663 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,35 @@ version: '3' + +networks: + default: + external: false + name: go-hello-world + services: gohelloworld: - build: . - image: gohelloworld:latest + image: ${FULL_TAG} ports: - "8080:8080" + healthcheck: + test: curl --fail http://localhost:8080 || exit 1 + interval: 10s + retries: 50 + start_period: 20s + timeout: 2s + healthcheck: + image: alpine/curl + command: http://gohelloworld:8080 --max-time 60 --retry-max-time 60 --connect-timeout 5 --retry 10 --retry-connrefused --silent + gobase: image: golang:latest working_dir: "/app/src" volumes: - "./:/app" + profiles: ["test"] awscli: image: amazon/aws-cli working_dir: "/app" env_file: - - ./aws.env \ No newline at end of file + - ./aws.env + profiles: ["aws"] + diff --git a/docs/deploy.md b/docs/deploy.md new file mode 100644 index 00000000..76f312af --- /dev/null +++ b/docs/deploy.md @@ -0,0 +1,38 @@ +# Deployments + +A deployment takes a built artifact (ie golang app in a docker container) and deploys it to an environment (a k8s cluster). + +## The k8s Environment + +We're using GKE from Google Cloud in our example, but any k8s cluster will work. + +There are currently a few things we're doing manually to prepare an environment: + +- Cluster Creation (Done through Console - Autopilot) +- IAM Service Account using permissions described here [Deploy to k8s](https://docs.github.com/en/actions/deployment/deploying-to-your-cloud-provider/deploying-to-google-kubernetes-engine) - ie cluster admin, and storage admin (you may have to tweak permissions for your own deployment) +- `GKE_SA_KEY` in github populated using the json key created from the above service account - ie `cat key.json | base64` + +The Cluster `gke-test-2022` lives in tihe project `contini-XXX-de5a` which is `Squad Zero > Andrew Khoury Contino`. + +## The deployment + +Our github workflow `deploy.yml` ("Deploy to k8s") does the deployment, and is based on [google-github-actions](https://github.com/google-github-actions/setup-gcloud/tree/main/example-workflows/gke). + +What it does: + +- Sets some key variables `PROJECT_ID`, `GKE_CLUSTER`, `GKE_ZONE`, `DEPLOYMENT_NAME`, `IMAGE` +- Runs as a production deployment, checks out code, auths to gcloud +- builds the app, publishes it +- deploys to k8s after ensuring we're using the newly built image (using a combo of kustomize and kubectl) + +What it depends on: + +- `kustomization.yml` (to tell it to look for `deployment.yml`) +- `deployment.yml` (k8s config - app/service name, container, ports, lb etc) + +# Todos + +- Use dev for this pipeline? And Prod for the prod pipeline? +- Automate env setup +- Make this step gsd like, ie `make deploy` +- Instead of building during this step, leverage the build from the main CI? (build once deploy many) diff --git a/kustomization.yml b/kustomization.yml index 989a38cf..a580c34b 100644 --- a/kustomization.yml +++ b/kustomization.yml @@ -16,4 +16,3 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - deployment.yml -- service.yml diff --git a/repo-visualizer.svg b/repo-visualizer.svg index 7a1c7327..bd47336d 100644 --- a/repo-visualizer.svg +++ b/repo-visualizer.svg @@ -1 +1 @@ -srcsrc.github/workflows.github/workflowsmain.gomain.gomain.gomain_test.gomain_test.gomain_test.goREADME.mdREADME.mdREADME.mdLICENSELICENSELICENSEMakefileMakefileMakefiledeployment.ymldeployment.ymldeployment.ymlservice.ymlservice.ymlservice.ymlkustomization...kustomization...kustomization...docker-com...docker-com...docker-com....gitignore.gitignore.gitignoresonar-pro...sonar-pro...sonar-pro...DockerfileDockerfileDockerfilemain.ymlmain.ymlmain.ymlgoogle.ymlgoogle.ymlgoogle.ymlsonar.ymlsonar.ymlsonar.ymllint.ymllint.ymllint.ymlcreate-dia...create-dia...create-dia....gitignore.go.md.mod.properties.svg.ymleach dot sized by file size \ No newline at end of file +srcsrc.github/workflows.github/workflowsmain_test.gomain_test.gomain_test.gomain.gomain.gomain.goREADME.mdREADME.mdREADME.mdLICENSELICENSELICENSEdocs/deploy.mddocs/deploy.mddocs/deploy.mdrunrunrunMakefileMakefileMakefiledeployment.ymldeployment.ymldeployment.ymldocker-compo...docker-compo...docker-compo...kustomizatio...kustomizatio...kustomizatio...security-co...security-co...security-co....gitignore.gitignore.gitignoresonar-pro...sonar-pro...sonar-pro...deploy.ymldeploy.ymldeploy.ymlmain.ymlmain.ymlmain.ymlverify.ymlverify.ymlverify.ymlquality.ymlquality.ymlquality.ymlmain-test.yml....main-test.yml....main-test.yml....playground.ymlplayground.ymlplayground.ymlsynk.ymlsynk.ymlsynk.ymlcreate-dia...create-dia...create-dia....gitignore.go.md.mod.properties.svg.ymleach dot sized by file size \ No newline at end of file diff --git a/run b/run new file mode 100755 index 00000000..a067f382 --- /dev/null +++ b/run @@ -0,0 +1,121 @@ +#!/usr/bin/env bash + +# This idea is heavily inspired by: https://github.com/adriancooney/Taskfile +# FOR WHEN YOU DO NOT WANT TO (OR CANNOT) US MAKE +# Found this here.... +# https://www.youtube.com/watch?v=SdmYd5hJISM + +# Ensure specific failures (add u if you want fail on any missing or unset variable) +set -eo pipefail + +# If we're running in CI we need to disable TTY allocation for docker-compose +# commands that enable it by default, such as exec and run. +TTY="" +if [[ ! -t 1 ]]; then + TTY="-T" +fi + +HASH="$(git rev-parse HEAD)" + +: "${ENVFILE:=aws.template}" +: "${IMAGE_NAME_3M:=flemay/musketeers}" +: "${REGISTRY_URL:=ghcr.io}" +: "${GITHUB_REPOSITORY:=contino/gsd-hello-world}" +: "${IMAGE_NAME:=go-hello-world}" +: "${FULL_TAG:=${REGISTRY_URL}/${GITHUB_REPOSITORY}/${IMAGE_NAME}:${HASH}}" +: "${DYNAMODB_TABLE:=${IMAGE_NAME}-v2}" +: "${PORT:=8080}" +: "${PIPELINE_BASE:=contino/gsd-hello-world}" + +# ----------------------------------------------------------------------------- +# Helper functions start with _ and aren't listed in this script's help menu. +# ----------------------------------------------------------------------------- + +function _dc { + docker-compose run --rm "${@}" +} + +# ----------------------------------------------------------------------------- + +function envfile { ## Create envfile + echo "from envfile" + echo "FOO=${FOO}" + echo "BAR=${BAR}" + cp $ENVFILE aws.env +} + +function build { ## Build the application + docker build -t ${FULL_TAG} . +} + +function push { ## Push the containerized application + dockerlogin + docker push ${FULL_TAG} +} + +function dockerlogin { ## Login to docker registry + docker login ${REGISTRY_URL} +} + +function run { ## Run the application + docker run -d -p ${PORT}:${PORT} --name ${IMAGE_NAME} ${FULL_TAG} +} + +function down { ## Stop the application + docker rm -f ${IMAGE_NAME} +} + +function test { ## Test the application + envfile + _dc gobase go test -v -cover +} + +function verify { + git clone git@github.com:contino/gsd-verification-rules.git || true + cd gsd-verification-rules && git pull && make verify +} + +function create_table { + envfile + echo "from create_table" + echo "FOO=${FOO}" + echo "BAR=${BAR}" + _dc awscli dynamodb create-table \ + --table-name ${DYNAMODB_TABLE} \ + --attribute-definitions AttributeName=GIT_COMMIT,AttributeType=S AttributeName=PIPELINE_ID,AttributeType=S \ + --key-schema AttributeName=GIT_COMMIT,KeyType=HASH AttributeName=PIPELINE_ID,KeyType=RANGE \ + --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5 +} + +function create_tags { + envfile + _dc awscli dynamodb put-item \ + --table-name ${DYNAMODB_TABLE} \ + --item '{ "GIT_COMMIT": {"S": "${HASH}"}, "PIPELINE_BASE":{"S": "${PIPELINE_BASE}"}, "PIPELINE_ID":{"S": "${PIPELINE_ID}"} }' +} + +function clean { ## Cleanup and remove docker application + docker kill ${IMAGE_NAME} + docker rm ${IMAGE_NAME} +} + +function pull-3m-image { ## Pull 3M image for local executions + docker pull ${3M_IMAGE_NAME} +} + +function help { + echo -e "\n------------------------------------- Tasks with descriptions -------------------------------------\n" + grep -E 'function [a-zA-Z_-]+ {.*?## .*$$' ./run | sort | sed 's|function ||' | awk 'BEGIN {FS = " {.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $1, $2}' + echo -e "\n---------------------------------------------------------------------------------------------------\n" +} + +function list { + printf "%s [args]\n\nTasks:\n" "${0}" + + compgen -A function | grep -v "^_" | cat -n + + printf "\nExtended help:\n Each task has comments for general usage\n" +} + +TIMEFORMAT=$'\nTask completed in %3lR' +time "${@:-list}" \ No newline at end of file diff --git a/security-compose.yml b/security-compose.yml new file mode 100644 index 00000000..3db10f3c --- /dev/null +++ b/security-compose.yml @@ -0,0 +1,22 @@ +version: '3' + +networks: + default: + external: false + name: go-hello-world + +services: + + zap-scan: + user: root + image: owasp/zap2docker-weekly + volumes: + - "./security-report:/security-report" + command: ["/bin/sh","-c","zap-baseline.py -t http://gohelloworld:8080 > /security-report/zap-security-report.txt || true"] + + # Used for testing but not very good. Waiting to add something better here + # nikto-scan: + # image: sullo/nikto + # volumes: + # - "./security-report:/output" + # command: -h http://gohelloworld:8080 -o output/nikto-security-report.txt diff --git a/service.yml b/service.yml deleted file mode 100644 index 7d6da5b1..00000000 --- a/service.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2019 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: v1 -kind: Service -metadata: - name: gke-test-service -spec: - type: LoadBalancer - ports: - - port: 80 - targetPort: 8080 - selector: - app: gke-test diff --git a/sonar-project.properties b/sonar-project.properties index a259be2c..6959fee7 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,4 +4,4 @@ sonar.projectKey=barrrrrrrrr # relative paths to source directories. More details and properties are described # in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/ -sonar.sources=. +sonar.sources=./src diff --git a/src/main.go b/src/main.go index 595de9d8..fd987e3b 100644 --- a/src/main.go +++ b/src/main.go @@ -41,7 +41,10 @@ func getOneEvent(w http.ResponseWriter, r *http.Request) { for _, singleEvent := range events { if singleEvent.ID == eventID { - json.NewEncoder(w).Encode(singleEvent) + err := json.NewEncoder(w).Encode(singleEvent) + if err != nil { + log.Printf("Error encoding event: %s Error: %s", singleEvent, err) + } } } } diff --git a/src/main_test.go b/src/main_test.go index 8e6531dd..171d8871 100644 --- a/src/main_test.go +++ b/src/main_test.go @@ -43,12 +43,32 @@ func TestGETHome(t *testing.T) { if testing.CoverMode() != "" { c := testing.Coverage() - if c < 0.8 { - fmt.Println("Tests passed but coverage failed at", c) + if c < 0.1 { + fmt.Println("Tests passed but test-coverage below threshold of less than 10%. Current test-coverage is: ", c) rc = -1 + os.Exit(rc) } + if c >= 0.1 { + fmt.Println("Tests passed and test-coverage is above threshold of at least 10%. Current test-coverage is: ", c) + } } - os.Exit(rc) - } + +func TestGetOneEvent(t *testing.T) { + t.Run("returns 200 status code", func(t *testing.T) { + request, _ := http.NewRequest(http.MethodGet, "/events/1", nil) + response := httptest.NewRecorder() + + getOneEvent(response, request) + + got := response.Result().StatusCode + want := 200 + + if got != want { + t.Errorf("got %q, want %q", got, want) + } + }) + + +} \ No newline at end of file