mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 16:54:58 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c5429b64a | ||
|
|
ac5bff4b32 | ||
|
|
6090ab224e | ||
|
|
10e140258d |
15
cmd/holos/testdata/issue15_cue_errors.txt
vendored
Normal file
15
cmd/holos/testdata/issue15_cue_errors.txt
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Want cue errors to show files and lines
|
||||
! exec holos build .
|
||||
stderr 'could not decode: content: cannot convert non-concrete value string'
|
||||
stderr '/component.cue:6:1$'
|
||||
|
||||
-- cue.mod --
|
||||
package holos
|
||||
-- component.cue --
|
||||
package holos
|
||||
|
||||
apiVersion: "holos.run/v1alpha1"
|
||||
kind: "KubernetesObjects"
|
||||
cluster: string @tag(cluster, string)
|
||||
content: foo
|
||||
foo: string
|
||||
@@ -922,7 +922,7 @@ import (
|
||||
kubernetes?: {
|
||||
// Auth configures how secret-manager authenticates with a
|
||||
// Kubernetes instance.
|
||||
auth: struct.MaxFields(1) & {
|
||||
auth: {
|
||||
// has both clientCert and clientKey as secretKeySelector
|
||||
cert?: {
|
||||
// A reference to a specific 'key' within a Secret resource,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package holos
|
||||
|
||||
// Manage Ceph CSI to provide PersistentVolumeClaims to a cluster.
|
||||
|
||||
#TargetNamespace: "ceph-system"
|
||||
|
||||
#InputKeys: {
|
||||
project: "metal"
|
||||
service: "ceph"
|
||||
component: "ceph"
|
||||
}
|
||||
|
||||
#Kustomization: spec: {
|
||||
dependsOn: [{name: "prod-secrets-namespaces"}]
|
||||
targetNamespace: #TargetNamespace
|
||||
}
|
||||
|
||||
#HelmChart & {
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "ceph-csi-rbd"
|
||||
version: "3.10.2"
|
||||
repository: {
|
||||
name: "ceph-csi"
|
||||
url: "https://ceph.github.io/csi-charts"
|
||||
}
|
||||
}
|
||||
objects: [#ExternalSecret & { _name: "ceph-csi-rbd" }]
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package holos
|
||||
|
||||
#Input: {
|
||||
config : {
|
||||
// (required) String representing a Ceph cluster to provision storage from.
|
||||
// Should be unique across all Ceph clusters in use for provisioning,
|
||||
// cannot be greater than 36 bytes in length, and should remain immutable for
|
||||
// the lifetime of the StorageClass in use.
|
||||
clusterID: string
|
||||
// (required) []String list of ceph monitor "address:port" values.
|
||||
monitors: [...string]
|
||||
}
|
||||
}
|
||||
|
||||
// Imported from https://github.com/holos-run/holos-infra/blob/0ae58858f5583d25fa7543e47b5f5e9f0b2f3c83/components/core/metal/ceph-csi-rbd/values.holos.yaml
|
||||
|
||||
#ChartValues: {
|
||||
// Necessary for Talos see https://github.com/siderolabs/talos/discussions/8163
|
||||
selinuxMount: false
|
||||
|
||||
csiConfig: [#Input.config]
|
||||
|
||||
storageClass: {
|
||||
annotations: "storageclass.kubernetes.io/is-default-class": "true"
|
||||
|
||||
// Specifies whether the storageclass should be created
|
||||
create: true
|
||||
name: "ceph-ssd"
|
||||
|
||||
// (optional) Prefix to use for naming RBD images.
|
||||
// If omitted, defaults to "csi-vol-".
|
||||
// NOTE: Set this to a cluster specific value, e.g. vol-k1-
|
||||
volumeNamePrefix: "vol-\(#InputKeys.cluster)-"
|
||||
|
||||
// (required) String representing a Ceph cluster to provision storage from.
|
||||
// Should be unique across all Ceph clusters in use for provisioning,
|
||||
// cannot be greater than 36 bytes in length, and should remain immutable for
|
||||
// the lifetime of the StorageClass in use.
|
||||
clusterID: #Input.config.clusterID
|
||||
|
||||
// (optional) If you want to use erasure coded pool with RBD, you need to
|
||||
// create two pools. one erasure coded and one replicated.
|
||||
// You need to specify the replicated pool here in the `pool` parameter, it is
|
||||
// used for the metadata of the images.
|
||||
// The erasure coded pool must be set as the `dataPool` parameter below.
|
||||
// dataPool: <ec-data-pool>
|
||||
dataPool: ""
|
||||
|
||||
// (required) Ceph pool into which the RBD image shall be created
|
||||
// eg: pool: replicapool
|
||||
pool: "k8s-dev"
|
||||
|
||||
// (optional) RBD image features, CSI creates image with image-format 2 CSI
|
||||
// RBD currently supports `layering`, `journaling`, `exclusive-lock`,
|
||||
// `object-map`, `fast-diff`, `deep-flatten` features.
|
||||
// Refer https://docs.ceph.com/en/latest/rbd/rbd-config-ref/#image-features
|
||||
// for image feature dependencies.
|
||||
// imageFeatures: layering,journaling,exclusive-lock,object-map,fast-diff
|
||||
imageFeatures: "layering"
|
||||
|
||||
// (optional) Specifies whether to try other mounters in case if the current
|
||||
// mounter fails to mount the rbd image for any reason. True means fallback
|
||||
// to next mounter, default is set to false.
|
||||
// Note: tryOtherMounters is currently useful to fallback from krbd to rbd-nbd
|
||||
// in case if any of the specified imageFeatures is not supported by krbd
|
||||
// driver on node scheduled for application pod launch, but in the future this
|
||||
// should work with any mounter type.
|
||||
// tryOtherMounters: false
|
||||
// (optional) uncomment the following to use rbd-nbd as mounter
|
||||
// on supported nodes
|
||||
// mounter: rbd-nbd
|
||||
mounter: ""
|
||||
|
||||
// (optional) ceph client log location, eg: rbd-nbd
|
||||
// By default host-path /var/log/ceph of node is bind-mounted into
|
||||
// csi-rbdplugin pod at /var/log/ceph mount path. This is to configure
|
||||
// target bindmount path used inside container for ceph clients logging.
|
||||
// See docs/rbd-nbd.md for available configuration options.
|
||||
// cephLogDir: /var/log/ceph
|
||||
cephLogDir: ""
|
||||
|
||||
// (optional) ceph client log strategy
|
||||
// By default, log file belonging to a particular volume will be deleted
|
||||
// on unmap, but you can choose to just compress instead of deleting it
|
||||
// or even preserve the log file in text format as it is.
|
||||
// Available options `remove` or `compress` or `preserve`
|
||||
// cephLogStrategy: remove
|
||||
cephLogStrategy: ""
|
||||
|
||||
// (optional) Instruct the plugin it has to encrypt the volume
|
||||
// By default it is disabled. Valid values are "true" or "false".
|
||||
// A string is expected here, i.e. "true", not true.
|
||||
// encrypted: "true"
|
||||
encrypted: ""
|
||||
|
||||
// (optional) Use external key management system for encryption passphrases by
|
||||
// specifying a unique ID matching KMS ConfigMap. The ID is only used for
|
||||
// correlation to configmap entry.
|
||||
encryptionKMSID: ""
|
||||
|
||||
// Add topology constrained pools configuration, if topology based pools
|
||||
// are setup, and topology constrained provisioning is required.
|
||||
// For further information read TODO<doc>
|
||||
// topologyConstrainedPools: |
|
||||
// [{"poolName":"pool0",
|
||||
// "dataPool":"ec-pool0" # optional, erasure-coded pool for data
|
||||
// "domainSegments":[
|
||||
// {"domainLabel":"region","value":"east"},
|
||||
// {"domainLabel":"zone","value":"zone1"}]},
|
||||
// {"poolName":"pool1",
|
||||
// "dataPool":"ec-pool1" # optional, erasure-coded pool for data
|
||||
// "domainSegments":[
|
||||
// {"domainLabel":"region","value":"east"},
|
||||
// {"domainLabel":"zone","value":"zone2"}]},
|
||||
// {"poolName":"pool2",
|
||||
// "dataPool":"ec-pool2" # optional, erasure-coded pool for data
|
||||
// "domainSegments":[
|
||||
// {"domainLabel":"region","value":"west"},
|
||||
// {"domainLabel":"zone","value":"zone1"}]}
|
||||
// ]
|
||||
topologyConstrainedPools: []
|
||||
|
||||
// (optional) mapOptions is a comma-separated list of map options.
|
||||
// For krbd options refer
|
||||
// https://docs.ceph.com/docs/master/man/8/rbd/#kernel-rbd-krbd-options
|
||||
// For nbd options refer
|
||||
// https://docs.ceph.com/docs/master/man/8/rbd-nbd/#options
|
||||
// Format:
|
||||
// mapOptions: "<mounter>:op1,op2;<mounter>:op1,op2"
|
||||
// An empty mounter field is treated as krbd type for compatibility.
|
||||
// eg:
|
||||
// mapOptions: "krbd:lock_on_read,queue_depth=1024;nbd:try-netlink"
|
||||
mapOptions: ""
|
||||
|
||||
// (optional) unmapOptions is a comma-separated list of unmap options.
|
||||
// For krbd options refer
|
||||
// https://docs.ceph.com/docs/master/man/8/rbd/#kernel-rbd-krbd-options
|
||||
// For nbd options refer
|
||||
// https://docs.ceph.com/docs/master/man/8/rbd-nbd/#options
|
||||
// Format:
|
||||
// unmapOptions: "<mounter>:op1,op2;<mounter>:op1,op2"
|
||||
// An empty mounter field is treated as krbd type for compatibility.
|
||||
// eg:
|
||||
// unmapOptions: "krbd:force;nbd:force"
|
||||
unmapOptions: ""
|
||||
|
||||
// The secrets have to contain Ceph credentials with required access
|
||||
// to the 'pool'.
|
||||
provisionerSecret: "csi-rbd-secret"
|
||||
// If Namespaces are left empty, the secrets are assumed to be in the
|
||||
// Release namespace.
|
||||
provisionerSecretNamespace: ""
|
||||
controllerExpandSecret: "csi-rbd-secret"
|
||||
controllerExpandSecretNamespace: ""
|
||||
nodeStageSecret: "csi-rbd-secret"
|
||||
nodeStageSecretNamespace: ""
|
||||
// Specify the filesystem type of the volume. If not specified,
|
||||
// csi-provisioner will set default as `ext4`.
|
||||
fstype: "ext4"
|
||||
reclaimPolicy: "Delete"
|
||||
allowVolumeExpansion: true
|
||||
mountOptions: []
|
||||
}
|
||||
|
||||
secret: {
|
||||
// Specifies whether the secret should be created
|
||||
create: false
|
||||
name: "csi-rbd-secret"
|
||||
// Key values correspond to a user name and its key, as defined in the
|
||||
// ceph cluster. User ID should have required access to the 'pool'
|
||||
// specified in the storage class
|
||||
userID: "admin"
|
||||
userKey: "$(ceph auth get-key client.admin)"
|
||||
// Encryption passphrase
|
||||
encryptionPassphrase: "$(python -c 'import secrets; print(secrets.token_hex(32));')"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package holos
|
||||
|
||||
#Input: {
|
||||
config: {
|
||||
clusterID: "a6de32ab-c84f-49a6-b97e-e31dc2a70931"
|
||||
monitors: ["10.64.1.21:6789", "10.64.1.31:6789", "10.64.1.41:6789"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Metal Clusters
|
||||
|
||||
This cluster type is overlaid onto other cluster types to add services necessary outside of a cloud like GKE or EKS. Ceph for PersistenVolumeClaim support on a Talos Proxmox cluster is the primary use case.
|
||||
@@ -13,11 +13,7 @@ package holos
|
||||
|
||||
objects: [
|
||||
#SecretStore,
|
||||
#ExternalSecret & {
|
||||
_name: "validate"
|
||||
metadata: namespace: #TargetNamespace
|
||||
spec: dataFrom: [{extract: key: "ns/" + #TargetNamespace + "/test"}]
|
||||
},
|
||||
#ExternalSecret & { _name: "validate" },
|
||||
]
|
||||
|
||||
{} & #KubernetesObjects
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package holos
|
||||
|
||||
// #PlatformNamespaces is the union of all namespaces across all cluster types. Namespaces are created in all clusters regardless of if they're
|
||||
// used within the cluster or not. The is important for security and consistency with IAM, RBAC, and Secrets sync between clusters.
|
||||
#PlatformNamespaces: [
|
||||
{name: "external-secrets"},
|
||||
{name: "holos-system"},
|
||||
|
||||
@@ -79,10 +79,10 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
kind: string | *"GitRepository"
|
||||
name: string | *"flux-system"
|
||||
}
|
||||
suspend?: bool
|
||||
suspend?: bool
|
||||
targetNamespace?: string
|
||||
timeout: string | *"3m0s"
|
||||
wait: bool | *true
|
||||
timeout: string | *"3m0s"
|
||||
wait: bool | *true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
#ExternalSecret: #NamespaceObject & es.#ExternalSecret & {
|
||||
_name: string
|
||||
metadata: {
|
||||
namespace: string
|
||||
namespace: #TargetNamespace
|
||||
name: _name
|
||||
}
|
||||
spec: {
|
||||
@@ -102,24 +102,29 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
target: {
|
||||
creationPolicy: string | *"Owner"
|
||||
}
|
||||
data: [{
|
||||
remoteRef: key: _name
|
||||
secretKey: _name
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#SecretStore: #NamespaceObject & ss.#SecretStore & {
|
||||
metadata: {
|
||||
name: string | *"default"
|
||||
namespace: string | *#TargetNamespace
|
||||
namespace: #TargetNamespace
|
||||
}
|
||||
spec: provider: {
|
||||
vault: {
|
||||
auth: kubernetes: {
|
||||
mountPath: #InputKeys.cluster
|
||||
role: string | *"default"
|
||||
serviceAccountRef: name: string | *"default"
|
||||
kubernetes: {
|
||||
remoteNamespace: #TargetNamespace
|
||||
auth: token: bearerToken: {
|
||||
name: string | *"eso-reader"
|
||||
key: string | *"token"
|
||||
}
|
||||
server: {
|
||||
caBundle: #InputKeys.provisionerCABundle
|
||||
url: #InputKeys.provisionerURL
|
||||
}
|
||||
path: string | *"kv/k8s"
|
||||
server: "https://vault.core." + #Platform.org.domain
|
||||
version: string | *"v2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +145,11 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
// GCP Project Info used for the Provisioner Cluster
|
||||
gcpProjectID: string @tag(gcpProjectID, type=string)
|
||||
gcpProjectNumber: int @tag(gcpProjectNumber, type=int)
|
||||
|
||||
// Same as cluster certificate-authority-data field in ~/.holos/kubeconfig.provisioner
|
||||
provisionerCABundle: string @tag(provisionerCABundle, type=string)
|
||||
// Same as the cluster server field in ~/.holos/kubeconfig.provisioner
|
||||
provisionerURL: string @tag(provisionerURL, type=string)
|
||||
}
|
||||
|
||||
// #Platform defines the primary lookup table for the platform. Lookup keys should be limited to those defined in #KeyTags.
|
||||
@@ -189,8 +199,9 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
kind: "KubernetesObjects"
|
||||
// objects holds a list of the kubernetes api objects to configure.
|
||||
objects: [...metav1.#TypeMeta] | *[]
|
||||
// out holds the rendered yaml text stream of kubernetes api objects.
|
||||
// content holds the rendered yaml text stream of kubernetes api objects.
|
||||
content: yaml.MarshalStream(objects)
|
||||
contentType: "application/yaml"
|
||||
// ksObjects holds the flux Kustomization objects for gitops
|
||||
ksObjects: [...#Kustomization] | *[#Kustomization]
|
||||
// ksContent is the yaml representation of kustomization
|
||||
@@ -209,6 +220,9 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
}
|
||||
}
|
||||
|
||||
// #ChartValues represent the values provided to a helm chart. Existing values may be imorted using cue import values.yaml -p holos then wrapping the values.cue content in #Values: {}
|
||||
#ChartValues: {...}
|
||||
|
||||
// #HelmChart is a holos component which produces kubernetes api objects from cue values provided to the helm template command.
|
||||
#HelmChart: {
|
||||
#OutputTypeMeta
|
||||
@@ -222,13 +236,18 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
// chart defines the upstream helm chart to process.
|
||||
chart: #Chart
|
||||
// values represents the helm values to provide to the chart.
|
||||
values: {...}
|
||||
values: #ChartValues
|
||||
// valuesContent holds the values yaml
|
||||
valuesContent: yaml.Marshal(values)
|
||||
// platform returns the platform data structure for visibility / troubleshooting.
|
||||
platform: #Platform
|
||||
// instance returns the key values of the holos component instance.
|
||||
instance: #InputKeys
|
||||
// objects holds a list of the kubernetes api objects to configure.
|
||||
objects: [...metav1.#TypeMeta] | *[]
|
||||
// content holds the rendered yaml text stream of kubernetes api objects.
|
||||
content: yaml.MarshalStream(objects)
|
||||
contentType: "application/yaml"
|
||||
}
|
||||
|
||||
// #PlatformSpec is the output schema of a platform specification.
|
||||
|
||||
@@ -27,11 +27,3 @@ func New(name string) *cobra.Command {
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// EnsureNewline adds a trailing newline if not already there.
|
||||
func EnsureNewline(b []byte) []byte {
|
||||
if len(b) > 0 && b[len(b)-1] != '\n' {
|
||||
b = append(b, '\n')
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/holos-run/holos/pkg/cli/secret"
|
||||
"github.com/holos-run/holos/pkg/holos"
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"github.com/holos-run/holos/pkg/util"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -77,7 +78,7 @@ func makeGetRunFunc(cfg *holos.Config, cf getConfig) command.RunFunc {
|
||||
// Print one file to stdout
|
||||
if key := *cf.file; key != "" {
|
||||
if data, found := secret.Data[key]; found {
|
||||
cfg.Write(command.EnsureNewline(data))
|
||||
cfg.Write(util.EnsureNewline(data))
|
||||
return nil
|
||||
}
|
||||
return wrapper.Wrap(fmt.Errorf("not found: %s have %#v", key, keys))
|
||||
@@ -89,7 +90,7 @@ func makeGetRunFunc(cfg *holos.Config, cf getConfig) command.RunFunc {
|
||||
|
||||
for k, v := range secret.Data {
|
||||
cfg.Printf("-- %s --\n", k)
|
||||
cfg.Write(command.EnsureNewline(v))
|
||||
cfg.Write(util.EnsureNewline(v))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -2,7 +2,8 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"cuelang.org/go/cue/errors"
|
||||
"fmt"
|
||||
"github.com/holos-run/holos/pkg/holos"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"log/slog"
|
||||
@@ -15,21 +16,27 @@ func MakeMain(options ...holos.Option) func() int {
|
||||
slog.SetDefault(cfg.Logger())
|
||||
ctx := context.Background()
|
||||
if err := New(cfg).ExecuteContext(ctx); err != nil {
|
||||
return handleError(ctx, err, cfg)
|
||||
return HandleError(ctx, err, cfg)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// handleError is the top level error handler that unwraps and logs errors.
|
||||
func handleError(ctx context.Context, err error, hc *holos.Config) (exitCode int) {
|
||||
// HandleError is the top level error handler that unwraps and logs errors.
|
||||
func HandleError(ctx context.Context, err error, hc *holos.Config) (exitCode int) {
|
||||
log := hc.NewTopLevelLogger()
|
||||
var cueErr errors.Error
|
||||
var errAt *wrapper.ErrorAt
|
||||
const msg = "could not execute"
|
||||
if ok := errors.As(err, &errAt); ok {
|
||||
if errors.As(err, &errAt) {
|
||||
log.ErrorContext(ctx, msg, "err", errAt.Unwrap(), "loc", errAt.Source.Loc())
|
||||
} else {
|
||||
log.ErrorContext(ctx, msg, "err", err)
|
||||
}
|
||||
// cue errors are bundled up as a list and refer to multiple files / lines.
|
||||
if errors.As(err, &cueErr) {
|
||||
msg := errors.Details(cueErr, nil)
|
||||
_, _ = fmt.Fprint(hc.Stderr(), msg)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func makeGetRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
|
||||
|
||||
log.DebugContext(ctx, "results", "len", len(list.Items))
|
||||
if len(list.Items) < 1 {
|
||||
continue
|
||||
return wrapper.Wrap(fmt.Errorf("not found: %v", secretName))
|
||||
}
|
||||
|
||||
// Sort oldest first.
|
||||
|
||||
@@ -55,11 +55,12 @@ func cmdHolos(ts *testscript.TestScript, neg bool, args []string) {
|
||||
cmd := cli.New(cfg)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
|
||||
if neg {
|
||||
if err == nil {
|
||||
ts.Fatalf("want: error\nhave: %v", err)
|
||||
ts.Fatalf("\nwant: error\nhave: %v", err)
|
||||
} else {
|
||||
ts.Logf("want: error\nhave: %v", err)
|
||||
cli.HandleError(cmd.Context(), err, cfg)
|
||||
}
|
||||
} else {
|
||||
ts.Check(err)
|
||||
|
||||
3
pkg/cli/secret/testdata/issue20_secret_not_found.txt
vendored
Normal file
3
pkg/cli/secret/testdata/issue20_secret_not_found.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Want missing secrets to exit non-zero https://github.com/holos-run/holos/issues/20
|
||||
! holos get secret does-not-exist
|
||||
stderr 'not found: does-not-exist'
|
||||
@@ -67,9 +67,9 @@ func printFile(w io.Writer, idx int, a *txtar.Archive) (err error) {
|
||||
return wrapper.Wrap(fmt.Errorf("idx cannot be 0"))
|
||||
}
|
||||
if idx > 0 {
|
||||
_, err = w.Write(command.EnsureNewline(a.Files[idx-1].Data))
|
||||
_, err = w.Write(util.EnsureNewline(a.Files[idx-1].Data))
|
||||
} else {
|
||||
_, err = w.Write(command.EnsureNewline(a.Files[len(a.Files)+idx].Data))
|
||||
_, err = w.Write(util.EnsureNewline(a.Files[len(a.Files)+idx].Data))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/holos-run/holos"
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"github.com/holos-run/holos/pkg/util"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -74,9 +75,10 @@ type Metadata struct {
|
||||
|
||||
// Result is the build result for display or writing.
|
||||
type Result struct {
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
KsContent string `json:"ksContent,omitempty"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ContentType string `json:"contentType"`
|
||||
KsContent string `json:"ksContent,omitempty"`
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
@@ -99,6 +101,8 @@ type HelmChart struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Chart Chart `json:"chart"`
|
||||
ValuesContent string `json:"valuesContent"`
|
||||
ContentType string `json:"contentType"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// Name returns the metadata name of the result. Equivalent to the
|
||||
@@ -225,6 +229,16 @@ func (b *Builder) Run(ctx context.Context) (results []*Result, err error) {
|
||||
if err := runHelm(ctx, &helmChart, &result, holos.PathComponent(instance.Dir)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Append any cue api objects defined alongside the helm holos component.
|
||||
if helmChart.Content != "" && helmChart.ContentType == "application/yaml" {
|
||||
buf := []byte(result.Content)
|
||||
util.EnsureNewline(buf)
|
||||
buf = append(buf, []byte("---\n# Source: holos component overlay objects\n")...)
|
||||
buf = append(buf, []byte(helmChart.Content)...)
|
||||
log.DebugContext(ctx, "added additional api objects", "bytes", len(buf))
|
||||
result.Content = string(buf)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, wrapper.Wrap(fmt.Errorf("build kind not implemented: %v", kind))
|
||||
}
|
||||
|
||||
9
pkg/util/util.go
Normal file
9
pkg/util/util.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package util
|
||||
|
||||
// EnsureNewline adds a trailing newline if not already there.
|
||||
func EnsureNewline(b []byte) []byte {
|
||||
if len(b) > 0 && b[len(b)-1] != '\n' {
|
||||
b = append(b, '\n')
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
46
|
||||
47
|
||||
|
||||
Reference in New Issue
Block a user