Development Environment
Initial Development Environment
- In a terminal on your host system, not within VSCode, create a new directory with the name of your team’s final project repository and
cd
into it:mkdir comp423-final-project
cd comp423-final-project
- Initialize the new empty directory as a
git
repository:git init
- Add a git remote repository named
upstream
bound to the public, open source CSXL web app repository:git remote add upstream https://github.com/unc-csxl/csxl.unc.edu.git
- Pull the
main
branch fromupstream
:git pull upstream main
- Add a git remote repository named
origin
bound to your team’s private final project repository. You will need to find the correct repository URL. Open your team’s final project repository on GitHub in a web browser, click the down triangle on the green Code button, and copy the Clone URL. Use this URL in the placeholder below:git remote add origin TEAM_REPO_URL_PLACEHOLDER
- Push
main
toorigin
. If you are the first member on the team to do this, you will see the repository’s history being pushed up to GitHub. If you are not the first member on the team to do this, you may see an error or “Everything up to date”:git push origin main
- Your team will work off of a staging branch named
stage
, rather than themain
branch. Go ahead and establish astage
branch locally and push it:git fetch --all
git branch stage
git switch stage
git pull origin stage
git push origin stage
GitHub Repository Configuration
Only one member of your team needs to setup the following repository branch settings:
- Settings > Branches
- Change Default Branch to
stage
- Press the swap/switch button (not the pencil!) and select
stage
- If you do not see
stage
, be sure you completed all the steps above
- Press the swap/switch button (not the pencil!) and select
- Add Branch Protection Rule to prevent any pushing to
main
- Branch name pattern:
main
- (Check) Lock branch
- (Check) Do not allow bypassing the above settings
- Save Changes
- Branch name pattern:
- Add Branch Protection Rule to enforce a professional team workflow on
stage
- Branch name pattern:
stage
- (Check) Require a pull request before merging
- (Check) Require approvals: 1 required
- (Check) Dismiss stale pull request approvals when new commits are pushed
- (Check) Require approval of the most recent reviewable push
- (Check) Require conversation resolution before merging
- (Check) Require linear history
- (Check) Do not allow bypassing the above settings
- Save Changes
- Branch name pattern:
Setting up your Development Environment
Open your project locally, without opening it in a Dev Container at first, and follow the instructions to get started found in the project’s docs
directory: docs/get_started.md
Understanding User Authentication and Authorization
See the official documentation on CSXL.unc.edu Authentication and Authorization for feature development.
Sprint 0 Expectations
“Minimum Viable” Data Models, Entities, and Dev Data merged into
stage
branch and added to database viabackend/script/reset_database.py
.Implement at least one story across all layers, running, and demoable on CloudApps.
- Add an entry to NavigationComponent on the front-end so that you can reach your feature from the sidebar.
- Aiming for a “view” story here, as opposed to an update/edit story, is advisable. For example, viewing upcoming workshops, a list of clubs, rooms with availability, etc.
Everything committed and merged from #2 is properly documented, tested, and up to standards.
- Backend service layer needs unit tests that cover methods all methods and functionality implemented.
- Backend service layer classes should not have any HTTP concerns. Similarly, your FastAPI routes should not have any SQLAlchemy or Entity concerns. These two layers are integrated via Pydantic models.
Everyone is expected to make at least one Pull Request. The Pull Request should have at least one additional improvement commit that improves based on a Code Reviewer’s suggestions. This PR should ultimatey be approved and merged into Stage ahead of the deadline. After final approval, it is your responsibility to merge the changes in and write the final commit message for your merge. When merging, you should “Squash and Merge” the commits into a single, well documented commit.
Everyone is expected to serve as a Code Reviewer for at least one Pull Request. As a Code Reviewer, you are expected to find and request at least one improvement to the PR. Once the PR author addresses your concerns with improvement commit(s), you should review and approve the PR author to merge in the changes.
Significant effort and care has gone into managing the project’s board with Cards/Issues. All members are actively participating in the GitHub project. Best practices are being followed, e.g. commit descriptions and change requests, per the assigned readings (Google Dev Engineering Practices).
Staging Server Environment (DevOps - At Least Two Members of Team Should Pair on This)
Choose one team member’s CloudApps namespace to setup as the staging environment where your team’s work will be hosted in the cloud. Other team members will be added to this namespace as collaborators so everyone has access.
In establishing the staging environment on our cloud infrastructure, we will work from the bottom up. We will start with the database server, then add secrets needed to build and run the application, then add the application, and finally add the route to expose the application to the internet.
Cleaning Up from EX03
- Login to OpenShift (remember: off-campus access requires connecting to the VPN)
- Open a Terminal on your Host Machine (not the VSCode Dev Env!)
- Login to the OpenShift Terminal on your Host Machine (not the VSCode DevEnv!)
- Navigate to your project’s directory in the terminal
- Login to OpenShift’s
oc
program you installed for EX03. You will need a new token from OpenShift: Click your name after logging in, select Copy Login Command, Display Token, copy theoc login ...
line, paste into Terminal.
- Go ahead and delete your EX03 deployment now that everything is graded:
- Run:
oc delete all --selector app=comp423-ex03
- Run:
Giving Team Members Access to Your Project
- Add team members to your workspace:
- Navigate to the Administrator sidebar
- Select: User Management > Role Bindings
- For each team member, with their
onyen
:- Create binding
- Name:
admin-ONYEN
(replaceONYEN
with teammate’s ONYEN) - Namespace: Your ONYEN
- Role name:
admin
- Subject:
- User
- Name: your teammate’s ONYEN
Creating the Database Server
- Add a PostgreSQL database to your project:
- Developer View Sidebar
- Add (from the Sidebar)
- Database
- PostgreSQL Provided by Red Hat
- Instantiate Template
- Change the following settings:
- Database Service Name:
db
- PostgreSQL Database Name:
csxl
- Version of PostgreSQL Image:
latest
- Database Service Name:
- Create
Once the database is created, you can go to the Secrets page and view the generated credentials for the database under db
(the name you gave it). If you select “Reveal Values” you can see the name, username, and password for the database. These secrets will be used as environment variables in your application in the next step.
Creating Secrets for your Application
Let’s create a secret for your application to use. This will be used to store the database credentials, and will ultimately be mounted as environment variables in your application.
Make note of the username and password for the database, from above. Additionally, you will need to generate a random string for the JWT_SECRET
environment variable. This will be used to sign the JWT tokens that your application will use to authenticate users. You can generate a random string using the following command in your Dev Container:
openssl rand -hex 32
From your host machine’s terminal, run the following command to create the secret:
oc create secret generic final-project-environment \
--from-literal=POSTGRES_HOST=db \
--from-literal=POSTGRES_PORT=5432 \
--from-literal=POSTGRES_DATABASE=csxl \
--from-literal=POSTGRES_USER=<from-secret-above> \
--from-literal=POSTGRES_PASSWORD=<from-secret-above> \
--from-literal=JWT_SECRET=<generate-random-string>
From the OpenShift web console, you can verify that the secret was created by navigating to the Secrets page and selecting the final-project-environment
secret.
Repository Deploy Key (Secret)
As with in EX03, you will need to add a deploy key to your repository so that OpenShift can pull your code from GitHub. This is the same process as EX03, but you will need to add the deploy key to your final project repository’s settings.
In your Dev Container’s terminal, run the following command to generate a new deploy key:
ssh-keygen -t ed25519 -C "Deploy Key for Final Project" -f ./deploy_key
Do NOT set a passphrase. When prompted for a passphrase, just press enter.
This will generate two files: deploy_key
and deploy_key.pub
. The public key (deploy_key.pub
) will need to be added to your repository’s settings, and the private key (deploy_key
) will need to be added to OpenShift as a secret.
Add the public key to your repository’s settings:
- Navigate to your repository’s settings
- Select Deploy Keys
- Add Deploy Key
- Title:
CloudApps Deploy Key
- Key: Copy the contents of
deploy_key.pub
into the key field - Check the box to allow write access
- Click Add Key
Create the secret in OpenShift:
- Back in your host machine’s terminal, not the Dev Container’s terminal, navigate to your project directory and run the following command to create the secret in OpenShift:
oc create secret generic final-project-deploy-key \
--from-file=ssh-privatekey=./deploy_key \
--type=kubernetes.io/ssh-auth
To verify the secret was correctly created, run the following command:
oc get secret final-project-deploy-key
Finally, you need to link the secret to the “builder” process of OpenShift. This will allow OpenShift to use the secret when it pulls your code from GitHub and builds your project.
oc secrets link builder final-project-deploy-key
This command will succeed silently.
Create the OpenShift Application
Back in your host’s terminal, since it has the oc
command installed, run the following commands to create the application in OpenShift:
oc new-app python:3.11~git@github.com:comp423-23s/<your-final_repo_name>.git#stage \
--source-secret=final-project-deploy-key \
--name=final-project \
--strategy=docker \
--env=MODE=development \
--env=HOST=team-k9-comp423-23s.apps.cloudapps.unc.edu
Notice the #stage
at the end of the repository URL. This is the branch name that OpenShift will pull from. When setting up the final project, you created a branch named stage
and established it as the primary branch for your repository. This notion of a staging branch is a common practice in DevOps, and is a good way to keep your production code separate (live at csxl.unc.edu) from your development code (which you are establishing right now).
While the project is building, add secrets to the environment variables of the deployment and verify their existence with list
:
oc set env deployment/final-project --from=secret/final-project-environment
oc set env deployment/final-project --list
Exposing the Application
Once your application builds, it will be running on a pod that is not exposed to the internet. To establish a public route, first we need to expose it as a service, run the following command:
oc expose deployment final-project \
--port=80 \
--target-port=8080
Next, we can create a route to the service with a specifically chosen hostname. Please replace k9
with your team’s zone (lowercase) and table number:
oc create route edge \
--service=final-project \
--hostname=team-k9-comp423-23s.apps.cloudapps.unc.edu
You can now visit the hostname for your team and access it in the browser. If you see a message from OpenShift that says “Application is not available”, it means that the application is still building. Once your build completes, you should see the application running, but there is still one more important step: resetting the database.
Resetting the Database
The database that you created in the previous step is empty. You will need to reset the database to the state that it was in when you submitted your final project. To do this, you will need to run the reset_db.py
script that is included in your final project repository.
This script needs to be run from within your pod, so in this section you will learn how to connect to your pod and run commands from within it.
First, you will need to find the name of your pod. Run the following command to get a list of pods running in your project:
oc get pods --selector deployment=final-project
You should see a single pod with a name like final-project-648fdff8d5-rr4fs
. The letters and numbers at the end of the name are a unique identifier for the pod. This identifier changes every time a new build of your pod is deployed, environment variables change, the pod gets restarted, and in other instances. Copy the name of your running pod and run the following command to connect to it:
oc rsh final-project-YOUR-POD-IDENTIFIER
The rsh
stands for “remote shell”. You are now connected to your pod running in the cloud via a secure shell (ssh)! The commands you run are not running on your host machine, but on the CloudApps infrastructure. If you ls
you will see you are in your project’s built directory. Not everything is there, importantly not the frontend because it was compiled into the static
directory as part of the build process.
To confirm you are logged into your pod, you can assure yourself with the following command:
hostname
You can now run the reset_database
script to reset the database. Run the following command to do so:
python -m backend.script.reset_database
You should see the SQLAlchemy log messages creating tables, inserting dev data, etc. Your staging database is now reset!
Important: As you deploy new versions, add new entities, add new dev data, etc., this process of resetting the database in staging is one you and your team members will both need to be comfortable doing and remember to do.
Setting up Push-to-Deploy Webhooks
GitHub repositories can be configured with webhooks, which are URLs that get called when events occur in order to notify another service of the event. In our case, we want to set up a webhook so that when we push to the stage
branch, OpenShift’s build configuration for our project will receive a webhook notification and kick off a new build.
To find the URL for the web hook, open up your project in OpenShift and navigate to the Admin sidebar, followed by Builds > BuildConfigs. Select final-project
and look for the webhooks section at the bottom. Click the Copy URL with Secrets button for the GitHub webhook. This copies the URL to your clipboard.
Next, open your project’s settings in GitHub and navigate to Webhooks. Click Add Webhook and paste the URL into the Payload URL field. Be sure to set the content type to application/json
and leave the secret field empty. Click Add Webhook.
From the “Webhooks” page you’re brought back to, click your webhook. Then go to the Recent Deliveries tab. You should see a successful delivery. Congratulations, your project is now set up to automatically build and deploy every time your team merges PRs into the stage
branch! As a reminder, if your data entities change, you will need to reset the database in staging after the build and deploy completes.