Revisits: OpenStatus and Gitness

Published: Jan 16, 2024 by Isaac Johnson

It’s a been a while since we looked at OpenStatus and Gitness. Have they added any features or functionality since our last looks? We’ll do a quick look at OpenStatus and a much more in-depth review of Gitness as there has been much added since our last writeup.

Openstatus

We last looked at OpenStatus back in October.

I can tell just from comparing our last writeup to now they added SMS to the offerings on the “Pro” plan.

/content/images/2023/10/tools-10.png

versus today

/content/images/2024/01/revisits-01.png

Status Reports

One of the new features is a “Status Report”.

Click either “Create” button to create the report

/content/images/2024/01/revisits-02.png

It appears this is a way to create a User Notification on an Incident

/content/images/2024/01/revisits-03.png

Clicking confirm indicates it was saved, but stops there

/content/images/2024/01/revisits-04.png

I can now see it listed

/content/images/2024/01/revisits-05.png

The report is viewable here and on our Public URL. Internally:

/content/images/2024/01/revisits-06.png

And on our Public URL: https://fb-status.openstatus.dev/

/content/images/2024/01/revisits-10.png

And I can see a summary of the incident(s) there as well

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

At any point, we can resolve the incident

/content/images/2024/01/revisits-07.png

And see the status line in the report

/content/images/2024/01/revisits-08.png

I like that it calculates the total time. Often, when doing RCAs at work, we have to build a timetable. One could use this Incident system to keep track of updates along the way.

SMS notifications

I can see, however I cannot use, in the free tier, SMS notifications

/content/images/2024/01/revisits-09.png

Uptime

Since I have had this running now for at least 3mo, I can see any time I was down

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

This is really handy as a free external monitoring system.

Gitness

Back in October we also looked at Gitness, a new OS GIT offering from Harness.io.

I only checked out the local instance in my Windows box. Let’s try it in Ubuntu proper this time.

In their getting started they mapped to a tmp dir which seems fraut with danger if you really hold GIT code. We’ll use a docker volume instead

$ docker volume create gitnessdata
gitnessdata

$ docker run -d -p 3333:3000 -v /var/run/docker.sock:/var/run/docker.sock -v gitnessdata:/data --name gitness --restart always harness/gitness
Unable to find image 'harness/gitness:latest' locally
latest: Pulling from harness/gitness
96526aa774ef: Already exists
08e4cf3e8a96: Pull complete
0079f8833b55: Pull complete
7c961e087222: Pull complete
adcd91e533d5: Pull complete
7c60e7576997: Pull complete
Digest: sha256:f0da879e02676d93665ddb00252b3d91361a5819e9b7d1396214b99881736849
Status: Downloaded newer image for harness/gitness:latest
e9163bc1f994a26e158e894191df578e88d8e9a3320de22396ac854161dcd725

I can now see it served up on port 3333

/content/images/2024/01/revisits-12.png

I’ll signup for a new account

/content/images/2024/01/revisits-13.png

Now through the UI, there is no way to disable user signups, however I can via docker settings

I’ll reset

$ docker stop gitness
gitness
$ docker rm gitness
gitness
$ docker volume rm gitnessdata
gitnessdata
$ docker volume create gitnessdata
gitnessdata

$ docker run -d -e GITNESS_PRINCIPAL_ADMIN_PASSWORD=MyAdminPassword -e GITNESS_USER_SIGNUP_ENABLED=false -p 3333:3000 -v /var/run/docker.sock:/var/run/docker.sock -v gitnessdata:/data --name gitness --restart always harness/gitness
2dde059b2253882a836f9a9a3c12b3c421cb2e40f99bb8c88cbed0d68d39b7f0

The sign up page still exists, but does not complete

/content/images/2024/01/revisits-14.png

The idea is that I now login as “Admin” with the password I set, then I can create users there

/content/images/2024/01/revisits-15.png

That will create a password for the user at create time

/content/images/2024/01/revisits-16.png

which they can change easily

/content/images/2024/01/revisits-17.png

/content/images/2024/01/revisits-18.png

Let’s create a project

/content/images/2024/01/revisits-19.png

I’ll name it FreshbrewedPublic

/content/images/2024/01/revisits-20.png

Next, I’ll click “New Repository” under Repositories

/content/images/2024/01/revisits-21.png

I’ll make it public with GPLv3

/content/images/2024/01/revisits-22.png

From the Clone, I see it thinks it’s running on the local docker port of 3000, not the exposed 3333

/content/images/2024/01/revisits-23.png

The documentation is still rather absent. but we can see some settings by looking at the code. I believe from this line that what needs to be set is “GITNESS_URL_BASE”

I did a stop and rm, but did not remove the volume (that had data)

$ docker stop gitness
gitness
$ docker rm gitness
gitness

$ docker run -d -e GITNESS_PRINCIPAL_ADMIN_PASSWORD=adfasdfsafasdf -e GITNESS_URL_BASE=http://192.168.1.100:3333/ -e GITNESS_USER_SIGNUP_ENABLED=false -p 3333:3000 -v /var/run/docker.sock:/var/run/docker.sock -v gitnessdata:/data --name gitness --restart always harness/gitness
816c49cb503d7a5f204d4b14daf23b8125cf9d120a122cb9a5096a354677af6b

Now the clone looks right:

/content/images/2024/01/revisits-24.png

We can test the clone

builder@DESKTOP-QADGF36:~/Workspaces$ git clone http://192.168.1.100:3333/git/FreshbrewedPublic/FreshbrewedPublic.git
Cloning into 'FreshbrewedPublic'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 11.16 KiB | 11.16 MiB/s, done.
builder@DESKTOP-QADGF36:~/Workspaces$ cd FreshbrewedPublic/
builder@DESKTOP-QADGF36:~/Workspaces/FreshbrewedPublic$ cat LICENSE | wc -l
234

How might we expose with TLS? I get gunshy with passwords and HTTP without SSL.

First, I need a quick A record

$ cat r53-gitness.json
{
    "Comment": "CREATE gitness fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "gitness.freshbrewed.science",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "75.73.224.240"
            }
          ]
        }
      }
    ]
  }

$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-gitness.json
{
    "ChangeInfo": {
        "Id": "/change/C0229976T0QGL2PJV9OO",
        "Status": "PENDING",
        "SubmittedAt": "2023-12-26T11:58:24.255Z",
        "Comment": "CREATE gitness fb.s A record "
    }
}

Next, I need to create an external endpoint / external IP entry for this containerized gitness in my primary cluster.

We’ve done this before for Uptime and Rundeck.

As a reminder, we need a service, albeit one without selectors, this matches an “Endpoint” that points to an in-house IP. Lastly, an Ingress handles certs and directing TLS traffic internally through to the endpoint.

$ cat gitness.ingress.yaml
apiVersion: v1
kind: Service
metadata:
  name: gitness-external-ip
spec:
  ports:
  - name: gitnessapp
    port: 80
    protocol: TCP
    targetPort: 3333
  clusterIP: None
  type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
  name: gitness-external-ip
subsets:
- addresses:
  - ip: 192.168.1.100
  ports:
  - name: gitnessapp
    port: 3333
    protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    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"
  labels:
    app.kubernetes.io/instance: gitness
  name: gitnessingress
spec:
  rules:
  - host: gitness.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: gitness-external-ip
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - gitness.freshbrewed.science
    secretName: gitness-tls

$ kubectl apply -f ./gitness.ingress.yaml
service/gitness-external-ip created
endpoints/gitness-external-ip created
ingress.networking.k8s.io/gitnessingress created

I had to wait for the cert (slow today)

$ kubectl get cert | tail -n1
gitness-tls                       True    gitness-tls                       3m26s

This works, of course:

/content/images/2024/01/revisits-25.png

But to work externally, I also need to let Gitness know it has a new front door:

$ docker stop gitness
gitness
$ docker rm gitness
gitness

$ docker run -d -e GITNESS_PRINCIPAL_ADMIN_PASSWORD=asdfasdfsadfasdf -e GITNESS_URL_BASE=https://gitness.freshbrewed.science/ -e GITNESS_USER_SIGNUP_ENABLED=false -p 3333:3000 -v /var/run/docker.sock:/var/run/docker.sock -v gitnessdata:/data --name gitness --restart always harness/gitness
2bc8660dc4002dfd592bd3895b2751d3a8ddf415b4e5495760029e45e128c92d

We can see this work in action

However, in a fresh InPrivate window I can see even this “Public” repo isn’t insofar as it requires a login

/content/images/2024/01/revisits-26.png

Webhooks

Let’s see what Webhooks can deliver now. I’ll create a new webhook using https://webhook.site/ to test.

/content/images/2024/01/revisits-27.png

I can see I now have a webhook set for all events

/content/images/2024/01/revisits-28.png

I’ll use the inline editor to create a file

/content/images/2024/01/revisits-29.png

I’ll then commit the changes to a new branch

/content/images/2024/01/revisits-30.png

Immediately I saw a payload delivered to Webhook.site:

/content/images/2024/01/revisits-31.png

{
  "trigger": "branch_created",
  "repo": {
    "id": 1,
    "path": "FreshbrewedPublic/FreshbrewedPublic",
    "uid": "FreshbrewedPublic",
    "default_branch": "main",
    "git_url": "https://gitness.freshbrewed.science/git/FreshbrewedPublic/FreshbrewedPublic.git"
  },
  "principal": {
    "id": 4,
    "uid": "idjohnson",
    "display_name": "Isaac Johnson",
    "email": "isaac.johnson@gmail.com",
    "type": "user",
    "created": 1703537228034,
    "updated": 1703537325044
  },
  "ref": {
    "name": "refs/heads/mynewbranch",
    "repo": {
      "id": 1,
      "path": "FreshbrewedPublic/FreshbrewedPublic",
      "uid": "FreshbrewedPublic",
      "default_branch": "main",
      "git_url": "https://gitness.freshbrewed.science/git/FreshbrewedPublic/FreshbrewedPublic.git"
    }
  },
  "sha": "652a29168a386d7039de727d1a5f68571b13931a",
  "commit": {
    "sha": "652a29168a386d7039de727d1a5f68571b13931a",
    "message": "Create MyNewFile.md\n\nSome notes about this commit",
    "author": {
      "identity": {
        "name": "Isaac Johnson",
        "email": "isaac.johnson@gmail.com"
      },
      "when": "2023-12-26T12:53:29Z"
    },
    "committer": {
      "identity": {
        "name": "Gitness",
        "email": "system@gitness.io"
      },
      "when": "2023-12-26T12:53:29Z"
    }
  },
  "old_sha": "0000000000000000000000000000000000000000",
  "forced": false
}

Creating a PR

/content/images/2024/01/revisits-32.png

Delivered

{
  "trigger": "pullreq_created",
  "repo": {
    "id": 1,
    "path": "FreshbrewedPublic/FreshbrewedPublic",
    "uid": "FreshbrewedPublic",
    "default_branch": "main",
    "git_url": "https://gitness.freshbrewed.science/git/FreshbrewedPublic/FreshbrewedPublic.git"
  },
  "principal": {
    "id": 4,
    "uid": "idjohnson",
    "display_name": "Isaac Johnson",
    "email": "isaac.johnson@gmail.com",
    "type": "user",
    "created": 1703537228034,
    "updated": 1703537325044
  },
  "pull_req": {
    "number": 1,
    "state": "open",
    "is_draft": false,
    "title": "Create MyNewFile.md",
    "source_repo_id": 1,
    "source_branch": "mynewbranch",
    "target_repo_id": 1,
    "target_branch": "main",
    "merge_strategy": null,
    "author": {
      "id": 4,
      "uid": "idjohnson",
      "display_name": "Isaac Johnson",
      "email": "isaac.johnson@gmail.com",
      "type": "user",
      "created": 1703537228034,
      "updated": 1703537325044
    }
  },
  "target_ref": {
    "name": "refs/heads/main",
    "repo": {
      "id": 1,
      "path": "FreshbrewedPublic/FreshbrewedPublic",
      "uid": "FreshbrewedPublic",
      "default_branch": "main",
      "git_url": "https://gitness.freshbrewed.science/git/FreshbrewedPublic/FreshbrewedPublic.git"
    }
  },
  "ref": {
    "name": "refs/heads/mynewbranch",
    "repo": {
      "id": 1,
      "path": "FreshbrewedPublic/FreshbrewedPublic",
      "uid": "FreshbrewedPublic",
      "default_branch": "main",
      "git_url": "https://gitness.freshbrewed.science/git/FreshbrewedPublic/FreshbrewedPublic.git"
    }
  },
  "sha": "652a29168a386d7039de727d1a5f68571b13931a",
  "commit": {
    "sha": "652a29168a386d7039de727d1a5f68571b13931a",
    "message": "Create MyNewFile.md\n\nSome notes about this commit",
    "author": {
      "identity": {
        "name": "Isaac Johnson",
        "email": "isaac.johnson@gmail.com"
      },
      "when": "2023-12-26T12:53:29Z"
    },
    "committer": {
      "identity": {
        "name": "Gitness",
        "email": "system@gitness.io"
      },
      "when": "2023-12-26T12:53:29Z"
    }
  }
}

Squash and merge

/content/images/2024/01/revisits-33.png

delivered

{
  "trigger": "branch_updated",
  "repo": {
    "id": 1,
    "path": "FreshbrewedPublic/FreshbrewedPublic",
    "uid": "FreshbrewedPublic",
    "default_branch": "main",
    "git_url": "https://gitness.freshbrewed.science/git/FreshbrewedPublic/FreshbrewedPublic.git"
  },
  "principal": {
    "id": 4,
    "uid": "idjohnson",
    "display_name": "Isaac Johnson",
    "email": "isaac.johnson@gmail.com",
    "type": "user",
    "created": 1703537228034,
    "updated": 1703537325044
  },
  "ref": {
    "name": "refs/heads/main",
    "repo": {
      "id": 1,
      "path": "FreshbrewedPublic/FreshbrewedPublic",
      "uid": "FreshbrewedPublic",
      "default_branch": "main",
      "git_url": "https://gitness.freshbrewed.science/git/FreshbrewedPublic/FreshbrewedPublic.git"
    }
  },
  "sha": "edb9e4bafcbf54d92eff8e7ebfed30edb4cea997",
  "commit": {
    "sha": "edb9e4bafcbf54d92eff8e7ebfed30edb4cea997",
    "message": "Create MyNewFile.md (#1)",
    "author": {
      "identity": {
        "name": "Isaac Johnson",
        "email": "isaac.johnson@gmail.com"
      },
      "when": "2023-12-26T12:56:48Z"
    },
    "committer": {
      "identity": {
        "name": "Gitness",
        "email": "system@gitness.io"
      },
      "when": "2023-12-26T12:56:48Z"
    }
  },
  "old_sha": "259234405c98932716632da8f05d8c3366c49152",
  "forced": false
}

I think we get the idea.

We can just use the toggle to disable a webhook

/content/images/2024/01/revisits-34.png

Pipelines

Since the first release, they have added working pipelines.

Let’s create a quick hello-world one

In Pipelines, I’ll click “+ New Pipeline”

/content/images/2024/01/revisits-37.png

Give it a name, which prefills the destination

/content/images/2024/01/revisits-36.png

and we see it created one for us

/content/images/2024/01/revisits-35.png

version: 1
kind: pipeline
spec:
  stages:
    - name: build
      type: ci
      spec:
        steps:
          - name: build
            type: run
            spec:
              container: alpine
              script: echo "hello world"

I can now “save and run”. This then prompts for “Run pipeline”

/content/images/2024/01/revisits-38.png

I see it kick in

/content/images/2024/01/revisits-39.png

Interestingly, it got stuck on it’s host URL

/content/images/2024/01/revisits-40.png

This makes me wonder if we really need to set all the values in config.go, even though they say the derive from base.

	URL struct {
		// Base is used to generate external facing URLs in case they aren't provided explicitly.
		// Value is derived from HTTP.Server unless explicitly specified (e.g. http://localhost:3000).
		Base string `envconfig:"GITNESS_URL_BASE"`

		// Git defines the external URL via which the GIT API is reachable.
		// NOTE: for routing to work properly, the request path & hostname reaching gitness
		// have to statisfy at least one of the following two conditions:
		// - Path ends with `/git`
		// - Hostname is different to API hostname
		// (this could be after proxy path / header rewrite).
		// Value is derived from Base unless explicitly specified (e.g. http://localhost:3000/git).
		Git string `envconfig:"GITNESS_URL_GIT"`

		// API defines the external URL via which the rest API is reachable.
		// NOTE: for routing to work properly, the request path reaching gitness has to end with `/api`
		// (this could be after proxy path rewrite).
		// Value is derived from Base unless explicitly specified (e.g. http://localhost:3000/api).
		API string `envconfig:"GITNESS_URL_API"`

		// UI defines the external URL via which the UI is reachable.
		// Value is derived from Base unless explicitly specified (e.g. http://localhost:3000).
		UI string `envconfig:"GITNESS_URL_UI"`

		// Internal defines the internal URL via which the service is reachable.
		// Value is derived from HTTP.Server unless explicitly specified (e.g. http://localhost:3000).
		Internal string `envconfig:"GITNESS_URL_INTERNAL"`

		// Container is the endpoint that can be used by running container builds to communicate
		// with gitness (for example while performing a clone on a local repo).
		// host.docker.internal allows a running container to talk to services exposed on the host
		// (either running directly or via a port exposed in a docker container).
		// Value is derived from HTTP.Server unless explicitly specified (e.g. http://host.docker.internal:3000).
		Container string `envconfig:"GITNESS_URL_CONTAINER"`

I’ll set all the values

builder@builder-T100:~$ docker stop gitness
gitness
builder@builder-T100:~$ docker rm gitness
gitness
builder@builder-T100:~$ docker run -d -e GITNESS_PRINCIPAL_ADMIN_PASSWORD=asdfasdfasfasfasfasdfsdf -e GITNESS_URL_BASE=https://gitness.freshbrewed.science/ -e GITNESS_URL_GIT=https://gitness.freshbrewed.science/git -e GITNESS_URL_API=https://gitness.freshbrewed.science/api -e GITNESS_URL_UI=https://gitness.freshbrewed.science -e GITNESS_URL_INTERNAL=http://192.168.1.100:3333 -e GITNESS_URL_CONTAINER=http://192.168.1.100:3333 -e GITNESS_USER_SIGNUP_ENABLED=false -p 3333:3000 -v /var/run/docker.sock:/var/run/docker.sock -v gitnessdata:/data --name gitness --restart always harness/gitness
5bcb5bf109cc9563011a55fba6803998363dfabb525a0a67abb6c767cd1825a0

This time it worked like a charm. Clearly, it uses some of the internal URLs

/content/images/2024/01/revisits-41.png

I can see it pulled and used alpine without issue

/content/images/2024/01/revisits-42.png

Secrets

I see there is a section called “Secrets”. Let’s create a secret to see what we can do with it.

/content/images/2024/01/revisits-43.png

I will then edit my pipeline to see if I can fetch the secret

version: 1
kind: pipeline
spec:
  stages:
    - name: build
      type: ci
      spec:
        steps:
          - name: build
            type: run
            spec:
              container: alpine
              script: |
                 echo "hello world"
                 echo ${{ secrets.get("mysecret") }}
                 echo ${{ secrets.get("mysecret") }} > t.o
                 cat t.o | base64

This worked, though clearly it shows the value - I expected some basic masking like I would get in other CI tools

/content/images/2024/01/revisits-44.png

So the short version would be to use “set +x” and/or accept that “secrets” leak in build logs.

Branch rules

It appears they have expanded the Branch Rules we can use to protect a branch

/content/images/2024/01/revisits-45.png

Let’s create a proper branch rule that only the built-in “Admin” can bypass:

/content/images/2024/01/revisits-46.png

I can now see main is protected:

/content/images/2024/01/revisits-47.png

I’ll now try and create against main as my normal user account

/content/images/2024/01/revisits-48.png

Surprisingly that worked without issue

/content/images/2024/01/revisits-49.png

I’ll try with a normal user, perhaps my Gmail is an admin since I “own” that repo. I’ll add Tristan as a contributor

/content/images/2024/01/revisits-50.png

When I try and save an edit as Tristan

/content/images/2024/01/revisits-51.png

I see a “422” error

/content/images/2024/01/revisits-52.png

However, if that user creates a branch for a PR, we are fine

/content/images/2024/01/revisits-53.png

In creating the PR, I see the CI build is run as a check

/content/images/2024/01/revisits-54.png

However, Tristan was able to merge into main without any other stops

/content/images/2024/01/revisits-55.png

I’m wondering if I didn’t include “main” somehow.

/content/images/2024/01/revisits-56.png

I went back to rules and made sure to “+include” main and master branch names

/content/images/2024/01/revisits-57.png

I definitely see them now listed

/content/images/2024/01/revisits-58.png

I tested again as Tristan and clearly the branch rules don’t really stop anything other than direct changes to main. That is a PR is required, but no approvals or checks otherwise.

And just to check, i could bypass with a new file, not just edits. So it clearly isn’t really blocking anything

/content/images/2024/01/revisits-60.png

Summary

We looked at some of the minor tweaks added to OpenStatus since our writeup in October. Really, just some notes on SMS for the paid tier. They did have Incidents back then, but I didn’t explore them in that writeup.

With Gitness, actually quite a lot has changed since our October writeup. Everything from PRs to Webhooks to Branch rules (which clearly do not fully work) have improved. In my first writeup, we did some pipelines but it lacked the plugins and the docs were limited. There is still some improvements needed before I would go all-in, but it’s a positive sign to see it improve as much as it has.

Kubernetes Container Docker Gitness Openstatus

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