Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .github/workflows/username_service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Username Service

on:
push:
branches:
- master
pull_request:
paths:
- ".github/workflows/username_service.yaml"
- "stfc-username-service/**"

jobs:
golangci-lint:
name: runner / golangci-lint
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: golangci-lint
uses: golangci/golangci-lint-action@v8.0.0
with:
working-directory: stfc-username-service/

run_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Run tests in container
uses: docker/build-push-action@v6
with:
no-cache: true
cache-to: type=gha,mode=max
target: test-stage
push: false
context: "{{defaultContext}}:stfc-username-service"

push_dev_image_harbor:
runs-on: ubuntu-latest
needs: [run_tests, golangci-lint]
steps:
- uses: actions/checkout@v5

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: harbor.stfc.ac.uk
username: ${{ secrets.STAGING_HARBOR_USERNAME }}
password: ${{ secrets.STAGING_HARBOR_TOKEN }}

- name: Set commit SHA for later
id: commit_sha
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT

- name: Build and push to staging project
uses: docker/build-push-action@v6
with:
cache-from: type=gha
cache-to: type=gha,mode=max
push: false
context: "{{defaultContext}}:stfc-username-service"
tags: "harbor.stfc.ac.uk/stfc-cloud-staging/stfc-username-service:${{ steps.commit_sha.outputs.sha_short }}"

- name: Inform of tagged name
run: echo "Image published to harbor.stfc.ac.uk/stfc-cloud-staging/stfc-username-service:${{ steps.commit_sha.outputs.sha_short }}"

push_release_image_harbor:
runs-on: ubuntu-latest
needs: [run_tests, golangci-lint]
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v5

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: harbor.stfc.ac.uk
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_TOKEN }}

- name: Get release tag for later
id: release_tag
run: echo "version=$(cat stfc-username-service/version.txt)" >> $GITHUB_OUTPUT

- name: Check if release file has updated
uses: dorny/paths-filter@v3
id: release_updated
with:
filters: |
version:
- 'stfc-username-service/version.txt'

- name: Build and push on version change
uses: docker/build-push-action@v6
if: steps.release_updated.outputs.version == 'true'
with:
cache-from: type=gha
cache-to: type=gha,mode=max
push: true
context: "{{defaultContext}}:stfc-username-service"
tags: "harbor.stfc.ac.uk/stfc-cloud/stfc-username-service:v${{ steps.release_tag.outputs.version }}"

- name: Inform of tagged name
if: steps.release_updated.outputs.version == 'true'
run: echo "Image published to harbor.stfc.ac.uk/stfc-cloud/stfc-username-service:v${{ steps.release_tag.outputs.version }}"
20 changes: 20 additions & 0 deletions stfc-username-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM golang:1.25-alpine AS build-stage
WORKDIR /app
COPY . .
RUN go mod download && go mod verify
RUN go build -o username .

# Run the tests in the container
FROM build-stage AS test-stage
RUN go test -v ./...

FROM alpine:latest AS build-release-stage
WORKDIR /
COPY --from=build-stage /app/username /username

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
RUN apk add --no-cache tini

USER appuser
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["./username"]
47 changes: 47 additions & 0 deletions stfc-username-service/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package main

import (
"context"
"log/slog"

"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/openstack"
"github.com/gophercloud/gophercloud/v2/openstack/config"
"github.com/gophercloud/gophercloud/v2/openstack/config/clouds"
)

// Struct containing openstack service clients for interacting with the API
type OpenstackClient struct {
computeClient *gophercloud.ServiceClient
identityClient *gophercloud.ServiceClient
}

// Creates a new client which can be used to interact with the API
// uses a cloud defined in cloud.yaml
func (c *OpenstackClient) New(cloud string) {
ctx := context.Background()

authOptions, endpointOptions, tlsConfig, err := clouds.Parse(clouds.WithCloudName(cloud))
if err != nil {
panic(err)
}

authOptions.AllowReauth = true

providerClient, err := config.NewProviderClient(ctx, authOptions, config.WithTLSConfig(tlsConfig))
if err != nil {
panic(err)
}

c.computeClient, err = openstack.NewComputeV2(providerClient, endpointOptions)
if err != nil {
slog.Error("Failed to initilize compute client")
panic(err)
}
c.identityClient, err = openstack.NewIdentityV3(providerClient, endpointOptions)
if err != nil {
slog.Error("Failed to initilize identity client")
panic(err)
}

}
7 changes: 7 additions & 0 deletions stfc-username-service/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module stfc-cloud/username

go 1.25.1

require github.com/gophercloud/gophercloud/v2 v2.8.0

require gopkg.in/yaml.v2 v2.4.0 // indirect
6 changes: 6 additions & 0 deletions stfc-username-service/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
github.com/gophercloud/gophercloud/v2 v2.8.0 h1:of2+8tT6+FbEYHfYC8GBu8TXJNsXYSNm9KuvpX7Neqo=
github.com/gophercloud/gophercloud/v2 v2.8.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
82 changes: 82 additions & 0 deletions stfc-username-service/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import (
"context"
"fmt"
"log/slog"
"net/http"
"os"

"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/v2/openstack/identity/v3/users"
)

type service struct {
client OpenstackClient
}

// Get the user ID associated with a given server
func getServerUserID(computeClient *gophercloud.ServiceClient, serverID string) (string, error) {
server := servers.Get(context.TODO(), computeClient, serverID)
serverDetails, err := server.Extract()
if err != nil {
slog.Error("Failed to get server details", "ID", serverID)
return "", err
}

return serverDetails.UserID, nil
}

// Get the username associated with a given user ID
func getUsername(identityClient *gophercloud.ServiceClient, userID string) (string, error) {
user := users.Get(context.TODO(), identityClient, userID)
userDetails, err := user.Extract()
if err != nil {
slog.Error("Failed to get user details", "ID", userID)
return "", err
}

return userDetails.Name, nil
}

// Handle requests to the path: .../getusername?serverID=<ID>
func (s service) getUserHandler(w http.ResponseWriter, r *http.Request) {

serverID := r.URL.Query().Get("serverID")
userID, err := getServerUserID(s.client.identityClient, serverID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
name, err := getUsername(s.client.identityClient, userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_, err = fmt.Fprintf(w, "%s", name)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

func main() {

addr := os.Getenv("ADDR")
if addr == "" {
addr = ":80"
}

var client OpenstackClient
client.New("openstack")

service := service{client: client}

http.HandleFunc("/getusername", service.getUserHandler)
err := http.ListenAndServe(addr, nil)
if err != nil {
slog.Error("Failed to start server", "addr", addr)
panic(err)
}
}
Loading