Module: Chef::Knife::Ec2Base

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(includer) ⇒ Object

TODO:

Would prefer to do this in a rational way, but can't be done b/c of Mixlib::CLI's design :(



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/chef/knife/helpers/ec2_base.rb', line 29

def self.included(includer)
  includer.class_eval do

    deps do
      require "aws-sdk-ec2"
      require "chef/json_compat"
      require "chef/util/path_helper"
    end

    option :aws_credential_file,
      long: "--aws-credential-file FILE",
      description: "File containing AWS credentials as used by the AWS Command Line Interface."

    option :aws_config_file,
      long: "--aws-config-file FILE",
      description: "File containing AWS configurations as used by the AWS Command Line Interface."

    option :aws_profile,
      long: "--aws-profile PROFILE",
      description: "AWS profile, from AWS credential file and AWS config file, to use",
      default: "default"

    option :aws_access_key_id,
      short: "-A ID",
      long: "--aws-access-key-id KEY",
      description: "Your AWS Access Key ID"

    option :aws_secret_access_key,
      short: "-K SECRET",
      long: "--aws-secret-access-key SECRET",
      description: "Your AWS API Secret Access Key"

    option :aws_session_token,
      long: "--aws-session-token TOKEN",
      description: "Your AWS Session Token, for use with AWS STS Federation or Session Tokens"

    option :region,
      long: "--region REGION",
      description: "Your AWS region"

    option :use_iam_profile,
      long: "--use-iam-profile",
      description: "Use IAM profile assigned to current machine",
      boolean: true,
      default: false
  end
end

Instance Method Details

#ami ⇒ Object



180
181
182
# File 'lib/chef/knife/helpers/ec2_base.rb', line 180

def ami
  @ami ||= fetch_ami(config[:image])
end

#connection_string ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/chef/knife/helpers/ec2_base.rb', line 77

def connection_string
  conn = {}
  conn[:region] = config[:region] || "us-east-1"
  Chef::Log.debug "Using AWS region #{conn[:region]}"
  conn[:credentials] =
    if config[:use_iam_profile]
      Chef::Log.debug "Using iam profile for authentication as use_iam_profile set"
      Aws::InstanceProfileCredentials.new
    else
      Chef::Log.debug "Setting up AWS connection using aws_access_key_id: #{mask(config[:aws_access_key_id])} aws_secret_access_key: #{mask(config[:aws_secret_access_key])} aws_session_token: #{mask(config[:aws_session_token])}"

      Aws::Credentials.new(config[:aws_access_key_id], config[:aws_secret_access_key], config[:aws_session_token])
    end
  conn
end

#ec2_connection ⇒ Aws::EC2::Client

Returns:

  • (Aws::EC2::Client)


94
95
96
# File 'lib/chef/knife/helpers/ec2_base.rb', line 94

def ec2_connection
  @ec2_connection ||= Aws::EC2::Client.new(connection_string)
end

#fetch_ami(image_id) ⇒ Object



102
103
104
105
106
107
108
# File 'lib/chef/knife/helpers/ec2_base.rb', line 102

def fetch_ami(image_id)
  return nil unless image_id

  ec2_connection.describe_images({
    image_ids: [image_id],
  }).images.first
end

#fetch_ec2_instance(instance_id) ⇒ Object



110
111
112
113
114
115
116
117
# File 'lib/chef/knife/helpers/ec2_base.rb', line 110

def fetch_ec2_instance(instance_id)
  instance = ec2_connection.describe_instances({
    instance_ids: [
      instance_id,
    ],
  }).reservations[0]
  normalize_server_data(server_hashes(instance))
end

#fetch_network_interfaces(nic_id) ⇒ Object



119
120
121
122
123
# File 'lib/chef/knife/helpers/ec2_base.rb', line 119

def fetch_network_interfaces(nic_id)
  ec2_connection.describe_network_interfaces({
    network_interface_ids: [nic_id],
  }).network_interfaces[0]
end

#fetch_password_data(server_id) ⇒ Object



125
126
127
128
129
# File 'lib/chef/knife/helpers/ec2_base.rb', line 125

def fetch_password_data(server_id)
  ec2_connection.get_password_data({
    instance_id: server_id,
  })
end

#fetch_region ⇒ String

Returns:

  • (String)


132
133
134
# File 'lib/chef/knife/helpers/ec2_base.rb', line 132

def fetch_region
  ec2_connection.instance_variable_get(:@config).region
end

#fetch_subnet(subnet_id) ⇒ Object



136
137
138
139
140
# File 'lib/chef/knife/helpers/ec2_base.rb', line 136

def fetch_subnet(subnet_id)
  ec2_connection.describe_subnets({
    subnet_ids: [subnet_id],
  }).subnets[0]
end

#find_name_tag(tags) ⇒ String

Name tag value return.

Returns:

  • (String)


186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/chef/knife/helpers/ec2_base.rb', line 186

def find_name_tag(tags)
  name_tag = tags.find do |tag|
    key_value = tag.respond_to?(:key) ? tag.key : (tag[:key] || tag["key"])
    key_value == "Name"
  end
  if name_tag
    if name_tag.respond_to?(:value)
      name_tag.value
    else
      name_tag[:value] || name_tag["value"]
    end
  end
end

#is_image_windows? ⇒ Boolean

Platform value return for Windows AMIs; otherwise, it is blank.

Returns:

  • (Boolean)


202
203
204
# File 'lib/chef/knife/helpers/ec2_base.rb', line 202

def is_image_windows?
  ami && ami.platform == "windows"
end

#msg_pair(label, value, color = :cyan) ⇒ Object



174
175
176
177
178
# File 'lib/chef/knife/helpers/ec2_base.rb', line 174

def msg_pair(label, value, color = :cyan)
  if value && !value.to_s.empty?
    ui.info("#{ui.color(label, color)}: #{value}")
  end
end

#normalize_server_data(server_hashes) ⇒ Struct

Returns:

  • (Struct)


169
170
171
172
# File 'lib/chef/knife/helpers/ec2_base.rb', line 169

def normalize_server_data(server_hashes)
  require "ostruct" unless defined?(OpenStruct)
  OpenStruct.new(server_hashes)
end

#server_hashes(server_obj) ⇒ Hash

Returns:

  • (Hash)


143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/chef/knife/helpers/ec2_base.rb', line 143

def server_hashes(server_obj)
  server_data = {}
  %w{ebs_optimized image_id instance_id instance_type key_name platform public_dns_name public_ip_address private_dns_name private_ip_address root_device_type}.each do |id|
    server_data[id] = server_obj.instances[0].send(id)
  end
  server_data["availability_zone"] = server_obj.instances[0].placement.availability_zone
  server_data["groups"] = server_obj.groups.map(&:group_name) unless vpc_mode?
  server_data["iam_instance_profile"] = ( server_obj.instances[0].iam_instance_profile.nil? ? nil : server_obj.instances[0].iam_instance_profile.arn[%r{instance-profile/(.*)}] )
  server_data["id"] = server_data["instance_id"]

  tags = server_obj.instances[0].tags.map(&:value)
  server_data["name"] = find_name_tag(server_obj.instances[0].tags)
  server_data["placement_group"] = server_obj.instances[0].placement.group_name
  server_data["security_groups"] = server_obj.instances[0].security_groups.map(&:group_name)
  server_data["security_group_ids"] = server_obj.instances[0].security_groups.map(&:group_id)
  server_data["state"] = server_obj.instances[0].state.name
  server_data["subnet_id"] = server_obj.instances[0].network_interfaces[0].subnet_id if vpc_mode?
  server_data["source_dest_check"] = server_obj.instances[0].network_interfaces[0].source_dest_check if vpc_mode?
  server_data["tags"] = tags
  server_data["tenancy"] = server_obj.instances[0].placement.tenancy
  server_data["volume_id"] = server_obj.instances[0].block_device_mappings[0]&.ebs&.volume_id
  server_data["block_device_mappings"] = server_obj.instances[0].block_device_mappings
  server_data
end

#validate_aws_config!(keys = %i{aws_access_key_id aws_secret_access_key})) ⇒ Object

validate the config options that were passed since some of them cannot be used together also validate the aws configuration file contents if present



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/chef/knife/helpers/ec2_base.rb', line 208

def validate_aws_config!(keys = %i{aws_access_key_id aws_secret_access_key})
  errors = [] # track all errors so we report on all of them

  validate_aws_config_file! if config[:aws_config_file]
  unless config[:use_iam_profile] # skip config file / key validation if we're using iam profile
    # validate the creds file if:
    #   aws keys have not been passed in config / CLI and the default cred file location does exist
    #   OR
    #   the user passed aws_credential_file
    if (config.keys & %i{aws_access_key_id aws_secret_access_key}).empty? && aws_cred_file_location ||
        config[:aws_credential_file]

      unless (config.keys & %i{aws_access_key_id aws_secret_access_key}).empty?
        errors << "Either provide a credentials file or the access key and secret keys but not both."
      end

      validate_aws_credential_file!
    end

    keys.each do |k|
      pretty_key = k.to_s.tr("_", " ").gsub(/\w+/) { |w| (w =~ /(ssh)|(aws)/i) ? w.upcase : w.capitalize }
      if config[k].nil?
        errors << "You did not provide a valid '#{pretty_key}' value."
      end
    end

    if errors.each { |e| ui.error(e) }.any?
      exit 1
    end
  end
end

#vpc_mode? ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
# File 'lib/chef/knife/helpers/ec2_base.rb', line 98

def vpc_mode?
  !!config[:subnet_id]
end