chef-rundeck2 0.1.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 +7 -0
- data/.gitignore +8 -0
- data/.gitlab-ci.yml +38 -0
- data/.rubocop.yml +12 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +76 -0
- data/LICENSE.txt +21 -0
- data/README.md +98 -0
- data/Rakefile +6 -0
- data/bin/console +17 -0
- data/bin/setup +8 -0
- data/chef-rundeck2.gemspec +44 -0
- data/config/auth.json +13 -0
- data/config/config.json +13 -0
- data/config/projects.json +17 -0
- data/config/state.json +22 -0
- data/exe/chef-rundeck2 +14 -0
- data/lib/chef-rundeck/api.rb +215 -0
- data/lib/chef-rundeck/auth.rb +78 -0
- data/lib/chef-rundeck/chef.rb +193 -0
- data/lib/chef-rundeck/cli.rb +132 -0
- data/lib/chef-rundeck/config.rb +100 -0
- data/lib/chef-rundeck/helpers/configuration.rb +57 -0
- data/lib/chef-rundeck/state.rb +86 -0
- data/lib/chef-rundeck/util.rb +64 -0
- data/lib/chef-rundeck/version.rb +3 -0
- data/lib/chef-rundeck.rb +17 -0
- metadata +198 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
# rubocop: disable LineLength
|
3
|
+
#
|
4
|
+
# Gem Name:: chef-rundeck
|
5
|
+
# Module:: Auth
|
6
|
+
#
|
7
|
+
# Copyright (C) 2016 Brian Dwyer - Intelligent Digital Services
|
8
|
+
#
|
9
|
+
# All rights reserved - Do Not Redistribute
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'chef-rundeck/config'
|
13
|
+
require 'chef-rundeck/util'
|
14
|
+
require 'digest'
|
15
|
+
|
16
|
+
module ChefRunDeck
|
17
|
+
# => Authorization Module
|
18
|
+
module Auth
|
19
|
+
extend self
|
20
|
+
|
21
|
+
#############################
|
22
|
+
# => Authorization <= #
|
23
|
+
#############################
|
24
|
+
|
25
|
+
# => This holds the Authorization State
|
26
|
+
attr_accessor :auth
|
27
|
+
|
28
|
+
def auth
|
29
|
+
# => Define Authorization
|
30
|
+
@auth ||= reset!
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset!
|
34
|
+
# => Reset Authorization
|
35
|
+
@auth = { 'roles' => [] }
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse(user = nil)
|
39
|
+
# => Try to Find the User and their Authorization
|
40
|
+
auth = Util.parse_json_config(Config.auth_file, false)
|
41
|
+
return reset! unless auth && auth[user]
|
42
|
+
@auth = auth[user]
|
43
|
+
end
|
44
|
+
|
45
|
+
def admin?
|
46
|
+
# => Check if a User is an Administrator
|
47
|
+
auth['roles'].any? { |x| x.casecmp('admin') == 0 }
|
48
|
+
end
|
49
|
+
|
50
|
+
def creator?(node)
|
51
|
+
# => Grab the Node-State Object
|
52
|
+
existing = State.find_state(node)
|
53
|
+
return false unless existing
|
54
|
+
# => Check if Auth User was the Node-State Creator
|
55
|
+
existing[:creator].to_s.casecmp(Config.query_params['auth_user'].to_s) == 0
|
56
|
+
end
|
57
|
+
|
58
|
+
# => Validate the User's Authentication Key ## TODO: Use this, passthrough from a RunDeck Option Field
|
59
|
+
def key?
|
60
|
+
# => We store a SHA512 Hex Digest of the Key
|
61
|
+
return false unless Config.query_params['auth_key']
|
62
|
+
Digest::SHA512.hexdigest(Config.query_params['auth_key']) == auth['auth_key']
|
63
|
+
end
|
64
|
+
|
65
|
+
# => TODO: Project-Based Validation
|
66
|
+
def project_admin?(project = nil)
|
67
|
+
return false unless project.is_a?(Array)
|
68
|
+
# => parse_auth.include?(user) && parse_auth[user]['roles'].any? { |r| ['admin', project].include? r.to_s.downcase }
|
69
|
+
auth['roles'].any? { |r| ['admin', project].include? r.to_s.downcase }
|
70
|
+
end
|
71
|
+
|
72
|
+
# => Role-Based Administration
|
73
|
+
def role_admin?(run_list = nil)
|
74
|
+
return false unless run_list.is_a?(Array)
|
75
|
+
run_list.empty? || auth['roles'].any? { |role| run_list.any? { |r| r =~ /role\[#{role}\]/i } }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
# rubocop: disable LineLength, MethodLength
|
3
|
+
#
|
4
|
+
# Gem Name:: chef-rundeck
|
5
|
+
# Module:: Chef
|
6
|
+
#
|
7
|
+
# Copyright (C) 2016 Brian Dwyer - Intelligent Digital Services
|
8
|
+
#
|
9
|
+
# All rights reserved - Do Not Redistribute
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'chef-api'
|
13
|
+
require 'chef-rundeck/config'
|
14
|
+
|
15
|
+
module ChefRunDeck
|
16
|
+
# => This is the Chef module. It interacts with the Chef server
|
17
|
+
module Chef # rubocop: disable ModuleLength
|
18
|
+
extend self
|
19
|
+
# => Include Modules
|
20
|
+
include ChefAPI::Resource
|
21
|
+
|
22
|
+
#########################
|
23
|
+
# => ChefAPI <= #
|
24
|
+
#########################
|
25
|
+
|
26
|
+
def api_client
|
27
|
+
# => Configure a Chef API Client
|
28
|
+
ChefAPI.endpoint = Config.chef_api_endpoint
|
29
|
+
ChefAPI.client = Config.chef_api_client
|
30
|
+
ChefAPI.key = Config.chef_api_client_key
|
31
|
+
end
|
32
|
+
|
33
|
+
def admin_api_client
|
34
|
+
# => Configure an Administrative Chef API Client
|
35
|
+
ChefAPI.endpoint = Config.chef_api_endpoint
|
36
|
+
ChefAPI.client = Config.chef_api_admin
|
37
|
+
ChefAPI.key = Config.chef_api_admin_key
|
38
|
+
end
|
39
|
+
|
40
|
+
def reset!
|
41
|
+
# => Reset the Chef API Configuration
|
42
|
+
ChefAPI.reset!
|
43
|
+
# => Clear Transient Configuration
|
44
|
+
Config.clear(:rundeck)
|
45
|
+
end
|
46
|
+
|
47
|
+
# => Get Node
|
48
|
+
def get_node(node, casecomp = false)
|
49
|
+
node = Node.list.find { |n| n =~ /^#{node}$/i } if casecomp
|
50
|
+
return false unless Node.exists?(node)
|
51
|
+
Node.fetch(node)
|
52
|
+
end
|
53
|
+
|
54
|
+
# => Return Array List of Nodes
|
55
|
+
def list
|
56
|
+
Node.list
|
57
|
+
end
|
58
|
+
|
59
|
+
# => Return a Node's Run List
|
60
|
+
def run_list(node)
|
61
|
+
return [] unless Node.exists?(node)
|
62
|
+
Node.fetch(node).run_list
|
63
|
+
end
|
64
|
+
|
65
|
+
# => Delete a Node Object
|
66
|
+
def delete(node)
|
67
|
+
# => Make sure the Node Exists
|
68
|
+
return 'Node not found on Chef Server' unless Node.exists?(node)
|
69
|
+
|
70
|
+
# => Initialize the Admin API Client Settings
|
71
|
+
admin_api_client
|
72
|
+
|
73
|
+
# => Delete the Client & Node Object
|
74
|
+
Client.delete(node)
|
75
|
+
Node.delete(node)
|
76
|
+
'Client/Node Deleted from Chef Server'
|
77
|
+
end
|
78
|
+
|
79
|
+
#############################
|
80
|
+
# => Resource Provider <= #
|
81
|
+
#############################
|
82
|
+
|
83
|
+
#
|
84
|
+
# => Try to Parse Project-Specific Settings
|
85
|
+
#
|
86
|
+
def project_settings(project)
|
87
|
+
settings = Util.parse_json_config(Config.projects_file, false)
|
88
|
+
return {} unless settings && settings[project]
|
89
|
+
settings[project]
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# => Construct Query-Specific Configuration
|
94
|
+
#
|
95
|
+
def transient_settings # rubocop: disable AbcSize
|
96
|
+
# => Initialize any Project-Specific Settings
|
97
|
+
project = project_settings(Config.query_params['project'])
|
98
|
+
|
99
|
+
# => Build the Configuration
|
100
|
+
cfg = {}
|
101
|
+
cfg[:username] = Config.query_params['username'] || project['username'] || Config.rd_node_username
|
102
|
+
cfg[:pattern] = Config.query_params['pattern'] || project['pattern'] || '*:*'
|
103
|
+
cfg[:extras] = Util.serialize_csv(Config.query_params['extras']) || project['extras']
|
104
|
+
|
105
|
+
# => Make the Settings Available via the Config Object
|
106
|
+
Config.add(rundeck: cfg)
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# => Base Search Filter Definition
|
111
|
+
#
|
112
|
+
def default_search_filter
|
113
|
+
{
|
114
|
+
name: ['name'],
|
115
|
+
kernel_machine: ['kernel', 'machine'],
|
116
|
+
kernel_os: ['kernel', 'os'],
|
117
|
+
fqdn: ['fqdn'],
|
118
|
+
run_list: ['run_list'],
|
119
|
+
roles: ['roles'],
|
120
|
+
recipes: ['recipes'],
|
121
|
+
chef_environment: ['chef_environment'],
|
122
|
+
platform: ['platform'],
|
123
|
+
platform_version: ['platform_version'],
|
124
|
+
tags: ['tags'],
|
125
|
+
hostname: ['hostname']
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# => Parse Additional Filter Elements
|
131
|
+
#
|
132
|
+
# => Default Elements can be removed by passing them in here as null or empty
|
133
|
+
#
|
134
|
+
def search_filter_additions
|
135
|
+
attribs = {}
|
136
|
+
Array(Config.rundeck[:extras]).each do |attrib|
|
137
|
+
attribs[attrib.to_sym] = [attrib]
|
138
|
+
end
|
139
|
+
# => Return the Custom Filter Additions Hash
|
140
|
+
attribs
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# => Construct the Search Filter
|
145
|
+
#
|
146
|
+
def search_filter
|
147
|
+
# => Merge the Default Filter with Additions
|
148
|
+
default_search_filter.merge(search_filter_additions).reject { |_k, v| v.nil? || String(v).empty? }
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# => Define Extra Attributes for Resource Return
|
153
|
+
#
|
154
|
+
def custom_attributes(node)
|
155
|
+
attribs = {}
|
156
|
+
Array(Config.rundeck[:extras]).each do |attrib|
|
157
|
+
attribs[attrib.to_sym] = node[attrib].inspect
|
158
|
+
end
|
159
|
+
# => Return the Custom Attributes Hash
|
160
|
+
attribs
|
161
|
+
end
|
162
|
+
|
163
|
+
def search(pattern = '*:*') # rubocop: disable AbcSize
|
164
|
+
# => Initialize the Configuration
|
165
|
+
transient_settings
|
166
|
+
|
167
|
+
# => Pull in the Pattern
|
168
|
+
pattern = Config.rundeck[:pattern]
|
169
|
+
|
170
|
+
# => Execute the Chef Search
|
171
|
+
result = PartialSearch.query(:node, search_filter, pattern, start: 0)
|
172
|
+
|
173
|
+
# => Custom-Tailor the Resulting Objects
|
174
|
+
result.rows.collect do |node|
|
175
|
+
{
|
176
|
+
nodename: node['name'],
|
177
|
+
hostname: node['fqdn'] || node['hostname'],
|
178
|
+
osArch: node['kernel_machine'],
|
179
|
+
osFamily: node['platform'],
|
180
|
+
osName: node['platform'],
|
181
|
+
osVersion: node['platform_version'],
|
182
|
+
description: node['name'],
|
183
|
+
roles: node['roles'].join(','),
|
184
|
+
recipes: node['recipes'].join(','),
|
185
|
+
tags: [node['roles'], node['recipes'], node['chef_environment'], node['tags']].flatten.join(','),
|
186
|
+
environment: node['chef_environment'],
|
187
|
+
editUrl: ::File.join(Config.chef_api_endpoint, 'nodes', node['name']),
|
188
|
+
username: Config.rundeck[:username]
|
189
|
+
}.merge(custom_attributes(node)).reject { |_k, v| v.nil? || String(v).empty? }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
# rubocop: disable LineLength, MethodLength, AbcSize
|
3
|
+
#
|
4
|
+
# Gem Name:: chef-rundeck
|
5
|
+
# ChefRunDeck:: CLI
|
6
|
+
#
|
7
|
+
# Copyright (C) 2016 Brian Dwyer - Intelligent Digital Services
|
8
|
+
#
|
9
|
+
# All rights reserved - Do Not Redistribute
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'mixlib/cli'
|
13
|
+
require 'chef-rundeck/config'
|
14
|
+
require 'chef-rundeck/util'
|
15
|
+
|
16
|
+
module ChefRunDeck
|
17
|
+
#
|
18
|
+
# => Chef-RunDeck Launcher
|
19
|
+
#
|
20
|
+
module CLI
|
21
|
+
extend self
|
22
|
+
#
|
23
|
+
# => Options Parser
|
24
|
+
#
|
25
|
+
class Options
|
26
|
+
# => Mix-In the CLI Option Parser
|
27
|
+
include Mixlib::CLI
|
28
|
+
|
29
|
+
option :cache_timeout,
|
30
|
+
short: '-t CACHE_TIMEOUT',
|
31
|
+
long: '--timeout CACHE_TIMEOUT',
|
32
|
+
description: 'Sets the cache timeout in seconds for API query response data.'
|
33
|
+
|
34
|
+
option :config_file,
|
35
|
+
short: '-c CONFIG',
|
36
|
+
long: '--config CONFIG',
|
37
|
+
description: 'The configuration file to use, as opposed to command-line parameters (optional)'
|
38
|
+
|
39
|
+
option :auth_file,
|
40
|
+
short: '-a CONFIG',
|
41
|
+
long: '--auth-json CONFIG',
|
42
|
+
description: "The JSON file containing authorization information (Default: #{Config.auth_file})"
|
43
|
+
|
44
|
+
option :state_file,
|
45
|
+
short: '-s STATE',
|
46
|
+
long: '--state-json STATE',
|
47
|
+
description: "The JSON file containing node state & auditing information (Default: #{Config.state_file})"
|
48
|
+
|
49
|
+
option :chef_api_endpoint,
|
50
|
+
short: '-ce ENDPOINT',
|
51
|
+
long: '--chef-api-endpoint ENDPOINT',
|
52
|
+
description: 'The Chef API Endpoint URL (e.g. https://api.chef.io/)'
|
53
|
+
|
54
|
+
option :chef_api_client,
|
55
|
+
short: '-ccn CLIENT_NAME',
|
56
|
+
long: '--chef-api-client-name CLIENT_NAME',
|
57
|
+
description: 'The name of the Non-Privileged API Client'
|
58
|
+
|
59
|
+
option :chef_api_client_key,
|
60
|
+
short: '-cck CLIENT_KEY',
|
61
|
+
long: '--chef-api-client-key CLIENT_KEY',
|
62
|
+
description: 'The path to the Non-Privileged API Client Keyfile'
|
63
|
+
|
64
|
+
option :chef_api_admin,
|
65
|
+
short: '-can ADMIN_NAME',
|
66
|
+
long: '--chef-api-admin-name ADMIN_NAME',
|
67
|
+
description: 'The name of the Administratively-Privileged API Client'
|
68
|
+
|
69
|
+
option :chef_api_admin_key,
|
70
|
+
short: '-cak ADMIN_KEY',
|
71
|
+
long: '--chef-api-admin-key ADMIN_KEY',
|
72
|
+
description: 'The path to the Administratively-Privileged API Client Keyfile'
|
73
|
+
|
74
|
+
option :rd_node_username,
|
75
|
+
short: '-u USERNAME',
|
76
|
+
long: '--rundeck-node-user USERNAME',
|
77
|
+
description: 'The name of the User Account to place into the RunDeck Resource Provider'
|
78
|
+
|
79
|
+
option :bind,
|
80
|
+
short: '-b HOST',
|
81
|
+
long: '--bind HOST',
|
82
|
+
description: "Listen on Interface or IP (Default: #{Config.bind})"
|
83
|
+
|
84
|
+
option :port,
|
85
|
+
short: '-p PORT',
|
86
|
+
long: '--port PORT',
|
87
|
+
description: "The port to run on. (Default: #{Config.port})"
|
88
|
+
|
89
|
+
option :environment,
|
90
|
+
short: '-e ENV',
|
91
|
+
long: '--env ENV',
|
92
|
+
description: 'Sets the environment for chef-rundeck to execute under. Use "development" for more logging.',
|
93
|
+
default: 'production'
|
94
|
+
end
|
95
|
+
|
96
|
+
# => Launch the Application
|
97
|
+
def run(argv = ARGV)
|
98
|
+
# => Parse CLI Configuration
|
99
|
+
cli = Options.new
|
100
|
+
cli.parse_options(argv)
|
101
|
+
|
102
|
+
# => Parse JSON Config File (If Specified & Exists)
|
103
|
+
json_config = Util.parse_json_config(cli.config[:config_file])
|
104
|
+
|
105
|
+
# => Grab the Default Values
|
106
|
+
default = ChefRunDeck::Config.options
|
107
|
+
|
108
|
+
# => Merge Configuration (JSON File Wins)
|
109
|
+
config = [default, json_config, cli.config].compact.reduce(:merge)
|
110
|
+
|
111
|
+
# => Apply Configuration
|
112
|
+
ChefRunDeck::Config.setup do |cfg|
|
113
|
+
cfg.config_file = config[:config_file]
|
114
|
+
cfg.cache_timeout = config[:cache_timeout].to_i
|
115
|
+
cfg.bind = config[:bind]
|
116
|
+
cfg.port = config[:port]
|
117
|
+
cfg.auth_file = config[:auth_file]
|
118
|
+
cfg.state_file = config[:state_file]
|
119
|
+
cfg.environment = config[:environment].to_sym
|
120
|
+
cfg.chef_api_endpoint = config[:chef_api_endpoint]
|
121
|
+
cfg.chef_api_client = config[:chef_api_client]
|
122
|
+
cfg.chef_api_client_key = config[:chef_api_client_key]
|
123
|
+
cfg.chef_api_admin = config[:chef_api_admin]
|
124
|
+
cfg.chef_api_admin_key = config[:chef_api_admin_key]
|
125
|
+
cfg.rd_node_username = config[:rd_node_username]
|
126
|
+
end
|
127
|
+
|
128
|
+
# => Launch the API
|
129
|
+
ChefRunDeck::API.run!
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Gem Name:: chef-rundeck
|
4
|
+
# ChefRunDeck:: Config
|
5
|
+
#
|
6
|
+
# Copyright (C) 2016 Brian Dwyer - Intelligent Digital Services
|
7
|
+
#
|
8
|
+
# All rights reserved - Do Not Redistribute
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'chef-rundeck/helpers/configuration'
|
12
|
+
require 'pathname'
|
13
|
+
|
14
|
+
module ChefRunDeck
|
15
|
+
# => This is the Configuration module.
|
16
|
+
module Config
|
17
|
+
extend self
|
18
|
+
extend Configuration
|
19
|
+
|
20
|
+
# => Gem Root Directory
|
21
|
+
define_setting :root, Pathname.new(File.expand_path('../../../', __FILE__))
|
22
|
+
|
23
|
+
# => My Name
|
24
|
+
define_setting :author, 'Brian Dwyer - Intelligent Digital Services'
|
25
|
+
|
26
|
+
# => Application Environment
|
27
|
+
define_setting :environment, :production
|
28
|
+
|
29
|
+
# => Sinatra Configuration
|
30
|
+
define_setting :port, '9125'
|
31
|
+
define_setting :bind, 'localhost'
|
32
|
+
define_setting :cache_timeout, 30
|
33
|
+
|
34
|
+
# => Config File
|
35
|
+
define_setting :config_file, File.join(root, 'config', 'config.json')
|
36
|
+
|
37
|
+
# => Authentication File
|
38
|
+
define_setting :auth_file, File.join(root, 'config', 'auth.json')
|
39
|
+
|
40
|
+
# => State File
|
41
|
+
define_setting :state_file, File.join(root, 'config', 'state.json')
|
42
|
+
|
43
|
+
# => Project Configuration File
|
44
|
+
define_setting :projects_file, File.join(root, 'config', 'projects.json')
|
45
|
+
|
46
|
+
#
|
47
|
+
# => Chef API Configuration
|
48
|
+
#
|
49
|
+
# => Chef Endpoint
|
50
|
+
define_setting :chef_api_endpoint, 'https://api.chef.io'
|
51
|
+
|
52
|
+
# => Unprivileged Client
|
53
|
+
define_setting :chef_api_client # => Username
|
54
|
+
define_setting :chef_api_client_key # => Path to Key
|
55
|
+
|
56
|
+
# => Administratively-Privileged Client
|
57
|
+
define_setting :chef_api_admin # => Username
|
58
|
+
define_setting :chef_api_admin_key # => Path to Key
|
59
|
+
|
60
|
+
#
|
61
|
+
# => RunDeck Node Resource Configuration
|
62
|
+
#
|
63
|
+
# => Default Username (nil)
|
64
|
+
define_setting :rd_node_username, nil
|
65
|
+
|
66
|
+
#
|
67
|
+
# => Facilitate Dynamic Addition of Configuration Values
|
68
|
+
#
|
69
|
+
# => @return [class_variable]
|
70
|
+
#
|
71
|
+
def add(config = {})
|
72
|
+
config.each do |key, value|
|
73
|
+
define_setting key.to_sym, value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# => Facilitate Dynamic Removal of Configuration Values
|
79
|
+
#
|
80
|
+
# => @return nil
|
81
|
+
#
|
82
|
+
def clear(config)
|
83
|
+
Array(config).each do |setting|
|
84
|
+
delete_setting setting
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# => List the Configurable Keys as a Hash
|
90
|
+
#
|
91
|
+
# @return [Hash]
|
92
|
+
#
|
93
|
+
def options
|
94
|
+
map = ChefRunDeck::Config.class_variables.map do |key|
|
95
|
+
[key.to_s.tr('@', '').to_sym, class_variable_get(:"#{key}")]
|
96
|
+
end
|
97
|
+
Hash[map]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Gem Name:: chef-rundeck
|
4
|
+
# Helper:: Configuration
|
5
|
+
#
|
6
|
+
# Author: Eli Fatsi - https://www.viget.com/articles/easy-gem-configuration-variables-with-defaults
|
7
|
+
# Contributor: Brian Dwyer - Intelligent Digital Services
|
8
|
+
#
|
9
|
+
|
10
|
+
# => Configuration Helper Module
|
11
|
+
module Configuration
|
12
|
+
#
|
13
|
+
# => Provides a method to configure an Application
|
14
|
+
# => Example:
|
15
|
+
# ChefRunDeck::Config.setup do |cfg|
|
16
|
+
# cfg.config_file = 'abc.json'
|
17
|
+
# cfg.app_name = 'GemBase'
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
def setup
|
21
|
+
yield self
|
22
|
+
end
|
23
|
+
|
24
|
+
def define_setting(name, default = nil)
|
25
|
+
class_variable_set("@@#{name}", default)
|
26
|
+
|
27
|
+
define_class_method "#{name}=" do |value|
|
28
|
+
class_variable_set("@@#{name}", value)
|
29
|
+
end
|
30
|
+
|
31
|
+
define_class_method name do
|
32
|
+
class_variable_get("@@#{name}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete_setting(name)
|
37
|
+
remove_class_variable("@@#{name}")
|
38
|
+
|
39
|
+
delete_class_method(name)
|
40
|
+
rescue NameError # => Handle Non-Existent Settings
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def define_class_method(name, &block)
|
47
|
+
(class << self; self; end).instance_eval do
|
48
|
+
define_method name, &block
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete_class_method(name)
|
53
|
+
(class << self; self; end).instance_eval do
|
54
|
+
undef_method name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
# rubocop: disable LineLength
|
3
|
+
#
|
4
|
+
# Gem Name:: chef-rundeck
|
5
|
+
# ChefRunDeck:: CLI
|
6
|
+
#
|
7
|
+
# Copyright (C) 2016 Brian Dwyer - Intelligent Digital Services
|
8
|
+
#
|
9
|
+
# All rights reserved - Do Not Redistribute
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'chef-rundeck/config'
|
13
|
+
require 'chef-rundeck/util'
|
14
|
+
|
15
|
+
module ChefRunDeck
|
16
|
+
# => This is the State controller. It manages State information
|
17
|
+
module State
|
18
|
+
extend self
|
19
|
+
|
20
|
+
##############################
|
21
|
+
# => State Operations <= #
|
22
|
+
##############################
|
23
|
+
|
24
|
+
attr_accessor :state
|
25
|
+
|
26
|
+
def state
|
27
|
+
@state ||= Util.parse_json_config(Config.state_file) || []
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_state(node)
|
31
|
+
state.detect { |h| h[:name].casecmp(node) == 0 }
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_state(hash) # rubocop: disable AbcSize
|
35
|
+
# => Check if Node Already Exists
|
36
|
+
# => existing = state.detect { |h| h[:name].casecmp(hash[:name]) == 0 }
|
37
|
+
existing = find_state(hash[:name])
|
38
|
+
if existing # => Update the Existing Node
|
39
|
+
state.delete(existing)
|
40
|
+
audit_string = [DateTime.now, hash[:creator]].join(' - ')
|
41
|
+
existing[:last_modified] = existing[:last_modified].is_a?(Array) ? existing[:last_modified].take(5).unshift(audit_string) : [audit_string]
|
42
|
+
hash = existing
|
43
|
+
end
|
44
|
+
|
45
|
+
# => Update the State
|
46
|
+
state.push(hash)
|
47
|
+
|
48
|
+
# => Write Out the Updated State
|
49
|
+
write_state
|
50
|
+
end
|
51
|
+
|
52
|
+
# => Add Node to the State
|
53
|
+
def add_state(node, user, params)
|
54
|
+
# => Create a Node-State Object
|
55
|
+
(n = {}) && (n[:name] = node)
|
56
|
+
n[:created] = DateTime.now
|
57
|
+
n[:creator] = user
|
58
|
+
n[:type] = params['type'] if params['type']
|
59
|
+
# => Build the Updated State
|
60
|
+
update_state(n)
|
61
|
+
# => Return the Added Node
|
62
|
+
find_state(node)
|
63
|
+
end
|
64
|
+
|
65
|
+
# => Remove Node from the State
|
66
|
+
def delete_state(node)
|
67
|
+
# => Find the Node
|
68
|
+
existing = find_state(node)
|
69
|
+
return 'Node not present in state' unless existing
|
70
|
+
# => Delete the Node from State
|
71
|
+
state.delete(existing)
|
72
|
+
# => Write Out the Updated State
|
73
|
+
write_state
|
74
|
+
# => Return the Deleted Node
|
75
|
+
existing
|
76
|
+
end
|
77
|
+
|
78
|
+
def write_state
|
79
|
+
# => Sort & Unique State
|
80
|
+
state.sort_by! { |h| h[:name].downcase }.uniq!
|
81
|
+
|
82
|
+
# => Write Out the Updated State
|
83
|
+
Util.write_json_config(Config.state_file, state)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
# rubocop: disable LineLength
|
3
|
+
#
|
4
|
+
# Gem Name:: chef-rundeck
|
5
|
+
# ChefRunDeck:: Util
|
6
|
+
#
|
7
|
+
# Copyright (C) 2016 Brian Dwyer - Intelligent Digital Services
|
8
|
+
#
|
9
|
+
# All rights reserved - Do Not Redistribute
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'json'
|
13
|
+
|
14
|
+
module ChefRunDeck
|
15
|
+
# => Utility Methods
|
16
|
+
module Util
|
17
|
+
extend self
|
18
|
+
|
19
|
+
########################
|
20
|
+
# => File I/O <= #
|
21
|
+
########################
|
22
|
+
|
23
|
+
# => Define JSON Parser
|
24
|
+
def parse_json_config(file = nil, symbolize = true)
|
25
|
+
return unless file && ::File.exist?(file.to_s)
|
26
|
+
begin
|
27
|
+
::JSON.parse(::File.read(file.to_s), symbolize_names: symbolize)
|
28
|
+
rescue JSON::ParserError
|
29
|
+
return
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# => Define JSON Writer
|
34
|
+
def write_json_config(file, object)
|
35
|
+
return unless file && object
|
36
|
+
begin
|
37
|
+
File.open(file, 'w') { |f| f.write(JSON.pretty_generate(object)) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#############################
|
42
|
+
# => Serialization <= #
|
43
|
+
#############################
|
44
|
+
|
45
|
+
def serialize(response)
|
46
|
+
# => Serialize Object into JSON Array
|
47
|
+
JSON.pretty_generate(response.map(&:name).sort_by(&:downcase))
|
48
|
+
end
|
49
|
+
|
50
|
+
def serialize_csv(csv)
|
51
|
+
# => Serialize a CSV String into an Array
|
52
|
+
return unless csv && csv.is_a?(String)
|
53
|
+
csv.split(',')
|
54
|
+
end
|
55
|
+
|
56
|
+
def serialize_revisions(branches, tags)
|
57
|
+
# => Serialize Branches/Tags into JSON Array
|
58
|
+
# => Branches = String, Tags = Key/Value
|
59
|
+
branches = branches.map(&:name).sort_by(&:downcase)
|
60
|
+
tags = tags.map(&:name).sort_by(&:downcase).reverse.map { |tag| { name: "Tag: #{tag}", value: tag } }
|
61
|
+
JSON.pretty_generate(branches + tags)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|