Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/rstudio-library/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v2
name: rstudio-library
description: Helm library helpers for use by official RStudio charts
type: library
version: 0.1.37
version: 0.1.38
appVersion: 0.1.35

icon: https://raw.githubusercontent.com/rstudio/helm/main/images/posit-icon-fullcolor.svg
Expand Down
23 changes: 23 additions & 0 deletions charts/rstudio-library/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## 0.1.38

- `rstudio-library.config.ini` now accepts a file's contents as a list of single-entry maps, and
renders them in the order written rather than sorted by name. A single-entry item becomes a
`[name]` section when its value is a map, and a `name=value` line when its value is a scalar.
A list entry with more than one key still renders as a blank-line separated record, which is what
`/etc/rstudio/r-versions` expects. Previously a list of single-entry maps rendered broken lines
such as `*=map[max-memory-mb:1024]`, with no section headers.
- A list entry that is empty, or that names a section alongside any other key, now fails with a
message naming the sections and showing the `- ` placement to fix it. The most common cause is a
section that is missing its own `- `, which would otherwise render as `name=map[key:value]`.
- An option inside a section must now be a single value. ini files have no nesting, so a map or a
list there had no representation and rendered as `key=map[a:1]` or `key=[a b]`. This applies to
both the map and the list form. Lists at the top level of a file are unaffected: a list of maps
still repeats a section, and a list of values still repeats a key.
`rstudio-library.profiles.ini` is also unaffected, since it defines a meaning for a list inside a
section (it comma-joins).
- `rstudio-library.profiles.ini.advanced`, `rstudio-library.profiles.ini.singleFile`, and
`rstudio-library.profiles.json-from-overrides-config` accept the same ordered list form for a
profiles file's sections, including its `job-json-overrides` handling.
- New `rstudio-library.config.entries` helper, which normalizes either form into an ordered list of
entries.

## 0.1.37

- **DEPRECATED**: Chronicle agent helpers are deprecated.
Expand Down
2 changes: 1 addition & 1 deletion charts/rstudio-library/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# rstudio-library

![Version: 0.1.37](https://img.shields.io/badge/Version-0.1.37-informational?style=flat-square) ![Type: library](https://img.shields.io/badge/Type-library-informational?style=flat-square) ![AppVersion: 0.1.35](https://img.shields.io/badge/AppVersion-0.1.35-informational?style=flat-square)
![Version: 0.1.38](https://img.shields.io/badge/Version-0.1.38-informational?style=flat-square) ![Type: library](https://img.shields.io/badge/Type-library-informational?style=flat-square) ![AppVersion: 0.1.35](https://img.shields.io/badge/AppVersion-0.1.35-informational?style=flat-square)

#### _Helm library helpers for use by official RStudio charts_

Expand Down
137 changes: 127 additions & 10 deletions charts/rstudio-library/templates/_config.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,75 @@
{{- end }}
{{- end }}

{{- define "rstudio-library.config.ini" -}}
{{- range $file, $keys := . -}}
{{- printf "%s: |" $file | nindent 0 }}
{{- if kindIs "string" $keys }}
{{- $keys | nindent 2 }}
{{- /*
Normalizes config-file contents into an ordered list of entries.

Accepts either form:
- a map of {name: value}, which Go templates iterate in sorted key order
- a list of single-entry maps, which is iterated in the order it was written

Templates cannot return values, so the caller passes a dict to write into:
data: the contents to normalize
result: a dict, which this sets an "entries" key on. Each entry is a dict
with a "name" (string) and a "config" (the value written under it)
*/ -}}
{{- define "rstudio-library.config.entries" -}}
{{- $entries := list }}
{{- $data := default (dict) .data }}
{{- if kindIs "slice" $data }}
{{- range $item := $data }}
{{- if not (kindIs "map" $item) }}
{{- fail (print "\n\nEvery entry written as a list must be a map of a single name and its value. Instead got '" (kindOf $item) "' : '" (print $item) "'") }}
{{- end }}
{{- $names := keys $item | sortAlpha }}
{{- if ne (len $names) 1 }}
{{- $hint := "" }}
{{- range $n := $names }}
{{- $hint = print $hint "\n - " ($n | quote) ":\n ..." }}
{{- end }}
{{- fail (print "\n\nAn entry written as a list holds " (len $names) " keys: " (join ", " $names) "\n\nEach entry names one section, so that the sections keep the order they were\nwritten in. Put '- ' in front of each one:\n" $hint "\n") }}
{{- end }}
{{- range $name, $config := $item }}
{{- $entries = append $entries (dict "name" (toString $name) "config" $config) }}
{{- end }}
{{- end }}
{{- else }}
{{- range $parent, $child := $keys -}}
{{/* ini files may have multiple sections with the same name */}}
{{- range $name, $config := $data }}
{{- $entries = append $entries (dict "name" (toString $name) "config" $config) }}
{{- end }}
{{- end }}
{{- $_ := set .result "entries" $entries }}
{{- end -}}

{{- /*
Renders a single ini entry.

Takes a dict:
file: the file name, used in error messages
entry: a map of {name: value}, where the value is either
- a map, which becomes a [name] section followed by its key=value pairs
- a list of maps, which becomes repeated [name] sections (ini files may
have more than one section with the same name)
- anything else, which becomes a bare name=value line

An option inside a section must be a single value. ini has no nesting, so a map
or a list there has no representation and used to render as "key=map[a:1]" or
"key=[a b]". (rstudio-library.profiles.ini does define a meaning for a list --
it comma-joins -- which is why it does not share this helper.)
*/ -}}
{{- define "rstudio-library.config.ini.entry" -}}
{{- $file := .file }}
{{- range $parent, $child := .entry -}}
{{- $sections := ( (kindIs "slice" $child) | ternary $child ( list $child ))}}
{{- range $i, $section := $sections -}}
{{- if kindIs "map" $section }}
{{- if not ( kindIs "slice" $keys ) -}}
{{- printf "[%s]" (toString $parent) | nindent 2 }}
{{- end }}
{{- printf "[%s]" (toString $parent) | nindent 2 }}
{{- range $key, $val := $section }}
{{- if or (kindIs "map" $val) (kindIs "slice" $val) }}
{{- $kind := (kindIs "map" $val) | ternary "a map" "a list" }}
{{- $fix := (kindIs "map" $val) | ternary "" "\n\nIf the file expects several values, write them as one value, such as \"a,b\"." }}
{{- fail (print "\n\n'" (toString $key) "' in section [" (toString $parent) "] of '" $file "' is " $kind ", but an option\ninside a section must be a single value. ini files have no nesting, so this\nwould have rendered as '" (toString $key) "=" (toString $val) "'." $fix "\n") }}
{{- end }}
{{- printf "%s=%s" (toString $key) (toString $val) | nindent 2 }}
{{- end }}
{{- printf "" | nindent 0 }}
Expand All @@ -67,6 +121,69 @@
{{- end }}
{{- end }}
{{- end }}

{{- /*
Takes a map of {filename: contents} and renders each as an ini file.

Contents may be:
- a raw string, rendered verbatim
- a map of {name: value}, rendered in sorted key order. Files whose behavior
depends on the order of their sections or entries should use the list form
- a list, rendered in the order it was written. Each entry is a non-empty map:
- a key whose value is a map names a section, and must be the only key in
its entry, so that sections keep the order they were written in
- otherwise the entry is a record of fields, rendered as name=value lines
followed by a blank line (the shape /etc/rstudio/r-versions expects)
Keys within one entry are sorted, so the order of a list is the order of its
sections and entries, not of the options inside them
*/ -}}
{{- define "rstudio-library.config.ini" -}}
{{- range $file, $keys := . -}}
{{- printf "%s: |" $file | nindent 0 }}
{{- if kindIs "string" $keys }}
{{- $keys | nindent 2 }}
{{- else if kindIs "slice" $keys }}
{{- range $i, $item := $keys -}}
{{- $where := print "entry " (add $i 1) " of '" $file "'" }}
{{- if not (kindIs "map" $item) }}
{{- fail (print "\n\n" $where " must be a map of a name and its value. Instead got '" (kindOf $item) "' : '" (print $item) "'") }}
{{- end }}
{{- $names := keys $item | sortAlpha }}
{{- if eq (len $names) 0 }}
{{- fail (print "\n\n" $where " is empty. Every entry written as a list must be a map of a name and its value.") }}
{{- end }}
{{- /* A map value names a section, so it has to be the only key in its entry. Otherwise the
sections in one entry would be sorted against each other, losing the order the list is
there to preserve -- and before this check, they rendered as "name=map[key:value]".
Almost always a list entry that is missing its own "- ". */ -}}
{{- $sections := list }}
{{- range $key, $val := $item }}
{{- if kindIs "map" $val }}
{{- $sections = append $sections (toString $key) }}
{{- end }}
{{- end }}
{{- if and $sections (gt (len $names) 1) }}
{{- $hint := "" }}
{{- range $s := $sections }}
{{- $hint = print $hint "\n - " ($s | quote) ":\n ..." }}
{{- end }}
{{- fail (print "\n\n" $where " holds more than one key: " (join ", " $names) "\n\nA key whose value is a section must be the only key in its entry, so that the\nsections keep the order they were written in. These name sections: " (join ", " $sections) "\n\nPut '- ' in front of each one:\n\n " $file ":" $hint "\n") }}
{{- end }}
{{- if gt (len $names) 1 }}
{{- /* a record of fields, the shape /etc/rstudio/r-versions expects */ -}}
{{- range $key, $val := $item }}
{{- printf "%s=%s" (toString $key) (toString $val) | nindent 2 }}
{{- end }}
{{- printf "" | nindent 0 }}
{{- else }}
{{- include "rstudio-library.config.ini.entry" (dict "file" $file "entry" $item) }}
{{- end }}
{{- end }}
{{- else }}
{{- range $parent, $child := $keys -}}
{{- include "rstudio-library.config.ini.entry" (dict "file" $file "entry" (dict (toString $parent) $child)) }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

Expand Down
82 changes: 51 additions & 31 deletions charts/rstudio-library/templates/_profiles.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
Looks at the "json" key of the job-json-overrides definition

Takes a dict:
data: the launcher.kubernetes.profiles.conf configuration as a dict (map of maps)
data: the launcher.kubernetes.profiles.conf configuration, as a map of sections or as an
ordered list of single-entry maps
default: optional. The default job-json-overrides to append

- Build a unique list of overrides (and a unique list of names for testing uniqueness)
Expand All @@ -30,8 +31,11 @@
{{- if $data }}
{{- $data = $data | deepCopy }}
{{- end }}
{{- include "rstudio-library.debug.type-check" (dict "name" "config data" "object" $data "expected" "map" "description" "of section headers and configuration" ) }}
{{- range $key, $config := $data -}}
{{- $normalized := dict }}
{{- include "rstudio-library.config.entries" (dict "data" $data "result" $normalized) }}
{{- range $entry := $normalized.entries -}}
{{- $key := $entry.name -}}
{{- $config := $entry.config -}}
{{- include "rstudio-library.debug.type-check" (dict "name" (print "[" $key "] section") "object" $config "expected" "map" "description" "of config data" ) }}
{{- if hasKey $config "job-json-overrides" -}}
{{- $overrides := get $config "job-json-overrides" -}}
Expand Down Expand Up @@ -93,19 +97,24 @@
- output the ini file

Takes a dict:
data: the launcher.kubernetes.profiles.conf configuration as a dict (map of maps)
data: the launcher.kubernetes.profiles.conf configuration, as a map of sections or as an
ordered list of single-entry maps. The list form renders in the order written
default: optional. the default job-json-overrides to append
filePath: optional. the default is none
*/}}
{{- define "rstudio-library.profiles.apply-everyone-and-default-to-others" }}
{{- $newDict := dict }}
{{- $everyoneConfig := dict }}
{{- $data := .data }}
{{- if $data }}
{{- $data = $data | deepCopy }}
{{- end }}
{{- if hasKey $data "*" }}
{{- $everyoneConfig = get $data "*" }}
{{- $normalized := dict }}
{{- include "rstudio-library.config.entries" (dict "data" $data "result" $normalized) }}
{{- $entries := $normalized.entries }}
{{- $everyoneConfig := dict }}
{{- range $entry := $entries }}
{{- if eq $entry.name "*" }}
{{- $everyoneConfig = $entry.config }}
{{- end }}
{{- end }}
{{- include "rstudio-library.debug.type-check" (dict "name" "[*] section" "object" $everyoneConfig "expected" "map" "description" "of config values") }}
{{- $defaultConfig := default (list) .default }}
Expand All @@ -127,43 +136,51 @@
{{- end }}
{{- $defaultConfig = concat $defaultConfig $everyone }}
{{- end }}
{{- /* if default config is defined, ensure that "everyone" is updated by it */ -}}
{{- if ge (len $defaultConfig) 1 }}
{{- $newDict = mergeOverwrite $newDict (dict "*" (dict "job-json-overrides" $defaultConfig)) }}
{{- end }}
{{- /* loop over non-everyone config, prepending the default configuration */ -}}
{{- $others := omit $data "*" }}
{{- range $key, $one := $others }}
{{- include "rstudio-library.debug.type-check" (dict "name" (print "[" $key "] section" ) "object" $one "expected" "map" "description" "of config values") }}
{{- if hasKey $one "job-json-overrides" }}
{{- $oneConfig := get $one "job-json-overrides" }}
{{- include "rstudio-library.debug.type-check" (dict "name" ( print "[" $key "].job-json-overrides" ) "object" $oneConfig "expected" "slice" "description" "of job-json-overrides definitions") }}
{{- range $entry := $oneConfig }}
{{- $_ := set $entry "file" ( print $filePath ($entry.name | nospace) ".json" ) }}
{{- /* walk the sections in order, prepending the default configuration to each */ -}}
{{- $output := list }}
{{- $hasEveryone := false }}
{{- range $entry := $entries }}
{{- $name := $entry.name }}
{{- $config := $entry.config }}
{{- include "rstudio-library.debug.type-check" (dict "name" (print "[" $name "] section" ) "object" $config "expected" "map" "description" "of config values") }}
{{- if eq $name "*" }}
{{- $hasEveryone = true }}
{{- if ge (len $defaultConfig) 1 }}
{{- $config = mergeOverwrite $config (dict "job-json-overrides" $defaultConfig) }}
{{- end }}
{{- $oneList := concat $defaultConfig $oneConfig }}
{{- $oneDict := dict $key (dict "job-json-overrides" $oneList) }}
{{- $newDict = mergeOverwrite $newDict $oneDict }}
{{- else if hasKey $config "job-json-overrides" }}
{{- $oneConfig := get $config "job-json-overrides" }}
{{- include "rstudio-library.debug.type-check" (dict "name" ( print "[" $name "].job-json-overrides" ) "object" $oneConfig "expected" "slice" "description" "of job-json-overrides definitions") }}
{{- range $one := $oneConfig }}
{{- $_ := set $one "file" ( print $filePath ($one.name | nospace) ".json" ) }}
{{- end }}
{{- $config = mergeOverwrite $config (dict "job-json-overrides" (concat $defaultConfig $oneConfig)) }}
{{- end }}
{{- $output = append $output (dict $name $config) }}
{{- end }}
{{- /* the defaults need an "everyone" section to live in, even if none was written */ -}}
{{- if and (not $hasEveryone) (ge (len $defaultConfig) 1) }}
{{- $output = prepend $output (dict "*" (dict "job-json-overrides" $defaultConfig)) }}
{{- end }}
{{- /* output the configuration file */ -}}
{{- $output := mergeOverwrite $data $newDict }}
{{- include "rstudio-library.profiles.ini.singleFile" $output }}
{{- end }}

{{/*
Builds a single ini file
Builds a single ini file, from either a map of sections or an ordered list of single-entry maps
Modified from rstudio-library.config.ini to:
- collapse arrays
- via rstudio-library.profiles.ini.collapse-array
*/}}
{{- define "rstudio-library.profiles.ini.singleFile" -}}
{{- range $parent, $child := . -}}
{{- $normalized := dict }}
{{- include "rstudio-library.config.entries" (dict "data" . "result" $normalized) }}
{{- range $entry := $normalized.entries -}}
{{- $parent := $entry.name -}}
{{- $child := $entry.config -}}
{{- if kindIs "map" $child }}

{{ if not ( kindIs "slice" . ) -}}
[{{ $parent }}]
{{- end }}
{{- range $key, $val := $child }}
{{- if kindIs "slice" $val }}
{{ $key }}={{ include "rstudio-library.profiles.ini.collapse-array" $val }}
Expand Down Expand Up @@ -192,7 +209,8 @@

{{/*
Takes a dict:
- .data : the configuration map of maps
- .data : a map of {filename: contents}. Each file's contents is either a map of sections or
an ordered list of single-entry maps, which renders in the order written
- .jobJsonDefaults : an array of {target:target, name:name, json:json} defaults
- .filePath : the path from the root of the system to where json overrides files will be mounted
*/}}
Expand All @@ -203,7 +221,9 @@
{{- $data := .data }}
{{- include "rstudio-library.debug.type-check" (dict "name" "profiles data" "object" $data "expected" "map" "description" "of filenames and config data") }}
{{- range $file, $keys := $data -}}
{{- include "rstudio-library.debug.type-check" (dict "name" (print "profiles content for file '" $file "'") "object" $keys "expected" "map" "description" "of section headers and configuration") }}
{{- if not (or (kindIs "map" $keys) (kindIs "slice" $keys)) }}
{{- fail (print "\n\nprofiles content for file '" $file "' must be a 'map' of section headers and configuration, or a 'slice' of single-entry maps. Instead got '" (kindOf $keys) "' : '" (print $keys) "'") }}
{{- end }}
{{ $file }}: |
{{- include "rstudio-library.profiles.apply-everyone-and-default-to-others" (dict "data" $keys "default" $jobJsonDefaults "filePath" $filePath) }}

Expand Down
6 changes: 3 additions & 3 deletions other-charts/rstudio-library-test/Chart.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
dependencies:
- name: rstudio-library
repository: file://../../charts/rstudio-library
version: 0.1.37
digest: sha256:d2c41673ebb1b0dfb2c5548e34d62727906636a7ae40a18fbf35cc5fedb53351
generated: "2026-06-18T14:27:05.729695-04:00"
version: 0.1.38
digest: sha256:c4509e159bcf8fdb934a720ebc558bafad7db2687414568b81c76183563b6e9d
generated: "2026-09-24T17:46:06.957171-07:00"
2 changes: 1 addition & 1 deletion other-charts/rstudio-library-test/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v2
name: rstudio-library-test
description: Test harness for rstudio-library templates
type: application
version: 0.1.1
version: 0.1.2
appVersion: "0.1.0"

maintainers:
Expand Down
4 changes: 4 additions & 0 deletions other-charts/rstudio-library-test/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# NEWS

## 0.1.2

- Exercise the ordered list form of `rstudio-library.config.ini` and the profiles helpers

## 0.1.1

- Update rstudio-library dependency to 0.1.37
Expand Down
2 changes: 1 addition & 1 deletion other-charts/rstudio-library-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# rstudio-library-test

![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square)
![Version: 0.1.2](https://img.shields.io/badge/Version-0.1.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.1.0](https://img.shields.io/badge/AppVersion-0.1.0-informational?style=flat-square)

Test harness for rstudio-library templates

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ data:
{{- end }}

{{- /* Test profiles.ini.singleFile */}}
{{- if and .Values.testProfiles.singleFile (kindIs "map" .Values.testProfiles.singleFile) }}
{{- if and .Values.testProfiles.singleFile (or (kindIs "map" .Values.testProfiles.singleFile) (kindIs "slice" .Values.testProfiles.singleFile)) }}
---
apiVersion: v1
kind: ConfigMap
Expand Down
Loading
Loading