Class: Convox::Client
- Inherits:
-
Object
- Object
- Convox::Client
- Defined in:
- lib/convox/client.rb
Constant Summary collapse
- CONVOX_CONFIG_DIR =
if OS.mac? # Convox v3 moved this to ~/Library/Preferences/convox/ on Mac File.('~/Library/Preferences/convox').freeze else File.('~/.convox').freeze end
- CURRENT_FILE =
File.join(CONVOX_CONFIG_DIR, 'current')
Instance Attribute Summary collapse
-
#config ⇒ Object
Returns the value of attribute config.
-
#logger ⇒ Object
Returns the value of attribute logger.
Instance Method Summary collapse
- #add_docker_registry! ⇒ Object
- #add_elasticache_cluster ⇒ Object
- #add_rds_database ⇒ Object
-
#add_s3_bucket ⇒ Object
Create the s3 bucket, and also apply a CORS configuration Convox v3 update - They removed support for S3 resources, so we have to do in terraform now (which is actually pretty nice!).
- #apply_terraform_update! ⇒ Object
- #backup_convox_host_and_rack ⇒ Object
- #cli_version ⇒ Object
- #cli_version_string ⇒ Object
- #convox_2_cli? ⇒ Boolean
- #convox_3_cli? ⇒ Boolean
- #convox_app_exists? ⇒ Boolean
- #convox_rack_data ⇒ Object
- #create_convox_app! ⇒ Object
- #default_service_domain_name ⇒ Object
- #elasticache_details ⇒ Object
-
#initialize(options = {}) ⇒ Client
constructor
A new instance of Client.
- #install_convox ⇒ Object
- #rack_already_installed? ⇒ Boolean
-
#rack_dir ⇒ Object
Convox v3 creates a folder for each rack for the Terraform config.
- #rds_details ⇒ Object
- #run_convox_command!(cmd, env = {}, rack_arg: true) ⇒ Object
- #s3_bucket_details ⇒ Object
- #set_default_app_for_directory! ⇒ Object
- #terraform_resource(resource_type, resource_name) ⇒ Object
- #terraform_state ⇒ Object
-
#validate_convox_rack_and_write_current! ⇒ Object
Auth for a detached rack is not saved in the auth file anymore.
- #validate_convox_rack_api! ⇒ Object
- #write_current(rack_name) ⇒ Object
- #write_terraform_template(name) ⇒ Object
Constructor Details
#initialize(options = {}) ⇒ Client
Returns a new instance of Client.
57 58 59 60 61 |
# File 'lib/convox/client.rb', line 57 def initialize( = {}) @logger = Logger.new($stdout) logger.level = [:log_level] || Logger::INFO @config = [:config] || {} end |
Instance Attribute Details
#config ⇒ Object
Returns the value of attribute config.
21 22 23 |
# File 'lib/convox/client.rb', line 21 def config @config end |
#logger ⇒ Object
Returns the value of attribute logger.
21 22 23 |
# File 'lib/convox/client.rb', line 21 def logger @logger end |
Instance Method Details
#add_docker_registry! ⇒ Object
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
# File 'lib/convox/client.rb', line 376 def add_docker_registry! require_config(%i[docker_registry_url docker_registry_username docker_registry_password]) registry_url = config.fetch(:docker_registry_url) logger.debug 'Looking up existing Docker registries...' registries_response = `convox api get /registries --rack #{config.fetch(:stack_name)}` unless $CHILD_STATUS.success? raise 'Something went wrong while fetching the list of registries!' end registries = JSON.parse(registries_response) if registries.any? { |r| r['server'] == registry_url } logger.debug "=> Docker Registry already exists: #{registry_url}" return true end logger.info "Adding Docker Registry: #{registry_url}..." logger.info '=> Documentation: ' \ 'https://docs.convox.com/configuration/private-registries/' `convox registries add "#{registry_url}" \ "#{config.fetch(:docker_registry_username)}" \ "#{config.fetch(:docker_registry_password)}" \ --rack #{config.fetch(:stack_name)}` return if $CHILD_STATUS.success? raise "Something went wrong while adding the #{registry_url} registry!" end |
#add_elasticache_cluster ⇒ Object
281 282 283 |
# File 'lib/convox/client.rb', line 281 def add_elasticache_cluster write_terraform_template('elasticache') end |
#add_rds_database ⇒ Object
276 277 278 279 |
# File 'lib/convox/client.rb', line 276 def add_rds_database require_config(%i[database_username database_password]) write_terraform_template('rds') end |
#add_s3_bucket ⇒ Object
Create the s3 bucket, and also apply a CORS configuration Convox v3 update - They removed support for S3 resources, so we have to do in terraform now (which is actually pretty nice!)
265 266 267 268 269 270 271 272 273 274 |
# File 'lib/convox/client.rb', line 265 def add_s3_bucket require_config(%i[s3_bucket_name]) unless config.key? :s3_bucket_cors_rule logger.debug 'No CORS rule provided in config: s3_bucket_cors_rule (optional)' return end write_terraform_template('s3_bucket') end |
#apply_terraform_update! ⇒ Object
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/convox/client.rb', line 299 def apply_terraform_update! logger.info 'Applying terraform update...' command = if ENV['DEBUG_TERRAFORM'] 'terraform plan' else 'terraform apply -auto-approve' end logger.debug "+ #{command}" env = { 'AWS_ACCESS_KEY_ID' => config.fetch(:aws_access_key_id), 'AWS_SECRET_ACCESS_KEY' => config.fetch(:aws_secret_access_key) } Dir.chdir(rack_dir) do system env, command raise 'terraform command failed!' unless $CHILD_STATUS.success? end end |
#backup_convox_host_and_rack ⇒ Object
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/convox/client.rb', line 69 def backup_convox_host_and_rack FileUtils.mkdir_p CONVOX_CONFIG_DIR path = File.join(CONVOX_CONFIG_DIR, 'current') return unless File.exist?(path) bak_file = "#{path}.bak" logger.info "Moving existing #{path} to #{bak_file}..." FileUtils.mv(path, bak_file) end |
#cli_version ⇒ Object
32 33 34 35 36 37 38 39 |
# File 'lib/convox/client.rb', line 32 def cli_version return unless cli_version_string.is_a?(String) if cli_version_string.match?(/^\d+\.\d+\.\d+/) @cli_version ||= Gem::Version.new(version_string) end @cli_version end |
#cli_version_string ⇒ Object
23 24 25 26 27 28 29 30 |
# File 'lib/convox/client.rb', line 23 def cli_version_string return @cli_version_string if @cli_version_string cli_version_string ||= `convox --version` return unless $CHILD_STATUS.success? @cli_version_string = cli_version_string.chomp end |
#convox_2_cli? ⇒ Boolean
41 42 43 44 45 |
# File 'lib/convox/client.rb', line 41 def convox_2_cli? return false unless cli_version_string.is_a?(String) cli_version_string.match?(/^20\d+$/) end |
#convox_3_cli? ⇒ Boolean
47 48 49 50 51 52 53 54 55 |
# File 'lib/convox/client.rb', line 47 def convox_3_cli? return false if !cli_version_string.is_a?(String) || convox_2_cli? || !cli_version_string.match?(/^\d+\.\d+\.\d+/) cli_version = Gem::Version.new(cli_version_string) cli_version >= Gem::Version.new('3.0.0') && cli_version < Gem::Version.new('4.0.0') end |
#convox_app_exists? ⇒ Boolean
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/convox/client.rb', line 243 def convox_app_exists? require_config(%i[convox_app_name]) app_name = config.fetch(:convox_app_name) logger.debug "Looking for existing #{app_name} app..." convox_output = `convox api get /apps --rack #{config.fetch(:stack_name)}` raise 'convox command failed!' unless $CHILD_STATUS.success? apps = JSON.parse(convox_output) apps.each do |app| if app['name'] == app_name logger.debug "=> Found #{app_name} app." return true end end logger.debug "=> Did not find #{app_name} app." false end |
#convox_rack_data ⇒ Object
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/convox/client.rb', line 182 def convox_rack_data @convox_rack_data ||= begin logger.debug 'Fetching convox rack attributes...' command = "convox api get /system --rack #{config.fetch(:stack_name)}" logger.debug "+ #{command}" # It can take a while for the API to be ready. start_time = Time.now convox_output = nil loop do convox_output = `#{command}` break if $CHILD_STATUS.success? if Time.now - start_time > 360 raise 'Could not connect to Convox rack API!' end logger.debug 'Waiting for Convox rack API to be ready... (can take a few minutes)' sleep 5 end JSON.parse(convox_output) end end |
#create_convox_app! ⇒ Object
206 207 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 |
# File 'lib/convox/client.rb', line 206 def create_convox_app! require_config(%i[convox_app_name]) return true if convox_app_exists? app_name = config.fetch(:convox_app_name) logger.info "Creating app: #{app_name}..." logger.info '=> Documentation: ' \ 'https://docs.convox.com/reference/cli/apps/' # NOTE: --wait flags were removed in Convox 3. It now waits by default. run_convox_command! "apps create #{app_name}" retries = 0 loop do break if convox_app_exists? if retries > 5 raise "Something went wrong while creating the #{app_name} app! " \ '(Please wait a few moments and then restart the installation script.)' end logger.info "Waiting for #{app_name} to be ready..." sleep 3 retries += 1 end logger.info "=> #{app_name} app created!" end |
#default_service_domain_name ⇒ Object
407 408 409 410 411 412 413 414 415 416 417 |
# File 'lib/convox/client.rb', line 407 def default_service_domain_name require_config(%i[convox_app_name]) app_name = config.fetch(:convox_app_name) default_service = config[:default_service] || 'web' convox_api_url = terraform_state['outputs']['api']['value'] convox_router_host = convox_api_url.split('@').last.sub(/^api\./, '') [default_service, app_name, convox_router_host].join('.').downcase end |
#elasticache_details ⇒ Object
362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/convox/client.rb', line 362 def elasticache_details require_config(%i[s3_bucket_name]) # Just ensure that the bucket exists in the state cluster = terraform_resource('aws_elasticache_cluster', 'elasticache_cluster') cluster_attributes = cluster['instances'][0]['attributes'] cache_node = cluster_attributes['cache_nodes'][0] redis_url = "redis://#{cache_node['address']}:#{cache_node['port']}/0" { redis_url: redis_url } end |
#install_convox ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/convox/client.rb', line 80 def install_convox require_config(%i[aws_region stack_name]) region = config.fetch(:aws_region) stack_name = config.fetch(:stack_name) if rack_already_installed? logger.info "There is already a Convox rack named #{stack_name}. Using this rack." logger.debug 'If you need to start over, you can run: ' \ "convox rack uninstall #{stack_name} " \ '(Make sure you export AWS_ACCESS_KEY_ID and ' \ "AWS_SECRET_ACCESS_KEY first.)\n" \ "If this fails, you can try deleting the rack directory: rm -rf #{rack_dir}" return true end require_config(%i[ aws_region aws_access_key_id aws_secret_access_key stack_name instance_type ]) logger.info "Installing Convox (#{stack_name})..." env = { 'AWS_REGION' => region, 'AWS_ACCESS_KEY_ID' => config.fetch(:aws_access_key_id), 'AWS_SECRET_ACCESS_KEY' => config.fetch(:aws_secret_access_key) } # Set proxy_protocol=true by default to forward client IPs command = %(rack install aws \ "#{config.fetch(:stack_name)}" \ "node_type=#{config.fetch(:instance_type)}" \ "proxy_protocol=true" \ "region=#{config.fetch(:aws_region)}") # us-east constantly has problems with the us-east-1c AZ: # "Cannot create cluster 'ds-enterprise-cx3' because us-east-1c, the targeted # availability zone, does not currently have sufficient capacity to support the cluster. # Retry and choose from these availability zones: # us-east-1a, us-east-1b, us-east-1d, us-east-1e, us-east-1f if config.fetch(:aws_region) == 'us-east-1' command += ' "availability_zones=us-east-1a,us-east-1b,us-east-1d,us-east-1e,us-east-1f"' end run_convox_command!(command, env, rack_arg: false) end |
#rack_already_installed? ⇒ Boolean
128 129 130 131 132 133 |
# File 'lib/convox/client.rb', line 128 def rack_already_installed? require_config(%i[aws_region stack_name]) return true if File.exist?(rack_dir) false end |
#rack_dir ⇒ Object
Convox v3 creates a folder for each rack for the Terraform config
64 65 66 67 |
# File 'lib/convox/client.rb', line 64 def rack_dir stack_name = config.fetch(:stack_name) File.join(CONVOX_CONFIG_DIR, 'racks', stack_name) end |
#rds_details ⇒ Object
347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/convox/client.rb', line 347 def rds_details require_config(%i[database_username database_password]) database = terraform_resource('aws_db_instance', 'rds_database') database_attributes = database['instances'][0]['attributes'] username = database_attributes['username'] password = database_attributes['password'] endpoint = database_attributes['endpoint'] postgres_url = "postgres://#{username}:#{password}@#{endpoint}/app" { postgres_url: postgres_url } end |
#run_convox_command!(cmd, env = {}, rack_arg: true) ⇒ Object
419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/convox/client.rb', line 419 def run_convox_command!(cmd, env = {}, rack_arg: true) # Always include the rack as an argument, to # make sure that 'convox switch' doesn't affect any commands command = "convox #{cmd}" if rack_arg command = "#{command} --rack #{config.fetch(:stack_name)}" end logger.debug "+ #{command}" system env, command raise "Error running: #{command}" unless $CHILD_STATUS.success? end |
#s3_bucket_details ⇒ Object
332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/convox/client.rb', line 332 def s3_bucket_details require_config(%i[s3_bucket_name]) s3_bucket = terraform_resource('aws_s3_bucket', 'docs_s3_bucket') bucket_attributes = s3_bucket['instances'][0]['attributes'] access_key = terraform_resource('aws_iam_access_key', 'docspring_user_access_key') key_attributes = access_key['instances'][0]['attributes'] { access_key_id: key_attributes['id'], secret_access_key: key_attributes['secret'], name: bucket_attributes['bucket'] } end |
#set_default_app_for_directory! ⇒ Object
235 236 237 238 239 240 241 |
# File 'lib/convox/client.rb', line 235 def set_default_app_for_directory! logger.info 'Setting default app in ./.convox/app...' FileUtils.mkdir_p File.('./.convox') File.open(File.('./.convox/app'), 'w') do |f| f.puts config.fetch(:convox_app_name) end end |
#terraform_resource(resource_type, resource_name) ⇒ Object
323 324 325 326 327 328 329 330 |
# File 'lib/convox/client.rb', line 323 def terraform_resource(resource_type, resource_name) resource = terraform_state['resources'].find do |resource| resource['type'] == resource_type && resource['name'] == resource_name end return resource if resource raise "Could not find #{resource_type} resource named #{resource_name} in terraform state!" end |
#terraform_state ⇒ Object
318 319 320 321 |
# File 'lib/convox/client.rb', line 318 def terraform_state tf_state_file = File.join(rack_dir, 'terraform.tfstate') JSON.parse(File.read(tf_state_file)) end |
#validate_convox_rack_and_write_current! ⇒ Object
Auth for a detached rack is not saved in the auth file anymore. It can be found in the terraform state: ~/Library/Preferences/convox/racks/ds-enterprise-cx3/terraform.tfstate Under outputs/api/value. The API URL contains the convox username and API token as basic auth.
139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/convox/client.rb', line 139 def validate_convox_rack_and_write_current! require_config(%i[aws_region stack_name]) unless rack_already_installed? raise "Could not find rack terraform directory at: #{rack_dir}" end # Tells the Convox CLI to use our terraform stack stack_name = config.fetch(:stack_name) write_current(stack_name) stack_name end |
#validate_convox_rack_api! ⇒ Object
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/convox/client.rb', line 158 def validate_convox_rack_api! require_config(%i[ aws_region stack_name instance_type ]) logger.debug 'Validating that convox rack has the correct attributes...' # Convox 3 racks no longer return info about region or type. (These are blank strings.) { provider: 'aws', # region: config.fetch(:aws_region), # type: config.fetch(:instance_type), name: config.fetch(:stack_name) }.each do |k, v| convox_value = convox_rack_data[k.to_s] if convox_value != v raise "Convox data did not match! Expected #{k} to be '#{v}', " \ "but was: '#{convox_value}'" end end logger.debug '=> Convox rack has the correct attributes.' true end |
#write_current(rack_name) ⇒ Object
152 153 154 155 156 |
# File 'lib/convox/client.rb', line 152 def write_current(rack_name) logger.debug "Setting convox rack to #{rack_name} (in #{CURRENT_FILE})..." current_hash = { name: rack_name, type: 'terraform' } File.open(CURRENT_FILE, 'w') { |f| f.puts current_hash.to_json } end |
#write_terraform_template(name) ⇒ Object
285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/convox/client.rb', line 285 def write_terraform_template(name) template_path = File.join(__dir__, "../../terraform/#{name}.tf.erb") unless File.exist?(template_path) raise "Could not find terraform template at: #{template_path}" end template = ERB.new(File.read(template_path)) template_output = template.result(binding) tf_file_path = File.join(rack_dir, "#{name}.tf") logger.debug "Writing terraform config to #{tf_file_path}..." File.open(tf_file_path, 'w') { |f| f.puts template_output } end |