chef-licensing 0.4.43
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/LICENSE +1 -0
- data/chef-licensing.gemspec +35 -0
- data/lib/chef-licensing/api/client.rb +39 -0
- data/lib/chef-licensing/api/describe.rb +62 -0
- data/lib/chef-licensing/api/license_feature_entitlement.rb +55 -0
- data/lib/chef-licensing/api/license_software_entitlement.rb +53 -0
- data/lib/chef-licensing/api/list_licenses.rb +30 -0
- data/lib/chef-licensing/api/parser/client.rb +100 -0
- data/lib/chef-licensing/api/parser/describe.rb +118 -0
- data/lib/chef-licensing/cli_flags/mixlib_cli.rb +28 -0
- data/lib/chef-licensing/cli_flags/thor.rb +21 -0
- data/lib/chef-licensing/config.rb +44 -0
- data/lib/chef-licensing/config_fetcher/arg_fetcher.rb +38 -0
- data/lib/chef-licensing/config_fetcher/env_fetcher.rb +21 -0
- data/lib/chef-licensing/context.rb +98 -0
- data/lib/chef-licensing/exceptions/client_error.rb +9 -0
- data/lib/chef-licensing/exceptions/describe_error.rb +9 -0
- data/lib/chef-licensing/exceptions/error.rb +4 -0
- data/lib/chef-licensing/exceptions/feature_not_entitled.rb +9 -0
- data/lib/chef-licensing/exceptions/invalid_license.rb +10 -0
- data/lib/chef-licensing/exceptions/license_generation_failed.rb +9 -0
- data/lib/chef-licensing/exceptions/license_generation_rejected.rb +7 -0
- data/lib/chef-licensing/exceptions/list_licenses_error.rb +12 -0
- data/lib/chef-licensing/exceptions/missing_api_credentials_error.rb +7 -0
- data/lib/chef-licensing/exceptions/restful_client_connection_error.rb +9 -0
- data/lib/chef-licensing/exceptions/restful_client_error.rb +9 -0
- data/lib/chef-licensing/exceptions/software_not_entitled.rb +9 -0
- data/lib/chef-licensing/license.rb +151 -0
- data/lib/chef-licensing/license_key_fetcher/base.rb +28 -0
- data/lib/chef-licensing/license_key_fetcher/chef_licensing_interactions.yaml +534 -0
- data/lib/chef-licensing/license_key_fetcher/file.rb +275 -0
- data/lib/chef-licensing/license_key_fetcher/prompt.rb +43 -0
- data/lib/chef-licensing/license_key_fetcher.rb +314 -0
- data/lib/chef-licensing/license_key_generator.rb +47 -0
- data/lib/chef-licensing/license_key_validator.rb +24 -0
- data/lib/chef-licensing/licensing_service/local.rb +29 -0
- data/lib/chef-licensing/list_license_keys.rb +142 -0
- data/lib/chef-licensing/restful_client/base.rb +139 -0
- data/lib/chef-licensing/restful_client/middleware/exceptions_handler.rb +16 -0
- data/lib/chef-licensing/restful_client/v1.rb +17 -0
- data/lib/chef-licensing/tui_engine/tui_actions.rb +238 -0
- data/lib/chef-licensing/tui_engine/tui_engine.rb +174 -0
- data/lib/chef-licensing/tui_engine/tui_engine_state.rb +62 -0
- data/lib/chef-licensing/tui_engine/tui_exceptions.rb +17 -0
- data/lib/chef-licensing/tui_engine/tui_interaction.rb +17 -0
- data/lib/chef-licensing/tui_engine/tui_prompt.rb +117 -0
- data/lib/chef-licensing/tui_engine.rb +2 -0
- data/lib/chef-licensing/version.rb +3 -0
- data/lib/chef-licensing.rb +70 -0
- 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,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
|