#!/usr/bin/env ruby # encoding: UTF-8 # (c) Copyright 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'rubygems' # require 'debugger' # Use to debug with Ruby < 2.0 # require 'byebug' # Use to debug with Ruby >= 2.0 require 'bundler/setup' require 'thor' require 'ansi' require 'forj' APP_PATH = File.dirname(File.dirname(__FILE__)) LIB_PATH = File.expand_path(File.join(APP_PATH, 'lib')) require 'lorj' PrcLib.app_name = 'forj' PrcLib.app_defaults = File.join(APP_PATH, PrcLib.app_name) PrcLib.pdata_path = File.expand_path(File.join('~', '.cache', PrcLib.app_name)) # Initialize forj paths require 'appinit.rb' # Load Application settings features Forj.keypairs_path = File.join(PrcLib.data_path, 'keypairs') Forj.build_path = File.join(PrcLib.data_path, '.build') Forj.file_version = '1.0.0' require 'forj-settings.rb' # Settings features # This is the main Forj class # it helps you to create and support your forge. # rubocop:disable ClassLength class ForjThor < Thor class_option :debug, :aliases => '-d', :desc => 'Set debug mode' class_option :verbose, :aliases => '-v', :desc => 'Set verbose mode' class_option :config, :aliases => '-c', :desc => 'Path to a different forj'\ ' config file. By default, use ~/.forj/config.yaml' class_option :lorj_debug, :desc => 'Set lorj debug level verbosity. 1 to 5.'\ 'Default is one.' desc 'help [action]', 'Describe available FORJ actions or one specific action' def help(task = nil, subcommand = false) if task self.class.task_help(shell, task) else puts <<-LONGDESC Quick steps: How to create a forj? ---------------------------------- To test a forj blueprint, you will need an account on a cloud solution. Currently forj cli supports folowing providers: - openstack: openstack cloud and hp helion cloud (Fog) - hpcloud : HP Public cloud (Fog) This list can be expanded to more clouds. If you want to contribute to support your cloud, check out: http://github.com/forj-oss/lorj_cloud 1. Setup your FORJ account. `$ forj setup` This will create an account 'hpcloud' configured with 'hpcloud' provider If you want another provider: `$ forj setup MyAccountName openstack` The first time you setup an account, it will become the default one. 2. Create your forge on your default account `$ forj boot ` Ex: `forj boot redstone MyForge`. In this example: Forj will create a `redstone` blueprint forge named `MyForge`, using the default account. If you want to create your forge on another cloud account, do the following: `$ forj boot [-a account]` Ex: `forj boot redstone MyForge -a MyAccountName`. In this example: Forj will create a `redstone` blueprint forge named `MyForge`, using the 'MyAccountName' account. forj command line details: -------------------------- LONGDESC self.class.help(shell, subcommand) end end desc 'version', 'get GEM version of forj.' def version puts Gem.loaded_specs['forj'].version.to_s if Gem.loaded_specs['forj'] end ################################# BOOT desc 'boot [options]', 'boot a Maestro box and' \ ' instruct it to provision the blueprint' long_desc <<-LONGDESC This task boot a new forge with the following options \x5- blueprint : Is the name of the blueprint \x5- InstanceName : name of the forge Ex: forj boot redstone maestro_test -a dev When `forj boot` starts, some variables are loaded. If you want to check/updated them, use `forj get [-a account]` LONGDESC method_option :account_name, :aliases => '-a', :desc => 'Set the forj' \ ' account name to use. By default, uses the default account set in' \ ' your local config file.' + "\n\n" + 'Following options superseed your' \ ' Account, config file or forj defaults.' method_option :maestro_repo, :aliases => '-m', :desc => 'config: ' \ ' maestro_repo : To use a different Maestro repository already' \ ' cloned.' + "\n" + 'By default, Maestro is systematically cloned ' \ ' to ~/.forj/maestro from github.' method_option :infra, :aliases => '-r', :desc => 'config:' \ ' infra_repo : Defines your Infra directory to use while' \ ' booting. You can also set FORJ_INFRA_DIR.' method_option :key_name, :aliases => '-k', :desc => 'config:' \ ' keypair_name : Keypair name to use.' method_option :key_path, :aliases => '-p', :desc => 'config:' \ ' keypair_path : Private or Public key file to use.' method_option :security_group, :aliases => '-s', :desc => 'config:' \ ' security_group: Security group name to use and configure.' method_option :image_name, :aliases => '-i', :desc => 'config:' \ ' image_name : Image name to use to build Maestro and blueprint nodes.' method_option :maestro_flavor, :aliases => '-f', :desc => 'config:' \ ' flavor : Maestro flavor to use.' method_option :bp_flavor, :aliases => '-b', :desc => 'config:' \ ' bp_flavor : Blueprint nodes default flavor to use.'\ "\n\nBuild system options:" method_option :boothook, :aliases => '-H', :desc => 'By default, ' \ ' boothook file used is build/bin/build-tools/boothook.sh. ' \ ' Use this option to set another one.' method_option :build, :aliases => '-B', :desc => 'Replace' \ ' the default build.sh' method_option :branch, :aliases => '-R', :desc => 'Branch' \ "name to clone for maestro.\n\n"\ "Remote box bootstrap debugging (test-box):\n"\ 'test-box is a shell script used to connect one or more local repository'\ " to be connected to the new remote box. \n"\ 'If your box support test-box feature, at boot time, the remote box may '\ 'wait for your local repository to be sent out to the new box. '\ 'For more details on test-box, call it to get help.' method_option :tb_path, :aliases => '-t', :desc => 'Define the path to the test-box script. '\ 'This option superseeds the TEST_BOX '\ 'environment variable.' method_option :test_box, :aliases => '-T', :type => :array, :desc => "Use a local repository for test-box\n\n"\ 'Other options:' method_option :extra_metadata, :aliases => '-e', :desc => 'Custom' \ ' server metadata format key1=value1,key2=value2...,keyN=valueN' def boot(blueprint, on_or_name, old_accountname = nil, as = nil, old_name = nil) Forj::Settings.common_options(options) require 'boot.rb' deprecated_name = [old_accountname, as, old_name] Forj::Boot.boot(blueprint, on_or_name, deprecated_name, options) end ################################# Show defaults long_desc <<-LONGDESC This command helps to show values used by 'forj' to boot a blueprint (account data, default values, etc...) queriable Objects: - defaults : Provide the list of predefined values, configured by forj, or by you in your ~/.forj/config.yaml (or another config file with -c) - account [name] : without name, forj will give you the list of account saved. Otherwise print 'name' account data. LONGDESC desc 'show [name]', 'Show Object (default valuesr, account data,' \ ' etc...) values.' def show(object, name = nil) Forj::Settings.common_options(options) case object when 'defaults' o_config = Lorj::Account.new puts 'List of default values: (local refer to your config file.' \ ' hash refer to your FORJ account data)' dump = o_config.config_dump(%w(account local)) dump[:application] = o_config.config_dump(%w(default))[:default] puts dump.to_yaml puts '---' puts "To change default values, use 'forj get' to check valid keys," \ " and update with 'forj set'" when 'account' unless name o_accounts = Lorj::Accounts.new accounts = o_accounts.dump if accounts.length == 0 PrcLib.message 'No accounts found. Use forj setup [Account '\ '[provider]] to create your first account.' return end PrcLib.message "List of FORJ accounts: Use 'forj account YourAccount'" \ ' to see one account details.' puts accounts.to_yaml return end o_config = Lorj::Account.new o_config[:account_name] = name PrcLib.fatal(1, "Unable to load the account '%s'", name) unless o_config.ac_load o_config[:account_name] puts format("Account value for '%s':", name) puts o_config.config_dump([%(account)]).to_yaml puts '---' puts format("To change those values, execute 'forj setup -a %s'.", options[:account_name]) else PrcLib.error("object '%s' unknown.", name) end end ################################# DESTROY desc 'destroy [options]', 'delete the Maestro box and all' \ ' systems installed by the blueprint' long_desc <<-LONGDESC This action destroy all servers found under the instance name and allow you to destroy all of them or just one of them. Warning! This action do not remove any network/security groups cloud object. LONGDESC method_option :force, :aliases => '-f', :desc => 'force deletion of all' \ ' servers for the given InstanceName' method_option :account_name, :aliases => '-a', :desc => 'Set the forj' \ ' account name to use. By default, uses the default account set in' \ ' your local config file.' + "\n\n" + 'Following options superseed your' \ ' Account, config file or forj defaults.' def destroy(name) require 'destroy.rb' Forj::Settings.common_options(options) Forj::Destroy.destroy(name, options) end ################################# SET desc 'set [key=value] [...] [options]', 'Set one or more variables in' \ ' defaults or a forj account.' long_desc <<-LONGDESC You can set some variables to change 'forj' defaults or specifically some account data. Ex: By default, forj use ~/.ssh/forj-id_rsa as keypair for all forge instance. During setup, if this keypair doesn't exist, it proposes to create it for you, with ssh-keygen. If you want to use a keypair that already exists, you can set it as your default, with: `forj set keypair_name=~/.ssh/id_rsa` If you want to set this key only for your account 'dev': `forj set keypair_name=~/.ssh/id_rsa -a dev` If you want to get the list of possible key to set: `forj set` If you want to remove the key from dev, and ask for to re-use defaults (from your config or application default) `forj set keypair_name= -a dev` If you want to remove the key from your default, and re-use application default `forj set keypair_name=` LONGDESC method_option :account_name, :aliases => '-a', :desc => 'Set the forj' \ ' account name to use. By default, uses the default account set in your' \ ' local config file.' def set(*p) PrcLib.level = Logger::INFO if options[:verbose] PrcLib.level = Logger::DEBUG if options[:debug] if p.length == 0 Forj::Settings.show_settings(options) else Forj::Settings.set_settings(options, p) end end ################################# GET desc 'get', 'Get data from defaults or account values.' long_desc <<-LONGDESC forj cli maintain a list of key/value at 3 Levels: \x5- Application defaults \x5- Local config defaults \x5- Account data This function will help you identify which value has been retrieved from which level. Ex: To get the default keypair_name, from your ~/.forj/config.yaml, or if not found, from application defaults. `forj get keypair_name` Ex: To get the keypair_name defined from the account, or from your ~/.forj/config.yaml, or if not found, from application defaults. `forj get keypair_name -a dev` LONGDESC method_option :account_name, :aliases => '-a', :desc => 'Set the forj' \ ' account name to use. By default, uses the default account set in your' \ ' local config file.' def get(key = nil) PrcLib.level = Logger::INFO if options[:verbose] PrcLib.level = Logger::DEBUG if options[:debug] require 'get.rb' Forj::Get.get(options, key) end ################################# SSH desc 'ssh [options]', 'connect to your forge thru ssh' long_desc <<-LONGDESC Connect through ssh to a node attached to an instance ex: forj ssh myforge -n review LONGDESC method_option :box_name, :aliases => '-n', :desc => 'box name to' \ ' create ssh connection' method_option :identity, :aliases => '-i', :desc => 'Private key' \ ' file name.' method_option :account_name, :aliases => '-a', :desc => 'Set the forj' \ ' account name to use. By default, uses the default account set in your' \ ' local config file.' def ssh(oInstanceName) Forj::Settings.common_options(options) require 'ssh.rb' account = Lorj::Account.new(options[:config]) # Setting account at runtime layer account[:account_name] = options[:account_name] if options[:account_name] # Setting account at account layer unless account.ac_load account[:account_name] PrcLib.fatal(1, "Invalid account '%s'. Use `forj show account` "\ 'to get the list of valid accounts.', account[:account_name]) end account.set(:box_ssh, options[:box_name]) if options[:box_name] account.set(:identity, options[:identity]) if options[:identity] Forj::Ssh.connect(oInstanceName, account) end ################################# SETUP desc 'setup [AccountName [Provider]] [options]', 'Setup FORJ cloud account' \ ' credentials and information.' long_desc <<-LONGDESC This setup will configure a FORJ account used to connect to your cloud system. \x5It will ask for your cloud provider credentials and services. If AccountName is not set, 'hpcloud' will be used for AccountName and provider name, by default. \x5If AccountName is not set without provider, 'hpcloud' provider will be used, by default. WARNING! Currently supports only hpcloud provider. Several data will be requested like: \x5- Cloud provider credentials and services. \x5- user/password (password is encrypted) \x5- DNS settings if you want Maestro to manage it. \x5- domain name to add to each boxes hostname LONGDESC def setup(sAccountName = 'hpcloud', sProvider = 'hpcloud') Forj::Settings.common_options(options) require 'cloud_connection.rb' account = Lorj::Account.new(options[:config], Forj.file_version) account.ac_new(sAccountName, sProvider) unless account.ac_load(sAccountName) o_cloud = Forj::CloudConnection.connect(account) PrcLib.high_level_msg("Setting up '%s' with provider '%s'\n", sAccountName, account[:provider]) o_cloud.setup(:forge) o_cloud.config.ac_save unless o_cloud.config.local_exist?(:account_name) PrcLib.info("Setting account '%s' as default. You can change it with "\ '`forj set account_name=`', sAccountName) o_cloud.config.local_set(:account_name, sAccountName) end o_cloud.config.save_local_config PrcLib.high_level_msg("\nAccount %s '%s' saved.\n", sProvider, sAccountName) end end ForjThor.start