diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml new file mode 100644 index 0000000000..27d15f879d --- /dev/null +++ b/.github/workflows/manual.yml @@ -0,0 +1,46 @@ +# Workflow to ensure whenever a Github PR is submitted, +# a JIRA ticket gets created automatically. +name: Manual Workflow + +# Controls when the action will run. +on: + # Triggers the workflow on pull request events but only for the master branch + pull_request_target: + types: [assigned, opened, reopened] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + test-transition-issue: + name: Convert Github Issue to Jira Issue + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Login + uses: atlassian/gajira-login@master + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + - name: Create NEW JIRA ticket + id: create + uses: atlassian/gajira-create@master + with: + project: CONUPDATE + issuetype: Task + summary: | + Github PR - nd0044 - Full Stack Nanodegree C4 | Repo: ${{ github.repository }} | PR# ${{github.event.number}} + description: | + Repo link: https://github.com/${{ github.repository }} + PR no. ${{ github.event.pull_request.number }} + PR title: ${{ github.event.pull_request.title }} + PR description: ${{ github.event.pull_request.description }} + In addition, please resolve other issues, if any. + fields: '{"components": [{"name":"nd0044 - Full Stack Nanodegree"}], "customfield_16449":"https://classroom.udacity.com/nanodegrees/nd0044/dashboard/overview", "customfield_16450":"Resolve the PR", "labels": ["github"], "priority":{"id": "4"}}' + + - name: Log created issue + run: echo "Issue ${{ steps.create.outputs.issue }} was created" diff --git a/.gitignore b/.gitignore index 811d05a927..9500e81546 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ .DS_Store -.env +.env* .flaskenv *.pyc *.pyo @@ -16,6 +16,7 @@ _mailinglist .idea/ docs/_build/ __pycache__ +.env_file # Coverage reports htmlcov/ @@ -26,3 +27,5 @@ htmlcov/ # Direnv .envrc .direnv + +.github/** diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..2a6bcb2832 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @udacity/active-public-content \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..6972c73b1b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# Use the `python:3.9` as a source image from the Amazon ECR Public Gallery +# We are not using `python:3.7.2-slim` from Dockerhub because it has put a pull rate limit. +FROM public.ecr.aws/sam/build-python3.9:latest + +# Set up an app directory for your code +COPY . /app +WORKDIR /app + +# Install `pip` and needed Python packages from `requirements.txt` +RUN pip install --upgrade pip +RUN pip install -r requirements.txt + +# Define an entrypoint which will run the main app using the Gunicorn WSGI server. +ENTRYPOINT ["gunicorn", "-b", ":8080", "main:APP"] diff --git a/README.md b/README.md index e1eb11edb4..34efd6ab75 100755 --- a/README.md +++ b/README.md @@ -1,244 +1,91 @@ -# Deploying Flask API +# Deploying a Flask API -## Initial setup -1. Fork this project by pressing the fork buton -2. Locally clone your Forked version. You can now begin modifying it. - -## Containarizing and Running Locally -The supplied flask app is a very simple api with three endpoints. -GET '/': This is a simple health check, which returns the response 'Healthy'. -POST '/auth': This takes a email and password as json arguments and returns a jwt token base on a custom secret. -GET '/contents': This requires a valid jwt token, and returns the un-encrpyted contents of that token. - -### Run the Api using Flask Server -1. Install python dependencies. These dependencies are kept in a requirements.txt file. To install them, use pip: - - ```bash -pip install -r requirements.txt - ``` - -1. Setting up environment - - The following environment variable is required: - - **JWT_SECRET** - The secret used to make the JWT token, for the purpose of this course it can be any string. - - The following environment variable is optional: - - **LOG_LEVEL** - The level of logging. Will default to 'INFO', but when debugging an app locally, you may want to set it to 'DEBUG' +This is the project starter repo for the course Server Deployment, Containerization, and Testing. - ```bash -export JWT_SECRET=myjwtsecret -export LOG_LEVEL=DEBUG -``` +In this project you will containerize and deploy a Flask API to a Kubernetes cluster using Docker, AWS EKS, CodePipeline, and CodeBuild. -3. Run the app using the Flask server, from the flask-app directory, run: - ```bash -python app/main.py -``` +The Flask app that will be used for this project consists of a simple API with three endpoints: - To try the api endpoints, open a new shell and run, replacing '\' and '\' with and any values: +- `GET '/'`: This is a simple health check, which returns the response 'Healthy'. +- `POST '/auth'`: This takes a email and password as json arguments and returns a JWT based on a custom secret. +- `GET '/contents'`: This requires a valid JWT, and returns the un-encrpyted contents of that token. - ```bash -export TOKEN=`curl -d '{"email":"","password":""}' -H "Content-Type: application/json" -X POST localhost:80/auth | jq -r '.token'` -``` +The app relies on a secret set as the environment variable `JWT_SECRET` to produce a JWT. The built-in Flask server is adequate for local development, but not production, so you will be using the production-ready [Gunicorn](https://gunicorn.org/) server when deploying the app. - This calls the endpoint 'localhost:80/auth' with the '{"email":"","password":""}' as the message body. The return value is a jwt token based on the secret you supplied. We are assigning that secret to the environment variable 'TOKEN'. To see the jwt token, run: +## Prerequisites - ```bash -echo $TOKEN -``` - To call the 'contents' endpoint, which decrpyts the token and returns it content, run: +- Docker Desktop - Installation instructions for all OSes can be found here. +- Git: Download and install Git for your system. +- Code editor: You can download and install VS code here. +- AWS Account +- Python version between 3.7 and 3.9. Check the current version using: - ```bash -curl --request GET 'http://127.0.0.1:80/contents' -H "Authorization: Bearer ${TOKEN}" | jq . +```bash +# Mac/Linux/Windows +python --version ``` - You should see the email that you passed in as one of the values. - -### Dockerize and Run Locally -1. Install Docker: [installation instructions](https://docs.docker.com/install/) - -2. Create a Docker file. A Docker file decribes how to build a Docker image. Create a file named 'Dockerfile' in the app repo. The contents of the file describe the steps in creating a Docker image. Your Dockerfile should: - - use the 'python:strech' image as a source image - - Setup an app directory for your code - - Install needed python requirements - - Define an entrypoint which will run the main app using the gunicorn WSGI server - - gunicorn should be run with the arguments: - - ``` -gunicorn -b :8080 main:APP -``` - - -3. Create a file named 'env_file' and use it to set the environment variables which will be run locally in your container. Here we do not need the export command, just an equals sign: - - - \=\ - -4. Build a Local Docker Image - To build a Docker image run: -``` -docker build -t jwt-api-test . -``` - -5. Run the image locally, using the 'gunicorn' server: - ``` -docker run --env-file=env_file -p 80:8080 jwt-api-test -``` - - To use the endpoints use the same curl commands as before: - - ```bash -export TOKEN=`curl -d '{"email":"","password":""}' -H "Content-Type: application/json" -X POST localhost:80/auth | jq -r '.token'` -``` - ```bash -curl --request GET 'http://127.0.0.1:80/contents' -H "Authorization: Bearer ${TOKEN}" | jq . - ``` - -## Deployment to Kubernetes using CodePipeline and CodeBuild - -### Create a Kubernetes (EKS) Cluster - -1. Install aws cli - - ```bash -pip install awscli --upgrade --user -``` +You can download a specific release version from here. - Note: If you are using a Python virtual environment, the command will be: +- Python package manager - PIP 19.x or higher. PIP is already installed in Python 3 >=3.4 downloaded from python.org . However, you can upgrade to a specific version, say 20.2.3, using the command: - ```bash -pip install awscli --upgrade +```bash +# Mac/Linux/Windows Check the current version +pip --version +# Mac/Linux +pip install --upgrade pip==20.2.3 +# Windows +python -m pip install --upgrade pip==20.2.3 ``` -2. -[Generate a aws access key id and secret key](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys) +- Terminal + - Mac/Linux users can use the default terminal. + - Windows users can use either the GitBash terminal or WSL. +- Command line utilities: + - AWS CLI installed and configured using the `aws configure` command. Another important configuration is the region. Do not use the us-east-1 because the cluster creation may fails mostly in us-east-1. Let's change the default region to: + ```bash + aws configure set region us-east-2 + ``` + Ensure to create all your resources in a single region. + - EKSCTL installed in your system. Follow the instructions [available here](https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html#installing-eksctl) or here to download and install `eksctl` utility. + - The KUBECTL installed in your system. Installation instructions for kubectl can be found here. -3. Setup your environment to use these keys: - If you not already have a aws 'credentials' file setup, run: - - ```bash -aws configure -``` -And use the credentials you generated in step 2. Your aws commandline tools will now use these credentials. - -4. Install the 'eksctl' tool. - - The 'eksctl' tool allow interaction wth a EKS cluster from the command line. To install, follow the [directions for your platform](https://docs.aws.amazon.com/eks/latest/userguide/eksctl.html) - -5. Create a EKS cluster - - ```bash -eksctl create cluster --name simple-jwt-api --version 1.12 --nodegroup-name standard-workers --nodes 3 --nodes-min 1 --nodes-max 4 --node-ami auto -``` - - This will take some time to do. Progress can be checked by visiting the aws console and selecting EKS from the services. - -6. Check the cluster is ready: - - ```bash -kubectl get nodes -``` - - If the nodes are up and healthy, the cluster should be ready. - -### Create Pipeline -You will now create a pipeline which watches your Github. When changes are checked in, it will build a new image and deploy it to your cluster. - - -1. Create an IAM role that CodeBuild can use to interact with EKS: - - ```bash -ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) -TRUST="{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Effect\": \"Allow\", \"Principal\": { \"AWS\": \"arn:aws:iam::${ACCOUNT_ID}:root\" }, \"Action\": \"sts:AssumeRole\" } ] }" -echo '{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "eks:Describe*", "ssm:GetParameters" ], "Resource": "*" } ] }' > /tmp/iam-role-policy -aws iam create-role --role-name UdacityFlaskDeployCBKubectlRole --assume-role-policy-document "$TRUST" --output text --query 'Role.Arn' -aws iam put-role-policy --role-name UdacityFlaskDeployCBKubectlRole --policy-name eks-describe --policy-document file:///tmp/iam-role-policy -``` - - You have now created a role named 'UdacityFlaskDeployCBKubectlRole' - -1. Grant the role access to the cluster. -The 'aws-auth ConfigMap' is used to grant role based access control to your cluster. - - ``` -ROLE=" - rolearn: arn:aws:iam::$ACCOUNT_ID:role/UdacityFlaskDeployCBKubectlRole\n username: build\n groups:\n - system:masters" -kubectl get -n kube-system configmap/aws-auth -o yaml | awk "/mapRoles: \|/{print;print \"$ROLE\";next}1" > /tmp/aws-auth-patch.yml -kubectl patch configmap/aws-auth -n kube-system --patch "$(cat /tmp/aws-auth-patch.yml)" -``` - -1. Generate a GitHub access token. - A Github acces token will allow CodePipeline to monitor when a repo is changed. A token can be generated [here](https://github.com/settings/tokens/=). -This token should be saved somewhere that is secure. - -1. The file *buildspec.yml* instructs CodeBuild. We need a way to pass your jwt secret to the app in kubernetes securly. You will be using AWS parameter-store to do this. First add the following to your buildspec.yml file: - - ```yaml -env: - parameter-store: - JWT_SECRET: JWT_SECRET -``` - - This lets CodeBuild know to set an evironment variable based on a value in the parameter-store. - -1. Put secret into AWS Parameter Store - - ``` -aws ssm put-parameter --name JWT_SECRET --value "YourJWTSecret" --type SecureString -``` - -1. Modify CloudFormation template. - - There is file named *ci-cd-codepipeline.cfn.yml*, this the the template file you will use to create your CodePipeline pipeline. Open this file and go to the 'Parameters' section. These are parameters that will accept values when you create a stack. Fill in the 'Default' value for the following: - - **EksClusterName** : use the name of the EKS cluster you created above - - **GitSourceRepo** : use the name of your project's github repo. - - **GitHubUser** : use your github user name - - **KubectlRoleName** : use the name of the role you created for kubectl above - - Save this file. - -1. Create a stack for CodePipeline - - Go the the [CloudFormation service](https://us-east-2.console.aws.amazon.com/cloudformation/) in the aws console. - - Press the 'Create Stack' button. - - Choose the 'Upload template to S3' option and upload the template file 'ci-cd-codepipeline.cfn.yml' - - Press 'Next'. Give the stack a name, fill in your GitHub login and the Github access token generated in step 9. - - Confirm the cluster name matches your cluster, the 'kubectl IAM role' matches the role you created above, and the repository matches the name of your forked repo. - - Create the stack. - - You can check it's status in the [CloudFormation console](https://us-east-2.console.aws.amazon.com/cloudformation/). - -1. Check the pipeline works. Once the stack is successfully created, commit a change to the master branch of your github repo. Then, in the aws console go to the [CodePipeline UI](https://us-east-2.console.aws.amazon.com/codesuite/codepipeline). You should see that the build is running. - -16. To test your api endpoints, get the external ip for your service: +## Initial setup +1. Fork the Server and Deployment Containerization Github repo to your Github account. +1. Locally clone your forked version to begin working on the project. - ``` -kubectl get services simple-jwt-api -o wide +```bash +git clone https://github.com/SudKul/cd0157-Server-Deployment-and-Containerization.git +cd cd0157-Server-Deployment-and-Containerization/ ``` - Now use the external ip url to test the app: +1. These are the files relevant for the current project: - ``` -export TOKEN=`curl -d '{"email":"","password":""}' -H "Content-Type: application/json" -X POST :80/auth | jq -r '.token'` -curl --request GET ':80/contents' -H "Authorization: Bearer ${TOKEN}" | jq +```bash +. +├── Dockerfile +├── README.md +├── aws-auth-patch.yml #ToDo +├── buildspec.yml #ToDo +├── ci-cd-codepipeline.cfn.yml #ToDo +├── iam-role-policy.json #ToDo +├── main.py +├── requirements.txt +├── simple_jwt_api.yml +├── test_main.py #ToDo +└── trust.json #ToDo ``` -17. Paste the external id from above below this line for the reviewer to use: - - **EXTERNAL IP**: - -18. Add running tests as part of the build. +## Project Steps - To require the unit tests to pass before our build will deploy new code to your cluster, you will add the tests to the build stage. Remember you installed the requirements and ran the unit tests locally at the beginning of this project. You will add the same commands to the *buildspec.yml*: - - Open *buildspec.yml* - - In the prebuild section, add a line to install the requirements and a line to run the tests. You may need to refer to 'pip' as 'pip3' and 'python' as 'python3' - - save the file +Completing the project involves several steps: -19. You can check the tests prevent a bad deployment by breaking the tests on purpose: - - Open the *test_main.py* file - - Add `assert False` to any of the tests - - Commit your code and push it to Github - - Check that the build fails in [CodePipeline](https://us-east-2.console.aws.amazon.com/codesuite/codepipeline) +1. Write a Dockerfile for a simple Flask API +2. Build and test the container locally +3. Create an EKS cluster +4. Store a secret using AWS Parameter Store +5. Create a CodePipeline pipeline triggered by GitHub checkins +6. Create a CodeBuild stage which will build, test, and deploy your code +For more detail about each of these steps, see the project lesson. diff --git a/aws-auth-patch.yml b/aws-auth-patch.yml new file mode 100644 index 0000000000..de96fee422 --- /dev/null +++ b/aws-auth-patch.yml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: aws-auth + namespace: kube-system +data: + mapRoles: | + - groups: + - system:bootstrappers + - system:nodes + rolearn: arn:aws:iam::672636165283:role/eksctl-simple-jwt-api3-nodegroup-n-NodeInstanceRole-iZj7nkJ0suwx + username: system:node:{{EC2PrivateDNSName}} + - groups: + - system:masters + rolearn: arn:aws:iam::672636165283:role/UdacityFlaskDeployCBKubectlRole + username: build diff --git a/aws-auth.patch.json b/aws-auth.patch.json new file mode 100644 index 0000000000..fe372e4ac1 --- /dev/null +++ b/aws-auth.patch.json @@ -0,0 +1,11 @@ +{ + "apiVersion": "v1", + "data": { + "mapRoles": " - groups:\n - system:bootstrappers\n - system:nodes\n rolearn: arn:aws:iam::672636165283:role/eksctl-simple-jwt-api3-nodegroup-n-NodeInstanceRole-iZj7nkJ0suwx\n username: system:node:{{EC2PrivateDNSName}}\n - groups:\n - system:masters\n rolearn: arn:aws:iam::672636165283:role/UdacityFlaskDeployCBKubectlRole\n username: build" + }, + "kind": "ConfigMap", + "metadata": { + "name": "aws-auth", + "namespace": "kube-system" + } +} diff --git a/aws-auth.yaml b/aws-auth.yaml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/buildspec.yml b/buildspec.yml index 7f8a116e27..db3de3995d 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -4,18 +4,42 @@ version: 0.2 phases: install: + runtime-versions: + python: 3.7 commands: + - echo 'about to call dockerd' + - nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2& + - timeout 15 sh -c "until docker info; do echo .; sleep 1; done" - curl -sS -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-07-26/bin/linux/amd64/aws-iam-authenticator - - curl -sS -o kubectl https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-07-26/bin/linux/amd64/kubectl - - chmod +x ./kubectl ./aws-iam-authenticator - - export PATH=$PWD/:$PATH - - apt-get update && apt-get -y install jq python3-pip python3-dev && pip3 install --upgrade awscli + # Download the latest stable release kubectl + # - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" + # You must use a kubectl version that is within one minor version difference of your Amazon EKS cluster control plane. + # For example, a 1.21 kubectl client works with Kubernetes 1.20, 1.21 and 1.22 clusters. + # Ref: https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html OR https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/ + # To download a specific version v1.27.9 on Linux, use: + - curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.27.9/2024-01-04/bin/linux/amd64/kubectl + # Download the kubectl checksum file + - curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.27.9/2024-01-04/bin/linux/amd64/kubectl.sha256 + # Validate the kubectl binary against the checksum file + - sha256sum -c kubectl.sha256 + # Install kubectl + - chmod +x ./kubectl ./aws-iam-authenticator + # - mkdir -p $HOME/bin && cp ./kubectl $HOME/bin/kubectl && export PATH=$PATH:$HOME/bin + - export PATH=$PWD/:$PATH + - python --version + - echo 'export PATH=$PWD/:$PATH' >> $HOME/.bashrc + - echo `kubectl version --short --client` + - python -m pip install --upgrade --force pip + - wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add + - apt-get update && apt-get -y install jq && pip install --upgrade awscli pytest pre_build: - commands: - - TAG="$REPOSITORY_NAME.$REPOSITORY_BRANCH.$ENVIRONMENT_NAME.$(date +%Y-%m-%d.%H.%M.%S).$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" - - sed -i 's@CONTAINER_IMAGE@'"$REPOSITORY_URI:$TAG"'@' simple_jwt_api.yml - - $(aws ecr get-login --no-include-email) - - export KUBECONFIG=$HOME/.kube/config + commands: + - TAG="$REPOSITORY_NAME.$REPOSITORY_BRANCH.$ENVIRONMENT_NAME.$(date +%Y-%m-%d.%H.%M.%S).$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | head -c 8)" + - sed -i 's@CONTAINER_IMAGE@'"$REPOSITORY_URI:$TAG"'@' simple_jwt_api.yml + - $(aws ecr get-login --no-include-email) + - export KUBECONFIG=$HOME/.kube/config + - echo `ls -l` + - pip install -r requirements.txt build: commands: - docker build --tag $REPOSITORY_URI:$TAG . @@ -23,15 +47,13 @@ phases: post_build: commands: - docker push $REPOSITORY_URI:$TAG - - CREDENTIALS=$(aws sts assume-role --role-arn $EKS_KUBECTL_ROLE_ARN --role-session-name codebuild-kubectl --duration-seconds 900) - - export AWS_ACCESS_KEY_ID="$(echo ${CREDENTIALS} | jq -r '.Credentials.AccessKeyId')" - - export AWS_SECRET_ACCESS_KEY="$(echo ${CREDENTIALS} | jq -r '.Credentials.SecretAccessKey')" - - export AWS_SESSION_TOKEN="$(echo ${CREDENTIALS} | jq -r '.Credentials.SessionToken')" - - export AWS_EXPIRATION=$(echo ${CREDENTIALS} | jq -r '.Credentials.Expiration') - - aws eks update-kubeconfig --name $EKS_CLUSTER_NAME - - kubectl apply -f simple_jwt_api.yml + - echo $EKS_CLUSTER_NAME + - echo $EKS_KUBECTL_ROLE_ARN + - aws eks update-kubeconfig --name $EKS_CLUSTER_NAME --role-arn $EKS_KUBECTL_ROLE_ARN + - kubectl apply -f simple_jwt_api.yml - printf '[{"name":"simple_jwt_api","imageUri":"%s"}]' $REPOSITORY_URI:$TAG > build.json - - pwd - - ls artifacts: files: build.json +env: + parameter-store: + JWT_SECRET: JWT_SECRET diff --git a/ci-cd-codepipeline.cfn.yml b/ci-cd-codepipeline.cfn.yml index 4787331893..f4cfeea5eb 100755 --- a/ci-cd-codepipeline.cfn.yml +++ b/ci-cd-codepipeline.cfn.yml @@ -3,13 +3,11 @@ AWSTemplateFormatVersion: 2010-09-09 Description: EKSWSV1 - Parameters: - EksClusterName: Type: String Description: The name of the EKS cluster created - Default: + Default: simple-jwt-api3 MinLength: 1 MaxLength: 100 ConstraintDescription: You must enter the EKS cluster name @@ -17,7 +15,7 @@ Parameters: GitSourceRepo: Type: String Description: GitHub source repository - must contain a Dockerfile and buildspec.yml in the base - Default: + Default: cd0157-Server-Deployment-and-Containerization MinLength: 1 MaxLength: 100 ConstraintDescription: You must enter a GitHub repository name @@ -40,7 +38,7 @@ Parameters: GitHubUser: Type: String - Default: + Default: ChinhQuoc Description: GitHub username or organization MinLength: 3 MaxLength: 100 @@ -48,21 +46,20 @@ Parameters: CodeBuildDockerImage: Type: String - Default: aws/codebuild/docker:17.09.0 - Description: Default AWS CodeBuild Docker optimized image + Default: aws/codebuild/standard:4.0 + Description: AWS CodeBuild Docker optimized image MinLength: 3 MaxLength: 100 ConstraintDescription: You must enter a CodeBuild Docker image KubectlRoleName: Type: String - Default: + Default: UdacityFlaskDeployCBKubectlRole Description: IAM role used by kubectl to interact with EKS cluster MinLength: 3 MaxLength: 100 ConstraintDescription: You must enter a kubectl IAM role - Metadata: AWS::CloudFormation::Interface: ParameterGroups: @@ -101,9 +98,7 @@ Metadata: EksClusterName: default: EKS cluster name - Resources: - EcrDockerRepository: Type: AWS::ECR::Repository DeletionPolicy: Retain @@ -119,7 +114,7 @@ Resources: ZipFile: | import json import boto3 - from botocore.vendored import requests + import urllib3 def handler(event, context): response = { 'Status': 'SUCCESS', @@ -131,6 +126,7 @@ Resources: 'Data': {"Message": "Resource creation successful!"}, } + http = urllib3.PoolManager() client = boto3.client('iam') try: if event['RequestType'] == 'Create': @@ -155,13 +151,16 @@ Resources: response['Data'] = {"Message": "Resource creation failed"} response_body = json.dumps(response) - headers = {'content-type': '', "content-length": len(response_body) } - put_response = requests.put(event['ResponseURL'], headers=headers, data=response_body) + headers = {'Content-Type': 'application/json', "content-length": str(len(response_body)) } + put_response = http.request('PUT', + event['ResponseURL'], + body = response_body, + headers=headers) return response Handler: index.handler Role: !GetAtt CustomResourceLambdaExecutionRole.Arn - Runtime: python2.7 + Runtime: python3.9 Timeout: 300 CustomResourceLambdaExecutionRole: @@ -256,25 +255,25 @@ Resources: Effect: Allow Action: - sts:AssumeRole - - Resource: '*' + - Resource: "*" Effect: Allow Action: - eks:Describe* - - Resource: '*' + - Resource: "*" Effect: Allow Action: - ssm:GetParameters - - Resource: '*' + - Resource: "*" Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - - Resource: '*' + - Resource: "*" Effect: Allow Action: - ecr:GetAuthorizationToken - - Resource: '*' + - Resource: "*" Effect: Allow Action: - ec2:CreateNetworkInterface @@ -324,6 +323,7 @@ Resources: Value: !Ref EksClusterName - Name: EKS_KUBECTL_ROLE_ARN Value: !Sub arn:aws:iam::${AWS::AccountId}:role/${KubectlRoleName} + PrivilegedMode: true Name: !Ref AWS::StackName ServiceRole: !GetAtt CodeBuildServiceRole.Arn DependsOn: KubectlAssumeRoleCustomResource diff --git a/config b/config new file mode 100644 index 0000000000..31f955e001 --- /dev/null +++ b/config @@ -0,0 +1,11 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority: fake-ca-file + server: https://7C0C739CE282E44638066B74C876E36D.gr7.us-east-2.eks.amazonaws.com + name: simple-jwt-api +contexts: null +current-context: "" +kind: Config +preferences: {} +users: null diff --git a/examples/Cloudformation/my_parameters.json b/examples/Cloudformation/my_parameters.json new file mode 100644 index 0000000000..97660b431c --- /dev/null +++ b/examples/Cloudformation/my_parameters.json @@ -0,0 +1,14 @@ +[ + { + "ParameterKey": "myVPC", + "ParameterValue": "vpc-098defbc811aad87c" + }, + { + "ParameterKey": "PublicSubnet", + "ParameterValue": "subnet-031d7c02210432b1c" + }, + { + "ParameterKey": "AMItoUse", + "ParameterValue": "ami-0fa49cc9dc8d62c84" + } +] \ No newline at end of file diff --git a/examples/Cloudformation/template_EC2.yml b/examples/Cloudformation/template_EC2.yml new file mode 100644 index 0000000000..cc104b9929 --- /dev/null +++ b/examples/Cloudformation/template_EC2.yml @@ -0,0 +1,46 @@ +Parameters: + myVPC: + Description: VPC used to deploy our resources below + Type: AWS::EC2::VPC::Id + PublicSubnet: + Description: Subnet to be used for our Web Server + Type: AWS::EC2::Subnet::Id + AMItoUse: + Description: AMI to use for our base image + Type: String +Resources: + myWebAccessSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Allow http to our test host + VpcId: + Ref: myVPC + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + SecurityGroupEgress: + - IpProtocol: -1 + FromPort: -1 + ToPort: -1 + CidrIp: 0.0.0.0/0 + myWebServerInstance: + Type: AWS::EC2::Instance + Properties: + InstanceType: t3.micro + ImageId: !Ref AMItoUse + NetworkInterfaces: + - AssociatePublicIpAddress: "true" + DeviceIndex: "0" + GroupSet: + - Ref: "myWebAccessSecurityGroup" + SubnetId: + Ref: "PublicSubnet" + UserData: + Fn::Base64: !Sub | + #!/bin/bash + sudo yum update -y + sudo yum install -y httpd + sudo systemctl start httpd + sudo systemctl enable httpd \ No newline at end of file diff --git a/examples/Cloudformation/template_VPN.yml b/examples/Cloudformation/template_VPN.yml new file mode 100644 index 0000000000..14324ad508 --- /dev/null +++ b/examples/Cloudformation/template_VPN.yml @@ -0,0 +1,11 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: Udacity - This template deploys a Virtual Private Network +Resources: + UdacityVPC: + Type: 'AWS::EC2::VPC' + Properties: + CidrBlock: 10.0.0.0/16 + EnableDnsHostnames: 'true' + Tags: + - Key: name + Value: myfirsttestvpc \ No newline at end of file diff --git a/examples/Create_Pipeline/buildspec.yml b/examples/Create_Pipeline/buildspec.yml new file mode 100644 index 0000000000..66a2c7a365 --- /dev/null +++ b/examples/Create_Pipeline/buildspec.yml @@ -0,0 +1,9 @@ +--- +version: 0.2 + + +phases: + build: + commands: + - echo Hello from Code Build! + diff --git a/examples/Create_Pipeline/buildspec.yml.zip b/examples/Create_Pipeline/buildspec.yml.zip new file mode 100644 index 0000000000..7be46347d7 Binary files /dev/null and b/examples/Create_Pipeline/buildspec.yml.zip differ diff --git a/examples/Create_Pipeline/simple-pipeline-build.yml b/examples/Create_Pipeline/simple-pipeline-build.yml new file mode 100644 index 0000000000..fd44f15bc6 --- /dev/null +++ b/examples/Create_Pipeline/simple-pipeline-build.yml @@ -0,0 +1,228 @@ +Parameters: + SourceObjectKey: + Description: 'S3 source artifact' + Type: String + Default: buildspec.yml.zip + CodeBuildDockerImage: + Type: String + Default: aws/codebuild/standard:4.0 + Description: AWS CodeBuild Docker optimized image + MinLength: 3 + MaxLength: 100 + ConstraintDescription: You must enter a CodeBuild Docker image + +Resources: + SourceBucket: + Type: AWS::S3::Bucket + Properties: + VersioningConfiguration: + Status: Enabled + CodePipelineArtifactStoreBucket: + Type: AWS::S3::Bucket + CodePipelineArtifactStoreBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref CodePipelineArtifactStoreBucket + PolicyDocument: + Version: 2012-10-17 + Statement: + - + Sid: DenyUnEncryptedObjectUploads + Effect: Deny + Principal: '*' + Action: s3:PutObject + Resource: !Join [ '', [ !GetAtt CodePipelineArtifactStoreBucket.Arn, '/*' ] ] + Condition: + StringNotEquals: + s3:x-amz-server-side-encryption: aws:kms + - + Sid: DenyInsecureConnections + Effect: Deny + Principal: '*' + Action: s3:* + Resource: !Join [ '', [ !GetAtt CodePipelineArtifactStoreBucket.Arn, '/*' ] ] + Condition: + Bool: + aws:SecureTransport: false + CodePipelineServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Principal: + Service: + - codepipeline.amazonaws.com + Action: sts:AssumeRole + Path: / + Policies: + - + PolicyName: AWS-CodePipeline-Service-3 + PolicyDocument: + Version: 2012-10-17 + Statement: + - + Effect: Allow + Action: + - codecommit:CancelUploadArchive + - codecommit:GetBranch + - codecommit:GetCommit + - codecommit:GetUploadArchiveStatus + - codecommit:UploadArchive + Resource: '*' + - + Effect: Allow + Action: + - codedeploy:CreateDeployment + - codedeploy:GetApplicationRevision + - codedeploy:GetDeployment + - codedeploy:GetDeploymentConfig + - codedeploy:RegisterApplicationRevision + Resource: '*' + - + Effect: Allow + Action: + - codebuild:BatchGetBuilds + - codebuild:StartBuild + Resource: '*' + - + Effect: Allow + Action: + - devicefarm:ListProjects + - devicefarm:ListDevicePools + - devicefarm:GetRun + - devicefarm:GetUpload + - devicefarm:CreateUpload + - devicefarm:ScheduleRun + Resource: '*' + - + Effect: Allow + Action: + - lambda:InvokeFunction + - lambda:ListFunctions + Resource: '*' + - + Effect: Allow + Action: + - iam:PassRole + Resource: '*' + - + Effect: Allow + Action: + - elasticbeanstalk:* + - ec2:* + - elasticloadbalancing:* + - autoscaling:* + - cloudwatch:* + - s3:* + - sns:* + - cloudformation:* + - rds:* + - sqs:* + - ecs:* + Resource: '*' + AppPipeline: + Type: AWS::CodePipeline::Pipeline + Properties: + Name: s3-events-pipeline + RoleArn: + !GetAtt CodePipelineServiceRole.Arn + Stages: + - + Name: Source + Actions: + - + Name: SourceAction + ActionTypeId: + Category: Source + Owner: AWS + Version: 1 + Provider: S3 + OutputArtifacts: + - Name: SourceOutput + Configuration: + S3Bucket: !Ref SourceBucket + S3ObjectKey: !Ref SourceObjectKey + PollForSourceChanges: false + RunOrder: 1 + - Name: Build + Actions: + - Name: Build + ActionTypeId: + Category: Build + Owner: AWS + Version: 1 + Provider: CodeBuild + Configuration: + ProjectName: !Ref CodeBuildProject + InputArtifacts: + - Name: SourceOutput + OutputArtifacts: + - Name: BuildOutput + RunOrder: 1 + ArtifactStore: + Type: S3 + Location: !Ref CodePipelineArtifactStoreBucket + CodeBuildProject: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: CODEPIPELINE + Source: + Type: CODEPIPELINE + Environment: + ComputeType: BUILD_GENERAL1_SMALL + Type: LINUX_CONTAINER + Image: !Ref CodeBuildDockerImage + PrivilegedMode: true + Name: !Ref AWS::StackName + ServiceRole: !GetAtt CodeBuildServiceRole.Arn + CodeBuildServiceRole: + Type: AWS::IAM::Role + Properties: + Path: / + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: root + PolicyDocument: + Version: 2012-10-17 + Statement: + - Resource: '*' + Effect: Allow + Action: + - ssm:GetParameters + - Resource: '*' + Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - Resource: '*' + Effect: Allow + Action: + - ecr:GetAuthorizationToken + - Resource: '*' + Effect: Allow + Action: + - ec2:CreateNetworkInterface + - ec2:DescribeDhcpOptions + - ec2:DescribeNetworkInterfaces + - ec2:DeleteNetworkInterface + - ec2:DescribeSubnets + - ec2:DescribeSecurityGroups + - ec2:DescribeVpcs + - ec2:CreateNetworkInterfacePermission + - Resource: !Sub arn:aws:s3:::${CodePipelineArtifactStoreBucket}/* + Effect: Allow + Action: + - s3:GetObject + - s3:PutObject + - s3:GetObjectVersion diff --git a/examples/Deploy_Flask_App/Dockerfile b/examples/Deploy_Flask_App/Dockerfile new file mode 100644 index 0000000000..af45b00394 --- /dev/null +++ b/examples/Deploy_Flask_App/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.7.2-slim + +COPY . /app +WORKDIR /app + +RUN pip install --upgrade pip +RUN pip install flask + + +ENTRYPOINT ["python", "app.py"] + diff --git a/examples/Deploy_Flask_App/app.py b/examples/Deploy_Flask_App/app.py new file mode 100644 index 0000000000..5a42e26453 --- /dev/null +++ b/examples/Deploy_Flask_App/app.py @@ -0,0 +1,12 @@ +from flask import Flask +APP = Flask(__name__) + + +@APP.route('/') +def hello_world(): + return 'Hello, World from Flask!\n' + + + +if __name__ == '__main__': + APP.run(host='0.0.0.0', port=8080, debug=True) diff --git a/examples/Deploy_Flask_App/deployment.yml b/examples/Deploy_Flask_App/deployment.yml new file mode 100644 index 0000000000..202f84ba87 --- /dev/null +++ b/examples/Deploy_Flask_App/deployment.yml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: simple-flask-deployment + labels: + app: simple-flask +spec: + replicas: 3 + selector: + matchLabels: + app: simple-flask + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 2 + maxSurge: 2 + template: + metadata: + labels: + app: simple-flask + spec: + containers: + - name: simple-flask + # image: IMAGE_TAG + image: sudkul/simple-flask + securityContext: + privileged: false + readOnlyRootFilesystem: false + allowPrivilegeEscalation: false + ports: + - containerPort: 8080 diff --git a/fake-ca-file b/fake-ca-file new file mode 100644 index 0000000000..0a08f39f7a --- /dev/null +++ b/fake-ca-file @@ -0,0 +1 @@ +LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJVTgwTjZGUTFVMWN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRFeE1qY3hNREV6TURCYUZ3MHpOREV4TWpVeE1ERTRNREJhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUROeTVWSXF5NkF6aHFrcDlMeVN3ZWNYd1J1MlNRZEljZlVacVZZanVDSEVxa2JhbDFTUDF6eWl6R08KVjRNdlpoTnI0QzR6cGxtRXUrcDFDczQ2NlpqOTlNUEU5dm1MNjE2emh5ckRkMDdsRHdKUXlicm9ZcUcreXVNeAp1N0Z4bW8zY2xBMjhWWHFSYXJ2UzNVUldmYVhFd1F2UzYzc09lem9TcWEzLzhYb1RhUjgzd2k1bmdZcFl5R1VoClNwTS9oTDRIanRDUXY5cU02R201Z3dIN1o2VmZpL0xER3BtSVNzOGkvTlJPcjBaRjFuRHQzRHM5R3JTS3RPbjQKcjJwRjAvUmRtMjlUb3ZwUU5nbEQwSUVtVncrcmwxcnRIYmYyemViQWxsbmw2cm9FR2h5OTliL0tjc2RMOVhZVApwZy9KeENCRkhFcDNLdCtjOGdQandHYjcrNVJIQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUVGJ2dmNObXIyZFZwVEVKdlJGSklHSVFmWXdEQVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQkU1dW1sSDVhYwpzUXAyQ3YvVUc4MzBmOWNpZC8wUEs0RStoOWJ1aUJyVWc0WjYvb2NEelFNbVV5eStOem5IWmgrTUVuVzAzNHd6CnlMU0QwOVdyS3dwaGxFMWVZTlZBeGpXeXo5cm1BRDY0eTdWMksxaHJVSk9Nb3ppc2RVUU0veS81d2hMWUlJWEQKZkxTUUVIaUZMdFI0d1ZkcmN1Y2tzZXA4UVVOcmRGNmVONUtvNUdzQlJZTnhvNWk3Q21GOHRYeCt1TzBxOXBhVgprK2sxRkJlT0plcFF5MHB3bkhKWTFNSnUzRGhib0s1dzJ6OHBRYUwzc1hVdTZsQjV4UjB6bTBZQTRteitHTXJTCjBsSFk4ZWpaT08rTGQ0S1FNNTc0b2hxWlYwV3JLVnM2TFd6WWZaRUVjN0xTZmFlMVYrZ1BJbEQvcGY5dE1Tbm0KSDdKb0lpa0ZuOFlHCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K \ No newline at end of file diff --git a/iam-role-policy.json b/iam-role-policy.json new file mode 100644 index 0000000000..b0c1222d9b --- /dev/null +++ b/iam-role-policy.json @@ -0,0 +1,8 @@ +{ + "Version": "2012-10-17", + "Statement":[{ + "Effect": "Allow", + "Action": ["eks:Describe*", "ssm:GetParameters"], + "Resource":"*" + }] +} diff --git a/main.py b/main.py index 7663afa7b1..f6c8219f37 100755 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python """ A simple app to create a JWT token. """ diff --git a/mypv.yml b/mypv.yml new file mode 100644 index 0000000000..f9c5b6fafa --- /dev/null +++ b/mypv.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: my-pv +spec: + capacity: + storage: 5Gi # Specify the size of the volume + accessModes: + - ReadWriteOnce # Specify the access mode + hostPath: # This is for local development; change it for production + path: /mnt/data # Path on the host machine diff --git a/pvc.yml b/pvc.yml new file mode 100644 index 0000000000..870d465e4a --- /dev/null +++ b/pvc.yml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: my-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi # This should match or be less than the PV capacity diff --git a/requirements.txt b/requirements.txt index 504cee7240..111531cfe5 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,9 @@ -pyjwt -flask -gunicorn -pytest +pyjwt==1.7.1 +flask==1.1.2 +Jinja2<3.0.0 +MarkupSafe<2.0.0 +ruamel.yaml==0.16.5 +itsdangerous==2.0.1 +werkzeug==2.0.3 +gunicorn==20.0.4 +pytest==6.2.2 \ No newline at end of file diff --git a/trust.json b/trust.json new file mode 100644 index 0000000000..380b952b82 --- /dev/null +++ b/trust.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::672636165283:root" + }, + "Action": "sts:AssumeRole" + } + ] +}