-
Notifications
You must be signed in to change notification settings - Fork 684
Add Chef Automate support to inspec compliance login
#2203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This provides a single interface for logging into either Chef Automate or Chef Compliance servers. Server type is evaluated at run time via HTTP responses from designated endpoints. This also moves the login logic from `Compliance::ComplianceCLI` to a separate set of modules in `Compliance::API`. This removes logic from Thor and allows for more in depth Unit testing. Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just flipped through this quickly, some comments and questions, probably worth a Zoom review when it's no longer WIP.
lib/bundles/inspec-compliance/api.rb
Outdated
|
||
# API Implementation does not hold any state by itself, | ||
# everything will be stored in local Configuration store | ||
class API # rubocop:disable Metrics/ClassLength | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: extra newline at class beginning
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pick all the nits! Fixed.
lib/bundles/inspec-compliance/api.rb
Outdated
@@ -238,5 +243,11 @@ def self.server_version_from_config(config) | |||
return nil unless config['version'].is_a?(Hash) | |||
config['version']['version'] | |||
end | |||
|
|||
def self.determine_server_type(url, insecure) | |||
return 'automate' if Compliance::HTTP.get(url + '/compliance/version', nil, insecure).code == '401' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more idiomatic to return symbols for something like this rather than strings.
return :automate if ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I always prefer going the idiomatic route. Fixed. Thanks @adamleff!
lib/bundles/inspec-compliance/api.rb
Outdated
def self.determine_server_type(url, insecure) | ||
return 'automate' if Compliance::HTTP.get(url + '/compliance/version', nil, insecure).code == '401' | ||
return 'compliance' if Compliance::HTTP.get(url + '/api/version', nil, insecure).code == '200' | ||
raise CannotDetermineServerType |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should also add an exception message when raising.
raise CannotDetermineServerType, "Unable to determine if #{url} is a Compliance or Automate server"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Fixed.
|
||
options['url'] = options['server'] + '/compliance' | ||
token = options['dctoken'] || options['token'] | ||
puts store_access_token(options, token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it's a WIP, to just a reminder to remove this :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
store_access_token
returns a string at this time. I will look at the flow and see if we should pass this up as a return from login
.
Will investigate.
def login(options) | ||
options['server_type'] = Compliance::API.determine_server_type(options['server'], options['insecure']) | ||
|
||
return Login::ComplianceServer.login(options) if options['server_type'] == 'compliance' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will you be doing the Automate login here too? Probably worth Zooming to review the expected flow, etc. would be good when you're ready :) This mid-method return with no comments as to why is confusing me as to your intentions for this method and how Automate login will be handled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This redirects to the login
method of the ComplianceServer
module if the server type is a Compliance server. I was returning here, since this will complete the login. Initially I had it just exit 0
at the end, but that was causing me issues with the Unit tests.
Will investigate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd second @adamleff here - If you're switching between two sets of logic, and you've encapsulated one in a module, I'd expect the other logic - for Automate login - to also be encapsulated in a module (presumably Login::AutomateServer).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally I thought only to encapsulate the Compliance server stuff because it would be deprecated in the future leaving only Login
...but the future is not now. I see your point.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adamleff let me know what you think of the flow now that I've modified the namespacing.
private | ||
|
||
def store_access_token(options, token) | ||
token_info = { type: 'dctoken', msg: 'data colletor token' } if options['dctoken'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will you ever not have either a dctoken or a token? What happens if token_info is not set?
I'd recommend changing it to something like:
token_info = if options['dctoken']
{ # dctoken stuff }
else
{ # non-dctoken stuff }
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Fixed.
Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
Agreed, a Zoom review would be helpful! Thanks for the initial feedback @adamleff. |
We should also update the README docs to reflect that the |
lib/bundles/inspec-compliance/cli.rb
Outdated
option :insecure, aliases: :k, type: :boolean, | ||
desc: 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers' | ||
def login_automate(server) # rubocop:disable Metrics/AbcSize | ||
def login(server) # rubocop:disable Metrics/AbcSize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the rubocop disable flag here anymore (when you refactored this out, the method became much simpler). You can run a rubocop scan with
bundle exec rake lint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great find! Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, nice catch! Fixed.
lib/bundles/inspec-compliance/api.rb
Outdated
|
||
# API Implementation does not hold any state by itself, | ||
# everything will be stored in local Configuration store | ||
class API # rubocop:disable Metrics/ClassLength | ||
extend Login |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be a matter of taste, but I'd usually have the module name here more deeply namespaced - like Compliance::API::Login . I don't know that we'd ever (within InSpec) have a name collision with a module named Login, but being specific avoids that possibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed! Fixed.
Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
7627db0
to
f7d36f1
Compare
@clintoncwolfe I was certain I did a Great catch! README.md in bundle is updated. I also removed references to |
Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
login
and login_automate
commandslogin
and login_automate
commands
login
and login_automate
commandsinspec compliance login
Changing this PR to target |
Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
f1f9c49
to
5448731
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is in great shape, @jerryaldrichiii. I'd like to see a bit of cleanup around where we raise exceptions and keeping method purposes focused. Let me know if any of my feedback is unclear.
class API | ||
module Login | ||
def login(options) | ||
options['server_type'] = Compliance::API.determine_server_type(options['server'], options['insecure']).to_s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coercing to a string here seems... weird? We don't need it to be a string here or most of these methods -- only when we store the config to disk. Can we move the coercion there?
lib/bundles/inspec-compliance/api.rb
Outdated
def self.determine_server_type(url, insecure) | ||
return :automate if Compliance::HTTP.get(url + '/compliance/version', nil, insecure).code == '401' | ||
return :compliance if Compliance::HTTP.get(url + '/api/version', nil, insecure).code == '200' | ||
raise CannotDetermineServerType, "Unable to determine if #{url} is a Chef Automate or Chef Compliance server" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just tossing out a thought here... I prefer to have exceptions raised at the point of the implementation where "it matters" (for lack of a better phrase). I'd almost prefer to see this method return nil
if it can't figure out the server type, and let the caller determine how to handle. That way other methods can use this if needed without having to worry about the stack unwinding on them.
def self.determine_server_type(url, insecure)
if Compliance::HTTP.get(url + '/compliance/version', nil, insecure).code == '401'
:automate
elsif Compliance::HTTP.get(url + '/api/version', nil, insecure).code == '200'
:compliance
end
end
def login(options) | ||
options['server_type'] = Compliance::API.determine_server_type(options['server'], options['insecure']).to_s | ||
|
||
return Login::ComplianceServer.login(options) if options['server_type'] == 'compliance' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is where I'd raise an exception if we don't know the server type since this is actually where an error should be raised to the user:
if options['server_type'] == :compliance
Login::ComplianceServer.login(options)
elsif options['server_type'] == :automate
Login::AutomateServer.login(options)
else
raise CannotDetermineServerType, # .....
end
|
||
options['url'] = options['server'] + '/compliance' | ||
token = options['dctoken'] || options['token'] | ||
puts store_access_token(options, token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize I'm being a bit pedantic here, but it feels strange to me to have a method called store_access_token
return a string intended to be user-facing messaging.
I'd rather see store_access_token
return the Compliance::Configuration
object it generated to successfully write the config to disk, and then a method that takes that config object and formats a message to the user. It will keep the concerns of the methods succinct and clear. This honestly just reads like a debug message to me. :)
config = store_access_token(options, token)
# print out a success message to the user
puts format_status_message(config)
|
||
raise msg unless success | ||
|
||
puts msg |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same feedback as above - I bet there's a good opportunity for reuse here, actually.
lib/bundles/inspec-compliance/cli.rb
Outdated
msg = login_automate_config(url, options['user'], options['dctoken'], options['usertoken'], options['ent'], options['insecure']) | ||
puts '', msg | ||
exit 0 | ||
if options['usertoken'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Despite what was in the issue that preceded this PR, we probably don't need this deprecation here since the whole command is deprecated. As long as the inspec compliance login
command doesn't accept a --usertoken
flag, we're covered.
This does the following: - Moves `CannotDetermineServerType` error to `.login` - Changes methods that store configuration to return the configuration - Moves user output to one location in `.login` - Makes other small improvements Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>
I made all the changes you suggested @adamleff. Feel free to give it another look! 😄 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lookin' good, @jerryaldrichiii - thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jerryaldrichiii Awesome work. I just have some mini-questions before we can go forward
@@ -5,13 +5,16 @@ | |||
require 'net/http' | |||
require 'uri' | |||
|
|||
require_relative 'api/login' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we move this file to the api directory too? Feels strange to have a api.rb
and api
directory. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's actually pretty standard in Ruby.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's common in Ruby. I'm fine with it if you are @chris-rock.
@@ -238,5 +241,13 @@ def self.server_version_from_config(config) | |||
return nil unless config['version'].is_a?(Hash) | |||
config['version']['version'] | |||
end | |||
|
|||
def self.determine_server_type(url, insecure) | |||
if Compliance::HTTP.get(url + '/compliance/version', nil, insecure).code == '401' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this also return 200 if the user is already logged in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not passing any headers to the GET
request so it should always act as if the user is not logged in.
|
||
case options['server_type'] | ||
when :automate | ||
config = Login::AutomateServer.login(options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice separation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Took some work that's for sure 😅
end | ||
|
||
puts '', msg | ||
Compliance::API.login(options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice clean-up! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works like a charm!
This provides a single interface for logging into either Chef Automate or Chef Compliance servers. Server type is evaluated at run time via HTTP responses from designated endpoints.
This also moves the login logic from
Compliance::ComplianceCLI
to a separate set of modules inCompliance::API
. Thus removing logic from Thor and allowing for more in depth Unit testing.This also adds deprecation warnings for
login_automate
and--usertoken
.This fixes #1795.
Signed-off-by: Jerry Aldrich jerryaldrichiii@gmail.com