diff --git a/.github/workflows/time-tracker-ui-cd-prod.yml b/.github/workflows/time-tracker-ui-cd-prod.yml new file mode 100644 index 000000000..13e7f6b86 --- /dev/null +++ b/.github/workflows/time-tracker-ui-cd-prod.yml @@ -0,0 +1,52 @@ +name: time-tracker-ui-cd-prod + +on: + release: + types: + - published + +jobs: + cd: + runs-on: ubuntu-latest + env: + TF_WORKSPACE: prod + WORKING_DIR: infrastructure/ + ARM_CLIENT_ID: ${{secrets.TF_ARM_CLIENT_ID}} + ARM_CLIENT_SECRET: ${{secrets.TF_ARM_CLIENT_SECRET}} + ARM_SUBSCRIPTION_ID: ${{secrets.TF_ARM_SUBSCRIPTION_ID}} + ARM_TENANT_ID: ${{secrets.TF_ARM_TENANT_ID}} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get the release_version + run: | + echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + echo $RELEASE_VERSION + - name: Login to azure + uses: Azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Build the docker image + run: make build + + - name: Publish docker image to prod azure container registry + run: | + make login publish acr=timetrackerserviceprodregistry image_tag=$RELEASE_VERSION + - name: Setup terraform + uses: hashicorp/setup-terraform@v1 + + - name: Authenticate with the TF modules repository + uses: webfactory/ssh-agent@v0.5.4 + with: + ssh-private-key: ${{ secrets.INFRA_TERRAFORM_MODULES_SSH_PRIV_KEY }} + + - name: Terraform Init + working-directory: ${{ env.WORKING_DIR }} + run: terraform init + + - name: Terraform Apply + working-directory: ${{ env.WORKING_DIR }} + run: terraform apply -lock=false -var-file="${{ env.TF_WORKSPACE }}.tfvars" -var "image_tag=$RELEASE_VERSION" -auto-approve diff --git a/.github/workflows/time-tracker-ui-cd-stage.yml b/.github/workflows/time-tracker-ui-cd-stage.yml new file mode 100644 index 000000000..ab736d69d --- /dev/null +++ b/.github/workflows/time-tracker-ui-cd-stage.yml @@ -0,0 +1,45 @@ +name: time-tracker-ui-cd-stage + +on: + push: + tags: + - 'v*.*.*' + +jobs: + cd: + runs-on: ubuntu-latest + env: + TF_WORKSPACE: stage + WORKING_DIR: infrastructure/ + ARM_CLIENT_ID: ${{secrets.TF_ARM_CLIENT_ID}} + ARM_CLIENT_SECRET: ${{secrets.TF_ARM_CLIENT_SECRET}} + ARM_SUBSCRIPTION_ID: ${{secrets.TF_ARM_SUBSCRIPTION_ID}} + ARM_TENANT_ID: ${{secrets.TF_ARM_TENANT_ID}} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Get the release_version + run: | + echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + echo $RELEASE_VERSION + - name: Login to azure + uses: Azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + - name: Build the docker image + run: make build + - name: Publish docker image to stage azure container registry + run: | + make login publish acr=timetrackerservicestageregistry image_tag=$RELEASE_VERSION + - name: Setup terraform + uses: hashicorp/setup-terraform@v1 + - name: Authenticate with the TF modules repository + uses: webfactory/ssh-agent@v0.5.4 + with: + ssh-private-key: ${{ secrets.INFRA_TERRAFORM_MODULES_SSH_PRIV_KEY }} + - name: Terraform Init + working-directory: ${{ env.WORKING_DIR }} + run: terraform init + - name: Terraform Apply + working-directory: ${{ env.WORKING_DIR }} + run: terraform apply -lock=false -var-file="${{ env.TF_WORKSPACE }}.tfvars" -var "image_tag=$RELEASE_VERSION" -auto-approve diff --git a/.github/workflows/time-tracker-ui-ci.yml b/.github/workflows/time-tracker-ui-ci.yml new file mode 100644 index 000000000..92b8985b3 --- /dev/null +++ b/.github/workflows/time-tracker-ui-ci.yml @@ -0,0 +1,138 @@ +name: time-tracker-ui-ci + +on: + push: + branches: + - master + + pull_request: + branches: + - master + +jobs: + ci: + runs-on: ubuntu-latest + env: + WORKING_DIR: infrastructure/ + DB_CONNECTION: ${{ secrets.DB_CONNECTION }} + ARM_CLIENT_ID: ${{secrets.TF_ARM_CLIENT_ID}} + ARM_CLIENT_SECRET: ${{secrets.TF_ARM_CLIENT_SECRET}} + ARM_SUBSCRIPTION_ID: ${{secrets.TF_ARM_SUBSCRIPTION_ID}} + ARM_TENANT_ID: ${{secrets.TF_ARM_TENANT_ID}} + strategy: + max-parallel: 5 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Authenticate with the TF modules repository + uses: webfactory/ssh-agent@v0.5.4 + with: + ssh-private-key: ${{ secrets.INFRA_TERRAFORM_MODULES_SSH_PRIV_KEY }} + + - name: build docker + run: make build + + - name: Inject Secrets + env: + SCOPES: ${{ secrets.SCOPES }} + CLIENT_ID: ${{ secrets.CLIENT_ID }} + AUTHORITY: ${{ secrets.AUTHORITY }} + STACK_EXCHANGE_ID: ${{ secrets.STACK_EXCHANGE_ID }} + STACK_EXCHANGE_ACCESS_TOKEN: ${{ secrets.STACK_EXCHANGE_ACCESS_TOKEN }} + AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${{ secrets.AZURE_APP_CONFIGURATION_CONNECTION_STRING }} + run: | + chmod +x ./scripts/populate-keys.sh + sh ./scripts/populate-keys.sh + + - name: Running tests + run: | + chmod -R 777 ./$home + make test + - name: Generate coverage report + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: bash <(curl -s https://codecov.io/bash) + + - name: Setup terraform + uses: hashicorp/setup-terraform@v1 + + - name: 'Terraform Init' + id: init + working-directory: ./${{ env.WORKING_DIR }} + run: terraform init + + - name: 'Terraform validate' + id: validate + working-directory: ./${{ env.WORKING_DIR }} + run: terraform validate + + - name: Terraform Plan Stage + id: plan-stage + if: github.event_name == 'pull_request' + run: terraform plan -var-file=${{ env.TF_WORKSPACE }}.tfvars -var image_tag=latest -no-color + continue-on-error: true + working-directory: ./${{ env.WORKING_DIR }} + env: + TF_WORKSPACE: stage + + - name: Terraform Plan Prod + id: plan-prod + if: github.event_name == 'pull_request' + run: terraform plan -var-file=${{ env.TF_WORKSPACE }}.tfvars -no-color + continue-on-error: true + working-directory: ./${{ env.WORKING_DIR }} + env: + TF_WORKSPACE: prod + + - name: Update Pull Request with Stage Plan + uses: actions/github-script@0.9.0 + if: github.event_name == 'pull_request' + env: + PLAN: "terraform\n${{ steps.plan-stage.outputs.stdout }}" + TF_WORKSPACE: stage + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const output = `#### [${{ env.WORKING_DIR }}][${{ env.TF_WORKSPACE }}] Terraform Plan 📖 \`${{ steps.plan-stage.outcome }}\` +
Show Plan + \`\`\`\n + ${process.env.PLAN} + \`\`\` +
+ *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`; + github.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + - name: Update Pull Request with Prod Plan + uses: actions/github-script@0.9.0 + if: github.event_name == 'pull_request' + env: + PLAN: "terraform\n${{ steps.plan-prod.outputs.stdout }}" + TF_WORKSPACE: prod + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const output = `#### [${{ env.WORKING_DIR }}][${{ env.TF_WORKSPACE }}] Terraform Plan 📖 \`${{ steps.plan-prod.outcome }}\` +
Show Plan + \`\`\`\n + ${process.env.PLAN} + \`\`\` +
+ *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`; + github.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + - name: Terraform Plan Stage Status + if: steps.plan-stage.outcome == 'failure' + run: exit 1 + + - name: Terraform Plan Prod Status + if: steps.plan-prod.outcome == 'failure' + run: exit 1 diff --git a/Dockerfile b/Dockerfile index 01a5d3727..7c2029c6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,33 @@ FROM node:14 AS development ENV USERNAME timetracker ENV HOME /home/${USERNAME} +ENV CHROME_BIN /opt/google/chrome/google-chrome +#Essential tools and xvfb +RUN apt-get update && apt-get install -y \ + software-properties-common \ + unzip \ + curl \ + wget \ + xvfb + +#Chrome browser to run the tests +ARG CHROME_VERSION=65.0.3325.181 +RUN curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add \ + && wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \ + && dpkg -i google-chrome-stable_current_amd64.deb || true +RUN apt-get install -y -f \ + && rm -rf /var/lib/apt/lists/* + +#Disable the SUID sandbox so that chrome can launch without being in a privileged container +RUN dpkg-divert --add --rename --divert /opt/google/chrome/google-chrome.real /opt/google/chrome/google-chrome \ + && echo "#! /bin/bash\nexec /opt/google/chrome/google-chrome.real --no-sandbox --disable-setuid-sandbox \"\$@\"" > /opt/google/chrome/google-chrome \ + && chmod 755 /opt/google/chrome/google-chrome + +#Chrome Driver +ARG CHROME_DRIVER_VERSION=2.37 +RUN mkdir -p /opt/selenium \ + && curl http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -o /opt/selenium/chromedriver_linux64.zip \ + && cd /opt/selenium; unzip /opt/selenium/chromedriver_linux64.zip; rm -rf chromedriver_linux64.zip; ln -fs /opt/selenium/chromedriver /usr/local/bin/chromedriver; RUN useradd -ms /bin/bash ${USERNAME} @@ -9,6 +36,9 @@ WORKDIR ${HOME}/time-tracker-ui COPY . . RUN rm -f .env RUN chown ${USERNAME}:${USERNAME} -R ${HOME}/time-tracker-ui +RUN chmod -R 777 ${HOME}/time-tracker-ui + + USER ${USERNAME} RUN npm cache clean --force && npm install diff --git a/Makefile b/Makefile index d86ace915..7d77f85ad 100644 --- a/Makefile +++ b/Makefile @@ -27,11 +27,11 @@ logs: ## Show logs of timetracker_ui. .PHONY: stop stop: ## Stop container timetracker_ui. - docker-compose stop + docker-compose stop .PHONY: restart restart: ## Restart container timetracker_ui. - docker-compose stop + docker-compose stop docker-compose up -d .PHONY: remove @@ -39,14 +39,19 @@ remove: ## Delete container timetracker_ui. docker-compose down --volumes --remove-orphans --rmi local .PHONY: test -test: ## Run all tests on docker container timetracker_ui. - docker-compose --env-file ./.env up -d - docker exec -it timetracker_ui bash -c "npm run test" +test: ## Run all tests on docker container timetracker_ui at the CLI. + docker-compose -f docker-compose.yml --env-file ./.env up -d + docker exec timetracker_ui bash -c "npm run ci-test" + +.PHONY: testdev +testdev: ## Run all tests on docker container timetracker_ui at the Dev + docker-compose -f docker-compose.yml -f docker-compose.dev.yml --env-file ./.env up -d + docker exec timetracker_ui bash -c "npm run ci-test" .PHONY: publish -publish: ## Publish the container image timetracker_ui. - docker tag timetracker_ui:latest $(registry_url)/timetracker_ui:latest - docker push $(registry_url)/timetracker_ui:latest +publish: require-acr-arg require-image_tag-arg ## Upload a docker image to an azure container registry acr= image_tag= + docker tag timetracker_api $(acr).azurecr.io/timetracker_api:$(image_tag) + docker push $(acr).azurecr.io/timetracker_api:$(image_tag) .PHONY: build_prod build_prod: ## Create docker image with dependencies needed for production. @@ -54,7 +59,7 @@ build_prod: ## Create docker image with dependencies needed for production. .PHONY: run_prod run_prod: ## Execute timetracker_ui_prod docker container. - docker run -d -p 4200:4200 --name timetracker_ui_prod timetracker_ui_prod + docker run -d -p 4200:4200 --name timetracker_ui_prod timetracker_ui_prod .PHONY: remove_prod remove_prod: ## Delete container timetracker_ui_pro. @@ -63,9 +68,9 @@ remove_prod: ## Delete container timetracker_ui_pro. .PHONY: publish_prod publish_prod: ## Publish the container image timetracker_ui_prod. - docker tag timetracker_ui_prod:latest $(registry_url)/timetracker_ui_prod:latest - docker push $(registry_url)/timetracker_ui_prod:latest + docker tag timetracker_ui_prod:$(image_tag) $(registry_url)/timetracker_ui_prod:$(image_tag) + docker push $(registry_url)/timetracker_ui_prod:$(image_tag) .PHONY: login login: ## Login in respository of docker images. - az acr login --name $(container_registry) + az acr login --name $(acr) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 000000000..18535960c --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,13 @@ +version: '3.9' +services: + time-tracker-ui: + user: root + volumes: + - ./src:/home/timetracker/time-tracker-ui/src/ + - ./scripts:/home/timetracker/time-tracker-ui/scripts/ + - ./e2e:/home/timetracker/time-tracker-ui/e2e/ + - ./coverage:/home/timetracker/time-tracker-ui/coverage + - ./angular.json:/home/timetracker/time-tracker-ui/angular.json + - ./karma.conf.js:/home/timetracker/time-tracker-ui/karma.conf.js + - ./package.json:/home/timetracker/time-tracker-ui/package.json + - ./webpack.config.js:/home/timetracker/time-tracker-ui/webpack.config.js diff --git a/docker-compose.yml b/docker-compose.yml index d7516c1a1..61bd1ca44 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: - 4200:4200 - 9876:9876 environment: + CHROME_BIN: /opt/google/chrome/google-chrome AUTHORITY: ${AUTHORITY} CLIENT_ID: ${CLIENT_ID} SCOPES: ${SCOPES} @@ -20,12 +21,4 @@ services: AUTHORITY_JSON: ${AUTHORITY_JSON} CLIENT_ID_JSON: ${CLIENT_ID_JSON} SCOPES_JSON: ${SCOPES_JSON} - volumes: - - ./src:/home/timetracker/time-tracker-ui/src/ - - ./scripts:/home/timetracker/time-tracker-ui/scripts/ - - ./e2e:/home/timetracker/time-tracker-ui/e2e/ - - ./coverage:/home/timetracker/time-tracker-ui/coverage - - ./angular.json:/home/timetracker/time-tracker-ui/angular.json - - ./karma.conf.js:/home/timetracker/time-tracker-ui/karma.conf.js - - ./package.json:/home/timetracker/time-tracker-ui/package.json - - ./webpack.config.js:/home/timetracker/time-tracker-ui/webpack.config.js + diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js index 7c798cfff..17f6b4754 100644 --- a/e2e/protractor.conf.js +++ b/e2e/protractor.conf.js @@ -13,7 +13,14 @@ exports.config = { './src/**/*.e2e-spec.ts' ], capabilities: { - browserName: 'chrome' + browserName: 'chrome', + 'chromeOptions': { + 'args': [ + '--no-sandbox', + '--headless', + '--window-size=1024,768' + ] + } }, directConnect: true, baseUrl: 'http://localhost:4200/', @@ -29,4 +36,4 @@ exports.config = { }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); } -}; \ No newline at end of file +}; diff --git a/karma.conf.js b/karma.conf.js index c51657f20..afb638d5f 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,6 +1,7 @@ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html + module.exports = function (config) { config.set({ basePath: '', @@ -21,6 +22,12 @@ module.exports = function (config) { seed: '90967', }, // leave Jasmine Spec Runner output visible in browser }, + // Karma Typescript compiler options + karmaTypescriptConfig: { + coverageOptions : { + instrumentation: false + } + }, coverageIstanbulReporter: { dir: require('path').join(__dirname, './coverage/time-tracker'), reports: ['html', 'lcovonly', 'text-summary'], diff --git a/package.json b/package.json index fc71525be..38b121695 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "ng": "ng", "start": "ng serve", "build": "ng build --prod", - "test": "ng test", + "test": "ng test --browsers ChromeHeadless", "test-headless": "ng test --browsers ChromeHeadless", "ci-test": "ng test --no-watch --no-progress --browsers ChromeHeadless", "lint": "ng lint", diff --git a/scripts/populate-keys.sh b/scripts/populate-keys.sh index f395689af..6ab4a5cd4 100644 --- a/scripts/populate-keys.sh +++ b/scripts/populate-keys.sh @@ -1,10 +1,10 @@ #!/bin/bash -> src/environments/keys.ts -echo 'export const AUTHORITY = "'$AUTHORITY'";' >> src/environments/keys.ts -echo 'export const CLIENT_ID = "'$CLIENT_ID'";' >> src/environments/keys.ts -echo 'export const SCOPES = ["'$SCOPES'"];' >> src/environments/keys.ts -echo 'export const STACK_EXCHANGE_ID = "'$STACK_EXCHANGE_ID'";' >> src/environments/keys.ts -echo 'export const STACK_EXCHANGE_ACCESS_TOKEN = "'$STACK_EXCHANGE_ACCESS_TOKEN'";' >> src/environments/keys.ts -echo 'export const AZURE_APP_CONFIGURATION_CONNECTION_STRING = "'$AZURE_APP_CONFIGURATION_CONNECTION_STRING'";' >> src/environments/keys.ts -cat src/environments/keys.ts +> .env +echo 'AUTHORITY = '$AUTHORITY'' >> .env +echo 'CLIENT_ID = '$CLIENT_ID'' >> .env +echo 'SCOPES = '$SCOPES'' >> .env +echo 'STACK_EXCHANGE_ID = '$STACK_EXCHANGE_ID'' >> .env +echo 'STACK_EXCHANGE_ACCESS_TOKEN = '$STACK_EXCHANGE_ACCESS_TOKEN'' >> .env +echo 'AZURE_APP_CONFIGURATION_CONNECTION_STRING = '$AZURE_APP_CONFIGURATION_CONNECTION_STRING'' >> .env +cat .env