Skip to content

Commit d254ac8

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 8940cc2 commit d254ac8

File tree

7 files changed

+454
-52
lines changed

7 files changed

+454
-52
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,35 @@ kubectl apply -f feature-flags.yaml
134134
Within the next reconciliation loop the operator will begin creating a `<name>-network-policy` resource for each distribution.
135135
Set `enabled: false` (or remove the block) to turn the feature back off; the operator will delete the previously managed policies.
136136

137+
## Image Mapping Overrides
138+
139+
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.
140+
141+
### Configuration
142+
143+
Create or update the operator ConfigMap with an `image-overrides` key:
144+
145+
```yaml
146+
147+
image-overrides: |
148+
starter-gpu: quay.io/custom/llama-stack:starter-gpu
149+
starter: quay.io/custom/llama-stack:starter
150+
```
151+
152+
### Configuration Format
153+
154+
Use the distribution name directly as the key (e.g., `starter-gpu`, `starter`). The operator will apply these overrides automatically
155+
156+
### Example Usage
157+
158+
To update the LLS Distribution image for all `starter` distributions:
159+
160+
```bash
161+
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"}}'
162+
```
163+
164+
This will cause all LlamaStackDistribution resources using the `starter` distribution to restart with the new image.
165+
137166
## Developer Guide
138167

139168
### Prerequisites

controllers/llamastackdistribution_controller.go

Lines changed: 112 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333

3434
"github.com/go-logr/logr"
3535
"github.com/google/go-cmp/cmp"
36+
"github.com/google/go-containerregistry/pkg/name"
3637
llamav1alpha1 "github.com/llamastack/llama-stack-k8s-operator/api/v1alpha1"
3738
"github.com/llamastack/llama-stack-k8s-operator/pkg/cluster"
3839
"github.com/llamastack/llama-stack-k8s-operator/pkg/deploy"
@@ -92,6 +93,8 @@ type LlamaStackDistributionReconciler struct {
9293
Scheme *runtime.Scheme
9394
// Feature flags
9495
EnableNetworkPolicy bool
96+
// Image mapping overrides
97+
ImageMappingOverrides map[string]string
9598
// Cluster info
9699
ClusterInfo *cluster.ClusterInfo
97100
httpClient *http.Client
@@ -597,21 +600,40 @@ func (r *LlamaStackDistributionReconciler) configMapUpdatePredicate(e event.Upda
597600
return false
598601
}
599602

600-
// Parse the feature flags if the operator config ConfigMap has changed
603+
// Check if this is the operator config ConfigMap
604+
if r.handleOperatorConfigUpdate(newConfigMap) {
605+
return true
606+
}
607+
608+
// Handle referenced ConfigMap updates
609+
return r.handleReferencedConfigMapUpdate(oldConfigMap, newConfigMap)
610+
}
611+
612+
// handleOperatorConfigUpdate processes updates to the operator config ConfigMap.
613+
func (r *LlamaStackDistributionReconciler) handleOperatorConfigUpdate(configMap *corev1.ConfigMap) bool {
601614
operatorNamespace, err := deploy.GetOperatorNamespace()
602615
if err != nil {
603616
return false
604617
}
605-
if newConfigMap.Name == operatorConfigData && newConfigMap.Namespace == operatorNamespace {
606-
EnableNetworkPolicy, err := parseFeatureFlags(newConfigMap.Data)
607-
if err != nil {
608-
log.FromContext(context.Background()).Error(err, "Failed to parse feature flags")
609-
} else {
610-
r.EnableNetworkPolicy = EnableNetworkPolicy
611-
}
612-
return true
618+
619+
if configMap.Name != operatorConfigData || configMap.Namespace != operatorNamespace {
620+
return false
613621
}
614622

623+
// Update feature flags
624+
EnableNetworkPolicy, err := parseFeatureFlags(configMap.Data)
625+
if err != nil {
626+
log.FromContext(context.Background()).Error(err, "Failed to parse feature flags")
627+
} else {
628+
r.EnableNetworkPolicy = EnableNetworkPolicy
629+
}
630+
631+
r.ImageMappingOverrides = ParseImageMappingOverrides(configMap.Data)
632+
return true
633+
}
634+
635+
// handleReferencedConfigMapUpdate processes updates to referenced ConfigMaps.
636+
func (r *LlamaStackDistributionReconciler) handleReferencedConfigMapUpdate(oldConfigMap, newConfigMap *corev1.ConfigMap) bool {
615637
// Only proceed if this ConfigMap is referenced by any LlamaStackDistribution
616638
if !r.isConfigMapReferenced(newConfigMap) {
617639
return false
@@ -783,7 +805,7 @@ func (r *LlamaStackDistributionReconciler) findLlamaStackDistributionsForConfigM
783805

784806
operatorNamespace, err := deploy.GetOperatorNamespace()
785807
if err != nil {
786-
log.FromContext(context.Background()).Error(err, "Failed to get operator namespace for config map event processing")
808+
logger.Error(err, "Failed to get operator namespace for config map event processing")
787809
return nil
788810
}
789811
// If the operator config was changed, we reconcile all LlamaStackDistributions
@@ -1672,53 +1694,103 @@ func NewLlamaStackDistributionReconciler(ctx context.Context, client client.Clie
16721694
return nil, fmt.Errorf("failed to get operator namespace: %w", err)
16731695
}
16741696

1675-
// Get the ConfigMap
1676-
// If the ConfigMap doesn't exist, create it with default feature flags
1677-
// If the ConfigMap exists, parse the feature flags from the Configmap
1697+
// Initialize operator config ConfigMap
1698+
configMap, err := initializeOperatorConfigMap(ctx, client, operatorNamespace)
1699+
if err != nil {
1700+
return nil, err
1701+
}
1702+
1703+
// Parse feature flags from ConfigMap
1704+
enableNetworkPolicy, err := parseFeatureFlags(configMap.Data)
1705+
if err != nil {
1706+
return nil, fmt.Errorf("failed to parse feature flags: %w", err)
1707+
}
1708+
1709+
// Parse image mapping overrides from ConfigMap
1710+
imageMappingOverrides := ParseImageMappingOverrides(configMap.Data)
1711+
1712+
return &LlamaStackDistributionReconciler{
1713+
Client: client,
1714+
Scheme: scheme,
1715+
EnableNetworkPolicy: enableNetworkPolicy,
1716+
ImageMappingOverrides: imageMappingOverrides,
1717+
ClusterInfo: clusterInfo,
1718+
httpClient: &http.Client{Timeout: 5 * time.Second},
1719+
}, nil
1720+
}
1721+
1722+
// initializeOperatorConfigMap gets or creates the operator config ConfigMap.
1723+
func initializeOperatorConfigMap(ctx context.Context, c client.Client, operatorNamespace string) (*corev1.ConfigMap, error) {
16781724
configMap := &corev1.ConfigMap{}
16791725
configMapName := types.NamespacedName{
16801726
Name: operatorConfigData,
16811727
Namespace: operatorNamespace,
16821728
}
16831729

1684-
if err = client.Get(ctx, configMapName, configMap); err != nil {
1685-
if !k8serrors.IsNotFound(err) {
1686-
return nil, fmt.Errorf("failed to get ConfigMap: %w", err)
1687-
}
1730+
err := c.Get(ctx, configMapName, configMap)
1731+
if err == nil {
1732+
return configMap, nil
1733+
}
16881734

1689-
// ConfigMap doesn't exist, create it with defaults
1690-
configMap, err = createDefaultConfigMap(configMapName)
1691-
if err != nil {
1692-
return nil, fmt.Errorf("failed to generate default configMap: %w", err)
1735+
if !k8serrors.IsNotFound(err) {
1736+
return nil, fmt.Errorf("failed to get ConfigMap: %w", err)
1737+
}
1738+
1739+
// ConfigMap doesn't exist, create it with defaults
1740+
configMap, err = createDefaultConfigMap(configMapName)
1741+
if err != nil {
1742+
return nil, fmt.Errorf("failed to generate default configMap: %w", err)
1743+
}
1744+
1745+
if err = c.Create(ctx, configMap); err != nil {
1746+
return nil, fmt.Errorf("failed to create ConfigMap: %w", err)
1747+
}
1748+
1749+
return configMap, nil
1750+
}
1751+
1752+
func ParseImageMappingOverrides(configMapData map[string]string) map[string]string {
1753+
imageMappingOverrides := make(map[string]string)
1754+
logger := log.FromContext(context.Background())
1755+
1756+
// Look for the image-overrides key in the ConfigMap data
1757+
if overridesYAML, exists := configMapData["image-overrides"]; exists {
1758+
// Parse the YAML content
1759+
var overrides map[string]string
1760+
if err := yaml.Unmarshal([]byte(overridesYAML), &overrides); err != nil {
1761+
// Log error but continue with empty overrides
1762+
logger.V(1).Info("failed to parse image-overrides YAML", "error", err)
1763+
return imageMappingOverrides
16931764
}
16941765

1695-
if err = client.Create(ctx, configMap); err != nil {
1696-
return nil, fmt.Errorf("failed to create ConfigMap: %w", err)
1766+
// Validate and copy the parsed overrides to our result map
1767+
for version, image := range overrides {
1768+
// Validate the image reference format
1769+
if _, err := name.ParseReference(image); err != nil {
1770+
logger.V(1).Info(
1771+
"skipping invalid image override",
1772+
"version", version,
1773+
"image", image,
1774+
"error", err,
1775+
)
1776+
continue
1777+
}
1778+
imageMappingOverrides[version] = image
16971779
}
16981780
}
16991781

1700-
// Parse feature flags from ConfigMap
1701-
enableNetworkPolicy, err := parseFeatureFlags(configMap.Data)
1702-
if err != nil {
1703-
return nil, fmt.Errorf("failed to parse feature flags: %w", err)
1704-
}
1705-
return &LlamaStackDistributionReconciler{
1706-
Client: client,
1707-
Scheme: scheme,
1708-
EnableNetworkPolicy: enableNetworkPolicy,
1709-
ClusterInfo: clusterInfo,
1710-
httpClient: &http.Client{Timeout: 5 * time.Second},
1711-
}, nil
1782+
return imageMappingOverrides
17121783
}
17131784

17141785
// NewTestReconciler creates a reconciler for testing, allowing injection of a custom http client and feature flags.
17151786
func NewTestReconciler(client client.Client, scheme *runtime.Scheme, clusterInfo *cluster.ClusterInfo,
17161787
httpClient *http.Client, enableNetworkPolicy bool) *LlamaStackDistributionReconciler {
17171788
return &LlamaStackDistributionReconciler{
1718-
Client: client,
1719-
Scheme: scheme,
1720-
ClusterInfo: clusterInfo,
1721-
httpClient: httpClient,
1722-
EnableNetworkPolicy: enableNetworkPolicy,
1789+
Client: client,
1790+
Scheme: scheme,
1791+
ClusterInfo: clusterInfo,
1792+
httpClient: httpClient,
1793+
EnableNetworkPolicy: enableNetworkPolicy,
1794+
ImageMappingOverrides: make(map[string]string),
17231795
}
17241796
}

0 commit comments

Comments
 (0)