chef-licensing 0.4.43
Sign up to get free protection for your applications and to get access to all the features.
- 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
|