Class: RKSeal::Kubectl

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

Overview

Thin adapter over the ‘kubectl` binary.

Each public method maps to one kubectl invocation. Methods return raw strings (JSON / context name) or nothing; this adapter does not parse the Secret into the domain model – Secret.from_kubectl_json does that.

As with Kubeseal, all process execution funnels through one private runner that is the single stub seam for unit tests. The runner must never log Secret contents.

Constant Summary collapse

BINARY =
"kubectl"
NOT_FOUND_MARKER =

kubectl prints this token to stderr when a resource is absent. Matched case-insensitively to map the failure onto NotFoundError.

"notfound"

Instance Method Summary collapse

Constructor Details

#initialize(binary: BINARY) ⇒ Kubectl

Returns a new instance of Kubectl.

Parameters:

  • binary (String) (defaults to: BINARY)

    override the executable name/path (testing/env).



23
24
25
# File 'lib/rkseal/kubectl.rb', line 23

def initialize(binary: BINARY)
  @binary = binary
end

Instance Method Details

#apply(file:) ⇒ String

Apply a manifest file to the cluster (‘kubectl apply -f <file>`). Only the `edit` deploy step calls this, and only after ContextGuard has approved the active context.

Parameters:

  • file (String)

    path to the SealedSecret manifest to apply.

Returns:

  • (String)

    kubectl’s stdout (the apply result line).

Raises:



104
105
106
# File 'lib/rkseal/kubectl.rb', line 104

def apply(file:)
  run("apply", "-f", file)
end

#current_contextString

Return the active kube context (‘kubectl config current-context`). Used by ContextGuard to gate deploys.

Returns:

  • (String)

    the current context name (whitespace stripped).

Raises:



113
114
115
# File 'lib/rkseal/kubectl.rb', line 113

def current_context
  run("config", "current-context").strip
end

#ensure_available!void

This method returns an undefined value.

Verify the kubectl binary is present and executable; raise otherwise.

Raises:



31
32
33
34
35
36
37
# File 'lib/rkseal/kubectl.rb', line 31

def ensure_available!
  return if executable_on_path?(@binary)

  raise DependencyMissingError,
        "kubectl not found on PATH (looked for #{@binary.inspect}). " \
        "Install it from https://kubernetes.io/docs/tasks/tools/."
end

#get_sealedsecret(name:, namespace:) ⇒ String

Read a SealedSecret from the cluster as JSON (‘kubectl get sealedsecret <name> -n <namespace> -o json`). The `edit` flow consults this to recover the existing seal’s scope when it is not otherwise known; callers rescue NotFoundError to fall back to the local file.

Parameters:

  • name (String)
  • namespace (String)

Returns:

  • (String)

    the JSON document kubectl prints on stdout.

Raises:



72
73
74
75
76
77
78
79
# File 'lib/rkseal/kubectl.rb', line 72

def get_sealedsecret(name:, namespace:)
  run("get", "sealedsecret", name, "-n", namespace, "-o", "json")
rescue CommandError => e
  raise unless not_found?(e.stderr)

  raise NotFoundError,
        "SealedSecret #{name.inspect} not found in namespace #{namespace.inspect}."
end

#get_secret(name:, namespace:) ⇒ String

Read a Secret from the cluster as JSON (‘kubectl get secret <name> -n <namespace> -o json`). This is the only way to recover the current plaintext of an existing SealedSecret, so it drives the `edit` flow.

Parameters:

  • name (String)
  • namespace (String)

Returns:

  • (String)

    the JSON document kubectl prints on stdout.

Raises:

  • (RKSeal::NotFoundError)

    if the Secret does not exist (kubectl “NotFound”); the message must point the user at ‘rkseal create`.

  • (RKSeal::CommandError)

    on any other kubectl failure (e.g. cluster unreachable, unknown namespace).



51
52
53
54
55
56
57
58
59
# File 'lib/rkseal/kubectl.rb', line 51

def get_secret(name:, namespace:)
  run("get", "secret", name, "-n", namespace, "-o", "json")
rescue CommandError => e
  raise unless not_found?(e.stderr)

  raise NotFoundError,
        "Secret #{name.inspect} not found in namespace #{namespace.inspect}. " \
        "Use `rkseal create #{namespace} #{name}` to author it first."
end

#list_sealedsecrets(namespace: nil) ⇒ String

List SealedSecrets as JSON (‘kubectl get sealedsecret -o json`), scoped to one namespace (`-n <namespace>`) or across all namespaces (`-A`) when none is given. Drives the `list` flow.

An empty namespace is not an error: kubectl returns a List object with an empty ‘items: []`, so this does NOT map NotFound – only operational failures (cluster unreachable, CRD absent) surface, as CommandError.

Parameters:

  • namespace (String, nil) (defaults to: nil)

    a single namespace, or nil for all.

Returns:

  • (String)

    the JSON List document kubectl prints on stdout.

Raises:



92
93
94
95
# File 'lib/rkseal/kubectl.rb', line 92

def list_sealedsecrets(namespace: nil)
  scope = namespace ? ["-n", namespace] : ["-A"]
  run("get", "sealedsecret", *scope, "-o", "json")
end