choria-colt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fa21249d732edfc1a2391bd20dcfc33cd8eee373329f90ffbf95ad6e9feb0a5c
4
+ data.tar.gz: 1ee9fbda91dd047ca932e8d117b65c2aab0252fdc96c30bf4b3be7a841ef3af5
5
+ SHA512:
6
+ metadata.gz: d6b9e92533f0911ba97d3032f46a6d6b7c8f779317cf471dae1ee70a5457cca53ee62b1410a8ecba691cba504dadc3df54f92f9dfb4a63238e11cbc16c04d261
7
+ data.tar.gz: cc601e0c25747af5ff818a32eff04138138a66277a2daf430016ac5755a2906a3016f918fe43c23cffd3a5a3a66cc40267267a3cfe65bef211b178c1cc5ca8ef
data/.rubocop.yml ADDED
@@ -0,0 +1,38 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.5
3
+ Exclude:
4
+ - 'bin/colt'
5
+ - 'vendor/**/*'
6
+ NewCops: enable
7
+
8
+ Metrics/AbcSize:
9
+ Max: 20
10
+
11
+ Metrics/MethodLength:
12
+ Max: 17
13
+
14
+ Layout/LineLength:
15
+ Enabled: false
16
+
17
+ Style/Documentation:
18
+ Enabled: false
19
+
20
+ Style/FrozenStringLiteralComment:
21
+ Enabled: false
22
+
23
+ Style/NumericLiterals:
24
+ Enabled: false
25
+
26
+ Style/TrailingCommaInArrayLiteral:
27
+ EnforcedStyleForMultiline: comma
28
+
29
+ Style/TrailingCommaInHashLiteral:
30
+ EnforcedStyleForMultiline: comma
31
+
32
+ Layout/HashAlignment:
33
+ EnforcedHashRocketStyle: table
34
+
35
+ Metrics/BlockLength:
36
+ Exclude:
37
+ - '*.gemspec'
38
+ - 'spec/**/*_spec.rb'
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in choria-colt.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # Choria::Colt
2
+
3
+ This _gem_ provides:
4
+ * `colt` executable, a [Bolt](https://puppet.com/docs/bolt/latest/bolt.html)-like command line interface to run _Bolt_ task through [Choria](https://puppet.com/docs/bolt/latest/bolt.html)
5
+ * helpers functions and classes to run _Bolt_ tasks through _Choria_ infrastructure from any _ruby_ code
6
+
7
+ ## Usage
8
+
9
+ ### CLI
10
+
11
+ ```shell-session
12
+ $ colt tasks run exec=hostname -t vm012345.example.net,vm543210.example.net
13
+ ```
14
+
15
+ ### Code
16
+
17
+ ```ruby
18
+ require 'choria/colt'
19
+
20
+ targets = [ 'vm012345.example.net' ]
21
+ task_name = 'exec'
22
+ task_input = { 'command' => 'hostname' }
23
+
24
+ colt = Choria::Colt.new(logger: Logger.new($stdout))
25
+ results = colt.run_bolt_task task_name, input: task_input, targets: targets
26
+
27
+ $stdout.puts JSON.pretty_generate(results)
28
+ ```
29
+
30
+ ## Installation
31
+
32
+ Add this line to your application's Gemfile:
33
+
34
+ ```ruby
35
+ gem 'choria-colt'
36
+ ```
37
+
38
+ And then execute:
39
+
40
+ $ bundle install
41
+
42
+ Or install it yourself as:
43
+
44
+ $ gem install choria-colt
45
+
46
+ ## Development
47
+
48
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
49
+
50
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
51
+
52
+ ## Contributing
53
+
54
+ Bug reports and pull requests are welcome on GitHub at https://github.com/opus-codium/choria-colt.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rubocop/rake_task'
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/choria/colt/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'choria-colt'
7
+ spec.version = Choria::Colt::VERSION
8
+ spec.authors = ['Romuald Conty']
9
+ spec.email = ['romuald@opus-codium.fr']
10
+
11
+ spec.summary = 'Bolt-like CLI to run Bolt tasks, through Choria'
12
+ spec.description = 'Colt eases the Bolt tasks run through Choria'
13
+ spec.homepage = 'https://github.com/opus-codium/choria-colt'
14
+ spec.required_ruby_version = '>= 2.5.0'
15
+
16
+ spec.metadata['homepage_uri'] = spec.homepage
17
+ spec.metadata['source_code_uri'] = 'https://github.com/opus-codium/choria-colt'
18
+ spec.metadata['changelog_uri'] = 'https://github.com/opus-codium/choria-colt/CHANGELOG.md'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ spec.add_dependency 'choria-mcorpc-support'
32
+ spec.add_dependency 'deep_merge'
33
+ spec.add_dependency 'puppet'
34
+ spec.add_dependency 'thor'
35
+
36
+ spec.add_development_dependency 'rake'
37
+ spec.add_development_dependency 'rspec'
38
+ spec.add_development_dependency 'rubocop'
39
+ #spec.add_development_dependency 'byebug'
40
+
41
+ # For more information and examples about making a new gem, check out our
42
+ # guide at: https://bundler.io/guides/creating_gem.html
43
+ spec.metadata['rubygems_mfa_required'] = 'true'
44
+ end
data/exe/colt ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'choria/colt/cli'
4
+
5
+ Choria::Colt::CLI.start ARGV
@@ -0,0 +1,33 @@
1
+ require 'yaml'
2
+
3
+ module Choria
4
+ class Colt
5
+ class Cache
6
+ def initialize(path:, force_refresh: false)
7
+ @path = path
8
+ @data = YAML.safe_load File.read(@path)
9
+ @clean = true unless force_refresh
10
+ rescue Errno::ENOENT
11
+ @clean = false
12
+ end
13
+
14
+ def dirty?
15
+ !clean?
16
+ end
17
+
18
+ def clean?
19
+ @clean
20
+ end
21
+
22
+ def load
23
+ @data
24
+ end
25
+
26
+ def save(data)
27
+ @data = data
28
+ File.write @path, data.to_yaml
29
+ @clean = true
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,42 @@
1
+ require 'thor'
2
+ require 'choria/colt/cli'
3
+
4
+ module Choria
5
+ class Colt
6
+ # Workaround some, still unfixed, Thor behaviors
7
+ #
8
+ # This class extends ::Thor class to
9
+ # - exit with status code sets to `1` on Thor failure (e.g. missing required option)
10
+ # - exit with status code sets to `1` when user calls `msync` (or a subcommand) without required arguments
11
+ # - show subcommands help using `msync subcommand --help`
12
+ class Thor < ::Thor
13
+ def self.start(*args)
14
+ if (Thor::HELP_MAPPINGS & ARGV).any? && subcommands.none? { |command| command.start_with?(ARGV[0]) }
15
+ Thor::HELP_MAPPINGS.each do |cmd|
16
+ if (match = ARGV.delete(cmd))
17
+ ARGV.unshift match
18
+ end
19
+ end
20
+ end
21
+ super
22
+ end
23
+
24
+ desc '_invalid_command_call', 'Invalid command', hide: true
25
+ def _invalid_command_call
26
+ self.class.new.help
27
+ exit 1
28
+ end
29
+ default_task :_invalid_command_call
30
+
31
+ def self.exit_on_failure?
32
+ true
33
+ end
34
+
35
+ def self.is_thor_reserved_word?(word, type) # rubocop:disable Naming/PredicateName
36
+ return false if word == 'run'
37
+
38
+ super
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,109 @@
1
+ require 'choria/colt'
2
+ require 'choria/colt/cli/thor'
3
+
4
+ require 'json'
5
+ require 'logger'
6
+
7
+ module Choria
8
+ class Colt
9
+ class CLI < Thor
10
+ class Tasks < Thor
11
+ # BOLT: desc 'run <task name> [parameters] {--targets TARGETS | --query QUERY | --rerun FILTER} [options]', 'Run a Bolt task'
12
+ desc 'run <task name> [parameters] --targets TARGETS [options]', 'Run a Bolt task'
13
+ long_desc <<~DESC
14
+ Run a task on the specified targets.
15
+
16
+ Parameters take the form parameter=value.
17
+ DESC
18
+ option :targets,
19
+ aliases: ['--target', '-t'],
20
+ desc: 'Identifies the targets of the command.',
21
+ required: true
22
+ def run(*args)
23
+ input = extract_task_parameters_from_args(args)
24
+
25
+ raise Thor::Error, 'Task name is required' if args.empty?
26
+ raise Thor::Error, "Too many arguments: #{args}" unless args.count == 1
27
+
28
+ task_name = args.shift
29
+
30
+ targets = options['targets'].split ','
31
+ targets = nil if options['targets'] == 'all'
32
+
33
+ results = colt.run_bolt_task task_name, input: input, targets: targets
34
+ $stdout.puts JSON.pretty_generate(results)
35
+ rescue Choria::Orchestrator::Error => e
36
+ raise Thor::Error, "#{e.class}: #{e}"
37
+ end
38
+
39
+ desc 'show [task name] [options]', 'Show available tasks and task documentation'
40
+ long_desc <<~DESC
41
+ Show available tasks and task documentation.
42
+
43
+ Omitting the name of a task will display a list of tasks available
44
+ in the Bolt project.
45
+
46
+ Providing the name of a task will display detailed documentation for
47
+ the task, including a list of available parameters.
48
+ DESC
49
+ option :environment,
50
+ aliases: ['-E'],
51
+ desc: 'Puppet environment to grab tasks from',
52
+ default: 'production'
53
+ def show(*tasks_names)
54
+ environment = options['environment']
55
+ cache_directory = File.expand_path('.cache/colt/tasks')
56
+ FileUtils.mkdir_p cache_directory
57
+ # TODO: Support multiple infrastructure
58
+ cache = Cache.new(path: File.join(cache_directory, "#{environment}.yaml"))
59
+
60
+ tasks = colt.tasks(environment: environment, cache: cache)
61
+
62
+ if tasks_names.nil?
63
+ show_tasks_summary
64
+ else
65
+ tasks_names.each { |task_name| show_task_details(task_name, tasks) }
66
+ end
67
+ end
68
+
69
+ no_commands do
70
+ def colt
71
+ @colt ||= Choria::Colt.new logger: Logger.new($stdout)
72
+ end
73
+
74
+ def extract_task_parameters_from_args(args)
75
+ parameters = args.grep(/^\w+=/)
76
+ args.reject! { |arg| arg =~ /^\w+=/ }
77
+
78
+ parameters.map do |parameter|
79
+ key, value = parameter.split('=')
80
+ [key, value]
81
+ end.to_h
82
+ end
83
+
84
+ def show_tasks_summary(tasks)
85
+ tasks.reject! { |_task, metadata| metadata['metadata']['private'] }
86
+
87
+ puts <<~OUTPUT
88
+ Tasks
89
+ #{tasks.map { |task, metadata| "#{task}#{' ' * (60 - task.size)}#{metadata['metadata']['description']}" }.join("\n").gsub(/^/, ' ')}
90
+ OUTPUT
91
+ end
92
+
93
+ def show_task_details(task_name, tasks)
94
+ metadata = tasks[task_name]
95
+ puts <<~OUTPUT
96
+ Task: '#{task_name}'
97
+ #{metadata['metadata']['description']}
98
+
99
+ #{metadata}
100
+ OUTPUT
101
+ end
102
+ end
103
+ end
104
+
105
+ desc 'tasks', 'Show and run Bolt tasks.'
106
+ subcommand 'tasks', CLI::Tasks
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Choria
4
+ class Colt
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'choria/colt/cache'
4
+ require 'choria/colt/version'
5
+ require 'choria/orchestrator'
6
+ require 'choria/orchestrator/task'
7
+
8
+ require 'logger'
9
+
10
+ module Choria
11
+ class Colt
12
+ class Error < StandardError; end
13
+
14
+ attr_reader :logger, :orchestrator
15
+
16
+ def initialize(logger: nil)
17
+ @logger = logger
18
+
19
+ @orchestrator = Choria::Orchestrator.new logger: @logger
20
+ end
21
+
22
+ def run_bolt_task(task_name, input: {}, targets: nil)
23
+ logger.debug "Instantiate task '#{task_name}' and validate input"
24
+ task = Choria::Orchestrator::Task.new(task_name, input: input, orchestrator: orchestrator)
25
+
26
+ orchestrator.run(task, targets: targets)
27
+ task.wait
28
+ task.results
29
+ rescue Choria::Orchestrator::Error => e
30
+ logger.error e.message
31
+ raise
32
+ end
33
+
34
+ def tasks(environment:, cache: nil)
35
+ tasks_names = orchestrator.tasks_support.tasks(environment).map do |task|
36
+ task['name']
37
+ end
38
+
39
+ def tasks_metadata(tasks, environment)
40
+ tasks.map do |task|
41
+ logger.debug "Fetching metadata for task '#{task}' (environment: '#{environment}')"
42
+ metadata = orchestrator.tasks_support.task_metadata(task, environment)
43
+ [task, metadata]
44
+ end.to_h
45
+ end
46
+
47
+ return tasks_metadata(tasks_names, environment) if cache.nil?
48
+
49
+ cached_tasks = cache.load
50
+ return cached_tasks if cache.clean? && cached_tasks.keys.sort == tasks_names.sort
51
+
52
+ updated_tasks = tasks_metadata(tasks_names, environment)
53
+ cache.save updated_tasks
54
+
55
+ updated_tasks
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,59 @@
1
+ module Choria
2
+ class Orchestrator
3
+ class Task
4
+ class Error < Orchestrator::Error; end
5
+
6
+ attr_reader :name, :input, :environment, :rpc_results
7
+ attr_accessor :rpc_responses
8
+
9
+ def initialize(name, orchestrator:, input: {}, environment: 'production')
10
+ @name = name
11
+ @input = input
12
+ @environment = environment
13
+ @orchestrator = orchestrator
14
+
15
+ validate_inputs
16
+ end
17
+
18
+ def metadata
19
+ @metadata ||= _metadata
20
+ end
21
+
22
+ def files
23
+ metadata['files'].to_json
24
+ end
25
+
26
+ def wait
27
+ task_ids = rpc_responses.map { |res| res[:body][:data][:task_id] }.uniq
28
+ raise NotImplementedError, "Multiple task IDs: #{task_ids}" unless task_ids.count == 1
29
+
30
+ @rpc_results = @orchestrator.wait_results task_id: task_ids.first
31
+ end
32
+
33
+ def results
34
+ @rpc_results.map do |res|
35
+ raise NotImplementedError, 'What to do when res[:data][:stderr] contains something?' unless res[:data][:stderr].empty?
36
+
37
+ res[:result] = JSON.parse res[:data][:stdout]
38
+ res[:data].delete :stderr
39
+ res[:data].delete :stdout
40
+ res
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def _metadata
47
+ # puts 'Retrieving task metadata for task %s from the Puppet Server' % task if verbose
48
+ @orchestrator.tasks_support.task_metadata(@name, @environment)
49
+ rescue RuntimeError => e
50
+ raise Error, e.message
51
+ end
52
+
53
+ def validate_inputs
54
+ ok, reason = @orchestrator.tasks_support.validate_task_inputs(@input, metadata)
55
+ raise Error, reason.sub(/^\n/, '') unless ok
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,99 @@
1
+ require 'English'
2
+ require 'mcollective'
3
+
4
+ module Choria
5
+ class Orchestrator
6
+ class Error < StandardError; end
7
+ class DiscoverError < Error; end
8
+
9
+ include MCollective::RPC
10
+
11
+ attr_reader :logger
12
+
13
+ def initialize(logger:)
14
+ @logger = logger
15
+
16
+ configfile ||= MCollective::Util.config_file_for_user
17
+ MCollective::Config.instance.loadconfig(configfile)
18
+ end
19
+
20
+ def tasks_support
21
+ @tasks_support ||= MCollective::Util::Choria.new.tasks_support
22
+ end
23
+
24
+ def run(task, targets: nil, verbose: false)
25
+ logger.debug "Running task: '#{task.name}' (targets: #{targets.nil? ? 'all' : targets})"
26
+ rpc_client.progress = verbose
27
+
28
+ targets&.each { |target| rpc_client.identity_filter target }
29
+
30
+ raise DiscoverError, 'No request sent, no node discovered' if rpc_client.discover.size.zero?
31
+
32
+ logger.info "Attempting to download and run task '#{task.name}' on #{rpc_client.discover.size} nodes"
33
+
34
+ rpc_client.download(task: task.name, files: task.files, verbose: verbose)
35
+
36
+ # TODO: Extract error from 'rpc' (see MCollective::RPC#printrpc)
37
+
38
+ responses = []
39
+ rpc_client.run_no_wait(task: task.name, files: task.files, input: task.input.to_json, verbose: verbose) do |response|
40
+ logger.debug " Response: '#{response}'"
41
+ responses << response
42
+ end
43
+
44
+ # TODO: Include stats in logs when logger will be available (see MCollective::RPC#printrpcstats)
45
+
46
+ task.rpc_responses = responses
47
+ end
48
+
49
+ def wait_results(task_id:)
50
+ raise 'Task ID is required!' if task_id.nil?
51
+
52
+ loop do
53
+ task_status_results = rpc_client.task_status(task_id: task_id).map(&:results)
54
+ logger.debug "Task ##{task_id} status: #{task_status_results}"
55
+ break if task_completed? task_status_results
56
+ end
57
+
58
+ task_status_results
59
+ end
60
+
61
+ def task_completed?(results)
62
+ results.each do |result|
63
+ return false unless result[:data][:completed]
64
+ end
65
+
66
+ true
67
+ end
68
+
69
+ def validate_rpc_result(result)
70
+ raise Error, "The RPC agent returned an error: #{result[:statusmsg]}" unless (result[:statuscode]).zero?
71
+ end
72
+
73
+ private
74
+
75
+ def rpc_client
76
+ @rpc_client ||= rpcclient('bolt_tasks', options: rpc_options)
77
+ end
78
+
79
+ def rpc_options
80
+ {
81
+ verbose: false,
82
+ disctimeout: nil,
83
+ timeout: 5,
84
+ config: '/etc/choria/client.conf',
85
+ collective: 'mcollective',
86
+ discovery_method: nil,
87
+ discovery_options: [],
88
+ filter: {
89
+ 'fact' => [], 'cf_class' => [], 'agent' => [], 'identity' => [], 'compound' => []
90
+ },
91
+ progress_bar: false,
92
+ mcollective_limit_targets: false,
93
+ batch_size: nil,
94
+ batch_sleep_time: 1,
95
+ output_format: :json,
96
+ }
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,6 @@
1
+ module Choria
2
+ module Colt
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: choria-colt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Romuald Conty
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-03-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: choria-mcorpc-support
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: deep_merge
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: puppet
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Colt eases the Bolt tasks run through Choria
112
+ email:
113
+ - romuald@opus-codium.fr
114
+ executables:
115
+ - colt
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".rubocop.yml"
120
+ - Gemfile
121
+ - README.md
122
+ - Rakefile
123
+ - choria-colt.gemspec
124
+ - exe/colt
125
+ - lib/choria/colt.rb
126
+ - lib/choria/colt/cache.rb
127
+ - lib/choria/colt/cli.rb
128
+ - lib/choria/colt/cli/thor.rb
129
+ - lib/choria/colt/version.rb
130
+ - lib/choria/orchestrator.rb
131
+ - lib/choria/orchestrator/task.rb
132
+ - sig/choria/colt.rbs
133
+ homepage: https://github.com/opus-codium/choria-colt
134
+ licenses: []
135
+ metadata:
136
+ homepage_uri: https://github.com/opus-codium/choria-colt
137
+ source_code_uri: https://github.com/opus-codium/choria-colt
138
+ changelog_uri: https://github.com/opus-codium/choria-colt/CHANGELOG.md
139
+ rubygems_mfa_required: 'true'
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 2.5.0
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubygems_version: 3.1.2
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Bolt-like CLI to run Bolt tasks, through Choria
159
+ test_files: []