Skip to content

Commit 2c8f802

Browse files
committed
Add binary version info
Make it easier to keep track of the version of sf-operator that was used to deploy a resource. * CLI: The `version` subcommand returns the version of the executable. * Standalone mode: the "sf-standalone-owner" configmap has two new annotations: "sf-operator-version" with the version of the binary used to deploy, and "last-reconcile" with the EPOCH timestamp of the the end time of the deployment. The configmap's data also holds the latest "CR" that was applied. Assisted-By: Gemini-2.5-pro Change-Id: Ibe363403d57f299dda69f9fa3ad1ade7a7fdf464
1 parent 8ab8975 commit 2c8f802

File tree

8 files changed

+112
-4
lines changed

8 files changed

+112
-4
lines changed

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ CATALOG_IMG ?= $(CATALOG_REPO):latest
1818
ENVTEST_K8S_VERSION = 1.29.0
1919
CERT_MANAGER_VERSION = v1.14.6
2020

21+
LDFLAGS = -ldflags="-X github.com/softwarefactory-project/sf-operator/controllers/libs/utils.version=$(VERSION)"
22+
2123
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
2224
ifeq (,$(shell go env GOBIN))
2325
GOBIN=$(shell go env GOPATH)/bin
@@ -93,15 +95,15 @@ setenv:
9395

9496
.PHONY: build
9597
build: setenv generate fmt vet sc build-api-doc ## Build manager binary.
96-
go build -o bin/sf-operator main.go
98+
go build $(LDFLAGS) -o bin/sf-operator main.go
9799

98100
.PHONY: build-api-doc
99101
build-api-doc: # Build the API documentation.
100102
./hack/build-api-doc.sh
101103

102104
.PHONY: run
103105
run: manifests generate fmt vet ## Run a controller from your host.
104-
go run main.go
106+
go run $(LDFLAGS) main.go
105107

106108
.PHONY: operator-build
107109
operator-build: ## Build podman image with the manager.

cli/cmd/version.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (C) 2025 Red Hat
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/softwarefactory-project/sf-operator/controllers/libs/utils"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func MkVersionCmd() *cobra.Command {
14+
var versionCmd = &cobra.Command{
15+
Use: "version",
16+
Short: "Print the version number of sf-operator",
17+
Long: `All software has versions. This is sf-operator's`,
18+
Run: func(cmd *cobra.Command, args []string) {
19+
fmt.Println(utils.GetVersion())
20+
},
21+
}
22+
return versionCmd
23+
}

controllers/libs/utils/version.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (C) 2025 Red Hat
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package utils
5+
6+
import (
7+
"os/exec"
8+
"strings"
9+
)
10+
11+
// version is a variable that will be set at build time
12+
var version string
13+
14+
// GetVersion returns the latest tag known to git.
15+
// If it doesn't work, try to get the current commit hash.
16+
// If it doesn't work, return "development".
17+
func GetVersion() string {
18+
if version != "" {
19+
return version
20+
}
21+
cmd := exec.Command("git", "describe", "--tags")
22+
out, err := cmd.Output()
23+
if err == nil {
24+
return strings.TrimSpace(string(out))
25+
}
26+
27+
cmd = exec.Command("git", "rev-parse", "--short", "HEAD")
28+
out, err = cmd.Output()
29+
if err == nil {
30+
return strings.TrimSpace(string(out))
31+
}
32+
33+
return "development"
34+
}

controllers/softwarefactory_controller.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/fatih/color"
1818
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
19+
"gopkg.in/yaml.v3"
1920

2021
"k8s.io/client-go/kubernetes"
2122
"k8s.io/client-go/rest"
@@ -657,18 +658,24 @@ func (r *SoftwareFactoryReconciler) StandaloneReconcile(ctx context.Context, ns
657658
Name: controllerCMName,
658659
Namespace: ns,
659660
}}
661+
controllerAnnotations := map[string]string{
662+
"sf-operator-version": utils.GetVersion(),
663+
"last-reconcile": strconv.FormatInt(time.Now().Unix(), 10),
664+
}
660665
err := r.Client.Get(
661666
ctx, client.ObjectKey{Name: controllerCMName, Namespace: ns}, &controllerCM)
662667
if err != nil && k8s_errors.IsNotFound(err) {
663-
controllerCM.Data = nil
668+
marshaledSpec, _ := yaml.Marshal(sf.Spec)
669+
controllerCM.Data = map[string]string{
670+
"spec": string(marshaledSpec),
671+
}
664672
logging.LogI("Creating ConfigMap, name: " + controllerCMName)
665673
// Create the fake controller configMap
666674
if err := r.Create(ctx, &controllerCM); err != nil {
667675
log.Error(err, "Unable to create configMap", "name", controllerCMName)
668676
return err
669677
}
670678
}
671-
672679
sfCtrl := r.mkSFController(ctx, ns, &controllerCM, sf, true)
673680
attempt := 0
674681

@@ -680,6 +687,19 @@ func (r *SoftwareFactoryReconciler) StandaloneReconcile(ctx context.Context, ns
680687
}
681688
if status.Ready {
682689
log.Info("Standalone reconcile done.")
690+
log.Info("Updating controller configmap ...")
691+
if err := r.Client.Get(
692+
ctx, client.ObjectKey{Name: controllerCMName, Namespace: ns}, &controllerCM); err == nil {
693+
controllerCM.ObjectMeta.Annotations = controllerAnnotations
694+
if err := r.Update(ctx, &controllerCM); err != nil {
695+
log.Error(err, "Unable to update configMap", "name", controllerCMName)
696+
return err
697+
} else {
698+
log.Info("Controller configmap is up to date.")
699+
}
700+
} else {
701+
log.Error(err, "Controller configmap not found")
702+
}
683703
return nil
684704
}
685705
log.Info("[attempt #" + strconv.Itoa(attempt) + "] Waiting 5s for the next reconcile call ...")

doc/reference/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ All notable changes to this project will be documented in this file.
77
### Added
88

99
- Enable storage (PVC) configuration and resizing for the `logjuicer` component.
10+
- CLI: add the `version` subcommand, displaying the current version of the executable.
11+
- standalone mode: the sf-standalone-owner configMap is annotated with the CLI's version that
12+
deployed the resource, and the end time of the deployment. The configMap's data
13+
is also set to hold the last applied SoftwareFactory spec.
1014

1115
### Changed
1216

doc/reference/cli/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ deployments, beyond what can be defined in a custom resource manifest.
3131
- [create auth-token](#create-auth-token)
3232
- [create client-config](#create-client-config)
3333
1. [Deploy](#deploy-sf)
34+
1. [Version](#version)
3435

3536
## Installing the CLI
3637

@@ -508,3 +509,8 @@ a Software Factory manifest as input and deploy all the required services as a o
508509
```sh
509510
sf-operator [GLOBAL FLAGS] deploy /path/to/manifest
510511
```
512+
513+
### Version
514+
515+
Return the version of the executable. If run directly without building the executable first (i.e. with `go run ./main.go`),
516+
this command requires `git` to be installed in the environment. It must also be running from the project's source directory.

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ func main() {
138138
cmd.MkInitCmd(),
139139
cmd.MkSFCmd(),
140140
cmd.MkNodepoolCmd(),
141+
cmd.MkVersionCmd(),
141142
operatorCmd,
142143
dev.MkDevCmd(),
143144
zuul.MkZuulCmd(),

playbooks/run-tests.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,21 @@
4040
# - health-check/config-update-nodepool-builder
4141
# - health-check/test-nodepool-launcher-pod
4242
# - health-check/test-volumestats-sidecar
43+
44+
tasks:
45+
# test versioning
46+
- name: Register CLI version
47+
register: cli_version
48+
ansible.builtin.shell: |
49+
set -o pipefail
50+
go run main.go version
51+
args:
52+
chdir: "{{ zuul.project.src_dir }}"
53+
54+
- name: ensure annotations are present on sf-standalone-owner
55+
ansible.builtin.shell: "kubectl -n sf get configmap sf-standalone-owner -o jsonpath='{.metadata.annotations}'"
56+
register: cm_annotations
57+
failed_when:
58+
- "'last_reconciled' not in cm_annotations.stdout"
59+
- "'version' not in cm_annotations.stdout"
60+
- "cli_version.stdout not in cm_annotations.stdout"

0 commit comments

Comments
 (0)