lib/bosh/cloudfoundry/deployment_attributes.rb
module Bosh::Cloudfoundry
# Each version/CPI/size combination of Cloud Foundry deployment template has
# input attributes that can or must be provided by a user.
class DeploymentAttributes
include BoshExtensions
include Bosh::Cli::Validation
attr_reader :attributes
attr_reader :release_version_cpi
def initialize(director_client, bosh_status, release_version_cpi, attributes = {})
@director_client = director_client
@bosh_status = bosh_status
@release_version_cpi = release_version_cpi
@attributes = attributes
@attributes[:name] ||= default_name
@attributes[:deployment_size] ||= default_deployment_size
@attributes[:persistent_disk] ||= default_persistent_disk
@attributes[:security_group] ||= default_security_group
@attributes[:common_password] ||= random_string(12, :common)
@attributes[:skip_dns_validation] ||= default_skip_dns_validation
@attributes[:dea_server_ram] ||= default_dea_server_ram
@attributes[:dea_container_depot_disk] ||= default_dea_container_depot_disk
end
def name
@attributes[:name]
end
def deployment_size
@attributes[:deployment_size]
end
def persistent_disk
@attributes[:persistent_disk]
end
def security_group
@attributes[:security_group]
end
def common_password
@attributes[:common_password]
end
def ip_addresses
@attributes[:ip_addresses]
end
def dns
@attributes[:dns]
end
def dea_server_ram
@attributes[:dea_server_ram]
end
def dea_container_depot_disk
@attributes[:dea_container_depot_disk]
end
def available_attributes
@attributes.keys
end
# Attributes & their values that can be changed via setters & deployment re-deployed successfully
def mutable_attributes
release_version_cpi.mutable_attributes.map(&:to_sym)
end
# Attributes & their values that are not to be changed over time
def immutable_attributes
release_version_cpi.immutable_attributes.map(&:to_sym)
end
def mutable_attribute?(attribute)
mutable_attributes.include?(attribute.to_sym)
end
def set_unless_nil(attribute, value)
attributes[attribute.to_sym] = value if value
end
def set(attribute, value)
attributes[attribute.to_sym] = value
case attribute.to_sym
when :ip_addresses
set_default_dns
end
end
def set_mutable(attribute, value)
if mutable_attribute?(attribute)
set(attribute, value)
else
false
end
end
def validate(attribute)
value = attributes[attribute.to_sym]
if attribute.to_s == "deployment_size"
available_deployment_sizes.include?(value)
elsif attribute.to_s =~ /size$/
available_resources.include?(value)
else
true
end
end
def validate_deployment_size
step("Validating deployment size", "Available deployment sizes are #{available_deployment_sizes.join(', ')}", :fatal) do
validate(:deployment_size)
end
end
# FIXME only supports a single ip_address
def validate_dns_mapping
unless @attributes[:skip_dns_validation]
validate_dns_a_record("api.#{dns}", ip_addresses.first)
validate_dns_a_record("demoapp.#{dns}", ip_addresses.first)
end
end
def validate_dns_a_record(domain, expected_ip_address)
dns_mapping = Bosh::Cloudfoundry::Validations::DnsIpMappingValidation.new(domain, expected_ip_address)
if dns_mapping.valid?
say "`#{dns_mapping.domain}' maps to #{dns_mapping.ip_address}".make_green
else
say "Validation errors:"
dns_mapping.errors.each do |error|
say "- %s" % [error]
end
err "`#{dns_mapping.domain}' does not map to #{dns_mapping.ip_address}"
end
end
def format(attribute)
value = attributes[attribute.to_sym].to_s
end
def validated_color(attribute)
validate(attribute) ?
format(attribute).make_green :
format(attribute).make_red
end
# TODO move these validations into a "ValidatedSize" class or similar
def available_resources
@available_resources ||= begin
resources = release_version_cpi.spec["resources"]
if resources && resources.is_a?(Array) && resources.first.is_a?(String)
resources
else
err "template spec needs 'resources' key with list of resource pool names available; found #{release_version_cpi.spec.inspect}"
end
end
end
def available_deployment_sizes
@available_deployment_sizes ||= begin
deployment_sizes = release_version_cpi.spec["deployment_sizes"]
if deployment_sizes && deployment_sizes.is_a?(Array) && deployment_sizes.first.is_a?(String)
deployment_sizes
else
err "template spec needs 'deployment_sizes' key with list of deployment sizes names available; found #{deployment_sizes.spec.inspect}"
end
end
end
# If using security groups, the following ports must be opened for external access:
# * 22 - ssh to all servers
# * 80 - http traffic to routers
# * 443 - https traffic to routers
# * 4222 - access to nats server
def required_ports
[22, 80, 443, 4222]
end
def attributes_with_string_keys
attributes.inject({}) do |mem, key_value|
key, value = key_value
mem[key.to_s] = value
mem
end
end
private
def default_name
"cf-#{Time.now.to_i}"
end
# TODO change to small when its implemented
def default_deployment_size
"medium"
end
def default_persistent_disk
4096
end
def default_security_group
"default"
end
def default_skip_dns_validation
false
end
# Chosen to be small enough to fit within EC2 m1.small (1.7G)
def default_dea_server_ram
1500
end
def default_dea_container_depot_disk
10 * 1024
end
def set_default_dns
if dns.nil?
first_ip_address = ip_addresses.first
set(:dns, "#{first_ip_address}.xip.io")
end
end
def bosh_uuid
@bosh_status["uuid"]
end
def bosh_cpi
@bosh_status["cpi"]
end
# Generate a random string for passwords and tokens.
# Length is the length of the string.
# name is an optional name of a previously generated string. This is used
# to allow setting the same password for different components.
# Extracted from Bosh::Cli::Command::Biff
def random_string(length, name=nil)
random_string = SecureRandom.hex(length)[0...length]
@random_cache ||= {}
if name
@random_cache[name] ||= random_string
else
random_string
end
end
end
end