diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..ec6d3d2 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,11 @@ +# Global Owners +* @dinkar + +# dockerfiles +dockerfiles/** @dinkar + +# e2e tests +tests/e2e/** @dinkar + +# devworkspace happy path test +tests/devworkspace-happy-path/** \ No newline at end of file diff --git a/.github/workflows/aks.yml b/.github/workflows/aks.yml new file mode 100644 index 0000000..9dd5856 --- /dev/null +++ b/.github/workflows/aks.yml @@ -0,0 +1,61 @@ +name: main +on: workflow_dispatch +env: + ACR_RESOURCE_GROUP: sai-arch + AZURE_CONTAINER_REGISTRY: myacr108 + CLUSTER_NAME: my_aks + CLUSTER_RESOURCE_GROUP: sai-arch + CONTAINER_NAME: my-container + DEPLOYMENT_MANIFEST_PATH: | + manifests/deployment.yaml + manifests/service.yaml +jobs: + buildImage: + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 + name: Azure login + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + - name: Build and push image to ACR + run: az acr build --image ${{ env.CONTAINER_NAME }}:${{ github.sha }} --registry ${{ env.AZURE_CONTAINER_REGISTRY }} -g ${{ env.ACR_RESOURCE_GROUP }} -f Dockerfile ./ + deploy: + permissions: + actions: read + contents: read + id-token: write + runs-on: ubuntu-latest + needs: + - buildImage + steps: + - uses: actions/checkout@v3 + - uses: azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 + name: Azure login + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + - uses: azure/use-kubelogin@v1 + name: Set up kubelogin for non-interactive login + with: + kubelogin-version: v0.0.25 + - uses: azure/aks-set-context@v3 + name: Get K8s context + with: + admin: "false" + cluster-name: ${{ env.CLUSTER_NAME }} + resource-group: ${{ env.CLUSTER_RESOURCE_GROUP }} + use-kubelogin: "true" + - uses: Azure/k8s-deploy@v4 + name: Deploys application + with: + action: deploy + images: ${{ env.AZURE_CONTAINER_REGISTRY }}.azurecr.io/${{ env.CONTAINER_NAME }}:${{ github.sha }} + manifests: ${{ env.DEPLOYMENT_MANIFEST_PATH }} + namespace: namespace-workflow-1693722624174 \ No newline at end of file diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..5a518fe --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,113 @@ +#this pipeline is for python API on Azure Kubernetes Services + + +name: Build CI + +on: workflow_dispatch + +# Below environment variables available to all jobs and steps in this workflow +env: + ARTIFACTORY_URL: https://dkregistry.jfrog.io/artifactory/docker/ + ARTIFACTORY_NAME: dkregistry + ARTIFACTORY_USERNAME: saidinkargedela97@gmail.com + CONTAINER_NAME: myimage + CLUSTER_NAME: my-aks-cluster + CLUSTER_RESOURCE_GROUP: dinkar-rg + NAMESPACE: default + DEPLOYMENT_MANIFEST_PATH: | + manifests/deployment.yaml + manifests/service.yaml + + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@master + + # Connecting to Jfrog Artifactory + - name: Jfrog Artifactory Login + uses: docker/login-action@v1 + with: + registry: ${{ env.ARTIFACTORY_NAME }}.jfrog.io + username: ${{ secrets.ARTIFACTORY_USERNAME }} + password: ${{ secrets.ARTIFACTORY_PASSWORD }} + + # Docker container build + - name: Docker Build + run: | + echo '<-----------------------Docker build started----------------------->' + docker build . -t dkregistry.jfrog.io/docker/mytestimage:${{ github.sha }} + echo '<------------------------Docker build Ended------------------------>' + + # Docker Container Push to ACR + - name: Docker Push + run: | + echo '<-----------------------Docker Publish started----------------------->' + docker push dkregistry.jfrog.io/docker/mytestimage:${{ github.sha }} + echo '<------------------------Docker Publish Ended------------------------>' + + Deploy: + permissions: + actions: read + contents: read + id-token: write + + runs-on: ubuntu-latest + + # Delpoy job won't run until build job is completed + needs: [build] + + steps: + # Checks out the repository this file is in + - uses: actions/checkout@v3 + + - name: Azure Kubernetes set context + uses: Azure/aks-set-context@v1 + with: + # Azure credentials i.e. output of + creds: '${{secrets.AZURE_CREDENTIALS}}' + # Resource Group Name + resource-group: ${{ env.CLUSTER_RESOURCE_GROUP }} + # AKS Cluster Name + cluster-name: ${{ env.CLUSTER_NAME }} + + + # Use kubelogin to configure your kubeconfig for Azure auth + - name: Set up kubelogin for non-interactive login + uses: azure/use-kubelogin@v1 + with: + kubelogin-version: 'v0.0.25' + + # Create K8s secrets to pull images + - name: Create secret in Kubernetes cluster + uses: Azure/k8s-create-secret@v1.1 + with: + # Container registry URL + container-registry-url: ${{env.ARTIFACTORY_URL}} + # Container registry username + container-registry-username: '${{ secrets.ARTIFACTORY_USERNAME }}' + # Container registry password + container-registry-password: '${{ secrets.ARTIFACTORY_PASSWORD }}' + # Type of Kubernetes secret. For example, docker-registry or generic + secret-type: docker-registry + # Name of the secret. You can use this secret name in the Kubernetes YAML configuration file. + secret-name: jfrog-image-pull-secret + + # Deploy to k8s cluster + - name: Deploy to Kubernetes cluster + uses: Azure/k8s-deploy@v4 + with: + # deploy/promote/reject + action: deploy + # Path to the manifest files which will be used for deployment. + manifests: | + ${{ env.DEPLOYMENT_MANIFEST_PATH }} + # Fully qualified resource URL of the image(s) to be used for substitutions on the manifest files Example: contosodemo.azurecr.io/helloworld:test + images: | + dkregistry.jfrog.io/docker/mytestimage:${{ github.sha }} + # Name of a docker-registry secret that has already been set up within the cluster. Each of these secret names are added under imagePullSecrets field for the workloads found in the input manifest files + imagepullsecrets: | + acr-image-pull-secret + diff --git a/.github/workflows/jfrog.yml b/.github/workflows/jfrog.yml new file mode 100644 index 0000000..5091d23 --- /dev/null +++ b/.github/workflows/jfrog.yml @@ -0,0 +1,41 @@ +name: Build and Tag Docker Image + +on: + push: + branches: + - workflow_dispatch + +jobs: + build-and-tag: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup JFrog CLI + uses: jfrog/setup-jfrog-cli@v3 + env: + JF_URL: ${{ secrets.JF_URL }} + JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }} + + - name: Build Tag and push Docker Image + env: + IMAGE_NAME: dkregistry.jfrog.io/docker/jfrog-docker-example-image:${{ github.run_number }} + run: | + jf docker build -t $IMAGE_NAME . + jf docker push $IMAGE_NAME + + - name: Publish Build info With JFrog CLI + env: + # Generated and maintained by GitHub + JFROG_CLI_BUILD_NAME: jfrog-docker-build-example + # JFrog organization secret + JFROG_CLI_BUILD_NUMBER : ${{ github.run_number }} + run: | + # Export the build name and build nuber + # Collect environment variables for the build + jf rt build-collect-env + # Collect VCS details from git and add them to the build + jf rt build-add-git + # Publish build info + jf rt build-publish \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..0063408 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,121 @@ +#this pipeline is for python API on Azure Kubernetes Services + +# Set the following environment variables (or replace the values below): +# - AZURE_CONTAINER_REGISTRY (name of your container registry / ACR) +# - RESOURCE_GROUP (where your cluster is deployed) +# - CLUSTER_NAME (name of your AKS cluster) +# - CONTAINER_NAME (name of the container image you would like to push up to your ACR) + + +name: Python Flask on Kubernetes + +on: + workflow_dispatch: + +# Below environment variables available to all jobs and steps in this workflow +env: + REGISTRY_NAME: myacr108 + CONTAINER_NAME: my-container + RESOURCE_GROUP: sai-arch + CLUSTER_NAME: my_aks + CLUSTER_RESOURCE_GROUP: sai-arch + NAMESPACE: default + DEPLOYMENT_MANIFEST_PATH: | + manifests/deployment.yaml + manifests/service.yaml + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@master + + # Connecting to Azure Container registry (ACR) + - name: ACR Login + uses: azure/docker-login@v1 + with: + login-server: ${{ env.REGISTRY_NAME }}.azurecr.io + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + # Docker container build + - name: Docker Build + run: | + echo '<-----------------------Docker build started----------------------->' + docker build . -t ${{ env.REGISTRY_NAME }}.azurecr.io/${{env.CONTAINER_NAME}}:${{ github.sha }} + echo '<------------------------Docker build Ended------------------------>' + + # Docker Container Push to ACR + - name: Docker Push + run: | + echo '<-----------------------Docker Publish started----------------------->' + docker push ${{ env.REGISTRY_NAME }}.azurecr.io/${{env.CONTAINER_NAME}}:${{ github.sha }} + echo '<------------------------Docker Publish Ended------------------------>' + + Deploy: + permissions: + actions: read + contents: read + id-token: write + + runs-on: ubuntu-latest + + # Delpoy job won't run until build job is completed + needs: [build] + + steps: + # Checks out the repository this file is in + - uses: actions/checkout@v3 + + - name: Azure Kubernetes set context + uses: Azure/aks-set-context@v1 + with: + # Azure credentials i.e. output of + creds: '${{secrets.AZURE_CREDENTIALS}}' + # Resource Group Name + resource-group: ${{ env.RESOURCE_GROUP }} + # AKS Cluster Name + cluster-name: ${{ env.CLUSTER_NAME }} + + + # Use kubelogin to configure your kubeconfig for Azure auth + - name: Set up kubelogin for non-interactive login + uses: azure/use-kubelogin@v1 + with: + kubelogin-version: 'v0.0.25' + + # Create K8s secrets to pull images + - name: Create secret in Kubernetes cluster + uses: Azure/k8s-create-secret@v1.1 + with: + # Container registry URL + container-registry-url: https://${{env.REGISTRY_NAME}}.azurecr.io/${{env.CONTAINER_NAME}} + # Container registry username + container-registry-username: '${{ secrets.REGISTRY_USERNAME }}' + # Container registry password + container-registry-password: '${{ secrets.REGISTRY_PASSWORD }}' + # Type of Kubernetes secret. For example, docker-registry or generic + secret-type: docker-registry + # Name of the secret. You can use this secret name in the Kubernetes YAML configuration file. + secret-name: acr-image-pull-secret + + # Deploy to k8s cluster + - name: Deploy to Kubernetes cluster + uses: Azure/k8s-deploy@v4 + with: + # deploy/promote/reject + action: deploy + # Path to the manifest files which will be used for deployment. + manifests: | + ${{ env.DEPLOYMENT_MANIFEST_PATH }} + # Fully qualified resource URL of the image(s) to be used for substitutions on the manifest files Example: contosodemo.azurecr.io/helloworld:test + images: | + ${{ env.REGISTRY_NAME }}.azurecr.io/${{ env.CONTAINER_NAME }}:${{ github.sha }} + # Name of a docker-registry secret that has already been set up within the cluster. Each of these secret names are added under imagePullSecrets field for the workloads found in the input manifest files + imagepullsecrets: | + acr-image-pull-secret + + \ No newline at end of file diff --git a/.github/workflows/test_scan.yml b/.github/workflows/test_scan.yml new file mode 100644 index 0000000..3f6f418 --- /dev/null +++ b/.github/workflows/test_scan.yml @@ -0,0 +1,74 @@ +name: Pull Request - Build, Test, & Scan + +on: + push + +jobs: + build-test: + name: Build and Test + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + - name: Test with pytest + run: | + export PYTHONPATH=src + pytest + + lint: + name: Lint Codebase + runs-on: ubuntu-latest + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + + - name: Install Python dependencies + run: pip install black flake8 + + - name: Run linters + uses: wearerequired/lint-action@v1 + with: + flake8: true + + build-scan-image: + name: Build and Scan Container + runs-on: ubuntu-latest + needs: [lint, build-test] + steps: + - uses: actions/checkout@v1 + + - name: Build docker image + run: | + docker build -t my-app:${{ github.sha }} . + - name: Scan container image + uses: Azure/container-scan@v0 + with: + image-name: my-app:${{ github.sha }} + severity-threshold: CRITICAL + run-quality-checks: false \ No newline at end of file diff --git a/.github/workflows/python-app.yml b/.github/workflows/testing.yml similarity index 59% rename from .github/workflows/python-app.yml rename to .github/workflows/testing.yml index ac225a9..6cc3470 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/testing.yml @@ -1,10 +1,7 @@ -name: Python application +name: Python application Testing -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] +on: + workflow_dispatch permissions: contents: read @@ -14,25 +11,43 @@ jobs: runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.x] + steps: + #Code checkout and python setup - uses: actions/checkout@v3 - - name: Set up Python 3.10 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: - python-version: "3.10" + python-version: ${{ matrix.python-version }} + + #Installling all python Dependancies - name: Install dependencies run: | python -m pip install --upgrade pip + pip install setuptools pip install flake8 pytest + pip install pytest + pip install pylint if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + #Python code linting with Flake8 - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + #Python code linting with pylint + - name: Analysing the code with pylint + run: | + pylint $(git ls-files '*.py') || true + + #python code test with pytest - name: Test with pytest run: | - pip install pytest export PYTHONPATH=src pytest diff --git a/Azure_servcie_connection.md b/Azure_servcie_connection.md new file mode 100644 index 0000000..cc5f30f --- /dev/null +++ b/Azure_servcie_connection.md @@ -0,0 +1,50 @@ +# Use this below method to create azure service principal + +The quickest way and easiest way to create this Service Principal is to use Azure CLI and issue the command: + +```sh +az ad sp create-for-rbac \ + --name "myAzureServiceGitHubConnection" \ + --role contributor \ + --scopes /subscriptions/{subscription-ID}/resourceGroups/{RG-name} \ + --sdk-auth +``` + +# Execute below powershell commands in Azure to collect Service Principal information + +To store the information inside a GitHub Actions secret, it needs to be stored within a JSON format. We can run this PowerShell subscription to collect all the information you will need for GitHub and in the form that GitHub needs. + + +```sh +$ServicePrincipalName = "MyGithubActionsConnection" +$AzSubscriptionName = "production" + +Connect-AzureAD + +$Subscription = (Get-AzSubscription -SubscriptionName $AzSubscriptionName) +$ServicePrincipal = Get-AzADServicePrincipal -DisplayName $ServicePrincipalName +$AzureADApplication = Get-AzureADApplication -SearchString $ServicePrincipalName + +$OutputObject = [PSCustomObject]@{ + clientId = $ServicePrincipal.AppId + clientSecret = (New-AzureADApplicationPasswordCredential -ObjectId $AzureADApplication.ObjectId).Value + subscriptionId = $Subscription.Id + tenantId = $Subscription.TenantId +} + +$OutputObject | ConvertTo-Json +``` + + +# Expected output of the above commands and copy as the below mentioned json format to AZURE_CREDENTIALS + +Take a copy of the output from the PowerShell query. This will be stored inside a GitHub Secret for use within your workflows. +Within the repository where your workflow is, click on Settings > Secrets > Actions and then click on new repository secret. + +```sh +{ + "clientId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "subscriptionId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "tenantId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx" +}``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9ceed0a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +#This is the base image for this Dockerfile +FROM python:3-alpine3.15 + +LABEL BuildBy="Dinkar Gedela" +LABEL BuilderEmail="dinkarsai03@gmail.com" + +#Set the working directory in the container +WORKDIR /app + +#Copy the all files like requirements file into the container +COPY . /app + +#install the dependencies from requirements.txt +RUN pip install -r requirements.txt + +#Open the port on which Flask app will run +EXPOSE 8080 + +#commands to run the Flask app +ENTRYPOINT [ "python" ] +CMD [ "/src/app.py" ] \ No newline at end of file diff --git a/manifests/deployment.yml b/manifests/deployment.yml new file mode 100644 index 0000000..edd087a --- /dev/null +++ b/manifests/deployment.yml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: python-app-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: python-app + template: + metadata: + labels: + app: python-app + spec: + containers: + - name: python-app + image: myacr.azurecr.io/my-container + imagePullPolicy: Always + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 \ No newline at end of file diff --git a/manifests/ingress.yml b/manifests/ingress.yml new file mode 100644 index 0000000..56bcfe0 --- /dev/null +++ b/manifests/ingress.yml @@ -0,0 +1,18 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: python-app + labels: + name: python-app +spec: + rules: + - host: + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: python-app + port: + number: 80 \ No newline at end of file diff --git a/manifests/service.yml b/manifests/service.yml new file mode 100644 index 0000000..0e0bdcb --- /dev/null +++ b/manifests/service.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: python-app-svc +spec: + selector: + app: python-app + type: NodePort + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + nodePort: 30000 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 491b91e..f531384 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ flask pytest gunicorn +requests diff --git a/src/app.py b/src/app.py index e77ad22..619b248 100644 --- a/src/app.py +++ b/src/app.py @@ -1,12 +1,16 @@ +'''This is a module-level docstring that describes the purpose of the app.py module.''' + + from flask import Flask + app = Flask(__name__) + @app.route("/") def index(): - return "Hello, world!" + return "Hello Arch world!" if __name__ == "__main__": - app.run() - + app.run(host='0.0.0.0') diff --git a/tests/test_app.py b/tests/test_app.py index abfa82a..a7fe235 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,6 +1,9 @@ +'''This is a module-level docstring that describes the purpose of the test_app.py module.''' + + from app import index def test_index(): - assert index() == "Hello, world!" - + '''This is a docstring that describes the test_index function.''' + assert index() == "Hello Arch world!"