From 76d027c2346c6d48e12299dd30248b18c579ceca Mon Sep 17 00:00:00 2001 From: Tim <165851289+timflyio@users.noreply.github.com> Date: Sat, 14 Sep 2024 10:26:51 -1000 Subject: [PATCH] Add flyctl commands for managing secrets that are kms keys (#3901) Adds `flyctl secrets keys` with `ls`, `gen` and `rm` commands for managing KMS keys. Keys are generated randomly with a semantic type of `encrypting` or `signing`. Keys are versioned, and versioning is usually automatic, but can be specified explicitly. Adding new versions to a key label requires that the new key semantic type match the existing semantic types. Deletion can be done with an explicit key version, or across all versions for a label, and will prompt for confirmation unless overridden with a force flag. Key management is done through a flaps API. --- internal/command/deploy/mock_client_test.go | 16 ++ internal/command/secrets/key_delete.go | 112 +++++++++++++ internal/command/secrets/key_set.go | 147 ++++++++++++++++ internal/command/secrets/keys.go | 75 +++++++++ internal/command/secrets/keys_common.go | 177 ++++++++++++++++++++ internal/command/secrets/keys_list.go | 120 +++++++++++++ internal/command/secrets/secrets.go | 1 + internal/flapsutil/flaps_client.go | 4 + internal/inmem/flaps_client.go | 16 ++ internal/mock/flaps_client.go | 20 +++ 10 files changed, 688 insertions(+) create mode 100644 internal/command/secrets/key_delete.go create mode 100644 internal/command/secrets/key_set.go create mode 100644 internal/command/secrets/keys.go create mode 100644 internal/command/secrets/keys_common.go create mode 100644 internal/command/secrets/keys_list.go diff --git a/internal/command/deploy/mock_client_test.go b/internal/command/deploy/mock_client_test.go index 18836f3b7c..6e78fe07f9 100644 --- a/internal/command/deploy/mock_client_test.go +++ b/internal/command/deploy/mock_client_test.go @@ -35,6 +35,10 @@ func (m *mockFlapsClient) CreateApp(ctx context.Context, name string, org string return fmt.Errorf("failed to create app %s", name) } +func (m *mockFlapsClient) CreateSecret(ctx context.Context, sLabel, sType string, in fly.CreateSecretRequest) (err error) { + return fmt.Errorf("failed to create secret %s", sLabel) +} + func (m *mockFlapsClient) CreateVolume(ctx context.Context, req fly.CreateVolumeRequest) (*fly.Volume, error) { return nil, fmt.Errorf("failed to create volume %s", req.Name) } @@ -47,6 +51,10 @@ func (m *mockFlapsClient) DeleteMetadata(ctx context.Context, machineID, key str return fmt.Errorf("failed to delete metadata %s", key) } +func (m *mockFlapsClient) DeleteSecret(ctx context.Context, label string) (err error) { + return fmt.Errorf("failed to delete secret %s", label) +} + func (m *mockFlapsClient) DeleteVolume(ctx context.Context, volumeId string) (*fly.Volume, error) { return nil, fmt.Errorf("failed to delete volume %s", volumeId) } @@ -67,6 +75,10 @@ func (m *mockFlapsClient) FindLease(ctx context.Context, machineID string) (*fly return nil, fmt.Errorf("failed to find lease for %s", machineID) } +func (m *mockFlapsClient) GenerateSecret(ctx context.Context, sLabel, sType string) (err error) { + return fmt.Errorf("failed to generate secret %s", sLabel) +} + func (m *mockFlapsClient) Get(ctx context.Context, machineID string) (*fly.Machine, error) { return nil, fmt.Errorf("failed to get %s", machineID) } @@ -122,6 +134,10 @@ func (m *mockFlapsClient) ListFlyAppsMachines(ctx context.Context) ([]*fly.Machi return nil, nil, fmt.Errorf("failed to list fly apps machines") } +func (m *mockFlapsClient) ListSecrets(ctx context.Context) (out []fly.ListSecret, err error) { + return nil, fmt.Errorf("failed to list secrets") +} + func (m *mockFlapsClient) NewRequest(ctx context.Context, method, path string, in interface{}, headers map[string][]string) (*http.Request, error) { return nil, fmt.Errorf("failed to create request") } diff --git a/internal/command/secrets/key_delete.go b/internal/command/secrets/key_delete.go new file mode 100644 index 0000000000..0d37323fb3 --- /dev/null +++ b/internal/command/secrets/key_delete.go @@ -0,0 +1,112 @@ +package secrets + +import ( + "context" + "errors" + "fmt" + + "github.com/spf13/cobra" + "github.com/superfly/fly-go/flaps" + "github.com/superfly/flyctl/internal/command" + "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/prompt" + "github.com/superfly/flyctl/iostreams" +) + +func newKeyDelete() (cmd *cobra.Command) { + const ( + long = `Delete the application key secret by label.` + short = `Delete the application key secret` + usage = "delete [flags] label" + ) + + cmd = command.New(usage, short, long, runKeyDelete, command.RequireSession, command.RequireAppName) + + cmd.Aliases = []string{"rm"} + + flag.Add(cmd, + flag.App(), + flag.AppConfig(), + flag.Bool{ + Name: "force", + Shorthand: "f", + Description: "Force deletion without prompting", + }, + flag.Bool{ + Name: "noversion", + Shorthand: "n", + Default: false, + Description: "do not automatically match all versions of a key when version is unspecified. all matches must be explicit", + }, + ) + + cmd.Args = cobra.ExactArgs(1) + + return cmd +} + +func runKeyDelete(ctx context.Context) (err error) { + label := flag.Args(ctx)[0] + ver, prefix, err := SplitLabelKeyver(label) + if err != nil { + return err + } + + flapsClient, err := getFlapsClient(ctx) + if err != nil { + return err + } + + secrets, err := flapsClient.ListSecrets(ctx) + if err != nil { + return err + } + + // Delete all matching secrets, prompting if necessary. + var rerr error + out := iostreams.FromContext(ctx).Out + for _, secret := range secrets { + ver2, prefix2, err := SplitLabelKeyver(secret.Label) + if err != nil { + continue + } + if prefix != prefix2 { + continue + } + + if ver != ver2 { + // Subtle: If the `noversion` flag was specified, then we must have + // an exact match. Otherwise if version is unspecified, we + // match all secrets with the same version regardless of version. + if flag.GetBool(ctx, "noversion") { + continue + } + if ver != KeyverUnspec { + continue + } + } + + if !flag.GetBool(ctx, "force") { + confirm, err := prompt.Confirm(ctx, fmt.Sprintf("delete secrets key %s?", secret.Label)) + if err != nil { + rerr = errors.Join(rerr, err) + continue + } + if !confirm { + continue + } + } + + err = flapsClient.DeleteSecret(ctx, secret.Label) + if err != nil { + var ferr *flaps.FlapsError + if errors.As(err, &ferr) && ferr.ResponseStatusCode == 404 { + err = fmt.Errorf("not found") + } + rerr = errors.Join(rerr, fmt.Errorf("deleting %v: %w", secret.Label, err)) + } else { + fmt.Fprintf(out, "Deleted %v\n", secret.Label) + } + } + return rerr +} diff --git a/internal/command/secrets/key_set.go b/internal/command/secrets/key_set.go new file mode 100644 index 0000000000..0f5aaf0fe9 --- /dev/null +++ b/internal/command/secrets/key_set.go @@ -0,0 +1,147 @@ +package secrets + +import ( + "context" + "encoding/base64" + "fmt" + + "github.com/spf13/cobra" + fly "github.com/superfly/fly-go" + "github.com/superfly/flyctl/internal/command" + "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/iostreams" +) + +func newKeyGenerate() (cmd *cobra.Command) { + const ( + long = `Generate a random application key secret. If the label is not fully qualified +with a version, and a secret with the same label already exists, the label will be +updated to include the next version number.` + short = `Generate the application key secret` + usage = "generate [flags] type label" + ) + + cmd = command.New(usage, short, long, runKeySetOrGenerate, command.RequireSession, command.RequireAppName) + + flag.Add(cmd, + flag.App(), + flag.AppConfig(), + flag.Bool{ + Name: "force", + Shorthand: "f", + Description: "Force overwriting existing values", + }, + flag.Bool{ + Name: "noversion", + Shorthand: "n", + Default: false, + Description: "do not automatically version the key label", + }, + flag.Bool{ + Name: "quiet", + Shorthand: "q", + Description: "Don't print key label", + }, + ) + + cmd.Aliases = []string{"gen"} + cmd.Args = cobra.ExactArgs(2) + + return cmd +} + +// runKeySetOrGenerate handles both `keys set typ label value` and +// `keys generate typ label`. The sole difference is whether a `value` +// arg is present or not. +func runKeySetOrGenerate(ctx context.Context) (err error) { + out := iostreams.FromContext(ctx).Out + args := flag.Args(ctx) + semType := SemanticType(args[0]) + label := args[1] + val := []byte{} + + ver, prefix, err := SplitLabelKeyver(label) + if err != nil { + return err + } + + typ, err := SemanticTypeToSecretType(semType) + if err != nil { + return err + } + + gen := true + if len(args) > 2 { + gen = false + val, err = base64.StdEncoding.DecodeString(args[2]) + if err != nil { + return fmt.Errorf("bad value encoding: %w", err) + } + } + + flapsClient, err := getFlapsClient(ctx) + if err != nil { + return err + } + + secrets, err := flapsClient.ListSecrets(ctx) + if err != nil { + return err + } + + // Verify consistency with existing keys with the same prefix + // while finding the highest version with the same prefix. + bestVer := KeyverUnspec + for _, secret := range secrets { + if label == secret.Label { + if !flag.GetBool(ctx, "force") { + return fmt.Errorf("refusing to overwrite existing key") + } + } + + ver2, prefix2, err := SplitLabelKeyver(secret.Label) + if err != nil { + continue + } + if prefix != prefix2 { + continue + } + + // The semantic type must be the same as any existing keys with the same label prefix. + semType2, _ := SecretTypeToSemanticType(secret.Type) + if semType2 != semType { + typs := secretTypeToString(secret.Type) + return fmt.Errorf("key %v (%v) has conflicting type %v (%v)", prefix, secret.Label, semType2, typs) + } + + if CompareKeyver(ver2, bestVer) > 0 { + bestVer = ver2 + } + } + + // If the label does not contain an explicit version, + // we will automatically apply a version to the label + // unless the user said not to. + if ver == KeyverUnspec && !flag.GetBool(ctx, "noversion") { + ver, err := bestVer.Incr() + if err != nil { + return err + } + label = JoinLabelVersion(ver, prefix) + } + + if !flag.GetBool(ctx, "quiet") { + typs := secretTypeToString(typ) + fmt.Fprintf(out, "Setting %s %s (%s)\n", label, semType, typs) + } + + if gen { + err = flapsClient.GenerateSecret(ctx, label, typ) + } else { + err = flapsClient.CreateSecret(ctx, label, typ, fly.CreateSecretRequest{Value: val}) + } + if err != nil { + return err + } + return nil +} diff --git a/internal/command/secrets/keys.go b/internal/command/secrets/keys.go new file mode 100644 index 0000000000..af254987e1 --- /dev/null +++ b/internal/command/secrets/keys.go @@ -0,0 +1,75 @@ +package secrets + +import ( + "context" + "fmt" + "strings" + + "github.com/spf13/cobra" + fly "github.com/superfly/fly-go" + "github.com/superfly/fly-go/flaps" + "github.com/superfly/flyctl/internal/appconfig" + "github.com/superfly/flyctl/internal/command" + "github.com/superfly/flyctl/internal/flapsutil" + "github.com/superfly/flyctl/internal/flyutil" +) + +type SecretType = string + +const ( + SECRET_TYPE_KMS_HS256 = fly.SECRET_TYPE_KMS_HS256 + SECRET_TYPE_KMS_HS384 = fly.SECRET_TYPE_KMS_HS384 + SECRET_TYPE_KMS_HS512 = fly.SECRET_TYPE_KMS_HS512 + SECRET_TYPE_KMS_XAES256GCM = fly.SECRET_TYPE_KMS_XAES256GCM + SECRET_TYPE_KMS_NACL_AUTH = fly.SECRET_TYPE_KMS_NACL_AUTH + SECRET_TYPE_KMS_NACL_BOX = fly.SECRET_TYPE_KMS_NACL_BOX + SECRET_TYPE_KMS_NACL_SECRETBOX = fly.SECRET_TYPE_KMS_NACL_SECRETBOX + SECRET_TYPE_KMS_NACL_SIGN = fly.SECRET_TYPE_KMS_NACL_SIGN +) + +func newKeys() *cobra.Command { + const ( + long = `Keys are available to applications through the /.fly/kms filesystem. Names are case + sensitive and stored as-is, so ensure names are appropriate as filesystem names. + Names optionally include version information with a "vN" suffix. + ` + + short = "Manage application key secrets with the gen, list, and delete commands." + ) + + keys := command.New("keys", short, long, nil) + + keys.AddCommand( + newKeysList(), + newKeyGenerate(), + newKeyDelete(), + ) + + keys.Hidden = true // TODO: unhide when we're ready to go public. + + return keys +} + +// secretTypeToString converts from a standard sType to flyctl's abbreviated string form. +func secretTypeToString(sType string) string { + return strings.TrimPrefix(strings.ToLower(sType), "secret_type_kms_") +} + +// getFlapsClient builds and returns a flaps client for the App from the context. +func getFlapsClient(ctx context.Context) (*flaps.Client, error) { + client := flyutil.ClientFromContext(ctx) + appName := appconfig.NameFromContext(ctx) + app, err := client.GetAppCompact(ctx, appName) + if err != nil { + return nil, fmt.Errorf("get app: %w", err) + } + + flapsClient, err := flapsutil.NewClientWithOptions(ctx, flaps.NewClientOpts{ + AppCompact: app, + AppName: app.Name, + }) + if err != nil { + return nil, fmt.Errorf("could not create flaps client: %w", err) + } + return flapsClient, nil +} diff --git a/internal/command/secrets/keys_common.go b/internal/command/secrets/keys_common.go new file mode 100644 index 0000000000..d7c1c7baf9 --- /dev/null +++ b/internal/command/secrets/keys_common.go @@ -0,0 +1,177 @@ +package secrets + +// This is common implementation between internal packages and flyctl. +// Sync between nmf:kms/kmsfs/keys_common.go and flyctl:internal/command/secrets/keys_common.go + +import ( + "fmt" + "regexp" + "strconv" +) + +type SemanticType string + +const ( + SemTypeSigning = SemanticType("signing") + SemTypeEncrypting = SemanticType("encrypting") +) + +type KeyTypeInfo struct { + secretType SecretType + semanticType SemanticType +} + +// supportedKeyTypes lists all supported key types with their semantic key type. +// In this list, the most preferred types are listed first. +var supportedKeyTypes = []KeyTypeInfo{ + // Preferred key types: + {SECRET_TYPE_KMS_NACL_AUTH, SemTypeSigning}, + {SECRET_TYPE_KMS_NACL_SECRETBOX, SemTypeEncrypting}, + + // Also supported key types: + {SECRET_TYPE_KMS_HS256, SemTypeSigning}, + {SECRET_TYPE_KMS_HS384, SemTypeSigning}, + {SECRET_TYPE_KMS_HS512, SemTypeSigning}, + {SECRET_TYPE_KMS_XAES256GCM, SemTypeEncrypting}, + + // Unsupported: + // SECRET_TYPE_KMS_NACL_BOX, SemTypePublicEncrypting + // SECRET_TYPE_KMS_NACL_SIGN, SmeTypePublicSigning +} + +// SupportedSecretTypes is a list of the SecretTypes for supported key types. +var SupportedSecretTypes = GetSupportedSecretTypes() + +// SupportedSecretTypes is a list of the SemanticTypes for supported key types. +var SupportedSemanticTypes = GetSupportedSemanticTypes() + +func GetSupportedSecretTypes() []SecretType { + var r []SecretType + seen := map[SecretType]bool{} + for _, info := range supportedKeyTypes { + st := info.secretType + if !seen[st] { + seen[st] = true + r = append(r, st) + } + } + return r +} + +func GetSupportedSemanticTypes() []SemanticType { + var r []SemanticType + seen := map[SemanticType]bool{} + for _, info := range supportedKeyTypes { + st := info.semanticType + if !seen[st] { + seen[st] = true + r = append(r, st) + } + } + return r +} + +func SecretTypeToSemanticType(st SecretType) (SemanticType, error) { + for _, info := range supportedKeyTypes { + if info.secretType == st { + return info.semanticType, nil + } + } + var r SemanticType + return r, fmt.Errorf("unsupported secret type %s", st) +} + +func SemanticTypeToSecretType(st SemanticType) (SecretType, error) { + for _, info := range supportedKeyTypes { + if info.semanticType == st { + return info.secretType, nil + } + } + + var r SecretType + return r, fmt.Errorf("unsupported semantic type %s. use one of %v", st, SupportedSemanticTypes) +} + +// Keyver is a key version. +type Keyver int64 + +const ( + KeyverUnspec Keyver = -1 + KeyverZero Keyver = 0 + KeyverMax Keyver = 0x7fff_ffff_ffff_ffff // 9223372036854775807, 19 digits. +) + +func (v Keyver) String() string { + if v == KeyverUnspec { + return "unspec" + } + return fmt.Sprintf("%d", int64(v)) +} + +func (v Keyver) Incr() (Keyver, error) { + if v >= KeyverMax { + return KeyverUnspec, fmt.Errorf("cannot increment version beyond maximum") + } + return v + 1, nil +} + +func CompareKeyver(a, b Keyver) int { + d := int64(a) - int64(b) + switch { + case d < 0: + return -1 + case d == 0: + return 0 + case d > 0: + return 1 + default: + return 0 + } +} + +// labelPat is a regexp that determines which labels are valid. +// Importantly labels should not have Nil, slashes (we use them in kmsfs paths), colons +// (we use colons as a separator in signatures and ciphertexts that include labels as tags), +// or commas (we use commas to separate multiple arguments, which could include labels). +var labelPat = regexp.MustCompile("^[a-zA-Z0-9_-]+$") + +// validKeyLabel determines if a key label is valid or not. +func ValidKeyLabel(label string) error { + m := labelPat.FindStringSubmatch(label) + if m == nil { + return fmt.Errorf("invalid label") + } + return nil +} + +var labelVersionPat = regexp.MustCompile("^(.*)v([0-9]{1,19})$") + +// splitLabelKeyver splits a label into an integer version and the remaining label. +// It returns a version of KeyverUnspec if no label is present or if it would be out of range. +func SplitLabelKeyver(label string) (Keyver, string, error) { + if err := ValidKeyLabel(label); err != nil { + return KeyverUnspec, "", err + } + + m := labelVersionPat.FindStringSubmatch(label) + if m == nil { + return KeyverUnspec, label, nil + } + + l, nstr := m[1], m[2] + n, _ := strconv.ParseUint(nstr, 10, 64) + ver := Keyver(n) + if !(KeyverZero <= ver && ver <= KeyverMax) { + return KeyverUnspec, label, nil + } + + return ver, l, nil +} + +// JoinLabelVersion adds a keyversion to a key label. +func JoinLabelVersion(ver Keyver, prefix string) string { + if ver == KeyverUnspec { + return prefix + } + return fmt.Sprintf("%sv%d", prefix, int64(ver)) +} diff --git a/internal/command/secrets/keys_list.go b/internal/command/secrets/keys_list.go new file mode 100644 index 0000000000..b253a68f0d --- /dev/null +++ b/internal/command/secrets/keys_list.go @@ -0,0 +1,120 @@ +package secrets + +import ( + "context" + "fmt" + "slices" + "strings" + + "github.com/spf13/cobra" + fly "github.com/superfly/fly-go" + "github.com/superfly/flyctl/internal/command" + "github.com/superfly/flyctl/internal/config" + "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/render" + "github.com/superfly/flyctl/iostreams" +) + +func newKeysList() (cmd *cobra.Command) { + const ( + long = `List the keys secrets available to the application. It shows each secret's +name and version.` + short = `List application keys secrets names` + usage = "list [flags]" + ) + + cmd = command.New(usage, short, long, runKeysList, command.RequireSession, command.RequireAppName) + + cmd.Aliases = []string{"ls"} + + flag.Add(cmd, + flag.App(), + flag.AppConfig(), + flag.JSONOutput(), + ) + + return cmd +} + +func compareSecrets(a, b fly.ListSecret) int { + aver, aprefix, err1 := SplitLabelKeyver(a.Label) + if err1 != nil { + return -1 + } + bver, bprefix, err2 := SplitLabelKeyver(b.Label) + if err2 != nil { + return 1 + } + + diff := strings.Compare(aprefix, bprefix) + if diff != 0 { + return diff + } + + diff = CompareKeyver(aver, bver) + return diff +} + +type jsonSecret struct { + Label string `json:"label"` + Name string `json:"name"` + Version string `json:"version"` + SemType string `json:"type"` + Type string `json:"secret_type"` +} + +func runKeysList(ctx context.Context) (err error) { + cfg := config.FromContext(ctx) + out := iostreams.FromContext(ctx).Out + flapsClient, err := getFlapsClient(ctx) + if err != nil { + return err + } + + secrets, err := flapsClient.ListSecrets(ctx) + if err != nil { + return err + } + + var rows [][]string + var jsecrets []jsonSecret + slices.SortFunc(secrets, compareSecrets) + for _, secret := range secrets { + semType, err := SecretTypeToSemanticType(secret.Type) + if err != nil { + continue + } + + ver, prefix, err := SplitLabelKeyver(secret.Label) + if err != nil { + continue + } + jsecret := jsonSecret{ + Label: secret.Label, + Name: prefix, + Version: ver.String(), + SemType: string(semType), + Type: secretTypeToString(secret.Type), + } + + jsecrets = append(jsecrets, jsecret) + rows = append(rows, []string{ + jsecret.Label, + jsecret.Name, + jsecret.Version, + fmt.Sprintf("%s (%s)", jsecret.SemType, jsecret.Type), + }) + } + + headers := []string{ + "Label", + "Name", + "Version", + "Type", + } + if cfg.JSONOutput { + return render.JSON(out, jsecrets) + } else { + return render.Table(out, "", rows, headers...) + } +} diff --git a/internal/command/secrets/secrets.go b/internal/command/secrets/secrets.go index 04d55608a5..92d0d9792a 100644 --- a/internal/command/secrets/secrets.go +++ b/internal/command/secrets/secrets.go @@ -44,6 +44,7 @@ func New() *cobra.Command { newUnset(), newImport(), newDeploy(), + newKeys(), ) return secrets diff --git a/internal/flapsutil/flaps_client.go b/internal/flapsutil/flaps_client.go index c411d2b555..f33faeb5e8 100644 --- a/internal/flapsutil/flaps_client.go +++ b/internal/flapsutil/flaps_client.go @@ -15,14 +15,17 @@ type FlapsClient interface { AcquireLease(ctx context.Context, machineID string, ttl *int) (*fly.MachineLease, error) Cordon(ctx context.Context, machineID string, nonce string) (err error) CreateApp(ctx context.Context, name string, org string) (err error) + CreateSecret(ctx context.Context, sLabel, sType string, in fly.CreateSecretRequest) (err error) CreateVolume(ctx context.Context, req fly.CreateVolumeRequest) (*fly.Volume, error) CreateVolumeSnapshot(ctx context.Context, volumeId string) error DeleteMetadata(ctx context.Context, machineID, key string) error + DeleteSecret(ctx context.Context, label string) (err error) DeleteVolume(ctx context.Context, volumeId string) (*fly.Volume, error) Destroy(ctx context.Context, input fly.RemoveMachineInput, nonce string) (err error) Exec(ctx context.Context, machineID string, in *fly.MachineExecRequest) (*fly.MachineExecResponse, error) ExtendVolume(ctx context.Context, volumeId string, size_gb int) (*fly.Volume, bool, error) FindLease(ctx context.Context, machineID string) (*fly.MachineLease, error) + GenerateSecret(ctx context.Context, sLabel, sType string) (err error) Get(ctx context.Context, machineID string) (*fly.Machine, error) GetAllVolumes(ctx context.Context) ([]fly.Volume, error) GetMany(ctx context.Context, machineIDs []string) ([]*fly.Machine, error) @@ -36,6 +39,7 @@ type FlapsClient interface { List(ctx context.Context, state string) ([]*fly.Machine, error) ListActive(ctx context.Context) ([]*fly.Machine, error) ListFlyAppsMachines(ctx context.Context) ([]*fly.Machine, *fly.Machine, error) + ListSecrets(ctx context.Context) (out []fly.ListSecret, err error) NewRequest(ctx context.Context, method, path string, in interface{}, headers map[string][]string) (*http.Request, error) RefreshLease(ctx context.Context, machineID string, ttl *int, nonce string) (*fly.MachineLease, error) ReleaseLease(ctx context.Context, machineID, nonce string) error diff --git a/internal/inmem/flaps_client.go b/internal/inmem/flaps_client.go index 837868ac00..8eb40a42dc 100644 --- a/internal/inmem/flaps_client.go +++ b/internal/inmem/flaps_client.go @@ -36,6 +36,10 @@ func (m *FlapsClient) CreateApp(ctx context.Context, name string, org string) (e panic("TODO") } +func (m *FlapsClient) CreateSecret(ctx context.Context, sLabel, sType string, in fly.CreateSecretRequest) (err error) { + panic("TODO") +} + func (m *FlapsClient) CreateVolume(ctx context.Context, req fly.CreateVolumeRequest) (*fly.Volume, error) { panic("TODO") } @@ -48,6 +52,10 @@ func (m *FlapsClient) DeleteMetadata(ctx context.Context, machineID, key string) panic("TODO") } +func (m *FlapsClient) DeleteSecret(ctx context.Context, label string) (err error) { + panic("TODO") +} + func (m *FlapsClient) DeleteVolume(ctx context.Context, volumeId string) (*fly.Volume, error) { panic("TODO") } @@ -68,6 +76,10 @@ func (m *FlapsClient) FindLease(ctx context.Context, machineID string) (*fly.Mac panic("TODO") } +func (m *FlapsClient) GenerateSecret(ctx context.Context, sLabel, sType string) (err error) { + panic("TODO") +} + func (m *FlapsClient) Get(ctx context.Context, machineID string) (*fly.Machine, error) { return m.server.GetMachine(ctx, m.appName, machineID) } @@ -140,6 +152,10 @@ func (m *FlapsClient) ListFlyAppsMachines(ctx context.Context) (machines []*fly. return machines, releaseCmdMachine, nil } +func (m *FlapsClient) ListSecrets(ctx context.Context) (out []fly.ListSecret, err error) { + panic("TODO") +} + func (m *FlapsClient) NewRequest(ctx context.Context, method, path string, in interface{}, headers map[string][]string) (*http.Request, error) { panic("TODO") } diff --git a/internal/mock/flaps_client.go b/internal/mock/flaps_client.go index dcb62bd975..e6dc8ad23a 100644 --- a/internal/mock/flaps_client.go +++ b/internal/mock/flaps_client.go @@ -15,14 +15,17 @@ type FlapsClient struct { AcquireLeaseFunc func(ctx context.Context, machineID string, ttl *int) (*fly.MachineLease, error) CordonFunc func(ctx context.Context, machineID string, nonce string) (err error) CreateAppFunc func(ctx context.Context, name string, org string) (err error) + CreateSecretFunc func(ctx context.Context, sLabel, sType string, in fly.CreateSecretRequest) (err error) CreateVolumeFunc func(ctx context.Context, req fly.CreateVolumeRequest) (*fly.Volume, error) CreateVolumeSnapshotFunc func(ctx context.Context, volumeId string) error DeleteMetadataFunc func(ctx context.Context, machineID, key string) error + DeleteSecretFunc func(ctx context.Context, label string) (err error) DeleteVolumeFunc func(ctx context.Context, volumeId string) (*fly.Volume, error) DestroyFunc func(ctx context.Context, input fly.RemoveMachineInput, nonce string) (err error) ExecFunc func(ctx context.Context, machineID string, in *fly.MachineExecRequest) (*fly.MachineExecResponse, error) ExtendVolumeFunc func(ctx context.Context, volumeId string, size_gb int) (*fly.Volume, bool, error) FindLeaseFunc func(ctx context.Context, machineID string) (*fly.MachineLease, error) + GenerateSecretFunc func(ctx context.Context, sLabel, sType string) (err error) GetFunc func(ctx context.Context, machineID string) (*fly.Machine, error) GetAllVolumesFunc func(ctx context.Context) ([]fly.Volume, error) GetManyFunc func(ctx context.Context, machineIDs []string) ([]*fly.Machine, error) @@ -36,6 +39,7 @@ type FlapsClient struct { ListFunc func(ctx context.Context, state string) ([]*fly.Machine, error) ListActiveFunc func(ctx context.Context) ([]*fly.Machine, error) ListFlyAppsMachinesFunc func(ctx context.Context) ([]*fly.Machine, *fly.Machine, error) + ListSecretsFunc func(ctx context.Context) (out []fly.ListSecret, err error) NewRequestFunc func(ctx context.Context, method, path string, in interface{}, headers map[string][]string) (*http.Request, error) RefreshLeaseFunc func(ctx context.Context, machineID string, ttl *int, nonce string) (*fly.MachineLease, error) ReleaseLeaseFunc func(ctx context.Context, machineID, nonce string) error @@ -63,6 +67,10 @@ func (m *FlapsClient) CreateApp(ctx context.Context, name string, org string) (e return m.CreateAppFunc(ctx, name, org) } +func (m *FlapsClient) CreateSecret(ctx context.Context, sLabel, sType string, in fly.CreateSecretRequest) (err error) { + return m.CreateSecretFunc(ctx, sLabel, sType, in) +} + func (m *FlapsClient) CreateVolume(ctx context.Context, req fly.CreateVolumeRequest) (*fly.Volume, error) { return m.CreateVolumeFunc(ctx, req) } @@ -75,6 +83,10 @@ func (m *FlapsClient) DeleteMetadata(ctx context.Context, machineID, key string) return m.DeleteMetadataFunc(ctx, machineID, key) } +func (m *FlapsClient) DeleteSecret(ctx context.Context, label string) (err error) { + return m.DeleteSecretFunc(ctx, label) +} + func (m *FlapsClient) DeleteVolume(ctx context.Context, volumeId string) (*fly.Volume, error) { return m.DeleteVolumeFunc(ctx, volumeId) } @@ -95,6 +107,10 @@ func (m *FlapsClient) FindLease(ctx context.Context, machineID string) (*fly.Mac return m.FindLeaseFunc(ctx, machineID) } +func (m *FlapsClient) GenerateSecret(ctx context.Context, sLabel, sType string) (err error) { + return m.GenerateSecretFunc(ctx, sLabel, sType) +} + func (m *FlapsClient) Get(ctx context.Context, machineID string) (*fly.Machine, error) { return m.GetFunc(ctx, machineID) } @@ -147,6 +163,10 @@ func (m *FlapsClient) ListFlyAppsMachines(ctx context.Context) ([]*fly.Machine, return m.ListFlyAppsMachinesFunc(ctx) } +func (m *FlapsClient) ListSecrets(ctx context.Context) (out []fly.ListSecret, err error) { + return m.ListSecretsFunc(ctx) +} + func (m *FlapsClient) NewRequest(ctx context.Context, method, path string, in interface{}, headers map[string][]string) (*http.Request, error) { return m.NewRequestFunc(ctx, method, path, in, headers) }