diff --git a/blueprints/starter/README.md b/blueprints/starter/README.md index bd425519..f6d3a8ab 100644 --- a/blueprints/starter/README.md +++ b/blueprints/starter/README.md @@ -41,19 +41,36 @@ timoni -n default delete blueprint ## Configuration -| Key | Type | Default | Description | -|--------------------------|----------------------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------| -| `image: tag:` | `string` | `` | Container image tag | -| `image: digest:` | `string` | `""` | Container image digest, takes precedence over `tag` when specified | -| `image: repository:` | `string` | `docker.io/nginx` | Container image repository | -| `image: pullPolicy:` | `string` | `IfNotPresent` | [Kubernetes image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy) | -| `metadata: labels:` | `{[ string]: string}` | `{}` | Common labels for all resources | -| `metadata: annotations:` | `{[ string]: string}` | `{}` | Common annotations for all resources | -| `pod: annotations:` | `{[ string]: string}` | `{}` | Annotations applied to pods | -| `pod: affinity:` | `corev1.#Affinity` | `{}` | [Kubernetes affinity and anti-affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) | -| `pod: imagePullSecrets:` | `[...timoniv1.#ObjectReference]` | `[]` | [Kubernetes image pull secrets](https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod) | -| `replicas:` | `int` | `1` | Kubernetes deployment replicas | -| `resources:` | `timoniv1.#ResourceRequirements` | `{}` | [Kubernetes resource requests and limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers) | -| `securityContext:` | `corev1.#SecurityContext` | `{}` | [Kubernetes container security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context) | -| `service: annotations:` | `{[ string]: string}` | `{}` | Annotations applied to the Kubernetes Service | -| `service: port:` | `int` | `80` | Kubernetes Service HTTP port | +| KEY | TYPE | DESCRIPTION | +|----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `metadata: name:` | `"module-name"` | Name must be unique within a namespace. Is required when creating resources. Name is primarily intended for creation idempotence and configuration definition. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names | +| `metadata: namespace:` | `"default"` | Namespace defines the space within which each name must be unique. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces | +| `metadata: annotations:` | `{}` | Annotations is an unstructured key value map stored with a resource that may be set to store and retrieve arbitrary metadata. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations The annotations allows adding `metadata.annotations` to all resources. | +| `metadata: labels:` | `{ "app.kubernetes.io/name": "module-name" "app.kubernetes.io/version": "0.0.0-devel" "app.kubernetes.io/managed-by": "timoni"}` | Map of string keys and values that can be used to organize and categorize (scope and select) objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels Standard Kubernetes labels: app name, version and managed-by. The labels allows adding `metadata.labels` to all resources. The `app.kubernetes.io/name` and `app.kubernetes.io/version` labels are automatically generated and can't be overwritten. | +| `selector: labels:` | `{ "app.kubernetes.io/name": "module-name"}` | Map of string keys and values that can be used to organize and categorize (scope and select) objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels Standard Kubernetes label: app name. | +| `image: repository:` | `*"docker.io/nginx" \| 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. | +| `image: tag:` | `*"1-alpine" \| 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. | +| `image: digest:` | `*"" \| string` | Digest uniquely and immutably identifies an image in the repository. Spec: https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests. | +| `image: pullPolicy:` | `*"IfNotPresent" \| "Always" \| "Never"` | PullPolicy defines the pull policy for the image. By default, it is set to IfNotPresent. | +| `pod: annotations:` | `{}` | | +| `pod: affinity:` | `*{ nodeAffinity: { requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [{ matchExpressions: [{ key: "kubernetes.io/os" operator: "In" values: ["linux"] }] }] } }} \| { nodeAffinity: { requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [{ matchExpressions: [{ key: "kubernetes.io/os" operator: "In" values: ["linux"] }] }] } }} \| {}` | | +| `pod: imagePullSecrets:` | `[...{ name!: strings.MaxRunes(256)}]` | | +| `resources: limits:` | `{}` | Limits describes the maximum amount of compute resources allowed. | +| `resources: requests: cpu:` | `*"10m" \| =~"^[1-9]\\d*m$"` | | +| `resources: requests: memory:` | `*"32Mi" \| =~"^[1-9]\\d*(Mi\|Gi)$"` | | +| `replicas:` | `*1 \| >0 & int` | The number of pods replicas. By default, the number of replicas is 1. | +| `securityContext: capabilities: drop:` | `*["ALL"] \| [string]` | Removed capabilities | +| `securityContext: capabilities: add:` | `*["CHOWN", "NET_BIND_SERVICE", "SETGID", "SETUID"] \| [string]` | Added capabilities | +| `securityContext: privileged:` | `*false \| true` | Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. Note that this field cannot be set when spec.os.name is windows. | +| `securityContext: seLinuxOptions:` | `null \| {}` | The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. | +| `securityContext: windowsOptions:` | `null \| {}` | The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux. | +| `securityContext: runAsUser:` | `null \| int64` | The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. | +| `securityContext: runAsGroup:` | `null \| int64` | The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows. | +| `securityContext: runAsNonRoot:` | `null \| bool` | Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. | +| `securityContext: readOnlyRootFilesystem:` | `null \| bool` | Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows. | +| `securityContext: allowPrivilegeEscalation:` | `*false \| true` | AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows. | +| `securityContext: procMount:` | `null \| string` | procMount denotes the type of proc mount to use for the containers. The default is DefaultProcMount which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows. | +| `securityContext: seccompProfile:` | `null \| { type: string}` | The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. Note that this field cannot be set when spec.os.name is windows. | +| `service: annotations:` | `{}` | | +| `service: port:` | `*80 \| uint16 & >0` | | + diff --git a/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/image.cue b/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/image.cue index 1535ea43..8378bb08 100644 --- a/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/image.cue +++ b/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/image.cue @@ -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 != "" { diff --git a/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/metadata.cue b/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/metadata.cue index 188ff505..afed90dc 100644 --- a/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/metadata.cue +++ b/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/metadata.cue @@ -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 } @@ -74,6 +78,7 @@ import "strings" namespace: #Meta.namespace labels: #Meta.labels + // +nodoc labels: (#StdLabelComponent): #Component annotations?: #Annotations @@ -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 } } @@ -104,6 +111,7 @@ import "strings" name: #Meta.name + "-" + #Component labels: #Meta.labels + // +nodoc labels: (#StdLabelComponent): #Component annotations?: #Annotations @@ -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 } } diff --git a/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/selector.cue b/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/selector.cue index 9c4f2384..87bd377e 100644 --- a/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/selector.cue +++ b/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/selector.cue @@ -15,5 +15,8 @@ package v1alpha1 labels: #Labels // Standard Kubernetes label: app name. - labels: (#StdLabelName): #Name + labels: { + // +nodoc + (#StdLabelName): #Name + } } diff --git a/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/semver.cue b/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/semver.cue index ecd1e397..14dc8ab8 100644 --- a/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/semver.cue +++ b/blueprints/starter/cue.mod/pkg/timoni.sh/core/v1alpha1/semver.cue @@ -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]) } diff --git a/blueprints/starter/templates/config.cue b/blueprints/starter/templates/config.cue index 0e784603..7ed1330a 100644 --- a/blueprints/starter/templates/config.cue +++ b/blueprints/starter/templates/config.cue @@ -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. diff --git a/cmd/timoni/mod_show_config.go b/cmd/timoni/mod_show_config.go index b32534e5..e58e1459 100644 --- a/cmd/timoni/mod_show_config.go +++ b/cmd/timoni/mod_show_config.go @@ -124,17 +124,13 @@ func runConfigShowModCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("build failed: %w", err) } - buildResult, err := builder.Build() - if err != nil { - return describeErr(f.GetModuleRoot(), "validation failed", err) - } + rows, err := builder.GetConfigDoc() - rows, err := builder.GetConfigDoc(buildResult) 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) diff --git a/cmd/timoni/testdata/module/README.md b/cmd/timoni/testdata/module/README.md index 8672510b..d788dfcd 100644 --- a/cmd/timoni/testdata/module/README.md +++ b/cmd/timoni/testdata/module/README.md @@ -41,15 +41,14 @@ timoni -n module delete module ## Configuration -| KEY | TYPE | DEFAULT | DESCRIPTION | -|------------------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `metadata: labels:` | `struct` | `{"app.kubernetes.io/name": "module-name","app.kubernetes.io/kube": "1.27.5","app.kubernetes.io/version": "0.0.0-devel","app.kubernetes.io/team": "test"}` | Map of string keys and values that can be used to organize and categorize (scope and select) objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels Standard Kubernetes labels: app name and version. | -| `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. | -| `server: enabled:` | `bool` | `true` | | -| `domain:` | `string` | `"example.internal"` | | -| `ns: enabled:` | `bool` | `false` | | -| `team:` | `string` | `"test"` | | +| 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. | +| `server: enabled:` | `*true \| bool` | | +| `domain:` | `*"example.internal" \| string` | | +| `ns: enabled:` | `*false \| bool` | | +| `team:` | `"test"` | | diff --git a/cmd/timoni/testdata/module/templates/config.cue b/cmd/timoni/testdata/module/templates/config.cue index dd5722b8..3da673ba 100644 --- a/cmd/timoni/testdata/module/templates/config.cue +++ b/cmd/timoni/testdata/module/templates/config.cue @@ -17,11 +17,9 @@ 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 @@ -29,13 +27,11 @@ import ( } } - // +nodoc server: { enabled: *true | bool } domain: *"example.internal" | string - // +nodoc ns: { enabled: *false | bool } diff --git a/internal/engine/get_config.go b/internal/engine/get_config.go new file mode 100644 index 00000000..d1b3e7f3 --- /dev/null +++ b/internal/engine/get_config.go @@ -0,0 +1,189 @@ +/* +Copyright 2023 Stefan Prodan + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "errors" + "fmt" + "regexp" + "strings" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/ast" + "cuelang.org/go/cue/load" + + apiv1 "github.com/stefanprodan/timoni/api/v1alpha1" +) + +// GetConfigDoc extracts the config structure from the module. +func (b *ModuleBuilder) GetConfigDoc() ([][]string, error) { + var value cue.Value + + cfg := &load.Config{ + ModuleRoot: b.moduleRoot, + Package: b.pkgName, + Dir: b.pkgPath, + DataFiles: true, + Tags: []string{ + "name=" + b.name, + "namespace=" + b.namespace, + }, + TagVars: map[string]load.TagVar{ + "moduleVersion": { + Func: func() (ast.Expr, error) { + return ast.NewString(b.moduleVersion), nil + }, + }, + "kubeVersion": { + Func: func() (ast.Expr, error) { + return ast.NewString(b.kubeVersion), nil + }, + }, + }, + } + + modInstances := load.Instances([]string{}, cfg) + if len(modInstances) == 0 { + return nil, errors.New("no instances found") + } + + modInstance := modInstances[0] + if modInstance.Err != nil { + return nil, fmt.Errorf("instance error: %w", modInstance.Err) + } + + value = b.ctx.BuildInstance(modInstance) + if value.Err() != nil { + return nil, value.Err() + } + + cfgValues := value.LookupPath(cue.ParsePath(apiv1.ConfigValuesSelector.String())) + if cfgValues.Err() != nil { + return nil, fmt.Errorf("lookup %s failed: %w", apiv1.ConfigValuesSelector, cfgValues.Err()) + } + + rows, err := iterateFields(cfgValues) + if err != nil { + return nil, err + } + + return rows, nil +} + +func iterateFields(v cue.Value) ([][]string, error) { + var rows [][]string + + fields, err := v.Fields( + cue.Optional(true), + cue.Concrete(true), + cue.Docs(true), + ) + if err != nil { + return nil, fmt.Errorf("Cue Fields Error: %w", err) + } + + for fields.Next() { + v := fields.Value() + _, noDoc := hasNoDoc(v) + + if noDoc { + continue + } + + // We are chekcing if the field is a struct and not optional and is concrete before we iterate through it + // this allows for definition of default values as full structs without generating output for each + // field in the struct where it doesn't make sense e.g. + // + // - annotations?: {[string]: string} + // - affinity: corev1.Affinity | *{nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: [...]} + if v.IncompleteKind() == cue.StructKind && !fields.IsOptional() && v.IsConcrete() { + //if _, ok := v.Default(); v.IncompleteKind() == cue.StructKind && !fields.IsOptional() && ok { + // Assume we want to use the field + useField := true + iRows, err := iterateFields(v) + + if err != nil { + return nil, err + } + + for _, row := range iRows { + if len(row) > 0 { + // If we have a row with more than 0 elements, we don't want to use the field and should use the child rows instead + useField = false + rows = append(rows, row) + } + } + + if useField { + rows = append(rows, getField(v)) + } + } else { + rows = append(rows, getField(v)) + } + } + + return rows, nil +} + +func hasNoDoc(v cue.Value) (string, bool) { + var noDoc bool + var doc string + + for _, d := range v.Doc() { + if line := len(d.List) - 1; line >= 0 { + switch d.List[line].Text { + case "// +nodoc": + noDoc = true + break + } + } + + doc += d.Text() + doc = strings.ReplaceAll(doc, "\n", " ") + doc = strings.ReplaceAll(doc, "+required", "") + doc = strings.ReplaceAll(doc, "+optional", "") + } + + return doc, noDoc +} + +func getField(v cue.Value) []string { + var row []string + labelDomain := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)?(".+")?$`) + doc, noDoc := hasNoDoc(v) + + if !noDoc { + fieldType := strings.ReplaceAll(fmt.Sprintf("%v", v), "\n", "") + fieldType = strings.ReplaceAll(fieldType, "|", "\\|") + fieldType = strings.ReplaceAll(fieldType, "\":", "\": ") + fieldType = strings.ReplaceAll(fieldType, "\":[", "\": [") + fieldType = strings.ReplaceAll(fieldType, "},", "}, ") + + if len(fieldType) == 0 { + fieldType = " " + } + + field := strings.Replace(v.Path().String(), "timoni.instance.config.", "", 1) + match := labelDomain.FindStringSubmatch(field) + + row = append(row, fmt.Sprintf("`%s:`", strings.ReplaceAll(match[1], ".", ": ")+match[2])) + row = append(row, fmt.Sprintf("`%s`", fieldType)) + row = append(row, fmt.Sprintf("%s", doc)) + } + + return row +} diff --git a/internal/engine/module_builder.go b/internal/engine/module_builder.go index 29b9bbfb..3c2ba998 100644 --- a/internal/engine/module_builder.go +++ b/internal/engine/module_builder.go @@ -22,9 +22,7 @@ import ( "os" "path/filepath" "reflect" - "regexp" "slices" - "strings" "cuelang.org/go/cue" "cuelang.org/go/cue/ast" @@ -316,60 +314,3 @@ func (b *ModuleBuilder) GetContainerImages(value cue.Value) ([]string, error) { return images, nil } - -// GetConfigDoc extracts the config structure from the module. -func (b *ModuleBuilder) GetConfigDoc(value cue.Value) ([][]string, error) { - cfgValues := value.LookupPath(cue.ParsePath(apiv1.ConfigValuesSelector.String())) - if cfgValues.Err() != nil { - return nil, fmt.Errorf("lookup %s failed: %w", apiv1.ConfigValuesSelector, cfgValues.Err()) - } - - labelDomain := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)?(".+")?$`) - - var rows [][]string - configDataInfo := func(v cue.Value) bool { - var row []string - var noDoc bool - var doc string - - for _, d := range v.Doc() { - if line := len(d.List) - 1; line >= 0 { - switch d.List[line].Text { - case "// +nodoc": - noDoc = true - break - } - } - - doc += d.Text() - doc = strings.ReplaceAll(doc, "\n", " ") - doc = strings.ReplaceAll(doc, "+required", "") - doc = strings.ReplaceAll(doc, "+optional", "") - } - - if !noDoc { - defaultVal, _ := v.Default() - valueBytes, _ := defaultVal.MarshalJSON() - valueType := strings.ReplaceAll(v.IncompleteKind().String(), "|", "\\|") - - value := strings.ReplaceAll(string(valueBytes), "\":", "\": ") - value = strings.ReplaceAll(value, "\":[", "\": [") - value = strings.ReplaceAll(value, "},", "}, ") - value = strings.ReplaceAll(value, "|", "\\|") - - field := strings.Replace(v.Path().String(), "timoni.instance.config.", "", 1) - match := labelDomain.FindStringSubmatch(field) - - row = append(row, fmt.Sprintf("`%s:`", strings.ReplaceAll(match[1], ".", ": ")+match[2])) - row = append(row, fmt.Sprintf("`%s`", valueType)) - row = append(row, fmt.Sprintf("`%s`", value)) - row = append(row, fmt.Sprintf("%s", doc)) - rows = append(rows, row) - } - - return true - } - - cfgValues.Walk(configDataInfo, nil) - return rows, nil -} diff --git a/schemas/timoni.sh/core/v1alpha1/image.cue b/schemas/timoni.sh/core/v1alpha1/image.cue index 1535ea43..8378bb08 100644 --- a/schemas/timoni.sh/core/v1alpha1/image.cue +++ b/schemas/timoni.sh/core/v1alpha1/image.cue @@ -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 != "" { diff --git a/schemas/timoni.sh/core/v1alpha1/metadata.cue b/schemas/timoni.sh/core/v1alpha1/metadata.cue index 188ff505..16e38afe 100644 --- a/schemas/timoni.sh/core/v1alpha1/metadata.cue +++ b/schemas/timoni.sh/core/v1alpha1/metadata.cue @@ -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 } @@ -74,7 +78,10 @@ import "strings" namespace: #Meta.namespace labels: #Meta.labels - labels: (#StdLabelComponent): #Component + labels: { + // +nodoc + (#StdLabelComponent): #Component + } annotations?: #Annotations if #Meta.annotations != _|_ { @@ -84,8 +91,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 } } @@ -104,7 +113,10 @@ import "strings" name: #Meta.name + "-" + #Component labels: #Meta.labels - labels: (#StdLabelComponent): #Component + labels: { + // +nodoc + (#StdLabelComponent): #Component + } annotations?: #Annotations if #Meta.annotations != _|_ { @@ -114,7 +126,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 } } diff --git a/schemas/timoni.sh/core/v1alpha1/selector.cue b/schemas/timoni.sh/core/v1alpha1/selector.cue index 9c4f2384..87bd377e 100644 --- a/schemas/timoni.sh/core/v1alpha1/selector.cue +++ b/schemas/timoni.sh/core/v1alpha1/selector.cue @@ -15,5 +15,8 @@ package v1alpha1 labels: #Labels // Standard Kubernetes label: app name. - labels: (#StdLabelName): #Name + labels: { + // +nodoc + (#StdLabelName): #Name + } } diff --git a/schemas/timoni.sh/core/v1alpha1/semver.cue b/schemas/timoni.sh/core/v1alpha1/semver.cue index ecd1e397..14dc8ab8 100644 --- a/schemas/timoni.sh/core/v1alpha1/semver.cue +++ b/schemas/timoni.sh/core/v1alpha1/semver.cue @@ -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]) }