kdeploy 1.3.5 → 1.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca352bd1378382e30e5b258fd620e63a31daafadeb9a4ea54975efc9937b452d
4
- data.tar.gz: d49e37b0c7fa428f3dacd0db5abe77388864e0d214bbddfb9bdc71f71385b0d8
3
+ metadata.gz: 8f48ad2c400882c0c590c830ae5f2a0e2a29d1a5bd2c978100fcd55f7f2df609
4
+ data.tar.gz: 3490c322e7f539ba8bd2daccd20c3d5176bdfe9e4e03ee051c696a041d57eadc
5
5
  SHA512:
6
- metadata.gz: 54f4e120d476fa41c117b80bc5918a87be836fe224a19a23b3a32b8581a86e205405ff4c7e9e24495956f39e210d27d8daccd1ab52e5417988a7f7393cce7037
7
- data.tar.gz: 49e08ea10d3ceee0887bac60b919dc17cc34258c3ffc5538df45015ad2c081c17019e62b3c96a42583b2ba62481e14e0aa8a1d3632c8ab17e03447e3ee8b39d5
6
+ metadata.gz: dd89c966452448bc952599312847c7996abd6956e0d6575c6b305b8860bcfaefcf525191819662498b317af58a4378746a882cea0e19a127ae52902a77a8fcf9
7
+ data.tar.gz: bd1f54080f1ddbdbb27be6ff053e744052d5a5bbda309a02879bb04f88fd51af74179170d7a27e9776fe5e66c55929cdf1ee0579a95f36c724cd3b95bd703f0a
data/CHANGELOG.md ADDED
@@ -0,0 +1,80 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.4.1] - 2026-11-12
11
+
12
+ ### Added
13
+
14
+ - `--report PATH` JSON execution report (`version`, `generated_at`, per-task hosts/steps)
15
+ - `--tags` task filter; `task ..., tags:`
16
+ - `vars` / `--extra-vars` with `%{name}` interpolation in steps
17
+ - `wait_port` / `wait_http` readiness steps (local probes)
18
+ - `on_failure { ... }` cleanup steps after a failed host step
19
+ - CI: Ruby 3.1/3.2/3.3 matrix; lab workflow on `docker/lab` / `lib` PRs
20
+
21
+ ### Changed
22
+
23
+ - Default `verify_host_key` is `accept_new` (was `never`)
24
+ - Task plans are `{ commands:, on_failure: }` via per-invocation `TaskContext` (parallel-safe)
25
+
26
+ ### Fixed
27
+
28
+ - Host timeout clock / abort path; sudo multi-line packing; inventory warnings (carry-over from 1.3.5+)
29
+
30
+ ## [1.3.5] - 2026-11-12
31
+
32
+ ### Added
33
+
34
+ - Open-source packaging: `LICENSE`, `CHANGELOG.md`, `SECURITY.md`
35
+ - CI: RSpec + RuboCop before RubyGems publish; weekly Docker lab workflow
36
+ - Safer timeout handling (`Executor#abort!`), sudo password hardening, inventory warnings
37
+
38
+ ### Changed
39
+
40
+ - Rakefile default task is `spec` (no longer auto bump/push)
41
+
42
+ ### Fixed
43
+
44
+ - Missing SSH exit status treated as failure
45
+ - Auth / argument errors no longer retried as transport failures
46
+ - Sudo upload paths escaped; inventory cleared between `execute` loads
47
+ - Host timeout clock starts when work is scheduled
48
+
49
+ ## [1.3.0] - 2026-07-07
50
+
51
+ ### Added
52
+
53
+ - `docker/lab` multi-distro Compose integration lab
54
+
55
+ ## [1.2.0] - 2026-07-07
56
+
57
+ ### Changed
58
+
59
+ - Project focused on CLI gem (Web Job Console removed)
60
+
61
+ ## [1.1.0] - 2026-07-07
62
+
63
+ ### Fixed
64
+
65
+ - Exit code 1 when no hosts match
66
+ - Config load from task directory; `KDEPLOY_*` env overrides
67
+
68
+ ## [1.0.0] - 2026-07-07
69
+
70
+ ### Added
71
+
72
+ - Initial public CLI: DSL, Runner, SSH/SCP sync, dry-run, JSON output
73
+
74
+ [Unreleased]: https://github.com/kevin197011/kdeploy/compare/v1.4.1...HEAD
75
+ [1.4.1]: https://github.com/kevin197011/kdeploy/compare/v1.3.5...v1.4.1
76
+ [1.3.5]: https://github.com/kevin197011/kdeploy/compare/v1.3.4...v1.3.5
77
+ [1.3.0]: https://github.com/kevin197011/kdeploy/releases/tag/v1.3.0
78
+ [1.2.0]: https://github.com/kevin197011/kdeploy/releases/tag/v1.2.0
79
+ [1.1.0]: https://github.com/kevin197011/kdeploy/releases/tag/v1.1.0
80
+ [1.0.0]: https://github.com/kevin197011/kdeploy/releases/tag/v1.0.0
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,35 @@
1
+ # Contributing
2
+
3
+ Thanks for considering a contribution.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ git clone git@github.com:kevin197011/kdeploy.git
9
+ cd kdeploy
10
+ bundle install
11
+ bundle exec rspec
12
+ bundle exec rubocop
13
+ ```
14
+
15
+ Optional multi-host lab:
16
+
17
+ ```bash
18
+ cd docker/lab
19
+ docker compose up -d --build
20
+ docker compose --profile test up --build runner
21
+ ```
22
+
23
+ ## Pull requests
24
+
25
+ 1. Use a feature branch.
26
+ 2. Keep diffs focused; follow existing style (`frozen_string_literal`, RuboCop).
27
+ 3. Add or update RSpec for behavior changes.
28
+ 4. Use [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, …).
29
+ 5. Do **not** run `rake push` / `push.rb` unless you are a maintainer releasing — the default Rake task is `spec`.
30
+ Maintainer `rake push` runs RuboCop `-A`, RSpec, patch-bumps `lib/kdeploy/version.rb` only, then commit/pull --rebase/push (does **not** `git add .`).
31
+
32
+ ## Scope
33
+
34
+ Please keep changes aligned with [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md) non-goals
35
+ (no agent, no Chef-style convergence engine, no Web UI).
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 kk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -26,7 +26,7 @@
26
26
  - 无代理 SSH 远程执行,多主机并发
27
27
  - Ruby DSL:主机、角色、任务;Chef 风格资源(`package`、`service`、`template`、`sync` 等)
28
28
  - 文件上传、ERB 模板、目录同步(可选 rsync 快速路径)
29
- - 试运行、JSON 输出、重试/超时策略
29
+ - 试运行、JSON 输出/`--report`、`--tags` / `--extra-vars`、`wait_*` / `on_failure`、重试/超时策略
30
30
 
31
31
  **技术栈**:Ruby、Thor、net-ssh、concurrent-ruby
32
32
 
@@ -77,17 +77,27 @@ kdeploy execute deploy.rb deploy_web # 执行
77
77
 
78
78
  ## 配置
79
79
 
80
- 项目根目录可放 `.kdeploy.yml`(从当前目录向上查找):
80
+ 项目根目录可放 `.kdeploy.yml`(从当前目录向上查找)。重试策略示例:`retry_policy.example.json`
81
+
82
+ ## 非目标
83
+
84
+ Kdeploy **不是** Ansible/Chef 收敛引擎,也 **没有** Web UI / Agent。边界见 [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md)。
85
+
86
+ ## 安全注意
87
+
88
+ - 默认 `verify_host_key: accept_new`。更严用 `always`;临时 lab 才用 `never`。
89
+ - 优先 SSH 密钥 + NOPASSWD sudo;任务文件若含密码视为机密。
90
+ - 详见 [SECURITY.md](SECURITY.md)
91
+
92
+ 配置示例:
81
93
 
82
94
  ```yaml
83
95
  parallel: 10
84
96
  ssh_timeout: 30
85
- verify_host_key: never
97
+ verify_host_key: accept_new
86
98
  retries: 0
87
99
  ```
88
100
 
89
- 重试策略示例:`retry_policy.example.json`
90
-
91
101
  ## 示例
92
102
 
93
103
  ```bash
@@ -110,11 +120,11 @@ bundle exec rubocop
110
120
 
111
121
  ## 贡献
112
122
 
113
- Fork → 功能分支 → 测试通过 → PR。提交信息遵循 [Conventional Commits](https://www.conventionalcommits.org/)。
123
+ 见 [CONTRIBUTING.md](CONTRIBUTING.md)。Fork → 功能分支 → 测试通过 → PR。提交信息遵循 [Conventional Commits](https://www.conventionalcommits.org/)。
114
124
 
115
125
  ## 许可证
116
126
 
117
- [MIT](https://opensource.org/licenses/MIT)
127
+ [MIT](LICENSE) · [变更记录](CHANGELOG.md) · [安全策略](SECURITY.md) · [贡献指南](CONTRIBUTING.md)
118
128
 
119
129
  ## 链接
120
130
 
data/README_EN.md CHANGED
@@ -26,7 +26,7 @@ Define hosts and tasks in Ruby DSL; execute deployments and configuration across
26
26
  - Agentless SSH execution with multi-host concurrency
27
27
  - Ruby DSL: hosts, roles, tasks; Chef-style resources (`package`, `service`, `template`, `sync`, etc.)
28
28
  - File upload, ERB templates, directory sync (optional rsync fast path)
29
- - Dry-run, JSON output, retry/timeout policies
29
+ - Dry-run, JSON output/`--report`, `--tags` / `--extra-vars`, `wait_*` / `on_failure`, retry/timeout policies
30
30
 
31
31
  **Stack**: Ruby, Thor, net-ssh, concurrent-ruby
32
32
 
@@ -77,17 +77,28 @@ Common flags: `--limit web01`, `--parallel 5`, `--format json`, `--retries 3`. S
77
77
 
78
78
  ## Configuration
79
79
 
80
- Place `.kdeploy.yml` in your project (walks up from CWD):
80
+ Place `.kdeploy.yml` in your project (walks up from CWD). Retry policy examples: `retry_policy.example.json`
81
+
82
+ ## Non-goals
83
+
84
+ Kdeploy is **not** an Ansible/Chef convergence engine and has **no** Web UI / agent. See [docs/REQUIREMENTS.md](docs/REQUIREMENTS.md).
85
+
86
+ ## Security
87
+
88
+ - Default `verify_host_key` is `accept_new`. Prefer `always` for locked fleets; use `never` only for ephemeral labs.
89
+ - Prefer SSH keys and NOPASSWD sudo; treat task files with passwords as secrets.
90
+ - See [SECURITY.md](SECURITY.md)
91
+
92
+ Example:
81
93
 
82
94
  ```yaml
83
95
  parallel: 10
84
96
  ssh_timeout: 30
85
- verify_host_key: never
97
+ # Production:
98
+ verify_host_key: accept_new
86
99
  retries: 0
87
100
  ```
88
101
 
89
- Retry policy examples: `retry_policy.example.json`
90
-
91
102
  ## Samples
92
103
 
93
104
  ```bash
@@ -110,11 +121,11 @@ Requirements and acceptance criteria: [docs/REQUIREMENTS.md](docs/REQUIREMENTS.m
110
121
 
111
122
  ## Contributing
112
123
 
113
- Fork → feature branch → tests pass → PR. Use [Conventional Commits](https://www.conventionalcommits.org/).
124
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Fork → feature branch → tests pass → PR. Use [Conventional Commits](https://www.conventionalcommits.org/).
114
125
 
115
126
  ## License
116
127
 
117
- [MIT](https://opensource.org/licenses/MIT)
128
+ [MIT](LICENSE) · [Changelog](CHANGELOG.md) · [Security](SECURITY.md) · [Contributing](CONTRIBUTING.md)
118
129
 
119
130
  ## Links
120
131
 
data/SECURITY.md ADDED
@@ -0,0 +1,37 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ | Version | Supported |
6
+ | ------- | --------- |
7
+ | 1.4.x | Yes |
8
+ | 1.3.x | Best-effort |
9
+ | < 1.3 | Best-effort only |
10
+
11
+ ## Reporting a vulnerability
12
+
13
+ Please open a [GitHub Security Advisory](https://github.com/kevin197011/kdeploy/security/advisories/new)
14
+ or email the maintainer listed in the gemspec. Do **not** file a public issue for
15
+ unfixed vulnerabilities that could lead to remote code execution or credential theft.
16
+
17
+ We aim to acknowledge reports within 7 days.
18
+
19
+ ## Important defaults
20
+
21
+ Kdeploy defaults `verify_host_key` to `accept_new` (first-seen host keys are
22
+ recorded). For locked-down fleets prefer `always`. Use `never` only on ephemeral
23
+ lab hosts:
24
+
25
+ ```yaml
26
+ verify_host_key: accept_new # default
27
+ # verify_host_key: always
28
+ # verify_host_key: never # labs only
29
+ ```
30
+
31
+ Also prefer SSH keys and NOPASSWD sudo over embedding `password` / `sudo_password`
32
+ in task files. Treat deploy task files as secrets if they contain credentials.
33
+
34
+ ## Scope
35
+
36
+ Kdeploy executes operator-supplied shell over SSH. Malicious task files are out of
37
+ scope; protect who can edit and run your deploy definitions.
@@ -57,14 +57,19 @@ module Kdeploy
57
57
  method_option :step_timeout, type: :numeric, desc: 'Per-step execution timeout seconds (default: none)'
58
58
  method_option :retry_policy, type: :string, desc: 'Retry policy JSON to override config file'
59
59
  method_option :retry_policy_file, type: :string, desc: 'Retry policy JSON file to override config file'
60
+ method_option :report, type: :string, desc: 'Write JSON execution report to PATH'
61
+ method_option :tags, type: :string, desc: 'Only run tasks with these tags (comma-separated)'
62
+ method_option :extra_vars, type: :string, desc: 'Extra vars key=value,key2=value2 (overrides DSL vars)'
60
63
  def execute(task_file, task_name = nil)
61
64
  @task_file_dir = File.dirname(File.expand_path(task_file))
62
65
  Configuration.load_for_execute(task_dir: @task_file_dir)
63
66
  show_banner_once
64
67
  load_task_file(task_file)
68
+ apply_extra_vars!
65
69
 
66
70
  tasks_to_run = determine_tasks(task_name)
67
71
  all_results = execute_tasks(tasks_to_run)
72
+ write_report_file(all_results) if options[:report]
68
73
 
69
74
  # Exit non-zero if any executed host failed or a task could not run.
70
75
  exit 1 if any_failed?(all_results) || @task_errors
@@ -82,6 +87,8 @@ module Kdeploy
82
87
 
83
88
  def load_task_file(file)
84
89
  validate_task_file(file)
90
+ # Avoid leaking hosts/roles/tasks across execute invocations in one process.
91
+ self.class.clear_inventory!
85
92
  # Use module_eval with top-level binding to keep heredoc compatible
86
93
  self.class.module_eval(File.read(file), file)
87
94
  rescue StandardError => e
@@ -125,7 +132,8 @@ module Kdeploy
125
132
  end
126
133
 
127
134
  def print_dry_run_for_host(name, config, task_name, formatter)
128
- commands = self.class.kdeploy_tasks[task_name][:block].call
135
+ plan = Kdeploy::DSL.normalize_plan(self.class.kdeploy_tasks[task_name][:block].call)
136
+ commands = plan[:commands]
129
137
  output = commands.map { |cmd| formatter.format_command_for_dry_run(cmd) }.join("\n")
130
138
  title = "#{name} (#{config[:ip]})"
131
139
  puts formatter.format_dry_run_box(title, output)
@@ -184,7 +192,7 @@ module Kdeploy
184
192
  formatter.format_template_steps(steps, shown)
185
193
  when :sync
186
194
  formatter.format_sync_steps(steps, shown)
187
- when :run
195
+ when :run, :wait_port, :wait_http
188
196
  formatter.format_run_steps(steps, shown)
189
197
  else
190
198
  []
@@ -228,14 +236,26 @@ module Kdeploy
228
236
  end
229
237
 
230
238
  def determine_tasks(task_name)
231
- if task_name
232
- sym = task_name.to_sym
233
- raise TaskNotFoundError, task_name unless self.class.kdeploy_tasks.key?(sym)
239
+ selected =
240
+ if task_name
241
+ sym = task_name.to_sym
242
+ raise TaskNotFoundError, task_name unless self.class.kdeploy_tasks.key?(sym)
234
243
 
235
- [sym]
236
- else
237
- self.class.kdeploy_tasks.keys
244
+ [sym]
245
+ else
246
+ self.class.kdeploy_tasks.keys
247
+ end
248
+
249
+ return selected unless options[:tags]
250
+
251
+ wanted = options[:tags].split(',').map(&:strip)
252
+ tagged = self.class.tasks_matching_tags(wanted)
253
+ filtered = selected.select { |name| tagged.include?(name) }
254
+ if filtered.empty?
255
+ puts Kdeploy::Banner.show_error("No tasks matched tags: #{wanted.join(', ')}")
256
+ @task_errors = true
238
257
  end
258
+ filtered
239
259
  end
240
260
 
241
261
  def execute_tasks(tasks_to_run)
@@ -366,7 +386,8 @@ module Kdeploy
366
386
 
367
387
  def print_dry_run_json(hosts, task_name)
368
388
  tasks = self.class.kdeploy_tasks
369
- commands = tasks[task_name][:block].call
389
+ plan = Kdeploy::DSL.normalize_plan(tasks[task_name][:block].call)
390
+ commands = plan[:commands]
370
391
 
371
392
  payload = {
372
393
  task: task_name.to_s,
@@ -393,6 +414,7 @@ module Kdeploy
393
414
  command: step[:command],
394
415
  duration: step[:duration]
395
416
  }
417
+ out[:error] = step[:error] if step[:error]
396
418
 
397
419
  if step[:output].is_a?(Hash)
398
420
  out[:stdout] = step[:output][:stdout]
@@ -423,6 +445,10 @@ module Kdeploy
423
445
  fast: cmd[:fast],
424
446
  parallel: cmd[:parallel]
425
447
  }
448
+ when :wait_port
449
+ { type: 'wait_port', port: cmd[:port], timeout: cmd[:timeout], interval: cmd[:interval] }
450
+ when :wait_http
451
+ { type: 'wait_http', url: cmd[:url], timeout: cmd[:timeout], interval: cmd[:interval], code: cmd[:code] }
426
452
  else
427
453
  { type: cmd[:type].to_s }
428
454
  end
@@ -489,5 +515,34 @@ module Kdeploy
489
515
  rescue JSON::ParserError, Psych::SyntaxError => e
490
516
  raise ArgumentError, "retry_policy parse error: #{e.message}"
491
517
  end
518
+
519
+ def apply_extra_vars!
520
+ raw = options[:extra_vars]
521
+ return if raw.nil? || raw.strip.empty?
522
+
523
+ self.class.merge_extra_vars!(parse_extra_vars(raw))
524
+ end
525
+
526
+ def parse_extra_vars(raw)
527
+ raw.split(',').each_with_object({}) do |pair, acc|
528
+ key, value = pair.split('=', 2)
529
+ raise ArgumentError, "Invalid --extra-vars pair: #{pair}" if key.nil? || value.nil?
530
+
531
+ acc[key.strip.to_sym] = value.strip
532
+ end
533
+ end
534
+
535
+ def write_report_file(all_results)
536
+ path = options[:report]
537
+ payload = {
538
+ version: Kdeploy::VERSION,
539
+ generated_at: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
540
+ tasks: all_results.transform_keys(&:to_s).transform_values do |task_results|
541
+ task_results.transform_values { |r| serialize_host_result(r) }
542
+ end
543
+ }
544
+ File.write(path, JSON.pretty_generate(payload))
545
+ warn "Report written: #{path}" unless options[:format] == 'json'
546
+ end
492
547
  end
493
548
  end
@@ -35,6 +35,9 @@ module Kdeploy
35
35
  #{@pastel.dim(' --retries N')} Retry count for network operations (default: 0; overridden by .kdeploy.yml)
36
36
  #{@pastel.dim(' --retry-delay SECONDS')} Retry delay seconds (default: 1; overridden by .kdeploy.yml)
37
37
  #{@pastel.dim(' --retry-on-nonzero')} Retry commands on nonzero exit status (default: false; overridden by .kdeploy.yml)
38
+ #{@pastel.dim(' --report PATH')} Write JSON report to PATH
39
+ #{@pastel.dim(' --tags TAGS')} Only run tasks matching tags
40
+ #{@pastel.dim(' --extra-vars K=V,...')} Set/override DSL vars
38
41
  #{@pastel.dim(' --timeout SECONDS')} Per-host execution timeout seconds (default: none; overridden by .kdeploy.yml)
39
42
  #{@pastel.dim(' --step-timeout SECONDS')} Per-step execution timeout seconds (default: none; overridden by .kdeploy.yml)
40
43
  #{@pastel.dim(' --retry-policy JSON')} Retry policy JSON (overrides .kdeploy.yml)
@@ -68,6 +71,9 @@ module Kdeploy
68
71
  #{@pastel.dim('# Machine-readable output')}
69
72
  #{@pastel.bright_cyan('kdeploy execute deploy.rb deploy_web --format json --no-banner')}
70
73
 
74
+ #{@pastel.dim('# Tags, vars, and JSON report')}
75
+ #{@pastel.bright_cyan('kdeploy execute deploy.rb --tags web --extra-vars app=demo --report out.json')}
76
+
71
77
  #{@pastel.dim('# Retry transient network failures')}
72
78
  #{@pastel.bright_cyan('kdeploy execute deploy.rb deploy_web --retries 3 --retry-delay 1')}
73
79
  EXAMPLES
@@ -7,7 +7,7 @@ module Kdeploy
7
7
  class Configuration
8
8
  DEFAULT_PARALLEL = 10
9
9
  DEFAULT_SSH_TIMEOUT = 30
10
- DEFAULT_VERIFY_HOST_KEY = :never
10
+ DEFAULT_VERIFY_HOST_KEY = :accept_new
11
11
  DEFAULT_RETRIES = 0
12
12
  DEFAULT_RETRY_DELAY = 1
13
13
  DEFAULT_HOST_TIMEOUT = nil
@@ -49,7 +49,12 @@ module Kdeploy
49
49
  config_path ||= find_config_file
50
50
  return unless config_path && File.exist?(config_path)
51
51
 
52
- config = YAML.safe_load_file(config_path, permitted_classes: [Symbol])
52
+ raw = File.read(config_path)
53
+ config = begin
54
+ YAML.safe_load(raw, permitted_classes: [Symbol])
55
+ rescue ArgumentError
56
+ YAML.safe_load(raw, [Symbol])
57
+ end
53
58
  apply_config(config)
54
59
  rescue StandardError => e
55
60
  warn "Warning: Failed to load config from #{config_path}: #{e.message}"