diff --git a/api/core/v1beta1/connect_config.go b/api/core/v1beta1/connect_config.go index b19fa249..e43a07f1 100644 --- a/api/core/v1beta1/connect_config.go +++ b/api/core/v1beta1/connect_config.go @@ -2,7 +2,6 @@ package v1beta1 import ( "fmt" - "reflect" "strings" ) @@ -31,6 +30,12 @@ type ConnectConfig struct { // see the GenerateGcfg method for our custom handling RPackageRepository map[string]RPackageRepositoryConfig `json:"RPackageRepositories,omitempty"` TableauIntegration *ConnectTableauIntegrationConfig `json:"TableauIntegration,omitempty"` + + // 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 + AdditionalConfig string `json:"additionalConfig,omitempty"` } type RPackageRepositoryConfig struct { @@ -197,6 +202,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) @@ -204,28 +220,43 @@ func (configStruct *ConnectConfig) GenerateGcfg() (string, error) { fieldName := configStructVals.Type().Field(i).Name fieldValue := configStructVals.Field(i) + // Skip the AdditionalConfig string — we handle it at the end + if fieldName == "AdditionalConfig" { + 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 @@ -235,29 +266,54 @@ 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) } + } + // 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") + } + } } + + 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 769c0ca5..c1064880 100644 --- a/api/core/v1beta1/connect_config_test.go +++ b/api/core/v1beta1/connect_config_test.go @@ -249,3 +249,47 @@ func TestConnectConfig_CustomScope(t *testing.T) { require.Contains(t, str, "OpenIDConnectIssuer = https://example.com") require.NotContains(t, str, "CustomScope") } + +func TestConnectConfig_AdditionalConfig(t *testing.T) { + // Test basic string append + cfg := ConnectConfig{ + Server: &ConnectServerConfig{ + Address: "some-address.com", + }, + AdditionalConfig: "\n[NewSection]\nNewKey = custom-value\n", + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + 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") +} + +func TestConnectConfig_AdditionalConfigOverride(t *testing.T) { + // gcfg last-write-wins: appended scalar overrides typed field + cfg := ConnectConfig{ + Server: &ConnectServerConfig{ + Address: "typed-address.com", + }, + AdditionalConfig: "\n[Server]\nAddress = passthrough-address.com\n", + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + // 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") +} + +func TestConnectConfig_AdditionalConfigEmpty(t *testing.T) { + // Empty string has no effect + cfg := ConnectConfig{ + Server: &ConnectServerConfig{ + Address: "some-address.com", + }, + AdditionalConfig: "", + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + require.Contains(t, str, "Address = some-address.com") +} diff --git a/api/core/v1beta1/package_manager_config.go b/api/core/v1beta1/package_manager_config.go index 92b7d623..b6dabf5d 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"` + + // 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 + AdditionalConfig string `json:"additionalConfig,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 AdditionalConfig string — we handle it at the end + if fieldName == "AdditionalConfig" { + 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,46 @@ 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) } + + // 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") + } + } + } + + 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 363fed3e..ddf37a91 100644 --- a/api/core/v1beta1/package_manager_config_test.go +++ b/api/core/v1beta1/package_manager_config_test.go @@ -34,3 +34,31 @@ func TestPackageManagerConfig_GenerateGcfg(t *testing.T) { require.Contains(t, str, "/some/path") require.Contains(t, str, "/another/path") } + +func TestPackageManagerConfig_AdditionalConfig(t *testing.T) { + // Test basic string append + cfg := PackageManagerConfig{ + Server: &PackageManagerServerConfig{ + Address: "some-address.com", + }, + AdditionalConfig: "\n[NewSection]\nNewKey = custom-value\n", + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + 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") +} + +func TestPackageManagerConfig_AdditionalConfigEmpty(t *testing.T) { + cfg := PackageManagerConfig{ + Server: &PackageManagerServerConfig{ + Address: "some-address.com", + }, + AdditionalConfig: "", + } + str, err := cfg.GenerateGcfg() + require.Nil(t, err) + 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 3e7a3b06..d257bd63 100644 --- a/api/core/v1beta1/site_types.go +++ b/api/core/v1beta1/site_types.go @@ -221,6 +221,10 @@ type InternalPackageManagerSpec struct { // AzureFiles configures Azure Files integration for persistent storage // +optional AzureFiles *AzureFilesConfig `json:"azureFiles,omitempty"` + + // AdditionalConfig allows appending arbitrary gcfg config content to the generated config. + // +optional + AdditionalConfig string `json:"additionalConfig,omitempty"` } type InternalConnectSpec struct { @@ -275,6 +279,10 @@ type InternalConnectSpec struct { // for Connect off-host execution // +optional AdditionalRuntimeImages []ConnectRuntimeImageSpec `json:"additionalRuntimeImages,omitempty"` + + // AdditionalConfig allows appending arbitrary gcfg config content to the generated config. + // +optional + AdditionalConfig string `json:"additionalConfig,omitempty"` } type DatabaseSettings struct { @@ -402,6 +410,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 { @@ -442,7 +460,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 1df0a709..96b5a519 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"` @@ -493,15 +522,21 @@ 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 - Resources map[string]*WorkbenchLauncherKubnernetesResourcesConfigSection `json:"launcher.kubernetes.resources.conf,omitempty"` + 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. + // 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 } @@ -1162,7 +1219,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 8b015f4b..029305ce 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", }, @@ -301,6 +301,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{ @@ -380,18 +489,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", @@ -400,12 +509,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", @@ -414,12 +523,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", @@ -428,13 +537,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", @@ -444,12 +553,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", @@ -477,23 +586,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", @@ -553,18 +662,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", @@ -599,23 +708,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 @@ -858,15 +967,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", @@ -904,8 +1013,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", @@ -942,20 +1051,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", @@ -993,14 +1102,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 a8d7c254..b1443fbf 100644 --- a/api/core/v1beta1/zz_generated.deepcopy.go +++ b/api/core/v1beta1/zz_generated.deepcopy.go @@ -1321,15 +1321,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 @@ -1454,6 +1454,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. @@ -2515,20 +2529,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. @@ -2664,16 +2685,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 } @@ -3007,6 +3028,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..8be73222 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"` + AdditionalConfig *string `json:"additionalConfig,omitempty"` } // ConnectConfigApplyConfiguration constructs a declarative configuration of the ConnectConfig type for use with @@ -183,3 +184,11 @@ func (b *ConnectConfigApplyConfiguration) WithTableauIntegration(value *ConnectT b.TableauIntegration = value return b } + +// 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 84e69b2f..621f46c4 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalconnectspec.go @@ -33,6 +33,7 @@ type InternalConnectSpecApplyConfiguration struct { DatabaseSettings *DatabaseSettingsApplyConfiguration `json:"databaseSettings,omitempty"` ScheduleConcurrency *int `json:"scheduleConcurrency,omitempty"` AdditionalRuntimeImages []ConnectRuntimeImageSpecApplyConfiguration `json:"additionalRuntimeImages,omitempty"` + AdditionalConfig *string `json:"additionalConfig,omitempty"` } // InternalConnectSpecApplyConfiguration constructs a declarative configuration of the InternalConnectSpec type for use with @@ -217,3 +218,11 @@ func (b *InternalConnectSpecApplyConfiguration) WithAdditionalRuntimeImages(valu } return b } + +// 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 32cdab2e..c9e12970 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 *string `json:"additionalConfig,omitempty"` } // InternalPackageManagerSpecApplyConfiguration constructs a declarative configuration of the InternalPackageManagerSpec type for use with @@ -145,3 +146,11 @@ func (b *InternalPackageManagerSpecApplyConfiguration) WithAzureFiles(value *Azu b.AzureFiles = value return b } + +// 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/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 027c94c9..1776cfc0 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go +++ b/client-go/applyconfiguration/core/v1beta1/internalworkbenchspec.go @@ -45,6 +45,8 @@ type InternalWorkbenchSpecApplyConfiguration struct { AuthLoginPageHtml *string `json:"authLoginPageHtml,omitempty"` AuditedJobs *AuditedJobsConfigApplyConfiguration `json:"auditedJobs,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 @@ -338,3 +340,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..c853572d 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"` + AdditionalConfig *string `json:"additionalConfig,omitempty"` } // PackageManagerConfigApplyConfiguration constructs a declarative configuration of the PackageManagerConfig type for use with @@ -114,3 +115,11 @@ func (b *PackageManagerConfigApplyConfiguration) WithDebug(value *PackageManager b.Debug = value return b } + +// 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/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 9f3d735d..bba2e5a1 100644 --- a/client-go/applyconfiguration/utils.go +++ b/client-go/applyconfiguration/utils.go @@ -211,8 +211,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 95869aee..8e341377 100644 --- a/config/crd/bases/core.posit.team_connects.yaml +++ b/config/crd/bases/core.posit.team_connects.yaml @@ -423,6 +423,12 @@ spec: Logging: type: boolean type: object + additionalConfig: + description: |- + 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 aab63f82..72619fa8 100644 --- a/config/crd/bases/core.posit.team_packagemanagers.yaml +++ b/config/crd/bases/core.posit.team_packagemanagers.yaml @@ -154,6 +154,12 @@ spec: Default: type: string type: object + additionalConfig: + description: |- + 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 bb2ebf36..49f1d9f1 100644 --- a/config/crd/bases/core.posit.team_sites.yaml +++ b/config/crd/bases/core.posit.team_sites.yaml @@ -76,6 +76,10 @@ spec: additionalProperties: type: string type: object + additionalConfig: + 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 @@ -610,6 +614,10 @@ spec: additionalProperties: type: string type: object + additionalConfig: + description: AdditionalConfig allows appending arbitrary gcfg + config content to the generated config. + type: string azureFiles: description: AzureFiles configures Azure Files integration for persistent storage @@ -834,6 +842,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 086f374b..d047931a 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: @@ -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/dist/chart/templates/crd/core.posit.team_connects.yaml b/dist/chart/templates/crd/core.posit.team_connects.yaml index 8b5d5a2f..f1861c93 100755 --- a/dist/chart/templates/crd/core.posit.team_connects.yaml +++ b/dist/chart/templates/crd/core.posit.team_connects.yaml @@ -444,6 +444,12 @@ spec: Logging: type: boolean type: object + additionalConfig: + description: |- + 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 f4834378..323d55ae 100755 --- a/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml +++ b/dist/chart/templates/crd/core.posit.team_packagemanagers.yaml @@ -175,6 +175,12 @@ spec: Default: type: string type: object + additionalConfig: + description: |- + 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 e002d1b7..066ba9e9 100755 --- a/dist/chart/templates/crd/core.posit.team_sites.yaml +++ b/dist/chart/templates/crd/core.posit.team_sites.yaml @@ -97,6 +97,10 @@ spec: additionalProperties: type: string type: object + additionalConfig: + 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 @@ -631,6 +635,10 @@ spec: additionalProperties: type: string type: object + additionalConfig: + description: AdditionalConfig allows appending arbitrary gcfg + config content to the generated config. + type: string azureFiles: description: AzureFiles configures Azure Files integration for persistent storage @@ -855,6 +863,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 b6151e34..ff0ed92c 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: @@ -545,6 +553,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 2462486a..5281496c 100644 --- a/internal/controller/core/site_controller_connect.go +++ b/internal/controller/core/site_controller_connect.go @@ -218,6 +218,9 @@ func (r *SiteReconciler) reconcileConnect( } } + // Propagate additional config if configured + 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 { 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..bca0a645 100644 --- a/internal/controller/core/site_controller_package_manager.go +++ b/internal/controller/core/site_controller_package_manager.go @@ -113,6 +113,10 @@ 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 + pm.Spec.Config.AdditionalConfig = 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 8b489ab8..f03aaf39 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 + } + // Propagate audited jobs configuration if site.Spec.Workbench.AuditedJobs != nil { aj := site.Spec.Workbench.AuditedJobs @@ -467,8 +477,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", @@ -488,7 +498,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)