class Morpheus::Cli::Deploy
Public Instance Methods
connect(opts)
click to toggle source
# File lib/morpheus/cli/commands/deploy.rb, line 10 def connect(opts) @api_client = establish_remote_appliance_connection(opts) @instances_interface = @api_client.instances @deploy_interface = @api_client.deploy @deployments_interface = @api_client.deployments end
handle(args)
click to toggle source
# File lib/morpheus/cli/commands/deploy.rb, line 17 def handle(args) options={} optparse = Morpheus::Cli::OptionParser.new do|opts| opts.banner = "Usage: morpheus deploy [environment]" build_common_options(opts, options, [:auto_confirm, :quiet, :remote, :dry_run]) opts.footer = <<-EOT Deploy to an instance using the morpheus.yml file, located in the working directory. [environment] is optional. Merge settings under environments.{environment}. Default is no environment. First the morpheus.yml YAML file is parsed, merging the specified environment's nested settings. The specified instance must exist and the specified deployment version must not exist. If the settings are valid, the new deployment version will be created. If is a file type deployment, all the discovered files are uploaded to the new deployment version. Finally, it deploys the new version to the instance using any specified config options. The morpheus.yml should be located in the working directory. This YAML file contains the settings that specify how to execute the deployment. File Settings ================== * name - (required) The instance name being deployed to, also the default name of the deployment. * version - (required) The version identifier of the deployment being created (userVersion) * deployment - The name of the deployment being created, name is used by default * type - The type of deployment, file, 'git' or 'fetch', default is 'file'. * script - The initial script to run, happens before finding the files to be uploaded. * files - (required) List of file patterns to use for uploading files and their target destination. Each item should contain path and pattern, path may be relative to the working directory, default pattern is: '**/*' only applies to type 'file' * url - (required) The url to fetch files from, only applies to types 'git' and 'fetch'. * ref - The git reference, default is master (main), only applies to type git. * config - Map of deployment config options depending on deployment type * options - alias for config * post_script - A post operation script to be run on the local machine * stage_only - If set to true the deploy will only be staged and not actually run * environments - Map of objects that contain nested properties for each environment name It is possible to nest these properties in an "environments" map to override based on a passed environment. Example ================== name: mysite version: 5.0 script: "rake build" files: - path: build environments: production: files: - path: production-build Git Example ================== name: morpheus-apidoc version: 5.0.0 type: git url: "https://github.com/gomorpheus/morpheus-apidoc" EOT end optparse.parse!(args) verify_args!(args:args, optparse:optparse, min:0, max:1) options[:options]['name'] = args[0] if args[0] connect(options) payload = {} environment = default_deploy_environment if args.count > 0 environment = args[0] end if load_deploy_file().nil? raise_command_error "Morpheus Deploy File `morpheus.yml` not detected. Please create one and try again." end # Parse and validate config, need instance + deployment + version + files # name can be specified as a single value for both instance and deployment deploy_args = merged_deploy_args(environment) instance_name = deploy_args['name'] if deploy_args['instance'].is_a?(String) instance_name = deploy_args['instance'] end if instance_name.nil? raise_command_error "Instance not specified. Please specify the instance name and try again." end deployment_name = deploy_args['name'] || instance_name if deploy_args['deployment'].is_a?(String) deployment_name = deploy_args['deployment'] end version_number = deploy_args['version'] if version_number.nil? raise_command_error "Version not specified. Please specify the version and try again." end instance_results = @instances_interface.list(name: instance_name) if instance_results['instances'].empty? raise_command_error "Instance not found by name '#{instance_name}'" end instance = instance_results['instances'][0] instance_id = instance['id'] # auto detect type, default to file deploy_type = deploy_args['type'] || deploy_args['deployType'] if deploy_type.nil? if deploy_args['gitUrl'] deploy_type = 'git' elsif deploy_args['fetchUrl'] || deploy_args['url'] deploy_type = 'fetch' end end if deploy_type.nil? deploy_type = "file" end deploy_url = deploy_args['url'] || deploy_args['fetchUrl'] || deploy_args['gitUrl'] if deploy_url.nil? && (deploy_type == "git" || deploy_type == "fetch") raise_command_error "Deploy type '#{deploy_type}' requires a url to be specified" end #deploy_type = "file" if deploy_type.to_s.downcase == "files" deploy_config = deploy_args['options'] || deploy_args['config'] # ok do it # fetch/create deployment, create deployment version, upload files, and deploy it to instance. unless options[:quiet] print_h1 "Morpheus Deployment", options columns = { "Instance" => :name, "Deployment" => :deployment, "Version" => :version, "Deploy Type" => :type, "Script" => :script, "Post Script" => :post_script, "Files" => :files, "Git Url" => :git_url, "Git Ref" => :git_ref, "Fetch Url" => :fetch_url, "Environment" => :environment, } pretty_file_config = deploy_args['files'] ? deploy_args['files'].collect {|it| [(it['path'] ? "path: #{it['path']}" : nil), (it['pattern'] ? "pattern: #{it['pattern']}" : nil)].compact.join(", ") }.join(", ") : "(none)" deploy_settings = { :name => instance_name, :deployment => deployment_name, :version => version_number, :script => deploy_args['script'], :post_script => deploy_args['post_script'], :files => pretty_file_config, :type => format_deploy_type(deploy_type), :git_url => deploy_args['gitUrl'] || (deploy_type == "git" ? deploy_args['url'] : nil), :git_ref => deploy_args['gitRef'] || (deploy_type == "git" ? deploy_args['ref'] : nil), :fetch_url => deploy_args['fetchUrl'] || (deploy_type == "fetch" ? deploy_args['url'] : nil), # :files => deploy_args['files'], # :files => deploy_files.size, # :file_config => (deploy_files.size == 1 ? deploy_files[0][:destination] : deploy_args['files']) :environment => environment } columns.delete("Script") if deploy_settings[:script].nil? columns.delete("Post Script") if deploy_settings[:post_script].nil? columns.delete("Environment") if deploy_settings[:environment].nil? columns.delete("Files") if deploy_type != "file" && deploy_type != "files" columns.delete("Git Url") if deploy_settings[:git_url].nil? columns.delete("Git Ref") if deploy_settings[:git_ref].nil? columns.delete("Fetch Url") if deploy_settings[:fetch_url].nil? print_description_list(columns, deploy_settings) print reset, "\n" if deploy_config print_h2 "Config Options", options print cyan puts as_json(deploy_config) print "\n\n", reset end end # unless options[:quiet] if !deploy_args['script'].nil? # do this for dry run too since this is usually what creates the files to be uploaded unless options[:quiet] print cyan, "Executing Pre Deploy Script...", reset, "\n" puts "running command: #{deploy_args['script']}" end if !system(deploy_args['script']) raise_command_error "Error executing pre script..." end end # Find Files to Upload deploy_files = [] if deploy_type == "file" || deploy_type == "files" if deploy_args['files'].nil? || deploy_args['files'].empty? || !deploy_args['files'].is_a?(Array) raise_command_error "Files not specified. Please specify the files to include, each item may specify a path or pattern of file(s) to upload" else #print "\n",cyan, "Finding Files...", reset, "\n" current_working_dir = Dir.pwd deploy_args['files'].each do |fmap| Dir.chdir(fmap['path'] || current_working_dir) files = Dir.glob(fmap['pattern'] || '**/*') files.each do |file| if File.file?(file) destination = file.split("/")[0..-2].join("/") # deploy_files << {filepath: File.expand_path(file), destination: destination} deploy_files << {filepath: File.expand_path(file), destination: file} end end end #print cyan, "Found #{deploy_files.size} Files to Upload!", reset, "\n" Dir.chdir(current_working_dir) end if deploy_files.empty? raise_command_error "0 files found for: #{deploy_args['files'].inspect}" else unless options[:quiet] print cyan, "Found #{deploy_files.size} Files to Upload!", reset, "\n" end end elsif deploy_type == "git" # make it work with simpler config, url instead of gitUrl if deploy_args['gitUrl'].nil? && deploy_args['url'] deploy_args['gitUrl'] = deploy_args['url'] # .delete('url') maybe? end if deploy_args['gitRef'].nil? && deploy_args['ref'] deploy_args['gitRef'] = deploy_args['ref'] # .delete('ref') maybe? end if deploy_args['gitRef'].nil? raise_command_error "fetchUrl not specified. Please specify the git url to fetch the deploy files from." end if deploy_args['gitRef'].nil? #raise_command_error "gitRef not specified. Please specify the git reference to use. eg. main" # deploy_args['gitRef'] = "main" end elsif deploy_type == "git" # make it work with simpler config, url instead of fetchUrl if deploy_args['fetchUrl'].nil? && deploy_args['url'] deploy_args['fetchUrl'] = deploy_args['url'] # .delete('url') maybe? end if deploy_args['fetchUrl'].nil? raise_command_error "fetchUrl not specified. Please specify the url to fetch the deploy files from." end end confirm_warning = "" confirm_message = "Are you sure you want to perform this action?" if deploy_type == "file" || deploy_type == "files" confirm_warning = "This will create deployment #{deployment_name} version #{version_number} and deploy it to instance #{instance['name']}." elsif deploy_type == "git" confirm_warning = "This will create deployment #{deployment_name} version #{version_number} and deploy it to instance #{instance['name']}." elsif deploy_type == "fetch" confirm_warning = "This will create deployment #{deployment_name} version #{version_number} and deploy it to instance #{instance['name']}." end puts confirm_warning if !options[:quiet] unless options[:yes] || Morpheus::Cli::OptionTypes.confirm(confirm_message) return 9, "aborted command" end # Find or Create Deployment deployment = nil deployments = @deployments_interface.list(name: deployment_name)['deployments'] @instances_interface.setopts(options) @deploy_interface.setopts(options) @deployments_interface.setopts(options) if deployments.size > 1 raise_command_error "#{deployments.size} deployment versions found by deployment '#{name}'" elsif deployments.size == 1 deployment = deployments[0] # should update here, eg description else # create it payload = { 'deployment' => { 'name' => deployment_name } } payload['deployment']['description'] = deploy_args['description'] if deploy_args['description'] if options[:dry_run] print_dry_run @deployments_interface.dry.create(payload) # return 0, nil deployment = {'id' => ':deploymentId', 'name' => deployment_name} else json_response = @deployments_interface.create(payload) deployment = json_response['deployment'] end end # Find or Create Deployment Version # Actually, for now this this errors if the version already exists, but it should update it. @deployments_interface = @api_client.deployments deployment_version = nil if options[:dry_run] print_dry_run @deployments_interface.dry.list_versions(deployment['id'], {userVersion: version_number}) # return 0, nil #deployment_versions =[{'id' => ':versionId', 'version' => version_number}] deployment_versions = [] else deployment_versions = @deployments_interface.list_versions(deployment['id'], {userVersion: version_number})['versions'] @deployments_interface.setopts(options) end if deployment_versions.size > 0 raise_command_error "Deployment '#{deployment['name']}' version '#{version_number}' already exists. Specify a new version or delete the existing version." # if deployment_versions.size > 1 # raise_command_error "#{deployment_versions.size} versions found by version '#{name}'" # elsif deployment_versions.size == 1 # deployment_version = deployment_versions[0] # # should update here, eg description else # create it payload = { 'version' => { 'userVersion' => version_number, 'deployType' => deploy_type } } payload['version']['fetchUrl'] = deploy_args['fetchUrl'] if deploy_args['fetchUrl'] payload['version']['gitUrl'] = deploy_args['gitUrl'] if deploy_args['gitUrl'] payload['version']['gitRef'] = deploy_args['gitRef'] if deploy_args['gitRef'] if options[:dry_run] print_dry_run @deployments_interface.dry.create_version(deployment['id'], payload) # return 0, nil deployment_version = {'id' => ':versionId', 'version' => version_number} else json_response = @deployments_interface.create_version(deployment['id'], payload) deployment_version = json_response['version'] end end # Upload Files if deploy_type == "file" || deploy_type == "files" if deploy_files && !deploy_files.empty? print "\n",cyan, "Uploading #{deploy_files.size} Files...", reset, "\n" if !options[:quiet] current_working_dir = Dir.pwd deploy_files.each do |f| destination = f[:destination] if options[:dry_run] print_dry_run @deployments_interface.upload_file(deployment['id'], deployment_version['id'], f[:filepath], f[:destination]) else print cyan," - Uploading #{f[:destination]} ...", reset if !options[:quiet] upload_result = @deployments_interface.upload_file(deployment['id'], deployment_version['id'], f[:filepath], f[:destination]) #print green + "SUCCESS" + reset + "\n" if !options[:quiet] print reset, "\n" if !options[:quiet] end end print cyan, "Upload Complete!", reset, "\n" if !options[:quiet] Dir.chdir(current_working_dir) else print "\n",cyan, "0 files to upload", reset, "\n" if !options[:quiet] end end if !deploy_args['post_script'].nil? print cyan, "Executing Post Script...", reset, "\n" if !options[:quiet] puts "running command: #{deploy_args['post_script']}" if !options[:quiet] if !system(deploy_args['post_script']) raise_command_error "Error executing post script..." end end # JD: restart for evars eh? if deploy_args['env'] evars = [] deploy_args['env'].each_pair do |key, value| evars << {name: key, value: value, export: false} end payload = {envs: evars} if options[:dry_run] print_dry_run @instances_interface.dry.create_env(instance_id, payload) print_dry_run @instances_interface.dry.restart(instance_id) else @instances_interface.create_env(instance_id, payload) @instances_interface.restart(instance_id) end end # Create the AppDeploy, this does the deploy async (as of 4.2.2-3) payload = {'appDeploy' => {} } payload['appDeploy']['versionId'] = deployment_version['id'] if deploy_args['options'] payload['appDeploy']['config'] = deploy_args['options'] end # stageOnly means do not actually deploy yet, can invoke @deploy_interface.deploy(deployment['id']) later # there is no cli command for that yet though.. stage_only = deploy_args['stage'] || deploy_args['stage_deploy'] || deploy_args['stage_only'] || deploy_args['stageOnly'] if stage_only payload['appDeploy']['stageOnly'] = true end # config/options to apply to deployment if deploy_config payload['appDeploy']['config'] = deploy_config end app_deploy_id = nil if options[:dry_run] print_dry_run @deploy_interface.dry.create(instance_id, payload) # return 0, nil app_deploy_id = ':appDeployId' else # Create a new appDeploy record, without stageOnly, this actually does the deployment #print cyan, "Deploying #{deployment_name} version #{version_number} to instance #{instance_name} ...", reset, "\n" deploy_result = @deploy_interface.create(instance_id, payload) app_deploy = deploy_result['appDeploy'] app_deploy_id = app_deploy['id'] if !options[:quiet] if app_deploy['status'] == 'staged' print_green_success "Staged Deploy #{deployment_name} version #{version_number} to instance #{instance_name}" else print_green_success "Deploying #{deployment_name} version #{version_number} to instance #{instance_name}" end end end return 0, nil end
Protected Instance Methods
default_deploy_environment()
click to toggle source
# File lib/morpheus/cli/commands/deploy.rb, line 478 def default_deploy_environment nil end
load_deploy_file()
click to toggle source
Loads a morpheus.yml file from within the current working directory. This file contains information necessary to perform a deployment via the cli.
Example File Attributes¶ ↑
-
script
- The initial script to run before uploading files -
name
- The instance name we are deploying to (can be overridden in CLI) -
files
- List of file patterns to use for uploading files and their target destination -
options
- Map of deployment options depending on deployment type -
post_script
- A post operation script to be run on the local machine -
stage_deploy
- If set to true the deploy will only be staged and not actually run
+NOTE: + It is also possible to nest these properties in an “environments” map to override based on a passed environment deploy name
# File lib/morpheus/cli/commands/deploy.rb, line 460 def load_deploy_file if !File.exist? "morpheus.yml" puts "No morpheus.yml file detected in the current directory. Nothing to do." return nil end @deploy_file = YAML.load_file("morpheus.yml") return @deploy_file end
merged_deploy_args(environment)
click to toggle source
# File lib/morpheus/cli/commands/deploy.rb, line 470 def merged_deploy_args(environment) deploy_args = @deploy_file.reject { |key,value| key == 'environment'} if environment && !@deploy_file['environment'].nil? && !@deploy_file['environment'][environment].nil? deploy_args = deploy_args.merge(@deploy_file['environment'][environment]) end return deploy_args end