Skip to content
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

Platform framework and detect DSL #204

Merged
merged 24 commits into from
Nov 13, 2017
Merged

Platform framework and detect DSL #204

merged 24 commits into from
Nov 13, 2017

Conversation

jquick
Copy link
Contributor

@jquick jquick commented Oct 26, 2017

This change moves the detect logic for platforms into a new framework. You can create platforms on the fly using the new platform DSL. There are still some detect tests to write for specific platforms.

For more info about this RFC please visit inspec/inspec#1661

jquick added 10 commits October 13, 2017 09:02
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
@jquick jquick requested a review from a team October 26, 2017 16:37
@jquick jquick requested a review from adamleff October 26, 2017 16:38
include Train::Platforms::Detect::Linux
include Train::Platforms::Detect::Windows

def rbconfig(regex)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this name as this will be part of what you can use in the detect logic. Maybe "get_host_from_ruby" or something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about rbconfig_host_os or ruby_host_os? Not a fan of get_ methods personally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like ruby_host_os

Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
@jquick jquick force-pushed the jq/platform_objects branch from 3179679 to 7fd444d Compare October 30, 2017 14:29
Signed-off-by: Jared Quick <jquick@chef.io>
Copy link
Contributor

@adamleff adamleff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jquick this is pretty stellar work for your first time mucking around in Train/InSpec. Nicely done.

I have a bunch of style feedback and questions, and I'm happy to get on a Zoom and talk through them if needed. Ultimately, the structure, concepts, and layout all make sense to me and I'm happy to see them in this state. Great work.

module Train
class Platform
include Train::Platforms::Common
attr_accessor :condition, :families, :backend, :platform, :family_hierarchy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick warning, but could I implore you to sort these alphabetically so, as they grow/shrink, it's easier to find what you're looking for?

@family_hierarchy = []
@platform = {}
@detect = nil
@title = name =~ /^[[:alpha:]]+$/ ? name.capitalize : name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious as to why this match is needed. Even if the name is just a number, making it a string and capitalizing it won't throw an error.

Would it be simpler to make it this?: @title = name.to_s.capitalize

@platform
end

# Add genaric family? and platform methods to an existing platform
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/genaric/generic

(at least you know I read the comments, too 🙂 )

family_list = Train::Platforms.families
family_list.each_value do |k|
next if respond_to?(k.name + '?')
define_singleton_method(k.name + '?') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use do ... end for these blocks that consume multiple lines?

# Helper methods for direct platform info
@platform.each_key do |m|
next if respond_to?(m)
define_singleton_method(m) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use do ... end for these blocks that consume multiple lines?

def scan_children(parent)
parent.children.each do |plat, condition|
next if plat.detect.nil?
result = instance_eval(&plat.detect)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above re: unused variable result with the exception of seeing if it's true or not.

end

def scan_family_children(plat)
child_result = scan_children(plat) if !plat.children.nil?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be consistent with our uses of if and unless... since you do a bunch of unless blah.nil?, we should do that here too:

child_result = scan_children(plat) unless plat.children.nil?

@families = {}
@children = {}
@detect = nil
@title = name =~ /^[[:alpha:]]+$/ ? "#{name.capitalize} Family" : name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as above re: Platform name/title -- I'm curious as to why this regex match is necessary.

@@ -0,0 +1,456 @@
# encoding: utf-8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stylistically, this file reads really weird to me because it basically takes action when it's required. We don't really do anything else like that without our codebase.

I'm wondering if it would make sense to wrap this in a class/method and call that in lib/train/platforms.rb

For example:

module Train::Platforms
  class Specifications
    def self.load_specifications
      plat = Train::Platforms

      plat.family('windows')
        .detect # .... etc ....
    end
  end
end

... and then at the very bottom of lib/train/platforms:

Train::Platforms::Specifications.load_specifications

Honestly, this file just looks like a left-behind test file even though it's the meat of our data mapping 🙂

What do you think?

Copy link
Contributor Author

@jquick jquick Oct 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this to a proper class. I decided to load it on the transport creation as we are going to have the API transports require a different set of specifications.

def local?
true
end

def os
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're not adding any value by having this method defined (i.e. we're not adding a deprecation warning), can we just call alias instead and alias os to platform with a comment added as to why we're doing it (i.e. because InSpec still expects it to be os).

Same comment applies to all the other Transport classes.

Signed-off-by: Jared Quick <jquick@chef.io>
end

def unix_file_exist?(path)
@backend.run_command("test -f #{path}").exit_status.zero?
end

def uname_call(cmd)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really just run_command now, rather than uname-specific... maybe we should change the method name and that way it can be used for other things too?

Signed-off-by: Jared Quick <jquick@chef.io>
Copy link
Contributor

@chris-rock chris-rock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @jquick I added some questions.

end

# we need to keep os as a method for backwards compatibility with inspec
alias os platform
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do this in our base connection? This ensures that it is consistent across all connections and the implementations can focus on using platform

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we need a os_transport then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be fine, if we are not going to be using BaseConnection for API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also move it to base for now and add a os connection, once we add api

@@ -56,6 +56,7 @@ def reuse_connection
class Train::Transports::Docker
class Connection < BaseConnection
def initialize(conf)
Train::Platforms::Specifications::OS.load_specifications
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move this to base connection?

Copy link
Contributor Author

@jquick jquick Nov 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason it was done on a per transport level was that we will likely have a Train::Platforms::Specifications::API.load_specifications for http and api transports. I don't think we want to load the OS for all transports including API ones.

That is if we decide to sub the BaseConnection for API as well. Maybe we make a new one for that.

@@ -55,10 +56,13 @@ def close
@session = nil
end

def os
@os ||= OS.new(self)
def platform
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the method calls are more similar now, we could move that to base connection too?

@@ -0,0 +1,56 @@
# encoding: utf-8

require 'train/platforms/detect/os_linux'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like os_common.rb, os_linux.rb and os_windows.rb are just detect helper, the actual detection is done with the specifications. Am I right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, they are just helper methods that can be used inside the detection blocks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make this more clear in our naming?

# start with the platform/families who have no families (the top levels)
top = Train::Platforms.top_platforms
top.each do |_name, plat|
next unless plat.detect
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we implement a method detect in family.rb? This would abstract the issue for us here. Also I am not sure if we want to use lambda for detect, so that we do not need to call instance_eval

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added the false lambda instead of the check for detect. This way everything can be evaluated accordingly.
As far as moving away from instance_eval/exec. The reason we are doing this is so the detect logic will have access to all the helper methods (os_common, os_linux, etc...) in the detect context. We may be able to require all this logic in each instance but that seems overkill for the memory and really doing the same thing under the hood. This also makes it easy to create custom platforms on the fly via inspec or other instances. After talking it over and due to its controlled nature we are voting to keep it for now. I can comment it up to explain a bit more in code.

@@ -0,0 +1,80 @@
# encoding: utf-8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this file not be located in lib/train/platforms/platform?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I do think that should be moved in after thinking about it.

Signed-off-by: Jared Quick <jquick@chef.io>
@jquick jquick force-pushed the jq/platform_objects branch from 83e3599 to 453a51d Compare November 1, 2017 18:19
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
Signed-off-by: Jared Quick <jquick@chef.io>
@jquick jquick force-pushed the jq/platform_objects branch from d76d0f4 to babc875 Compare November 9, 2017 19:43
@jquick jquick merged commit babc875 into master Nov 13, 2017
adamleff added a commit that referenced this pull request Nov 13, 2017
Signed-off-by: Adam Leff <adam@leff.co>
chris-rock pushed a commit that referenced this pull request Nov 13, 2017
Signed-off-by: Adam Leff <adam@leff.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants