From d577f218249c6fb1fa67ab2dd34fe4243ad47d48 Mon Sep 17 00:00:00 2001 From: ian-flores Date: Mon, 9 Feb 2026 13:40:46 -0800 Subject: [PATCH 1/5] feat: add passthrough config mechanism for Connect, Package Manager, and Workbench MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add escape-hatch passthrough fields to all three product configs so users can set arbitrary config values without waiting for operator releases. Connect & Package Manager: - Add `additional` map[string]string on config structs - Keys use "Section.Key" format (e.g., "Server.DataDir") - Passthrough values override typed fields when both are set - Rewrite GenerateGcfg() to use intermediate representation for merge Workbench: - Add `additionalConfigs` map[string]string on WorkbenchIniConfig and WorkbenchSessionIniConfig - Keys are config file names (e.g., "rserver.conf") - Raw content appended after generated config per file Site CRD propagation added for all three products. Also fixes typo: WorkbenchLauncherKubnernetesResourcesConfigSection → WorkbenchLauncherKubernetesResourcesConfigSection --- api/core/v1beta1/connect_config.go | 114 +++++++++-- api/core/v1beta1/connect_config_test.go | 182 +++++++++++++++++ api/core/v1beta1/package_manager_config.go | 92 ++++++++- .../v1beta1/package_manager_config_test.go | 82 ++++++++ api/core/v1beta1/site_types.go | 24 ++- api/core/v1beta1/workbench_config.go | 67 ++++++- api/core/v1beta1/workbench_config_test.go | 189 ++++++++++++++---- api/core/v1beta1/zz_generated.deepcopy.go | 76 ++++++- .../core/v1beta1/connectconfig.go | 15 ++ .../core/v1beta1/connectruntimeimagespec.go | 62 ++++++ .../core/v1beta1/internalconnectspec.go | 15 ++ .../v1beta1/internalpackagemanagerspec.go | 39 ++-- .../internalworkbenchexperimentalfeatures.go | 44 ++-- .../core/v1beta1/internalworkbenchspec.go | 30 +++ .../core/v1beta1/packagemanagerconfig.go | 37 +++- .../core/v1beta1/workbenchconfig.go | 19 +- .../core/v1beta1/workbenchiniconfig.go | 37 +++- ...uncherkubernetesresourcesconfigsection.go} | 26 +-- .../core/v1beta1/workbenchsessioniniconfig.go | 23 ++- client-go/applyconfiguration/utils.go | 4 +- .../crd/bases/core.posit.team_connects.yaml | 8 + .../core.posit.team_packagemanagers.yaml | 8 + config/crd/bases/core.posit.team_sites.yaml | 30 +++ .../bases/core.posit.team_workbenches.yaml | 16 ++ .../core/site_controller_connect.go | 5 + .../core/site_controller_package_manager.go | 6 + .../core/site_controller_workbench.go | 16 +- 27 files changed, 1113 insertions(+), 153 deletions(-) create mode 100644 client-go/applyconfiguration/core/v1beta1/connectruntimeimagespec.go rename client-go/applyconfiguration/core/v1beta1/{workbenchlauncherkubnernetesresourcesconfigsection.go => workbenchlauncherkubernetesresourcesconfigsection.go} (61%) diff --git a/api/core/v1beta1/connect_config.go b/api/core/v1beta1/connect_config.go index ab94868f..35636c63 100644 --- a/api/core/v1beta1/connect_config.go +++ b/api/core/v1beta1/connect_config.go @@ -31,6 +31,12 @@ type ConnectConfig struct { // see the GenerateGcfg method for our custom handling RPackageRepository map[string]RPackageRepositoryConfig `json:"RPackageRepositories,omitempty"` TableauIntegration *ConnectTableauIntegrationConfig `json:"TableauIntegration,omitempty"` + + // Additional allows setting arbitrary gcfg config values not covered by typed fields. + // Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). + // Values set here take precedence over typed fields if both specify the same key. + // +optional + Additional map[string]string `json:"additional,omitempty"` } type RPackageRepositoryConfig struct { @@ -204,6 +210,17 @@ func (configStruct *ConnectConfig) GenerateGcfg() (string, error) { var builder strings.Builder + // Build an intermediate representation: ordered sections with key-value pairs. + // We use ordered slices to preserve the deterministic output order from reflection. + type sectionEntry struct { + name string + keys []string // ordered key names (for non-slice values) + values map[string]string // key → value + slices map[string][]string // key → multiple values (for gcfg multi-value keys) + } + sections := []sectionEntry{} + sectionIndex := map[string]int{} // section name → index in sections slice + configStructValsPtr := reflect.ValueOf(configStruct) configStructVals := reflect.Indirect(configStructValsPtr) @@ -211,28 +228,43 @@ func (configStruct *ConnectConfig) GenerateGcfg() (string, error) { fieldName := configStructVals.Type().Field(i).Name fieldValue := configStructVals.Field(i) + // Skip the Additional map — we handle it after typed fields + if fieldName == "Additional" { + continue + } + if fieldValue.IsNil() { continue } sectionStructVals := reflect.Indirect(fieldValue) - // This is to handle the case of the RPackageRepositories + // Handle the RPackageRepositories map (named sections) if fieldValue.Kind() == reflect.Map { iter := sectionStructVals.MapRange() - for iter.Next() { repoName := iter.Key() repoValue := iter.Value() - - builder.WriteString("\n[" + fieldName + " \"" + fmt.Sprintf("%v", repoName) + "\"" + "]\n") - + qualifiedName := fieldName + " \"" + fmt.Sprintf("%v", repoName) + "\"" + entry := sectionEntry{ + name: qualifiedName, + values: map[string]string{}, + slices: map[string][]string{}, + } if repoValue.Kind() == reflect.Struct { - builder.WriteString("Url = " + fmt.Sprintf("%v", repoValue.FieldByName("Url")) + "\n") + url := fmt.Sprintf("%v", repoValue.FieldByName("Url")) + entry.keys = append(entry.keys, "Url") + entry.values["Url"] = url } + sections = append(sections, entry) + // Named sections are not indexed for override — passthrough uses "Section.Key" format } } else { - builder.WriteString("\n[" + fieldName + "]\n") + entry := sectionEntry{ + name: fieldName, + values: map[string]string{}, + slices: map[string][]string{}, + } for j := 0; j < sectionStructVals.NumField(); j++ { sectionFieldName := sectionStructVals.Type().Field(j).Name @@ -242,29 +274,85 @@ func (configStruct *ConnectConfig) GenerateGcfg() (string, error) { if sectionFieldValue.Kind() == reflect.Ptr { if !sectionFieldValue.IsNil() { derefValue := sectionFieldValue.Elem() - // Always write pointer fields when they're not nil, even if empty string - builder.WriteString(fmt.Sprintf("%v", sectionFieldName) + " = " + fmt.Sprintf("%v", derefValue) + "\n") + entry.keys = append(entry.keys, sectionFieldName) + entry.values[sectionFieldName] = fmt.Sprintf("%v", derefValue) } - // Skip nil pointers entirely continue } if sectionStructVals.Field(j).String() != "" { if sectionFieldValue.Kind() == reflect.Slice { + var vals []string for k := 0; k < sectionFieldValue.Len(); k++ { arrayValue := sectionFieldValue.Index(k).String() if arrayValue != "" { - builder.WriteString(fmt.Sprintf("%v", sectionFieldName) + " = " + fmt.Sprintf("%v", arrayValue) + "\n") + vals = append(vals, arrayValue) } } - + if len(vals) > 0 { + entry.keys = append(entry.keys, sectionFieldName) + entry.slices[sectionFieldName] = vals + } } else { - builder.WriteString(fmt.Sprintf("%v", sectionFieldName) + " = " + fmt.Sprintf("%v", sectionFieldValue) + "\n") + entry.keys = append(entry.keys, sectionFieldName) + entry.values[sectionFieldName] = fmt.Sprintf("%v", sectionFieldValue) } } } + + sectionIndex[fieldName] = len(sections) + sections = append(sections, entry) } + } + // Apply Additional (passthrough) overrides + if configStruct.Additional != nil { + for key, value := range configStruct.Additional { + parts := strings.SplitN(key, ".", 2) + if len(parts) != 2 { + continue // skip malformed keys + } + sectionName := parts[0] + keyName := parts[1] + + if idx, ok := sectionIndex[sectionName]; ok { + // Override or add to existing section + if _, exists := sections[idx].values[keyName]; !exists { + // Check if it's overriding a slice key + if _, sliceExists := sections[idx].slices[keyName]; !sliceExists { + sections[idx].keys = append(sections[idx].keys, keyName) + } + } + // Remove from slices if it was a multi-value key (passthrough replaces it) + delete(sections[idx].slices, keyName) + sections[idx].values[keyName] = value + } else { + // Create new section + entry := sectionEntry{ + name: sectionName, + keys: []string{keyName}, + values: map[string]string{keyName: value}, + slices: map[string][]string{}, + } + sectionIndex[sectionName] = len(sections) + sections = append(sections, entry) + } + } } + + // Render sections to gcfg format + for _, section := range sections { + builder.WriteString("\n[" + section.name + "]\n") + for _, key := range section.keys { + if vals, isSlice := section.slices[key]; isSlice { + for _, v := range vals { + builder.WriteString(key + " = " + v + "\n") + } + } else if val, ok := section.values[key]; ok { + builder.WriteString(key + " = " + val + "\n") + } + } + } + return builder.String(), nil } diff --git a/api/core/v1beta1/connect_config_test.go b/api/core/v1beta1/connect_config_test.go index 70882641..3e68e81d 100644 --- a/api/core/v1beta1/connect_config_test.go +++ b/api/core/v1beta1/connect_config_test.go @@ -210,3 +210,185 @@ func TestConnectConfig_CustomScope(t *testing.T) { require.Contains(t, str, "OpenIDConnectIssuer = https://example.com") require.NotContains(t, str, "CustomScope") } + +func TestConnectConfig_AdditionalPassthrough(t *testing.T) { + // Test passthrough-only values (new section and key via Additional) + cfg := ConnectConfig{ + Additional: map[string]string{ + "NewSection.NewKey": "custom-value", + "Server.CustomServerField": "server-custom", + "Database.Timeout": "30", + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + t.Logf("Generated gcfg with passthrough-only:\n%s", str) + + // Check that passthrough values are present + require.Contains(t, str, "[NewSection]") + require.Contains(t, str, "NewKey = custom-value") + require.Contains(t, str, "[Server]") + require.Contains(t, str, "CustomServerField = server-custom") + require.Contains(t, str, "[Database]") + require.Contains(t, str, "Timeout = 30") +} + +func TestConnectConfig_AdditionalOverride(t *testing.T) { + // Test override behavior (typed field + same key in Additional, passthrough wins) + cfg := ConnectConfig{ + Server: &ConnectServerConfig{ + Address: "typed-address.com", + HideEmailAddresses: false, + DefaultContentListView: ContentListViewExpanded, + }, + Additional: map[string]string{ + "Server.Address": "passthrough-address.com", // Should override typed + "Server.HideEmailAddresses": "true", // Should override typed + "Server.DefaultContentListView": "card", // Should override typed + "Server.CustomField": "custom-value", // New field + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + t.Logf("Generated gcfg with overrides:\n%s", str) + + // Check that passthrough values override typed values + require.Contains(t, str, "[Server]") + require.Contains(t, str, "Address = passthrough-address.com") + require.Contains(t, str, "HideEmailAddresses = true") + require.Contains(t, str, "DefaultContentListView = card") + require.Contains(t, str, "CustomField = custom-value") + + // Original typed values should not be present + require.NotContains(t, str, "Address = typed-address.com") + require.NotContains(t, str, "HideEmailAddresses = false") + require.NotContains(t, str, "DefaultContentListView = expanded") +} + +func TestConnectConfig_AdditionalEmpty(t *testing.T) { + // Test empty Additional map (no effect on output) + cfg := ConnectConfig{ + Server: &ConnectServerConfig{ + Address: "some-address.com", + }, + Additional: map[string]string{}, // Empty map + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + t.Logf("Generated gcfg with empty Additional:\n%s", str) + + // Should contain normal typed fields + require.Contains(t, str, "[Server]") + require.Contains(t, str, "Address = some-address.com") + + // Should not have any extra sections or fields + require.Equal(t, 1, countOccurrences(str, "[Server]")) +} + +func TestConnectConfig_AdditionalNil(t *testing.T) { + // Test nil Additional map (no effect on output) + cfg := ConnectConfig{ + Server: &ConnectServerConfig{ + Address: "some-address.com", + }, + Additional: nil, // Nil map + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + + // Should contain normal typed fields + require.Contains(t, str, "[Server]") + require.Contains(t, str, "Address = some-address.com") +} + +func TestConnectConfig_AdditionalMalformedKey(t *testing.T) { + // Test malformed key in Additional (no "." separator — should be skipped) + cfg := ConnectConfig{ + Additional: map[string]string{ + "MalformedKey": "should-be-skipped", // No section separator + "Server.ValidKey": "should-be-included", + "AnotherBadKey": "also-skipped", + "Good.Section.Key": "multi-dot-ok", // Multiple dots are OK (first is section separator) + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + t.Logf("Generated gcfg with malformed keys:\n%s", str) + + // Valid keys should be present + require.Contains(t, str, "[Server]") + require.Contains(t, str, "ValidKey = should-be-included") + require.Contains(t, str, "[Good]") + require.Contains(t, str, "Section.Key = multi-dot-ok") + + // Malformed keys should be skipped + require.NotContains(t, str, "MalformedKey") + require.NotContains(t, str, "should-be-skipped") + require.NotContains(t, str, "AnotherBadKey") + require.NotContains(t, str, "also-skipped") +} + +func TestConnectConfig_AdditionalComplexScenario(t *testing.T) { + // Test complex scenario with multiple sections, overrides, and new fields + cfg := ConnectConfig{ + Server: &ConnectServerConfig{ + Address: "original.com", + }, + Http: &ConnectHttpConfig{ + Listen: ":3939", + }, + Applications: &ConnectApplicationsConfig{ + ScheduleConcurrency: 2, + }, + Additional: map[string]string{ + // Override existing fields + "Server.Address": "override.com", + "Http.Listen": ":8080", + "Applications.ScheduleConcurrency": "10", + + // Add new fields to existing sections + "Server.NewServerField": "server-new", + "Http.Timeout": "60", + + // Add entirely new sections + "CustomSection.Field1": "value1", + "CustomSection.Field2": "value2", + "AnotherSection.Setting": "some-setting", + }, + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + t.Logf("Generated complex gcfg:\n%s", str) + + // Check overrides + require.Contains(t, str, "Address = override.com") + require.Contains(t, str, "Listen = :8080") + require.Contains(t, str, "ScheduleConcurrency = 10") + + // Check new fields in existing sections + require.Contains(t, str, "NewServerField = server-new") + require.Contains(t, str, "Timeout = 60") + + // Check new sections + require.Contains(t, str, "[CustomSection]") + require.Contains(t, str, "Field1 = value1") + require.Contains(t, str, "Field2 = value2") + require.Contains(t, str, "[AnotherSection]") + require.Contains(t, str, "Setting = some-setting") + + // Original values should not be present (they were overridden) + require.NotContains(t, str, "Address = original.com") + require.NotContains(t, str, "Listen = :3939") + require.NotContains(t, str, "ScheduleConcurrency = 2") +} + +// Helper function to count occurrences of a substring +func countOccurrences(str, substr string) int { + count := 0 + for i := 0; i+len(substr) <= len(str); i++ { + if str[i:i+len(substr)] == substr { + count++ + } + } + return count +} diff --git a/api/core/v1beta1/package_manager_config.go b/api/core/v1beta1/package_manager_config.go index 92b7d623..026bfc3b 100644 --- a/api/core/v1beta1/package_manager_config.go +++ b/api/core/v1beta1/package_manager_config.go @@ -18,12 +18,29 @@ type PackageManagerConfig struct { Repos *PackageManagerReposConfig `json:"Repos,omitempty"` Cran *PackageManagerCRANConfig `json:"CRAN,omitempty"` Debug *PackageManagerDebugConfig `json:"Debug,omitempty"` + + // Additional allows setting arbitrary gcfg config values not covered by typed fields. + // Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). + // Values set here take precedence over typed fields if both specify the same key. + // +optional + Additional map[string]string `json:"additional,omitempty"` } func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { var builder strings.Builder + // Build an intermediate representation: ordered sections with key-value pairs. + // We use ordered slices to preserve the deterministic output order from reflection. + type sectionEntry struct { + name string + keys []string // ordered key names (for non-slice values) + values map[string]string // key → value + slices map[string][]string // key → multiple values (for gcfg multi-value keys) + } + sections := []sectionEntry{} + sectionIndex := map[string]int{} // section name → index in sections slice + configStructValsPtr := reflect.ValueOf(configStruct) configStructVals := reflect.Indirect(configStructValsPtr) @@ -31,11 +48,20 @@ func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { fieldName := configStructVals.Type().Field(i).Name fieldValue := configStructVals.Field(i) + // Skip the Additional map — we handle it after typed fields + if fieldName == "Additional" { + continue + } + if fieldValue.IsNil() { continue } - builder.WriteString("\n[" + fieldName + "]\n") + entry := sectionEntry{ + name: fieldName, + values: map[string]string{}, + slices: map[string][]string{}, + } sectionStructVals := reflect.Indirect(fieldValue) @@ -45,19 +71,77 @@ func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { if sectionStructVals.Field(j).String() != "" { if sectionFieldValue.Kind() == reflect.Slice { + var vals []string for k := 0; k < sectionFieldValue.Len(); k++ { arrayValue := sectionFieldValue.Index(k).String() if arrayValue != "" { - builder.WriteString(fmt.Sprintf("%v", sectionFieldName) + " = " + fmt.Sprintf("%v", arrayValue) + "\n") + vals = append(vals, arrayValue) } } - + if len(vals) > 0 { + entry.keys = append(entry.keys, sectionFieldName) + entry.slices[sectionFieldName] = vals + } } else { - builder.WriteString(fmt.Sprintf("%v", sectionFieldName) + " = " + fmt.Sprintf("%v", sectionFieldValue) + "\n") + entry.keys = append(entry.keys, sectionFieldName) + entry.values[sectionFieldName] = fmt.Sprintf("%v", sectionFieldValue) } } } + + sectionIndex[fieldName] = len(sections) + sections = append(sections, entry) } + + // Apply Additional (passthrough) overrides + if configStruct.Additional != nil { + for key, value := range configStruct.Additional { + parts := strings.SplitN(key, ".", 2) + if len(parts) != 2 { + continue // skip malformed keys + } + sectionName := parts[0] + keyName := parts[1] + + if idx, ok := sectionIndex[sectionName]; ok { + // Override or add to existing section + if _, exists := sections[idx].values[keyName]; !exists { + // Check if it's overriding a slice key + if _, sliceExists := sections[idx].slices[keyName]; !sliceExists { + sections[idx].keys = append(sections[idx].keys, keyName) + } + } + // Remove from slices if it was a multi-value key (passthrough replaces it) + delete(sections[idx].slices, keyName) + sections[idx].values[keyName] = value + } else { + // Create new section + entry := sectionEntry{ + name: sectionName, + keys: []string{keyName}, + values: map[string]string{keyName: value}, + slices: map[string][]string{}, + } + sectionIndex[sectionName] = len(sections) + sections = append(sections, entry) + } + } + } + + // Render sections to gcfg format + for _, section := range sections { + builder.WriteString("\n[" + section.name + "]\n") + for _, key := range section.keys { + if vals, isSlice := section.slices[key]; isSlice { + for _, v := range vals { + builder.WriteString(key + " = " + v + "\n") + } + } else if val, ok := section.values[key]; ok { + builder.WriteString(key + " = " + val + "\n") + } + } + } + return builder.String(), nil } diff --git a/api/core/v1beta1/package_manager_config_test.go b/api/core/v1beta1/package_manager_config_test.go index 363fed3e..9baae20a 100644 --- a/api/core/v1beta1/package_manager_config_test.go +++ b/api/core/v1beta1/package_manager_config_test.go @@ -34,3 +34,85 @@ func TestPackageManagerConfig_GenerateGcfg(t *testing.T) { require.Contains(t, str, "/some/path") require.Contains(t, str, "/another/path") } + +func TestPackageManagerConfig_GenerateGcfg_WithAdditional(t *testing.T) { + // Test passthrough-only: Additional sets a value in a new section + pmCfg := PackageManagerConfig{ + Additional: map[string]string{ + "NewSection.SomeKey": "some-value", + }, + } + str, err := pmCfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "[NewSection]") + require.Contains(t, str, "SomeKey = some-value") + + // Test passthrough override: typed field + Additional for same key + pmCfg = PackageManagerConfig{ + Server: &PackageManagerServerConfig{ + LauncherDir: "/typed/path", + }, + Additional: map[string]string{ + "Server.LauncherDir": "/passthrough/path", + }, + } + str, err = pmCfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "LauncherDir = /passthrough/path") + require.NotContains(t, str, "/typed/path") // passthrough wins + + // Test empty Additional map + pmCfg = PackageManagerConfig{ + Server: &PackageManagerServerConfig{ + LauncherDir: "/test/path", + }, + Additional: map[string]string{}, + } + str, err = pmCfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "LauncherDir = /test/path") + + // Test Additional adding to existing section + pmCfg = PackageManagerConfig{ + Server: &PackageManagerServerConfig{ + LauncherDir: "/test/path", + }, + Additional: map[string]string{ + "Server.DataDir": "/data/dir", + }, + } + str, err = pmCfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "[Server]") + require.Contains(t, str, "LauncherDir = /test/path") + require.Contains(t, str, "DataDir = /data/dir") + + // Test malformed key (no ".") - should be skipped gracefully + pmCfg = PackageManagerConfig{ + Server: &PackageManagerServerConfig{ + LauncherDir: "/test/path", + }, + Additional: map[string]string{ + "InvalidKey": "some-value", + }, + } + str, err = pmCfg.GenerateGcfg() + require.Nil(t, err) + require.NotContains(t, str, "InvalidKey") + require.Contains(t, str, "LauncherDir = /test/path") + + // Test passthrough with multiple values + pmCfg = PackageManagerConfig{ + Server: &PackageManagerServerConfig{ + RVersion: []string{"/opt/R/4.2.0", "/opt/R/4.3.0"}, + }, + Additional: map[string]string{ + "Server.RVersion": "/opt/R/custom", + }, + } + str, err = pmCfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "RVersion = /opt/R/custom") + require.NotContains(t, str, "/opt/R/4.2.0") + require.NotContains(t, str, "/opt/R/4.3.0") +} diff --git a/api/core/v1beta1/site_types.go b/api/core/v1beta1/site_types.go index c14e1cda..ac34e5d4 100644 --- a/api/core/v1beta1/site_types.go +++ b/api/core/v1beta1/site_types.go @@ -221,6 +221,12 @@ type InternalPackageManagerSpec struct { // AzureFiles configures Azure Files integration for persistent storage // +optional AzureFiles *AzureFilesConfig `json:"azureFiles,omitempty"` + + // AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. + // Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). + // Values set here take precedence over typed fields if both specify the same key. + // +optional + AdditionalConfig map[string]string `json:"additionalConfig,omitempty"` } type InternalConnectSpec struct { @@ -271,6 +277,12 @@ type InternalConnectSpec struct { // for Connect off-host execution // +optional AdditionalRuntimeImages []ConnectRuntimeImageSpec `json:"additionalRuntimeImages,omitempty"` + + // AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. + // Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). + // Values set here take precedence over typed fields if both specify the same key. + // +optional + AdditionalConfig map[string]string `json:"additionalConfig,omitempty"` } type DatabaseSettings struct { @@ -391,6 +403,16 @@ type InternalWorkbenchSpec struct { // JupyterConfig contains Jupyter configuration for Workbench JupyterConfig *WorkbenchJupyterConfig `json:"jupyterConfig,omitempty"` + + // AdditionalConfigs allows appending arbitrary content to Workbench server config files. + // Keys are config file names (e.g., "rserver.conf", "launcher.conf"). + // +optional + AdditionalConfigs map[string]string `json:"additionalConfigs,omitempty"` + + // AdditionalSessionConfigs allows appending arbitrary content to Workbench session config files. + // Keys are config file names (e.g., "rsession.conf", "repos.conf"). + // +optional + AdditionalSessionConfigs map[string]string `json:"additionalSessionConfigs,omitempty"` } type InternalWorkbenchExperimentalFeatures struct { @@ -431,7 +453,7 @@ type InternalWorkbenchExperimentalFeatures struct { VsCodeExtensionsDir string `json:"vsCodeExtensionsDir,omitempty"` // ResourceProfiles for use by Workbench. If not provided, a default will be used - ResourceProfiles map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection `json:"resourceProfiles,omitempty"` + ResourceProfiles map[string]*WorkbenchLauncherKubernetesResourcesConfigSection `json:"resourceProfiles,omitempty"` // CpuRequestRatio defines the ratio of CPU requests to limits for session pods // Value must be a decimal number between 0 and 1 (e.g., "0.6" means requests are 60% of limits) diff --git a/api/core/v1beta1/workbench_config.go b/api/core/v1beta1/workbench_config.go index 9c794d02..43b06505 100644 --- a/api/core/v1beta1/workbench_config.go +++ b/api/core/v1beta1/workbench_config.go @@ -106,6 +106,12 @@ type WorkbenchSessionIniConfig struct { Repos *WorkbenchRepoConfig `json:"repos.conf,omitempty"` WorkbenchNss *WorkbenchNssConfig `json:"workbench_nss.conf,omitempty"` Positron *WorkbenchPositronConfig `json:"positron.conf,omitempty"` + + // AdditionalConfigs allows appending arbitrary content to session config files. + // Keys are config file names (e.g., "rsession.conf", "repos.conf"). + // Values are raw config content that gets appended after the generated config. + // +optional + AdditionalConfigs map[string]string `json:"additionalConfigs,omitempty"` } func (w *WorkbenchSessionIniConfig) GenerateConfigMap() map[string]string { @@ -118,6 +124,12 @@ func (w *WorkbenchSessionIniConfig) GenerateConfigMap() map[string]string { var builder strings.Builder fieldName := configStructVals.Type().Field(i).Name + + // Skip AdditionalConfigs field in reflection loop + if fieldName == "AdditionalConfigs" { + continue + } + field, _ := reflect.TypeOf(w).Elem().FieldByName(fieldName) fieldTag := string(field.Tag) fieldTag = strings.ReplaceAll(fieldTag, "json:\"", "") @@ -155,6 +167,23 @@ func (w *WorkbenchSessionIniConfig) GenerateConfigMap() map[string]string { } configMap[fieldTag] = finalString } + + // Append additional configs + if w.AdditionalConfigs != nil { + for filename, content := range w.AdditionalConfigs { + if existing, ok := configMap[filename]; ok { + // Append to existing config file, ensure newline separation + if !strings.HasSuffix(existing, "\n") { + existing += "\n" + } + configMap[filename] = existing + content + } else { + // New config file + configMap[filename] = content + } + } + } + return configMap } @@ -373,7 +402,7 @@ const ( SessionSaveActionEmpty = "" ) -type WorkbenchLauncherKubnernetesResourcesConfigSection struct { +type WorkbenchLauncherKubernetesResourcesConfigSection struct { Name string `json:"name,omitempty"` Cpus string `json:"cpus,omitempty"` CpusRequest string `json:"cpus-request,omitempty"` @@ -501,7 +530,13 @@ type WorkbenchIniConfig struct { LauncherKubernetes *WorkbenchLauncherKubernetesConfig `json:"launcher.kubernetes.conf,omitempty"` LauncherLocal *WorkbenchLauncherLocalConfig `json:"launcher.local.conf,omitempty"` Databricks map[string]*WorkbenchDatabricksConfig `json:"databricks.conf,omitempty"` // TODO: DEPRECATED - Resources map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection `json:"launcher.kubernetes.resources.conf,omitempty"` + Resources map[string]*WorkbenchLauncherKubernetesResourcesConfigSection `json:"launcher.kubernetes.resources.conf,omitempty"` + + // AdditionalConfigs allows appending arbitrary content to server config files. + // Keys are config file names (e.g., "rserver.conf", "launcher.conf"). + // Values are raw config content that gets appended after the generated config. + // +optional + AdditionalConfigs map[string]string `json:"additionalConfigs,omitempty"` } type WorkbenchDatabricksConfig struct { @@ -521,6 +556,12 @@ func (w *WorkbenchIniConfig) GenerateConfigMap() map[string]string { var builder strings.Builder fieldName := configStructVals.Type().Field(i).Name + + // Skip AdditionalConfigs field in reflection loop + if fieldName == "AdditionalConfigs" { + continue + } + field, _ := reflect.TypeOf(w).Elem().FieldByName(fieldName) fieldTag := string(field.Tag) fieldTag = strings.ReplaceAll(fieldTag, "json:\"", "") @@ -539,14 +580,14 @@ func (w *WorkbenchIniConfig) GenerateConfigMap() map[string]string { // Collect all resource profiles type resourceProfile struct { key string - value *WorkbenchLauncherKubnernetesResourcesConfigSection + value *WorkbenchLauncherKubernetesResourcesConfigSection } var profiles []resourceProfile iter := sectionStructVals.MapRange() for iter.Next() { key := iter.Key().String() - value, ok := iter.Value().Interface().(*WorkbenchLauncherKubnernetesResourcesConfigSection) + value, ok := iter.Value().Interface().(*WorkbenchLauncherKubernetesResourcesConfigSection) if !ok { // Skip invalid entries continue @@ -649,6 +690,22 @@ func (w *WorkbenchIniConfig) GenerateConfigMap() map[string]string { configMap[fieldTag] = finalString } + // Append additional configs + if w.AdditionalConfigs != nil { + for filename, content := range w.AdditionalConfigs { + if existing, ok := configMap[filename]; ok { + // Append to existing config file, ensure newline separation + if !strings.HasSuffix(existing, "\n") { + existing += "\n" + } + configMap[filename] = existing + content + } else { + // New config file + configMap[filename] = content + } + } + } + return configMap } @@ -1094,7 +1151,7 @@ func getEffectiveResource(limit, request string) resource.Quantity { // Returns true if profile i should come before profile j. // Sorting is done by CPU first (using the higher of Cpus or CpusRequest), // then by memory (using the higher of MemMb or MemMbRequest). -func compareResourceProfiles(i, j *WorkbenchLauncherKubnernetesResourcesConfigSection, iKey, jKey string) bool { +func compareResourceProfiles(i, j *WorkbenchLauncherKubernetesResourcesConfigSection, iKey, jKey string) bool { // Get effective CPU values iEffectiveCpu := getEffectiveResource(i.Cpus, i.CpusRequest) jEffectiveCpu := getEffectiveResource(j.Cpus, j.CpusRequest) diff --git a/api/core/v1beta1/workbench_config_test.go b/api/core/v1beta1/workbench_config_test.go index 3d3b801f..8347690e 100644 --- a/api/core/v1beta1/workbench_config_test.go +++ b/api/core/v1beta1/workbench_config_test.go @@ -110,8 +110,8 @@ func TestWorkbenchConfig_GenerateConfigmap(t *testing.T) { LauncherSessionsInitContainerImageTag: "v1.0.0", AuthOpenidScopes: []string{"openid", "profile", "email", "offline_access"}, }, - Resources: map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection{ - "*": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + Resources: map[string]*WorkbenchLauncherKubernetesResourcesConfigSection{ + "*": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "test-name", Cpus: "6CPUS", }, @@ -244,6 +244,115 @@ func TestWorkbenchConfig_GenerateSessionConfigmap(t *testing.T) { require.Contains(t, cm["positron.conf"], "exe=/some/path") } +func TestWorkbenchIniConfig_AdditionalConfigs(t *testing.T) { + t.Run("appends to existing config file", func(t *testing.T) { + wb := WorkbenchIniConfig{ + RServer: &WorkbenchRServerConfig{ + AdminEnabled: 1, + AdminGroup: "workbench-admin", + }, + AdditionalConfigs: map[string]string{ + "rserver.conf": "custom-option=value\nanother-option=123\n", + }, + } + + cm := wb.GenerateConfigMap() + require.Contains(t, cm["rserver.conf"], "admin-enabled=1") + require.Contains(t, cm["rserver.conf"], "admin-group=workbench-admin") + require.Contains(t, cm["rserver.conf"], "custom-option=value") + require.Contains(t, cm["rserver.conf"], "another-option=123") + + // Verify the custom configs are appended after the generated ones + idx1 := strings.Index(cm["rserver.conf"], "admin-enabled=1") + idx2 := strings.Index(cm["rserver.conf"], "custom-option=value") + require.Less(t, idx1, idx2, "Additional configs should be appended after generated configs") + }) + + t.Run("adds new config file", func(t *testing.T) { + wb := WorkbenchIniConfig{ + AdditionalConfigs: map[string]string{ + "custom.conf": "setting1=value1\nsetting2=value2\n", + }, + } + + cm := wb.GenerateConfigMap() + require.Contains(t, cm, "custom.conf") + require.Equal(t, "setting1=value1\nsetting2=value2\n", cm["custom.conf"]) + }) + + t.Run("empty additional configs has no effect", func(t *testing.T) { + wb := WorkbenchIniConfig{ + RServer: &WorkbenchRServerConfig{ + AdminEnabled: 1, + }, + AdditionalConfigs: map[string]string{}, + } + + cm := wb.GenerateConfigMap() + require.Contains(t, cm["rserver.conf"], "admin-enabled=1") + require.Len(t, cm, 1) // Only rserver.conf should exist + }) + + t.Run("nil additional configs has no effect", func(t *testing.T) { + wb := WorkbenchIniConfig{ + RServer: &WorkbenchRServerConfig{ + AdminEnabled: 1, + }, + } + + cm := wb.GenerateConfigMap() + require.Contains(t, cm["rserver.conf"], "admin-enabled=1") + require.Len(t, cm, 1) // Only rserver.conf should exist + }) +} + +func TestWorkbenchSessionIniConfig_AdditionalConfigs(t *testing.T) { + t.Run("appends to existing session config file", func(t *testing.T) { + wb := WorkbenchSessionIniConfig{ + RSession: &WorkbenchRSessionConfig{ + SessionSaveActionDefault: "ask", + }, + AdditionalConfigs: map[string]string{ + "rsession.conf": "custom-session-option=value\n", + }, + } + + cm := wb.GenerateConfigMap() + require.Contains(t, cm["rsession.conf"], "session-save-action-default=ask") + require.Contains(t, cm["rsession.conf"], "custom-session-option=value") + + // Verify the custom configs are appended after the generated ones + idx1 := strings.Index(cm["rsession.conf"], "session-save-action-default=ask") + idx2 := strings.Index(cm["rsession.conf"], "custom-session-option=value") + require.Less(t, idx1, idx2, "Additional configs should be appended after generated configs") + }) + + t.Run("adds new session config file", func(t *testing.T) { + wb := WorkbenchSessionIniConfig{ + AdditionalConfigs: map[string]string{ + "custom-session.conf": "session-setting=value\n", + }, + } + + cm := wb.GenerateConfigMap() + require.Contains(t, cm, "custom-session.conf") + require.Equal(t, "session-setting=value\n", cm["custom-session.conf"]) + }) + + t.Run("empty additional session configs has no effect", func(t *testing.T) { + wb := WorkbenchSessionIniConfig{ + RSession: &WorkbenchRSessionConfig{ + SessionSaveActionDefault: "ask", + }, + AdditionalConfigs: map[string]string{}, + } + + cm := wb.GenerateConfigMap() + require.Contains(t, cm["rsession.conf"], "session-save-action-default=ask") + require.Len(t, cm, 1) // Only rsession.conf should exist + }) +} + func TestWorkbenchConfig_GenerateDcfConfigmap(t *testing.T) { wbc := WorkbenchConfig{ WorkbenchDcfConfig: WorkbenchDcfConfig{ @@ -323,18 +432,18 @@ func TestParseResourceQuantity(t *testing.T) { func TestCompareResourceProfiles(t *testing.T) { tests := []struct { name string - profile1 *WorkbenchLauncherKubnernetesResourcesConfigSection - profile2 *WorkbenchLauncherKubnernetesResourcesConfigSection + profile1 *WorkbenchLauncherKubernetesResourcesConfigSection + profile2 *WorkbenchLauncherKubernetesResourcesConfigSection expected bool // true if profile1 should come before profile2 }{ { name: "lower CPU comes first", - profile1: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile1: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "small", Cpus: "1", MemMb: "1024", }, - profile2: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile2: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "large", Cpus: "2", MemMb: "1024", @@ -343,12 +452,12 @@ func TestCompareResourceProfiles(t *testing.T) { }, { name: "equal CPU, lower memory comes first", - profile1: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile1: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "small", Cpus: "1", MemMb: "512", }, - profile2: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile2: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "medium", Cpus: "1", MemMb: "1024", @@ -357,12 +466,12 @@ func TestCompareResourceProfiles(t *testing.T) { }, { name: "millicores comparison", - profile1: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile1: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "small", Cpus: "500m", MemMb: "1024", }, - profile2: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile2: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "medium", Cpus: "1", MemMb: "1024", @@ -371,13 +480,13 @@ func TestCompareResourceProfiles(t *testing.T) { }, { name: "use request if higher than limit", - profile1: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile1: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "profile1", Cpus: "0.5", CpusRequest: "1", // Higher request should be used MemMb: "1024", }, - profile2: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile2: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "profile2", Cpus: "2", CpusRequest: "0.5", @@ -387,12 +496,12 @@ func TestCompareResourceProfiles(t *testing.T) { }, { name: "equal resources, sort by name", - profile1: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile1: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "a-profile", Cpus: "1", MemMb: "1024", }, - profile2: &WorkbenchLauncherKubnernetesResourcesConfigSection{ + profile2: &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "b-profile", Cpus: "1", MemMb: "1024", @@ -420,23 +529,23 @@ func TestCompareResourceProfiles(t *testing.T) { func TestWorkbenchConfig_GenerateConfigmap_ResourcesSorted(t *testing.T) { wb := WorkbenchConfig{ WorkbenchIniConfig: WorkbenchIniConfig{ - Resources: map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection{ - "large": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + Resources: map[string]*WorkbenchLauncherKubernetesResourcesConfigSection{ + "large": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Large Instance", Cpus: "4", MemMb: "8192", }, - "small": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "small": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Small Instance", Cpus: "1", MemMb: "1024", }, - "medium": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "medium": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Medium Instance", Cpus: "2", MemMb: "4096", }, - "tiny": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "tiny": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Tiny Instance", Cpus: "500m", MemMb: "512", @@ -496,18 +605,18 @@ func TestGetEffectiveResource(t *testing.T) { func TestWorkbenchConfig_GenerateConfigmap_DefaultProfileFirst(t *testing.T) { wb := WorkbenchConfig{ WorkbenchIniConfig: WorkbenchIniConfig{ - Resources: map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection{ - "large": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + Resources: map[string]*WorkbenchLauncherKubernetesResourcesConfigSection{ + "large": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Large Instance", Cpus: "4", MemMb: "8192", }, - "default": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "default": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Default Instance", Cpus: "2", // Medium-sized, but should still appear first MemMb: "2048", }, - "tiny": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "tiny": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Tiny Instance", Cpus: "500m", MemMb: "512", @@ -542,23 +651,23 @@ func TestWorkbenchConfig_GenerateConfigmap_DefaultProfileFirst(t *testing.T) { func TestWorkbenchConfig_GenerateConfigmap_MemoryUnits(t *testing.T) { wb := WorkbenchConfig{ WorkbenchIniConfig: WorkbenchIniConfig{ - Resources: map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection{ - "xlarge": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + Resources: map[string]*WorkbenchLauncherKubernetesResourcesConfigSection{ + "xlarge": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "XLarge Instance", Cpus: "8", MemMb: "16Gi", // 16384 Mi }, - "small": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "small": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Small Instance", Cpus: "1", MemMb: "512Mi", // 512 Mi }, - "medium": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "medium": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Medium Instance", Cpus: "2", MemMb: "2Gi", // 2048 Mi }, - "large": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "large": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Large Instance", Cpus: "4", MemMb: "8Gi", // 8192 Mi @@ -801,15 +910,15 @@ func TestValidateConstraintFormat(t *testing.T) { func TestWorkbenchConfig_GenerateConfigmap_WithPlacementConstraintsSync(t *testing.T) { wb := WorkbenchConfig{ WorkbenchIniConfig: WorkbenchIniConfig{ - Resources: map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection{ - "gpu-enabled": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + Resources: map[string]*WorkbenchLauncherKubernetesResourcesConfigSection{ + "gpu-enabled": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "GPU Instance", Cpus: "4", MemMb: "8192", NvidiaGpus: "1", PlacementConstraints: "node-type=gpu,gpu-vendor=nvidia", }, - "compute-heavy": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "compute-heavy": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Compute Heavy", Cpus: "16", MemMb: "32768", @@ -847,8 +956,8 @@ func TestWorkbenchConfig_GenerateConfigmap_WithPlacementConstraintsSync(t *testi func TestWorkbenchConfig_GenerateConfigmap_PreservesExistingConstraints(t *testing.T) { wb := WorkbenchConfig{ WorkbenchIniConfig: WorkbenchIniConfig{ - Resources: map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection{ - "gpu-enabled": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + Resources: map[string]*WorkbenchLauncherKubernetesResourcesConfigSection{ + "gpu-enabled": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "GPU Instance", Cpus: "4", MemMb: "8192", @@ -885,20 +994,20 @@ func TestWorkbenchConfig_GenerateConfigmap_PreservesExistingConstraints(t *testi func TestWorkbenchConfig_GenerateConfigmap_WithColonFormat(t *testing.T) { wb := WorkbenchConfig{ WorkbenchIniConfig: WorkbenchIniConfig{ - Resources: map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection{ - "default": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + Resources: map[string]*WorkbenchLauncherKubernetesResourcesConfigSection{ + "default": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Default Instance", Cpus: "1.0", MemMb: "2048", PlacementConstraints: "kubernetes.io/hostname:k8s-control", }, - "small": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "small": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Small Instance", Cpus: "1.0", MemMb: "512", PlacementConstraints: "kubernetes.io/hostname:k8s-worker2", }, - "medium": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "medium": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Medium Instance", Cpus: "2.0", MemMb: "4096", @@ -936,14 +1045,14 @@ func TestWorkbenchConfig_GenerateConfigmap_WithColonFormat(t *testing.T) { func TestWorkbenchConfig_GenerateConfigmap_HandlesEmptyConstraints(t *testing.T) { wb := WorkbenchConfig{ WorkbenchIniConfig: WorkbenchIniConfig{ - Resources: map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection{ - "basic": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + Resources: map[string]*WorkbenchLauncherKubernetesResourcesConfigSection{ + "basic": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Basic Instance", Cpus: "2", MemMb: "4096", PlacementConstraints: "", // Empty constraints }, - "standard": &WorkbenchLauncherKubnernetesResourcesConfigSection{ + "standard": &WorkbenchLauncherKubernetesResourcesConfigSection{ Name: "Standard Instance", Cpus: "4", MemMb: "8192", diff --git a/api/core/v1beta1/zz_generated.deepcopy.go b/api/core/v1beta1/zz_generated.deepcopy.go index b9831e4b..87375552 100644 --- a/api/core/v1beta1/zz_generated.deepcopy.go +++ b/api/core/v1beta1/zz_generated.deepcopy.go @@ -505,6 +505,13 @@ func (in *ConnectConfig) DeepCopyInto(out *ConnectConfig) { *out = new(ConnectTableauIntegrationConfig) **out = **in } + if in.Additional != nil { + in, out := &in.Additional, &out.Additional + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectConfig. @@ -1137,6 +1144,13 @@ func (in *InternalConnectSpec) DeepCopyInto(out *InternalConnectSpec) { *out = make([]ConnectRuntimeImageSpec, len(*in)) copy(*out, *in) } + if in.AdditionalConfig != nil { + in, out := &in.AdditionalConfig, &out.AdditionalConfig + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalConnectSpec. @@ -1227,6 +1241,13 @@ func (in *InternalPackageManagerSpec) DeepCopyInto(out *InternalPackageManagerSp *out = new(AzureFilesConfig) **out = **in } + if in.AdditionalConfig != nil { + in, out := &in.AdditionalConfig, &out.AdditionalConfig + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalPackageManagerSpec. @@ -1261,15 +1282,15 @@ func (in *InternalWorkbenchExperimentalFeatures) DeepCopyInto(out *InternalWorkb } if in.ResourceProfiles != nil { in, out := &in.ResourceProfiles, &out.ResourceProfiles - *out = make(map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection, len(*in)) + *out = make(map[string]*WorkbenchLauncherKubernetesResourcesConfigSection, len(*in)) for key, val := range *in { - var outVal *WorkbenchLauncherKubnernetesResourcesConfigSection + var outVal *WorkbenchLauncherKubernetesResourcesConfigSection if val == nil { (*out)[key] = nil } else { inVal := (*in)[key] in, out := &inVal, &outVal - *out = new(WorkbenchLauncherKubnernetesResourcesConfigSection) + *out = new(WorkbenchLauncherKubernetesResourcesConfigSection) **out = **in } (*out)[key] = outVal @@ -1389,6 +1410,20 @@ func (in *InternalWorkbenchSpec) DeepCopyInto(out *InternalWorkbenchSpec) { *out = new(WorkbenchJupyterConfig) **out = **in } + if in.AdditionalConfigs != nil { + in, out := &in.AdditionalConfigs, &out.AdditionalConfigs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.AdditionalSessionConfigs != nil { + in, out := &in.AdditionalSessionConfigs, &out.AdditionalSessionConfigs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalWorkbenchSpec. @@ -1521,6 +1556,13 @@ func (in *PackageManagerConfig) DeepCopyInto(out *PackageManagerConfig) { *out = new(PackageManagerDebugConfig) **out = **in } + if in.Additional != nil { + in, out := &in.Additional, &out.Additional + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageManagerConfig. @@ -2450,20 +2492,27 @@ func (in *WorkbenchIniConfig) DeepCopyInto(out *WorkbenchIniConfig) { } if in.Resources != nil { in, out := &in.Resources, &out.Resources - *out = make(map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection, len(*in)) + *out = make(map[string]*WorkbenchLauncherKubernetesResourcesConfigSection, len(*in)) for key, val := range *in { - var outVal *WorkbenchLauncherKubnernetesResourcesConfigSection + var outVal *WorkbenchLauncherKubernetesResourcesConfigSection if val == nil { (*out)[key] = nil } else { inVal := (*in)[key] in, out := &inVal, &outVal - *out = new(WorkbenchLauncherKubnernetesResourcesConfigSection) + *out = new(WorkbenchLauncherKubernetesResourcesConfigSection) **out = **in } (*out)[key] = outVal } } + if in.AdditionalConfigs != nil { + in, out := &in.AdditionalConfigs, &out.AdditionalConfigs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchIniConfig. @@ -2599,16 +2648,16 @@ func (in *WorkbenchLauncherKubernetesProfilesConfigSection) DeepCopy() *Workbenc } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WorkbenchLauncherKubnernetesResourcesConfigSection) DeepCopyInto(out *WorkbenchLauncherKubnernetesResourcesConfigSection) { +func (in *WorkbenchLauncherKubernetesResourcesConfigSection) DeepCopyInto(out *WorkbenchLauncherKubernetesResourcesConfigSection) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchLauncherKubnernetesResourcesConfigSection. -func (in *WorkbenchLauncherKubnernetesResourcesConfigSection) DeepCopy() *WorkbenchLauncherKubnernetesResourcesConfigSection { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchLauncherKubernetesResourcesConfigSection. +func (in *WorkbenchLauncherKubernetesResourcesConfigSection) DeepCopy() *WorkbenchLauncherKubernetesResourcesConfigSection { if in == nil { return nil } - out := new(WorkbenchLauncherKubnernetesResourcesConfigSection) + out := new(WorkbenchLauncherKubernetesResourcesConfigSection) in.DeepCopyInto(out) return out } @@ -2912,6 +2961,13 @@ func (in *WorkbenchSessionIniConfig) DeepCopyInto(out *WorkbenchSessionIniConfig *out = new(WorkbenchPositronConfig) (*in).DeepCopyInto(*out) } + if in.AdditionalConfigs != nil { + in, out := &in.AdditionalConfigs, &out.AdditionalConfigs + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchSessionIniConfig. diff --git a/client-go/applyconfiguration/core/v1beta1/connectconfig.go b/client-go/applyconfiguration/core/v1beta1/connectconfig.go index 4259df7b..ee286a86 100644 --- a/client-go/applyconfiguration/core/v1beta1/connectconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/connectconfig.go @@ -26,6 +26,7 @@ type ConnectConfigApplyConfiguration struct { Scheduler *ConnectSchedulerConfigApplyConfiguration `json:"Scheduler,omitempty"` RPackageRepository map[string]RPackageRepositoryConfigApplyConfiguration `json:"RPackageRepositories,omitempty"` TableauIntegration *ConnectTableauIntegrationConfigApplyConfiguration `json:"TableauIntegration,omitempty"` + Additional map[string]string `json:"additional,omitempty"` } // ConnectConfigApplyConfiguration constructs a declarative configuration of the ConnectConfig type for use with @@ -183,3 +184,17 @@ func (b *ConnectConfigApplyConfiguration) WithTableauIntegration(value *ConnectT b.TableauIntegration = value return b } + +// WithAdditional puts the entries into the Additional field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Additional field, +// overwriting an existing map entries in Additional field with the same key. +func (b *ConnectConfigApplyConfiguration) WithAdditional(entries map[string]string) *ConnectConfigApplyConfiguration { + if b.Additional == nil && len(entries) > 0 { + b.Additional = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Additional[k] = v + } + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/connectruntimeimagespec.go b/client-go/applyconfiguration/core/v1beta1/connectruntimeimagespec.go new file mode 100644 index 00000000..29cf31e7 --- /dev/null +++ b/client-go/applyconfiguration/core/v1beta1/connectruntimeimagespec.go @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023-2026 Posit Software, PBC + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta1 + +// ConnectRuntimeImageSpecApplyConfiguration represents a declarative configuration of the ConnectRuntimeImageSpec type for use +// with apply. +type ConnectRuntimeImageSpecApplyConfiguration struct { + RVersion *string `json:"rVersion,omitempty"` + PyVersion *string `json:"pyVersion,omitempty"` + OSVersion *string `json:"osVersion,omitempty"` + QuartoVersion *string `json:"quartoVersion,omitempty"` + Repo *string `json:"repo,omitempty"` +} + +// ConnectRuntimeImageSpecApplyConfiguration constructs a declarative configuration of the ConnectRuntimeImageSpec type for use with +// apply. +func ConnectRuntimeImageSpec() *ConnectRuntimeImageSpecApplyConfiguration { + return &ConnectRuntimeImageSpecApplyConfiguration{} +} + +// WithRVersion sets the RVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RVersion field is set to the value of the last call. +func (b *ConnectRuntimeImageSpecApplyConfiguration) WithRVersion(value string) *ConnectRuntimeImageSpecApplyConfiguration { + b.RVersion = &value + return b +} + +// WithPyVersion sets the PyVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PyVersion field is set to the value of the last call. +func (b *ConnectRuntimeImageSpecApplyConfiguration) WithPyVersion(value string) *ConnectRuntimeImageSpecApplyConfiguration { + b.PyVersion = &value + return b +} + +// WithOSVersion sets the OSVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the OSVersion field is set to the value of the last call. +func (b *ConnectRuntimeImageSpecApplyConfiguration) WithOSVersion(value string) *ConnectRuntimeImageSpecApplyConfiguration { + b.OSVersion = &value + return b +} + +// WithQuartoVersion sets the QuartoVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the QuartoVersion field is set to the value of the last call. +func (b *ConnectRuntimeImageSpecApplyConfiguration) WithQuartoVersion(value string) *ConnectRuntimeImageSpecApplyConfiguration { + b.QuartoVersion = &value + return b +} + +// WithRepo sets the Repo field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Repo field is set to the value of the last call. +func (b *ConnectRuntimeImageSpecApplyConfiguration) WithRepo(value string) *ConnectRuntimeImageSpecApplyConfiguration { + b.Repo = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go b/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go index 3d88320c..ec3952af 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go @@ -32,6 +32,7 @@ type InternalConnectSpecApplyConfiguration struct { DatabaseSettings *DatabaseSettingsApplyConfiguration `json:"databaseSettings,omitempty"` ScheduleConcurrency *int `json:"scheduleConcurrency,omitempty"` AdditionalRuntimeImages []ConnectRuntimeImageSpecApplyConfiguration `json:"additionalRuntimeImages,omitempty"` + AdditionalConfig map[string]string `json:"additionalConfig,omitempty"` } // InternalConnectSpecApplyConfiguration constructs a declarative configuration of the InternalConnectSpec type for use with @@ -208,3 +209,17 @@ func (b *InternalConnectSpecApplyConfiguration) WithAdditionalRuntimeImages(valu } return b } + +// WithAdditionalConfig puts the entries into the AdditionalConfig field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the AdditionalConfig field, +// overwriting an existing map entries in AdditionalConfig field with the same key. +func (b *InternalConnectSpecApplyConfiguration) WithAdditionalConfig(entries map[string]string) *InternalConnectSpecApplyConfiguration { + if b.AdditionalConfig == nil && len(entries) > 0 { + b.AdditionalConfig = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.AdditionalConfig[k] = v + } + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go b/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go index 32cdab2e..8ac49e39 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go @@ -13,18 +13,19 @@ import ( // InternalPackageManagerSpecApplyConfiguration represents a declarative configuration of the InternalPackageManagerSpec type for use // with apply. type InternalPackageManagerSpecApplyConfiguration struct { - License *product.LicenseSpec `json:"license,omitempty"` - Volume *product.VolumeSpec `json:"volume,omitempty"` - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - AddEnv map[string]string `json:"addEnv,omitempty"` - Image *string `json:"image,omitempty"` - ImagePullPolicy *v1.PullPolicy `json:"imagePullPolicy,omitempty"` - S3Bucket *string `json:"s3Bucket,omitempty"` - Replicas *int `json:"replicas,omitempty"` - DomainPrefix *string `json:"domainPrefix,omitempty"` - BaseDomain *string `json:"baseDomain,omitempty"` - GitSSHKeys []SSHKeyConfigApplyConfiguration `json:"gitSSHKeys,omitempty"` - AzureFiles *AzureFilesConfigApplyConfiguration `json:"azureFiles,omitempty"` + License *product.LicenseSpec `json:"license,omitempty"` + Volume *product.VolumeSpec `json:"volume,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + AddEnv map[string]string `json:"addEnv,omitempty"` + Image *string `json:"image,omitempty"` + ImagePullPolicy *v1.PullPolicy `json:"imagePullPolicy,omitempty"` + S3Bucket *string `json:"s3Bucket,omitempty"` + Replicas *int `json:"replicas,omitempty"` + DomainPrefix *string `json:"domainPrefix,omitempty"` + BaseDomain *string `json:"baseDomain,omitempty"` + GitSSHKeys []SSHKeyConfigApplyConfiguration `json:"gitSSHKeys,omitempty"` + AzureFiles *AzureFilesConfigApplyConfiguration `json:"azureFiles,omitempty"` + AdditionalConfig map[string]string `json:"additionalConfig,omitempty"` } // InternalPackageManagerSpecApplyConfiguration constructs a declarative configuration of the InternalPackageManagerSpec type for use with @@ -145,3 +146,17 @@ func (b *InternalPackageManagerSpecApplyConfiguration) WithAzureFiles(value *Azu b.AzureFiles = value return b } + +// WithAdditionalConfig puts the entries into the AdditionalConfig field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the AdditionalConfig field, +// overwriting an existing map entries in AdditionalConfig field with the same key. +func (b *InternalPackageManagerSpecApplyConfiguration) WithAdditionalConfig(entries map[string]string) *InternalPackageManagerSpecApplyConfiguration { + if b.AdditionalConfig == nil && len(entries) > 0 { + b.AdditionalConfig = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.AdditionalConfig[k] = v + } + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/internalworkbenchexperimentalfeatures.go b/client-go/applyconfiguration/core/v1beta1/internalworkbenchexperimentalfeatures.go index c5c9b7ff..9c969e32 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalworkbenchexperimentalfeatures.go +++ b/client-go/applyconfiguration/core/v1beta1/internalworkbenchexperimentalfeatures.go @@ -13,26 +13,26 @@ import ( // InternalWorkbenchExperimentalFeaturesApplyConfiguration represents a declarative configuration of the InternalWorkbenchExperimentalFeatures type for use // with apply. type InternalWorkbenchExperimentalFeaturesApplyConfiguration struct { - EnableManagedCredentialJobs *bool `json:"enableManagedCredentialJobs,omitempty"` - NonRoot *bool `json:"nonRoot,omitempty"` - SessionServiceAccountName *string `json:"sessionServiceAccountName,omitempty"` - SessionEnvVars []v1.EnvVar `json:"sessionEnvVars,omitempty"` - SessionImagePullPolicy *v1.PullPolicy `json:"sessionImagePullPolicy,omitempty"` - PrivilegedSessions *bool `json:"privilegedSessions,omitempty"` - DatabricksForceEnabled *bool `json:"databricksForceEnabled,omitempty"` - DsnSecret *string `json:"dsnSecret,omitempty"` - WwwThreadPoolSize *int `json:"wwwThreadPoolSize,omitempty"` - FirstProjectTemplatePath *string `json:"firstProjectTemplatePath,omitempty"` - LauncherSessionsProxyTimeoutSeconds *int `json:"launcherSessionsProxyTimeoutSecs,omitempty"` - VsCodeExtensionsDir *string `json:"vsCodeExtensionsDir,omitempty"` - ResourceProfiles map[string]*corev1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection `json:"resourceProfiles,omitempty"` - CpuRequestRatio *string `json:"cpuRequestRatio,omitempty"` - MemoryRequestRatio *string `json:"memoryRequestRatio,omitempty"` - SessionSaveActionDefault *corev1beta1.SessionSaveAction `json:"sessionSaveActionDefault,omitempty"` - VsCodePath *string `json:"vsCodePath,omitempty"` - LauncherEnvPath *string `json:"launcherEnvPath,omitempty"` - ChronicleSidecarProductApiKeyEnabled *bool `json:"chronicleSidecarProductApiKeyEnabled,omitempty"` - ForceAdminUiEnabled *bool `json:"forceAdminUiEnabled,omitempty"` + EnableManagedCredentialJobs *bool `json:"enableManagedCredentialJobs,omitempty"` + NonRoot *bool `json:"nonRoot,omitempty"` + SessionServiceAccountName *string `json:"sessionServiceAccountName,omitempty"` + SessionEnvVars []v1.EnvVar `json:"sessionEnvVars,omitempty"` + SessionImagePullPolicy *v1.PullPolicy `json:"sessionImagePullPolicy,omitempty"` + PrivilegedSessions *bool `json:"privilegedSessions,omitempty"` + DatabricksForceEnabled *bool `json:"databricksForceEnabled,omitempty"` + DsnSecret *string `json:"dsnSecret,omitempty"` + WwwThreadPoolSize *int `json:"wwwThreadPoolSize,omitempty"` + FirstProjectTemplatePath *string `json:"firstProjectTemplatePath,omitempty"` + LauncherSessionsProxyTimeoutSeconds *int `json:"launcherSessionsProxyTimeoutSecs,omitempty"` + VsCodeExtensionsDir *string `json:"vsCodeExtensionsDir,omitempty"` + ResourceProfiles map[string]*corev1beta1.WorkbenchLauncherKubernetesResourcesConfigSection `json:"resourceProfiles,omitempty"` + CpuRequestRatio *string `json:"cpuRequestRatio,omitempty"` + MemoryRequestRatio *string `json:"memoryRequestRatio,omitempty"` + SessionSaveActionDefault *corev1beta1.SessionSaveAction `json:"sessionSaveActionDefault,omitempty"` + VsCodePath *string `json:"vsCodePath,omitempty"` + LauncherEnvPath *string `json:"launcherEnvPath,omitempty"` + ChronicleSidecarProductApiKeyEnabled *bool `json:"chronicleSidecarProductApiKeyEnabled,omitempty"` + ForceAdminUiEnabled *bool `json:"forceAdminUiEnabled,omitempty"` } // InternalWorkbenchExperimentalFeaturesApplyConfiguration constructs a declarative configuration of the InternalWorkbenchExperimentalFeatures type for use with @@ -143,9 +143,9 @@ func (b *InternalWorkbenchExperimentalFeaturesApplyConfiguration) WithVsCodeExte // and returns the receiver, so that objects can be build by chaining "With" function invocations. // If called multiple times, the entries provided by each call will be put on the ResourceProfiles field, // overwriting an existing map entries in ResourceProfiles field with the same key. -func (b *InternalWorkbenchExperimentalFeaturesApplyConfiguration) WithResourceProfiles(entries map[string]*corev1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection) *InternalWorkbenchExperimentalFeaturesApplyConfiguration { +func (b *InternalWorkbenchExperimentalFeaturesApplyConfiguration) WithResourceProfiles(entries map[string]*corev1beta1.WorkbenchLauncherKubernetesResourcesConfigSection) *InternalWorkbenchExperimentalFeaturesApplyConfiguration { if b.ResourceProfiles == nil && len(entries) > 0 { - b.ResourceProfiles = make(map[string]*corev1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection, len(entries)) + b.ResourceProfiles = make(map[string]*corev1beta1.WorkbenchLauncherKubernetesResourcesConfigSection, len(entries)) } for k, v := range entries { b.ResourceProfiles[k] = v diff --git a/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go b/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go index f6b305d8..2b8f84ec 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go @@ -44,6 +44,8 @@ type InternalWorkbenchSpecApplyConfiguration struct { BaseDomain *string `json:"baseDomain,omitempty"` AuthLoginPageHtml *string `json:"authLoginPageHtml,omitempty"` JupyterConfig *WorkbenchJupyterConfigApplyConfiguration `json:"jupyterConfig,omitempty"` + AdditionalConfigs map[string]string `json:"additionalConfigs,omitempty"` + AdditionalSessionConfigs map[string]string `json:"additionalSessionConfigs,omitempty"` } // InternalWorkbenchSpecApplyConfiguration constructs a declarative configuration of the InternalWorkbenchSpec type for use with @@ -329,3 +331,31 @@ func (b *InternalWorkbenchSpecApplyConfiguration) WithJupyterConfig(value *Workb b.JupyterConfig = value return b } + +// WithAdditionalConfigs puts the entries into the AdditionalConfigs field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the AdditionalConfigs field, +// overwriting an existing map entries in AdditionalConfigs field with the same key. +func (b *InternalWorkbenchSpecApplyConfiguration) WithAdditionalConfigs(entries map[string]string) *InternalWorkbenchSpecApplyConfiguration { + if b.AdditionalConfigs == nil && len(entries) > 0 { + b.AdditionalConfigs = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.AdditionalConfigs[k] = v + } + return b +} + +// WithAdditionalSessionConfigs puts the entries into the AdditionalSessionConfigs field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the AdditionalSessionConfigs field, +// overwriting an existing map entries in AdditionalSessionConfigs field with the same key. +func (b *InternalWorkbenchSpecApplyConfiguration) WithAdditionalSessionConfigs(entries map[string]string) *InternalWorkbenchSpecApplyConfiguration { + if b.AdditionalSessionConfigs == nil && len(entries) > 0 { + b.AdditionalSessionConfigs = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.AdditionalSessionConfigs[k] = v + } + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go b/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go index 9701f194..27e0cecf 100644 --- a/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go @@ -8,17 +8,18 @@ package v1beta1 // PackageManagerConfigApplyConfiguration represents a declarative configuration of the PackageManagerConfig type for use // with apply. type PackageManagerConfigApplyConfiguration struct { - Server *PackageManagerServerConfigApplyConfiguration `json:"Server,omitempty"` - Http *PackageManagerHttpConfigApplyConfiguration `json:"Http,omitempty"` - Git *PackageManagerGitConfigApplyConfiguration `json:"Git,omitempty"` - Database *PackageManagerDatabaseConfigApplyConfiguration `json:"Database,omitempty"` - Postgres *PackageManagerPostgresConfigApplyConfiguration `json:"Postgres,omitempty"` - Storage *PackageManagerStorageConfigApplyConfiguration `json:"Storage,omitempty"` - S3Storage *PackageManagerS3StorageConfigApplyConfiguration `json:"S3Storage,omitempty"` - Metrics *PackageManagerMetricsConfigApplyConfiguration `json:"Metrics,omitempty"` - Repos *PackageManagerReposConfigApplyConfiguration `json:"Repos,omitempty"` - Cran *PackageManagerCRANConfigApplyConfiguration `json:"CRAN,omitempty"` - Debug *PackageManagerDebugConfigApplyConfiguration `json:"Debug,omitempty"` + Server *PackageManagerServerConfigApplyConfiguration `json:"Server,omitempty"` + Http *PackageManagerHttpConfigApplyConfiguration `json:"Http,omitempty"` + Git *PackageManagerGitConfigApplyConfiguration `json:"Git,omitempty"` + Database *PackageManagerDatabaseConfigApplyConfiguration `json:"Database,omitempty"` + Postgres *PackageManagerPostgresConfigApplyConfiguration `json:"Postgres,omitempty"` + Storage *PackageManagerStorageConfigApplyConfiguration `json:"Storage,omitempty"` + S3Storage *PackageManagerS3StorageConfigApplyConfiguration `json:"S3Storage,omitempty"` + Metrics *PackageManagerMetricsConfigApplyConfiguration `json:"Metrics,omitempty"` + Repos *PackageManagerReposConfigApplyConfiguration `json:"Repos,omitempty"` + Cran *PackageManagerCRANConfigApplyConfiguration `json:"CRAN,omitempty"` + Debug *PackageManagerDebugConfigApplyConfiguration `json:"Debug,omitempty"` + Additional map[string]string `json:"additional,omitempty"` } // PackageManagerConfigApplyConfiguration constructs a declarative configuration of the PackageManagerConfig type for use with @@ -114,3 +115,17 @@ func (b *PackageManagerConfigApplyConfiguration) WithDebug(value *PackageManager b.Debug = value return b } + +// WithAdditional puts the entries into the Additional field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Additional field, +// overwriting an existing map entries in Additional field with the same key. +func (b *PackageManagerConfigApplyConfiguration) WithAdditional(entries map[string]string) *PackageManagerConfigApplyConfiguration { + if b.Additional == nil && len(entries) > 0 { + b.Additional = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Additional[k] = v + } + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchconfig.go b/client-go/applyconfiguration/core/v1beta1/workbenchconfig.go index 3e2cc6e0..da978e97 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchconfig.go @@ -110,10 +110,10 @@ func (b *WorkbenchConfigApplyConfiguration) WithDatabricks(entries map[string]*c // and returns the receiver, so that objects can be build by chaining "With" function invocations. // If called multiple times, the entries provided by each call will be put on the Resources field, // overwriting an existing map entries in Resources field with the same key. -func (b *WorkbenchConfigApplyConfiguration) WithResources(entries map[string]*corev1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection) *WorkbenchConfigApplyConfiguration { +func (b *WorkbenchConfigApplyConfiguration) WithResources(entries map[string]*corev1beta1.WorkbenchLauncherKubernetesResourcesConfigSection) *WorkbenchConfigApplyConfiguration { b.ensureWorkbenchIniConfigApplyConfigurationExists() if b.WorkbenchIniConfigApplyConfiguration.Resources == nil && len(entries) > 0 { - b.WorkbenchIniConfigApplyConfiguration.Resources = make(map[string]*corev1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection, len(entries)) + b.WorkbenchIniConfigApplyConfiguration.Resources = make(map[string]*corev1beta1.WorkbenchLauncherKubernetesResourcesConfigSection, len(entries)) } for k, v := range entries { b.WorkbenchIniConfigApplyConfiguration.Resources[k] = v @@ -121,6 +121,21 @@ func (b *WorkbenchConfigApplyConfiguration) WithResources(entries map[string]*co return b } +// WithAdditionalConfigs puts the entries into the AdditionalConfigs field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the AdditionalConfigs field, +// overwriting an existing map entries in AdditionalConfigs field with the same key. +func (b *WorkbenchConfigApplyConfiguration) WithAdditionalConfigs(entries map[string]string) *WorkbenchConfigApplyConfiguration { + b.ensureWorkbenchIniConfigApplyConfigurationExists() + if b.WorkbenchIniConfigApplyConfiguration.AdditionalConfigs == nil && len(entries) > 0 { + b.WorkbenchIniConfigApplyConfiguration.AdditionalConfigs = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.WorkbenchIniConfigApplyConfiguration.AdditionalConfigs[k] = v + } + return b +} + func (b *WorkbenchConfigApplyConfiguration) ensureWorkbenchIniConfigApplyConfigurationExists() { if b.WorkbenchIniConfigApplyConfiguration == nil { b.WorkbenchIniConfigApplyConfiguration = &WorkbenchIniConfigApplyConfiguration{} diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchiniconfig.go b/client-go/applyconfiguration/core/v1beta1/workbenchiniconfig.go index 253889b8..0037797c 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchiniconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchiniconfig.go @@ -12,15 +12,16 @@ import ( // WorkbenchIniConfigApplyConfiguration represents a declarative configuration of the WorkbenchIniConfig type for use // with apply. type WorkbenchIniConfigApplyConfiguration struct { - Launcher *WorkbenchLauncherConfigApplyConfiguration `json:"launcher.conf,omitempty"` - VsCode *WorkbenchVsCodeConfigApplyConfiguration `json:"vscode.conf,omitempty"` - Logging *WorkbenchLoggingConfigApplyConfiguration `json:"logging.conf,omitempty"` - Jupyter *WorkbenchJupyterConfigApplyConfiguration `json:"jupyter.conf,omitempty"` - RServer *WorkbenchRServerConfigApplyConfiguration `json:"rserver.conf,omitempty"` - LauncherKubernetes *WorkbenchLauncherKubernetesConfigApplyConfiguration `json:"launcher.kubernetes.conf,omitempty"` - LauncherLocal *WorkbenchLauncherLocalConfigApplyConfiguration `json:"launcher.local.conf,omitempty"` - Databricks map[string]*corev1beta1.WorkbenchDatabricksConfig `json:"databricks.conf,omitempty"` - Resources map[string]*corev1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection `json:"launcher.kubernetes.resources.conf,omitempty"` + Launcher *WorkbenchLauncherConfigApplyConfiguration `json:"launcher.conf,omitempty"` + VsCode *WorkbenchVsCodeConfigApplyConfiguration `json:"vscode.conf,omitempty"` + Logging *WorkbenchLoggingConfigApplyConfiguration `json:"logging.conf,omitempty"` + Jupyter *WorkbenchJupyterConfigApplyConfiguration `json:"jupyter.conf,omitempty"` + RServer *WorkbenchRServerConfigApplyConfiguration `json:"rserver.conf,omitempty"` + LauncherKubernetes *WorkbenchLauncherKubernetesConfigApplyConfiguration `json:"launcher.kubernetes.conf,omitempty"` + LauncherLocal *WorkbenchLauncherLocalConfigApplyConfiguration `json:"launcher.local.conf,omitempty"` + Databricks map[string]*corev1beta1.WorkbenchDatabricksConfig `json:"databricks.conf,omitempty"` + Resources map[string]*corev1beta1.WorkbenchLauncherKubernetesResourcesConfigSection `json:"launcher.kubernetes.resources.conf,omitempty"` + AdditionalConfigs map[string]string `json:"additionalConfigs,omitempty"` } // WorkbenchIniConfigApplyConfiguration constructs a declarative configuration of the WorkbenchIniConfig type for use with @@ -103,12 +104,26 @@ func (b *WorkbenchIniConfigApplyConfiguration) WithDatabricks(entries map[string // and returns the receiver, so that objects can be build by chaining "With" function invocations. // If called multiple times, the entries provided by each call will be put on the Resources field, // overwriting an existing map entries in Resources field with the same key. -func (b *WorkbenchIniConfigApplyConfiguration) WithResources(entries map[string]*corev1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection) *WorkbenchIniConfigApplyConfiguration { +func (b *WorkbenchIniConfigApplyConfiguration) WithResources(entries map[string]*corev1beta1.WorkbenchLauncherKubernetesResourcesConfigSection) *WorkbenchIniConfigApplyConfiguration { if b.Resources == nil && len(entries) > 0 { - b.Resources = make(map[string]*corev1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection, len(entries)) + b.Resources = make(map[string]*corev1beta1.WorkbenchLauncherKubernetesResourcesConfigSection, len(entries)) } for k, v := range entries { b.Resources[k] = v } return b } + +// WithAdditionalConfigs puts the entries into the AdditionalConfigs field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the AdditionalConfigs field, +// overwriting an existing map entries in AdditionalConfigs field with the same key. +func (b *WorkbenchIniConfigApplyConfiguration) WithAdditionalConfigs(entries map[string]string) *WorkbenchIniConfigApplyConfiguration { + if b.AdditionalConfigs == nil && len(entries) > 0 { + b.AdditionalConfigs = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.AdditionalConfigs[k] = v + } + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchlauncherkubnernetesresourcesconfigsection.go b/client-go/applyconfiguration/core/v1beta1/workbenchlauncherkubernetesresourcesconfigsection.go similarity index 61% rename from client-go/applyconfiguration/core/v1beta1/workbenchlauncherkubnernetesresourcesconfigsection.go rename to client-go/applyconfiguration/core/v1beta1/workbenchlauncherkubernetesresourcesconfigsection.go index 93625d82..97d511a7 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchlauncherkubnernetesresourcesconfigsection.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchlauncherkubernetesresourcesconfigsection.go @@ -5,9 +5,9 @@ package v1beta1 -// WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration represents a declarative configuration of the WorkbenchLauncherKubnernetesResourcesConfigSection type for use +// WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration represents a declarative configuration of the WorkbenchLauncherKubernetesResourcesConfigSection type for use // with apply. -type WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration struct { +type WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration struct { Name *string `json:"name,omitempty"` Cpus *string `json:"cpus,omitempty"` CpusRequest *string `json:"cpus-request,omitempty"` @@ -18,16 +18,16 @@ type WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration struct PlacementConstraints *string `json:"placement-constraints,omitempty"` } -// WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration constructs a declarative configuration of the WorkbenchLauncherKubnernetesResourcesConfigSection type for use with +// WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration constructs a declarative configuration of the WorkbenchLauncherKubernetesResourcesConfigSection type for use with // apply. -func WorkbenchLauncherKubnernetesResourcesConfigSection() *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration { - return &WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration{} +func WorkbenchLauncherKubernetesResourcesConfigSection() *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration { + return &WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration{} } // WithName sets the Name field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Name field is set to the value of the last call. -func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) WithName(value string) *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration { +func (b *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration) WithName(value string) *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration { b.Name = &value return b } @@ -35,7 +35,7 @@ func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) W // WithCpus sets the Cpus field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Cpus field is set to the value of the last call. -func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) WithCpus(value string) *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration { +func (b *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration) WithCpus(value string) *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration { b.Cpus = &value return b } @@ -43,7 +43,7 @@ func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) W // WithCpusRequest sets the CpusRequest field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CpusRequest field is set to the value of the last call. -func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) WithCpusRequest(value string) *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration { +func (b *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration) WithCpusRequest(value string) *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration { b.CpusRequest = &value return b } @@ -51,7 +51,7 @@ func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) W // WithMemMb sets the MemMb field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the MemMb field is set to the value of the last call. -func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) WithMemMb(value string) *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration { +func (b *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration) WithMemMb(value string) *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration { b.MemMb = &value return b } @@ -59,7 +59,7 @@ func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) W // WithMemMbRequest sets the MemMbRequest field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the MemMbRequest field is set to the value of the last call. -func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) WithMemMbRequest(value string) *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration { +func (b *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration) WithMemMbRequest(value string) *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration { b.MemMbRequest = &value return b } @@ -67,7 +67,7 @@ func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) W // WithNvidiaGpus sets the NvidiaGpus field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the NvidiaGpus field is set to the value of the last call. -func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) WithNvidiaGpus(value string) *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration { +func (b *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration) WithNvidiaGpus(value string) *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration { b.NvidiaGpus = &value return b } @@ -75,7 +75,7 @@ func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) W // WithAmdGpus sets the AmdGpus field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the AmdGpus field is set to the value of the last call. -func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) WithAmdGpus(value string) *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration { +func (b *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration) WithAmdGpus(value string) *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration { b.AmdGpus = &value return b } @@ -83,7 +83,7 @@ func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) W // WithPlacementConstraints sets the PlacementConstraints field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the PlacementConstraints field is set to the value of the last call. -func (b *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration) WithPlacementConstraints(value string) *WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration { +func (b *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration) WithPlacementConstraints(value string) *WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration { b.PlacementConstraints = &value return b } diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchsessioniniconfig.go b/client-go/applyconfiguration/core/v1beta1/workbenchsessioniniconfig.go index e8a7252a..0b367352 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchsessioniniconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchsessioniniconfig.go @@ -8,10 +8,11 @@ package v1beta1 // WorkbenchSessionIniConfigApplyConfiguration represents a declarative configuration of the WorkbenchSessionIniConfig type for use // with apply. type WorkbenchSessionIniConfigApplyConfiguration struct { - RSession *WorkbenchRSessionConfigApplyConfiguration `json:"rsession.conf,omitempty"` - Repos *WorkbenchRepoConfigApplyConfiguration `json:"repos.conf,omitempty"` - WorkbenchNss *WorkbenchNssConfigApplyConfiguration `json:"workbench_nss.conf,omitempty"` - Positron *WorkbenchPositronConfigApplyConfiguration `json:"positron.conf,omitempty"` + RSession *WorkbenchRSessionConfigApplyConfiguration `json:"rsession.conf,omitempty"` + Repos *WorkbenchRepoConfigApplyConfiguration `json:"repos.conf,omitempty"` + WorkbenchNss *WorkbenchNssConfigApplyConfiguration `json:"workbench_nss.conf,omitempty"` + Positron *WorkbenchPositronConfigApplyConfiguration `json:"positron.conf,omitempty"` + AdditionalConfigs map[string]string `json:"additionalConfigs,omitempty"` } // WorkbenchSessionIniConfigApplyConfiguration constructs a declarative configuration of the WorkbenchSessionIniConfig type for use with @@ -51,3 +52,17 @@ func (b *WorkbenchSessionIniConfigApplyConfiguration) WithPositron(value *Workbe b.Positron = value return b } + +// WithAdditionalConfigs puts the entries into the AdditionalConfigs field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the AdditionalConfigs field, +// overwriting an existing map entries in AdditionalConfigs field with the same key. +func (b *WorkbenchSessionIniConfigApplyConfiguration) WithAdditionalConfigs(entries map[string]string) *WorkbenchSessionIniConfigApplyConfiguration { + if b.AdditionalConfigs == nil && len(entries) > 0 { + b.AdditionalConfigs = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.AdditionalConfigs[k] = v + } + return b +} diff --git a/client-go/applyconfiguration/utils.go b/client-go/applyconfiguration/utils.go index 22670ecf..9ac9acab 100644 --- a/client-go/applyconfiguration/utils.go +++ b/client-go/applyconfiguration/utils.go @@ -209,8 +209,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &corev1beta1.WorkbenchLauncherKubernetesConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("WorkbenchLauncherKubernetesProfilesConfigSection"): return &corev1beta1.WorkbenchLauncherKubernetesProfilesConfigSectionApplyConfiguration{} - case v1beta1.SchemeGroupVersion.WithKind("WorkbenchLauncherKubnernetesResourcesConfigSection"): - return &corev1beta1.WorkbenchLauncherKubnernetesResourcesConfigSectionApplyConfiguration{} + case v1beta1.SchemeGroupVersion.WithKind("WorkbenchLauncherKubernetesResourcesConfigSection"): + return &corev1beta1.WorkbenchLauncherKubernetesResourcesConfigSectionApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("WorkbenchLauncherLocalConfig"): return &corev1beta1.WorkbenchLauncherLocalConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("WorkbenchLauncherServerConfig"): diff --git a/config/crd/bases/core.posit.team_connects.yaml b/config/crd/bases/core.posit.team_connects.yaml index 1374cf2e..93dc976c 100644 --- a/config/crd/bases/core.posit.team_connects.yaml +++ b/config/crd/bases/core.posit.team_connects.yaml @@ -423,6 +423,14 @@ spec: Logging: type: boolean type: object + additional: + additionalProperties: + type: string + description: |- + Additional allows setting arbitrary gcfg config values not covered by typed fields. + Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). + Values set here take precedence over typed fields if both specify the same key. + type: object type: object databaseConfig: properties: diff --git a/config/crd/bases/core.posit.team_packagemanagers.yaml b/config/crd/bases/core.posit.team_packagemanagers.yaml index aab63f82..a91a62d0 100644 --- a/config/crd/bases/core.posit.team_packagemanagers.yaml +++ b/config/crd/bases/core.posit.team_packagemanagers.yaml @@ -154,6 +154,14 @@ spec: Default: type: string type: object + additional: + additionalProperties: + type: string + description: |- + Additional allows setting arbitrary gcfg config values not covered by typed fields. + Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). + Values set here take precedence over typed fields if both specify the same key. + type: object type: object databaseConfig: properties: diff --git a/config/crd/bases/core.posit.team_sites.yaml b/config/crd/bases/core.posit.team_sites.yaml index 701b84c9..6acd7a76 100644 --- a/config/crd/bases/core.posit.team_sites.yaml +++ b/config/crd/bases/core.posit.team_sites.yaml @@ -76,6 +76,14 @@ spec: additionalProperties: type: string type: object + additionalConfig: + additionalProperties: + type: string + description: |- + AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. + Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). + Values set here take precedence over typed fields if both specify the same key. + type: object additionalRuntimeImages: description: |- AdditionalRuntimeImages specifies additional runtime images to append to the defaults @@ -605,6 +613,14 @@ spec: additionalProperties: type: string type: object + additionalConfig: + additionalProperties: + type: string + description: |- + AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. + Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). + Values set here take precedence over typed fields if both specify the same key. + type: object azureFiles: description: AzureFiles configures Azure Files integration for persistent storage @@ -829,6 +845,20 @@ spec: additionalProperties: type: string type: object + additionalConfigs: + additionalProperties: + type: string + description: |- + AdditionalConfigs allows appending arbitrary content to Workbench server config files. + Keys are config file names (e.g., "rserver.conf", "launcher.conf"). + type: object + additionalSessionConfigs: + additionalProperties: + type: string + description: |- + AdditionalSessionConfigs allows appending arbitrary content to Workbench session config files. + Keys are config file names (e.g., "rsession.conf", "repos.conf"). + type: object additionalVolumes: description: AdditionalVolumes represents additional VolumeSpec's that can be defined for Workbench diff --git a/config/crd/bases/core.posit.team_workbenches.yaml b/config/crd/bases/core.posit.team_workbenches.yaml index 2d66295f..b588d61d 100644 --- a/config/crd/bases/core.posit.team_workbenches.yaml +++ b/config/crd/bases/core.posit.team_workbenches.yaml @@ -219,6 +219,14 @@ spec: type: object workbench-ini-config: properties: + additionalConfigs: + additionalProperties: + type: string + description: |- + AdditionalConfigs allows appending arbitrary content to server config files. + Keys are config file names (e.g., "rserver.conf", "launcher.conf"). + Values are raw config content that gets appended after the generated config. + type: object databricks.conf: additionalProperties: properties: @@ -503,6 +511,14 @@ spec: type: object workbench-session-ini-config: properties: + additionalConfigs: + additionalProperties: + type: string + description: |- + AdditionalConfigs allows appending arbitrary content to session config files. + Keys are config file names (e.g., "rsession.conf", "repos.conf"). + Values are raw config content that gets appended after the generated config. + type: object positron.conf: properties: allow-file-downloads: diff --git a/internal/controller/core/site_controller_connect.go b/internal/controller/core/site_controller_connect.go index 92b29780..0abda532 100644 --- a/internal/controller/core/site_controller_connect.go +++ b/internal/controller/core/site_controller_connect.go @@ -218,6 +218,11 @@ func (r *SiteReconciler) reconcileConnect( } } + // Propagate additional config if configured + if site.Spec.Connect.AdditionalConfig != nil { + targetConnect.Spec.Config.Additional = site.Spec.Connect.AdditionalConfig + } + // if volumeSource.type is set, then force volume creation for Connect if site.Spec.VolumeSource.Type != v1beta1.VolumeSourceTypeNone { if targetConnect.Spec.Volume == nil { diff --git a/internal/controller/core/site_controller_package_manager.go b/internal/controller/core/site_controller_package_manager.go index af0026b2..80bb1ae4 100644 --- a/internal/controller/core/site_controller_package_manager.go +++ b/internal/controller/core/site_controller_package_manager.go @@ -113,6 +113,12 @@ func (r *SiteReconciler) reconcilePackageManager( pm.Spec.Config.S3Storage.Prefix = site.Name + "/ppm-v0" pm.Spec.Config.S3Storage.Region = product.GetAWSRegion() } + + // Propagate additional config from Site to PackageManager + if site.Spec.PackageManager.AdditionalConfig != nil { + pm.Spec.Config.Additional = site.Spec.PackageManager.AdditionalConfig + } + return nil }); err != nil { l.Error(err, "error creating package manager instance") diff --git a/internal/controller/core/site_controller_workbench.go b/internal/controller/core/site_controller_workbench.go index e5ad5b3f..eee08a1b 100644 --- a/internal/controller/core/site_controller_workbench.go +++ b/internal/controller/core/site_controller_workbench.go @@ -397,6 +397,16 @@ func (r *SiteReconciler) reconcileWorkbench( targetWorkbench.Spec.Config.WorkbenchIniConfig.Jupyter = site.Spec.Workbench.JupyterConfig } + // Propagate additional configs for server config files + if site.Spec.Workbench.AdditionalConfigs != nil { + targetWorkbench.Spec.Config.WorkbenchIniConfig.AdditionalConfigs = site.Spec.Workbench.AdditionalConfigs + } + + // Propagate additional configs for session config files + if site.Spec.Workbench.AdditionalSessionConfigs != nil { + targetWorkbench.Spec.Config.WorkbenchSessionIniConfig.AdditionalConfigs = site.Spec.Workbench.AdditionalSessionConfigs + } + // if landing/auth page is customized if site.Spec.Workbench.AuthLoginPageHtml != "" { targetWorkbench.Spec.AuthLoginPageHtml = site.Spec.Workbench.AuthLoginPageHtml @@ -435,8 +445,8 @@ func (r *SiteReconciler) reconcileWorkbench( return nil } -func defaultWorkbenchResourceProfiles() map[string]*v1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection { - return map[string]*v1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection{ +func defaultWorkbenchResourceProfiles() map[string]*v1beta1.WorkbenchLauncherKubernetesResourcesConfigSection { + return map[string]*v1beta1.WorkbenchLauncherKubernetesResourcesConfigSection{ "default": { Name: "Small", Cpus: "1", @@ -456,7 +466,7 @@ func defaultWorkbenchResourceProfiles() map[string]*v1beta1.WorkbenchLauncherKub } // getResourceProfileKeys extracts the keys from a resource profiles map -func getResourceProfileKeys(resourceProfiles map[string]*v1beta1.WorkbenchLauncherKubnernetesResourcesConfigSection) []string { +func getResourceProfileKeys(resourceProfiles map[string]*v1beta1.WorkbenchLauncherKubernetesResourcesConfigSection) []string { keys := make([]string, 0, len(resourceProfiles)) for key := range resourceProfiles { keys = append(keys, key) From 1f64c021e4dca8380f93b59f430e1f6dfdb72a86 Mon Sep 17 00:00:00 2001 From: ian-flores Date: Mon, 9 Feb 2026 13:53:51 -0800 Subject: [PATCH 2/5] fix: address review feedback - sort map keys, add nil guards, sync helm chart - Sort Additional map keys before iterating in Connect and PM GenerateGcfg() for deterministic output - Add nil guards for WorkbenchIniConfig and WorkbenchSessionIniConfig in site controller before propagating additional configs - Regenerate Helm chart CRDs to match kustomize --- api/core/v1beta1/connect_config.go | 12 ++++++-- api/core/v1beta1/package_manager_config.go | 11 ++++++- .../crd/core.posit.team_connects.yaml | 8 +++++ .../crd/core.posit.team_packagemanagers.yaml | 8 +++++ .../templates/crd/core.posit.team_sites.yaml | 30 +++++++++++++++++++ .../crd/core.posit.team_workbenches.yaml | 16 ++++++++++ .../core/site_controller_workbench.go | 6 ++++ 7 files changed, 88 insertions(+), 3 deletions(-) diff --git a/api/core/v1beta1/connect_config.go b/api/core/v1beta1/connect_config.go index 35636c63..e1a7809a 100644 --- a/api/core/v1beta1/connect_config.go +++ b/api/core/v1beta1/connect_config.go @@ -2,8 +2,8 @@ package v1beta1 import ( "fmt" - "reflect" + "sort" "strings" ) @@ -307,7 +307,15 @@ func (configStruct *ConnectConfig) GenerateGcfg() (string, error) { // Apply Additional (passthrough) overrides if configStruct.Additional != nil { - for key, value := range configStruct.Additional { + // Sort keys for deterministic output + additionalKeys := make([]string, 0, len(configStruct.Additional)) + for key := range configStruct.Additional { + additionalKeys = append(additionalKeys, key) + } + sort.Strings(additionalKeys) + + for _, key := range additionalKeys { + value := configStruct.Additional[key] parts := strings.SplitN(key, ".", 2) if len(parts) != 2 { continue // skip malformed keys diff --git a/api/core/v1beta1/package_manager_config.go b/api/core/v1beta1/package_manager_config.go index 026bfc3b..9c32f414 100644 --- a/api/core/v1beta1/package_manager_config.go +++ b/api/core/v1beta1/package_manager_config.go @@ -3,6 +3,7 @@ package v1beta1 import ( "fmt" "reflect" + "sort" "strings" ) @@ -95,7 +96,15 @@ func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { // Apply Additional (passthrough) overrides if configStruct.Additional != nil { - for key, value := range configStruct.Additional { + // Sort keys for deterministic output + additionalKeys := make([]string, 0, len(configStruct.Additional)) + for key := range configStruct.Additional { + additionalKeys = append(additionalKeys, key) + } + sort.Strings(additionalKeys) + + for _, key := range additionalKeys { + value := configStruct.Additional[key] parts := strings.SplitN(key, ".", 2) if len(parts) != 2 { continue // skip malformed keys diff --git a/dist/chart/templates/crd/core.posit.team_connects.yaml b/dist/chart/templates/crd/core.posit.team_connects.yaml index d91ce9df..bcb3e65b 100755 --- a/dist/chart/templates/crd/core.posit.team_connects.yaml +++ b/dist/chart/templates/crd/core.posit.team_connects.yaml @@ -444,6 +444,14 @@ spec: Logging: type: boolean type: object + additional: + additionalProperties: + type: string + description: |- + Additional allows setting arbitrary gcfg config values not covered by typed fields. + Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). + Values set here take precedence over typed fields if both specify the same key. + type: object type: object databaseConfig: properties: diff --git a/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml b/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml index f4834378..e2fcb626 100755 --- a/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml +++ b/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml @@ -175,6 +175,14 @@ spec: Default: type: string type: object + additional: + additionalProperties: + type: string + description: |- + Additional allows setting arbitrary gcfg config values not covered by typed fields. + Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). + Values set here take precedence over typed fields if both specify the same key. + type: object type: object databaseConfig: properties: diff --git a/dist/chart/templates/crd/core.posit.team_sites.yaml b/dist/chart/templates/crd/core.posit.team_sites.yaml index d40af799..2e92d25c 100755 --- a/dist/chart/templates/crd/core.posit.team_sites.yaml +++ b/dist/chart/templates/crd/core.posit.team_sites.yaml @@ -97,6 +97,14 @@ spec: additionalProperties: type: string type: object + additionalConfig: + additionalProperties: + type: string + description: |- + AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. + Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). + Values set here take precedence over typed fields if both specify the same key. + type: object additionalRuntimeImages: description: |- AdditionalRuntimeImages specifies additional runtime images to append to the defaults @@ -626,6 +634,14 @@ spec: additionalProperties: type: string type: object + additionalConfig: + additionalProperties: + type: string + description: |- + AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. + Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). + Values set here take precedence over typed fields if both specify the same key. + type: object azureFiles: description: AzureFiles configures Azure Files integration for persistent storage @@ -850,6 +866,20 @@ spec: additionalProperties: type: string type: object + additionalConfigs: + additionalProperties: + type: string + description: |- + AdditionalConfigs allows appending arbitrary content to Workbench server config files. + Keys are config file names (e.g., "rserver.conf", "launcher.conf"). + type: object + additionalSessionConfigs: + additionalProperties: + type: string + description: |- + AdditionalSessionConfigs allows appending arbitrary content to Workbench session config files. + Keys are config file names (e.g., "rsession.conf", "repos.conf"). + type: object additionalVolumes: description: AdditionalVolumes represents additional VolumeSpec's that can be defined for Workbench diff --git a/dist/chart/templates/crd/core.posit.team_workbenches.yaml b/dist/chart/templates/crd/core.posit.team_workbenches.yaml index f2177734..13f5f1cd 100755 --- a/dist/chart/templates/crd/core.posit.team_workbenches.yaml +++ b/dist/chart/templates/crd/core.posit.team_workbenches.yaml @@ -240,6 +240,14 @@ spec: type: object workbench-ini-config: properties: + additionalConfigs: + additionalProperties: + type: string + description: |- + AdditionalConfigs allows appending arbitrary content to server config files. + Keys are config file names (e.g., "rserver.conf", "launcher.conf"). + Values are raw config content that gets appended after the generated config. + type: object databricks.conf: additionalProperties: properties: @@ -524,6 +532,14 @@ spec: type: object workbench-session-ini-config: properties: + additionalConfigs: + additionalProperties: + type: string + description: |- + AdditionalConfigs allows appending arbitrary content to session config files. + Keys are config file names (e.g., "rsession.conf", "repos.conf"). + Values are raw config content that gets appended after the generated config. + type: object positron.conf: properties: allow-file-downloads: diff --git a/internal/controller/core/site_controller_workbench.go b/internal/controller/core/site_controller_workbench.go index eee08a1b..44986113 100644 --- a/internal/controller/core/site_controller_workbench.go +++ b/internal/controller/core/site_controller_workbench.go @@ -399,11 +399,17 @@ func (r *SiteReconciler) reconcileWorkbench( // Propagate additional configs for server config files if site.Spec.Workbench.AdditionalConfigs != nil { + if targetWorkbench.Spec.Config.WorkbenchIniConfig == nil { + targetWorkbench.Spec.Config.WorkbenchIniConfig = &v1beta1.WorkbenchIniConfig{} + } targetWorkbench.Spec.Config.WorkbenchIniConfig.AdditionalConfigs = site.Spec.Workbench.AdditionalConfigs } // Propagate additional configs for session config files if site.Spec.Workbench.AdditionalSessionConfigs != nil { + if targetWorkbench.Spec.Config.WorkbenchSessionIniConfig == nil { + targetWorkbench.Spec.Config.WorkbenchSessionIniConfig = &v1beta1.WorkbenchSessionIniConfig{} + } targetWorkbench.Spec.Config.WorkbenchSessionIniConfig.AdditionalConfigs = site.Spec.Workbench.AdditionalSessionConfigs } From 58f416320d4e0ea89fb7de1ad52a4da021487311 Mon Sep 17 00:00:00 2001 From: ian-flores Date: Mon, 9 Feb 2026 13:58:29 -0800 Subject: [PATCH 3/5] fix: remove invalid nil guards on embedded value types, run go fmt WorkbenchIniConfig and WorkbenchSessionIniConfig are embedded value types (not pointers) in WorkbenchConfig, so they cannot be nil. Remove the incorrect nil guards that caused compilation errors. Also fix formatting flagged by CI. --- api/core/v1beta1/connect_config_test.go | 22 +++++++++---------- api/core/v1beta1/workbench_config.go | 16 +++++++------- api/core/v1beta1/workbench_config_test.go | 2 +- .../core/site_controller_workbench.go | 6 ----- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/api/core/v1beta1/connect_config_test.go b/api/core/v1beta1/connect_config_test.go index 3e68e81d..1d939e47 100644 --- a/api/core/v1beta1/connect_config_test.go +++ b/api/core/v1beta1/connect_config_test.go @@ -305,10 +305,10 @@ func TestConnectConfig_AdditionalMalformedKey(t *testing.T) { // Test malformed key in Additional (no "." separator — should be skipped) cfg := ConnectConfig{ Additional: map[string]string{ - "MalformedKey": "should-be-skipped", // No section separator - "Server.ValidKey": "should-be-included", - "AnotherBadKey": "also-skipped", - "Good.Section.Key": "multi-dot-ok", // Multiple dots are OK (first is section separator) + "MalformedKey": "should-be-skipped", // No section separator + "Server.ValidKey": "should-be-included", + "AnotherBadKey": "also-skipped", + "Good.Section.Key": "multi-dot-ok", // Multiple dots are OK (first is section separator) }, } str, err := cfg.GenerateGcfg() @@ -342,18 +342,18 @@ func TestConnectConfig_AdditionalComplexScenario(t *testing.T) { }, Additional: map[string]string{ // Override existing fields - "Server.Address": "override.com", - "Http.Listen": ":8080", + "Server.Address": "override.com", + "Http.Listen": ":8080", "Applications.ScheduleConcurrency": "10", // Add new fields to existing sections - "Server.NewServerField": "server-new", - "Http.Timeout": "60", + "Server.NewServerField": "server-new", + "Http.Timeout": "60", // Add entirely new sections - "CustomSection.Field1": "value1", - "CustomSection.Field2": "value2", - "AnotherSection.Setting": "some-setting", + "CustomSection.Field1": "value1", + "CustomSection.Field2": "value2", + "AnotherSection.Setting": "some-setting", }, } str, err := cfg.GenerateGcfg() diff --git a/api/core/v1beta1/workbench_config.go b/api/core/v1beta1/workbench_config.go index 43b06505..41088780 100644 --- a/api/core/v1beta1/workbench_config.go +++ b/api/core/v1beta1/workbench_config.go @@ -522,14 +522,14 @@ type SupervisordProgramConfig struct { } type WorkbenchIniConfig struct { - Launcher *WorkbenchLauncherConfig `json:"launcher.conf,omitempty"` - VsCode *WorkbenchVsCodeConfig `json:"vscode.conf,omitempty"` - Logging *WorkbenchLoggingConfig `json:"logging.conf,omitempty"` - Jupyter *WorkbenchJupyterConfig `json:"jupyter.conf,omitempty"` - RServer *WorkbenchRServerConfig `json:"rserver.conf,omitempty"` - LauncherKubernetes *WorkbenchLauncherKubernetesConfig `json:"launcher.kubernetes.conf,omitempty"` - LauncherLocal *WorkbenchLauncherLocalConfig `json:"launcher.local.conf,omitempty"` - Databricks map[string]*WorkbenchDatabricksConfig `json:"databricks.conf,omitempty"` // TODO: DEPRECATED + Launcher *WorkbenchLauncherConfig `json:"launcher.conf,omitempty"` + VsCode *WorkbenchVsCodeConfig `json:"vscode.conf,omitempty"` + Logging *WorkbenchLoggingConfig `json:"logging.conf,omitempty"` + Jupyter *WorkbenchJupyterConfig `json:"jupyter.conf,omitempty"` + RServer *WorkbenchRServerConfig `json:"rserver.conf,omitempty"` + LauncherKubernetes *WorkbenchLauncherKubernetesConfig `json:"launcher.kubernetes.conf,omitempty"` + LauncherLocal *WorkbenchLauncherLocalConfig `json:"launcher.local.conf,omitempty"` + Databricks map[string]*WorkbenchDatabricksConfig `json:"databricks.conf,omitempty"` // TODO: DEPRECATED Resources map[string]*WorkbenchLauncherKubernetesResourcesConfigSection `json:"launcher.kubernetes.resources.conf,omitempty"` // AdditionalConfigs allows appending arbitrary content to server config files. diff --git a/api/core/v1beta1/workbench_config_test.go b/api/core/v1beta1/workbench_config_test.go index 8347690e..8ddb0e67 100644 --- a/api/core/v1beta1/workbench_config_test.go +++ b/api/core/v1beta1/workbench_config_test.go @@ -249,7 +249,7 @@ func TestWorkbenchIniConfig_AdditionalConfigs(t *testing.T) { wb := WorkbenchIniConfig{ RServer: &WorkbenchRServerConfig{ AdminEnabled: 1, - AdminGroup: "workbench-admin", + AdminGroup: "workbench-admin", }, AdditionalConfigs: map[string]string{ "rserver.conf": "custom-option=value\nanother-option=123\n", diff --git a/internal/controller/core/site_controller_workbench.go b/internal/controller/core/site_controller_workbench.go index 44986113..eee08a1b 100644 --- a/internal/controller/core/site_controller_workbench.go +++ b/internal/controller/core/site_controller_workbench.go @@ -399,17 +399,11 @@ func (r *SiteReconciler) reconcileWorkbench( // Propagate additional configs for server config files if site.Spec.Workbench.AdditionalConfigs != nil { - if targetWorkbench.Spec.Config.WorkbenchIniConfig == nil { - targetWorkbench.Spec.Config.WorkbenchIniConfig = &v1beta1.WorkbenchIniConfig{} - } targetWorkbench.Spec.Config.WorkbenchIniConfig.AdditionalConfigs = site.Spec.Workbench.AdditionalConfigs } // Propagate additional configs for session config files if site.Spec.Workbench.AdditionalSessionConfigs != nil { - if targetWorkbench.Spec.Config.WorkbenchSessionIniConfig == nil { - targetWorkbench.Spec.Config.WorkbenchSessionIniConfig = &v1beta1.WorkbenchSessionIniConfig{} - } targetWorkbench.Spec.Config.WorkbenchSessionIniConfig.AdditionalConfigs = site.Spec.Workbench.AdditionalSessionConfigs } From 2be99b9d7749cb1789754075157eeaaf04ad75f8 Mon Sep 17 00:00:00 2001 From: ian-flores Date: Thu, 19 Feb 2026 12:14:13 -0800 Subject: [PATCH 4/5] refactor: switch Connect/PM passthrough config from dot-notation map to string appending Replace the Section.Key map approach with a simple string field that gets appended verbatim to the generated gcfg output, consistent with how Workbench already handles passthrough. gcfg naturally handles conflicts (last-write-wins for scalars, concatenation for lists), so the complex section-merge logic is no longer needed. Also renames the field from `additional` to `additionalConfig` for naming consistency with the site-level spec fields. --- api/core/v1beta1/connect_config.go | 60 ++----- api/core/v1beta1/connect_config_test.go | 170 ++---------------- api/core/v1beta1/package_manager_config.go | 60 ++----- .../v1beta1/package_manager_config_test.go | 86 ++------- api/core/v1beta1/site_types.go | 12 +- api/core/v1beta1/zz_generated.deepcopy.go | 28 --- .../core/site_controller_connect.go | 4 +- .../core/site_controller_package_manager.go | 4 +- 8 files changed, 58 insertions(+), 366 deletions(-) diff --git a/api/core/v1beta1/connect_config.go b/api/core/v1beta1/connect_config.go index e1a7809a..11676b0e 100644 --- a/api/core/v1beta1/connect_config.go +++ b/api/core/v1beta1/connect_config.go @@ -3,7 +3,6 @@ package v1beta1 import ( "fmt" "reflect" - "sort" "strings" ) @@ -32,11 +31,11 @@ type ConnectConfig struct { RPackageRepository map[string]RPackageRepositoryConfig `json:"RPackageRepositories,omitempty"` TableauIntegration *ConnectTableauIntegrationConfig `json:"TableauIntegration,omitempty"` - // Additional allows setting arbitrary gcfg config values not covered by typed fields. - // Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). - // Values set here take precedence over typed fields if both specify the same key. + // AdditionalConfig allows appending arbitrary gcfg config content not covered by typed fields. + // The value is appended verbatim after the generated config. gcfg parsing naturally handles + // conflicts: list values are combined, scalar values use the last occurrence. // +optional - Additional map[string]string `json:"additional,omitempty"` + AdditionalConfig string `json:"additionalConfig,omitempty"` } type RPackageRepositoryConfig struct { @@ -228,8 +227,8 @@ func (configStruct *ConnectConfig) GenerateGcfg() (string, error) { fieldName := configStructVals.Type().Field(i).Name fieldValue := configStructVals.Field(i) - // Skip the Additional map — we handle it after typed fields - if fieldName == "Additional" { + // Skip the AdditionalConfig string — we handle it at the end + if fieldName == "AdditionalConfig" { continue } @@ -305,49 +304,6 @@ func (configStruct *ConnectConfig) GenerateGcfg() (string, error) { } } - // Apply Additional (passthrough) overrides - if configStruct.Additional != nil { - // Sort keys for deterministic output - additionalKeys := make([]string, 0, len(configStruct.Additional)) - for key := range configStruct.Additional { - additionalKeys = append(additionalKeys, key) - } - sort.Strings(additionalKeys) - - for _, key := range additionalKeys { - value := configStruct.Additional[key] - parts := strings.SplitN(key, ".", 2) - if len(parts) != 2 { - continue // skip malformed keys - } - sectionName := parts[0] - keyName := parts[1] - - if idx, ok := sectionIndex[sectionName]; ok { - // Override or add to existing section - if _, exists := sections[idx].values[keyName]; !exists { - // Check if it's overriding a slice key - if _, sliceExists := sections[idx].slices[keyName]; !sliceExists { - sections[idx].keys = append(sections[idx].keys, keyName) - } - } - // Remove from slices if it was a multi-value key (passthrough replaces it) - delete(sections[idx].slices, keyName) - sections[idx].values[keyName] = value - } else { - // Create new section - entry := sectionEntry{ - name: sectionName, - keys: []string{keyName}, - values: map[string]string{keyName: value}, - slices: map[string][]string{}, - } - sectionIndex[sectionName] = len(sections) - sections = append(sections, entry) - } - } - } - // Render sections to gcfg format for _, section := range sections { builder.WriteString("\n[" + section.name + "]\n") @@ -362,5 +318,9 @@ func (configStruct *ConnectConfig) GenerateGcfg() (string, error) { } } + if configStruct.AdditionalConfig != "" { + builder.WriteString(configStruct.AdditionalConfig) + } + return builder.String(), nil } diff --git a/api/core/v1beta1/connect_config_test.go b/api/core/v1beta1/connect_config_test.go index 1d939e47..21449f0b 100644 --- a/api/core/v1beta1/connect_config_test.go +++ b/api/core/v1beta1/connect_config_test.go @@ -211,184 +211,46 @@ func TestConnectConfig_CustomScope(t *testing.T) { require.NotContains(t, str, "CustomScope") } -func TestConnectConfig_AdditionalPassthrough(t *testing.T) { - // Test passthrough-only values (new section and key via Additional) +func TestConnectConfig_AdditionalConfig(t *testing.T) { + // Test basic string append cfg := ConnectConfig{ - Additional: map[string]string{ - "NewSection.NewKey": "custom-value", - "Server.CustomServerField": "server-custom", - "Database.Timeout": "30", + Server: &ConnectServerConfig{ + Address: "some-address.com", }, + AdditionalConfig: "\n[NewSection]\nNewKey = custom-value\n", } str, err := cfg.GenerateGcfg() require.Nil(t, err) - t.Logf("Generated gcfg with passthrough-only:\n%s", str) - - // Check that passthrough values are present + require.Contains(t, str, "[Server]") + require.Contains(t, str, "Address = some-address.com") require.Contains(t, str, "[NewSection]") require.Contains(t, str, "NewKey = custom-value") - require.Contains(t, str, "[Server]") - require.Contains(t, str, "CustomServerField = server-custom") - require.Contains(t, str, "[Database]") - require.Contains(t, str, "Timeout = 30") } -func TestConnectConfig_AdditionalOverride(t *testing.T) { - // Test override behavior (typed field + same key in Additional, passthrough wins) +func TestConnectConfig_AdditionalConfigOverride(t *testing.T) { + // gcfg last-write-wins: appended scalar overrides typed field cfg := ConnectConfig{ Server: &ConnectServerConfig{ - Address: "typed-address.com", - HideEmailAddresses: false, - DefaultContentListView: ContentListViewExpanded, - }, - Additional: map[string]string{ - "Server.Address": "passthrough-address.com", // Should override typed - "Server.HideEmailAddresses": "true", // Should override typed - "Server.DefaultContentListView": "card", // Should override typed - "Server.CustomField": "custom-value", // New field + Address: "typed-address.com", }, + AdditionalConfig: "\n[Server]\nAddress = passthrough-address.com\n", } str, err := cfg.GenerateGcfg() require.Nil(t, err) - t.Logf("Generated gcfg with overrides:\n%s", str) - - // Check that passthrough values override typed values - require.Contains(t, str, "[Server]") + // Both values appear in the output; gcfg uses last occurrence + require.Contains(t, str, "Address = typed-address.com") require.Contains(t, str, "Address = passthrough-address.com") - require.Contains(t, str, "HideEmailAddresses = true") - require.Contains(t, str, "DefaultContentListView = card") - require.Contains(t, str, "CustomField = custom-value") - - // Original typed values should not be present - require.NotContains(t, str, "Address = typed-address.com") - require.NotContains(t, str, "HideEmailAddresses = false") - require.NotContains(t, str, "DefaultContentListView = expanded") } -func TestConnectConfig_AdditionalEmpty(t *testing.T) { - // Test empty Additional map (no effect on output) +func TestConnectConfig_AdditionalConfigEmpty(t *testing.T) { + // Empty string has no effect cfg := ConnectConfig{ Server: &ConnectServerConfig{ Address: "some-address.com", }, - Additional: map[string]string{}, // Empty map + AdditionalConfig: "", } str, err := cfg.GenerateGcfg() require.Nil(t, err) - t.Logf("Generated gcfg with empty Additional:\n%s", str) - - // Should contain normal typed fields - require.Contains(t, str, "[Server]") require.Contains(t, str, "Address = some-address.com") - - // Should not have any extra sections or fields - require.Equal(t, 1, countOccurrences(str, "[Server]")) -} - -func TestConnectConfig_AdditionalNil(t *testing.T) { - // Test nil Additional map (no effect on output) - cfg := ConnectConfig{ - Server: &ConnectServerConfig{ - Address: "some-address.com", - }, - Additional: nil, // Nil map - } - str, err := cfg.GenerateGcfg() - require.Nil(t, err) - - // Should contain normal typed fields - require.Contains(t, str, "[Server]") - require.Contains(t, str, "Address = some-address.com") -} - -func TestConnectConfig_AdditionalMalformedKey(t *testing.T) { - // Test malformed key in Additional (no "." separator — should be skipped) - cfg := ConnectConfig{ - Additional: map[string]string{ - "MalformedKey": "should-be-skipped", // No section separator - "Server.ValidKey": "should-be-included", - "AnotherBadKey": "also-skipped", - "Good.Section.Key": "multi-dot-ok", // Multiple dots are OK (first is section separator) - }, - } - str, err := cfg.GenerateGcfg() - require.Nil(t, err) - t.Logf("Generated gcfg with malformed keys:\n%s", str) - - // Valid keys should be present - require.Contains(t, str, "[Server]") - require.Contains(t, str, "ValidKey = should-be-included") - require.Contains(t, str, "[Good]") - require.Contains(t, str, "Section.Key = multi-dot-ok") - - // Malformed keys should be skipped - require.NotContains(t, str, "MalformedKey") - require.NotContains(t, str, "should-be-skipped") - require.NotContains(t, str, "AnotherBadKey") - require.NotContains(t, str, "also-skipped") -} - -func TestConnectConfig_AdditionalComplexScenario(t *testing.T) { - // Test complex scenario with multiple sections, overrides, and new fields - cfg := ConnectConfig{ - Server: &ConnectServerConfig{ - Address: "original.com", - }, - Http: &ConnectHttpConfig{ - Listen: ":3939", - }, - Applications: &ConnectApplicationsConfig{ - ScheduleConcurrency: 2, - }, - Additional: map[string]string{ - // Override existing fields - "Server.Address": "override.com", - "Http.Listen": ":8080", - "Applications.ScheduleConcurrency": "10", - - // Add new fields to existing sections - "Server.NewServerField": "server-new", - "Http.Timeout": "60", - - // Add entirely new sections - "CustomSection.Field1": "value1", - "CustomSection.Field2": "value2", - "AnotherSection.Setting": "some-setting", - }, - } - str, err := cfg.GenerateGcfg() - require.Nil(t, err) - t.Logf("Generated complex gcfg:\n%s", str) - - // Check overrides - require.Contains(t, str, "Address = override.com") - require.Contains(t, str, "Listen = :8080") - require.Contains(t, str, "ScheduleConcurrency = 10") - - // Check new fields in existing sections - require.Contains(t, str, "NewServerField = server-new") - require.Contains(t, str, "Timeout = 60") - - // Check new sections - require.Contains(t, str, "[CustomSection]") - require.Contains(t, str, "Field1 = value1") - require.Contains(t, str, "Field2 = value2") - require.Contains(t, str, "[AnotherSection]") - require.Contains(t, str, "Setting = some-setting") - - // Original values should not be present (they were overridden) - require.NotContains(t, str, "Address = original.com") - require.NotContains(t, str, "Listen = :3939") - require.NotContains(t, str, "ScheduleConcurrency = 2") -} - -// Helper function to count occurrences of a substring -func countOccurrences(str, substr string) int { - count := 0 - for i := 0; i+len(substr) <= len(str); i++ { - if str[i:i+len(substr)] == substr { - count++ - } - } - return count } diff --git a/api/core/v1beta1/package_manager_config.go b/api/core/v1beta1/package_manager_config.go index 9c32f414..b6dabf5d 100644 --- a/api/core/v1beta1/package_manager_config.go +++ b/api/core/v1beta1/package_manager_config.go @@ -3,7 +3,6 @@ package v1beta1 import ( "fmt" "reflect" - "sort" "strings" ) @@ -20,11 +19,11 @@ type PackageManagerConfig struct { Cran *PackageManagerCRANConfig `json:"CRAN,omitempty"` Debug *PackageManagerDebugConfig `json:"Debug,omitempty"` - // Additional allows setting arbitrary gcfg config values not covered by typed fields. - // Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). - // Values set here take precedence over typed fields if both specify the same key. + // AdditionalConfig allows appending arbitrary gcfg config content not covered by typed fields. + // The value is appended verbatim after the generated config. gcfg parsing naturally handles + // conflicts: list values are combined, scalar values use the last occurrence. // +optional - Additional map[string]string `json:"additional,omitempty"` + AdditionalConfig string `json:"additionalConfig,omitempty"` } func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { @@ -49,8 +48,8 @@ func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { fieldName := configStructVals.Type().Field(i).Name fieldValue := configStructVals.Field(i) - // Skip the Additional map — we handle it after typed fields - if fieldName == "Additional" { + // Skip the AdditionalConfig string — we handle it at the end + if fieldName == "AdditionalConfig" { continue } @@ -94,49 +93,6 @@ func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { sections = append(sections, entry) } - // Apply Additional (passthrough) overrides - if configStruct.Additional != nil { - // Sort keys for deterministic output - additionalKeys := make([]string, 0, len(configStruct.Additional)) - for key := range configStruct.Additional { - additionalKeys = append(additionalKeys, key) - } - sort.Strings(additionalKeys) - - for _, key := range additionalKeys { - value := configStruct.Additional[key] - parts := strings.SplitN(key, ".", 2) - if len(parts) != 2 { - continue // skip malformed keys - } - sectionName := parts[0] - keyName := parts[1] - - if idx, ok := sectionIndex[sectionName]; ok { - // Override or add to existing section - if _, exists := sections[idx].values[keyName]; !exists { - // Check if it's overriding a slice key - if _, sliceExists := sections[idx].slices[keyName]; !sliceExists { - sections[idx].keys = append(sections[idx].keys, keyName) - } - } - // Remove from slices if it was a multi-value key (passthrough replaces it) - delete(sections[idx].slices, keyName) - sections[idx].values[keyName] = value - } else { - // Create new section - entry := sectionEntry{ - name: sectionName, - keys: []string{keyName}, - values: map[string]string{keyName: value}, - slices: map[string][]string{}, - } - sectionIndex[sectionName] = len(sections) - sections = append(sections, entry) - } - } - } - // Render sections to gcfg format for _, section := range sections { builder.WriteString("\n[" + section.name + "]\n") @@ -151,6 +107,10 @@ func (configStruct *PackageManagerConfig) GenerateGcfg() (string, error) { } } + if configStruct.AdditionalConfig != "" { + builder.WriteString(configStruct.AdditionalConfig) + } + return builder.String(), nil } diff --git a/api/core/v1beta1/package_manager_config_test.go b/api/core/v1beta1/package_manager_config_test.go index 9baae20a..ddf37a91 100644 --- a/api/core/v1beta1/package_manager_config_test.go +++ b/api/core/v1beta1/package_manager_config_test.go @@ -35,84 +35,30 @@ func TestPackageManagerConfig_GenerateGcfg(t *testing.T) { require.Contains(t, str, "/another/path") } -func TestPackageManagerConfig_GenerateGcfg_WithAdditional(t *testing.T) { - // Test passthrough-only: Additional sets a value in a new section - pmCfg := PackageManagerConfig{ - Additional: map[string]string{ - "NewSection.SomeKey": "some-value", - }, - } - str, err := pmCfg.GenerateGcfg() - require.Nil(t, err) - require.Contains(t, str, "[NewSection]") - require.Contains(t, str, "SomeKey = some-value") - - // Test passthrough override: typed field + Additional for same key - pmCfg = PackageManagerConfig{ - Server: &PackageManagerServerConfig{ - LauncherDir: "/typed/path", - }, - Additional: map[string]string{ - "Server.LauncherDir": "/passthrough/path", - }, - } - str, err = pmCfg.GenerateGcfg() - require.Nil(t, err) - require.Contains(t, str, "LauncherDir = /passthrough/path") - require.NotContains(t, str, "/typed/path") // passthrough wins - - // Test empty Additional map - pmCfg = PackageManagerConfig{ - Server: &PackageManagerServerConfig{ - LauncherDir: "/test/path", - }, - Additional: map[string]string{}, - } - str, err = pmCfg.GenerateGcfg() - require.Nil(t, err) - require.Contains(t, str, "LauncherDir = /test/path") - - // Test Additional adding to existing section - pmCfg = PackageManagerConfig{ +func TestPackageManagerConfig_AdditionalConfig(t *testing.T) { + // Test basic string append + cfg := PackageManagerConfig{ Server: &PackageManagerServerConfig{ - LauncherDir: "/test/path", - }, - Additional: map[string]string{ - "Server.DataDir": "/data/dir", + Address: "some-address.com", }, + AdditionalConfig: "\n[NewSection]\nNewKey = custom-value\n", } - str, err = pmCfg.GenerateGcfg() + str, err := cfg.GenerateGcfg() require.Nil(t, err) require.Contains(t, str, "[Server]") - require.Contains(t, str, "LauncherDir = /test/path") - require.Contains(t, str, "DataDir = /data/dir") - - // Test malformed key (no ".") - should be skipped gracefully - pmCfg = PackageManagerConfig{ - Server: &PackageManagerServerConfig{ - LauncherDir: "/test/path", - }, - Additional: map[string]string{ - "InvalidKey": "some-value", - }, - } - str, err = pmCfg.GenerateGcfg() - require.Nil(t, err) - require.NotContains(t, str, "InvalidKey") - require.Contains(t, str, "LauncherDir = /test/path") + require.Contains(t, str, "Address = some-address.com") + require.Contains(t, str, "[NewSection]") + require.Contains(t, str, "NewKey = custom-value") +} - // Test passthrough with multiple values - pmCfg = PackageManagerConfig{ +func TestPackageManagerConfig_AdditionalConfigEmpty(t *testing.T) { + cfg := PackageManagerConfig{ Server: &PackageManagerServerConfig{ - RVersion: []string{"/opt/R/4.2.0", "/opt/R/4.3.0"}, - }, - Additional: map[string]string{ - "Server.RVersion": "/opt/R/custom", + Address: "some-address.com", }, + AdditionalConfig: "", } - str, err = pmCfg.GenerateGcfg() + str, err := cfg.GenerateGcfg() require.Nil(t, err) - require.Contains(t, str, "RVersion = /opt/R/custom") - require.NotContains(t, str, "/opt/R/4.2.0") - require.NotContains(t, str, "/opt/R/4.3.0") + require.Contains(t, str, "Address = some-address.com") } diff --git a/api/core/v1beta1/site_types.go b/api/core/v1beta1/site_types.go index ac34e5d4..d224862f 100644 --- a/api/core/v1beta1/site_types.go +++ b/api/core/v1beta1/site_types.go @@ -222,11 +222,9 @@ type InternalPackageManagerSpec struct { // +optional AzureFiles *AzureFilesConfig `json:"azureFiles,omitempty"` - // AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. - // Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). - // Values set here take precedence over typed fields if both specify the same key. + // AdditionalConfig allows appending arbitrary gcfg config content to the generated config. // +optional - AdditionalConfig map[string]string `json:"additionalConfig,omitempty"` + AdditionalConfig string `json:"additionalConfig,omitempty"` } type InternalConnectSpec struct { @@ -278,11 +276,9 @@ type InternalConnectSpec struct { // +optional AdditionalRuntimeImages []ConnectRuntimeImageSpec `json:"additionalRuntimeImages,omitempty"` - // AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. - // Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). - // Values set here take precedence over typed fields if both specify the same key. + // AdditionalConfig allows appending arbitrary gcfg config content to the generated config. // +optional - AdditionalConfig map[string]string `json:"additionalConfig,omitempty"` + AdditionalConfig string `json:"additionalConfig,omitempty"` } type DatabaseSettings struct { diff --git a/api/core/v1beta1/zz_generated.deepcopy.go b/api/core/v1beta1/zz_generated.deepcopy.go index 87375552..ae50e49d 100644 --- a/api/core/v1beta1/zz_generated.deepcopy.go +++ b/api/core/v1beta1/zz_generated.deepcopy.go @@ -505,13 +505,6 @@ func (in *ConnectConfig) DeepCopyInto(out *ConnectConfig) { *out = new(ConnectTableauIntegrationConfig) **out = **in } - if in.Additional != nil { - in, out := &in.Additional, &out.Additional - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectConfig. @@ -1144,13 +1137,6 @@ func (in *InternalConnectSpec) DeepCopyInto(out *InternalConnectSpec) { *out = make([]ConnectRuntimeImageSpec, len(*in)) copy(*out, *in) } - if in.AdditionalConfig != nil { - in, out := &in.AdditionalConfig, &out.AdditionalConfig - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalConnectSpec. @@ -1241,13 +1227,6 @@ func (in *InternalPackageManagerSpec) DeepCopyInto(out *InternalPackageManagerSp *out = new(AzureFilesConfig) **out = **in } - if in.AdditionalConfig != nil { - in, out := &in.AdditionalConfig, &out.AdditionalConfig - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InternalPackageManagerSpec. @@ -1556,13 +1535,6 @@ func (in *PackageManagerConfig) DeepCopyInto(out *PackageManagerConfig) { *out = new(PackageManagerDebugConfig) **out = **in } - if in.Additional != nil { - in, out := &in.Additional, &out.Additional - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageManagerConfig. diff --git a/internal/controller/core/site_controller_connect.go b/internal/controller/core/site_controller_connect.go index 0abda532..4a7e0aeb 100644 --- a/internal/controller/core/site_controller_connect.go +++ b/internal/controller/core/site_controller_connect.go @@ -219,9 +219,7 @@ func (r *SiteReconciler) reconcileConnect( } // Propagate additional config if configured - if site.Spec.Connect.AdditionalConfig != nil { - targetConnect.Spec.Config.Additional = site.Spec.Connect.AdditionalConfig - } + targetConnect.Spec.Config.AdditionalConfig = site.Spec.Connect.AdditionalConfig // if volumeSource.type is set, then force volume creation for Connect if site.Spec.VolumeSource.Type != v1beta1.VolumeSourceTypeNone { diff --git a/internal/controller/core/site_controller_package_manager.go b/internal/controller/core/site_controller_package_manager.go index 80bb1ae4..bca0a645 100644 --- a/internal/controller/core/site_controller_package_manager.go +++ b/internal/controller/core/site_controller_package_manager.go @@ -115,9 +115,7 @@ func (r *SiteReconciler) reconcilePackageManager( } // Propagate additional config from Site to PackageManager - if site.Spec.PackageManager.AdditionalConfig != nil { - pm.Spec.Config.Additional = site.Spec.PackageManager.AdditionalConfig - } + pm.Spec.Config.AdditionalConfig = site.Spec.PackageManager.AdditionalConfig return nil }); err != nil { From b176b8038a77e3c958da999fcf5a1bea4025ec57 Mon Sep 17 00:00:00 2001 From: ian-flores Date: Thu, 19 Feb 2026 12:34:51 -0800 Subject: [PATCH 5/5] chore: regenerate CRDs and client-go for additionalConfig string type change --- .../core/v1beta1/connectconfig.go | 18 +++------ .../core/v1beta1/internalconnectspec.go | 18 +++------ .../v1beta1/internalpackagemanagerspec.go | 18 +++------ .../core/v1beta1/packagemanagerconfig.go | 40 ++++++++----------- .../crd/bases/core.posit.team_connects.yaml | 12 +++--- .../core.posit.team_packagemanagers.yaml | 12 +++--- config/crd/bases/core.posit.team_sites.yaml | 20 +++------- .../crd/core.posit.team_connects.yaml | 12 +++--- .../crd/core.posit.team_packagemanagers.yaml | 12 +++--- .../templates/crd/core.posit.team_sites.yaml | 20 +++------- 10 files changed, 67 insertions(+), 115 deletions(-) diff --git a/client-go/applyconfiguration/core/v1beta1/connectconfig.go b/client-go/applyconfiguration/core/v1beta1/connectconfig.go index ee286a86..8be73222 100644 --- a/client-go/applyconfiguration/core/v1beta1/connectconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/connectconfig.go @@ -26,7 +26,7 @@ type ConnectConfigApplyConfiguration struct { Scheduler *ConnectSchedulerConfigApplyConfiguration `json:"Scheduler,omitempty"` RPackageRepository map[string]RPackageRepositoryConfigApplyConfiguration `json:"RPackageRepositories,omitempty"` TableauIntegration *ConnectTableauIntegrationConfigApplyConfiguration `json:"TableauIntegration,omitempty"` - Additional map[string]string `json:"additional,omitempty"` + AdditionalConfig *string `json:"additionalConfig,omitempty"` } // ConnectConfigApplyConfiguration constructs a declarative configuration of the ConnectConfig type for use with @@ -185,16 +185,10 @@ func (b *ConnectConfigApplyConfiguration) WithTableauIntegration(value *ConnectT return b } -// WithAdditional puts the entries into the Additional field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Additional field, -// overwriting an existing map entries in Additional field with the same key. -func (b *ConnectConfigApplyConfiguration) WithAdditional(entries map[string]string) *ConnectConfigApplyConfiguration { - if b.Additional == nil && len(entries) > 0 { - b.Additional = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.Additional[k] = v - } +// WithAdditionalConfig sets the AdditionalConfig field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AdditionalConfig field is set to the value of the last call. +func (b *ConnectConfigApplyConfiguration) WithAdditionalConfig(value string) *ConnectConfigApplyConfiguration { + b.AdditionalConfig = &value return b } diff --git a/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go b/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go index 6e86a0c8..621f46c4 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go @@ -33,7 +33,7 @@ type InternalConnectSpecApplyConfiguration struct { DatabaseSettings *DatabaseSettingsApplyConfiguration `json:"databaseSettings,omitempty"` ScheduleConcurrency *int `json:"scheduleConcurrency,omitempty"` AdditionalRuntimeImages []ConnectRuntimeImageSpecApplyConfiguration `json:"additionalRuntimeImages,omitempty"` - AdditionalConfig map[string]string `json:"additionalConfig,omitempty"` + AdditionalConfig *string `json:"additionalConfig,omitempty"` } // InternalConnectSpecApplyConfiguration constructs a declarative configuration of the InternalConnectSpec type for use with @@ -219,16 +219,10 @@ func (b *InternalConnectSpecApplyConfiguration) WithAdditionalRuntimeImages(valu return b } -// WithAdditionalConfig puts the entries into the AdditionalConfig field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the AdditionalConfig field, -// overwriting an existing map entries in AdditionalConfig field with the same key. -func (b *InternalConnectSpecApplyConfiguration) WithAdditionalConfig(entries map[string]string) *InternalConnectSpecApplyConfiguration { - if b.AdditionalConfig == nil && len(entries) > 0 { - b.AdditionalConfig = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.AdditionalConfig[k] = v - } +// WithAdditionalConfig sets the AdditionalConfig field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AdditionalConfig field is set to the value of the last call. +func (b *InternalConnectSpecApplyConfiguration) WithAdditionalConfig(value string) *InternalConnectSpecApplyConfiguration { + b.AdditionalConfig = &value return b } diff --git a/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go b/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go index 8ac49e39..c9e12970 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalpackagemanagerspec.go @@ -25,7 +25,7 @@ type InternalPackageManagerSpecApplyConfiguration struct { BaseDomain *string `json:"baseDomain,omitempty"` GitSSHKeys []SSHKeyConfigApplyConfiguration `json:"gitSSHKeys,omitempty"` AzureFiles *AzureFilesConfigApplyConfiguration `json:"azureFiles,omitempty"` - AdditionalConfig map[string]string `json:"additionalConfig,omitempty"` + AdditionalConfig *string `json:"additionalConfig,omitempty"` } // InternalPackageManagerSpecApplyConfiguration constructs a declarative configuration of the InternalPackageManagerSpec type for use with @@ -147,16 +147,10 @@ func (b *InternalPackageManagerSpecApplyConfiguration) WithAzureFiles(value *Azu return b } -// WithAdditionalConfig puts the entries into the AdditionalConfig field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the AdditionalConfig field, -// overwriting an existing map entries in AdditionalConfig field with the same key. -func (b *InternalPackageManagerSpecApplyConfiguration) WithAdditionalConfig(entries map[string]string) *InternalPackageManagerSpecApplyConfiguration { - if b.AdditionalConfig == nil && len(entries) > 0 { - b.AdditionalConfig = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.AdditionalConfig[k] = v - } +// WithAdditionalConfig sets the AdditionalConfig field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AdditionalConfig field is set to the value of the last call. +func (b *InternalPackageManagerSpecApplyConfiguration) WithAdditionalConfig(value string) *InternalPackageManagerSpecApplyConfiguration { + b.AdditionalConfig = &value return b } diff --git a/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go b/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go index 27e0cecf..c853572d 100644 --- a/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/packagemanagerconfig.go @@ -8,18 +8,18 @@ package v1beta1 // PackageManagerConfigApplyConfiguration represents a declarative configuration of the PackageManagerConfig type for use // with apply. type PackageManagerConfigApplyConfiguration struct { - Server *PackageManagerServerConfigApplyConfiguration `json:"Server,omitempty"` - Http *PackageManagerHttpConfigApplyConfiguration `json:"Http,omitempty"` - Git *PackageManagerGitConfigApplyConfiguration `json:"Git,omitempty"` - Database *PackageManagerDatabaseConfigApplyConfiguration `json:"Database,omitempty"` - Postgres *PackageManagerPostgresConfigApplyConfiguration `json:"Postgres,omitempty"` - Storage *PackageManagerStorageConfigApplyConfiguration `json:"Storage,omitempty"` - S3Storage *PackageManagerS3StorageConfigApplyConfiguration `json:"S3Storage,omitempty"` - Metrics *PackageManagerMetricsConfigApplyConfiguration `json:"Metrics,omitempty"` - Repos *PackageManagerReposConfigApplyConfiguration `json:"Repos,omitempty"` - Cran *PackageManagerCRANConfigApplyConfiguration `json:"CRAN,omitempty"` - Debug *PackageManagerDebugConfigApplyConfiguration `json:"Debug,omitempty"` - Additional map[string]string `json:"additional,omitempty"` + Server *PackageManagerServerConfigApplyConfiguration `json:"Server,omitempty"` + Http *PackageManagerHttpConfigApplyConfiguration `json:"Http,omitempty"` + Git *PackageManagerGitConfigApplyConfiguration `json:"Git,omitempty"` + Database *PackageManagerDatabaseConfigApplyConfiguration `json:"Database,omitempty"` + Postgres *PackageManagerPostgresConfigApplyConfiguration `json:"Postgres,omitempty"` + Storage *PackageManagerStorageConfigApplyConfiguration `json:"Storage,omitempty"` + S3Storage *PackageManagerS3StorageConfigApplyConfiguration `json:"S3Storage,omitempty"` + Metrics *PackageManagerMetricsConfigApplyConfiguration `json:"Metrics,omitempty"` + Repos *PackageManagerReposConfigApplyConfiguration `json:"Repos,omitempty"` + Cran *PackageManagerCRANConfigApplyConfiguration `json:"CRAN,omitempty"` + Debug *PackageManagerDebugConfigApplyConfiguration `json:"Debug,omitempty"` + AdditionalConfig *string `json:"additionalConfig,omitempty"` } // PackageManagerConfigApplyConfiguration constructs a declarative configuration of the PackageManagerConfig type for use with @@ -116,16 +116,10 @@ func (b *PackageManagerConfigApplyConfiguration) WithDebug(value *PackageManager return b } -// WithAdditional puts the entries into the Additional field in the declarative configuration -// and returns the receiver, so that objects can be build by chaining "With" function invocations. -// If called multiple times, the entries provided by each call will be put on the Additional field, -// overwriting an existing map entries in Additional field with the same key. -func (b *PackageManagerConfigApplyConfiguration) WithAdditional(entries map[string]string) *PackageManagerConfigApplyConfiguration { - if b.Additional == nil && len(entries) > 0 { - b.Additional = make(map[string]string, len(entries)) - } - for k, v := range entries { - b.Additional[k] = v - } +// WithAdditionalConfig sets the AdditionalConfig field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AdditionalConfig field is set to the value of the last call. +func (b *PackageManagerConfigApplyConfiguration) WithAdditionalConfig(value string) *PackageManagerConfigApplyConfiguration { + b.AdditionalConfig = &value return b } diff --git a/config/crd/bases/core.posit.team_connects.yaml b/config/crd/bases/core.posit.team_connects.yaml index a5a06124..8e341377 100644 --- a/config/crd/bases/core.posit.team_connects.yaml +++ b/config/crd/bases/core.posit.team_connects.yaml @@ -423,14 +423,12 @@ spec: Logging: type: boolean type: object - additional: - additionalProperties: - type: string + additionalConfig: description: |- - Additional allows setting arbitrary gcfg config values not covered by typed fields. - Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). - Values set here take precedence over typed fields if both specify the same key. - type: object + AdditionalConfig allows appending arbitrary gcfg config content not covered by typed fields. + The value is appended verbatim after the generated config. gcfg parsing naturally handles + conflicts: list values are combined, scalar values use the last occurrence. + type: string type: object databaseConfig: properties: diff --git a/config/crd/bases/core.posit.team_packagemanagers.yaml b/config/crd/bases/core.posit.team_packagemanagers.yaml index a91a62d0..72619fa8 100644 --- a/config/crd/bases/core.posit.team_packagemanagers.yaml +++ b/config/crd/bases/core.posit.team_packagemanagers.yaml @@ -154,14 +154,12 @@ spec: Default: type: string type: object - additional: - additionalProperties: - type: string + additionalConfig: description: |- - Additional allows setting arbitrary gcfg config values not covered by typed fields. - Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). - Values set here take precedence over typed fields if both specify the same key. - type: object + AdditionalConfig allows appending arbitrary gcfg config content not covered by typed fields. + The value is appended verbatim after the generated config. gcfg parsing naturally handles + conflicts: list values are combined, scalar values use the last occurrence. + type: string type: object databaseConfig: properties: diff --git a/config/crd/bases/core.posit.team_sites.yaml b/config/crd/bases/core.posit.team_sites.yaml index f2df2dbd..49f1d9f1 100644 --- a/config/crd/bases/core.posit.team_sites.yaml +++ b/config/crd/bases/core.posit.team_sites.yaml @@ -77,13 +77,9 @@ spec: type: string type: object additionalConfig: - additionalProperties: - type: string - description: |- - AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. - Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). - Values set here take precedence over typed fields if both specify the same key. - type: object + description: AdditionalConfig allows appending arbitrary gcfg + config content to the generated config. + type: string additionalRuntimeImages: description: |- AdditionalRuntimeImages specifies additional runtime images to append to the defaults @@ -619,13 +615,9 @@ spec: type: string type: object additionalConfig: - additionalProperties: - type: string - description: |- - AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. - Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). - Values set here take precedence over typed fields if both specify the same key. - type: object + description: AdditionalConfig allows appending arbitrary gcfg + config content to the generated config. + type: string azureFiles: description: AzureFiles configures Azure Files integration for persistent storage diff --git a/dist/chart/templates/crd/core.posit.team_connects.yaml b/dist/chart/templates/crd/core.posit.team_connects.yaml index 54f54e55..f1861c93 100755 --- a/dist/chart/templates/crd/core.posit.team_connects.yaml +++ b/dist/chart/templates/crd/core.posit.team_connects.yaml @@ -444,14 +444,12 @@ spec: Logging: type: boolean type: object - additional: - additionalProperties: - type: string + additionalConfig: description: |- - Additional allows setting arbitrary gcfg config values not covered by typed fields. - Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). - Values set here take precedence over typed fields if both specify the same key. - type: object + AdditionalConfig allows appending arbitrary gcfg config content not covered by typed fields. + The value is appended verbatim after the generated config. gcfg parsing naturally handles + conflicts: list values are combined, scalar values use the last occurrence. + type: string type: object databaseConfig: properties: diff --git a/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml b/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml index e2fcb626..323d55ae 100755 --- a/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml +++ b/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml @@ -175,14 +175,12 @@ spec: Default: type: string type: object - additional: - additionalProperties: - type: string + additionalConfig: description: |- - Additional allows setting arbitrary gcfg config values not covered by typed fields. - Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). - Values set here take precedence over typed fields if both specify the same key. - type: object + AdditionalConfig allows appending arbitrary gcfg config content not covered by typed fields. + The value is appended verbatim after the generated config. gcfg parsing naturally handles + conflicts: list values are combined, scalar values use the last occurrence. + type: string type: object databaseConfig: properties: diff --git a/dist/chart/templates/crd/core.posit.team_sites.yaml b/dist/chart/templates/crd/core.posit.team_sites.yaml index 2767cf4a..066ba9e9 100755 --- a/dist/chart/templates/crd/core.posit.team_sites.yaml +++ b/dist/chart/templates/crd/core.posit.team_sites.yaml @@ -98,13 +98,9 @@ spec: type: string type: object additionalConfig: - additionalProperties: - type: string - description: |- - AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. - Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Scheduler.MaxCPURequest"). - Values set here take precedence over typed fields if both specify the same key. - type: object + description: AdditionalConfig allows appending arbitrary gcfg + config content to the generated config. + type: string additionalRuntimeImages: description: |- AdditionalRuntimeImages specifies additional runtime images to append to the defaults @@ -640,13 +636,9 @@ spec: type: string type: object additionalConfig: - additionalProperties: - type: string - description: |- - AdditionalConfig allows setting arbitrary gcfg config values not covered by typed fields. - Keys should be in "Section.Key" format (e.g., "Server.DataDir", "Storage.Default"). - Values set here take precedence over typed fields if both specify the same key. - type: object + description: AdditionalConfig allows appending arbitrary gcfg + config content to the generated config. + type: string azureFiles: description: AzureFiles configures Azure Files integration for persistent storage