license-acceptance 0.0.1 → 0.2.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.
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