knife-depsolver 1.0.1 → 2.0.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/CHANGELOG.md +18 -9
- data/README.md +146 -38
- data/lib/chef/knife/depselector.rb +138 -0
- data/lib/chef/knife/depsolver.rb +322 -77
- data/lib/knife-depsolver/version.rb +3 -3
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6778025284948e0f288edf8c6c7f2c2524b183a4
|
4
|
+
data.tar.gz: 0ede42f4cc18050f8e27c28b96b454aa62e3c4a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed6a0cf93519256d488c6038f8aaf7c93647cd6620025b05cd95ba3cdb41b0b8194d012989a61c55ec1ecc208d4dd6d8adaa69619097e52748334dc2260924a0
|
7
|
+
data.tar.gz: 6d754f00136a1174ceefcd1f5c47025d4113b420e169eb9405786425de8fb308a7d0a99241677b8c261d452c52542b93cb02d5b5161f27187cb30efeec1ca58d
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,18 @@
|
|
1
|
-
# Change Log
|
2
|
-
|
3
|
-
##
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## 2.0.0 (2017-05-02)
|
4
|
+
|
5
|
+
* Set the node's environment to ensure accurate run_list expansion
|
6
|
+
* Add --capture option to capture all relevant information for local depsolver usage
|
7
|
+
* Add ability to use Chef DK's local depsolver to make calculations identical to a Chef Server
|
8
|
+
* Add --timeout option
|
9
|
+
* Add --csv-universe-to-json option for converting SQL captured cookbook universe to JSON
|
10
|
+
* Add --env-constraints-filter-universe option
|
11
|
+
|
12
|
+
## 1.0.1 (2016-03-17)
|
13
|
+
|
14
|
+
* Add error handling
|
15
|
+
|
16
|
+
## 1.0.0 (2016-03-17)
|
17
|
+
|
18
|
+
* Initial release
|
data/README.md
CHANGED
@@ -1,38 +1,146 @@
|
|
1
|
-
## knife-depsolver
|
2
|
-
|
3
|
-
Knife plugin that
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
```
|
38
|
-
|
1
|
+
## knife-depsolver
|
2
|
+
|
3
|
+
Knife plugin that calculates Chef cookbook dependencies for a given run_list.
|
4
|
+
|
5
|
+
Some features of knife-depsolver:
|
6
|
+
|
7
|
+
1. Able to capture all relevant information and use Chef DK's embedded depsolver.
|
8
|
+
2. JSON output for easy parsing
|
9
|
+
3. API error handling provides very helpful information
|
10
|
+
4. Accepts cookbook version constraints directly in the run_list
|
11
|
+
5. Useful information such as environment cookbook version constraints, expanded run_list and elapsed time for depsolver request
|
12
|
+
6. Ordering of depsolver results is maintained rather than sorted so you see what really gets passed to the chef-client
|
13
|
+
|
14
|
+
### How Does Cookbook Dependency Solving Work?
|
15
|
+
|
16
|
+
During a chef-client run the chef-client expands the node's run_list which means the chef-client recursively expands
|
17
|
+
any existing role run_lists until the expanded run_list only consists of a list of cookbook recipes.
|
18
|
+
|
19
|
+
Then chef-client sends the expanded run_list to the Chef Server's `/organizations/ORGNAME/environments/ENVNAME/cookbook_versions`
|
20
|
+
API endpoint which is serviced by opscode-erchef.
|
21
|
+
|
22
|
+
opscode-erchef gets the ORGNAME organization's cookbook universe (cookbook dependency info for every version of every cookbook)
|
23
|
+
and the ENVNAME environment's cookbook version constraints from the database.
|
24
|
+
|
25
|
+
opscode-erchef sends the expanded run_list, environment constraints and cookbook universe to a ruby process running the
|
26
|
+
[depselector.rb script](https://github.com/chef/chef-server/blob/12.9.1/src/oc_erchef/apps/chef_objects/priv/depselector_rb/depselector.rb).
|
27
|
+
|
28
|
+
This script uses the [dep_selector gem](https://github.com/chef/dep-selector) to build a dependency graph that filters out any
|
29
|
+
cookbook versions that don't satisfy the environment cookbook version constraints.
|
30
|
+
|
31
|
+
The dep_selector gem re-formulates the dependency graph and the solution constraints as a CSP and off-loads the hard work to
|
32
|
+
[Gecode](http://www.gecode.org/), a fast, license-friendly solver written in C++. If Gecode returns a solution then that is used by
|
33
|
+
opscode-erchef to send a list of cookbook file metadata back to the chef-client so it can synchronize its cookbook cache.
|
34
|
+
|
35
|
+
### Install
|
36
|
+
|
37
|
+
```
|
38
|
+
chef gem install knife-depsolver
|
39
|
+
```
|
40
|
+
|
41
|
+
### Using knife-depsolver with Chef Server's depsolver
|
42
|
+
|
43
|
+
Find the depsolver solution for a node's run_list.
|
44
|
+
|
45
|
+
```
|
46
|
+
knife depsolver -n demo-node
|
47
|
+
```
|
48
|
+
|
49
|
+
Find the depsolver solution for an arbitrary run_list.
|
50
|
+
Specifying roles, recipes and even cookbook version constraints is allowed.
|
51
|
+
|
52
|
+
```
|
53
|
+
knife depsolver 'role[base],cookbook-B,cookbook-A::foo@3.1.4,cookbook-R'
|
54
|
+
```
|
55
|
+
|
56
|
+
The "-E <environment>" option can be added to use a specific environment's cookbook version constraints.
|
57
|
+
|
58
|
+
```
|
59
|
+
knife depsolver -E production -n demo-node
|
60
|
+
OR
|
61
|
+
knife depsolver -E production 'role[base],cookbook-B,cookbook-A::foo@3.1.4,cookbook-R'
|
62
|
+
```
|
63
|
+
|
64
|
+
### Using knife-depsolver with Chef DK's embedded depsolver
|
65
|
+
|
66
|
+
It can be difficult to identify the source of the problem if we hit a depsolver issue in the Chef Server.
|
67
|
+
|
68
|
+
knife-depsolver can provide many more troubleshooting options by using the depsolver embedded in your workstation's Chef DK to make an identical calculation as the Chef Server.
|
69
|
+
|
70
|
+
First, you need to make sure that your version of Chef DK is using the same version of the dep_selector gem as your version of Chef Server. If your Chef DK is using a different version of the dep_selector gem then knife-depsolver's calculations will not be identical to the Chef Server's calculations which will confuse troubleshooting efforts.
|
71
|
+
|
72
|
+
| dep_selector gem | Chef Server | Chef DK |
|
73
|
+
| ---------------- | ----------- | ---------- |
|
74
|
+
| 1.0.3 | <= 12.8.0 | <= 0.16.28 |
|
75
|
+
| 1.0.4 | >= 12.9.0 | >= 0.17.17 |
|
76
|
+
|
77
|
+
Once you are sure you are using the correct version of Chef DK and you have installed the knife-depsolver plugin you can use the "--capture" option which queries the Chef Server and creates a separate file on the workstation for each of the following pieces of information.
|
78
|
+
|
79
|
+
1. environment cookbook version constraints
|
80
|
+
2. cookbook universe
|
81
|
+
3. expanded run list
|
82
|
+
|
83
|
+
The name of each file includes a creation timestamp and a SHA-1 checksum of the contents of the file. These can help during the troubleshooting process to make sure we know when the capture was done and whether anyone has modified the contents of the file.
|
84
|
+
|
85
|
+
For example:
|
86
|
+
|
87
|
+
```
|
88
|
+
knife depsolver -E production -n demo-node --capture
|
89
|
+
OR
|
90
|
+
knife depsolver -E production 'role[base],cookbook-B,cookbook-A::foo@3.1.4,cookbook-R' --capture
|
91
|
+
```
|
92
|
+
|
93
|
+
Now use the "--env-constraints", "universe" and "expanded-run-list" options to provide all the information required for local depsolver calculations.
|
94
|
+
|
95
|
+
For example:
|
96
|
+
|
97
|
+
```
|
98
|
+
knife depsolver --env-constraints rehearsal-environment-20170501185240-5f5843d819ecb0b174f308d76d4336bb7bbfacbf.txt --universe automate-universe-20170501185240-1c8e59e23530b1e1a8e0b3b3cc5236a29c84e469.txt --expanded-run-list expanded-run-list-20170501185240-387d90499514747792a805213c30be13d830d31f.txt
|
99
|
+
```
|
100
|
+
|
101
|
+
Now it is easy to modify any combination of the expanded run list, the environment cookbook version constraints or the cookbook universe in those files and see the impact on the depsolver in an effort to isolate the problem.
|
102
|
+
|
103
|
+
Sometimes it can help to give the depsolver more than the default five seconds to perform its calculations. This can be done by using the "--timeout <seconds>" option to change the depsolver timeout.
|
104
|
+
|
105
|
+
```
|
106
|
+
knife depsolver --env-constraints production-environment-20170501185240-5f5843d819ecb0b174f308d76d4336bb7bbfacbf.txt --universe my-org-universe-20170501185240-1c8e59e23530b1e1a8e0b3b3cc5236a29c84e469.txt --expanded-run-list expanded-run-list-20170501185240-387d90499514747792a805213c30be13d830d31f.txt --timeout 120
|
107
|
+
```
|
108
|
+
|
109
|
+
### Chef Server <= 12.4.0
|
110
|
+
|
111
|
+
knife-depsolver requires cookbook universe data in order to use Chef DK's embedded depsolver. The "/universe" API endpoint was added in the Chef Server 12.4.0 release.
|
112
|
+
|
113
|
+
If you are using a version of Chef Server older than 12.4.0 then you can run the following command on your Chef Server to extract the cookbook universe directly from the database in CSV format.
|
114
|
+
|
115
|
+
```
|
116
|
+
export ORGNAME=demo
|
117
|
+
|
118
|
+
cat <<EOF | chef-server-ctl psql opscode_chef --options '-tAF,' > universe.csv
|
119
|
+
SELECT cvd.name,
|
120
|
+
cvd.major || '.' || cvd.minor || '.' || cvd.patch AS version,
|
121
|
+
updated_at,
|
122
|
+
dependencies
|
123
|
+
FROM cookbook_version_dependencies cvd
|
124
|
+
INNER JOIN cookbooks ckbks
|
125
|
+
ON cvd.org_id = ckbks.org_id
|
126
|
+
AND cvd.name = ckbks.name
|
127
|
+
INNER JOIN cookbook_versions cv
|
128
|
+
ON ckbks.id = cv.cookbook_id
|
129
|
+
AND cvd.major = cv.major
|
130
|
+
AND cvd.minor = cv.minor
|
131
|
+
AND cvd.patch = cv.patch
|
132
|
+
WHERE cvd.org_id = (SELECT id
|
133
|
+
FROM orgs
|
134
|
+
WHERE name = '$ORGNAME'
|
135
|
+
LIMIT 1)
|
136
|
+
ORDER BY updated_at DESC;
|
137
|
+
EOF
|
138
|
+
```
|
139
|
+
|
140
|
+
Then you can run "knife depsolver --csv-universe-to-json universe.csv" to convert it to the normal JSON format.
|
141
|
+
|
142
|
+
If the Chef Server doesn't have the "chef-server-ctl psql" command available then you can try replacing that line with the following.
|
143
|
+
|
144
|
+
```
|
145
|
+
cat <<EOF | su -l opscode-pgsql -c 'psql opscode_chef -tAF,' > universe.csv
|
146
|
+
```
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# I pulled and slightly modified this script from
|
2
|
+
# https://github.com/chef/dep-selector/blob/master/scripts/depselector.rb
|
3
|
+
# which was originally pulled and slightly modified from
|
4
|
+
# https://github.com/chef/chef-server/blob/12.9.0/src/oc_erchef/apps/chef_objects/priv/depselector_rb/depselector.rb
|
5
|
+
|
6
|
+
# Test script to take a dependency graph in JSON format and solve it
|
7
|
+
# ensure that the Gemfile is in the cwd
|
8
|
+
# Dir.chdir(File.dirname(__FILE__))
|
9
|
+
|
10
|
+
# require 'json'
|
11
|
+
# require 'pp'
|
12
|
+
require 'dep_selector'
|
13
|
+
# require 'pry'
|
14
|
+
|
15
|
+
def translate_constraint(constraint)
|
16
|
+
case constraint
|
17
|
+
when Symbol
|
18
|
+
case constraint
|
19
|
+
when :gt then ">"
|
20
|
+
when :gte then ">="
|
21
|
+
when :lt then "<"
|
22
|
+
when :lte then "<="
|
23
|
+
when :eq then "="
|
24
|
+
when :pes then "~>"
|
25
|
+
else constraint.to_s
|
26
|
+
end
|
27
|
+
when NilClass
|
28
|
+
"="
|
29
|
+
else
|
30
|
+
constraint
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def constraint_to_str(constraint, constraint_version)
|
35
|
+
return nil unless constraint_version
|
36
|
+
"#{translate_constraint(constraint)} #{constraint_version}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def solve(data)
|
40
|
+
begin
|
41
|
+
# create dependency graph from cookbooks
|
42
|
+
graph = DepSelector::DependencyGraph.new
|
43
|
+
|
44
|
+
env_constraints = data[:environment_constraints].inject({}) do |acc, env_constraint|
|
45
|
+
name, version, constraint = env_constraint
|
46
|
+
acc[name] = DepSelector::VersionConstraint.new(constraint_to_str(constraint, version))
|
47
|
+
acc
|
48
|
+
end
|
49
|
+
|
50
|
+
all_versions = []
|
51
|
+
|
52
|
+
data[:all_versions].each do | vsn|
|
53
|
+
name, version_constraints = vsn
|
54
|
+
version_constraints.each do |version_constraint| # todo: constraints become an array in ruby
|
55
|
+
# due to the erlectricity conversion from
|
56
|
+
# tuples
|
57
|
+
version, constraints = version_constraint
|
58
|
+
|
59
|
+
# filter versions based on environment constraints
|
60
|
+
env_constraint = env_constraints[name]
|
61
|
+
if (!env_constraint || env_constraint.include?(DepSelector::Version.new(version)))
|
62
|
+
package_version = graph.package(name).add_version(DepSelector::Version.new(version))
|
63
|
+
constraints.each do |package_constraint|
|
64
|
+
constraint_name, constraint_version, constraint = package_constraint
|
65
|
+
version_constraint = DepSelector::VersionConstraint.new(constraint_to_str(constraint, constraint_version))
|
66
|
+
dependency = DepSelector::Dependency.new(graph.package(constraint_name), version_constraint)
|
67
|
+
package_version.dependencies << dependency
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# regardless of filter, add package reference to all_packages
|
73
|
+
all_versions << graph.package(name)
|
74
|
+
end
|
75
|
+
|
76
|
+
run_list = data[:run_list].map do |run_list_item|
|
77
|
+
item_name, item_constraint_version, item_constraint = run_list_item
|
78
|
+
version_constraint = DepSelector::VersionConstraint.new(constraint_to_str(item_constraint,
|
79
|
+
item_constraint_version))
|
80
|
+
DepSelector::SolutionConstraint.new(graph.package(item_name), version_constraint)
|
81
|
+
end
|
82
|
+
|
83
|
+
timeout_ms = data[:timeout_ms]
|
84
|
+
selector = DepSelector::Selector.new(graph, (timeout_ms / 1000.0))
|
85
|
+
|
86
|
+
answer = begin
|
87
|
+
solution = selector.find_solution(run_list, all_versions)
|
88
|
+
packages = []
|
89
|
+
solution.each do |package, v|
|
90
|
+
packages << [package, [v.major, v.minor, v.patch]]
|
91
|
+
end
|
92
|
+
[:ok, packages]
|
93
|
+
rescue DepSelector::Exceptions::InvalidSolutionConstraints => e
|
94
|
+
non_existent_cookbooks = e.non_existent_packages.inject([]) do |list, constraint|
|
95
|
+
list << constraint.package.name
|
96
|
+
end
|
97
|
+
|
98
|
+
constrained_to_no_versions = e.constrained_to_no_versions.inject([]) do |list, constraint|
|
99
|
+
list << constraint.to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
error_detail = [[:non_existent_cookbooks, non_existent_cookbooks],
|
103
|
+
[:constraints_not_met, constrained_to_no_versions]]
|
104
|
+
|
105
|
+
[:error, :invalid_constraints, error_detail]
|
106
|
+
rescue DepSelector::Exceptions::NoSolutionExists => e
|
107
|
+
most_constrained_cookbooks = e.disabled_most_constrained_packages.inject([]) do |list, package|
|
108
|
+
# WTF: this is the reported error format but I can't find this anywhere in the ruby code
|
109
|
+
list << "#{package.name} = #{package.versions.first.to_s}"
|
110
|
+
end
|
111
|
+
|
112
|
+
non_existent_cookbooks = e.disabled_non_existent_packages.inject([]) do |list, package|
|
113
|
+
list << package.name
|
114
|
+
end
|
115
|
+
|
116
|
+
error_detail = [[:message, e.message],
|
117
|
+
[:unsatisfiable_run_list_item, e.unsatisfiable_solution_constraint.to_s],
|
118
|
+
[:non_existent_cookbooks, non_existent_cookbooks],
|
119
|
+
[:most_constrained_cookbooks, most_constrained_cookbooks]]
|
120
|
+
|
121
|
+
[:error, :no_solution, error_detail]
|
122
|
+
rescue DepSelector::Exceptions::TimeBoundExceeded,
|
123
|
+
DepSelector::Exceptions::TimeBoundExceededNoSolution => e
|
124
|
+
# While dep_selector differentiates between the two solutions, the opscode-chef
|
125
|
+
# API returns the same error regardless of the timeout type. We'll swallow the
|
126
|
+
# difference here and return a unified timeout to erchef
|
127
|
+
[:error, :resolution_timeout]
|
128
|
+
end
|
129
|
+
rescue => e
|
130
|
+
answer = [:error, :exception, e.message, [e.backtrace]]
|
131
|
+
end
|
132
|
+
|
133
|
+
answer
|
134
|
+
end
|
135
|
+
|
136
|
+
# data = JSON.parse!(File.read(ARGV[0]), symbolize_names: true)
|
137
|
+
# pp data
|
138
|
+
# pp solve(data)
|
data/lib/chef/knife/depsolver.rb
CHANGED
@@ -1,77 +1,322 @@
|
|
1
|
-
require 'chef/knife'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
1
|
+
require 'chef/knife'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
class Knife
|
6
|
+
class Depsolver < Knife
|
7
|
+
deps do
|
8
|
+
end
|
9
|
+
|
10
|
+
banner 'knife depsolver RUN_LIST'
|
11
|
+
|
12
|
+
option :node,
|
13
|
+
short: '-n',
|
14
|
+
long: '--node NAME',
|
15
|
+
description: 'Use the run list from a given node'
|
16
|
+
|
17
|
+
option :timeout,
|
18
|
+
short: '-t',
|
19
|
+
long: '--timeout SECONDS',
|
20
|
+
description: 'Set the local depsolver timeout. Only valid when using the --env-constraints and --universe options'
|
21
|
+
|
22
|
+
option :capture,
|
23
|
+
long: '--capture',
|
24
|
+
description: 'Save the expanded run list, environment cookbook constraints and cookbook universe to files for local depsolving'
|
25
|
+
|
26
|
+
option :env_constraints,
|
27
|
+
long: '--env-constraints FILENAME',
|
28
|
+
description: 'Use the environment cookbook constraints from FILENAME. REQUIRED when using the local depsolver.'
|
29
|
+
|
30
|
+
option :universe,
|
31
|
+
long: '--universe FILENAME',
|
32
|
+
description: 'Use the cookbook universe from FILENAME. REQUIRED when using the local depsolver.'
|
33
|
+
|
34
|
+
option :expanded_run_list,
|
35
|
+
long: '--expanded-run-list FILENAME',
|
36
|
+
description: 'Use the expanded run list from FILENAME. REQUIRED when using the local depsolver.'
|
37
|
+
|
38
|
+
option :csv_universe_to_json,
|
39
|
+
long: '--csv-universe-to-json FILENAME',
|
40
|
+
description: 'Convert a CSV cookbook universe, FILENAME, to JSON.'
|
41
|
+
|
42
|
+
option :env_constraints_filter_universe,
|
43
|
+
long: '--env-constraints-filter-universe',
|
44
|
+
description: 'Filter the cookbook universe using the environment cookbook version constraints.'
|
45
|
+
|
46
|
+
def run
|
47
|
+
begin
|
48
|
+
use_local_depsolver = false
|
49
|
+
if config[:env_constraints_filter_universe]
|
50
|
+
if config[:node] || config[:environment] || config[:timeout] || config[:capture] || config[:expanded_run_list] || config[:csv_universe_to_json]
|
51
|
+
msg("ERROR: The --env-constraints-filter-universe option is only compatible with the --env-constraints and --universe options")
|
52
|
+
exit!
|
53
|
+
elsif !(config[:env_constraints] && config[:universe])
|
54
|
+
msg("ERROR: The --env-constraints-filter-universe option requires the --env-constraints and --universe options to be set")
|
55
|
+
exit!
|
56
|
+
end
|
57
|
+
else
|
58
|
+
if config[:env_constraints] || config[:universe] || config[:expanded_run_list]
|
59
|
+
if config[:env_constraints] && config[:universe] && config[:expanded_run_list]
|
60
|
+
use_local_depsolver = true
|
61
|
+
unless name_args.empty?
|
62
|
+
puts "ERROR: Setting a run list on the command line is not compatible with the --env-constraints, --universe or --expanded-run-list options"
|
63
|
+
exit!
|
64
|
+
end
|
65
|
+
if config[:node]
|
66
|
+
puts "ERROR: The --node option is not compatible with the --env-constraints, --universe or --expanded-run-list options"
|
67
|
+
exit!
|
68
|
+
end
|
69
|
+
if config[:environment]
|
70
|
+
puts "ERROR: The --environment option is not compatible with the --env-constraints, --universe or --expanded-run-list options"
|
71
|
+
exit!
|
72
|
+
end
|
73
|
+
if config[:capture]
|
74
|
+
puts "ERROR: The --capture option is not compatible with the --env-constraints, --universe or --expanded-run-list options"
|
75
|
+
exit!
|
76
|
+
end
|
77
|
+
else
|
78
|
+
puts "ERROR: The --env-constraints, --universe and --expanded-run-list options must be used together to use the local depsolver"
|
79
|
+
exit!
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
if config[:timeout] && !use_local_depsolver
|
84
|
+
msg("ERROR: The --timeout option requires the --env-constraints, --universe and --expanded-run-list options to be set")
|
85
|
+
exit!
|
86
|
+
end
|
87
|
+
|
88
|
+
timeout = (config[:timeout].to_f * 1000).to_i if config[:timeout]
|
89
|
+
timeout ||= 5 * 1000
|
90
|
+
|
91
|
+
if config[:csv_universe_to_json]
|
92
|
+
unless File.file?(config[:csv_universe_to_json])
|
93
|
+
msg("ERROR: #{config[:csv_universe_to_json]} does not exist or is not a file.")
|
94
|
+
exit!
|
95
|
+
end
|
96
|
+
universe = Hash.new { |hash, key| hash[key] = Hash.new }
|
97
|
+
IO.foreach(config[:csv_universe_to_json]) do |line|
|
98
|
+
name, version, updated_at, dependencies = line.split(",", 4)
|
99
|
+
universe[name][version] = {updated_at: updated_at, dependencies: JSON.parse(dependencies)}
|
100
|
+
end
|
101
|
+
|
102
|
+
universe_json = JSON.pretty_generate(universe)
|
103
|
+
universe_filename = "universe-#{Time.now.strftime("%Y-%m-%d-%H:%M:%S")}-#{Digest::SHA1.hexdigest(universe_json)}.txt"
|
104
|
+
IO.write(universe_filename, universe_json)
|
105
|
+
puts "Cookbook universe saved to #{universe_filename}"
|
106
|
+
exit!
|
107
|
+
end
|
108
|
+
|
109
|
+
if config[:node] && !(config[:env_constraints_filter_universe] && config[:env_constraints])
|
110
|
+
node = Chef::Node.load(config[:node])
|
111
|
+
else
|
112
|
+
node = Chef::Node.new
|
113
|
+
node.name('depsolver-tmp-node')
|
114
|
+
|
115
|
+
if config[:expanded_run_list]
|
116
|
+
unless File.file?(config[:expanded_run_list])
|
117
|
+
msg("ERROR: #{config[:expanded_run_list]} does not exist or is not a file.")
|
118
|
+
exit!
|
119
|
+
end
|
120
|
+
contents = JSON.parse(IO.read(config[:expanded_run_list]))
|
121
|
+
if !(contents.key?('expanded_run_list') && contents['expanded_run_list'].is_a?(Array))
|
122
|
+
msg("ERROR: #{config[:expanded_run_list]} does not contain an expanded run list array.")
|
123
|
+
exit!
|
124
|
+
else
|
125
|
+
expanded_run_list = contents['expanded_run_list']
|
126
|
+
expanded_run_list.each do |arg|
|
127
|
+
node.run_list.add(arg)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
else
|
131
|
+
run_list = name_args.map {|item| item.to_s.split(/,/) }.flatten.each{|item| item.strip! }
|
132
|
+
run_list.delete_if {|item| item.empty? }
|
133
|
+
|
134
|
+
run_list.each do |arg|
|
135
|
+
node.run_list.add(arg)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
node.chef_environment = config[:environment] if config[:environment]
|
141
|
+
|
142
|
+
if config[:capture]
|
143
|
+
if node.chef_environment == '_default'
|
144
|
+
environment_cookbook_versions = Hash.new
|
145
|
+
else
|
146
|
+
environment_cookbook_versions = Chef::Environment.load(node.chef_environment).cookbook_versions
|
147
|
+
end
|
148
|
+
env = { name: node.chef_environment, cookbook_versions: environment_cookbook_versions }
|
149
|
+
environment_constraints_json = JSON.pretty_generate(env)
|
150
|
+
environment_constraints_filename = "#{node.chef_environment}-environment-#{Time.now.strftime("%Y-%m-%d-%H:%M:%S")}-#{Digest::SHA1.hexdigest(environment_constraints_json)}.txt"
|
151
|
+
IO.write(environment_constraints_filename, environment_constraints_json)
|
152
|
+
puts "Environment constraints saved to #{environment_constraints_filename}"
|
153
|
+
|
154
|
+
begin
|
155
|
+
universe = rest.get_rest("universe")
|
156
|
+
universe_json = JSON.pretty_generate(universe)
|
157
|
+
universe_filename = ""
|
158
|
+
rest.url.to_s.match(".*/organizations/(.*)/?") { universe_filename = "#{$1}-" }
|
159
|
+
universe_filename += "universe-#{Time.now.strftime("%Y-%m-%d-%H:%M:%S")}-#{Digest::SHA1.hexdigest(universe_json)}.txt"
|
160
|
+
IO.write(universe_filename, universe_json)
|
161
|
+
puts "Cookbook universe saved to #{universe_filename}"
|
162
|
+
rescue Net::HTTPServerException
|
163
|
+
puts "WARNING: The cookbook universe API endpoint is not available."
|
164
|
+
puts "WARNING: Try capturing the cookbook universe using the SQL query found in the knife-depsolver README."
|
165
|
+
puts "WARNING: Then convert the results using knife-depsolver's --csv-universe-to-json option"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
if config[:env_constraints]
|
170
|
+
unless File.file?(config[:env_constraints])
|
171
|
+
msg("ERROR: #{config[:env_constraints]} does not exist or is not a file.")
|
172
|
+
exit!
|
173
|
+
end
|
174
|
+
env = JSON.parse(IO.read(config[:env_constraints]))
|
175
|
+
if env['name'].to_s.empty?
|
176
|
+
msg("ERROR: #{config[:env_constraints]} does not contain an environment name.")
|
177
|
+
exit!
|
178
|
+
else
|
179
|
+
node.chef_environment = env['name']
|
180
|
+
end
|
181
|
+
if !env['cookbook_versions'].is_a?(Hash)
|
182
|
+
msg("ERROR: #{config[:env_constraints]} does not contain a Hash of cookbook version constraints.")
|
183
|
+
exit!
|
184
|
+
else
|
185
|
+
environment_cookbook_versions = env['cookbook_versions']
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
if config[:universe]
|
190
|
+
unless File.file?(config[:universe])
|
191
|
+
msg("ERROR: #{config[:universe]} does not exist or is not a file.")
|
192
|
+
exit!
|
193
|
+
end
|
194
|
+
universe = JSON.parse(IO.read(config[:universe]))
|
195
|
+
if !universe.is_a?(Hash)
|
196
|
+
msg("ERROR: #{config[:universe]} does not contain a cookbook universe Hash.")
|
197
|
+
exit!
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
if config[:env_constraints_filter_universe]
|
202
|
+
env_constraints = environment_cookbook_versions.each_with_object({}) do |env_constraint, memo|
|
203
|
+
name, constraint = env_constraint
|
204
|
+
constraint, version = constraint.split
|
205
|
+
memo[name] = DepSelector::VersionConstraint.new(constraint_to_str(constraint, version))
|
206
|
+
end
|
207
|
+
universe.each do |name, versions|
|
208
|
+
versions.delete_if {|version, v| !env_constraints[name].include?(version)} if env_constraints[name]
|
209
|
+
end
|
210
|
+
filtered_universe_json = JSON.pretty_generate(universe)
|
211
|
+
filtered_universe_filename = "filtered-universe-#{Time.now.strftime("%Y-%m-%d-%H:%M:%S")}-#{Digest::SHA1.hexdigest(filtered_universe_json)}.txt"
|
212
|
+
IO.write(filtered_universe_filename, filtered_universe_json)
|
213
|
+
puts "Filtered cookbook universe saved to #{filtered_universe_filename}"
|
214
|
+
exit!
|
215
|
+
end
|
216
|
+
|
217
|
+
run_list_expansion = node.run_list.expand(node.chef_environment, 'server')
|
218
|
+
expanded_run_list_with_versions = run_list_expansion.recipes.with_version_constraints_strings
|
219
|
+
|
220
|
+
exit if config[:capture]
|
221
|
+
|
222
|
+
depsolver_results = Hash.new
|
223
|
+
if use_local_depsolver
|
224
|
+
env_ckbk_constraints = environment_cookbook_versions.map do |ckbk_name, ckbk_constraint|
|
225
|
+
[ckbk_name, ckbk_constraint.split.reverse].flatten
|
226
|
+
end
|
227
|
+
|
228
|
+
all_versions = universe.map do |ckbk_name, ckbk_metadata|
|
229
|
+
ckbk_versions = ckbk_metadata.map do |version, version_metadata|
|
230
|
+
[version, version_metadata['dependencies'].map { |dep_ckbk_name, dep_ckbk_constraint| [dep_ckbk_name, dep_ckbk_constraint.split.reverse].flatten }]
|
231
|
+
end
|
232
|
+
[ckbk_name, ckbk_versions]
|
233
|
+
end
|
234
|
+
|
235
|
+
expanded_run_list_with_split_versions = expanded_run_list_with_versions.map do |run_list_item|
|
236
|
+
name, version = run_list_item.split('@')
|
237
|
+
name.sub!(/::.*/, '')
|
238
|
+
version ? [name, version] : name
|
239
|
+
end
|
240
|
+
|
241
|
+
data = {environment_constraints: env_ckbk_constraints, all_versions: all_versions, run_list: expanded_run_list_with_split_versions, timeout_ms: timeout}
|
242
|
+
|
243
|
+
depsolver_start_time = Time.now
|
244
|
+
|
245
|
+
solution = solve(data)
|
246
|
+
|
247
|
+
depsolver_finish_time = Time.now
|
248
|
+
|
249
|
+
if solution.first == :ok
|
250
|
+
solution.last.map { |ckbk| ckbk_name, ckbk_version = ckbk; depsolver_results[ckbk_name] = ckbk_version.join('.') }
|
251
|
+
else
|
252
|
+
status, error_type, error_detail = solution
|
253
|
+
depsolver_error = { error_type => error_detail }
|
254
|
+
end
|
255
|
+
else
|
256
|
+
begin
|
257
|
+
chef_server_version = rest.get(server_url.sub(/organizations.*/, 'version')).split("\n")[0]
|
258
|
+
rescue Net::HTTPServerException
|
259
|
+
chef_server_version = "unknown"
|
260
|
+
end
|
261
|
+
|
262
|
+
depsolver_start_time = Time.now
|
263
|
+
|
264
|
+
ckbks = rest.post_rest("environments/" + node.chef_environment + "/cookbook_versions", { "run_list" => expanded_run_list_with_versions })
|
265
|
+
|
266
|
+
depsolver_finish_time = Time.now
|
267
|
+
|
268
|
+
ckbks.each do |name, ckbk|
|
269
|
+
version = ckbk.is_a?(Hash) ? ckbk['version'] : ckbk.version
|
270
|
+
depsolver_results[name] = version
|
271
|
+
end
|
272
|
+
end
|
273
|
+
rescue Net::HTTPServerException => e
|
274
|
+
api_error = {}
|
275
|
+
api_error[:error_code] = e.response.code
|
276
|
+
api_error[:error_message] = e.response.message
|
277
|
+
begin
|
278
|
+
api_error[:error_body] = JSON.parse(e.response.body)
|
279
|
+
rescue JSON::ParserError
|
280
|
+
end
|
281
|
+
rescue => e
|
282
|
+
msg("ERROR: #{e.message}")
|
283
|
+
exit!
|
284
|
+
ensure
|
285
|
+
local_software = Hash.new
|
286
|
+
%w(chef-dk chef dep_selector).each do |gem_name|
|
287
|
+
begin
|
288
|
+
local_software[gem_name] = Gem::Specification.find_by_name(gem_name).version
|
289
|
+
rescue Gem::MissingSpecError
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
results = {}
|
294
|
+
results[:local_software] = local_software unless local_software.empty?
|
295
|
+
if use_local_depsolver
|
296
|
+
results[:depsolver] = "used local depsolver"
|
297
|
+
else
|
298
|
+
results[:depsolver] = {"used chef server" => chef_server_version} unless chef_server_version.nil?
|
299
|
+
end
|
300
|
+
results[:node] = node.name unless node.nil? || node.name.nil?
|
301
|
+
results[:environment] = node.chef_environment unless node.chef_environment.nil?
|
302
|
+
results[:run_list] = node.run_list unless node.nil? || node.run_list.nil?
|
303
|
+
results[:expanded_run_list] = expanded_run_list_with_versions unless expanded_run_list_with_versions.nil?
|
304
|
+
results[:depsolver_results] = depsolver_results unless depsolver_results.nil? || depsolver_results.empty?
|
305
|
+
results[:depsolver_cookbook_count] = depsolver_results.count unless depsolver_results.nil? || depsolver_results.empty?
|
306
|
+
results[:depsolver_elapsed_ms] = ((depsolver_finish_time - depsolver_start_time) * 1000).to_i unless depsolver_finish_time.nil?
|
307
|
+
results[:depsolver_error] = depsolver_error unless depsolver_error.nil?
|
308
|
+
results[:api_error] = api_error unless api_error.nil?
|
309
|
+
|
310
|
+
if config[:capture]
|
311
|
+
results_json = JSON.pretty_generate(results)
|
312
|
+
expanded_run_list_filename = "expanded-run-list-#{Time.now.strftime("%Y-%m-%d-%H:%M:%S")}-#{Digest::SHA1.hexdigest(results_json)}.txt"
|
313
|
+
IO.write(expanded_run_list_filename, results_json)
|
314
|
+
puts "Expanded run list saved to #{expanded_run_list_filename}"
|
315
|
+
else
|
316
|
+
msg(JSON.pretty_generate(results))
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
module KnifeDepsolver
|
2
|
-
VERSION = "
|
3
|
-
end
|
1
|
+
module KnifeDepsolver
|
2
|
+
VERSION = "2.0.0"
|
3
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-depsolver
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremiah Snapp
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Knife plugin that uses Chef Server to calculate cookbook dependencies
|
14
14
|
for a given run_list.
|
@@ -23,6 +23,7 @@ files:
|
|
23
23
|
- CHANGELOG.md
|
24
24
|
- LICENSE
|
25
25
|
- README.md
|
26
|
+
- lib/chef/knife/depselector.rb
|
26
27
|
- lib/chef/knife/depsolver.rb
|
27
28
|
- lib/knife-depsolver/version.rb
|
28
29
|
homepage: https://github.com/jeremiahsnapp/knife-depsolver
|
@@ -44,10 +45,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
45
|
version: '0'
|
45
46
|
requirements: []
|
46
47
|
rubyforge_project:
|
47
|
-
rubygems_version: 2.
|
48
|
+
rubygems_version: 2.6.11
|
48
49
|
signing_key:
|
49
50
|
specification_version: 4
|
50
51
|
summary: Knife plugin that uses Chef Server to calculate cookbook dependencies for
|
51
52
|
a given run_list.
|
52
53
|
test_files: []
|
53
|
-
has_rdoc:
|