Skip to content

Commit 9e4c172

Browse files
committed
Merge remote-tracking branch 'origin/ai-factory' into inject-nvidia-auth
# Conflicts: # charts/aif-operator/values.yaml # pkg/aif-ui/pages/components/AppWizard.vue # pkg/aif-ui/pages/components/BlueprintInstallWizard.vue # pkg/aif-ui/services/fleet-bundle.ts # pkg/aif-ui/types/blueprint-types.ts
2 parents 8e1c5a3 + 9e7cdba commit 9e4c172

113 files changed

Lines changed: 5133 additions & 1871 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

aif-operator/Dockerfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
FROM --platform=$BUILDPLATFORM registry.suse.com/bci/golang:1.25 as builder
1+
FROM --platform=$BUILDPLATFORM registry.suse.com/bci/golang:1.25 AS builder
22
ARG TARGETOS
33
ARG TARGETARCH
4+
ARG VERSION=unknown
5+
ARG COMMIT=unknown
46

57
WORKDIR /workspace
68
COPY go.mod go.sum ./
@@ -10,7 +12,7 @@ COPY cmd/ cmd/
1012
COPY api/ api/
1113
COPY internal/ internal/
1214

13-
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -a -o manager cmd/main.go
15+
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -a -ldflags "-X main.version=${VERSION} -X main.commit=${COMMIT}" -o manager cmd/main.go
1416

1517
FROM gcr.io/distroless/static:nonroot
1618
WORKDIR /

aif-operator/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ lint-config: golangci-lint ## Verify golangci-lint linter configuration
107107

108108
.PHONY: build
109109
build: manifests generate fmt vet ## Build manager binary.
110-
go build -o bin/manager cmd/main.go
110+
go build -ldflags "-X main.version=$(shell cat VERSION 2>/dev/null || echo unknown) -X main.commit=$(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)" -o bin/manager cmd/main.go
111111

112112
.PHONY: run
113113
run: manifests generate fmt vet ## Run a controller from your host.

aif-operator/cmd/main.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ import (
5050
// +kubebuilder:scaffold:imports
5151
)
5252

53+
// version and commit are set at build time via -ldflags.
54+
var (
55+
version = "unknown"
56+
commit = "unknown"
57+
)
58+
5359
var (
5460
scheme = runtime.NewScheme()
5561
setupLog = ctrl.Log.WithName("setup")
@@ -184,6 +190,13 @@ func main() {
184190
// operatorNamespace secrets; aiworkload controller needs Helm
185191
// release secrets (owner=helm) from any target namespace.
186192
&corev1.Secret{}: {},
193+
// Restrict ConfigMap watch to the extension namespace — the namespaced
194+
// Role in cattle-ui-plugin-system grants watch; the ClusterRole does not.
195+
&corev1.ConfigMap{}: {
196+
Namespaces: map[string]cache.Config{
197+
config.GetExtensionNamespace(): {},
198+
},
199+
},
187200
},
188201
},
189202
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
@@ -245,6 +258,7 @@ func main() {
245258
api.NewSettingsHandler(mgr.GetClient(), operatorNamespace).Register(mux)
246259
api.NewAIWorkloadHandler(mgr.GetClient()).Register(mux)
247260
api.NewBlueprintHandler(mgr.GetClient()).Register(mux)
261+
api.NewVersionHandler(version, commit, os.Getenv("CHART_VERSION")).Register(mux)
248262
srv := &http.Server{Addr: apiBindAddr, Handler: api.Chain(mux)}
249263

250264
ctx := ctrl.SetupSignalHandler()
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package api
18+
19+
import "net/http"
20+
21+
// VersionHandler serves GET /api/v1/version.
22+
type VersionHandler struct {
23+
version string
24+
commit string
25+
chartVersion string
26+
}
27+
28+
// NewVersionHandler constructs a VersionHandler.
29+
func NewVersionHandler(version, commit, chartVersion string) *VersionHandler {
30+
return &VersionHandler{version: version, commit: commit, chartVersion: chartVersion}
31+
}
32+
33+
// Register wires the handler's route onto the mux.
34+
func (h *VersionHandler) Register(mux *http.ServeMux) {
35+
mux.HandleFunc("GET /api/v1/version", h.getVersion)
36+
}
37+
38+
func (h *VersionHandler) getVersion(w http.ResponseWriter, _ *http.Request) {
39+
writeJSON(w, http.StatusOK, map[string]string{
40+
"version": h.version,
41+
"commit": h.commit,
42+
"chartVersion": h.chartVersion,
43+
})
44+
}
45+
46+
var _ Handler = (*VersionHandler)(nil)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package api
18+
19+
import (
20+
"encoding/json"
21+
"net/http"
22+
"net/http/httptest"
23+
"testing"
24+
)
25+
26+
func TestGetVersion(t *testing.T) {
27+
mux := http.NewServeMux()
28+
NewVersionHandler("1.2.3", "abc1234", "0.1.0-dev.3").Register(mux)
29+
30+
req := httptest.NewRequest("GET", "/api/v1/version", nil)
31+
w := httptest.NewRecorder()
32+
mux.ServeHTTP(w, req)
33+
34+
if w.Code != http.StatusOK {
35+
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
36+
}
37+
38+
var body map[string]string
39+
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
40+
t.Fatalf("failed to decode response: %v", err)
41+
}
42+
if body["version"] != "1.2.3" {
43+
t.Errorf("expected version %q, got %q", "1.2.3", body["version"])
44+
}
45+
if body["commit"] != "abc1234" {
46+
t.Errorf("expected commit %q, got %q", "abc1234", body["commit"])
47+
}
48+
if body["chartVersion"] != "0.1.0-dev.3" {
49+
t.Errorf("expected chartVersion %q, got %q", "0.1.0-dev.3", body["chartVersion"])
50+
}
51+
}

aif-operator/internal/config/runtime.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,12 @@ func GetOperatorNamespace() string {
1919
}
2020
return DefaultOperatorNamespace
2121
}
22+
23+
const DefaultOperatorService = "aif-operator"
24+
25+
func GetOperatorService() string {
26+
if svc := os.Getenv("OPERATOR_SERVICE"); svc != "" {
27+
return svc
28+
}
29+
return DefaultOperatorService
30+
}

aif-operator/internal/controller/installaiextension/installaiextension_controller.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ import (
1111
urlpkg "net/url"
1212

1313
"helm.sh/helm/v3/pkg/cli"
14+
corev1 "k8s.io/api/core/v1"
1415
"k8s.io/apimachinery/pkg/api/meta"
1516
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1617
"k8s.io/apimachinery/pkg/runtime"
1718
ctrl "sigs.k8s.io/controller-runtime"
1819
"sigs.k8s.io/controller-runtime/pkg/client"
20+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1921
"sigs.k8s.io/controller-runtime/pkg/log"
2022

2123
v1alpha1 "github.com/SUSE/aif-operator/api/v1alpha1"
24+
"github.com/SUSE/aif-operator/internal/config"
2225
helmClient "github.com/SUSE/aif-operator/internal/infra/helm"
2326
"github.com/SUSE/aif-operator/internal/infra/kubernetes"
2427
"github.com/SUSE/aif-operator/internal/infra/rancher"
@@ -28,6 +31,7 @@ import (
2831
const (
2932
defaultReadinessTimeout = 5 * time.Minute
3033
readinessRequeue = 10 * time.Second
34+
uiConfigMapName = "aif-ui-config"
3135
healthCheckInterval = 60 * time.Second
3236

3337
conditionTypeReady = "Ready"
@@ -143,10 +147,42 @@ func (r *InstallAIExtensionReconciler) reconcile(ctx context.Context, ext *v1alp
143147
ext.Status.ActiveExtensionName = ext.Spec.Extension.Name
144148
ext.Status.ActiveSourceKind = ext.Spec.Source.Kind
145149

150+
if err := r.syncUIConfigMap(ctx); err != nil {
151+
logger.Error(err, "failed to sync operator coordinates to UI ConfigMap")
152+
return ctrl.Result{Requeue: true}, nil
153+
}
154+
146155
logger.Info("reconciled successfully")
147156
return ctrl.Result{RequeueAfter: healthCheckInterval}, nil
148157
}
149158

159+
// syncUIConfigMap writes the operator namespace and service name into the
160+
// aif-ui-config ConfigMap so the UI extension can reach the operator without
161+
// manual configuration. It runs on every successful reconcile loop, giving
162+
// self-healing behaviour if the ConfigMap is deleted or corrupted.
163+
// The ConfigMap is intentionally not deleted when the CR is removed — the UI
164+
// retains the last-known operator coordinates so it remains functional.
165+
func (r *InstallAIExtensionReconciler) syncUIConfigMap(ctx context.Context) error {
166+
logger := log.FromContext(ctx)
167+
ns, svc := config.GetOperatorNamespace(), config.GetOperatorService()
168+
logger.V(1).Info("syncing UI ConfigMap", "operatorNamespace", ns, "operatorService", svc)
169+
cm := &corev1.ConfigMap{
170+
ObjectMeta: metav1.ObjectMeta{
171+
Name: uiConfigMapName,
172+
Namespace: r.ExtensionNamespace,
173+
},
174+
}
175+
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, cm, func() error {
176+
if cm.Data == nil {
177+
cm.Data = make(map[string]string)
178+
}
179+
cm.Data["operatorNamespace"] = ns
180+
cm.Data["operatorService"] = svc
181+
return nil
182+
})
183+
return err
184+
}
185+
150186
func (r *InstallAIExtensionReconciler) reconcileHelmSource(
151187
ctx context.Context,
152188
ext *v1alpha1.InstallAIExtension,

0 commit comments

Comments
 (0)