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
51 changes: 35 additions & 16 deletions blueprints/starter/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

// Reference is the image address computed from repository, tag and digest
// in the format [REPOSITORY]:[TAG]@[DIGEST].
// +nodoc
reference: string

if digest != "" && tag != "" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@ import "strings"

// Standard Kubernetes labels: app name, version and managed-by.
labels: {
(#StdLabelName): name
(#StdLabelVersion): #Version
// +nodoc
(#StdLabelName): name
// +nodoc
(#StdLabelVersion): #Version
// +nodoc
(#StdLabelManagedBy): "timoni"
}

// LabelSelector selects Pods based on the app.kubernetes.io/name label.
#LabelSelector: #Labels & {
// +nodoc
(#StdLabelName): name
}

Expand All @@ -74,6 +78,7 @@ import "strings"
namespace: #Meta.namespace

labels: #Meta.labels
// +nodoc
labels: (#StdLabelComponent): #Component

annotations?: #Annotations
Expand All @@ -84,8 +89,10 @@ import "strings"
// LabelSelector selects Pods based on the app.kubernetes.io/name
// and app.kubernetes.io/component labels.
#LabelSelector: #Labels & {
// +nodoc
(#StdLabelComponent): #Component
(#StdLabelName): #Meta.name
// +nodoc
(#StdLabelName): #Meta.name
}
}

Expand All @@ -104,6 +111,7 @@ import "strings"
name: #Meta.name + "-" + #Component

labels: #Meta.labels
// +nodoc
labels: (#StdLabelComponent): #Component

annotations?: #Annotations
Expand All @@ -114,7 +122,9 @@ import "strings"
// LabelSelector selects Pods based on the app.kubernetes.io/name
// and app.kubernetes.io/component labels.
#LabelSelector: #Labels & {
// +nodoc
(#StdLabelComponent): #Component
(#StdLabelName): #Meta.name
// +nodoc
(#StdLabelName): #Meta.name
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ package v1alpha1
labels: #Labels

// Standard Kubernetes label: app name.
labels: (#StdLabelName): #Name
labels: {
// +nodoc
(#StdLabelName): #Name
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
let minMajor = strconv.Atoi(strings.Split(#Minimum, ".")[0])
let minMinor = strconv.Atoi(strings.Split(#Minimum, ".")[1])

// +nodoc
major: int & >=minMajor
major: strconv.Atoi(strings.Split(#Version, ".")[0])

// +nodoc
minor: int & >=minMinor
minor: strconv.Atoi(strings.Split(#Version, ".")[1])
}
3 changes: 3 additions & 0 deletions blueprints/starter/templates/config.cue
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import (
#Config: {
// The kubeVersion is a required field, set at apply-time
// via timoni.cue by querying the user's Kubernetes API.
// +nodoc
kubeVersion!: string
// Using the kubeVersion you can enforce a minimum Kubernetes minor version.
// By default, the minimum Kubernetes version is set to 1.20.
// +nodoc
clusterVersion: timoniv1.#SemVer & {#Version: kubeVersion, #Minimum: "1.20.0"}

// The moduleVersion is set from the user-supplied module version.
// This field is used for the `app.kubernetes.io/version` label.
// +nodoc
moduleVersion!: string

// The Kubernetes metadata common to all resources.
Expand Down
74 changes: 51 additions & 23 deletions cmd/timoni/mod_show_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ import (

var configShowModCmd = &cobra.Command{
Use: "config [MODULE PATH]",
Short: "Output the #Config structure of a local module",
Short: "Output the `#Config` structure of a local module",
Long: `The config command parses the local module configuration structure and outputs the information to stdout.`,
Example: ` # print the config of a module in the current directory
timoni mod show config

# output the config to a file, if the file is markdown, the table will overwrite a table in a Configuration section or
# be appended to the end of the file
# output the config to a file, if the file is markdown, the table will overwrite a table in the ## Configuration section
# under the sub heading ### General values or be appended to the end of the file
timoni mod show config --output ./README.md
`,
RunE: runConfigShowModCmd,
Expand Down Expand Up @@ -115,7 +115,7 @@ func runConfigShowModCmd(cmd *cobra.Command, args []string) error {
configShowModArgs.pkg.String(),
)

if err := builder.WriteSchemaFile(); err != nil {
if err = builder.WriteSchemaFile(); err != nil {
return err
}

Expand All @@ -124,17 +124,16 @@ func runConfigShowModCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("build failed: %w", err)
}

buildResult, err := builder.Build()
_, err = builder.Build()
if err != nil {
return describeErr(f.GetModuleRoot(), "validation failed", err)
}

rows, err := builder.GetConfigDoc(buildResult)
rows, err := builder.GetConfigDoc()
if err != nil {
return describeErr(f.GetModuleRoot(), "failed to get config structure", err)
}

header := []string{"Key", "Type", "Default", "Description"}
header := []string{"Key", "Type", "Description"}

if configShowModArgs.output == "" {
printMarkDownTable(rootCmd.OutOrStdout(), header, rows)
Expand All @@ -158,36 +157,49 @@ func writeFile(readFile string, header []string, rows [][]string, f fetcher.Fetc
var tableBuffer bytes.Buffer
tableWriter := bufio.NewWriter(&tableBuffer)
printMarkDownTable(tableWriter, header, rows)
tableWriter.Flush()
err := tableWriter.Flush()
if err != nil {
return "", describeErr(f.GetModuleRoot(), "Unable to create the temporary output file", err)
}
// get a temporary file name
tmpFileName := readFile + ".tmp"
// open the input file
inputFile, err := os.Open(readFile)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
inputFile, err = os.Create(readFile)

if err != nil {
return "", describeErr(f.GetModuleRoot(), "Unable to create the temporary output file", err)
}
} else {
return "", describeErr(f.GetModuleRoot(), "Unable to create the temporary output file", err)
}
}
defer inputFile.Close()
fileClose := func(file *os.File) {
err = file.Close()
if err != nil {
fmt.Println(describeErr(f.GetModuleRoot(), "Unable to create the temporary output file", err))
}
}
defer fileClose(inputFile)

// open the output file
outputFile, err := os.Create(tmpFileName)
if err != nil {
return "", describeErr(f.GetModuleRoot(), "Unable to create the temporary output file", err)
}
defer outputFile.Close()
defer fileClose(outputFile)

// Create the scanner and writer
inputScanner := bufio.NewScanner(inputFile)
outputWriter := bufio.NewWriter(outputFile)
var configSection bool
var generalValuesSection bool
var foundTable bool
regex, err := regexp.Compile(`^\|.*\|$`)
if err != nil {
return "", describeErr(f.GetModuleRoot(), "Unalbe to compile the regex check", err)
}

// Scan the input file line by line to find the table and replace it or append it to the end
for inputScanner.Scan() {
Expand All @@ -197,29 +209,45 @@ func writeFile(readFile string, header []string, rows [][]string, f fetcher.Fetc
if !configSection && line == "## Configuration" {
configSection = true
}

matched, err := regexp.MatchString(`^\|.*\|$`, line)
if err != nil {
return "", describeErr(f.GetModuleRoot(), "Regex Match for table content failed", err)
if configSection && !generalValuesSection && line == "### General values" {
generalValuesSection = true
}

if configSection && !foundTable && matched {
matched := regex.MatchString(line)
if configSection && generalValuesSection && !foundTable && matched {
// Write the new table
foundTable = true
outputWriter.WriteString(tableBuffer.String() + "\n")
} else if configSection && foundTable && matched {
} else if configSection && foundTable && !matched {
_, err = outputWriter.WriteString(tableBuffer.String() + "\n")
if err != nil {
return "", describeErr(f.GetModuleRoot(), "Unable to create the temporary output file", err)
}
} else if configSection && generalValuesSection && foundTable && !matched {
// not a match for configuraton section so we set to false
configSection = false
generalValuesSection = false
} else if configSection && generalValuesSection && foundTable && matched {
// do nothing here as we don't want to write the old table data
} else {
outputWriter.WriteString(line + "\n")
// write the existing data
_, err = outputWriter.WriteString(line + "\n")
if err != nil {
return "", describeErr(f.GetModuleRoot(), "Unable to create the temporary output file", err)
}
}
} else {
outputWriter.WriteString(line + "\n")
_, err = outputWriter.WriteString(line + "\n")
if err != nil {
return "", describeErr(f.GetModuleRoot(), "Unable to create the temporary output file", err)
}
}
}

// If no table was found, append it to the end of the file
if !foundTable {
outputWriter.WriteString("\n" + tableBuffer.String())
_, err = outputWriter.WriteString("\n" + tableBuffer.String())
if err != nil {
return "", describeErr(f.GetModuleRoot(), "Unable to create the temporary output file", err)
}
}

err = outputWriter.Flush()
Expand Down
24 changes: 13 additions & 11 deletions cmd/timoni/testdata/module/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,17 @@ timoni -n module delete module

## Configuration

| KEY | TYPE | DEFAULT | DESCRIPTION |
|------------------------------|----------|-----------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `client: enabled:` | `bool` | `true` | |
| `client: image: repository:` | `string` | `"cgr.dev/chainguard/timoni"` | Repository is the address of a container registry repository. An image repository is made up of slash-separated name components, optionally prefixed by a registry hostname and port in the format [HOST[:PORT_NUMBER]/]PATH. |
| `client: image: tag:` | `string` | `"latest-dev"` | Tag identifies an image in the repository. A tag name may contain lowercase and uppercase characters, digits, underscores, periods and dashes. A tag name may not start with a period or a dash and may contain a maximum of 128 characters. |
| `client: image: digest:` | `string` | `"sha256:b49fbaac0eedc22c1cfcd26684707179cccbed0df205171bae3e1bae61326a10"` | Digest uniquely and immutably identifies an image in the repository. Spec: https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests. |
| `client: image: pullPolicy:` | `string` | `"IfNotPresent"` | PullPolicy defines the pull policy for the image. By default, it is set to IfNotPresent. |
| `server: enabled:` | `bool` | `true` | |
| `domain:` | `string` | `"example.internal"` | |
| `globals: enabled:` | `bool` | `false` | |
| `team:` | `string` | `"test"` | |
### General values

| KEY | TYPE | DESCRIPTION |
|------------------------------|----------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `client: enabled:` | `*true \| bool` | |
| `client: image: repository:` | `*"cgr.dev/chainguard/timoni" \| string` | Repository is the address of a container registry repository. An image repository is made up of slash-separated name components, optionally prefixed by a registry hostname and port in the format [HOST[:PORT_NUMBER]/]PATH. |
| `client: image: tag:` | `*"latest-dev" \| strings.MaxRunes(128)` | Tag identifies an image in the repository. A tag name may contain lowercase and uppercase characters, digits, underscores, periods and dashes. A tag name may not start with a period or a dash and may contain a maximum of 128 characters. |
| `client: image: digest:` | `*"sha256:b49fbaac0eedc22c1cfcd26684707179cccbed0df205171bae3e1bae61326a10" \| string` | Digest uniquely and immutably identifies an image in the repository. Spec: https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests. |
| `client: image: pullPolicy:` | `*"IfNotPresent" \| "Always" \| "Never"` | PullPolicy defines the pull policy for the image. By default, it is set to IfNotPresent. |
| `server: enabled:` | `*true \| bool` | |
| `domain:` | `*"example.internal" \| string` | |
| `globals: enabled:` | `*false \| bool` | |
| `team:` | `"test"` | |

4 changes: 0 additions & 4 deletions cmd/timoni/testdata/module/templates/config.cue
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,21 @@ import (
"app.kubernetes.io/team": team
}

// +nodoc
client: {
enabled: *true | bool

// +nodoc
image: timoniv1.#Image & {
repository: *"cgr.dev/chainguard/timoni" | string
tag: *"latest-dev" | string
digest: *"sha256:b49fbaac0eedc22c1cfcd26684707179cccbed0df205171bae3e1bae61326a10" | string
}
}

// +nodoc
server: {
enabled: *true | bool
}
domain: *"example.internal" | string

// +nodoc
globals: {
enabled: *false | bool
}
Expand Down
Loading