Skip to content

Commit a8b6669

Browse files
committed
feat: ConfigMap image mapping overrides for LLS Distro
Implements a mechanism for the Llama Stack Operator to read and apply LLS Distribution image updates from a ConfigMap, enabling independent patching for security fixes or bug fixes without requiring a new LLS Operator. - Add ImageMappingOverrides field to LlamaStackDistributionReconciler - Implement parseImageMappingOverrides() to read image-overrides from ConfigMap - Support symbolic name mapping (e.g., `starter`) to specific images - Included unit tests The operator now reads image overrides from the 'image-overrides' key in the operator ConfigMap, supporting YAML format with version-to-image mappings. Overrides take precedence over default distribution images and are refreshed on each reconciler initialization. Closes: RHAIENG-1079 Signed-off-by: Derek Higgins <derekh@redhat.com>
1 parent abf87e5 commit a8b6669

File tree

5 files changed

+412
-40
lines changed

5 files changed

+412
-40
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,48 @@ Example to create a run.yaml ConfigMap, and a LlamaStackDistribution that refere
104104
kubectl apply -f config/samples/example-with-configmap.yaml
105105
```
106106

107+
## Image Mapping Overrides
108+
109+
The operator supports ConfigMap-driven image updates for LLS Distribution images. This allows independent patching for security fixes or bug fixes without requiring a new operator version.
110+
111+
### Configuration
112+
113+
Create or update the operator ConfigMap with an `image-overrides` key:
114+
115+
```yaml
116+
apiVersion: v1
117+
kind: ConfigMap
118+
metadata:
119+
name: llama-stack-operator-config
120+
namespace: llama-stack-k8s-operator-system
121+
data:
122+
image-overrides: |
123+
starter-gpu: quay.io/custom/llama-stack:starter-gpu
124+
starter: quay.io/custom/llama-stack:starter
125+
```
126+
127+
### Configuration Format
128+
129+
Use the distribution name directly as the key (e.g., `starter-gpu`, `starter`). The operator will apply these overrides automatically
130+
131+
### How It Works
132+
133+
1. The operator reads image overrides from the `image-overrides` key in the operator ConfigMap
134+
2. Overrides are parsed as YAML with distribution-name-to-image mappings
135+
3. When deploying a LlamaStackDistribution, the operator checks for overrides matching the distribution name
136+
4. If an override exists, it uses the specified image instead of the default distribution image
137+
5. Changes to the ConfigMap automatically trigger reconciliation of all LlamaStackDistribution resources
138+
139+
### Example Usage
140+
141+
To update the LLS Distribution image for all `starter` distributions:
142+
143+
```bash
144+
kubectl patch configmap llama-stack-operator-config -n llama-stack-k8s-operator-system --type merge -p '{"data":{"image-overrides":"starter: quay.io/opendatahub/llama-stack:latest"}}'
145+
```
146+
147+
This will cause all LlamaStackDistribution resources using the `starter` distribution to restart with the new image.
148+
107149
## Developer Guide
108150

109151
### Prerequisites

controllers/llamastackdistribution_controller.go

Lines changed: 100 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ type LlamaStackDistributionReconciler struct {
8888
Scheme *runtime.Scheme
8989
// Feature flags
9090
EnableNetworkPolicy bool
91+
// Image mapping overrides
92+
ImageMappingOverrides map[string]string
9193
// Cluster info
9294
ClusterInfo *cluster.ClusterInfo
9395
httpClient *http.Client
@@ -536,21 +538,40 @@ func (r *LlamaStackDistributionReconciler) configMapUpdatePredicate(e event.Upda
536538
return false
537539
}
538540

539-
// Parse the feature flags if the operator config ConfigMap has changed
541+
// Check if this is the operator config ConfigMap
542+
if r.handleOperatorConfigUpdate(newConfigMap) {
543+
return true
544+
}
545+
546+
// Handle referenced ConfigMap updates
547+
return r.handleReferencedConfigMapUpdate(oldConfigMap, newConfigMap)
548+
}
549+
550+
// handleOperatorConfigUpdate processes updates to the operator config ConfigMap.
551+
func (r *LlamaStackDistributionReconciler) handleOperatorConfigUpdate(configMap *corev1.ConfigMap) bool {
540552
operatorNamespace, err := deploy.GetOperatorNamespace()
541553
if err != nil {
542554
return false
543555
}
544-
if newConfigMap.Name == operatorConfigData && newConfigMap.Namespace == operatorNamespace {
545-
EnableNetworkPolicy, err := parseFeatureFlags(newConfigMap.Data)
546-
if err != nil {
547-
log.FromContext(context.Background()).Error(err, "Failed to parse feature flags")
548-
} else {
549-
r.EnableNetworkPolicy = EnableNetworkPolicy
550-
}
551-
return true
556+
557+
if configMap.Name != operatorConfigData || configMap.Namespace != operatorNamespace {
558+
return false
559+
}
560+
561+
// Update feature flags
562+
EnableNetworkPolicy, err := parseFeatureFlags(configMap.Data)
563+
if err != nil {
564+
log.FromContext(context.Background()).Error(err, "Failed to parse feature flags")
565+
} else {
566+
r.EnableNetworkPolicy = EnableNetworkPolicy
552567
}
553568

569+
r.ImageMappingOverrides = ParseImageMappingOverrides(configMap.Data)
570+
return true
571+
}
572+
573+
// handleReferencedConfigMapUpdate processes updates to referenced ConfigMaps.
574+
func (r *LlamaStackDistributionReconciler) handleReferencedConfigMapUpdate(oldConfigMap, newConfigMap *corev1.ConfigMap) bool {
554575
// Only proceed if this ConfigMap is referenced by any LlamaStackDistribution
555576
if !r.isConfigMapReferenced(newConfigMap) {
556577
return false
@@ -722,7 +743,7 @@ func (r *LlamaStackDistributionReconciler) findLlamaStackDistributionsForConfigM
722743

723744
operatorNamespace, err := deploy.GetOperatorNamespace()
724745
if err != nil {
725-
log.FromContext(context.Background()).Error(err, "Failed to get operator namespace for config map event processing")
746+
logger.Error(err, "Failed to get operator namespace for config map event processing")
726747
return nil
727748
}
728749
// If the operator config was changed, we reconcile all LlamaStackDistributions
@@ -1360,53 +1381,92 @@ func NewLlamaStackDistributionReconciler(ctx context.Context, client client.Clie
13601381
return nil, fmt.Errorf("failed to get operator namespace: %w", err)
13611382
}
13621383

1363-
// Get the ConfigMap
1364-
// If the ConfigMap doesn't exist, create it with default feature flags
1365-
// If the ConfigMap exists, parse the feature flags from the Configmap
1384+
// Initialize operator config ConfigMap
1385+
configMap, err := initializeOperatorConfigMap(ctx, client, operatorNamespace)
1386+
if err != nil {
1387+
return nil, err
1388+
}
1389+
1390+
// Parse feature flags from ConfigMap
1391+
enableNetworkPolicy, err := parseFeatureFlags(configMap.Data)
1392+
if err != nil {
1393+
return nil, fmt.Errorf("failed to parse feature flags: %w", err)
1394+
}
1395+
1396+
// Parse image mapping overrides from ConfigMap
1397+
imageMappingOverrides := ParseImageMappingOverrides(configMap.Data)
1398+
1399+
return &LlamaStackDistributionReconciler{
1400+
Client: client,
1401+
Scheme: scheme,
1402+
EnableNetworkPolicy: enableNetworkPolicy,
1403+
ImageMappingOverrides: imageMappingOverrides,
1404+
ClusterInfo: clusterInfo,
1405+
httpClient: &http.Client{Timeout: 5 * time.Second},
1406+
}, nil
1407+
}
1408+
1409+
// initializeOperatorConfigMap gets or creates the operator config ConfigMap.
1410+
func initializeOperatorConfigMap(ctx context.Context, c client.Client, operatorNamespace string) (*corev1.ConfigMap, error) {
13661411
configMap := &corev1.ConfigMap{}
13671412
configMapName := types.NamespacedName{
13681413
Name: operatorConfigData,
13691414
Namespace: operatorNamespace,
13701415
}
13711416

1372-
if err = client.Get(ctx, configMapName, configMap); err != nil {
1373-
if !k8serrors.IsNotFound(err) {
1374-
return nil, fmt.Errorf("failed to get ConfigMap: %w", err)
1375-
}
1417+
err := c.Get(ctx, configMapName, configMap)
1418+
if err == nil {
1419+
return configMap, nil
1420+
}
13761421

1377-
// ConfigMap doesn't exist, create it with defaults
1378-
configMap, err = createDefaultConfigMap(configMapName)
1379-
if err != nil {
1380-
return nil, fmt.Errorf("failed to generate default configMap: %w", err)
1422+
if !k8serrors.IsNotFound(err) {
1423+
return nil, fmt.Errorf("failed to get ConfigMap: %w", err)
1424+
}
1425+
1426+
// ConfigMap doesn't exist, create it with defaults
1427+
configMap, err = createDefaultConfigMap(configMapName)
1428+
if err != nil {
1429+
return nil, fmt.Errorf("failed to generate default configMap: %w", err)
1430+
}
1431+
1432+
if err = c.Create(ctx, configMap); err != nil {
1433+
return nil, fmt.Errorf("failed to create ConfigMap: %w", err)
1434+
}
1435+
1436+
return configMap, nil
1437+
}
1438+
1439+
func ParseImageMappingOverrides(configMapData map[string]string) map[string]string {
1440+
imageMappingOverrides := make(map[string]string)
1441+
1442+
// Look for the image-overrides key in the ConfigMap data
1443+
if overridesYAML, exists := configMapData["image-overrides"]; exists {
1444+
// Parse the YAML content
1445+
var overrides map[string]string
1446+
if err := yaml.Unmarshal([]byte(overridesYAML), &overrides); err != nil {
1447+
// Log error but continue with empty overrides
1448+
fmt.Printf("failed to parse image-overrides YAML: %v\n", err)
1449+
return imageMappingOverrides
13811450
}
13821451

1383-
if err = client.Create(ctx, configMap); err != nil {
1384-
return nil, fmt.Errorf("failed to create ConfigMap: %w", err)
1452+
// Copy the parsed overrides to our result map
1453+
for version, image := range overrides {
1454+
imageMappingOverrides[version] = image
13851455
}
13861456
}
13871457

1388-
// Parse feature flags from ConfigMap
1389-
enableNetworkPolicy, err := parseFeatureFlags(configMap.Data)
1390-
if err != nil {
1391-
return nil, fmt.Errorf("failed to parse feature flags: %w", err)
1392-
}
1393-
return &LlamaStackDistributionReconciler{
1394-
Client: client,
1395-
Scheme: scheme,
1396-
EnableNetworkPolicy: enableNetworkPolicy,
1397-
ClusterInfo: clusterInfo,
1398-
httpClient: &http.Client{Timeout: 5 * time.Second},
1399-
}, nil
1458+
return imageMappingOverrides
14001459
}
14011460

14021461
// NewTestReconciler creates a reconciler for testing, allowing injection of a custom http client and feature flags.
14031462
func NewTestReconciler(client client.Client, scheme *runtime.Scheme, clusterInfo *cluster.ClusterInfo,
14041463
httpClient *http.Client, enableNetworkPolicy bool) *LlamaStackDistributionReconciler {
14051464
return &LlamaStackDistributionReconciler{
1406-
Client: client,
1407-
Scheme: scheme,
1408-
ClusterInfo: clusterInfo,
1409-
httpClient: httpClient,
1410-
EnableNetworkPolicy: enableNetworkPolicy,
1465+
Client: client,
1466+
Scheme: scheme,
1467+
ClusterInfo: clusterInfo,
1468+
httpClient: httpClient,
1469+
EnableNetworkPolicy: enableNetworkPolicy,
1470+
ImageMappingOverrides: make(map[string]string),
14111471
}
14121472
}

0 commit comments

Comments
 (0)