Module: Pvectl::ManifestSerializer

Defined in:
lib/pvectl/manifest_serializer.rb,
sig/pvectl/manifest_serializer.rbs

Overview

Wraps ConfigSerializer output with a kubectl-like manifest envelope (apiVersion, kind, metadata, spec). Handles bidirectional conversion between YAML manifest files and internal config representations.

Constant Summary collapse

API_VERSION =
"pvectl/v1"
KINDS =
{
  vm: "VirtualMachine",
  container: "Container"
}.freeze
KINDS_REVERSE =
KINDS.invert.transform_keys(&:to_s).freeze

Class Method Summary collapse

Class Method Details

.from_yaml(yaml_string) ⇒ Hash

Parses a YAML manifest string into type, metadata, and spec.



41
42
43
44
45
46
47
48
49
50
# File 'lib/pvectl/manifest_serializer.rb', line 41

def from_yaml(yaml_string)
  parsed = YAML.safe_load(yaml_string)
  type = KINDS_REVERSE[parsed["kind"]]

  {
    type: type,
    metadata: (parsed["metadata"] || {}),
    spec: symbolize_keys_deep(parsed["spec"] || {})
  }
end

.to_yaml(nested_config, type:, metadata:) ⇒ String

Builds a YAML manifest string from nested config and metadata.



26
27
28
29
30
31
32
33
34
35
# File 'lib/pvectl/manifest_serializer.rb', line 26

def to_yaml(nested_config, type:, metadata:)
  manifest = {
    "apiVersion" => API_VERSION,
    "kind" => KINDS.fetch(type),
    "metadata" => (),
    "spec" => stringify_keys_deep(nested_config)
  }

  YAML.dump(manifest)
end

.validate(yaml_string) ⇒ Array<String>

Validates manifest structure (envelope only, not spec content).



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/pvectl/manifest_serializer.rb', line 56

def validate(yaml_string)
  errors = []

  begin
    parsed = YAML.safe_load(yaml_string)
  rescue Psych::SyntaxError => e
    return ["YAML syntax error: #{e.message}"]
  end

  unless parsed.is_a?(Hash)
    return ["Invalid manifest: expected a YAML mapping"]
  end

  unless parsed["apiVersion"]
    errors << "Missing required field 'apiVersion'"
  end

  if parsed["apiVersion"] && parsed["apiVersion"] != API_VERSION
    errors << "Unsupported apiVersion '#{parsed["apiVersion"]}'. Expected: #{API_VERSION}"
  end

  errors << "Missing required field 'kind'" unless parsed["kind"]

  if parsed["kind"] && !KINDS_REVERSE.key?(parsed["kind"])
    errors << "Unknown kind '#{parsed["kind"]}'. Valid: #{KINDS.values.join(', ')}"
  end

   = parsed["metadata"]
  if .nil? || !.is_a?(Hash)
    errors << "Missing required field 'metadata'"
  end

  errors
end