license-acceptance 0.0.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +97 -0
  4. data/config/product_info.toml +1 -0
  5. data/lib/license_acceptance/acceptor.rb +117 -0
  6. data/lib/license_acceptance/arg_acceptance.rb +33 -0
  7. data/lib/license_acceptance/cli_flags/mixlib_cli.rb +22 -0
  8. data/lib/license_acceptance/cli_flags/thor.rb +21 -0
  9. data/lib/license_acceptance/config.rb +65 -0
  10. data/lib/license_acceptance/env_acceptance.rb +19 -0
  11. data/lib/license_acceptance/file_acceptance.rb +97 -0
  12. data/lib/license_acceptance/logger.rb +19 -0
  13. data/lib/license_acceptance/product.rb +23 -0
  14. data/lib/license_acceptance/product_reader.rb +108 -0
  15. data/lib/license_acceptance/product_relationship.rb +13 -0
  16. data/lib/license_acceptance/prompt_acceptance.rb +104 -0
  17. data/lib/license_acceptance/version.rb +3 -0
  18. data/spec/license_acceptance/acceptor_spec.rb +222 -0
  19. data/spec/license_acceptance/arg_acceptance_spec.rb +37 -0
  20. data/spec/license_acceptance/cli_flags/mixlib_cli_spec.rb +14 -0
  21. data/spec/license_acceptance/cli_flags/thor_spec.rb +14 -0
  22. data/spec/license_acceptance/config_spec.rb +113 -0
  23. data/spec/license_acceptance/env_acceptance_spec.rb +43 -0
  24. data/spec/license_acceptance/file_acceptance_spec.rb +121 -0
  25. data/spec/license_acceptance/product_reader_spec.rb +139 -0
  26. data/spec/license_acceptance/product_spec.rb +13 -0
  27. data/spec/license_acceptance/prompt_acceptance_spec.rb +100 -0
  28. data/spec/spec_helper.rb +25 -0
  29. metadata +184 -22
  30. data/.gitignore +0 -11
  31. data/.rspec +0 -3
  32. data/.travis.yml +0 -7
  33. data/LICENSE +0 -1
  34. data/README.md +0 -35
  35. data/lib/license/acceptance/version.rb +0 -5
  36. data/lib/license/acceptance.rb +0 -8
  37. data/license-acceptance.gemspec +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7483f91b06d3f9eed8968382a03e5f2c7fb08448af51e42b0f104405e5339c22
4
- data.tar.gz: a41ee7fdd79eca87e3f29db2b53963f1a2d8b8a4f12c1275b6f81065d05e2eb8
3
+ metadata.gz: 5ae4a25ff89f94b78498d69d0d5805a243e065cc2e622e4ec08f2acf8294345d
4
+ data.tar.gz: b994a62576a2e0c24694f916d3e4672f556077d8882bb52aa853706ef1e4e9c3
5
5
  SHA512:
6
- metadata.gz: f93046a3fc6cf6eb2971bede8774e081aa3f398f919ad21009dfb3633286a98ddb7aabfd5632ee8fbdd923ce7667c4deace6407ffec8719930e9465ea3166025
7
- data.tar.gz: 34374e8e941827e6773d6764b1e39fe3689e5c10241af067d8e49b985c7d2a891cfc4e8ba61c5bd5a2864d4572d6ba8f1e3417a60966d93ef850104a02f2680f
6
+ metadata.gz: c6d8d023638ba548846b92b27e166a7ff2683b96b59eb5513b71c28ebbd46792dfd755e46893409e02dfbf4ce83dcee28e60e4c26716078fbe122b465553d399
7
+ data.tar.gz: d4501d2b154e6fcf8dabdc1a40c689d2bb575ea09f0f61b1f3ccbb5775609808ac6543fc6f9ed9bbee262fc085ddc634ecc4f3732a01cc17e951422162fdcded
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source "https://rubygems.org"
2
2
 
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
3
5
  # Specify your gem's dependencies in license-acceptance.gemspec
4
6
  gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,97 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ license-acceptance (0.1.0)
5
+ pastel (>= 0.7)
6
+ tomlrb (~> 1.2)
7
+ tty-box (>= 0.3)
8
+ tty-platform (>= 0.2)
9
+ tty-prompt (>= 0.18)
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ binding_of_caller (0.8.0)
15
+ debug_inspector (>= 0.0.1)
16
+ byebug (11.0.1)
17
+ climate_control (0.2.0)
18
+ coderay (1.1.2)
19
+ debug_inspector (0.0.3)
20
+ diff-lcs (1.3)
21
+ equatable (0.5.0)
22
+ method_source (0.9.2)
23
+ mixlib-cli (2.0.3)
24
+ necromancer (0.4.0)
25
+ pastel (0.7.2)
26
+ equatable (~> 0.5.0)
27
+ tty-color (~> 0.4.0)
28
+ pry (0.12.2)
29
+ coderay (~> 1.1.0)
30
+ method_source (~> 0.9.0)
31
+ pry-byebug (3.7.0)
32
+ byebug (~> 11.0)
33
+ pry (~> 0.10)
34
+ pry-stack_explorer (0.4.9.3)
35
+ binding_of_caller (>= 0.7)
36
+ pry (>= 0.9.11)
37
+ rake (12.3.2)
38
+ rspec (3.8.0)
39
+ rspec-core (~> 3.8.0)
40
+ rspec-expectations (~> 3.8.0)
41
+ rspec-mocks (~> 3.8.0)
42
+ rspec-core (3.8.0)
43
+ rspec-support (~> 3.8.0)
44
+ rspec-expectations (3.8.2)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.8.0)
47
+ rspec-mocks (3.8.0)
48
+ diff-lcs (>= 1.2.0, < 2.0)
49
+ rspec-support (~> 3.8.0)
50
+ rspec-support (3.8.0)
51
+ strings (0.1.4)
52
+ strings-ansi (~> 0.1.0)
53
+ unicode-display_width (~> 1.4.0)
54
+ unicode_utils (~> 1.4.0)
55
+ strings-ansi (0.1.0)
56
+ thor (0.20.3)
57
+ timers (4.3.0)
58
+ tomlrb (1.2.8)
59
+ tty-box (0.3.0)
60
+ pastel (~> 0.7.2)
61
+ strings (~> 0.1.4)
62
+ tty-cursor (~> 0.6.0)
63
+ tty-color (0.4.3)
64
+ tty-cursor (0.6.1)
65
+ tty-platform (0.2.0)
66
+ tty-prompt (0.18.1)
67
+ necromancer (~> 0.4.0)
68
+ pastel (~> 0.7.0)
69
+ timers (~> 4.0)
70
+ tty-cursor (~> 0.6.0)
71
+ tty-reader (~> 0.5.0)
72
+ tty-reader (0.5.0)
73
+ tty-cursor (~> 0.6.0)
74
+ tty-screen (~> 0.6.4)
75
+ wisper (~> 2.0.0)
76
+ tty-screen (0.6.5)
77
+ unicode-display_width (1.4.1)
78
+ unicode_utils (1.4.0)
79
+ wisper (2.0.0)
80
+
81
+ PLATFORMS
82
+ ruby
83
+
84
+ DEPENDENCIES
85
+ bundler (>= 1.17)
86
+ climate_control (>= 0.2)
87
+ license-acceptance!
88
+ mixlib-cli (>= 1.7)
89
+ pry (>= 0.12)
90
+ pry-byebug (>= 3.6)
91
+ pry-stack_explorer (>= 0.4)
92
+ rake (>= 10.0)
93
+ rspec (>= 3.0)
94
+ thor (>= 0.20)
95
+
96
+ BUNDLED WITH
97
+ 1.17.3
@@ -0,0 +1 @@
1
+ # DO NOT CHANGE - overwritten by expeditor at build time
@@ -0,0 +1,117 @@
1
+ require "forwardable"
2
+ require "license_acceptance/config"
3
+ require "license_acceptance/logger"
4
+ require "license_acceptance/product_reader"
5
+ require "license_acceptance/product_relationship"
6
+ require "license_acceptance/file_acceptance"
7
+ require "license_acceptance/arg_acceptance"
8
+ require "license_acceptance/prompt_acceptance"
9
+ require "license_acceptance/env_acceptance"
10
+
11
+ module LicenseAcceptance
12
+ class Acceptor
13
+ extend Forwardable
14
+ include Logger
15
+
16
+ attr_reader :config, :product_reader, :env_acceptance, :file_acceptance, :arg_acceptance, :prompt_acceptance
17
+
18
+ def initialize(opts={})
19
+ @config = Config.new(opts)
20
+ Logger.initialize(config.logger)
21
+ @product_reader = ProductReader.new
22
+ @env_acceptance = EnvAcceptance.new
23
+ @file_acceptance = FileAcceptance.new(config)
24
+ @arg_acceptance = ArgAcceptance.new
25
+ @prompt_acceptance = PromptAcceptance.new(config)
26
+ end
27
+
28
+ def_delegator :@config, :output
29
+
30
+ # For applications that just need simple logic to handle a failed license acceptance flow we include this small
31
+ # wrapper. Apps with more complex logic (like logging to a logging engine) should call the non-bang version and
32
+ # handle the exception.
33
+ def check_and_persist!(product_name, version)
34
+ check_and_persist(product_name, version)
35
+ rescue LicenseNotAcceptedError
36
+ output.puts "#{product_name} cannot execute without accepting the license"
37
+ exit 172
38
+ end
39
+
40
+ def check_and_persist(product_name, version)
41
+ if env_acceptance.check_no_persist(ENV) || arg_acceptance.check_no_persist(ARGV)
42
+ logger.debug("Chef License accepted with no persistence")
43
+ return true
44
+ end
45
+
46
+ product_reader.read
47
+ product_relationship = product_reader.lookup(product_name, version)
48
+
49
+ missing_licenses = file_acceptance.check(product_relationship)
50
+
51
+ # They have already accepted all licenses and stored their acceptance in the persistent files
52
+ if missing_licenses.empty?
53
+ logger.debug("All licenses present")
54
+ return true
55
+ end
56
+
57
+ if env_acceptance.check(ENV) || arg_acceptance.check(ARGV)
58
+ if config.persist
59
+ errs = file_acceptance.persist(product_relationship, missing_licenses)
60
+ if errs.empty?
61
+ output_num_persisted(missing_licenses.size)
62
+ else
63
+ output_persist_failed(errs)
64
+ end
65
+ end
66
+ return true
67
+ elsif config.output.isatty && prompt_acceptance.request(missing_licenses) do
68
+ if config.persist
69
+ file_acceptance.persist(product_relationship, missing_licenses)
70
+ else
71
+ []
72
+ end
73
+ end
74
+ return true
75
+ else
76
+ raise LicenseNotAcceptedError.new(missing_licenses)
77
+ end
78
+ end
79
+
80
+ def self.check_and_persist!(product_name, version, opts={})
81
+ new(opts).check_and_persist!(product_name, version)
82
+ end
83
+
84
+ def self.check_and_persist(product_name, version, opts={})
85
+ new(opts).check_and_persist(product_name, version)
86
+ end
87
+
88
+ # In the case where users accept with a command line argument or environment variable
89
+ # we still want to output the fact that the filesystem was changed.
90
+ def output_num_persisted(count)
91
+ s = count > 1 ? "s": ""
92
+ output.puts <<~EOM
93
+ #{PromptAcceptance::BORDER}
94
+ #{PromptAcceptance::CHECK} #{count} product license#{s} accepted.
95
+ #{PromptAcceptance::BORDER}
96
+ EOM
97
+ end
98
+
99
+ def output_persist_failed(errs)
100
+ output.puts <<~EOM
101
+ #{PromptAcceptance::BORDER}
102
+ #{PromptAcceptance::CHECK} Product license accepted.
103
+ Could not persist acceptance:\n\t* #{errs.map(&:message).join("\n\t* ")}
104
+ #{PromptAcceptance::BORDER}
105
+ EOM
106
+ end
107
+
108
+ end
109
+
110
+ class LicenseNotAcceptedError < RuntimeError
111
+ def initialize(missing_licenses)
112
+ msg = "Missing licenses for the following:\n* " + missing_licenses.join("\n* ")
113
+ super(msg)
114
+ end
115
+ end
116
+
117
+ end
@@ -0,0 +1,33 @@
1
+ module LicenseAcceptance
2
+ class ArgAcceptance
3
+
4
+ def check(argv)
5
+ if argv.include?("--chef-license=accept")
6
+ return true
7
+ end
8
+ i = argv.index("--chef-license")
9
+ unless i.nil?
10
+ val = argv[i+1]
11
+ if val != nil && val.downcase == "accept"
12
+ return true
13
+ end
14
+ end
15
+ return false
16
+ end
17
+
18
+ def check_no_persist(argv)
19
+ if argv.include?("--chef-license=accept-no-persist")
20
+ return true
21
+ end
22
+ i = argv.index("--chef-license")
23
+ unless i.nil?
24
+ val = argv[i+1]
25
+ if val != nil && val.downcase == "accept-no-persist"
26
+ return true
27
+ end
28
+ end
29
+ return false
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ begin
2
+ require 'mixlib/cli'
3
+ rescue => exception
4
+ raise "Must have mixlib-cli gem installed to use this mixin"
5
+ end
6
+
7
+ module LicenseAcceptance
8
+ module CLIFlags
9
+
10
+ module MixlibCLI
11
+
12
+ def self.included(klass)
13
+ klass.option :chef_license,
14
+ long: "--chef-license ACCEPTANCE",
15
+ description: "Accept the license for this product and any contained products ('accept' or 'accept-no-persist')",
16
+ required: false
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'thor'
3
+ rescue => exception
4
+ raise "Must have thor gem installed to use this mixin"
5
+ end
6
+
7
+ module LicenseAcceptance
8
+ module CLIFlags
9
+
10
+ module Thor
11
+
12
+ def self.included(klass)
13
+ klass.class_option :chef_license,
14
+ type: :string,
15
+ desc: 'Accept the license for this product and any contained products: accept, accept-no-persist'
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,65 @@
1
+ require 'logger'
2
+ require 'tty-platform'
3
+
4
+ module LicenseAcceptance
5
+ class Config
6
+ attr_accessor :output, :logger, :license_locations, :persist_location, :persist
7
+
8
+ def initialize(opts={})
9
+ @output = opts.fetch(:output, $stdout)
10
+ @logger = opts.fetch(:logger, ::Logger.new(IO::NULL))
11
+ @license_locations = opts.fetch(:license_locations, default_license_locations)
12
+ @license_locations = [ @license_locations ].flatten
13
+ @persist_location = opts.fetch(:persist_location, default_persist_location)
14
+ @persist = opts.fetch(:persist, true)
15
+ end
16
+
17
+ private
18
+
19
+ def platform
20
+ @platform ||= TTY::Platform.new
21
+ end
22
+
23
+ def is_root?
24
+ Process.uid == 0
25
+ end
26
+
27
+ def default_license_locations
28
+ if platform.windows?
29
+ l = [ File.join(ENV["HOMEDRIVE"], "chef/accepted_licenses/") ]
30
+ unless is_root?
31
+ # Look through a list of possible user locations and pick the first one that exists
32
+ # copied from path_helper.rb in chef-config gem
33
+ possible_dirs = []
34
+ possible_dirs << ENV["HOME"] if ENV["HOME"]
35
+ possible_dirs << ENV["HOMEDRIVE"] + ENV["HOMEPATH"] if ENV["HOMEDRIVE"] && ENV["HOMEPATH"]
36
+ possible_dirs << ENV["HOMESHARE"] + ENV["HOMEPATH"] if ENV["HOMESHARE"] && ENV["HOMEPATH"]
37
+ possible_dirs << ENV["USERPROFILE"] if ENV["USERPROFILE"]
38
+ raise NoValidEnvironmentVar if possible_dirs.empty?
39
+ possible_dirs.each do |possible_dir|
40
+ if Dir.exist?(possible_dir)
41
+ full_possible_dir = File.join(possible_dir, ".chef/accepted_licenses/")
42
+ l << full_possible_dir
43
+ break
44
+ end
45
+ end
46
+ end
47
+ else
48
+ l = [ "/etc/chef/accepted_licenses/" ]
49
+ l << File.join(ENV['HOME'], ".chef/accepted_licenses/") unless is_root?
50
+ end
51
+ l
52
+ end
53
+
54
+ def default_persist_location
55
+ license_locations[-1]
56
+ end
57
+
58
+ end
59
+
60
+ class NoValidEnvironmentVar < StandardError
61
+ def initialize
62
+ super("no valid environment variables set on Windows")
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,19 @@
1
+ module LicenseAcceptance
2
+ class EnvAcceptance
3
+
4
+ def check(env)
5
+ if env['CHEF_LICENSE'] && env['CHEF_LICENSE'].downcase == 'accept'
6
+ return true
7
+ end
8
+ return false
9
+ end
10
+
11
+ def check_no_persist(env)
12
+ if env['CHEF_LICENSE'] && env['CHEF_LICENSE'].downcase == 'accept-no-persist'
13
+ return true
14
+ end
15
+ return false
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,97 @@
1
+ require 'date'
2
+ require 'yaml'
3
+ require 'fileutils'
4
+ require 'etc'
5
+ require "license_acceptance/logger"
6
+
7
+ module LicenseAcceptance
8
+ class FileAcceptance
9
+ include Logger
10
+
11
+ attr_reader :config
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ end
16
+
17
+ INVOCATION_TIME = DateTime.now.freeze
18
+
19
+ # For all the given products in the product set, search all possible locations for the
20
+ # license acceptance files.
21
+ def check(product_relationship)
22
+ searching = [product_relationship.parent] + product_relationship.children
23
+ missing_licenses = searching.clone
24
+ logger.debug("Searching for the following licenses: #{missing_licenses.map(&:name)}")
25
+
26
+ searching.each do |product|
27
+ found = false
28
+ config.license_locations.each do |loc|
29
+ f = File.join(loc, product.filename)
30
+ if File.exist?(f)
31
+ found = true
32
+ logger.debug("Found license #{product.filename} at #{f}")
33
+ missing_licenses.delete(product)
34
+ break
35
+ end
36
+ end
37
+ break if missing_licenses.empty?
38
+ end
39
+ logger.debug("Missing licenses remaining: #{missing_licenses.map(&:name)}")
40
+ missing_licenses
41
+ end
42
+
43
+ def persist(product_relationship, missing_licenses)
44
+ parent = product_relationship.parent
45
+ parent_version = product_relationship.parent_version
46
+ root_dir = config.persist_location
47
+
48
+ if !Dir.exist?(root_dir)
49
+ begin
50
+ FileUtils.mkdir_p(root_dir)
51
+ rescue StandardError => e
52
+ msg = "Could not create license directory #{root_dir}"
53
+ logger.info "#{msg}\n\t#{e.message}\n\t#{e.backtrace.join("\n\t")}"
54
+ return [e]
55
+ end
56
+ end
57
+
58
+ errs = []
59
+ if missing_licenses.include?(parent)
60
+ err = persist_license(root_dir, parent, parent, parent_version)
61
+ errs << err unless err.nil?
62
+ end
63
+ product_relationship.children.each do |child|
64
+ if missing_licenses.include?(child)
65
+ err = persist_license(root_dir, child, parent, parent_version)
66
+ errs << err unless err.nil?
67
+ end
68
+ end
69
+ return errs
70
+ end
71
+
72
+ private
73
+
74
+ def persist_license(folder_path, product, parent, parent_version)
75
+ path = File.join(folder_path, product.filename)
76
+ logger.info("Persisting a license for #{product.name} at path #{path}")
77
+ File.open(path, File::WRONLY | File::CREAT | File::EXCL) do |license_file|
78
+ contents = {
79
+ name: product.name,
80
+ date_accepted: INVOCATION_TIME.iso8601,
81
+ accepting_product: parent.name,
82
+ accepting_product_version: parent_version,
83
+ user: Etc.getlogin,
84
+ file_format: 1,
85
+ }
86
+ contents = Hash[contents.map { |k, v| [k.to_s, v] }]
87
+ license_file << YAML.dump(contents)
88
+ end
89
+ return nil
90
+ rescue StandardError => e
91
+ msg = "Could not persist license to #{path}"
92
+ logger.info "#{msg}\n\t#{e.message}\n\t#{e.backtrace.join("\n\t")}"
93
+ return e
94
+ end
95
+
96
+ end
97
+ end