train 1.2.0 → 1.3.0
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 +4 -4
- data/CHANGELOG.md +20 -2
- data/lib/train/errors.rb +8 -5
- data/lib/train/platforms.rb +3 -2
- data/lib/train/platforms/detect/helpers/os_common.rb +55 -1
- data/lib/train/platforms/detect/helpers/os_windows.rb +41 -0
- data/lib/train/platforms/detect/specifications/os.rb +1 -0
- data/lib/train/platforms/detect/uuid.rb +34 -0
- data/lib/train/platforms/platform.rb +4 -0
- data/lib/train/transports/aws.rb +7 -0
- data/lib/train/transports/azure.rb +36 -7
- data/lib/train/transports/cisco_ios.rb +140 -0
- data/lib/train/transports/local.rb +2 -1
- data/lib/train/transports/mock.rb +2 -0
- data/lib/train/version.rb +1 -1
- data/test/unit/platforms/detect/uuid_test.rb +133 -0
- data/test/unit/transports/aws_test.rb +24 -0
- data/test/unit/transports/azure_test.rb +32 -0
- data/test/unit/transports/cisco_ios.rb +94 -0
- metadata +8 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: '095b1ae4856669cbe224dffbfa09f555ce7c0d05'
         | 
| 4 | 
            +
              data.tar.gz: ee6cc55a500e4466e497e853e49963a05b1f85d2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: ea321b3f07afc791e16628f7d4f26277dc7caed537e29f8351dd8934741af77b8ee137ce35184d820c2aca2e02b335d6fdb538784577f11a06e9c7be1924ef07
         | 
| 7 | 
            +
              data.tar.gz: d3a80455ea2819ccdb0ba1e92fdd94c7525604d6dfe49345902531e44eef9c06fd08d39b7911ac9464d44da4f762cd46fe10ab377804327237e4ba7e6405f987
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,7 +1,24 @@ | |
| 1 1 | 
             
            # Change Log
         | 
| 2 2 |  | 
| 3 | 
            -
            ## [1. | 
| 4 | 
            -
            [Full Changelog](https://github.com/chef/train/compare/v1. | 
| 3 | 
            +
            ## [1.3.0](https://github.com/chef/train/tree/1.3.0) (2018-03-29)
         | 
| 4 | 
            +
            [Full Changelog](https://github.com/chef/train/compare/v1.2.0...1.3.0)
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            **Implemented enhancements:**
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            - Update errors to have a base type of Train::Error [\#273](https://github.com/chef/train/pull/273) ([marcparadise](https://github.com/marcparadise))
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            **Closed issues:**
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            - RFC: Generate unique uuid for platforms [\#264](https://github.com/chef/train/issues/264)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            **Merged pull requests:**
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            - Add MSI connection option for azure. [\#272](https://github.com/chef/train/pull/272) ([jquick](https://github.com/jquick))
         | 
| 17 | 
            +
            - Add transport for Cisco IOS [\#271](https://github.com/chef/train/pull/271) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
         | 
| 18 | 
            +
            - Add platform uuid information. [\#270](https://github.com/chef/train/pull/270) ([jquick](https://github.com/jquick))
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            ## [v1.2.0](https://github.com/chef/train/tree/v1.2.0) (2018-03-15)
         | 
| 21 | 
            +
            [Full Changelog](https://github.com/chef/train/compare/v1.1.1...v1.2.0)
         | 
| 5 22 |  | 
| 6 23 | 
             
            **Implemented enhancements:**
         | 
| 7 24 |  | 
| @@ -14,6 +31,7 @@ | |
| 14 31 |  | 
| 15 32 | 
             
            **Merged pull requests:**
         | 
| 16 33 |  | 
| 34 | 
            +
            - Release train 1.2.0 [\#269](https://github.com/chef/train/pull/269) ([jquick](https://github.com/jquick))
         | 
| 17 35 | 
             
            - Force 64bit powershell for 32bit ruby running on 64bit windows [\#266](https://github.com/chef/train/pull/266) ([jquick](https://github.com/jquick))
         | 
| 18 36 | 
             
            - support cisco ios xe [\#262](https://github.com/chef/train/pull/262) ([arlimus](https://github.com/arlimus))
         | 
| 19 37 | 
             
            - Create a master OS family and refactor specifications [\#261](https://github.com/chef/train/pull/261) ([jquick](https://github.com/jquick))
         | 
    
        data/lib/train/errors.rb
    CHANGED
    
    | @@ -9,21 +9,24 @@ | |
| 9 9 | 
             
            # Licensed under the Apache License, Version 2.0 (the "License");
         | 
| 10 10 |  | 
| 11 11 | 
             
            module Train
         | 
| 12 | 
            +
              # Base exception for any exception explicitly raised by the Train library.
         | 
| 13 | 
            +
              class Error < ::StandardError; end
         | 
| 14 | 
            +
             | 
| 12 15 | 
             
              # Base exception class for all exceptions that are caused by user input
         | 
| 13 16 | 
             
              # errors.
         | 
| 14 | 
            -
              class UserError <  | 
| 17 | 
            +
              class UserError < Error; end
         | 
| 15 18 |  | 
| 16 19 | 
             
              # Base exception class for all exceptions that are caused by incorrect use
         | 
| 17 20 | 
             
              # of an API.
         | 
| 18 | 
            -
              class ClientError <  | 
| 21 | 
            +
              class ClientError < Error; end
         | 
| 19 22 |  | 
| 20 23 | 
             
              # Base exception class for all exceptions that are caused by other failures
         | 
| 21 24 | 
             
              # in the transport layer.
         | 
| 22 | 
            -
              class TransportError <  | 
| 25 | 
            +
              class TransportError < Error; end
         | 
| 23 26 |  | 
| 24 27 | 
             
              # Exception for when no platform can be detected.
         | 
| 25 | 
            -
              class PlatformDetectionFailed <  | 
| 28 | 
            +
              class PlatformDetectionFailed < Error; end
         | 
| 26 29 |  | 
| 27 30 | 
             
              # Exception for when a invalid cache type is passed.
         | 
| 28 | 
            -
              class UnknownCacheType <  | 
| 31 | 
            +
              class UnknownCacheType < Error; end
         | 
| 29 32 | 
             
            end
         | 
    
        data/lib/train/platforms.rb
    CHANGED
    
    | @@ -1,12 +1,13 @@ | |
| 1 1 | 
             
            # encoding: utf-8
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'train/platforms/common'
         | 
| 4 | 
            -
            require 'train/platforms/family'
         | 
| 5 | 
            -
            require 'train/platforms/platform'
         | 
| 6 4 | 
             
            require 'train/platforms/detect'
         | 
| 7 5 | 
             
            require 'train/platforms/detect/scanner'
         | 
| 8 6 | 
             
            require 'train/platforms/detect/specifications/os'
         | 
| 9 7 | 
             
            require 'train/platforms/detect/specifications/api'
         | 
| 8 | 
            +
            require 'train/platforms/detect/uuid'
         | 
| 9 | 
            +
            require 'train/platforms/family'
         | 
| 10 | 
            +
            require 'train/platforms/platform'
         | 
| 10 11 |  | 
| 11 12 | 
             
            module Train::Platforms
         | 
| 12 13 | 
             
              class << self
         | 
| @@ -5,7 +5,7 @@ require 'train/platforms/detect/helpers/os_windows' | |
| 5 5 | 
             
            require 'rbconfig'
         | 
| 6 6 |  | 
| 7 7 | 
             
            module Train::Platforms::Detect::Helpers
         | 
| 8 | 
            -
              module OSCommon
         | 
| 8 | 
            +
              module OSCommon # rubocop:disable Metrics/ModuleLength
         | 
| 9 9 | 
             
                include Train::Platforms::Detect::Helpers::Linux
         | 
| 10 10 | 
             
                include Train::Platforms::Detect::Helpers::Windows
         | 
| 11 11 |  | 
| @@ -87,5 +87,59 @@ module Train::Platforms::Detect::Helpers | |
| 87 87 |  | 
| 88 88 | 
             
                  @cache[:cisco] = nil
         | 
| 89 89 | 
             
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def unix_uuid
         | 
| 92 | 
            +
                  uuid = unix_uuid_from_chef
         | 
| 93 | 
            +
                  uuid = unix_uuid_from_machine_file if uuid.nil?
         | 
| 94 | 
            +
                  uuid = uuid_from_command if uuid.nil?
         | 
| 95 | 
            +
                  raise Train::TransportError, 'Cannot find a UUID for your node.' if uuid.nil?
         | 
| 96 | 
            +
                  uuid
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def unix_uuid_from_chef
         | 
| 100 | 
            +
                  file = @backend.file('/var/chef/cache/data_collector_metadata.json')
         | 
| 101 | 
            +
                  if file.exist? && !file.size.zero?
         | 
| 102 | 
            +
                    json = ::JSON.parse(file.content)
         | 
| 103 | 
            +
                    return json['node_uuid'] if json['node_uuid']
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                def unix_uuid_from_machine_file
         | 
| 108 | 
            +
                  %W(
         | 
| 109 | 
            +
                    /etc/chef/chef_guid
         | 
| 110 | 
            +
                    #{ENV['HOME']}/.chef/chef_guid
         | 
| 111 | 
            +
                    /etc/machine-id
         | 
| 112 | 
            +
                    /var/lib/dbus/machine-id
         | 
| 113 | 
            +
                    /var/db/dbus/machine-id
         | 
| 114 | 
            +
                  ).each do |path|
         | 
| 115 | 
            +
                    file = @backend.file(path)
         | 
| 116 | 
            +
                    next unless file.exist? && !file.size.zero?
         | 
| 117 | 
            +
                    return file.content.chomp if path =~ /guid/
         | 
| 118 | 
            +
                    return uuid_from_string(file.content.chomp)
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
                  nil
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                # This takes a command from the platform detect block to run.
         | 
| 124 | 
            +
                # We expect the command to return a unique identifier which
         | 
| 125 | 
            +
                # we turn into a UUID.
         | 
| 126 | 
            +
                def uuid_from_command
         | 
| 127 | 
            +
                  return unless @platform[:uuid_command]
         | 
| 128 | 
            +
                  result = @backend.run_command(@platform[:uuid_command])
         | 
| 129 | 
            +
                  uuid_from_string(result.stdout.chomp) if result.exit_status.zero? && !result.stdout.empty?
         | 
| 130 | 
            +
                end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                # This hashes the passed string into SHA1.
         | 
| 133 | 
            +
                # Then it downgrades the 160bit SHA1 to a 128bit
         | 
| 134 | 
            +
                # then we format it as a valid UUIDv5.
         | 
| 135 | 
            +
                def uuid_from_string(string)
         | 
| 136 | 
            +
                  hash = Digest::SHA1.new
         | 
| 137 | 
            +
                  hash.update(string)
         | 
| 138 | 
            +
                  ary = hash.digest.unpack('NnnnnN')
         | 
| 139 | 
            +
                  ary[2] = (ary[2] & 0x0FFF) | (5 << 12)
         | 
| 140 | 
            +
                  ary[3] = (ary[3] & 0x3FFF) | 0x8000
         | 
| 141 | 
            +
                  # rubocop:disable Style/FormatString
         | 
| 142 | 
            +
                  '%08x-%04x-%04x-%04x-%04x%08x' % ary
         | 
| 143 | 
            +
                end
         | 
| 90 144 | 
             
              end
         | 
| 91 145 | 
             
            end
         | 
| @@ -75,5 +75,46 @@ module Train::Platforms::Detect::Helpers | |
| 75 75 | 
             
                  arch_number = sys_info[:Architecture].to_i
         | 
| 76 76 | 
             
                  arch_map[arch_number]
         | 
| 77 77 | 
             
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                # This method scans the target os for a unique uuid to use
         | 
| 80 | 
            +
                def windows_uuid
         | 
| 81 | 
            +
                  uuid = windows_uuid_from_chef
         | 
| 82 | 
            +
                  uuid = windows_uuid_from_machine_file if uuid.nil?
         | 
| 83 | 
            +
                  uuid = windows_uuid_from_wmic if uuid.nil?
         | 
| 84 | 
            +
                  uuid = windows_uuid_from_registry if uuid.nil?
         | 
| 85 | 
            +
                  raise Train::TransportError, 'Cannot find a UUID for your node.' if uuid.nil?
         | 
| 86 | 
            +
                  uuid
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def windows_uuid_from_machine_file
         | 
| 90 | 
            +
                  %W(
         | 
| 91 | 
            +
                    #{ENV['SYSTEMDRIVE']}\\chef\\chef_guid
         | 
| 92 | 
            +
                    #{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}\\.chef\\chef_guid
         | 
| 93 | 
            +
                  ).each do |path|
         | 
| 94 | 
            +
                    file = @backend.file(path)
         | 
| 95 | 
            +
                    return file.content.chomp if file.exist? && !file.size.zero?
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
                  nil
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                def windows_uuid_from_chef
         | 
| 101 | 
            +
                  file = @backend.file("#{ENV['SYSTEMDRIVE']}\\chef\\cache\\data_collector_metadata.json")
         | 
| 102 | 
            +
                  return if !file.exist? || file.size.zero?
         | 
| 103 | 
            +
                  json = JSON.parse(file.content)
         | 
| 104 | 
            +
                  json['node_uuid'] if json['node_uuid']
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                def windows_uuid_from_wmic
         | 
| 108 | 
            +
                  result = @backend.run_command('wmic csproduct get UUID')
         | 
| 109 | 
            +
                  return unless result.exit_status.zero?
         | 
| 110 | 
            +
                  result.stdout.split("\r\n")[-1].strip
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                def windows_uuid_from_registry
         | 
| 114 | 
            +
                  cmd = '(Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" -Name "MachineGuid")."MachineGuid"'
         | 
| 115 | 
            +
                  result = @backend.run_command(cmd)
         | 
| 116 | 
            +
                  return unless result.exit_status.zero?
         | 
| 117 | 
            +
                  result.stdout.chomp
         | 
| 118 | 
            +
                end
         | 
| 78 119 | 
             
              end
         | 
| 79 120 | 
             
            end
         | 
| @@ -449,6 +449,7 @@ module Train::Platforms::Detect::Specifications | |
| 449 449 | 
             
                  plat.name('mac_os_x').title('macOS X').in_family('darwin')
         | 
| 450 450 | 
             
                      .detect {
         | 
| 451 451 | 
             
                        cmd = unix_file_contents('/System/Library/CoreServices/SystemVersion.plist')
         | 
| 452 | 
            +
                        @platform[:uuid_command] = "system_profiler SPHardwareDataType | awk '/UUID/ { print $3; }'"
         | 
| 452 453 | 
             
                        true if cmd =~ /Mac OS X/i
         | 
| 453 454 | 
             
                      }
         | 
| 454 455 | 
             
                  plat.name('darwin').title('Darwin').in_family('darwin')
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'digest/sha1'
         | 
| 4 | 
            +
            require 'securerandom'
         | 
| 5 | 
            +
            require 'json'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Train::Platforms::Detect
         | 
| 8 | 
            +
              class UUID
         | 
| 9 | 
            +
                include Train::Platforms::Detect::Helpers::OSCommon
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def initialize(platform)
         | 
| 12 | 
            +
                  @platform = platform
         | 
| 13 | 
            +
                  @backend = @platform.backend
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def find_or_create_uuid
         | 
| 17 | 
            +
                  # for api transports uuid is defined on the connection
         | 
| 18 | 
            +
                  if defined?(@backend.unique_identifier)
         | 
| 19 | 
            +
                    uuid_from_string(@backend.unique_identifier)
         | 
| 20 | 
            +
                  elsif @platform.unix?
         | 
| 21 | 
            +
                    unix_uuid
         | 
| 22 | 
            +
                  elsif @platform.windows?
         | 
| 23 | 
            +
                    windows_uuid
         | 
| 24 | 
            +
                  else
         | 
| 25 | 
            +
                    if @platform[:uuid_command]
         | 
| 26 | 
            +
                      result = @backend.run_command(@platform[:uuid_command])
         | 
| 27 | 
            +
                      return uuid_from_string(result.stdout.chomp) if result.exit_status.zero? && !result.stdout.empty?
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    raise 'Could not find platform uuid! Please set a uuid_command for your platform.'
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
    
        data/lib/train/transports/aws.rb
    CHANGED
    
    | @@ -59,6 +59,13 @@ module Train::Transports | |
| 59 59 | 
             
                  def uri
         | 
| 60 60 | 
             
                    "aws://#{@options[:region]}"
         | 
| 61 61 | 
             
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  def unique_identifier
         | 
| 64 | 
            +
                    # use aws account id
         | 
| 65 | 
            +
                    client = aws_client(::Aws::IAM::Client)
         | 
| 66 | 
            +
                    arn = client.get_user.user.arn
         | 
| 67 | 
            +
                    arn.split(':')[4]
         | 
| 68 | 
            +
                  end
         | 
| 62 69 | 
             
                end
         | 
| 63 70 | 
             
              end
         | 
| 64 71 | 
             
            end
         | 
| @@ -4,6 +4,8 @@ require 'train/plugins' | |
| 4 4 | 
             
            require 'ms_rest_azure'
         | 
| 5 5 | 
             
            require 'azure_mgmt_resources'
         | 
| 6 6 | 
             
            require 'inifile'
         | 
| 7 | 
            +
            require 'socket'
         | 
| 8 | 
            +
            require 'timeout'
         | 
| 7 9 |  | 
| 8 10 | 
             
            module Train::Transports
         | 
| 9 11 | 
             
              class Azure < Train.plugin(1)
         | 
| @@ -12,6 +14,7 @@ module Train::Transports | |
| 12 14 | 
             
                option :client_id, default: ENV['AZURE_CLIENT_ID']
         | 
| 13 15 | 
             
                option :client_secret, default: ENV['AZURE_CLIENT_SECRET']
         | 
| 14 16 | 
             
                option :subscription_id, default: ENV['AZURE_SUBSCRIPTION_ID']
         | 
| 17 | 
            +
                option :msi_port, default: ENV['AZURE_MSI_PORT'] || '50342'
         | 
| 15 18 |  | 
| 16 19 | 
             
                # This can provide the client id and secret
         | 
| 17 20 | 
             
                option :credentials_file, default: ENV['AZURE_CRED_FILE']
         | 
| @@ -21,6 +24,8 @@ module Train::Transports | |
| 21 24 | 
             
                end
         | 
| 22 25 |  | 
| 23 26 | 
             
                class Connection < BaseConnection
         | 
| 27 | 
            +
                  attr_reader :options
         | 
| 28 | 
            +
             | 
| 24 29 | 
             
                  def initialize(options)
         | 
| 25 30 | 
             
                    @apis = {}
         | 
| 26 31 |  | 
| @@ -36,6 +41,8 @@ module Train::Transports | |
| 36 41 | 
             
                      parse_credentials_file
         | 
| 37 42 | 
             
                    end
         | 
| 38 43 |  | 
| 44 | 
            +
                    @options[:msi_port] = @options[:msi_port].to_i unless @options[:msi_port].nil?
         | 
| 45 | 
            +
             | 
| 39 46 | 
             
                    # additional platform details
         | 
| 40 47 | 
             
                    release = Gem.loaded_specs['azure_mgmt_resources'].version
         | 
| 41 48 | 
             
                    @platform_details = { release: "azure_mgmt_resources-v#{release}" }
         | 
| @@ -54,19 +61,24 @@ module Train::Transports | |
| 54 61 | 
             
                  end
         | 
| 55 62 |  | 
| 56 63 | 
             
                  def connect
         | 
| 57 | 
            -
                     | 
| 58 | 
            -
                       | 
| 59 | 
            -
                      @options[: | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 64 | 
            +
                    if @options[:client_id].nil? && @options[:client_secret].nil? && port_open?(@options[:msi_port])
         | 
| 65 | 
            +
                      # try using MSI connection
         | 
| 66 | 
            +
                      provider = ::MsRestAzure::MSITokenProvider.new(@options[:msi_port])
         | 
| 67 | 
            +
                    else
         | 
| 68 | 
            +
                      provider = ::MsRestAzure::ApplicationTokenProvider.new(
         | 
| 69 | 
            +
                        @options[:tenant_id],
         | 
| 70 | 
            +
                        @options[:client_id],
         | 
| 71 | 
            +
                        @options[:client_secret],
         | 
| 72 | 
            +
                      )
         | 
| 73 | 
            +
                    end
         | 
| 62 74 |  | 
| 63 75 | 
             
                    @credentials = {
         | 
| 64 76 | 
             
                      credentials: ::MsRest::TokenCredentials.new(provider),
         | 
| 65 77 | 
             
                      subscription_id: @options[:subscription_id],
         | 
| 66 78 | 
             
                      tenant_id: @options[:tenant_id],
         | 
| 67 | 
            -
                      client_id: @options[:client_id],
         | 
| 68 | 
            -
                      client_secret: @options[:client_secret],
         | 
| 69 79 | 
             
                    }
         | 
| 80 | 
            +
                    @credentials[:client_id] = @options[:client_id] unless @options[:client_id].nil?
         | 
| 81 | 
            +
                    @credentials[:client_secret] = @options[:client_secret] unless @options[:client_secret].nil?
         | 
| 70 82 | 
             
                  end
         | 
| 71 83 |  | 
| 72 84 | 
             
                  def uri
         | 
| @@ -118,8 +130,25 @@ module Train::Transports | |
| 118 130 | 
             
                    @apis[resource_type]
         | 
| 119 131 | 
             
                  end
         | 
| 120 132 |  | 
| 133 | 
            +
                  def unique_identifier
         | 
| 134 | 
            +
                    options[:subscription_id] || options[:tenant_id]
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 121 137 | 
             
                  private
         | 
| 122 138 |  | 
| 139 | 
            +
                  def port_open?(port, seconds = 1)
         | 
| 140 | 
            +
                    Timeout.timeout(seconds) do
         | 
| 141 | 
            +
                      begin
         | 
| 142 | 
            +
                        TCPSocket.new('localhost', port).close
         | 
| 143 | 
            +
                        true
         | 
| 144 | 
            +
                      rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
         | 
| 145 | 
            +
                        false
         | 
| 146 | 
            +
                      end
         | 
| 147 | 
            +
                    end
         | 
| 148 | 
            +
                  rescue Timeout::Error
         | 
| 149 | 
            +
                    false
         | 
| 150 | 
            +
                  end
         | 
| 151 | 
            +
             | 
| 123 152 | 
             
                  def parse_credentials_file # rubocop:disable Metrics/AbcSize
         | 
| 124 153 | 
             
                    # If an AZURE_CRED_FILE environment variable has been specified set the
         | 
| 125 154 | 
             
                    # the credentials file to that, otherwise set the one in home
         | 
| @@ -0,0 +1,140 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'train/plugins'
         | 
| 4 | 
            +
            require 'train/transports/ssh'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Train::Transports
         | 
| 7 | 
            +
              class BadEnablePassword < Train::TransportError; end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              class CiscoIOS < SSH
         | 
| 10 | 
            +
                name 'cisco_ios'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                option :host, required: true
         | 
| 13 | 
            +
                option :user, required: true
         | 
| 14 | 
            +
                option :port, default: 22, required: true
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                option :password, required: true
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                # Used to elevate to enable mode (similar to `sudo su` in Linux)
         | 
| 19 | 
            +
                option :enable_password
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def connection
         | 
| 22 | 
            +
                  @connection ||= Connection.new(validate_options(@options).options)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                class Connection < BaseConnection
         | 
| 26 | 
            +
                  def initialize(options)
         | 
| 27 | 
            +
                    super(options)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    # Delete options to avoid passing them in to `Net::SSH.start` later
         | 
| 30 | 
            +
                    @host = @options.delete(:host)
         | 
| 31 | 
            +
                    @user = @options.delete(:user)
         | 
| 32 | 
            +
                    @port = @options.delete(:port)
         | 
| 33 | 
            +
                    @enable_password = @options.delete(:enable_password)
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    @prompt = /^\S+[>#]\r\n.*$/
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def uri
         | 
| 39 | 
            +
                    "ssh://#{@user}@#{@host}:#{@port}"
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  private
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  def establish_connection
         | 
| 45 | 
            +
                    logger.debug("[SSH] opening connection to #{self}")
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    Net::SSH.start(
         | 
| 48 | 
            +
                      @host,
         | 
| 49 | 
            +
                      @user,
         | 
| 50 | 
            +
                      @options.reject { |_key, value| value.nil? },
         | 
| 51 | 
            +
                    )
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def session
         | 
| 55 | 
            +
                    return @session unless @session.nil?
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    @session = open_channel(establish_connection)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    # Escalate privilege to enable mode if password is given
         | 
| 60 | 
            +
                    if @enable_password
         | 
| 61 | 
            +
                      run_command_via_connection("enable\r\n#{@enable_password}")
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    # Prevent `--MORE--` by removing terminal length limit
         | 
| 65 | 
            +
                    run_command_via_connection('terminal length 0')
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    @session
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  def run_command_via_connection(cmd)
         | 
| 71 | 
            +
                    # Ensure buffer is empty before sending data
         | 
| 72 | 
            +
                    @buf = ''
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    logger.debug("[SSH] Running `#{cmd}` on #{self}")
         | 
| 75 | 
            +
                    session.send_data(cmd + "\r\n")
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    logger.debug('[SSH] waiting for prompt')
         | 
| 78 | 
            +
                    until @buf =~ @prompt
         | 
| 79 | 
            +
                      raise BadEnablePassword if @buf =~ /Bad secrets/
         | 
| 80 | 
            +
                      session.connection.process(0)
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                    # Save the buffer and clear it for the next command
         | 
| 84 | 
            +
                    output = @buf.dup
         | 
| 85 | 
            +
                    @buf = ''
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    format_result(format_output(output, cmd))
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  ERROR_MATCHERS = [
         | 
| 91 | 
            +
                    'Bad IP address',
         | 
| 92 | 
            +
                    'Incomplete command',
         | 
| 93 | 
            +
                    'Invalid input detected',
         | 
| 94 | 
            +
                    'Unrecognized host',
         | 
| 95 | 
            +
                  ].freeze
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  # IOS commands do not have an exit code so we must compare the command
         | 
| 98 | 
            +
                  # output with partial segments of known errors. Then, we return a
         | 
| 99 | 
            +
                  # `CommandResult` with arguments in the correct position based on the
         | 
| 100 | 
            +
                  # result.
         | 
| 101 | 
            +
                  def format_result(result)
         | 
| 102 | 
            +
                    if ERROR_MATCHERS.none? { |e| result.include?(e) }
         | 
| 103 | 
            +
                      CommandResult.new(result, '', 0)
         | 
| 104 | 
            +
                    else
         | 
| 105 | 
            +
                      CommandResult.new('', result, 1)
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  # The buffer (@buf) contains all data sent/received on the SSH channel so
         | 
| 110 | 
            +
                  # we need to format the data to match what we would expect from Train
         | 
| 111 | 
            +
                  def format_output(output, cmd)
         | 
| 112 | 
            +
                    leading_prompt = /(\r\n|^)\S+[>#]/
         | 
| 113 | 
            +
                    command_string = /#{cmd}\r\n/
         | 
| 114 | 
            +
                    trailing_prompt = /\S+[>#](\r\n|$)/
         | 
| 115 | 
            +
                    trailing_line_endings = /(\r\n)+$/
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    output
         | 
| 118 | 
            +
                      .sub(leading_prompt, '')
         | 
| 119 | 
            +
                      .sub(command_string, '')
         | 
| 120 | 
            +
                      .gsub(trailing_prompt, '')
         | 
| 121 | 
            +
                      .gsub(trailing_line_endings, '')
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  # Create an SSH channel that writes to @buf when data is received
         | 
| 125 | 
            +
                  def open_channel(ssh)
         | 
| 126 | 
            +
                    logger.debug("[SSH] opening SSH channel to #{self}")
         | 
| 127 | 
            +
                    ssh.open_channel do |ch|
         | 
| 128 | 
            +
                      ch.on_data do |_, data|
         | 
| 129 | 
            +
                        @buf += data
         | 
| 130 | 
            +
                      end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                      ch.send_channel_request('shell') do |_, success|
         | 
| 133 | 
            +
                        raise 'Failed to open SSH shell' unless success
         | 
| 134 | 
            +
                        logger.debug('[SSH] shell opened')
         | 
| 135 | 
            +
                      end
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
                  end
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
              end
         | 
| 140 | 
            +
            end
         | 
| @@ -4,13 +4,14 @@ | |
| 4 4 | 
             
            # author: Christoph Hartmann
         | 
| 5 5 |  | 
| 6 6 | 
             
            require 'train/plugins'
         | 
| 7 | 
            +
            require 'train/errors'
         | 
| 7 8 | 
             
            require 'mixlib/shellout'
         | 
| 8 9 |  | 
| 9 10 | 
             
            module Train::Transports
         | 
| 10 11 | 
             
              class Local < Train.plugin(1)
         | 
| 11 12 | 
             
                name 'local'
         | 
| 12 13 |  | 
| 13 | 
            -
                class PipeError < :: | 
| 14 | 
            +
                class PipeError < Train::TransportError; end
         | 
| 14 15 |  | 
| 15 16 | 
             
                def connection(_ = nil)
         | 
| 16 17 | 
             
                  @connection ||= Connection.new(@options)
         | 
    
        data/lib/train/version.rb
    CHANGED
    
    
| @@ -0,0 +1,133 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'helper'
         | 
| 4 | 
            +
            require 'train/transports/mock'
         | 
| 5 | 
            +
            require 'securerandom'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class TestFile
         | 
| 8 | 
            +
              def initialize(string)
         | 
| 9 | 
            +
                @string = string
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def exist?
         | 
| 13 | 
            +
                true
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def size
         | 
| 17 | 
            +
                @string.length
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def content
         | 
| 21 | 
            +
                @string
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            describe 'uuid' do
         | 
| 26 | 
            +
              def mock_platform(name, commands = {}, files = {}, plat_options = {})
         | 
| 27 | 
            +
                Train::Platforms.list[name] = nil
         | 
| 28 | 
            +
                mock = Train::Transports::Mock::Connection.new
         | 
| 29 | 
            +
                commands.each do |command, data|
         | 
| 30 | 
            +
                  mock.mock_command(command, data)
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                file_objects = {}
         | 
| 34 | 
            +
                files.each do |path, content|
         | 
| 35 | 
            +
                  file_objects[path] = TestFile.new(content)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                mock.files = file_objects
         | 
| 39 | 
            +
                mock.direct_platform(name, plat_options)
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              it 'finds a linux uuid from chef entity_uuid' do
         | 
| 43 | 
            +
                files = { '/var/chef/cache/data_collector_metadata.json' => '{"node_uuid":"d400073f-0920-41aa-8dd3-2ea59b18f5ce"}' }
         | 
| 44 | 
            +
                plat = mock_platform('linux', {}, files)
         | 
| 45 | 
            +
                plat.uuid.must_equal 'd400073f-0920-41aa-8dd3-2ea59b18f5ce'
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              it 'finds a windows uuid from chef entity_uuid' do
         | 
| 49 | 
            +
                ENV['SYSTEMDRIVE'] = 'C:'
         | 
| 50 | 
            +
                files = { 'C:\chef\cache\data_collector_metadata.json' => '{"node_uuid":"d400073f-0920-41aa-8dd3-2ea59b18f5ce"}' }
         | 
| 51 | 
            +
                plat = mock_platform('windows', {}, files)
         | 
| 52 | 
            +
                plat.uuid.must_equal 'd400073f-0920-41aa-8dd3-2ea59b18f5ce'
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              it 'finds a linux uuid from /etc/chef/chef_guid' do
         | 
| 56 | 
            +
                files = { '/etc/chef/chef_guid' => '5e430326-b5aa-56f8-975f-c3ca1c21df91' }
         | 
| 57 | 
            +
                plat = mock_platform('linux', {}, files)
         | 
| 58 | 
            +
                plat.uuid.must_equal '5e430326-b5aa-56f8-975f-c3ca1c21df91'
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              it 'finds a linux uuid from /home/testuser/.chef/chef_guid' do
         | 
| 62 | 
            +
                ENV['HOME'] = '/home/testuser'
         | 
| 63 | 
            +
                files = { '/home/testuser/.chef/chef_guid' => '5e430326-b5aa-56f8-975f-c3ca1c21df91' }
         | 
| 64 | 
            +
                plat = mock_platform('linux', {}, files)
         | 
| 65 | 
            +
                plat.uuid.must_equal '5e430326-b5aa-56f8-975f-c3ca1c21df91'
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              it 'finds a linux uuid from /etc/machine-id' do
         | 
| 69 | 
            +
                files = { '/etc/machine-id' => '123141dsfadf' }
         | 
| 70 | 
            +
                plat = mock_platform('linux', {}, files)
         | 
| 71 | 
            +
                plat.uuid.must_equal '5e430326-b5aa-56f8-975f-c3ca1c21df91'
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
              it 'finds a linux uuid from /var/lib/dbus/machine-id' do
         | 
| 75 | 
            +
                files = {
         | 
| 76 | 
            +
                  '/etc/machine-id' => '',
         | 
| 77 | 
            +
                  '/var/lib/dbus/machine-id' => '123141dsfadf',
         | 
| 78 | 
            +
                }
         | 
| 79 | 
            +
                plat = mock_platform('linux', {}, files)
         | 
| 80 | 
            +
                plat.uuid.must_equal '5e430326-b5aa-56f8-975f-c3ca1c21df91'
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              it 'finds a linux uuid from /etc/machine-id' do
         | 
| 84 | 
            +
                files = { '/etc/machine-id' => '123141dsfadf' }
         | 
| 85 | 
            +
                plat = mock_platform('linux', {}, files)
         | 
| 86 | 
            +
                plat.uuid.must_equal '5e430326-b5aa-56f8-975f-c3ca1c21df91'
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
              it 'finds a windows uuid from wmic' do
         | 
| 90 | 
            +
                commands = { 'wmic csproduct get UUID' => "UUID\r\nd400073f-0920-41aa-8dd3-2ea59b18f5ce\r\n" }
         | 
| 91 | 
            +
                plat = mock_platform('windows', commands)
         | 
| 92 | 
            +
                plat.uuid.must_equal 'd400073f-0920-41aa-8dd3-2ea59b18f5ce'
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
              it 'finds a windows uuid from registry' do
         | 
| 96 | 
            +
                commands = { '(Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" -Name "MachineGuid")."MachineGuid"' => "d400073f-0920-41aa-8dd3-2ea59b18f5ce\r\n" }
         | 
| 97 | 
            +
                plat = mock_platform('windows', commands)
         | 
| 98 | 
            +
                plat.uuid.must_equal 'd400073f-0920-41aa-8dd3-2ea59b18f5ce'
         | 
| 99 | 
            +
              end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
              it 'finds a windows uuid from C:\chef\chef_guid' do
         | 
| 102 | 
            +
                ENV['SYSTEMDRIVE'] = 'C:'
         | 
| 103 | 
            +
                files = { 'C:\chef\chef_guid' => '5e430326-b5aa-56f8-975f-c3ca1c21df91' }
         | 
| 104 | 
            +
                plat = mock_platform('windows', {}, files)
         | 
| 105 | 
            +
                plat.uuid.must_equal '5e430326-b5aa-56f8-975f-c3ca1c21df91'
         | 
| 106 | 
            +
              end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              it 'finds a windows uuid from C:\Users\test\.chef\chef_guid' do
         | 
| 109 | 
            +
                ENV['HOMEDRIVE'] = 'C:\\'
         | 
| 110 | 
            +
                ENV['HOMEPATH'] = 'Users\test'
         | 
| 111 | 
            +
                files = { 'C:\Users\test\.chef\chef_guid' => '5e430326-b5aa-56f8-975f-c3ca1c21df91' }
         | 
| 112 | 
            +
                plat = mock_platform('windows', {}, files)
         | 
| 113 | 
            +
                plat.uuid.must_equal '5e430326-b5aa-56f8-975f-c3ca1c21df91'
         | 
| 114 | 
            +
              end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
              it 'generates a uuid from a string' do
         | 
| 117 | 
            +
                plat = mock_platform('linux')
         | 
| 118 | 
            +
                uuid = Train::Platforms::Detect::UUID.new(plat)
         | 
| 119 | 
            +
                uuid.uuid_from_string('123141dsfadf').must_equal '5e430326-b5aa-56f8-975f-c3ca1c21df91'
         | 
| 120 | 
            +
              end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
              it 'finds a aws uuid' do
         | 
| 123 | 
            +
                plat = mock_platform('aws')
         | 
| 124 | 
            +
                plat.backend.stubs(:unique_identifier).returns('158551926027')
         | 
| 125 | 
            +
                plat.uuid.must_equal '1d74ce61-ac15-5c48-9ee3-5aa8207ac37f'
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
              it 'finds an azure uuid' do
         | 
| 129 | 
            +
                plat = mock_platform('azure')
         | 
| 130 | 
            +
                plat.backend.stubs(:unique_identifier).returns('1d74ce61-ac15-5c48-9ee3-5aa8207ac37f')
         | 
| 131 | 
            +
                plat.uuid.must_equal '2c2e4fa9-7287-5dee-85a3-6527face7b7b'
         | 
| 132 | 
            +
              end
         | 
| 133 | 
            +
            end
         | 
| @@ -96,4 +96,28 @@ describe 'aws transport' do | |
| 96 96 | 
             
                  ENV['AWS_REGION'].must_equal 'xyz'
         | 
| 97 97 | 
             
                end
         | 
| 98 98 | 
             
              end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
              describe 'unique_identifier' do
         | 
| 101 | 
            +
                class AwsArn
         | 
| 102 | 
            +
                  def arn
         | 
| 103 | 
            +
                    'arn:aws:iam::158551926027:user/test-fixture-maker'
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                class AwsUser
         | 
| 108 | 
            +
                  def user
         | 
| 109 | 
            +
                    AwsArn.new
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                class AwsClient
         | 
| 114 | 
            +
                  def get_user
         | 
| 115 | 
            +
                    AwsUser.new
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
                it 'returns an account id' do
         | 
| 119 | 
            +
                  connection.stubs(:aws_client).returns(AwsClient.new)
         | 
| 120 | 
            +
                  connection.unique_identifier.must_equal '158551926027'
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
              end
         | 
| 99 123 | 
             
            end
         | 
| @@ -83,12 +83,44 @@ describe 'azure transport' do | |
| 83 83 | 
             
              describe 'connect' do
         | 
| 84 84 | 
             
                it 'validate credentials' do
         | 
| 85 85 | 
             
                  connection.connect
         | 
| 86 | 
            +
                  token = credentials[:credentials].instance_variable_get(:@token_provider)
         | 
| 87 | 
            +
                  token.class.must_equal MsRestAzure::ApplicationTokenProvider
         | 
| 88 | 
            +
             | 
| 86 89 | 
             
                  credentials[:credentials].class.must_equal MsRest::TokenCredentials
         | 
| 87 90 | 
             
                  credentials[:tenant_id].must_equal 'test_tenant_id'
         | 
| 88 91 | 
             
                  credentials[:client_id].must_equal 'test_client_id'
         | 
| 89 92 | 
             
                  credentials[:client_secret].must_equal 'test_client_secret'
         | 
| 90 93 | 
             
                  credentials[:subscription_id].must_equal 'test_subscription_id'
         | 
| 91 94 | 
             
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                it 'validate msi credentials' do
         | 
| 97 | 
            +
                  options[:client_id] = nil
         | 
| 98 | 
            +
                  options[:client_secret] = nil
         | 
| 99 | 
            +
                  Train::Transports::Azure::Connection.any_instance.stubs(:port_open?).returns(true)
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  connection.connect
         | 
| 102 | 
            +
                  token = credentials[:credentials].instance_variable_get(:@token_provider)
         | 
| 103 | 
            +
                  token.class.must_equal MsRestAzure::MSITokenProvider
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  credentials[:credentials].class.must_equal MsRest::TokenCredentials
         | 
| 106 | 
            +
                  credentials[:tenant_id].must_equal 'test_tenant_id'
         | 
| 107 | 
            +
                  credentials[:subscription_id].must_equal 'test_subscription_id'
         | 
| 108 | 
            +
                  credentials[:client_id].must_be_nil
         | 
| 109 | 
            +
                  credentials[:client_secret].must_be_nil
         | 
| 110 | 
            +
                  options[:msi_port].must_equal 50342
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
              describe 'unique_identifier' do
         | 
| 115 | 
            +
                it 'returns a subscription id' do
         | 
| 116 | 
            +
                  connection.unique_identifier.must_equal 'test_subscription_id'
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                it 'returns a tenant id' do
         | 
| 120 | 
            +
                  options = connection.instance_variable_get(:@options)
         | 
| 121 | 
            +
                  options[:subscription_id] = nil
         | 
| 122 | 
            +
                  connection.unique_identifier.must_equal 'test_tenant_id'
         | 
| 123 | 
            +
                end
         | 
| 92 124 | 
             
              end
         | 
| 93 125 |  | 
| 94 126 | 
             
              describe 'parse_credentials_file' do
         | 
| @@ -0,0 +1,94 @@ | |
| 1 | 
            +
            # encoding: utf-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'helper'
         | 
| 4 | 
            +
            require 'train/transports/cisco_ios'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            describe 'Train::Transports::CiscoIOS' do
         | 
| 7 | 
            +
              let(:cls) do
         | 
| 8 | 
            +
                plat = Train::Platforms.name('mock').in_family('cisco_ios')
         | 
| 9 | 
            +
                plat.add_platform_methods
         | 
| 10 | 
            +
                Train::Platforms::Detect.stubs(:scan).returns(plat)
         | 
| 11 | 
            +
                Train::Transports::CiscoIOS
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              let(:opts) do
         | 
| 15 | 
            +
                {
         | 
| 16 | 
            +
                  host: 'fakehost',
         | 
| 17 | 
            +
                  user: 'fakeuser',
         | 
| 18 | 
            +
                  password: 'fakepassword',
         | 
| 19 | 
            +
                }
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              let(:cisco_ios) do
         | 
| 23 | 
            +
                cls.new(opts)
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              describe 'CiscoIOS::Connection' do
         | 
| 27 | 
            +
                let(:connection) { cls.new(opts).connection }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                describe '#initialize' do
         | 
| 30 | 
            +
                  it 'raises an error when user is missing' do
         | 
| 31 | 
            +
                    opts.delete(:user)
         | 
| 32 | 
            +
                    err = proc { cls.new(opts).connection }.must_raise(Train::ClientError)
         | 
| 33 | 
            +
                    err.message.must_match(/must provide.*user/)
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  it 'raises an error when host is missing' do
         | 
| 37 | 
            +
                    opts.delete(:host)
         | 
| 38 | 
            +
                    err = proc { cls.new(opts).connection }.must_raise(Train::ClientError)
         | 
| 39 | 
            +
                    err.message.must_match(/must provide.*host/)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  it 'raises an error when password is missing' do
         | 
| 43 | 
            +
                    opts.delete(:password)
         | 
| 44 | 
            +
                    err = proc { cls.new(opts).connection }.must_raise(Train::ClientError)
         | 
| 45 | 
            +
                    err.message.must_match(/must provide.*password/)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  it 'provides a uri' do
         | 
| 49 | 
            +
                    connection.uri.must_equal 'ssh://fakeuser@fakehost:22'
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                describe '#format_result' do
         | 
| 54 | 
            +
                  it 'returns correctly when result is `good`' do
         | 
| 55 | 
            +
                    output = 'good'
         | 
| 56 | 
            +
                    Train::Extras::CommandResult.expects(:new).with(output, '', 0)
         | 
| 57 | 
            +
                    connection.send(:format_result, 'good')
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  it 'returns correctly when result matches /Bad IP address/' do
         | 
| 61 | 
            +
                    output = "Translating \"nope\"\r\n\r\nTranslating \"nope\"\r\n\r\n% Bad IP address or host name\r\n% Unknown command or computer name, or unable to find computer address\r\n"
         | 
| 62 | 
            +
                    Train::Extras::CommandResult.expects(:new).with('', output, 1)
         | 
| 63 | 
            +
                    connection.send(:format_result, output)
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  it 'returns correctly when result matches /Incomplete command/' do
         | 
| 67 | 
            +
                    output = "% Incomplete command.\r\n\r\n"
         | 
| 68 | 
            +
                    Train::Extras::CommandResult.expects(:new).with('', output, 1)
         | 
| 69 | 
            +
                    connection.send(:format_result, output)
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  it 'returns correctly when result matches /Invalid input detected/' do
         | 
| 73 | 
            +
                    output = "             ^\r\n% Invalid input detected at '^' marker.\r\n\r\n"
         | 
| 74 | 
            +
                    Train::Extras::CommandResult.expects(:new).with('', output, 1)
         | 
| 75 | 
            +
                    connection.send(:format_result, output)
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  it 'returns correctly when result matches /Unrecognized host/' do
         | 
| 79 | 
            +
                    output = "Translating \"nope\"\r\n% Unrecognized host or address, or protocol not running.\r\n\r\n"
         | 
| 80 | 
            +
                    Train::Extras::CommandResult.expects(:new).with('', output, 1)
         | 
| 81 | 
            +
                    connection.send(:format_result, output)
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                describe '#format_output' do
         | 
| 86 | 
            +
                  it 'returns output containing only the output of the command executed' do
         | 
| 87 | 
            +
                    cmd = 'show calendar'
         | 
| 88 | 
            +
                    output = "show calendar\r\n10:35:50 UTC Fri Mar 23 2018\r\n7200_ios_12#\r\n7200_ios_12#"
         | 
| 89 | 
            +
                    result = connection.send(:format_output, output, cmd)
         | 
| 90 | 
            +
                    result.must_equal '10:35:50 UTC Fri Mar 23 2018'
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: train
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Dominik Richter
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2018-03- | 
| 11 | 
            +
            date: 2018-03-29 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: json
         | 
| @@ -217,6 +217,7 @@ files: | |
| 217 217 | 
             
            - lib/train/platforms/detect/scanner.rb
         | 
| 218 218 | 
             
            - lib/train/platforms/detect/specifications/api.rb
         | 
| 219 219 | 
             
            - lib/train/platforms/detect/specifications/os.rb
         | 
| 220 | 
            +
            - lib/train/platforms/detect/uuid.rb
         | 
| 220 221 | 
             
            - lib/train/platforms/family.rb
         | 
| 221 222 | 
             
            - lib/train/platforms/platform.rb
         | 
| 222 223 | 
             
            - lib/train/plugins.rb
         | 
| @@ -224,6 +225,7 @@ files: | |
| 224 225 | 
             
            - lib/train/plugins/transport.rb
         | 
| 225 226 | 
             
            - lib/train/transports/aws.rb
         | 
| 226 227 | 
             
            - lib/train/transports/azure.rb
         | 
| 228 | 
            +
            - lib/train/transports/cisco_ios.rb
         | 
| 227 229 | 
             
            - lib/train/transports/docker.rb
         | 
| 228 230 | 
             
            - lib/train/transports/local.rb
         | 
| 229 231 | 
             
            - lib/train/transports/mock.rb
         | 
| @@ -279,6 +281,7 @@ files: | |
| 279 281 | 
             
            - test/unit/platforms/detect/os_linux_test.rb
         | 
| 280 282 | 
             
            - test/unit/platforms/detect/os_windows_test.rb
         | 
| 281 283 | 
             
            - test/unit/platforms/detect/scanner_test.rb
         | 
| 284 | 
            +
            - test/unit/platforms/detect/uuid_test.rb
         | 
| 282 285 | 
             
            - test/unit/platforms/family_test.rb
         | 
| 283 286 | 
             
            - test/unit/platforms/os_detect_test.rb
         | 
| 284 287 | 
             
            - test/unit/platforms/platform_test.rb
         | 
| @@ -289,6 +292,7 @@ files: | |
| 289 292 | 
             
            - test/unit/train_test.rb
         | 
| 290 293 | 
             
            - test/unit/transports/aws_test.rb
         | 
| 291 294 | 
             
            - test/unit/transports/azure_test.rb
         | 
| 295 | 
            +
            - test/unit/transports/cisco_ios.rb
         | 
| 292 296 | 
             
            - test/unit/transports/local_test.rb
         | 
| 293 297 | 
             
            - test/unit/transports/mock_test.rb
         | 
| 294 298 | 
             
            - test/unit/transports/ssh_test.rb
         | 
| @@ -368,6 +372,7 @@ test_files: | |
| 368 372 | 
             
            - test/unit/platforms/detect/os_linux_test.rb
         | 
| 369 373 | 
             
            - test/unit/platforms/detect/os_windows_test.rb
         | 
| 370 374 | 
             
            - test/unit/platforms/detect/scanner_test.rb
         | 
| 375 | 
            +
            - test/unit/platforms/detect/uuid_test.rb
         | 
| 371 376 | 
             
            - test/unit/platforms/family_test.rb
         | 
| 372 377 | 
             
            - test/unit/platforms/os_detect_test.rb
         | 
| 373 378 | 
             
            - test/unit/platforms/platform_test.rb
         | 
| @@ -378,6 +383,7 @@ test_files: | |
| 378 383 | 
             
            - test/unit/train_test.rb
         | 
| 379 384 | 
             
            - test/unit/transports/aws_test.rb
         | 
| 380 385 | 
             
            - test/unit/transports/azure_test.rb
         | 
| 386 | 
            +
            - test/unit/transports/cisco_ios.rb
         | 
| 381 387 | 
             
            - test/unit/transports/local_test.rb
         | 
| 382 388 | 
             
            - test/unit/transports/mock_test.rb
         | 
| 383 389 | 
             
            - test/unit/transports/ssh_test.rb
         |