OneDev: Part 1 - Setup and Usage

Published: Nov 21, 2024 by Isaac Johnson

First, credit to Marius of MariusHosting again for finding this very cool CICD suite, OneDev. I came across his post a week ago about hosting it on a NAS and really wanted to try it in Kubernetes.

OneDev

First, let’s cover the two editions available - Community, which is free, and Enterprise which is US$6/mo.

/content/images/2024/11/onedev-01.png

I would jump right in on the enterprise if it weren’t for that small note about a 12-user month minimum order

/content/images/2024/11/onedev-02.png

Kubernetes install

Let’s start with Kubernetes as they have good Helm docs already

I’ll add and update the chart

$ helm repo add onedev https://dl.cloudsmith.io/public/onedev/onedev/helm/charts
"onedev" has been added to your repositories

$ helm repo update onedev
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "onedev" chart repository
Update Complete. ⎈Happy Helming!⎈

They do have ingress values so I was tempted to setup Ingress immeditiately. However, they do not let me set the Issuer in the annotations which is a blocker for me. Therefore, we’ll do a standard helm install and come back on the Ingress

$ helm install onedev onedev/onedev -n onedev --create-namespace
NAME: onedev
LAST DEPLOYED: Sat Nov 16 08:08:52 2024
NAMESPACE: onedev
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: onedev
CHART VERSION: 11.5.2
APP VERSION: 11.5.2
###################################################################
#
# CAUTION: If you are upgrading from version <= 9.0.0, please make
# sure to follow https://docs.onedev.io/upgrade-guide/deploy-to-k8s
# to migrate your data
#
###################################################################

** Please be patient while the chart is being deployed **

Get the OneDev URL by running:

  kubectl port-forward --namespace onedev svc/onedev 6610:80 &

  URL: http://127.0.0.1:6610

I’ll do as suggested

$ kubectl port-forward --namespace onedev svc/onedev 6610:80
Forwarding from 127.0.0.1:6610 -> 6610
Forwarding from [::1]:6610 -> 6610

The first page of setup prompts me to create a user

/content/images/2024/11/onedev-03.png

I guess I’ll need to determine the DNS up front

/content/images/2024/11/onedev-04.png

Let’s whip up a 1dev DNS name in Azure

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n 1dev

Then create the ingress

$ cat 1dev.ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.org/websocket-services: onedev
  name: 1devingress
spec:
  rules:
  - host: 1dev.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: onedev
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - 1dev.tpk.pw
    secretName: 1dev-tls

$ kubectl apply -f ./1dev.ingress.yaml -n onedev
ingress.networking.k8s.io/1devingress created

Once I saw the cert ready

$ kubectl get cert -n onedev
NAME       READY   SECRET     AGE
1dev-tls   True    1dev-tls   2m20s

I set the proper value and clicked ‘Finish’

/content/images/2024/11/onedev-05.png

I wasn’t sure if it would redirect to the URL or not. I can confirm it kept with the port-forwarded one

/content/images/2024/11/onedev-06.png

At this point I pivoted to Firefox and using the DNS name

/content/images/2024/11/onedev-07.png

It was interesting how compact OneDev is. It just fired up one statefulset with a PVC and service

$ kubectl get pvc -n onedev
NAME            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-onedev-0   Bound    pvc-a86b22bc-f10f-4faa-8dd5-28554eefd2c4   100Gi      RWO            local-path     178m

$ kubectl get pods -n onedev
NAME       READY   STATUS    RESTARTS   AGE
onedev-0   1/1     Running   0          178m

$ kubectl get sts -n onedev
NAME     READY   AGE
onedev   1/1     178m

$ kubectl get svc -n onedev
NAME     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE
onedev   ClusterIP   10.43.62.251   <none>        80/TCP,22/TCP   178m

Projects

I next went to create a project

/content/images/2024/11/onedev-08.png

I am getting some real Azure DevOps vibes here - we have Code Management (akin to Repos), Issue Management (akin to Work Items), and Package Management (akin to Artifacts)

The project starts us out with an empty GIT repo

/content/images/2024/11/onedev-09.png

Let’s start with just adding some files interactively

/content/images/2024/11/onedev-10.png

As you can see we have two modes - edit

/content/images/2024/11/onedev-11.png

and preview

/content/images/2024/11/onedev-12.png

I saved the file and saw it had to trigger some reindexing

/content/images/2024/11/onedev-13.png

That said, I can view the GIT SHA and history at https://1dev.tpk.pw/MyFirstProject/~commits/38d0f2fa05b3a1e6c83855e8bc1803309a85ddd9

/content/images/2024/11/onedev-14.png

I can now clone the repo locally. While SSH is shown, my NGinx ingress won’t serve that

/content/images/2024/11/onedev-15.png

I should note that if I am in the network, i could use SSH

I would need to generate ssh keys if I haven’t already

/content/images/2024/11/onedev-16.png

Then add the pub key

/content/images/2024/11/onedev-17.png

I can then clone in-cluster with the Cluster IP for the service

$ kubectl get svc -n onedev
NAME     TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE
onedev   ClusterIP   10.43.62.251   <none>        80/TCP,22/TCP   57m

/content/images/2024/11/onedev-18.png

I could punch a whole through my Firewall to forward some obscure high numbered port to a matching nodeport, but I think HTTPS would be sufficient - but it is an option for external SSH connectivity.

Build Spec

Let’s add a Build Spec and the first thing we’ll do is add a Job

/content/images/2024/11/onedev-19.png

I can give it a name and choose which type of step to add

/content/images/2024/11/onedev-20.png

I’ll choose to not run in a container this time and just do a whoami

/content/images/2024/11/onedev-21.png

My last step for CI is to create a branch trigger to have this run when we push changes

/content/images/2024/11/onedev-22.png

I can now save it

/content/images/2024/11/onedev-23.png

It did save it, but I can see the first run failed

/content/images/2024/11/onedev-24.png

I think it doesn’t like the prepare job step

/content/images/2024/11/onedev-25.png

I should note that while we can live in the GUI’ed pipeline builder, we can also just view the buildspec file as code

/content/images/2024/11/onedev-26.png

version: 37
jobs:
- name: TestJob
  steps:
  - !CommandStep
    name: TestCommand
    runInContainer: false
    interpreter: !DefaultInterpreter
      commands: |
        whoami
    useTTY: true
    condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
  triggers:
  - !BranchUpdateTrigger {}
  retryCondition: never
  maxRetries: 3
  retryDelay: 30
  timeout: 14400

However, I was able to get it to work in a container

/content/images/2024/11/onedev-27.png

I just needed to use Alpine with the default shell (sh)

$ cat .onedev-buildspec.yml
version: 37
jobs:
- name: TestJob
  steps:
  - !CommandStep
    name: TestCommand
    runInContainer: true
    image: alpine:latest
    interpreter: !ShellInterpreter
      shell: /bin/sh
      commands: |
        whoami
    useTTY: true
    condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL
  triggers:
  - !BranchUpdateTrigger {}
  retryCondition: never
  maxRetries: 3
  retryDelay: 30
  timeout: 14400

Issue Management

In issues, we start out with a basic Issue query and as you would expect, it shows no issues

/content/images/2024/11/onedev-28.png

I can click the + to add a new issue and optionally add an assignee

/content/images/2024/11/onedev-29.png

I think my only issue with issues is that the reporter is only shown in the Activity stream

/content/images/2024/11/onedev-30.png

I did notice the Watch options had some interesting choices such as “Watch, but only once I get involved”

/content/images/2024/11/onedev-31.png

It looks like the OOTB states are just Open and Closed

/content/images/2024/11/onedev-32.png

This was confirmed once I looked at Boards

/content/images/2024/11/onedev-33.png

We can easily move issues to closed and back again in the UI

If I turn on “change history” in Issue Activities I can see those state changes reflected

/content/images/2024/11/onedev-35.png

Adding states

But what if I wanted more states? We can do that in “Administration/Issues Settings/States”

Perhaps I want to add a Blocked state

/content/images/2024/11/onedev-37.png

Once added and moved, I am prompted to do “reconciliation”

/content/images/2024/11/onedev-38.png

This just took a quick moment to complete

/content/images/2024/11/onedev-39.png

I can now see the new state in my Issue state picker

/content/images/2024/11/onedev-40.png

However, this is not reflected on the default issue board

/content/images/2024/11/onedev-41.png

If I move my issue to Blocked

/content/images/2024/11/onedev-42.png

It disappears from the Board

/content/images/2024/11/onedev-43.png

This is what I hoped - it tells me that these new states are full entities and not just display names over existing states in the flow (For instance, in Azure DevOps I would likely still see in Open as it has to derive from base states).

Now, I can edit the board and add the Blocked state

/content/images/2024/11/onedev-44.png

and now I can see that Issue

/content/images/2024/11/onedev-45.png

Actually, there is a lot more to Issue States we will save for an advanced overview.

Essentially, we can set any flow conditions and choose to remove fields or block flows based on queries

/content/images/2024/11/onedev-46.png

For instance, we could have QA review if a title includes QA

/content/images/2024/11/onedev-47.png

Another interesting feature is to define link types

/content/images/2024/11/onedev-48.png

One I tend to like is “related to” and “duplicate of”

/content/images/2024/11/onedev-49.png

And now I can see those types

/content/images/2024/11/onedev-50.png

Notifications

What I thought I might be stuck behind a paywall feature to use Sendgrid

/content/images/2024/11/onedev-51.png

But then one can just use SMTP with Sendgrid so perhaps it is a convenience thing?

/content/images/2024/11/onedev-52.png

To be certain, I fired off a test mail

/content/images/2024/11/onedev-53.png

And saw it come through

/content/images/2024/11/onedev-54.png

Summary

In this first pass of OneDev we looked at the self-hosted option which was easy to setup in Kubernetes using the helm chart.

We covered Project creation, GIT Repos, initial Pipelines, Issues and Issue state modifications. We also touched on notifications and issue links.

I really am quite surprised how complete this offering is. I didn’t tackle Artifacts yet (Packages), but in comparing, again, to AzDO, we see quite the feature parity

/content/images/2024/11/onedev-55.png

Honestly, I think the only exception is Test Plans which, due to costs, I never ever use.

OpenSource Kubernetes DevOps OneDev

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes