Published: Jan 11, 2024 by Isaac Johnson
In Part 3 we built out a containerized app we could run in Docker or Kubernetes, but it was all still private and not really a sharable setup.
In this post, we’ll create a Public GPL’ed Github Repo and populate it with a working version. We’ll apply some tweaks and move to creating a proper Helm Chart. Once created, we’ll (try) and publish with Github Pages and Harbor CR. We’ll finish out the Github actions to push on tags and have a working sharable solution.
We are going to explore the full update path and show off using Github Codespaces to leverage ephemeral cloud compute and containers to build and test updates and associate with Github Issues.
Open Source it
What is the point of being an Open-Source junkie if I don’t do it myself?
I created a blank new repo with GPL v3 license set at https://github.com/idjohnson/pyplanereport
I’ll bring it down
builder@DESKTOP-QADGF36:~/Workspaces$ git clone https://github.com/idjohnson/pyplanereport.git
Cloning into 'pyplanereport'...
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), 14.00 KiB | 494.00 KiB/s, done.
builder@DESKTOP-QADGF36:~/Workspaces$ cd pyplanereport/
builder@DESKTOP-QADGF36:~/Workspaces/pyplanereport$ git checkout -b initialsync
Switched to a new branch 'initialsync'
builder@DESKTOP-QADGF36:~/Workspaces/pyplanereport$ code .
There is a lot in my initial PR. We’ll just cover some of it:
here you can explore the helm chart with the values that need to be set by users
image:
repository: pyplanereport
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "0.0.1"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
planeso:
projectid: "" # e.g. 9ca799e6-52c4-4a9e-8b40-461eef4f57e9
workspacename: "" # e.g. tpk
apikey: "" #e.g. plane_api_xxxxxxxxxx
tabletitle: "Project Issue Report"
ignorestate: "Cancelled"
I also made a first pass at a Github Workflow to build and push to ghcr.io
name: Create and publish a Docker image
on:
push:
branches: ['main']
env:
REGISTRY: ghcr.io
IMAGE_NAME: $
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: $
username: $
password: $
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: $/$
- name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with:
context: src
push: true
tags: $
labels: $
If I read the build output properly, it did end up pushing to ghcr.io/idjohnson/pyplanereport:main
Next, let’s add some steps to push out the Helm chart too
...
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v3
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.6.0
env:
CR_TOKEN: "$"
We can see the new yaml in PR 2
which completed after merging
I fought this for a bit. I could not seem to get it to work. Most guides out there said it should just show up in the gh-pages for this Repo but try as I might, i got stuck.
Instead, I pivoted to using my Harbor which will do just fine.
First, I package the chart
builder@DESKTOP-QADGF36:~/Workspaces/pyplanereport$ helm package pyplanereport
Successfully packaged chart and saved it to: /home/builder/Workspaces/pyplanereport/pyplanereport-0.1.0.tgz
Then pushed up to Harbor where I created a public project to hold the charts
$ helm push pyplanereport-0.1.0.tgz oci://harbor.freshbrewed.science/chartrepo
Pushed: harbor.freshbrewed.science/chartrepo/pyplanereport:0.1.0
Digest: sha256:ba365f7d34043e8a5b0e6fb8b80b44724a008ec965f6613a89d6cff04ff174b7
I can now use helm pull
to fetch it
builder@DESKTOP-QADGF36:/tmp/testchart$ helm pull oci://harbor.freshbrewed.science/chartrepo/pyplanereport --version 0.1.0
Pulled: harbor.freshbrewed.science/chartrepo/pyplanereport:0.1.0
Digest: sha256:ba365f7d34043e8a5b0e6fb8b80b44724a008ec965f6613a89d6cff04ff174b7
builder@DESKTOP-QADGF36:/tmp/testchart$ ls -ltra
total 52
-rw-r--r-- 1 builder builder 4106 Dec 23 11:20 pyplanereport-0.1.0.tgz
drwxrwxrwt 104 root root 36864 Dec 23 11:20 ..
drwxr-xr-x 2 builder builder 4096 Dec 23 11:20 .
Now to test using the remote repo:
$ helm install --set image.repository=ghcr.io/idjohnson/pyplanereport --set image.tag=main --set planeso.projectid=9ca799e6-52c4-4a9e-8b40-461eef4f57e9 --set planeso.workspacename=tpk --set planeso.apikey=plane_api_aabbccddeeffaabbccddeeffaabb myplanefromhelm oci://harbor.freshbrewed.science/chartrepo/pyplanereport
Pulled: harbor.freshbrewed.science/chartrepo/pyplanereport:0.1.0
Digest: sha256:ba365f7d34043e8a5b0e6fb8b80b44724a008ec965f6613a89d6cff04ff174b7
NAME: myplanefromhelm
LAST DEPLOYED: Sat Dec 23 11:25:54 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=pyplanereport,app.kubernetes.io/instance=myplanefromhelm" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
Which with a kubectl port-forward worked fine:
$ kubectl port-forward svc/myplanefromhelm-pyplanereport 8989:80
Forwarding from 127.0.0.1:8989 -> 80
Forwarding from [::1]:8989 -> 80
Handling connection for 8989
Handling connection for 8989
To test ingress, I need to create an A Record first
builder@DESKTOP-QADGF36:~/Workspaces/pyplanereport$ cat r53-planereport.json
{
"Comment": "CREATE planereport fb.s A record ",
"Changes": [
{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "planereport.freshbrewed.science",
"Type": "A",
"TTL": 300,
"ResourceRecords": [
{
"Value": "75.73.224.240"
}
]
}
}
]
}
builder@DESKTOP-QADGF36:~/Workspaces/pyplanereport$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-planereport.json
{
"ChangeInfo": {
"Id": "/change/C07149561EOVHKWBG2TBA",
"Status": "PENDING",
"SubmittedAt": "2023-12-23T17:32:19.130Z",
"Comment": "CREATE planereport fb.s A record "
}
}
I can now create a values file with the Nginx settings. I know it can also be done with some more complicated “set” commands, but the moment I start dealing with annotations and hosts settings, I move to just doing it in a values.yaml file
$ cat helmvalues.yaml
autoscaling:
enabled: false
maxReplicas: 100
minReplicas: 1
targetCPUUtilizationPercentage: 80
fullnameOverride: ""
image:
pullPolicy: IfNotPresent
repository: ghcr.io/idjohnson/pyplanereport
tag: main
imagePullSecrets: []
ingress:
annotations:
kubernetes.io/ingress.class: nginx
ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
className: ""
enabled: true
hosts:
- host: planereport.freshbrewed.science
paths:
- path: /
pathType: ImplementationSpecific
tls:
- hosts:
- planereport.freshbrewed.science
secretName: planereport-tls
planeso:
apikey: plane_api_aabbccddeeffaabbccddeeffaabb
ignorestate: Cancelled
projectid: 9ca799e6-52c4-4a9e-8b40-461eef4f57e9
tabletitle: Project Issue Report
workspacename: tpk
podAnnotations: {}
podSecurityContext: {}
replicaCount: 1
resources: {}
securityContext: {}
service:
port: 80
type: ClusterIP
serviceAccount:
annotations: {}
create: true
name: ""
tolerations: []
$ helm upgrade myplanefromhelm -f helmvalues.yaml oci://harbor.freshbrewed.science/chartrepo/pyplanereport
Pulled: harbor.freshbrewed.science/chartrepo/pyplanereport:0.1.0
Digest: sha256:ba365f7d34043e8a5b0e6fb8b80b44724a008ec965f6613a89d6cff04ff174b7
Release "myplanefromhelm" has been upgraded. Happy Helming!
NAME: myplanefromhelm
LAST DEPLOYED: Sat Dec 23 11:38:15 2023
NAMESPACE: default
STATUS: deployed
REVISION: 2
NOTES:
1. Get the application URL by running these commands:
https://planereport.freshbrewed.science/
I can see the ingress came up
$ kubectl get ingress | grep plane
myplanefromhelm-pyplanereport <none> planereport.freshbrewed.science 192.168.1.215,192.168.1.36,192.168.1.57,192.168.1.78 80, 443 74s
Once the cert was issued, I could see the report as served through my local k8s with TLS
Let’s see how this is really live:
I now list some other ways to reach me on the feedback form, but I didn’t always do that. So I decided I wanted one more feature.
Full flow
Let’s use this to demonstrate the full flow.
I would really like to hide multiple states. For instance, I have some requests there now with private invites to Slack which were meant just for me - again, from when I didn’t have other ways to reach me shown.
The other, lower priority, is that the table title is rather boring
Seems that should really be a parameter.
Since this is about the report app, I would expect these issues in the Github repo itself
and
Also, this is the right time to make a release before I mess the whole thing up.
We now have 0.1.0 listed with notes
I’ll try using codespace for the first issue
This fires up a shell (noting it’s paid for by idjohnson - so we’ll see how that plays out).
I’ll add a quick list check def and add the compare. I can do a docker build here in codespace
I did a quick run locally and saw my logic is inverted
That looks fantastic
I’ll create a PR and indicate it fixes that issue
The PR Diff looks good so I’ll merge it
We can see from resulting page, on the right, it is associated and completes that issue
Seen here:
Testing
Since I merged it, we edit the deployment to force it pull always
$ kubectl edit deployments myplanefromhelm-pyplanereport
deployment.apps/myplanefromhelm-pyplanereport edited
$ kubectl get deployments myplanefromhelm-pyplanereport -o yaml | grep -i imagepullpolicy
imagePullPolicy: Always
$ kubectl get pods | grep plane
myplanefromhelm-pyplanereport-f59b9c584-h4tvc 1/1 Running 0 6h32m
myplanefromhelm-pyplanereport-bc77f898-lqt7k 1/1 Running 0 10s
$ kubectl get pods | grep plane
myplanefromhelm-pyplanereport-bc77f898-lqt7k 1/1 Running 0 16s
myplanefromhelm-pyplanereport-f59b9c584-h4tvc 1/1 Terminating 0 6h32m
Now to use it, I need to edit my values:
builder@LuiGi17:~/Workspaces/pyTest$ helm get values myplanefromhelm -o yaml > values.yaml
builder@LuiGi17:~/Workspaces/pyTest$ vi values.yaml
builder@LuiGi17:~/Workspaces/pyTest$ cat values.yaml | grep -i ignore
ignorestate: Cancelled,Hidden
And redeploy
$ helm upgrade myplanefromhelm -f values.yaml oci://harbor.freshbrewed.science/chartrepo/pyplanereport
Pulled: harbor.freshbrewed.science/chartrepo/pyplanereport:0.1.0
Digest: sha256:ba365f7d34043e8a5b0e6fb8b80b44724a008ec965f6613a89d6cff04ff174b7
Release "myplanefromhelm" has been upgraded. Happy Helming!
NAME: myplanefromhelm
LAST DEPLOYED: Sat Dec 23 18:06:15 2023
NAMESPACE: default
STATUS: deployed
REVISION: 3
NOTES:
1. Get the application URL by running these commands:
https://planereport.freshbrewed.science/
If there is any doubt on the values, I can just check the CM
$ kubectl get cm myplanefromhelm-pyplanereport-cfg -o yaml | grep -i ignore
IGNORESTATE: Cancelled,Hidden
Now to use it, I need to add that new State to the Project
Now I can change the ignore values and set some on issues
$ kubectl get cm myplanefromhelm-pyplanereport-cfg -o yaml | grep -i ignore
IGNORESTATE: Cancelled,Hidden
$ helm get values myplanefromhelm -o yaml > values.yaml
$ vi values.yaml
$ helm upgrade myplanefromhelm -f values.yaml oci://harbor.freshbrewed.science/chartrepo/pyplanereport
Pulled: harbor.freshbrewed.science/chartrepo/pyplanereport:0.1.0
Digest: sha256:ba365f7d34043e8a5b0e6fb8b80b44724a008ec965f6613a89d6cff04ff174b7
Release "myplanefromhelm" has been upgraded. Happy Helming!
NAME: myplanefromhelm
LAST DEPLOYED: Sat Dec 23 18:20:23 2023
NAMESPACE: default
STATUS: deployed
REVISION: 4
NOTES:
1. Get the application URL by running these commands:
https://planereport.freshbrewed.science/
$ kubectl get cm myplanefromhelm-pyplanereport-cfg -o yaml | grep -i ignore
IGNORESTATE: Cancelled,Hidden,Done
I then rotated the pod
$ kubectl delete pod myplanefromhelm-pyplanereport-f59b9c584-gbdcm
pod "myplanefromhelm-pyplanereport-f59b9c584-gbdcm" deleted
$ kubectl get pods | grep plane
myplanefromhelm-pyplanereport-f59b9c584-qcqhd 1/1 Running 0 62s
and tested
Once pushed, I created a tag, 0.2.1, which ended up building ghcr.io/idjohnson/pyplanereport:0.2.1
And our report page is still live at planereport.freshbrewed.science
Summary
I really hope you enjoyed following this 4-part series as much as I did writing it. We started in Part 1 by trying Plane.so self-host and hosted. After exploring the SaaS option in detail, we moved on to Part 2 were we got into integrations and the REST API. In that post we attempted to Sync with Github and did succeed with JIRA. In Part 3 we built something new - a containerized Issue Report App using Python and Jinja2.
Lastly, here in Part 4 we Open-sourced it using Github. We made a proper Helm Chart, Github workflow and used Github itself to host the containers (ghcr.io). We tried to get gh-pages to host the Helm charts, but failing that, shoved them into a public repo location in my Harbor CR. We tackled tags and releases and lastly a full Issue update inside of Github using Github Codespace.
I am not done, of course. I expect to do more updates such as supporting self-hosted instances. I’m debating going back to build out my own self-hosted, however, I’m quite happy so far with their SaaS solution.