Module: Kdeploy::DSL

Included in:
CLI
Defined in:
lib/kdeploy/dsl/dsl.rb

Overview

Domain-specific language for defining hosts, roles, and tasks

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(base) ⇒ Object



34
35
36
37
38
# File 'lib/kdeploy/dsl/dsl.rb', line 34

def self.extended(base)
  base.instance_variable_set(:@kdeploy_hosts, {})
  base.instance_variable_set(:@kdeploy_tasks, {})
  base.instance_variable_set(:@kdeploy_roles, {})
end

.included(base) ⇒ Object



27
28
29
30
31
32
# File 'lib/kdeploy/dsl/dsl.rb', line 27

def self.included(base)
  # Support `include Kdeploy::DSL` by promoting the DSL methods to class methods.
  # This keeps tests and external integrations simpler while preserving the
  # primary usage pattern (CLI uses `extend DSL`).
  base.extend(self)
end

Instance Method Details

#add_explicit_hosts(task, hosts) ⇒ Object



250
251
252
253
254
# File 'lib/kdeploy/dsl/dsl.rb', line 250

def add_explicit_hosts(task, hosts)
  task[:hosts]&.each do |host|
    hosts.add(host) if kdeploy_hosts.key?(host)
  end
end

#add_role_hosts(task, hosts) ⇒ Object



256
257
258
259
260
261
262
263
264
265
# File 'lib/kdeploy/dsl/dsl.rb', line 256

def add_role_hosts(task, hosts)
  task[:roles]&.each do |role|
    role_hosts = kdeploy_roles[role]
    next unless role_hosts

    role_hosts.each do |host|
      hosts.add(host) if kdeploy_hosts.key?(host)
    end
  end
end

#assign_task(task_name, on: nil, roles: nil) ⇒ Object

Assign task to roles or hosts after task definition

Raises:

  • (ArgumentError)


83
84
85
86
87
88
89
# File 'lib/kdeploy/dsl/dsl.rb', line 83

def assign_task(task_name, on: nil, roles: nil)
  task = kdeploy_tasks[task_name.to_sym]
  raise ArgumentError, "Task #{task_name} not found" unless task

  task[:hosts] = normalize_hosts_option(on) if on
  task[:roles] = normalize_roles_option(roles) if roles
end

#build_package_command(name, version, platform) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/kdeploy/dsl/dsl.rb', line 267

def build_package_command(name, version, platform)
  n = Shellwords.escape(name.to_s)
  case platform.to_sym
  when :yum, :rpm
    if version
      "yum install -y #{n}-#{Shellwords.escape(version.to_s)}"
    else
      "yum install -y #{n}"
    end
  else
    base = "apt-get update && apt-get install -y #{n}"
    version ? "#{base}=#{Shellwords.escape(version.to_s)}" : base
  end
end

#create_task_block(block) ⇒ Object



136
137
138
139
140
141
142
# File 'lib/kdeploy/dsl/dsl.rb', line 136

def create_task_block(block)
  lambda {
    @kdeploy_commands = []
    instance_eval(&block)
    @kdeploy_commands
  }
end

#directory(path, mode: nil) ⇒ Object

Ensure a remote directory exists. Supports mode option.



223
224
225
226
227
228
229
230
# File 'lib/kdeploy/dsl/dsl.rb', line 223

def directory(path, mode: nil)
  @kdeploy_commands ||= []
  cmd = "mkdir -p #{Shellwords.escape(path.to_s)}"
  @kdeploy_commands << { type: :run, command: cmd, sudo: true }
  return unless mode

  @kdeploy_commands << { type: :run, command: "chmod #{mode} #{Shellwords.escape(path.to_s)}", sudo: true }
end

#file(destination, source:) ⇒ Object

Upload a local file to a remote path.



217
218
219
220
# File 'lib/kdeploy/dsl/dsl.rb', line 217

def file(destination, source:)
  @kdeploy_commands ||= []
  upload(source, destination)
end

#get_task_hosts(task_name) ⇒ Object



236
237
238
239
240
241
242
243
244
# File 'lib/kdeploy/dsl/dsl.rb', line 236

def get_task_hosts(task_name)
  task = kdeploy_tasks[task_name]
  return kdeploy_hosts.keys if task_empty?(task)

  hosts = Set.new
  add_explicit_hosts(task, hosts)
  add_role_hosts(task, hosts)
  hosts.to_a
end

#host(name, **options) ⇒ Object



66
67
68
# File 'lib/kdeploy/dsl/dsl.rb', line 66

def host(name, **options)
  kdeploy_hosts[name] = options.merge(name: name)
end

#hostsObject

Stable read accessors for tests/integrations. Keep these as aliases so internal storage can evolve without breaking callers.



42
43
44
# File 'lib/kdeploy/dsl/dsl.rb', line 42

def hosts
  kdeploy_hosts
end

#include_tasks(file_path, roles: nil, on: nil) ⇒ Object

Include task file and automatically assign all tasks to specified roles or hosts



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/kdeploy/dsl/dsl.rb', line 92

def include_tasks(file_path, roles: nil, on: nil)
  # Resolve relative paths based on the caller's file location
  unless File.absolute_path?(file_path)
    caller_file = caller_locations(1, 1).first.path
    base_dir = File.dirname(File.expand_path(caller_file))
    file_path = File.expand_path(file_path, base_dir)
  end

  # Store tasks before loading
  tasks_before = kdeploy_tasks.keys

  # Load the task file
  module_eval(File.read(file_path), file_path)

  # Get newly added tasks
  tasks_after = kdeploy_tasks.keys
  new_tasks = tasks_after - tasks_before

  # Assign roles/hosts to all new tasks (only if task doesn't already have hosts/roles)
  new_tasks.each do |task_name|
    task = kdeploy_tasks[task_name]
    # Only assign if task doesn't already have hosts or roles defined
    next if task[:hosts] || task[:roles]

    assign_task(task_name, roles: roles, on: on) if roles || on
  end
end

#inventory(&block) ⇒ Object



232
233
234
# File 'lib/kdeploy/dsl/dsl.rb', line 232

def inventory(&block)
  instance_eval(&block) if block_given?
end

#kdeploy_hostsObject



54
55
56
# File 'lib/kdeploy/dsl/dsl.rb', line 54

def kdeploy_hosts
  @kdeploy_hosts ||= {}
end

#kdeploy_rolesObject



62
63
64
# File 'lib/kdeploy/dsl/dsl.rb', line 62

def kdeploy_roles
  @kdeploy_roles ||= {}
end

#kdeploy_tasksObject



58
59
60
# File 'lib/kdeploy/dsl/dsl.rb', line 58

def kdeploy_tasks
  @kdeploy_tasks ||= {}
end

#normalize_hosts_option(on) ⇒ Object



120
121
122
123
124
125
126
# File 'lib/kdeploy/dsl/dsl.rb', line 120

def normalize_hosts_option(on)
  return on if on.is_a?(Array)

  return [on] if on

  nil
end

#normalize_roles_option(roles) ⇒ Object



128
129
130
131
132
133
134
# File 'lib/kdeploy/dsl/dsl.rb', line 128

def normalize_roles_option(roles)
  return roles if roles.is_a?(Array)

  return [roles] if roles

  nil
end

#package(name, version: nil, platform: :apt) ⇒ Object

Install a system package. Defaults to apt; supports platform: :yum.



183
184
185
186
187
# File 'lib/kdeploy/dsl/dsl.rb', line 183

def package(name, version: nil, platform: :apt)
  @kdeploy_commands ||= []
  cmd = build_package_command(name, version, platform)
  @kdeploy_commands << { type: :run, command: cmd, sudo: true }
end

#role(name, hosts) ⇒ Object



70
71
72
# File 'lib/kdeploy/dsl/dsl.rb', line 70

def role(name, hosts)
  kdeploy_roles[name] = hosts
end

#rolesObject



50
51
52
# File 'lib/kdeploy/dsl/dsl.rb', line 50

def roles
  kdeploy_roles
end

#run(command, sudo: nil) ⇒ Object



144
145
146
147
# File 'lib/kdeploy/dsl/dsl.rb', line 144

def run(command, sudo: nil)
  @kdeploy_commands ||= []
  @kdeploy_commands << { type: :run, command: command, sudo: sudo }
end

#service(name, action: :start) ⇒ Object

Manage a systemd service. action supports :start, :stop, :restart, :reload, :enable, :disable.



190
191
192
193
194
195
196
197
# File 'lib/kdeploy/dsl/dsl.rb', line 190

def service(name, action: :start)
  @kdeploy_commands ||= []
  actions = Array(action)
  actions.each do |a|
    cmd = "systemctl #{a} #{Shellwords.escape(name.to_s)}"
    @kdeploy_commands << { type: :run, command: cmd, sudo: true }
  end
end

#sync(source, destination, ignore: [], delete: false, exclude: [], fast: nil, parallel: nil) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/kdeploy/dsl/dsl.rb', line 164

def sync(source, destination, ignore: [], delete: false, exclude: [], fast: nil, parallel: nil)
  @kdeploy_commands ||= []
  @kdeploy_commands << {
    type: :sync,
    source: source,
    destination: destination,
    ignore: Array(ignore),
    exclude: Array(exclude),
    delete: delete,
    fast: fast,
    parallel: parallel
  }
end

#task(name, on: nil, roles: nil, &block) ⇒ Object



74
75
76
77
78
79
80
# File 'lib/kdeploy/dsl/dsl.rb', line 74

def task(name, on: nil, roles: nil, &block)
  kdeploy_tasks[name] = {
    hosts: normalize_hosts_option(on),
    roles: normalize_roles_option(roles),
    block: create_task_block(block)
  }
end

#task_empty?(task) ⇒ Boolean

Returns:

  • (Boolean)


246
247
248
# File 'lib/kdeploy/dsl/dsl.rb', line 246

def task_empty?(task)
  !task || (!task[:hosts] && !task[:roles])
end

#tasksObject



46
47
48
# File 'lib/kdeploy/dsl/dsl.rb', line 46

def tasks
  kdeploy_tasks
end

#template(destination, source: nil, variables: nil, &block) ⇒ Object

Deploy an ERB template to a remote path. Supports block and keyword args.



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/kdeploy/dsl/dsl.rb', line 200

def template(destination, source: nil, variables: nil, &block)
  @kdeploy_commands ||= []
  if block
    opts = TemplateOptions.new
    opts.instance_eval(&block)
    src = opts.source || raise(ArgumentError, 'template requires source')
    vars = opts.variables || {}
  else
    raise ArgumentError, 'template requires source' unless source

    src = source
    vars = variables || {}
  end
  upload_template(src, destination, vars)
end

#upload(source, destination) ⇒ Object



149
150
151
152
# File 'lib/kdeploy/dsl/dsl.rb', line 149

def upload(source, destination)
  @kdeploy_commands ||= []
  @kdeploy_commands << { type: :upload, source: source, destination: destination }
end

#upload_template(source, destination, variables = {}) ⇒ Object



154
155
156
157
158
159
160
161
162
# File 'lib/kdeploy/dsl/dsl.rb', line 154

def upload_template(source, destination, variables = {})
  @kdeploy_commands ||= []
  @kdeploy_commands << {
    type: :upload_template,
    source: source,
    destination: destination,
    variables: variables
  }
end