mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 08:44:58 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c20872c92f | ||
|
|
ecce1f797e |
23
pkg/cli/get/get.go
Normal file
23
pkg/cli/get/get.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package get
|
||||
|
||||
import (
|
||||
"github.com/holos-run/holos/pkg/cli/command"
|
||||
"github.com/holos-run/holos/pkg/cli/secret"
|
||||
"github.com/holos-run/holos/pkg/holos"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// New returns the get command for the cli.
|
||||
func New(hc *holos.Config) *cobra.Command {
|
||||
cmd := command.New("get")
|
||||
cmd.Short = "get resources"
|
||||
cmd.Flags().SortFlags = false
|
||||
cmd.RunE = func(c *cobra.Command, args []string) error {
|
||||
return c.Usage()
|
||||
}
|
||||
// flags
|
||||
cmd.PersistentFlags().SortFlags = false
|
||||
// commands
|
||||
cmd.AddCommand(secret.NewGetCmd(hc))
|
||||
return cmd
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package cli
|
||||
import (
|
||||
"github.com/holos-run/holos/pkg/cli/build"
|
||||
"github.com/holos-run/holos/pkg/cli/create"
|
||||
"github.com/holos-run/holos/pkg/cli/get"
|
||||
"github.com/holos-run/holos/pkg/cli/kv"
|
||||
"github.com/holos-run/holos/pkg/cli/render"
|
||||
"github.com/holos-run/holos/pkg/cli/txtar"
|
||||
@@ -48,6 +49,7 @@ func New(cfg *holos.Config) *cobra.Command {
|
||||
// subcommands
|
||||
rootCmd.AddCommand(build.New(cfg))
|
||||
rootCmd.AddCommand(render.New(cfg))
|
||||
rootCmd.AddCommand(get.New(cfg))
|
||||
rootCmd.AddCommand(create.New(cfg))
|
||||
|
||||
// Maybe not needed?
|
||||
|
||||
124
pkg/cli/secret/create.go
Normal file
124
pkg/cli/secret/create.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package secret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/holos-run/holos/pkg/cli/command"
|
||||
"github.com/holos-run/holos/pkg/holos"
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"github.com/spf13/cobra"
|
||||
"io/fs"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubectl/pkg/util/hash"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sigs.k8s.io/yaml"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func NewCreateCmd(hc *holos.Config) *cobra.Command {
|
||||
cmd := command.New("secret NAME [--from-file=source]")
|
||||
cmd.Aliases = []string{"secrets", "sec"}
|
||||
cmd.Args = cobra.ExactArgs(1)
|
||||
cmd.Short = "Create a holos secret from files or directories"
|
||||
|
||||
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")
|
||||
|
||||
cmd.Flags().SortFlags = false
|
||||
cmd.Flags().AddGoFlagSet(flagSet)
|
||||
cmd.RunE = makeCreateRunFunc(hc, cfg)
|
||||
return cmd
|
||||
|
||||
}
|
||||
|
||||
func makeCreateRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
log := logger.FromContext(ctx)
|
||||
secretName := args[0]
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Labels: map[string]string{NameLabel: secretName},
|
||||
},
|
||||
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)
|
||||
}
|
||||
|
||||
for _, file := range cfg.files {
|
||||
if err := filepath.WalkDir(file, makeWalkFunc(secret.Data, file)); err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
if owner := os.Getenv("USER"); owner != "" {
|
||||
secret.Labels[OwnerLabel] = owner
|
||||
}
|
||||
if *cfg.cluster != "" {
|
||||
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.dryRun {
|
||||
out, err := yaml.Marshal(secret)
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
hc.Write(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
cs, err := hc.ProvisionerClientset()
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
secret, err = cs.CoreV1().
|
||||
Secrets(*cfg.namespace).
|
||||
Create(ctx, secret, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
|
||||
log.InfoContext(ctx, "created: "+secret.Name, "secret", secret.Name, "name", secretName, "namespace", secret.Namespace)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func makeWalkFunc(data secretData, root string) fs.WalkDirFunc {
|
||||
return func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Depth is the count of path separators from the root
|
||||
depth := strings.Count(path[len(root):], string(filepath.Separator))
|
||||
|
||||
if depth > 1 {
|
||||
if d.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
|
||||
if !d.IsDir() {
|
||||
key := filepath.Base(path)
|
||||
if data[key], err = os.ReadFile(path); err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
144
pkg/cli/secret/get.go
Normal file
144
pkg/cli/secret/get.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/holos-run/holos/pkg/cli/command"
|
||||
"github.com/holos-run/holos/pkg/holos"
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"github.com/spf13/cobra"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const printFlagName = "print-key"
|
||||
|
||||
func NewGetCmd(hc *holos.Config) *cobra.Command {
|
||||
cmd := command.New("secrets NAME [--to-file=destination]")
|
||||
cmd.Aliases = []string{"secret"}
|
||||
cmd.Args = cobra.MinimumNArgs(0)
|
||||
cmd.Short = "Get holos secrets from the provisioner cluster"
|
||||
|
||||
cfg, flagSet := newConfig()
|
||||
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")
|
||||
|
||||
cmd.Flags().SortFlags = false
|
||||
cmd.Flags().AddGoFlagSet(flagSet)
|
||||
cmd.RunE = makeGetRunFunc(hc, cfg)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func makeGetRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
namespace := *cfg.namespace
|
||||
ctx := cmd.Context()
|
||||
log := logger.FromContext(ctx).With("namespace", namespace)
|
||||
|
||||
cs, err := hc.ProvisionerClientset()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// List secrets if no arguments.
|
||||
if len(args) == 0 {
|
||||
return listSecrets(cmd.Context(), hc, namespace)
|
||||
}
|
||||
|
||||
// Get each secret.
|
||||
for _, secretName := range args {
|
||||
log := log.With(NameLabel, secretName)
|
||||
opts := metav1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("%s=%s", NameLabel, secretName),
|
||||
}
|
||||
list, err := cs.CoreV1().Secrets(namespace).List(ctx, opts)
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "results", "len", len(list.Items))
|
||||
if len(list.Items) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Sort oldest first.
|
||||
sort.Slice(list.Items, func(i, j int) bool {
|
||||
return list.Items[i].CreationTimestamp.Before(&list.Items[j].CreationTimestamp)
|
||||
})
|
||||
|
||||
// Get the most recent.
|
||||
secret := list.Items[len(list.Items)-1]
|
||||
log = log.With("secret", secret.Name)
|
||||
|
||||
// Extract the data keys (file names).
|
||||
keys := make([]string, 0, len(secret.Data))
|
||||
for k, v := range secret.Data {
|
||||
keys = append(keys, k)
|
||||
log.DebugContext(ctx, "data", "name", secret.Name, "key", k, "len", len(v))
|
||||
}
|
||||
|
||||
// Extract specified files or all files.
|
||||
toExtract := cfg.files
|
||||
if *cfg.extract {
|
||||
toExtract = keys
|
||||
}
|
||||
|
||||
printFile := *cfg.printFile
|
||||
if len(toExtract) == 0 {
|
||||
if printFile == "" {
|
||||
printFile = secretName
|
||||
}
|
||||
}
|
||||
|
||||
if printFile != "" {
|
||||
if data, found := secret.Data[printFile]; found {
|
||||
hc.Write(data)
|
||||
} else {
|
||||
err := fmt.Errorf("cannot print: want %s have %v: did you mean --extract-all or --%s=name", printFile, keys, printFlagName)
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over --to-file values.
|
||||
for _, name := range toExtract {
|
||||
data, found := secret.Data[name]
|
||||
if !found {
|
||||
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))
|
||||
}
|
||||
log.InfoContext(ctx, "wrote: "+name, "bytes", len(data))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// listSecrets lists holos secrets in the provisioner cluster
|
||||
func listSecrets(ctx context.Context, hc *holos.Config, namespace string) error {
|
||||
cs, err := hc.ProvisionerClientset()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
selector := metav1.ListOptions{LabelSelector: NameLabel}
|
||||
secrets, err := cs.CoreV1().Secrets(namespace).List(ctx, selector)
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
secretNames := make(map[string]bool)
|
||||
for _, secret := range secrets.Items {
|
||||
if labelValue, ok := secret.Labels[NameLabel]; ok {
|
||||
secretNames[labelValue] = true
|
||||
}
|
||||
}
|
||||
for secretName := range secretNames {
|
||||
hc.Println(secretName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2,132 +2,28 @@ package secret
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/holos-run/holos/pkg/cli/command"
|
||||
"github.com/holos-run/holos/pkg/holos"
|
||||
"github.com/holos-run/holos/pkg/logger"
|
||||
"github.com/holos-run/holos/pkg/wrapper"
|
||||
"github.com/spf13/cobra"
|
||||
"io/fs"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubectl/pkg/util/hash"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sigs.k8s.io/yaml"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const NameLabel = "holos.run/secret.name"
|
||||
const OwnerLabel = "holos.run/secret.owner"
|
||||
const OwnerLabel = "holos.run/owner.name"
|
||||
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
|
||||
}
|
||||
|
||||
func NewCreateCmd(hc *holos.Config) *cobra.Command {
|
||||
cmd := command.New("secret NAME [--from-file=source]")
|
||||
cmd.Args = cobra.ExactArgs(1)
|
||||
cmd.Short = "Create a holos secret from files or directories"
|
||||
cmd.Flags().SortFlags = false
|
||||
|
||||
func newConfig() (*config, *flag.FlagSet) {
|
||||
cfg := &config{}
|
||||
flagSet := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
flagSet.Var(&cfg.files, "from-file", "store files as keys in the secret")
|
||||
cfg.namespace = flagSet.String("namespace", holos.DefaultProvisionerNamespace, "namespace in the provisioner cluster where the secret is created")
|
||||
cfg.cluster = flagSet.String("cluster-name", "", "cluster name")
|
||||
cfg.dryRun = flagSet.Bool("dry-run", false, "dry run")
|
||||
cmd.Flags().AddGoFlagSet(flagSet)
|
||||
cmd.RunE = makeCreateRunFunc(hc, cfg)
|
||||
return cmd
|
||||
|
||||
}
|
||||
|
||||
func makeCreateRunFunc(hc *holos.Config, cfg *config) command.RunFunc {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
secretName := args[0]
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Labels: map[string]string{NameLabel: secretName},
|
||||
},
|
||||
Data: make(secretData),
|
||||
}
|
||||
|
||||
for _, file := range cfg.files {
|
||||
if err := filepath.WalkDir(file, makeWalkFunc(secret.Data, file)); err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
if owner := os.Getenv("USER"); owner != "" {
|
||||
secret.Labels[OwnerLabel] = owner
|
||||
}
|
||||
if *cfg.cluster != "" {
|
||||
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.dryRun {
|
||||
out, err := yaml.Marshal(secret)
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
hc.Write(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
cs, err := hc.ProvisionerClientset()
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
ctx := cmd.Context()
|
||||
secret, err = cs.CoreV1().
|
||||
Secrets(*cfg.namespace).
|
||||
Create(ctx, secret, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
|
||||
log := logger.FromContext(ctx)
|
||||
log.InfoContext(ctx, "created: "+secret.Name, "secret", secret.Name, "name", secretName, "namespace", secret.Namespace)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func makeWalkFunc(data secretData, root string) fs.WalkDirFunc {
|
||||
return func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Depth is the count of path separators from the root
|
||||
depth := strings.Count(path[len(root):], string(filepath.Separator))
|
||||
|
||||
if depth > 1 {
|
||||
if d.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
|
||||
if !d.IsDir() {
|
||||
key := filepath.Base(path)
|
||||
if data[key], err = os.ReadFile(path); err != nil {
|
||||
return wrapper.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
cfg.namespace = flagSet.String("namespace", holos.DefaultProvisionerNamespace, "namespace in the provisioner cluster")
|
||||
cfg.cluster = flagSet.String("cluster-name", "", "cluster name selector")
|
||||
return cfg, flagSet
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
0
|
||||
1
|
||||
|
||||
Reference in New Issue
Block a user