Class: ZestKnife

Inherits:
Chef::Knife show all
Defined in:
lib/knife-instance/zestknife.rb

Direct Known Subclasses

Chef::Knife::InstanceCreate

Constant Summary collapse

OPTS =
{
  :aws_access_key_id => {
    :short => "-A ID",
    :long => "--aws-access-key-id KEY",
    :description => "Your AWS Access Key ID",
    :proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key }
  },
  :aws_secret_access_key => {
    :short => "-K SECRET",
    :long => "--aws-secret-access-key SECRET",
    :description => "Your AWS API Secret Access Key",
    :proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key }
  },
  :cluster_tag => {
    :short => "-t TAG",
    :long => "--cluster-tag TAG",
    :description => "Tag that identifies this node as part of the <TAG> cluster"
  },
  :environment => {
    :short => "-E CHEF_ENV",
    :long => "--environment CHEF_ENV",
    :description => "Chef environment"
  },
  :region => {
    :long => "--region REGION",
    :short => '-R REGION',
    :description => "Your AWS region",
    :default => ENV['AWS_REGION'],
    :proc => Proc.new { |key| Chef::Config[:knife][:region] = key }
  },
  :encrypted_data_bag_secret => {
    :short => "-B FILE",
    :long => "--encrypted_data_bag_secret FILE",
    :description => "Path to the secret key to unlock encrypted chef data bags",
    :default => ENV['DATABAG_KEY_PATH']
  },
  :aws_ssh_key_id => {
    :short => "-S KEY",
    :long => "--aws-ssh-key KEY",
    :description => "AWS EC2 SSH Key Pair Name",
    :default => ENV["aws_ssh_key"]
  },
  :base_domain => {
    :long => "--base-domain DOMAIN",
    :description => "The domain to be used for this node.",
    :default => ENV["default_base_domain"] || ""
  },
   :wait_for_it => {
    :short => "-W",
    :long => "--wait-for-it",
    :description => "Wait for EC2 to return extended details about the host and register DNS",
    :boolean => true,
    :default => false
  },
  :prod => {
    :long => "--prod",
    :description => "If the environment for your command is production, you must also pass this parameter.  This is to make it slightly harder to do something unintentionally to production."
  }
}
VALIDATORS =
{
  :environment  => :validate_env,
  :base_domain  => :validate_domain,
  :cluster_tag  => :validate_color,
  :prod         => :validate_prod,
  :force_deploy => :validate_force_deploy,
  :region       => :validate_region
}

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.validated_optsObject

Returns the value of attribute validated_opts.



146
147
148
# File 'lib/knife-instance/zestknife.rb', line 146

def validated_opts
  @validated_opts
end

Instance Attribute Details

#base_domainObject

Returns the value of attribute base_domain.



6
7
8
# File 'lib/knife-instance/zestknife.rb', line 6

def base_domain
  @base_domain
end

#internal_domainObject

Returns the value of attribute internal_domain.



7
8
9
# File 'lib/knife-instance/zestknife.rb', line 7

def internal_domain
  @internal_domain
end

Class Method Details

.aws_for_region(region) ⇒ Object



15
16
17
# File 'lib/knife-instance/zestknife.rb', line 15

def self.aws_for_region(region)
  Zest::AWS.new(Chef::Config[:knife][:aws_access_key_id], Chef::Config[:knife][:aws_secret_access_key], region)
end

.AWS_REGIONSObject



19
20
21
# File 'lib/knife-instance/zestknife.rb', line 19

def self.AWS_REGIONS
  []
end

.in_all_aws_regionsObject



23
24
25
26
27
# File 'lib/knife-instance/zestknife.rb', line 23

def self.in_all_aws_regions
  self.AWS_REGIONS.each do |region|
    yield self.aws_for_region(region)
  end
end

.validates(*args) ⇒ Object



153
154
155
156
157
# File 'lib/knife-instance/zestknife.rb', line 153

def self.validates(*args)
  raise "Invalid argument(s) passed to validates: #{args - VALIDATORS.keys}" unless (args - VALIDATORS.keys).empty?
  self.validated_opts ||= []
  self.validated_opts.concat args
end

.with_opts(*args) ⇒ Object



137
138
139
140
141
142
143
144
# File 'lib/knife-instance/zestknife.rb', line 137

def self.with_opts(*args)
  invalid_args = args.select {|arg| !OPTS.keys.include? arg }
  raise "Invalid option(s) passed to with_opts: #{invalid_args.join(", ")}" unless invalid_args.empty?

  args.each do |arg|
    option arg, OPTS[arg]
  end
end

.with_validated_opts(*args) ⇒ Object



148
149
150
151
# File 'lib/knife-instance/zestknife.rb', line 148

def self.with_validated_opts(*args)
  with_opts(*args)
  validates(*args)
end

Instance Method Details

#check_services(hostname) ⇒ Object



114
115
116
117
118
119
# File 'lib/knife-instance/zestknife.rb', line 114

def check_services hostname
  find_item(Chef::Node, hostname) +
    find_item(Chef::ApiClient, hostname) +
    find_ec2(hostname) +
    find_r53(hostname)
end

#domainObject



88
89
90
# File 'lib/knife-instance/zestknife.rb', line 88

def domain
  @internal_domain || ""
end

#domain_prefixObject



125
126
127
# File 'lib/knife-instance/zestknife.rb', line 125

def domain_prefix
  base_domain[0]
end

#environment_prefix(env) ⇒ Object



129
130
131
# File 'lib/knife-instance/zestknife.rb', line 129

def environment_prefix env
  env[0]
end

#errorsObject



165
166
167
# File 'lib/knife-instance/zestknife.rb', line 165

def errors
  @errors ||= []
end

#errors?Boolean

Returns:

  • (Boolean)


169
170
171
# File 'lib/knife-instance/zestknife.rb', line 169

def errors?
  !errors.empty?
end

#find_ec2(name) ⇒ Object



38
39
40
41
42
43
44
# File 'lib/knife-instance/zestknife.rb', line 38

def find_ec2(name)
  nodes = {}
  self.class.in_all_aws_regions do |zest_aws|
    nodes = nodes.merge(zest_aws.compute.servers.group_by { |s| s.tags["Name"] })
  end
  nodes[name].nil? ? [] : nodes[name]
end

#find_item(klass, name) ⇒ Object



29
30
31
32
33
34
35
36
# File 'lib/knife-instance/zestknife.rb', line 29

def find_item(klass, name)
  begin
    object = klass.load(name)
    return [object]
  rescue Net::HTTPServerException
    return []
  end
end

#find_r53(name) ⇒ Object



46
47
48
49
50
51
52
53
54
55
# File 'lib/knife-instance/zestknife.rb', line 46

def find_r53 name
  in_zone = zone_from_name(name)
  if in_zone.nil?
    in_zone = zone
    name = fqdn(name)
  end
  name = "#{name}." unless name[-1] == "."
  recs = in_zone.records.select {|r| r.name == name }.to_a
  recs.empty? ? [in_zone.records.get(name)].compact : recs
end

#fqdn(name) ⇒ Object



83
84
85
86
# File 'lib/knife-instance/zestknife.rb', line 83

def fqdn(name)
  return '' if name.nil? || name.empty?
  "#{name}.#{domain}"
end

#generate_hostname(env) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/knife-instance/zestknife.rb', line 92

def generate_hostname env
  name = nil

  5.times do |i|
    name = random_hostname env
    break if check_services(name).empty?

    name = nil
    srand # re-seed rand so we don't get stuck in a sequence
  end

  errors << "Unable to find available hostname in 5 tries" if name.nil?
  name
end

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



9
10
11
12
13
# File 'lib/knife-instance/zestknife.rb', line 9

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

#random_hostname(env) ⇒ Object



121
122
123
# File 'lib/knife-instance/zestknife.rb', line 121

def random_hostname env
  "#{domain_prefix}#{environment_prefix env}#{random_three_digit_number}"
end

#random_three_digit_numberObject



133
134
135
# File 'lib/knife-instance/zestknife.rb', line 133

def random_three_digit_number
  sprintf("%03d", rand(1000))
end

#setup_config(keys = [:aws_access_key_id, :aws_secret_access_key]) ⇒ Object



159
160
161
162
163
# File 'lib/knife-instance/zestknife.rb', line 159

def setup_config(keys=[:aws_access_key_id, :aws_secret_access_key])
  keys.each do |k|
    Chef::Config[:knife][k] = ENV[k.to_s] if Chef::Config[:knife][k].nil? && ENV[k.to_s]
  end
end

#validate!(keys = [:aws_access_key_id, :aws_secret_access_key]) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/knife-instance/zestknife.rb', line 173

def validate!(keys=[:aws_access_key_id, :aws_secret_access_key])
  keys.each do |k|
    if Chef::Config[:knife][k].nil? & config[k].nil?
      errors << "You did not provide a valid '#{k}' value."
    end
  end

  self.class.validated_opts.each do |opt|
    send VALIDATORS[opt]
  end if self.class.validated_opts

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

#validate_colorObject



201
202
203
204
205
# File 'lib/knife-instance/zestknife.rb', line 201

def validate_color
  unless @color
    errors << "You must provide a cluster_tag with the -t option"
  end
end

#validate_domainObject



192
193
# File 'lib/knife-instance/zestknife.rb', line 192

def validate_domain
end

#validate_envObject



189
190
# File 'lib/knife-instance/zestknife.rb', line 189

def validate_env
end

#validate_force_deployObject



198
199
# File 'lib/knife-instance/zestknife.rb', line 198

def validate_force_deploy
end

#validate_hostname(hostname) ⇒ Object



107
108
109
110
111
112
# File 'lib/knife-instance/zestknife.rb', line 107

def validate_hostname hostname
  errors << "hostname can't be blank" and return if (hostname.nil? || hostname.empty?)
  check_services(hostname).each do |service|
    errors << "#{hostname} in #{service.class} already exists. Delete first."
  end
end

#validate_prodObject



207
208
# File 'lib/knife-instance/zestknife.rb', line 207

def validate_prod
end

#validate_regionObject



195
196
# File 'lib/knife-instance/zestknife.rb', line 195

def validate_region
end

#zoneObject



57
58
59
60
61
62
63
64
65
66
# File 'lib/knife-instance/zestknife.rb', line 57

def zone
  unless @zone
    self.class.in_all_aws_regions do |zest_aws|
      @zone ||= zest_aws.dns.zones.detect { |z| z.domain.downcase == domain }
    end
    raise "Could not find DNS zone" unless @zone
  end

  @zone
end

#zone_from_name(dns_name) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/knife-instance/zestknife.rb', line 68

def zone_from_name dns_name
  name, tld = dns_name.split(".")[-2..-1]
  if name && tld
    dns_domain = "#{name}.#{tld}"
    zone = nil

    self.class.in_all_aws_regions do |zest_aws|
      zone1 = zest_aws.dns.zones.select {|x| x.domain =~ /^#{dns_domain}/ }.first
      zone = zone1 if zone1
    end

    zone
  end
end