walheim 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/lib/walheim/cli/base_command.rb +98 -99
- data/lib/walheim/cli/helpers.rb +17 -17
- data/lib/walheim/cli/legacy_context.rb +54 -54
- data/lib/walheim/cli/resource_command.rb +11 -9
- data/lib/walheim/cli.rb +64 -19
- data/lib/walheim/cluster_resource.rb +5 -5
- data/lib/walheim/config.rb +36 -34
- data/lib/walheim/handler_registry.rb +2 -3
- data/lib/walheim/namespaced_resource.rb +40 -20
- data/lib/walheim/resource.rb +30 -14
- data/lib/walheim/resources/apps.rb +493 -121
- data/lib/walheim/resources/configmaps.rb +7 -7
- data/lib/walheim/resources/namespaces.rb +271 -28
- data/lib/walheim/resources/secrets.rb +9 -9
- data/lib/walheim/sync.rb +5 -5
- data/lib/walheim/version.rb +1 -1
- data/lib/walheim.rb +12 -12
- metadata +16 -2
data/lib/walheim/cli.rb
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
3
|
+
require "thor"
|
|
4
|
+
require_relative "cli/helpers"
|
|
5
|
+
require_relative "cli/base_command"
|
|
6
|
+
require_relative "cli/resource_command"
|
|
7
7
|
|
|
8
8
|
module Walheim
|
|
9
9
|
class CLI < Thor
|
|
10
|
+
# Exit with non-zero status on errors
|
|
11
|
+
def self.exit_on_failure?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
10
15
|
# Global flags
|
|
11
16
|
class_option :context,
|
|
12
17
|
type: :string,
|
|
13
|
-
desc:
|
|
18
|
+
desc: "Override active context"
|
|
14
19
|
|
|
15
20
|
class_option :whconfig,
|
|
16
21
|
type: :string,
|
|
17
|
-
desc:
|
|
22
|
+
desc: "Alternate config file path"
|
|
18
23
|
|
|
19
24
|
class_option :data_dir,
|
|
20
25
|
type: :string,
|
|
21
|
-
aliases: [:d],
|
|
22
|
-
desc:
|
|
26
|
+
aliases: [ :d ],
|
|
27
|
+
desc: "Data directory (deprecated: use contexts)"
|
|
23
28
|
|
|
24
29
|
# Dynamically register operations
|
|
25
30
|
def self.register_operations
|
|
@@ -29,11 +34,50 @@ module Walheim
|
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
# Version command
|
|
32
|
-
desc
|
|
37
|
+
desc "version", "Show version"
|
|
33
38
|
def version
|
|
34
39
|
puts "whctl version #{Walheim::VERSION}"
|
|
35
40
|
end
|
|
36
41
|
|
|
42
|
+
# Exec command - custom implementation to support variadic arguments
|
|
43
|
+
desc "exec app NAME [--] COMMAND...", "Execute command in app container"
|
|
44
|
+
method_option :namespace,
|
|
45
|
+
type: :string,
|
|
46
|
+
aliases: [ :n ],
|
|
47
|
+
desc: "Target namespace",
|
|
48
|
+
required: true
|
|
49
|
+
method_option :service,
|
|
50
|
+
type: :string,
|
|
51
|
+
aliases: [ :s ],
|
|
52
|
+
desc: "Target service (defaults to first)"
|
|
53
|
+
method_option :interactive,
|
|
54
|
+
type: :boolean,
|
|
55
|
+
aliases: [ :it ],
|
|
56
|
+
desc: "Allocate pseudo-TTY and keep stdin open",
|
|
57
|
+
default: false
|
|
58
|
+
def exec(kind, name, *command)
|
|
59
|
+
# Validate kind is 'app' or 'apps'
|
|
60
|
+
unless %w[app apps].include?(kind.downcase)
|
|
61
|
+
warn "Error: exec only supports 'app' kind, got '#{kind}'"
|
|
62
|
+
exit 1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Resolve data directory from context
|
|
66
|
+
data_dir = BaseCommand.send(:resolve_data_dir, options, self.class.class_options.transform_keys(&:to_sym).transform_values { |v| options[v.name] rescue nil })
|
|
67
|
+
|
|
68
|
+
# Initialize Apps handler
|
|
69
|
+
handler = Resources::Apps.new(data_dir: data_dir)
|
|
70
|
+
|
|
71
|
+
# Call exec_command method
|
|
72
|
+
handler.exec_command(
|
|
73
|
+
namespace: options[:namespace],
|
|
74
|
+
name: name,
|
|
75
|
+
service: options[:service],
|
|
76
|
+
interactive: options[:interactive],
|
|
77
|
+
command: command
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
37
81
|
# Override help to maintain kubectl-style help
|
|
38
82
|
def self.help(shell, subcommand = false)
|
|
39
83
|
list = printable_commands(true, subcommand)
|
|
@@ -42,19 +86,20 @@ module Walheim
|
|
|
42
86
|
end
|
|
43
87
|
|
|
44
88
|
# Group commands by category
|
|
45
|
-
shell.say
|
|
46
|
-
shell.say
|
|
47
|
-
shell.say
|
|
48
|
-
shell.say
|
|
49
|
-
shell.say
|
|
50
|
-
shell.say
|
|
51
|
-
shell.say
|
|
52
|
-
shell.say
|
|
53
|
-
shell.say
|
|
89
|
+
shell.say "Usage: whctl [global flags] <command> [arguments]"
|
|
90
|
+
shell.say ""
|
|
91
|
+
shell.say "Global flags:"
|
|
92
|
+
shell.say " --context CONTEXT Override active context for this command"
|
|
93
|
+
shell.say " --whconfig PATH Use alternate config file (default: ~/.walheim/config)"
|
|
94
|
+
shell.say " -d, --data-dir DIR Data directory containing namespaces (deprecated: use contexts)"
|
|
95
|
+
shell.say ""
|
|
96
|
+
shell.say "Available commands:"
|
|
97
|
+
shell.say ""
|
|
54
98
|
|
|
55
99
|
# Print commands
|
|
56
100
|
list.each do |command|
|
|
57
|
-
next if command[0] ==
|
|
101
|
+
next if command[0] == "help"
|
|
102
|
+
|
|
58
103
|
shell.say " #{command[0].ljust(30)} #{command[1]}"
|
|
59
104
|
end
|
|
60
105
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative "resource"
|
|
4
4
|
|
|
5
5
|
module Walheim
|
|
6
6
|
# ClusterResource represents resources that are cluster-scoped (not namespaced)
|
|
@@ -69,9 +69,9 @@ module Walheim
|
|
|
69
69
|
# Apply operation - create or update
|
|
70
70
|
def apply(name:, manifest_source: nil)
|
|
71
71
|
manifest_data = if manifest_source
|
|
72
|
-
|
|
72
|
+
File.read(manifest_source)
|
|
73
73
|
else
|
|
74
|
-
|
|
74
|
+
read_manifest_from_db(name)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
if resource_exists?(name)
|
|
@@ -91,7 +91,7 @@ module Walheim
|
|
|
91
91
|
|
|
92
92
|
manifest_path = File.join(resource_dir(name), manifest_filename)
|
|
93
93
|
manifest_content = File.read(manifest_path)
|
|
94
|
-
manifest_data = YAML.
|
|
94
|
+
manifest_data = YAML.safe_load(manifest_content)
|
|
95
95
|
|
|
96
96
|
# Compute summary fields
|
|
97
97
|
summary = {}
|
|
@@ -123,7 +123,7 @@ module Walheim
|
|
|
123
123
|
def find_resource_names
|
|
124
124
|
base_dir = File.join(@data_dir, self.class.kind_info[:plural])
|
|
125
125
|
Dir.entries(base_dir)
|
|
126
|
-
.select { |entry| File.directory?(File.join(base_dir, entry)) && !entry.start_with?(
|
|
126
|
+
.select { |entry| File.directory?(File.join(base_dir, entry)) && !entry.start_with?(".") }
|
|
127
127
|
.select { |entry| File.exist?(File.join(base_dir, entry, manifest_filename)) }
|
|
128
128
|
.sort
|
|
129
129
|
end
|
data/lib/walheim/config.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
5
|
|
|
6
6
|
module Walheim
|
|
7
7
|
# Configuration management for Walheim contexts
|
|
@@ -10,9 +10,9 @@ module Walheim
|
|
|
10
10
|
class ConfigError < StandardError; end
|
|
11
11
|
class ValidationError < ConfigError; end
|
|
12
12
|
|
|
13
|
-
DEFAULT_CONFIG_PATH = File.expand_path(
|
|
14
|
-
API_VERSION =
|
|
15
|
-
KIND =
|
|
13
|
+
DEFAULT_CONFIG_PATH = File.expand_path("~/.walheim/config")
|
|
14
|
+
API_VERSION = "walheim.io/v1"
|
|
15
|
+
KIND = "Config"
|
|
16
16
|
|
|
17
17
|
attr_reader :current_context, :contexts, :config_path
|
|
18
18
|
|
|
@@ -35,18 +35,18 @@ module Walheim
|
|
|
35
35
|
data = YAML.load_file(@config_path)
|
|
36
36
|
validate_schema!(data)
|
|
37
37
|
|
|
38
|
-
@current_context = data[
|
|
39
|
-
@contexts = data[
|
|
38
|
+
@current_context = data["currentContext"]
|
|
39
|
+
@contexts = data["contexts"].map do |ctx|
|
|
40
40
|
{
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
"name" => ctx["name"],
|
|
42
|
+
"dataDir" => expand_path(ctx["dataDir"])
|
|
43
43
|
}
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
validate_current_context!
|
|
47
47
|
rescue Psych::SyntaxError => e
|
|
48
48
|
raise ConfigError, "Invalid YAML in config file: #{e.message}"
|
|
49
|
-
rescue => e
|
|
49
|
+
rescue StandardError => e
|
|
50
50
|
raise ConfigError, "Failed to load config: #{e.message}"
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -56,13 +56,13 @@ module Walheim
|
|
|
56
56
|
# @raise [ConfigError] if file cannot be written
|
|
57
57
|
def save_config
|
|
58
58
|
data = {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
"apiVersion" => API_VERSION,
|
|
60
|
+
"kind" => KIND,
|
|
61
|
+
"currentContext" => @current_context,
|
|
62
|
+
"contexts" => @contexts.map do |ctx|
|
|
63
63
|
{
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
"name" => ctx["name"],
|
|
65
|
+
"dataDir" => ctx["dataDir"]
|
|
66
66
|
}
|
|
67
67
|
end
|
|
68
68
|
}
|
|
@@ -74,7 +74,7 @@ module Walheim
|
|
|
74
74
|
temp_file = "#{@config_path}.tmp.#{Process.pid}"
|
|
75
75
|
File.write(temp_file, YAML.dump(data))
|
|
76
76
|
File.rename(temp_file, @config_path)
|
|
77
|
-
rescue => e
|
|
77
|
+
rescue StandardError => e
|
|
78
78
|
File.delete(temp_file) if temp_file && File.exist?(temp_file)
|
|
79
79
|
raise ConfigError, "Failed to save config: #{e.message}"
|
|
80
80
|
end
|
|
@@ -86,12 +86,12 @@ module Walheim
|
|
|
86
86
|
# @raise [ConfigError] if context not found or no current context
|
|
87
87
|
def data_dir(context_name = nil)
|
|
88
88
|
name = context_name || @current_context
|
|
89
|
-
raise ConfigError,
|
|
89
|
+
raise ConfigError, "No active context selected" if name.nil?
|
|
90
90
|
|
|
91
91
|
context = find_context(name)
|
|
92
92
|
raise ConfigError, "Context '#{name}' not found" if context.nil?
|
|
93
93
|
|
|
94
|
-
context[
|
|
94
|
+
context["dataDir"]
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
# Add a new context
|
|
@@ -105,8 +105,8 @@ module Walheim
|
|
|
105
105
|
raise ValidationError, "Context '#{name}' already exists" if find_context(name)
|
|
106
106
|
|
|
107
107
|
@contexts << {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
"name" => name,
|
|
109
|
+
"dataDir" => expand_path(data_dir)
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
@current_context = name if activate
|
|
@@ -134,6 +134,7 @@ module Walheim
|
|
|
134
134
|
# @raise [ConfigError] if context not found
|
|
135
135
|
def use_context(name)
|
|
136
136
|
raise ConfigError, "Context '#{name}' not found" unless find_context(name)
|
|
137
|
+
|
|
137
138
|
@current_context = name
|
|
138
139
|
end
|
|
139
140
|
|
|
@@ -142,7 +143,7 @@ module Walheim
|
|
|
142
143
|
# @return [Array<Hash>] Array of context hashes with 'name', 'dataDir', and 'active' keys
|
|
143
144
|
def list_contexts
|
|
144
145
|
@contexts.map do |ctx|
|
|
145
|
-
ctx.merge(
|
|
146
|
+
ctx.merge("active" => ctx["name"] == @current_context)
|
|
146
147
|
end
|
|
147
148
|
end
|
|
148
149
|
|
|
@@ -159,7 +160,8 @@ module Walheim
|
|
|
159
160
|
# Resolve the config file path with precedence: param > $WHCONFIG > default
|
|
160
161
|
def resolve_config_path(config_path)
|
|
161
162
|
return expand_path(config_path) if config_path
|
|
162
|
-
return expand_path(ENV[
|
|
163
|
+
return expand_path(ENV["WHCONFIG"]) if ENV["WHCONFIG"]
|
|
164
|
+
|
|
163
165
|
DEFAULT_CONFIG_PATH
|
|
164
166
|
end
|
|
165
167
|
|
|
@@ -170,27 +172,27 @@ module Walheim
|
|
|
170
172
|
|
|
171
173
|
# Find a context by name
|
|
172
174
|
def find_context(name)
|
|
173
|
-
@contexts.find { |ctx| ctx[
|
|
175
|
+
@contexts.find { |ctx| ctx["name"] == name }
|
|
174
176
|
end
|
|
175
177
|
|
|
176
178
|
# Validate config schema
|
|
177
179
|
def validate_schema!(data)
|
|
178
|
-
raise ValidationError,
|
|
179
|
-
raise ValidationError, "Invalid apiVersion: expected '#{API_VERSION}'" unless data[
|
|
180
|
-
raise ValidationError, "Invalid kind: expected '#{KIND}'" unless data[
|
|
181
|
-
raise ValidationError,
|
|
182
|
-
raise ValidationError,
|
|
183
|
-
raise ValidationError,
|
|
180
|
+
raise ValidationError, "Config must be a Hash" unless data.is_a?(Hash)
|
|
181
|
+
raise ValidationError, "Invalid apiVersion: expected '#{API_VERSION}'" unless data["apiVersion"] == API_VERSION
|
|
182
|
+
raise ValidationError, "Invalid kind: expected '#{KIND}'" unless data["kind"] == KIND
|
|
183
|
+
raise ValidationError, "Missing required field: contexts" unless data["contexts"]
|
|
184
|
+
raise ValidationError, "contexts must be an Array" unless data["contexts"].is_a?(Array)
|
|
185
|
+
raise ValidationError, "contexts array cannot be empty" if data["contexts"].empty?
|
|
184
186
|
|
|
185
187
|
# Validate each context
|
|
186
|
-
data[
|
|
188
|
+
data["contexts"].each_with_index do |ctx, index|
|
|
187
189
|
raise ValidationError, "Context at index #{index} must be a Hash" unless ctx.is_a?(Hash)
|
|
188
|
-
raise ValidationError, "Context at index #{index} missing 'name'" unless ctx[
|
|
189
|
-
raise ValidationError, "Context at index #{index} missing 'dataDir'" unless ctx[
|
|
190
|
+
raise ValidationError, "Context at index #{index} missing 'name'" unless ctx["name"]
|
|
191
|
+
raise ValidationError, "Context at index #{index} missing 'dataDir'" unless ctx["dataDir"]
|
|
190
192
|
end
|
|
191
193
|
|
|
192
194
|
# Check for duplicate context names
|
|
193
|
-
names = data[
|
|
195
|
+
names = data["contexts"].map { |ctx| ctx["name"] }
|
|
194
196
|
duplicates = names.select { |name| names.count(name) > 1 }.uniq
|
|
195
197
|
raise ValidationError, "Duplicate context names: #{duplicates.join(', ')}" unless duplicates.empty?
|
|
196
198
|
end
|
|
@@ -57,9 +57,7 @@ module Walheim
|
|
|
57
57
|
handlers.values.uniq { |h| h[:handler] }.each do |handler_info|
|
|
58
58
|
handler_class = handler_info[:handler]
|
|
59
59
|
# Get operations from operation_info metadata
|
|
60
|
-
if handler_class.respond_to?(:operation_info)
|
|
61
|
-
handler_class.operation_info.keys.each { |op| ops[op] = true }
|
|
62
|
-
end
|
|
60
|
+
handler_class.operation_info.each_key { |op| ops[op] = true } if handler_class.respond_to?(:operation_info)
|
|
63
61
|
end
|
|
64
62
|
ops.keys.sort
|
|
65
63
|
end
|
|
@@ -75,6 +73,7 @@ module Walheim
|
|
|
75
73
|
def cluster_resource?(kind)
|
|
76
74
|
handler_info = get(kind)
|
|
77
75
|
return false unless handler_info
|
|
76
|
+
|
|
78
77
|
handler_info[:handler] < Walheim::ClusterResource
|
|
79
78
|
end
|
|
80
79
|
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative "resource"
|
|
4
4
|
|
|
5
5
|
module Walheim
|
|
6
6
|
# NamespacedResource represents resources that are scoped to a namespace
|
|
7
7
|
# Examples: Apps, Secrets, ConfigMaps
|
|
8
8
|
# These resources live under namespaces/{namespace}/{kind}/{name}/
|
|
9
9
|
class NamespacedResource < Resource
|
|
10
|
-
# Override operation_info to add namespace flags
|
|
10
|
+
# Override operation_info to add namespace flags and dispatch params
|
|
11
11
|
def self.operation_info
|
|
12
12
|
ops = super
|
|
13
13
|
|
|
@@ -15,15 +15,15 @@ module Walheim
|
|
|
15
15
|
namespace_options = {
|
|
16
16
|
namespace: {
|
|
17
17
|
type: :string,
|
|
18
|
-
aliases: [:n],
|
|
19
|
-
desc:
|
|
20
|
-
required: false
|
|
18
|
+
aliases: [ :n ],
|
|
19
|
+
desc: "Target namespace",
|
|
20
|
+
required: false # Validated at runtime
|
|
21
21
|
},
|
|
22
22
|
all: {
|
|
23
23
|
type: :boolean,
|
|
24
|
-
aliases: [:A],
|
|
25
|
-
desc:
|
|
26
|
-
banner:
|
|
24
|
+
aliases: [ :A ],
|
|
25
|
+
desc: "All namespaces",
|
|
26
|
+
banner: "" # No value needed for boolean
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -31,6 +31,17 @@ module Walheim
|
|
|
31
31
|
ops[:apply][:options].merge!(namespace: namespace_options[:namespace])
|
|
32
32
|
ops[:delete][:options].merge!(namespace: namespace_options[:namespace])
|
|
33
33
|
|
|
34
|
+
# Add namespace parameter to dispatch
|
|
35
|
+
ops[:get][:dispatch][:named_params] = { namespace: :namespace }
|
|
36
|
+
ops[:get][:dispatch][:namespace_handling] = :optional_with_all
|
|
37
|
+
|
|
38
|
+
ops[:apply][:dispatch][:named_params] ||= {}
|
|
39
|
+
ops[:apply][:dispatch][:named_params][:namespace] = :namespace
|
|
40
|
+
ops[:apply][:dispatch][:namespace_handling] = :required
|
|
41
|
+
|
|
42
|
+
ops[:delete][:dispatch][:named_params] = { namespace: :namespace }
|
|
43
|
+
ops[:delete][:dispatch][:namespace_handling] = :required
|
|
44
|
+
|
|
34
45
|
ops
|
|
35
46
|
end
|
|
36
47
|
|
|
@@ -38,9 +49,9 @@ module Walheim
|
|
|
38
49
|
|
|
39
50
|
def apply(namespace:, name:, manifest_source: nil)
|
|
40
51
|
manifest_data = if manifest_source
|
|
41
|
-
|
|
52
|
+
File.read(manifest_source)
|
|
42
53
|
else
|
|
43
|
-
|
|
54
|
+
read_manifest_from_db(namespace, name)
|
|
44
55
|
end
|
|
45
56
|
|
|
46
57
|
if resource_exists?(namespace, name)
|
|
@@ -105,7 +116,7 @@ module Walheim
|
|
|
105
116
|
# Get single resource
|
|
106
117
|
get_single_resource(namespace, name)
|
|
107
118
|
else
|
|
108
|
-
raise ArgumentError,
|
|
119
|
+
raise ArgumentError, "namespace is required when name is specified"
|
|
109
120
|
end
|
|
110
121
|
end
|
|
111
122
|
|
|
@@ -119,7 +130,7 @@ module Walheim
|
|
|
119
130
|
|
|
120
131
|
manifest_path = File.join(resource_dir(namespace, name), manifest_filename)
|
|
121
132
|
manifest_content = File.read(manifest_path)
|
|
122
|
-
manifest_data = YAML.
|
|
133
|
+
manifest_data = YAML.safe_load(manifest_content)
|
|
123
134
|
|
|
124
135
|
# Compute summary fields
|
|
125
136
|
summary = {}
|
|
@@ -137,7 +148,7 @@ module Walheim
|
|
|
137
148
|
|
|
138
149
|
# Path to resource directory: {data_dir}/namespaces/{namespace}/{kind_plural}/{name}/
|
|
139
150
|
def resource_dir(namespace, name)
|
|
140
|
-
File.join(@data_dir,
|
|
151
|
+
File.join(@data_dir, "namespaces", namespace, self.class.kind_info[:plural], name)
|
|
141
152
|
end
|
|
142
153
|
|
|
143
154
|
def resource_exists?(namespace, name)
|
|
@@ -169,7 +180,7 @@ module Walheim
|
|
|
169
180
|
end
|
|
170
181
|
|
|
171
182
|
def list_single_namespace(namespace)
|
|
172
|
-
namespaces_dir = File.join(@data_dir,
|
|
183
|
+
namespaces_dir = File.join(@data_dir, "namespaces")
|
|
173
184
|
namespace_path = File.join(namespaces_dir, namespace)
|
|
174
185
|
|
|
175
186
|
unless Dir.exist?(namespace_path)
|
|
@@ -182,7 +193,10 @@ module Walheim
|
|
|
182
193
|
|
|
183
194
|
# Find all resource directories
|
|
184
195
|
resource_names = Dir.entries(resources_path)
|
|
185
|
-
.select
|
|
196
|
+
.select do |entry|
|
|
197
|
+
File.directory?(File.join(resources_path,
|
|
198
|
+
entry)) && !entry.start_with?(".")
|
|
199
|
+
end
|
|
186
200
|
.sort
|
|
187
201
|
|
|
188
202
|
# Return array of manifest hashes
|
|
@@ -192,13 +206,16 @@ module Walheim
|
|
|
192
206
|
end
|
|
193
207
|
|
|
194
208
|
def list_all_namespaces
|
|
195
|
-
namespaces_dir = File.join(@data_dir,
|
|
209
|
+
namespaces_dir = File.join(@data_dir, "namespaces")
|
|
196
210
|
return [] unless Dir.exist?(namespaces_dir)
|
|
197
211
|
|
|
198
212
|
# Find all namespaces
|
|
199
213
|
namespace_names = Dir.entries(namespaces_dir)
|
|
200
|
-
.select
|
|
201
|
-
|
|
214
|
+
.select do |entry|
|
|
215
|
+
File.directory?(File.join(namespaces_dir,
|
|
216
|
+
entry)) && !entry.start_with?(".")
|
|
217
|
+
end
|
|
218
|
+
.select { |entry| File.exist?(File.join(namespaces_dir, entry, ".namespace.yaml")) }
|
|
202
219
|
.sort
|
|
203
220
|
|
|
204
221
|
# Collect all resources from all namespaces
|
|
@@ -208,7 +225,10 @@ module Walheim
|
|
|
208
225
|
next unless Dir.exist?(resources_path)
|
|
209
226
|
|
|
210
227
|
resource_names = Dir.entries(resources_path)
|
|
211
|
-
.select
|
|
228
|
+
.select do |entry|
|
|
229
|
+
File.directory?(File.join(resources_path,
|
|
230
|
+
entry)) && !entry.start_with?(".")
|
|
231
|
+
end
|
|
212
232
|
.sort
|
|
213
233
|
|
|
214
234
|
resource_names.each do |resource_name|
|
|
@@ -216,7 +236,7 @@ module Walheim
|
|
|
216
236
|
end
|
|
217
237
|
end
|
|
218
238
|
|
|
219
|
-
all_resources.sort_by { |resource| [resource[:namespace], resource[:name]] }
|
|
239
|
+
all_resources.sort_by { |resource| [ resource[:namespace], resource[:name] ] }
|
|
220
240
|
end
|
|
221
241
|
end
|
|
222
242
|
end
|
data/lib/walheim/resource.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "terminal-table"
|
|
6
6
|
|
|
7
7
|
module Walheim
|
|
8
8
|
# Base Resource class containing common functionality for all resource types
|
|
@@ -18,7 +18,7 @@ module Walheim
|
|
|
18
18
|
|
|
19
19
|
# Metadata - must be overridden by subclasses
|
|
20
20
|
def self.kind_info
|
|
21
|
-
raise NotImplementedError,
|
|
21
|
+
raise NotImplementedError, "Subclass must implement kind_info"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Lifecycle hooks - can be overridden by subclasses
|
|
@@ -32,33 +32,49 @@ module Walheim
|
|
|
32
32
|
|
|
33
33
|
# Summary fields for get output - can be overridden by subclasses
|
|
34
34
|
def self.summary_fields
|
|
35
|
-
{}
|
|
35
|
+
{} # Default: no summary fields
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
# Operation metadata - defines how operations appear in help
|
|
38
|
+
# Operation metadata - defines how operations appear in help and how they're dispatched
|
|
39
39
|
# Subclasses can override to add custom operations
|
|
40
40
|
def self.operation_info
|
|
41
41
|
{
|
|
42
42
|
get: {
|
|
43
|
-
description:
|
|
43
|
+
description: "List or retrieve resources",
|
|
44
44
|
usage: [
|
|
45
45
|
"get #{kind_info[:plural]} -n {namespace}",
|
|
46
46
|
"get #{kind_info[:plural]} --all/-A",
|
|
47
47
|
"get #{kind_info[:singular]} {name} -n {namespace}"
|
|
48
48
|
],
|
|
49
|
-
options: {} # Subclasses will override
|
|
49
|
+
options: {}, # Subclasses will override
|
|
50
|
+
dispatch: {
|
|
51
|
+
method: :get,
|
|
52
|
+
params: [ :name ],
|
|
53
|
+
output: :table
|
|
54
|
+
}
|
|
50
55
|
},
|
|
51
56
|
apply: {
|
|
52
|
-
description:
|
|
53
|
-
usage: ["apply #{kind_info[:singular]} {name} -n {namespace}"],
|
|
57
|
+
description: "Create or update a resource",
|
|
58
|
+
usage: [ "apply #{kind_info[:singular]} {name} -n {namespace}" ],
|
|
54
59
|
options: {
|
|
55
|
-
file: { type: :string, aliases: [:f], desc:
|
|
60
|
+
file: { type: :string, aliases: [ :f ], desc: "Manifest file (use - for stdin)" }
|
|
61
|
+
},
|
|
62
|
+
dispatch: {
|
|
63
|
+
method: :apply,
|
|
64
|
+
params: [ :name ],
|
|
65
|
+
named_params: {
|
|
66
|
+
manifest_source: :file
|
|
67
|
+
}
|
|
56
68
|
}
|
|
57
69
|
},
|
|
58
70
|
delete: {
|
|
59
|
-
description:
|
|
60
|
-
usage: ["delete #{kind_info[:singular]} {name} -n {namespace}"],
|
|
61
|
-
options: {} # Subclasses will override
|
|
71
|
+
description: "Delete a resource",
|
|
72
|
+
usage: [ "delete #{kind_info[:singular]} {name} -n {namespace}" ],
|
|
73
|
+
options: {}, # Subclasses will override
|
|
74
|
+
dispatch: {
|
|
75
|
+
method: :delete,
|
|
76
|
+
params: [ :name ]
|
|
77
|
+
}
|
|
62
78
|
}
|
|
63
79
|
}
|
|
64
80
|
end
|