diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..bd7cf876 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "" + target-branch: develop + schedule: + interval: daily + - package-ecosystem: pip + directory: "" + target-branch: develop + schedule: + interval: daily + - package-ecosystem: docker + directory: "" + target-branch: develop + schedule: + interval: daily diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml new file mode 100644 index 00000000..821b9aa5 --- /dev/null +++ b/.github/workflows/pipeline.yaml @@ -0,0 +1,227 @@ +name: pipeline + +on: + push: + branches: + - develop + - feat/* + - hotfix/* + - main + pull_request: + branches: + - develop + - feat/* + - hotfix/* + - main + +env: + CONTAINER_NAME: ${{ github.repository }} + CONTAINER_REGISTRY_GHCR: ghcr.io + CONTAINER_PLATFORMS: linux/amd64,linux/arm64/v8 + # https://github.com/docker/buildx/releases + BUILDX_VERSION: 0.11.2 + +jobs: + init: + name: Init + runs-on: ubuntu-22.04 + outputs: + VERSION: ${{ steps.version.outputs.version }} + VERSION_FULL: ${{ steps.version.outputs.version_full }} + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + # We need all Git history for "version.sh" + fetch-depth: 0 + # Ensure "version.sh" submodule are up-to-date + submodules: recursive + + - name: Version + id: version + run: | + echo "version=$(bash cicd/version/version.sh -g . -c)" >> $GITHUB_OUTPUT + echo "version_full=$(bash cicd/version/version.sh -g . -c -m)" >> $GITHUB_OUTPUT + + sast-creds: + name: SAST - Credentials + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + with: + # We need all Git history for testing credentials + fetch-depth: 0 + # Ensure all submodules up-to-date + submodules: recursive + + - name: SAST - Credentials + uses: trufflesecurity/trufflehog@v3.63.1 + with: + base: ${{ github.event.repository.default_branch }} + extra_args: --only-verified + head: HEAD + path: . + + build-image: + name: Build & publish image + needs: + - init + - sast-creds + - sast-semgrep + runs-on: ubuntu-22.04 + permissions: + # Allow to write to GitHub Packages + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + + - name: Configure Git + run: | + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" + + - name: Setup QEMU + id: setup-qemu + uses: docker/setup-qemu-action@v3.0.0 + with: + platforms: ${{ env.CONTAINER_PLATFORMS }} + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + with: + version: v${{ env.BUILDX_VERSION }} + + - name: Login to registry - GitHub + uses: docker/login-action@v3.0.0 + with: + registry: ${{ env.CONTAINER_REGISTRY_GHCR }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Container meta + id: meta + uses: docker/metadata-action@v5.0.0 + with: + images: ${{ env.CONTAINER_REGISTRY_GHCR }}/${{ env.CONTAINER_NAME }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=ref,event=pr + type=schedule + type=schedule,pattern={{date 'YYYYMMDD'}} + type=semver,pattern={{version}},value=${{ needs.init.outputs.VERSION_FULL }} + type=sha + labels: | + org.opencontainers.image.documentation=https://github.com/${{ env.CONTAINER_NAME }} + org.opencontainers.image.vendor=${{ github.actor }} + + - name: Store tag + id: tag + run: | + branch=$(echo "${{ github.ref_name }}" | sed 's/\//-/g') + tag=$(echo "${{ steps.meta.outputs.tags }}" | grep -m1 $branch) + echo "tag=$tag" >> $GITHUB_OUTPUT + + - name: Build/push container + uses: docker/build-push-action@v5.0.0 + with: + build-args: | + VERSION=${{ needs.init.outputs.VERSION_FULL }} + cache-from: type=gha + cache-to: type=gha + context: . + labels: ${{ steps.meta.outputs.labels }} + platforms: ${{ env.CONTAINER_PLATFORMS }} + provenance: true + push: true + sbom: true + tags: ${{ steps.meta.outputs.tags }} + + sast-semgrep: + name: SAST - Semgrep + runs-on: ubuntu-22.04 + permissions: + # Allow to write to GitHub Security + security-events: write + container: + image: returntocorp/semgrep + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + + - name: Run tests + # Semgrep can be used to break the build when it detects security issues. In this case we want to upload the issues to GitHub Security + continue-on-error: true + env: + SEMGREP_RULES: p/cwe-top-25 p/owasp-top-ten p/dockerfile + run: semgrep ci --sarif --output=semgrep.sarif + + - name: Upload results to GitHub Security + uses: github/codeql-action/upload-sarif@v2.22.8 + with: + sarif_file: semgrep.sarif + + create-release: + name: Create release + needs: + - build-image + - init + permissions: + # Allow to create releases + contents: write + runs-on: ubuntu-22.04 + outputs: + RELEASE_ID: ${{ steps.create-release.outputs.result }} + # Only publish on non-scheduled main branch, as there is only one Helm repo and we cannot override an existing version + if: (github.event_name != 'schedule') && (github.ref == 'refs/heads/main') + steps: + - name: Checkout + uses: actions/checkout@v4.1.1 + + - name: Create release + id: create-release + uses: actions/github-script@v7.0.1 + with: + script: | + const isMain = context.ref == `refs/heads/${context.payload.repository.default_branch}`; + const repoName = context.repo.repo; + + console.log(isMain ? 'Creating release for default branch' : 'Creating release for non-default branch'); + + const { data } = await github.rest.repos.createRelease({ + draft: true, + generate_release_notes: true, + name: `${repoName} v${{ needs.init.outputs.VERSION }}`, + owner: context.repo.owner, + prerelease: !isMain, + repo: repoName, + tag_name: 'v${{ needs.init.outputs.VERSION }}', + target_commitish: context.ref, + }); + return data.id + + publish-release: + name: Publish release + permissions: + # Allow to write releases + contents: write + runs-on: ubuntu-22.04 + needs: + - create-release + - init + # Only publish on non-scheduled default branch + if: (github.event_name != 'schedule') && (github.ref == 'refs/heads/${context.payload.repository.default_branch}') + steps: + - name: publish release + id: publish-release + uses: actions/github-script@v7.0.1 + with: + script: | + github.rest.repos.updateRelease({ + draft: false, + owner: context.repo.owner, + release_id: ${{ needs.create-release.outputs.RELEASE_ID }}, + repo: context.repo.repo, + }); diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..4c1fb770 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "modifications", + "python.analysis.typeCheckingMode": "basic", + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..7db2a947 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# Base container +FROM docker.io/library/python:3.11-slim-bullseye@sha256:9f35f3a6420693c209c11bba63dcf103d88e47ebe0b205336b5168c122967edf AS base + +RUN rm -f /etc/apt/apt.conf.d/docker-clean \ + && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache +RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked \ + apt-get update -q + +# Build container +FROM base AS build + +RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked --mount=target=/root/.cache/pip,type=cache,sharing=locked \ + apt-get install -y -q --no-install-recommends \ + gcc \ + python3-dev \ + && python3 -m pip install --upgrade \ + pip \ + setuptools \ + wheel + +RUN python -m venv /venv +ENV PATH=/venv/bin:$PATH + +COPY requirements.txt . +RUN --mount=target=/root/.cache/pip,type=cache,sharing=locked \ + python3 -m pip install --requirement requirements.txt + +# Output container +FROM base + +ARG VERSION +ENV VERSION=${VERSION} + +RUN useradd -m appuser \ + && mkdir /app \ + && chown -R appuser:appuser /app + +USER appuser + +COPY --from=build /venv /venv +ENV PATH=/venv/bin:$PATH + +COPY --chown=appuser:appuser . /app + +WORKDIR /app + +CMD ["bash", "-c", "uvicorn main:api --host 0.0.0.0 --port 8080 --proxy-headers --no-server-header --timeout-keep-alive 60 --header x-version:${VERSION}"] diff --git a/Makefile b/Makefile index 75094583..2923ec0e 100644 --- a/Makefile +++ b/Makefile @@ -49,4 +49,24 @@ start: --no-server-header \ --port 8080 \ --proxy-headers \ + --timeout-keep-alive 60 \ --reload + + +build: + $(docker) build \ + --build-arg VERSION=$(version_full) \ + --tag $(container_name):$(version_small) \ + --tag $(container_name):latest \ + . + +run: + $(docker) run \ + --env EVENTS_DOMAIN=$(tunnel_url) \ + --env VERSION=$(version_full) \ + --mount type=bind,source="$(CURDIR)/.env",target="/app/.env" \ + --mount type=bind,source="$(CURDIR)/config.yaml",target="/app/config.yaml" \ + --name claim-ai-phone-bot \ + --publish 8080:8080 \ + --rm \ + $(container_name):$(version_small) diff --git a/README.md b/README.md index ab7e62a1..cc277ce7 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ graph user -- Call --> communication_service_call ``` -## Installation +## Local installation ### Prerequisites @@ -193,3 +193,10 @@ make tunnel # Start the local API server make start ``` + +## Remote deployment + +Container is available on GitHub Actions, at: + +- Latest version from a branch: `ghcr.io/clemlesne/claim-ai-phone-bot:main` +- Specific tag: `ghcr.io/clemlesne/claim-ai-phone-bot:0.1.0` (recommended) diff --git a/resources/acknowledge.mp3 b/resources/acknowledge.mp3 index dd166d51..2babd96c 100644 Binary files a/resources/acknowledge.mp3 and b/resources/acknowledge.mp3 differ diff --git a/resources/ready.mp3 b/resources/ready.mp3 index 72fcdc5b..c52cb62f 100644 Binary files a/resources/ready.mp3 and b/resources/ready.mp3 differ