chef-licensing 0.4.43

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -0
  3. data/chef-licensing.gemspec +35 -0
  4. data/lib/chef-licensing/api/client.rb +39 -0
  5. data/lib/chef-licensing/api/describe.rb +62 -0
  6. data/lib/chef-licensing/api/license_feature_entitlement.rb +55 -0
  7. data/lib/chef-licensing/api/license_software_entitlement.rb +53 -0
  8. data/lib/chef-licensing/api/list_licenses.rb +30 -0
  9. data/lib/chef-licensing/api/parser/client.rb +100 -0
  10. data/lib/chef-licensing/api/parser/describe.rb +118 -0
  11. data/lib/chef-licensing/cli_flags/mixlib_cli.rb +28 -0
  12. data/lib/chef-licensing/cli_flags/thor.rb +21 -0
  13. data/lib/chef-licensing/config.rb +44 -0
  14. data/lib/chef-licensing/config_fetcher/arg_fetcher.rb +38 -0
  15. data/lib/chef-licensing/config_fetcher/env_fetcher.rb +21 -0
  16. data/lib/chef-licensing/context.rb +98 -0
  17. data/lib/chef-licensing/exceptions/client_error.rb +9 -0
  18. data/lib/chef-licensing/exceptions/describe_error.rb +9 -0
  19. data/lib/chef-licensing/exceptions/error.rb +4 -0
  20. data/lib/chef-licensing/exceptions/feature_not_entitled.rb +9 -0
  21. data/lib/chef-licensing/exceptions/invalid_license.rb +10 -0
  22. data/lib/chef-licensing/exceptions/license_generation_failed.rb +9 -0
  23. data/lib/chef-licensing/exceptions/license_generation_rejected.rb +7 -0
  24. data/lib/chef-licensing/exceptions/list_licenses_error.rb +12 -0
  25. data/lib/chef-licensing/exceptions/missing_api_credentials_error.rb +7 -0
  26. data/lib/chef-licensing/exceptions/restful_client_connection_error.rb +9 -0
  27. data/lib/chef-licensing/exceptions/restful_client_error.rb +9 -0
  28. data/lib/chef-licensing/exceptions/software_not_entitled.rb +9 -0
  29. data/lib/chef-licensing/license.rb +151 -0
  30. data/lib/chef-licensing/license_key_fetcher/base.rb +28 -0
  31. data/lib/chef-licensing/license_key_fetcher/chef_licensing_interactions.yaml +534 -0
  32. data/lib/chef-licensing/license_key_fetcher/file.rb +275 -0
  33. data/lib/chef-licensing/license_key_fetcher/prompt.rb +43 -0
  34. data/lib/chef-licensing/license_key_fetcher.rb +314 -0
  35. data/lib/chef-licensing/license_key_generator.rb +47 -0
  36. data/lib/chef-licensing/license_key_validator.rb +24 -0
  37. data/lib/chef-licensing/licensing_service/local.rb +29 -0
  38. data/lib/chef-licensing/list_license_keys.rb +142 -0
  39. data/lib/chef-licensing/restful_client/base.rb +139 -0
  40. data/lib/chef-licensing/restful_client/middleware/exceptions_handler.rb +16 -0
  41. data/lib/chef-licensing/restful_client/v1.rb +17 -0
  42. data/lib/chef-licensing/tui_engine/tui_actions.rb +238 -0
  43. data/lib/chef-licensing/tui_engine/tui_engine.rb +174 -0
  44. data/lib/chef-licensing/tui_engine/tui_engine_state.rb +62 -0
  45. data/lib/chef-licensing/tui_engine/tui_exceptions.rb +17 -0
  46. data/lib/chef-licensing/tui_engine/tui_interaction.rb +17 -0
  47. data/lib/chef-licensing/tui_engine/tui_prompt.rb +117 -0
  48. data/lib/chef-licensing/tui_engine.rb +2 -0
  49. data/lib/chef-licensing/version.rb +3 -0
  50. data/lib/chef-licensing.rb +70 -0
  51. metadata +191 -0
@@ -0,0 +1,98 @@
1
+ # Context defines the interface for state management in chef-licensing
2
+ # Different states : local or global
3
+
4
+ # Licensing service detection
5
+ require_relative "licensing_service/local"
6
+
7
+ module ChefLicensing
8
+ class Context
9
+
10
+ attr_accessor :state
11
+ attr_reader :logger, :options
12
+
13
+ class << self
14
+ attr_writer :current_context
15
+
16
+ def local_licensing_service?
17
+ ChefLicensing::Config.is_local_license_service ||= LicensingService::Local.detected?
18
+ end
19
+
20
+ # Implement methods on current context
21
+ # Current context changes the state determined using LicensingService module
22
+
23
+ # Return license keys from current context
24
+ def license_keys(options = {})
25
+ current_context(options).license_keys
26
+ end
27
+
28
+ private
29
+
30
+ def current_context(options)
31
+ return @current_context if @current_context
32
+
33
+ @current_context = context_based_on_state(options)
34
+ end
35
+
36
+ def context_based_on_state(options)
37
+ if local_licensing_service?
38
+ new(Local.new, options)
39
+ else
40
+ new(Global.new, options)
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ # @param [State] state
47
+ def initialize(state, options = {})
48
+ @options = options
49
+ @logger = ChefLicensing::Config.logger
50
+ transition_to(state)
51
+ end
52
+
53
+ # The Context allows changing the State object
54
+ def transition_to(state)
55
+ logger.debug "Chef Licensing Context: Transition to #{state.class}"
56
+ @state = state
57
+ @state.context = self
58
+ @state.options = options
59
+ end
60
+
61
+ # The Context delegates part of its behavior to the current State object.
62
+ def license_keys
63
+ @state.license_keys
64
+ end
65
+
66
+ class State
67
+ attr_accessor :context, :options
68
+
69
+ # @abstract
70
+ def license_keys
71
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
72
+ end
73
+ end
74
+
75
+ # Implement various behaviors, associated with a state of the Context.
76
+
77
+ class Local < State
78
+ def license_keys
79
+ @license_keys ||= ChefLicensing::Api::ListLicenses.info || []
80
+ end
81
+ end
82
+
83
+ class Global < State
84
+ def license_keys
85
+ @license_keys ||= fetch_license_keys_from_file || []
86
+ end
87
+
88
+ def fetch_license_keys_from_file
89
+ file_fetcher = LicenseKeyFetcher::File.new(options)
90
+ if file_fetcher.persisted?
91
+ # This could be useful if the file was writable in past but is not writable in current scenario and new keys are not persisted in the file
92
+ file_fetcher.fetch
93
+ end
94
+ end
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "error"
2
+
3
+ module ChefLicensing
4
+ class ClientError < Error
5
+ def message
6
+ super || "License Client API failure"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "error"
2
+
3
+ module ChefLicensing
4
+ class DescribeError < Error
5
+ def message
6
+ super || "License Describe API failure"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module ChefLicensing
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "error"
2
+
3
+ module ChefLicensing
4
+ class FeatureNotEntitled < Error
5
+ def message
6
+ super || "Feature not entitled"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ require_relative "error"
2
+
3
+ module ChefLicensing
4
+ class InvalidLicense < Error
5
+ def message
6
+ super || "Invalid License"
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,9 @@
1
+ require_relative "error"
2
+
3
+ module ChefLicensing
4
+ class LicenseGenerationFailed < Error
5
+ def message
6
+ super || "License Generation Failed"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module ChefLicensing
2
+ class LicenseGenerationRejected < Error
3
+ def message
4
+ super || "License Generation Rejected"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ require_relative "error"
2
+
3
+ module ChefLicensing
4
+ class ListLicensesError < Error
5
+ attr_reader :status_code
6
+
7
+ def initialize(message, status_code)
8
+ @status_code = status_code
9
+ super(message)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ module ChefLicensing
2
+ class MissingAPICredentialsError < Error
3
+ def message
4
+ super || "Missing API credentials. Check README for more details."
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "error"
2
+
3
+ module ChefLicensing
4
+ class RestfulClientConnectionError < Error
5
+ def message
6
+ super || "Restful Client Connection Error"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "error"
2
+
3
+ module ChefLicensing
4
+ class RestfulClientError < Error
5
+ def message
6
+ super || "License Server Error"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "error"
2
+
3
+ module ChefLicensing
4
+ class SoftwareNotEntitled < Error
5
+ def message
6
+ super || "Software not entitled"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,151 @@
1
+ require_relative "api/parser/client" unless defined?(ChefLicensing::Api::Parser::Client)
2
+ require_relative "api/parser/describe" unless defined?(ChefLicensing::Api::Parser::Describe)
3
+ require_relative "config"
4
+
5
+ # License data model has an id, status, type of license, entitlements and limits.
6
+
7
+ module ChefLicensing
8
+ class License
9
+
10
+ attr_reader :id, :license_type , :status, :expiration_date, :expiration_status
11
+ attr_accessor :number_of_days_in_expiration
12
+
13
+ def initialize(opts = {})
14
+ # API parser based on the API call
15
+ @parser = opts[:api_parser].new(opts[:data])
16
+ @product_name = ChefLicensing::Config.chef_product_name
17
+
18
+ @id = @parser.parse_id
19
+ @status = @parser.parse_status
20
+ @license_type = @parser.parse_license_type
21
+
22
+ # expiration details
23
+ @expiration_date = @parser.parse_expiration_date
24
+ @expiration_status = @parser.parse_license_expiration_status
25
+
26
+ # usage details
27
+ @limits = []
28
+
29
+ # Entitlements
30
+ @feature_entitlements = []
31
+ @software_entitlements = []
32
+ @asset_entitlements = []
33
+ end
34
+
35
+ def feature_entitlements
36
+ return @feature_entitlements unless @feature_entitlements.empty?
37
+
38
+ feat_entitlements = []
39
+ feat_entitlements_data = @parser.parse_feature_entitlements
40
+ feat_entitlements_data.each do |data|
41
+ feat_entitlements << FeatureEntitlement.new(data)
42
+ end
43
+ @feature_entitlements = feat_entitlements
44
+ end
45
+
46
+ def software_entitlements
47
+ return @software_entitlements unless @software_entitlements.empty?
48
+
49
+ sw_entitlements = []
50
+ sw_entitlements_data = @parser.parse_software_entitlements
51
+ sw_entitlements_data.each do |data|
52
+ sw_entitlements << SoftwareEntitlement.new(data)
53
+ end
54
+ @software_entitlements = sw_entitlements
55
+ end
56
+
57
+ def asset_entitlements
58
+ return @asset_entitlements unless @asset_entitlements.empty?
59
+
60
+ asset_entitlements = []
61
+ asset_entitlements_data = @parser.parse_asset_entitlements
62
+ asset_entitlements_data.each do |data|
63
+ asset_entitlements << AssetEntitlement.new(data)
64
+ end
65
+ @asset_entitlements = asset_entitlements
66
+ end
67
+
68
+ def limits
69
+ return @limits unless @limits.empty?
70
+
71
+ limits = []
72
+ limits_data = @parser.parse_limits
73
+ limits_data.each do |data|
74
+ limits << Limit.new(data, { product_name: @product_name })
75
+ end
76
+ @limits = limits
77
+ end
78
+
79
+ # License has list of limits for different softwares which includes usage details.
80
+ class Limit
81
+ attr_reader :usage_status, :usage_limit, :usage_measure, :used, :software
82
+
83
+ def initialize(limit_data, opts = {})
84
+ @usage_status = limit_data["usage_status"]
85
+ @usage_limit = limit_data["usage_limit"]
86
+ @usage_measure = limit_data["usage_measure"]
87
+ @used = limit_data["used"]
88
+ @software = limit_data["software"] || opts[:product_name]
89
+ end
90
+ end
91
+
92
+ # License can be entitled to list of features.
93
+ class FeatureEntitlement
94
+ attr_reader :id, :name, :entitled, :status
95
+
96
+ def initialize(entitlement_data)
97
+ @id = entitlement_data["id"]
98
+ @name = entitlement_data["name"]
99
+ @entitled = entitlement_data["entitled"]
100
+ @status = entitlement_data["status"]
101
+ end
102
+ end
103
+
104
+ # License can be entitled to list of softwares.
105
+ class SoftwareEntitlement
106
+ attr_reader :id, :name, :entitled, :status
107
+
108
+ def initialize(entitlement_data)
109
+ @id = entitlement_data["id"]
110
+ @name = entitlement_data["name"]
111
+ @entitled = entitlement_data["entitled"]
112
+ @status = entitlement_data["status"]
113
+ end
114
+ end
115
+
116
+ # License can be entitled to list of assets.
117
+ class AssetEntitlement
118
+ attr_reader :id, :name, :entitled, :status
119
+
120
+ def initialize(entitlement_data)
121
+ @id = entitlement_data["id"]
122
+ @name = entitlement_data["name"]
123
+ @entitled = entitlement_data["entitled"]
124
+ @status = entitlement_data["status"]
125
+ end
126
+ end
127
+
128
+ def have_grace?
129
+ status.eql?("Grace")
130
+ end
131
+
132
+ def expired?
133
+ status.eql?("Expired")
134
+ end
135
+
136
+ def active?
137
+ status.eql?("Active")
138
+ end
139
+
140
+ def expiring_or_expired?
141
+ have_grace? || expired? || about_to_expire?
142
+ end
143
+
144
+ def about_to_expire?
145
+ require "Date" unless defined?(Date)
146
+ self.number_of_days_in_expiration = (Date.parse(expiration_date) - Date.today).to_i
147
+ # starts to nag before a week when about to expire
148
+ status.eql?("Active") && expiration_status.eql?("Expired") && ((1..7).include? number_of_days_in_expiration)
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,28 @@
1
+ require_relative "../exceptions/invalid_license"
2
+ module ChefLicensing
3
+ class LicenseKeyFetcher
4
+ class Base
5
+ # @example LICENSE UUID: tmns-58555821-925e-4a27-8fdc-e79dae5a425b-9763
6
+ # @example SERIAL NUMBER: A8BCD1XS2B4F6FYBWG8TE0N490
7
+ # @example COMMERCIAL KEY: e0b8f309-6abd-4668-909b-ef2d5fc043a1
8
+ # 4[license type]-(8-4-4-4-12)[GUID]-4[timestamp]
9
+ LICENSE_KEY_REGEX = "([a-z]{4}-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-[0-9]{1,4})".freeze
10
+ LICENSE_KEY_PATTERN_DESC = "Hexadecimal".freeze
11
+ # Serial number is a 26 character alphanumeric string
12
+ SERIAL_KEY_REGEX = "([A-Z0-9]{26})".freeze
13
+ SERIAL_KEY_PATTERN_DESC = "26 character alphanumeric string".freeze
14
+ COMMERCIAL_KEY_REGEX = "([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})".freeze
15
+ QUIT_KEY_REGEX = "(q|Q)".freeze
16
+
17
+ def self.verify_and_extract_license(license_key)
18
+ if license_key && (match = license_key.match(/^#{LICENSE_KEY_REGEX}$/) || license_key.match(/^#{SERIAL_KEY_REGEX}$/) || license_key.match(/^#{COMMERCIAL_KEY_REGEX}$/))
19
+ match[1]
20
+ else
21
+ raise InvalidLicenseKeyFormat, "Malformed License Key passed on command line - should be #{LICENSE_KEY_PATTERN_DESC} or #{SERIAL_KEY_PATTERN_DESC}"
22
+ end
23
+ end
24
+
25
+ class InvalidLicenseKeyFormat < InvalidLicense; end
26
+ end
27
+ end
28
+ end