mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 16:54:58 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6090ab224e | ||
|
|
10e140258d | ||
|
|
40ac705f0d | ||
|
|
b4ad6425e5 | ||
|
|
3343d226e5 | ||
|
|
f3a9b7cfbc | ||
|
|
53b7246d5e |
@@ -1,28 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/holos-run/holos/pkg/cli"
|
||||
"github.com/holos-run/holos/pkg/holos"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := holos.New()
|
||||
slog.SetDefault(cfg.Logger())
|
||||
ctx := context.Background()
|
||||
if err := cli.New(cfg).ExecuteContext(ctx); err != nil {
|
||||
log := cfg.NewTopLevelLogger()
|
||||
var errAt *wrapper.ErrorAt
|
||||
const msg = "could not execute"
|
||||
if ok := errors.As(err, &errAt); ok {
|
||||
log.ErrorContext(ctx, msg, "err", errAt.Unwrap(), "loc", errAt.Source.Loc())
|
||||
} else {
|
||||
log.ErrorContext(ctx, msg, "err", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(cli.MakeMain()())
|
||||
}
|
||||
|
||||
20
cmd/holos/main_test.go
Normal file
20
cmd/holos/main_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/holos-run/holos/pkg/cli"
|
||||
"github.com/rogpeppe/go-internal/testscript"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(testscript.RunMain(m, map[string]func() int{
|
||||
"holos": cli.MakeMain(),
|
||||
}))
|
||||
}
|
||||
|
||||
func TestGetSecrets(t *testing.T) {
|
||||
testscript.Run(t, testscript.Params{
|
||||
Dir: "testdata",
|
||||
})
|
||||
}
|
||||
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
|
||||
5
cmd/holos/testdata/version.txt
vendored
Normal file
5
cmd/holos/testdata/version.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
exec holos --version
|
||||
# want version with no v on stdout
|
||||
stdout -count=1 '^\d+\.\d+\.\d+$'
|
||||
# want nothing on stderr
|
||||
! stderr .
|
||||
@@ -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,
|
||||
|
||||
@@ -15,6 +15,10 @@ objects: #CredsRefresherService.objects
|
||||
|
||||
#TargetNamespace: #CredsRefresher.namespace
|
||||
|
||||
#Kustomization: spec: {
|
||||
dependsOn: [{name: #InstancePrefix + "-namespaces"}]
|
||||
}
|
||||
|
||||
let NAME = #CredsRefresher.name
|
||||
let AUD = "//iam.googleapis.com/projects/\(#InputKeys.gcpProjectNumber)/locations/global/workloadIdentityPools/holos/providers/k8s-\(#InputKeys.cluster)"
|
||||
let MOUNT = "/var/run/service-account"
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
package holos
|
||||
|
||||
// Manages the External Secrets Operator from the official upstream Helm chart.
|
||||
|
||||
#TargetNamespace: "external-secrets"
|
||||
|
||||
#InputKeys: component: "eso"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
service: "eso"
|
||||
}
|
||||
|
||||
#Kustomization: spec: {
|
||||
dependsOn: [{name: #InstancePrefix + "-namespaces"}]
|
||||
targetNamespace: #TargetNamespace
|
||||
}
|
||||
|
||||
#HelmChart & {
|
||||
values: installCrds: true
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "external-secrets"
|
||||
version: "0.9.12"
|
||||
repository: {
|
||||
name: "external-secrets"
|
||||
url: "https://charts.external-secrets.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package holos
|
||||
|
||||
#TargetNamespace: "external-secrets"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
service: "eso"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package holos
|
||||
|
||||
#Kustomization: spec: dependsOn: [{name: #InstancePrefix + "-namespaces"}]
|
||||
|
||||
#HelmChart & {
|
||||
values: installCrds: true
|
||||
namespace: #TargetNamespace
|
||||
chart: {
|
||||
name: "external-secrets"
|
||||
version: "0.9.12"
|
||||
repository: {
|
||||
name: "external-secrets"
|
||||
url: "https://charts.external-secrets.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package holos
|
||||
|
||||
// Validate ESO by syncing a secret with a SecretStore.
|
||||
|
||||
#TargetNamespace: "holos-system"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "validate"
|
||||
}
|
||||
|
||||
#Kustomization: spec: dependsOn: [{name: #InstancePrefix + "-eso"}]
|
||||
|
||||
objects: [
|
||||
#SecretStore,
|
||||
#ExternalSecret & { _name: "validate" },
|
||||
]
|
||||
|
||||
{} & #KubernetesObjects
|
||||
@@ -1,6 +1,7 @@
|
||||
package holos
|
||||
|
||||
#PlatformNamespaces: [
|
||||
{name: "external-secrets"},
|
||||
{name: "holos-system"},
|
||||
{name: "flux-system"},
|
||||
{name: "ceph-system"},
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package holos
|
||||
|
||||
#Kustomization: spec: dependsOn: [{name: #InstancePrefix + "-eso"}]
|
||||
|
||||
objects: [
|
||||
#SecretStore,
|
||||
#ExternalSecret & {
|
||||
_name: "validate"
|
||||
spec: dataFrom: [{extract: key: "ns/" + #TargetNamespace + "/test"}]
|
||||
},
|
||||
]
|
||||
|
||||
{} & #KubernetesObjects
|
||||
@@ -1,8 +0,0 @@
|
||||
package holos
|
||||
|
||||
#TargetNamespace: "default"
|
||||
|
||||
#InputKeys: {
|
||||
project: "secrets"
|
||||
component: "validate"
|
||||
}
|
||||
@@ -79,8 +79,10 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
kind: string | *"GitRepository"
|
||||
name: string | *"flux-system"
|
||||
}
|
||||
timeout: string | *"3m0s"
|
||||
wait: bool | *true
|
||||
suspend?: bool
|
||||
targetNamespace?: string
|
||||
timeout: string | *"3m0s"
|
||||
wait: bool | *true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +90,7 @@ _apiVersion: "holos.run/v1alpha1"
|
||||
#ExternalSecret: #NamespaceObject & es.#ExternalSecret & {
|
||||
_name: string
|
||||
metadata: {
|
||||
namespace: string | *"default"
|
||||
namespace: #TargetNamespace
|
||||
name: _name
|
||||
}
|
||||
spec: {
|
||||
@@ -100,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,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.
|
||||
|
||||
3
go.mod
3
go.mod
@@ -17,6 +17,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/emicklei/proto v1.10.0 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
@@ -38,7 +39,9 @@ require (
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/oauth2 v0.10.0 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -13,6 +13,8 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw=
|
||||
github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
@@ -88,12 +90,16 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIsgmHpEOGbUs6VtHBXRR1OHevnj7hLx9ZcdNGW4=
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
|
||||
github.com/rogpeppe/go-internal v1.11.1-0.20231026093722-fa6a31e0812c h1:fPpdjePK1atuOg28PXfNSqgwf9I/qD1Hlo39JFwKBXk=
|
||||
github.com/rogpeppe/go-internal v1.11.1-0.20231026093722-fa6a31e0812c/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
|
||||
42
pkg/cli/main.go
Normal file
42
pkg/cli/main.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"cuelang.org/go/cue/errors"
|
||||
"fmt"
|
||||
"github.com/holos-run/holos/pkg/holos"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// MakeMain makes a main function for the cli or tests.
|
||||
func MakeMain(options ...holos.Option) func() int {
|
||||
return func() (exitCode int) {
|
||||
cfg := holos.New(options...)
|
||||
slog.SetDefault(cfg.Logger())
|
||||
ctx := context.Background()
|
||||
if err := New(cfg).ExecuteContext(ctx); err != nil {
|
||||
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) {
|
||||
log := hc.NewTopLevelLogger()
|
||||
var cueErr errors.Error
|
||||
var errAt *wrapper.ErrorAt
|
||||
const msg = "could not execute"
|
||||
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
|
||||
}
|
||||
@@ -26,6 +26,7 @@ func NewCreateCmd(hc *holos.Config) *cobra.Command {
|
||||
cfg, flagSet := newConfig()
|
||||
flagSet.Var(&cfg.files, "from-file", "store files as keys in the secret")
|
||||
cfg.dryRun = flagSet.Bool("dry-run", false, "dry run")
|
||||
cfg.appendHash = flagSet.Bool("append-hash", true, "append hash to kubernetes secret name")
|
||||
|
||||
cmd.Flags().SortFlags = false
|
||||
cmd.Flags().AddGoFlagSet(flagSet)
|
||||
@@ -40,6 +41,10 @@ func makeCreateRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
|
||||
log := logger.FromContext(ctx)
|
||||
secretName := args[0]
|
||||
secret := &v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Labels: map[string]string{NameLabel: secretName},
|
||||
@@ -47,10 +52,12 @@ func makeCreateRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
|
||||
Data: make(secretData),
|
||||
}
|
||||
|
||||
clusterPrefix := fmt.Sprintf("%s-", *cfg.cluster)
|
||||
if !strings.HasPrefix(secretName, clusterPrefix) {
|
||||
const msg = "missing cluster name prefix"
|
||||
log.WarnContext(ctx, msg, "have", secretName, "want", clusterPrefix)
|
||||
if *cfg.cluster != "" {
|
||||
clusterPrefix := fmt.Sprintf("%s-", *cfg.cluster)
|
||||
if !strings.HasPrefix(secretName, clusterPrefix) {
|
||||
const msg = "missing cluster name prefix"
|
||||
log.WarnContext(ctx, msg, "have", secretName, "want", clusterPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range cfg.files {
|
||||
@@ -66,10 +73,12 @@ func makeCreateRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
|
||||
secret.Labels[ClusterLabel] = *cfg.cluster
|
||||
}
|
||||
|
||||
if secretHash, err := hash.SecretHash(secret); err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
} else {
|
||||
secret.Name = fmt.Sprintf("%s-%s", secret.Name, secretHash)
|
||||
if *cfg.appendHash {
|
||||
if secretHash, err := hash.SecretHash(secret); err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
} else {
|
||||
secret.Name = fmt.Sprintf("%s-%s", secret.Name, secretHash)
|
||||
}
|
||||
}
|
||||
|
||||
if *cfg.dryRun {
|
||||
@@ -107,9 +116,7 @@ func makeWalkFunc(data secretData, root string) fs.WalkDirFunc {
|
||||
depth := strings.Count(path[len(root):], string(filepath.Separator))
|
||||
|
||||
if depth > 1 {
|
||||
if d.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if !d.IsDir() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
@@ -25,6 +26,7 @@ func NewGetCmd(hc *holos.Config) *cobra.Command {
|
||||
flagSet.Var(&cfg.files, "to-file", "extract files from the secret")
|
||||
cfg.printFile = flagSet.String(printFlagName, "", "print one key from the secret")
|
||||
cfg.extract = flagSet.Bool("extract-all", false, "extract all files from the secret")
|
||||
cfg.extractTo = flagSet.String("extract-to", ".", "extract to directory")
|
||||
|
||||
cmd.Flags().SortFlags = false
|
||||
cmd.Flags().AddGoFlagSet(flagSet)
|
||||
@@ -109,10 +111,11 @@ func makeGetRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
|
||||
err := fmt.Errorf("%s not found in %v", name, keys)
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
if err := os.WriteFile(name, data, 0666); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not write %s: %w", name, err))
|
||||
path := filepath.Join(*cfg.extractTo, name)
|
||||
if err := os.WriteFile(path, data, 0666); err != nil {
|
||||
return wrapper.Wrap(fmt.Errorf("could not write %s: %w", path, err))
|
||||
}
|
||||
log.InfoContext(ctx, "wrote: "+name, "bytes", len(data))
|
||||
log.InfoContext(ctx, "wrote: "+path, "name", name, "bytes", len(data))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,14 @@ const ClusterLabel = "holos.run/cluster.name"
|
||||
type secretData map[string][]byte
|
||||
|
||||
type config struct {
|
||||
files holos.StringSlice
|
||||
printFile *string
|
||||
extract *bool
|
||||
dryRun *bool
|
||||
cluster *string
|
||||
namespace *string
|
||||
files holos.StringSlice
|
||||
printFile *string
|
||||
extract *bool
|
||||
dryRun *bool
|
||||
appendHash *bool
|
||||
cluster *string
|
||||
namespace *string
|
||||
extractTo *string
|
||||
}
|
||||
|
||||
func newConfig() (*config, *flag.FlagSet) {
|
||||
|
||||
81
pkg/cli/secret/secret_test.go
Normal file
81
pkg/cli/secret/secret_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package secret_test
|
||||
|
||||
import (
|
||||
"github.com/holos-run/holos/pkg/cli"
|
||||
"github.com/holos-run/holos/pkg/holos"
|
||||
"github.com/rogpeppe/go-internal/testscript"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const clientsetKey = "clientset"
|
||||
|
||||
var secret = v1.Secret{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Secret",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "k2-talos",
|
||||
Namespace: "secrets",
|
||||
Labels: map[string]string{
|
||||
"holos.run/owner.name": "jeff",
|
||||
"holos.run/secret.name": "k2-talos",
|
||||
},
|
||||
CreationTimestamp: metav1.Time{
|
||||
Time: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"secrets.yaml": []byte("content: secret\n"),
|
||||
},
|
||||
Type: "Opaque",
|
||||
}
|
||||
|
||||
// cmdHolos executes the holos root command with a kubernetes.Interface that
|
||||
// persists for the duration of the testscript. holos is NOT executed in a
|
||||
// subprocess, the current working directory is not and should not be changed.
|
||||
// Take care to read and write to $WORK in the test scripts using flags.
|
||||
func cmdHolos(ts *testscript.TestScript, neg bool, args []string) {
|
||||
clientset, ok := ts.Value(clientsetKey).(kubernetes.Interface)
|
||||
if clientset == nil || !ok {
|
||||
ts.Fatalf("missing kubernetes.Interface")
|
||||
}
|
||||
|
||||
cfg := holos.New(
|
||||
holos.ProvisionerClientset(clientset),
|
||||
holos.Stdout(ts.Stdout()),
|
||||
holos.Stderr(ts.Stderr()),
|
||||
)
|
||||
|
||||
cmd := cli.New(cfg)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
if neg {
|
||||
if err == nil {
|
||||
ts.Fatalf("want: error\nhave: %v", err)
|
||||
} else {
|
||||
ts.Logf("want: error\nhave: %v", err)
|
||||
}
|
||||
} else {
|
||||
ts.Check(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecrets(t *testing.T) {
|
||||
// Add TestWork: true to the Params to keep the $WORK directory around.
|
||||
testscript.Run(t, testscript.Params{
|
||||
Dir: "testdata",
|
||||
Setup: func(env *testscript.Env) error {
|
||||
env.Values[clientsetKey] = fake.NewSimpleClientset(&secret)
|
||||
return nil
|
||||
},
|
||||
Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){
|
||||
"holos": cmdHolos,
|
||||
},
|
||||
})
|
||||
}
|
||||
21
pkg/cli/secret/testdata/create_secret_dry_run.txt
vendored
Normal file
21
pkg/cli/secret/testdata/create_secret_dry_run.txt
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Create the secret
|
||||
holos create secret directory --from-file=$WORK/fixture --dry-run
|
||||
|
||||
# Want no warnings.
|
||||
! stderr 'WRN'
|
||||
|
||||
# Want the data keys
|
||||
stdout 'one.yaml: Y29udGVudDogb25lCg=='
|
||||
stdout 'two.yaml: Y29udGVudDogdHdvCg=='
|
||||
|
||||
# Want the secret name label.
|
||||
stdout 'holos.run/secret.name: directory'
|
||||
|
||||
# Want the TypeMeta
|
||||
stdout 'kind: Secret'
|
||||
stdout 'apiVersion: v1'
|
||||
|
||||
-- fixture/one.yaml --
|
||||
content: one
|
||||
-- fixture/two.yaml --
|
||||
content: two
|
||||
22
pkg/cli/secret/testdata/create_secret_from_dir.txt
vendored
Normal file
22
pkg/cli/secret/testdata/create_secret_from_dir.txt
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Create the secret
|
||||
holos create secret directory --from-file=$WORK/want
|
||||
stderr 'created: directory-..........'
|
||||
stderr 'secret=directory-..........'
|
||||
stderr 'name=directory'
|
||||
stderr 'namespace=secrets'
|
||||
! stderr 'WRN'
|
||||
|
||||
# Get the secret back
|
||||
mkdir have
|
||||
holos get secret directory --extract-all --extract-to=$WORK/have
|
||||
stderr 'wrote: .*/have/one.yaml'
|
||||
stderr 'wrote: .*/have/two.yaml'
|
||||
|
||||
# Compare the secrets
|
||||
cmp want/one.yaml have/one.yaml
|
||||
cmp want/two.yaml have/two.yaml
|
||||
|
||||
-- want/one.yaml --
|
||||
content: one
|
||||
-- want/two.yaml --
|
||||
content: two
|
||||
14
pkg/cli/secret/testdata/create_secret_from_file.txt
vendored
Normal file
14
pkg/cli/secret/testdata/create_secret_from_file.txt
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Create the secret.
|
||||
holos create secret k3-talos --from-file $WORK/secrets.yaml
|
||||
|
||||
# Want info log attributes.
|
||||
stderr 'created: k3-talos-..........'
|
||||
stderr 'secret=k3-talos-..........'
|
||||
stderr 'name=k3-talos'
|
||||
stderr 'namespace=secrets'
|
||||
|
||||
# Want no warnings.
|
||||
! stderr 'WRN'
|
||||
|
||||
-- secrets.yaml --
|
||||
content: hello
|
||||
14
pkg/cli/secret/testdata/create_secret_namespace.txt
vendored
Normal file
14
pkg/cli/secret/testdata/create_secret_namespace.txt
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Create the secret.
|
||||
holos create secret k3-talos --namespace=jeff --from-file $WORK/secrets.yaml
|
||||
stderr 'created: k3-talos-..........'
|
||||
stderr 'secret=k3-talos-..........'
|
||||
stderr 'name=k3-talos'
|
||||
|
||||
# Want specified namespace.
|
||||
stderr 'namespace=jeff'
|
||||
|
||||
# Want no warnings.
|
||||
! stderr 'WRN'
|
||||
|
||||
-- secrets.yaml --
|
||||
content: hello
|
||||
24
pkg/cli/secret/testdata/create_secret_no_depth.txt
vendored
Normal file
24
pkg/cli/secret/testdata/create_secret_no_depth.txt
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Create the secret
|
||||
holos create secret directory --from-file=$WORK/want
|
||||
|
||||
# Get the secret back
|
||||
mkdir have
|
||||
holos get secret directory --extract-all --extract-to=$WORK/have
|
||||
stderr 'wrote: .*/have/one.yaml'
|
||||
stderr 'wrote: .*/have/two.yaml'
|
||||
! stderr 'wrote: .*omit.yaml'
|
||||
|
||||
# Compare the secrets
|
||||
cmp want/one.yaml have/one.yaml
|
||||
cmp want/two.yaml have/two.yaml
|
||||
|
||||
# Want no files with depth > 1
|
||||
! exists have/nope/omit.yaml
|
||||
! exists have/omit.yaml
|
||||
|
||||
-- want/one.yaml --
|
||||
content: one
|
||||
-- want/two.yaml --
|
||||
content: two
|
||||
-- want/nope/omit.yaml --
|
||||
content: not included
|
||||
7
pkg/cli/secret/testdata/create_secret_no_hash.txt
vendored
Normal file
7
pkg/cli/secret/testdata/create_secret_no_hash.txt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Want no hash appended
|
||||
holos create secret test --namespace holos-system --from-file $WORK/test --append-hash=false
|
||||
stderr ' created: test '
|
||||
stderr ' secret=test '
|
||||
|
||||
-- test --
|
||||
sekret
|
||||
6
pkg/cli/secret/testdata/create_secret_no_hash_dry_run.txt
vendored
Normal file
6
pkg/cli/secret/testdata/create_secret_no_hash_dry_run.txt
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Want no hash appended
|
||||
holos create secret test --namespace holos-system --from-file $WORK/test --append-hash=false --dry-run
|
||||
stdout 'name: test$'
|
||||
|
||||
-- test --
|
||||
sekret
|
||||
11
pkg/cli/secret/testdata/create_secret_warns.txt
vendored
Normal file
11
pkg/cli/secret/testdata/create_secret_warns.txt
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Create the secret.
|
||||
holos create secret k3-talos --cluster-name=k2 --from-file $WORK/secrets.yaml
|
||||
stderr 'created: k3-talos-..........'
|
||||
|
||||
# Want a warning about the cluster name prefix.
|
||||
stderr 'missing cluster name prefix'
|
||||
stderr 'have=k3-talos'
|
||||
stderr 'want=k2-'
|
||||
|
||||
-- secrets.yaml --
|
||||
content: hello
|
||||
10
pkg/cli/secret/testdata/get_secret_extract_all.txt
vendored
Normal file
10
pkg/cli/secret/testdata/get_secret_extract_all.txt
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Get and extract the secret
|
||||
holos get secrets k2-talos --extract-all --extract-to=$WORK
|
||||
! stdout .
|
||||
stderr 'wrote: .*/secrets\.yaml'
|
||||
|
||||
# Check the secret keys
|
||||
cmp want.secrets.yaml secrets.yaml
|
||||
|
||||
-- want.secrets.yaml --
|
||||
content: secret
|
||||
3
pkg/cli/secret/testdata/get_secret_print.txt
vendored
Normal file
3
pkg/cli/secret/testdata/get_secret_print.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
holos get secrets k2-talos --print-key=secrets.yaml
|
||||
stdout -count=1 '^content: secret$'
|
||||
! stderr .
|
||||
3
pkg/cli/secret/testdata/get_secrets.txt
vendored
Normal file
3
pkg/cli/secret/testdata/get_secrets.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
holos get secrets
|
||||
stdout '^k2-talos$'
|
||||
! stderr .
|
||||
@@ -21,9 +21,11 @@ const DefaultProvisionerNamespace = "secrets"
|
||||
type Option func(o *options)
|
||||
|
||||
type options struct {
|
||||
stdin io.Reader
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
stdin io.Reader
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
provisionerClientset kubernetes.Interface
|
||||
clientset kubernetes.Interface
|
||||
}
|
||||
|
||||
// Stdin redirects standard input to r, useful for test capture.
|
||||
@@ -41,6 +43,16 @@ func Stderr(w io.Writer) Option {
|
||||
return func(o *options) { o.stderr = w }
|
||||
}
|
||||
|
||||
// ProvisionerClientset sets the kubernetes Clientset, useful for test fake.
|
||||
func ProvisionerClientset(clientset kubernetes.Interface) Option {
|
||||
return func(o *options) { o.provisionerClientset = clientset }
|
||||
}
|
||||
|
||||
// ClusterClientset sets the kubernetes Clientset, useful for test fake.
|
||||
func ClusterClientset(clientset *kubernetes.Clientset) Option {
|
||||
return func(o *options) { o.clientset = clientset }
|
||||
}
|
||||
|
||||
// New returns a new top level cli Config.
|
||||
func New(opts ...Option) *Config {
|
||||
cfgOptions := &options{
|
||||
@@ -56,14 +68,15 @@ func New(opts ...Option) *Config {
|
||||
kvFlagSet := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
txFlagSet := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
cfg := &Config{
|
||||
logConfig: logger.NewConfig(),
|
||||
writeTo: getenv("HOLOS_WRITE_TO", "deploy"),
|
||||
clusterName: getenv("HOLOS_CLUSTER_NAME", ""),
|
||||
writeFlagSet: writeFlagSet,
|
||||
clusterFlagSet: clusterFlagSet,
|
||||
options: cfgOptions,
|
||||
kvFlagSet: kvFlagSet,
|
||||
txtarFlagSet: txFlagSet,
|
||||
logConfig: logger.NewConfig(),
|
||||
writeTo: getenv("HOLOS_WRITE_TO", "deploy"),
|
||||
clusterName: getenv("HOLOS_CLUSTER_NAME", ""),
|
||||
writeFlagSet: writeFlagSet,
|
||||
clusterFlagSet: clusterFlagSet,
|
||||
options: cfgOptions,
|
||||
kvFlagSet: kvFlagSet,
|
||||
txtarFlagSet: txFlagSet,
|
||||
provisionerClientset: cfgOptions.provisionerClientset,
|
||||
}
|
||||
writeFlagSet.StringVar(&cfg.writeTo, "write-to", cfg.writeTo, "write to directory")
|
||||
clusterFlagSet.StringVar(&cfg.clusterName, "cluster-name", cfg.clusterName, "cluster name")
|
||||
@@ -96,7 +109,7 @@ type Config struct {
|
||||
kvFlagSet *flag.FlagSet
|
||||
txtarIndex *int
|
||||
txtarFlagSet *flag.FlagSet
|
||||
provisionerClientset *kubernetes.Clientset
|
||||
provisionerClientset kubernetes.Interface
|
||||
}
|
||||
|
||||
// LogFlagSet returns the logging *flag.FlagSet for use by the command handler.
|
||||
@@ -229,7 +242,7 @@ func (c *Config) TxtarIndex() int {
|
||||
}
|
||||
|
||||
// ProvisionerClientset returns a kubernetes client set for the provisioner cluster.
|
||||
func (c *Config) ProvisionerClientset() (*kubernetes.Clientset, error) {
|
||||
func (c *Config) ProvisionerClientset() (kubernetes.Interface, error) {
|
||||
if c.provisionerClientset == nil {
|
||||
kcfg, err := clientcmd.BuildConfigFromFlags("", c.KVKubeconfig())
|
||||
if err != nil {
|
||||
|
||||
@@ -1 +1 @@
|
||||
45
|
||||
46
|
||||
|
||||
@@ -1 +1 @@
|
||||
1
|
||||
2
|
||||
|
||||
Reference in New Issue
Block a user