Class: RKSeal::Commands::Edit

Inherits:
Object
  • Object
show all
Defined in:
lib/rkseal/commands/edit.rb

Overview

Orchestrates the ‘rkseal edit <namespace> <secret-name>` flow.

Recovers the current state from the live cluster Secret (the only source of truth – a SealedSecret cannot be decrypted client-side), shows it in ‘$EDITOR` on a RAM-backed buffer with `data` kept as base64, re-seals, and writes `<secret-name>.yaml` to the current working directory.

Three behaviours distinguish this flow:

- **scope preservation:** the existing SealedSecret's scope is read from
  the cluster (annotation), falling back to the local `<name>.yaml`, then
  to :strict. An explicit `scope:` always overrides.
- **no-op:** if the saved buffer is equivalent to the cluster Secret,
  nothing is written and no fresh ciphertext is produced (re-sealing
  identical input still yields new ciphertext, which would create spurious
  diffs); the flow exits cleanly with a "no changes" Result -- and, since
  there is nothing new to apply, a requested deploy is skipped too.
- **deploy:** opt-in only. When requested, {RKSeal::ContextGuard} surfaces
  the active context and asks the operator to confirm before
  `kubectl apply` (unless `assume_yes`). If the Secret is absent from the
  cluster, the flow fails fast and points the user at `create`.

Collaborators are injected so the flow is unit-testable without a cluster.

Examples:

write-only (default)

RKSeal::Commands::Edit.new(namespace: "app", name: "db").call

deploy after editing (explicit opt-in)

RKSeal::Commands::Edit.new(namespace: "app", name: "db", deploy: true).call

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(namespace:, name:, scope: nil, deploy: false, assume_yes: false, string_data: false, kubectl: Kubectl.new, kubeseal: Kubeseal.new, editor: Editor.new, context_guard: nil, prompt: Thor::Shell::Basic.new, workspace: SecureWorkspace, output_dir: Dir.pwd) ⇒ Edit

Returns a new instance of Edit.

Parameters:

  • namespace (String)

    target namespace (positional CLI arg).

  • name (String)

    Secret name (positional CLI arg).

  • scope (Symbol, nil) (defaults to: nil)

    explicit scope override; nil preserves the secret’s existing scope (read from cluster / local file, else :strict).

  • deploy (Boolean) (defaults to: false)

    opt-in deploy after writing; defaults to false.

  • assume_yes (Boolean) (defaults to: false)

    skip the interactive deploy confirmation (only meaningful with deploy:); for non-interactive pipelines.

  • string_data (Boolean) (defaults to: false)

    present values as decoded plaintext ‘stringData` instead of base64 `data`; defaults to false (an opt-in plaintext exposure of the cluster Secret).

  • kubectl (RKSeal::Kubectl) (defaults to: Kubectl.new)

    cluster adapter (read + apply).

  • kubeseal (RKSeal::Kubeseal) (defaults to: Kubeseal.new)

    sealing adapter.

  • editor (RKSeal::Editor) (defaults to: Editor.new)

    editor launcher.

  • context_guard (RKSeal::ContextGuard, nil) (defaults to: nil)

    deploy gatekeeper; built from the kubectl adapter + prompt when nil and a deploy is requested.

  • prompt (Thor::Shell::Basic) (defaults to: Thor::Shell::Basic.new)

    shell used for the deploy confirmation (passed to the ContextGuard when one is built here).

  • workspace (#with) (defaults to: SecureWorkspace)

    RAM-backed scratch provider (block-scoped).

  • output_dir (String) (defaults to: Dir.pwd)

    directory the manifest is written to (CWD).



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/rkseal/commands/edit.rb', line 64

def initialize(namespace:, name:, scope: nil, deploy: false, assume_yes: false,
               string_data: false,
               kubectl: Kubectl.new, kubeseal: Kubeseal.new, editor: Editor.new,
               context_guard: nil, prompt: Thor::Shell::Basic.new,
               workspace: SecureWorkspace, output_dir: Dir.pwd)
  @namespace = namespace
  @name = name
  @scope = scope
  @deploy = deploy
  @assume_yes = assume_yes
  @string_data = string_data
  @kubectl = kubectl
  @kubeseal = kubeseal
  @editor = editor
  @context_guard = context_guard
  @prompt = prompt
  @workspace = workspace
  @output_dir = output_dir
end

Instance Attribute Details

#deployBoolean (readonly)

Returns whether to deploy after writing the manifest.

Returns:

  • (Boolean)

    whether to deploy after writing the manifest.



43
44
45
# File 'lib/rkseal/commands/edit.rb', line 43

def deploy
  @deploy
end

#nameString (readonly)

Returns:

  • (String)


38
39
40
# File 'lib/rkseal/commands/edit.rb', line 38

def name
  @name
end

#namespaceString (readonly)

Returns:

  • (String)


36
37
38
# File 'lib/rkseal/commands/edit.rb', line 36

def namespace
  @namespace
end

#scopeSymbol? (readonly)

Returns explicit sealing scope override, or nil to preserve the secret’s existing scope.

Returns:

  • (Symbol, nil)

    explicit sealing scope override, or nil to preserve the secret’s existing scope.



41
42
43
# File 'lib/rkseal/commands/edit.rb', line 41

def scope
  @scope
end

Instance Method Details

#callRKSeal::Commands::Result

Run the edit flow end to end.

Side effects: reads the cluster Secret (and SealedSecret scope) via ‘kubectl`; spawns `$EDITOR`; provisions/tears down a RAM-backed workspace; shells out to `kubeseal`; writes `<name>.yaml` (unless unchanged); and, only when #deploy is true and the operator confirms, runs `kubectl apply`.

Returns:

Raises:



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/rkseal/commands/edit.rb', line 99

def call
  ensure_dependencies!

  cluster_secret = Secret.from_kubectl_json(@kubectl.get_secret(name: @name,
                                                                namespace: @namespace))
  edited = edit(cluster_secret)

  return unchanged_result if edited == cluster_secret

  edited.validate!
  effective_scope = @scope || resolve_scope
  path = write_manifest(@kubeseal.seal(edited.to_manifest(scope: effective_scope),
                                       scope: effective_scope))
  deployed = @deploy && deploy_confirmed?
  @kubectl.apply(file: path) if deployed
  Result.new(secret_name: @name, namespace: @namespace, output_path: path, deployed: deployed)
end