#!/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 'require_relative' require 'thor' require 'ansi' $APP_PATH = File.dirname(__FILE__) $LIB_PATH = File.expand_path(File.join(File.dirname($APP_PATH),'lib')) $LOAD_PATH << $LIB_PATH require 'boot.rb' require 'down.rb' require 'setup.rb' require 'ssh.rb' include Boot include Down include Setup include Ssh require 'forj-config.rb' # Load class ForjConfig require 'forj-account.rb' # Load class ForjAccount require 'log.rb' # Load default loggers require 'connection.rb' # Load class ForjConnection #require 'debugger' # Use to debug with Ruby < 2.0 #require 'byebug' # Use to debug with Ruby >= 2.0 include Logging # Initialize forj paths ensure_forj_dirs_exists() # Initialize global Log object $FORJ_LOGGER=ForjLog.new() class Forj < 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' 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 only HPHelion (https://horizon.hp.com) but will be expanded to support most of known clouds supported by FOG. (http://fog.io) 1. Setup your FORJ account. `$ forj setup [AccountName] [--provider Provider]` Ex: `forj setup MyForjAccount`. In this example, you will setup 'MyForjAccount' with your HPHelion account. The first time, this account will become the default one. **IMPORTANT NOTE** By default, forj setup will propose you to create your forge on HPHelion (provider 'hpcloud'). If AccountName is not set, the account name will be proposed to be name 'hpcloud' as well! 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 `MyForjAccount` forj command line details: -------------------------- LONGDESC self.class.help(shell, subcommand) end end ################################# BOOT desc 'boot on as [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 (currently cli only supports redstone) \x5- InstanceName : name of the forge Ex: forj boot redstone on hpcloud as maestro_test `forj boot` load predefined forj values from `lib/defaults.yaml`. If you need to change one of them, add the key/value in your local config (-c or `~/.forj/config.yaml`). The list of predefined values can be retrieved with forj show defaults 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." method_option :maestro_repo, :aliases => '-m', :desc => "To use a different Maestro repository already cloned. By default, Maestro is systematically cloned to ~/.forj/maestro from github. Following options superseed your config file or forj defaults." 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, :aliases => '-i', :desc => "config: image : 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. Build 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. Maestro/infra bootstrap debugging:" method_option :test_box, :aliases => '-T', :desc => "Identify a path to become your test-box repository. Ex: if your maestro is in ~/src/forj-oss, --test_box ~/src/forj-oss/maestro build.sh and test-box will send your local maestro repo to your box, for boot." def boot(blueprint, on_or_name, old_accountname = nil, as = nil, old_name = nil) Logging.set_level(Logger::INFO) if options[:verbose] Logging.set_level(Logger::DEBUG) if options[:debug] oConfig = ForjConfig.new(options[:config]) # depreciated: on as if old_accountname and as and old_name msg = "The syntax `forj boot '%s' on '%s' as '%s'` is depreciated.\nUse `forj boot '%s' '%s'" % [blueprint, old_accountname, old_name, blueprint, old_name] if oConfig.get('account_name') == old_accountname Logging.warning("%s` instead." % msg) else Logging.warning("%s -a '%s'` instead." % [msg, old_accountname]) end name = old_name oConfig.set('account_name', old_accountname) else name = on_or_name end Logging.fatal(1, "instance name '%s' not supported. Support only lower case, numeric and dash caracters." % [name]) if not /^[\d[[:lower:]]-]+$/ =~ name # Options are added if they are set. Otherwise, get will retrieve the default value. oConfig.set('account_name', options[:account_name]) if options[:account_name] oConfig.set('infra_repo', options[:infra]) oConfig.set('keypair_name', options[:key_name]) oConfig.set('keypair_path', options[:key_path]) oConfig.set('security_group', options[:security_group]) oConfig.set('image', options[:image]) oConfig.set('flavor', options[:maestro_flavor]) oConfig.set('bp_flavor', options[:bp_flavor]) oConfig.set(:maestro_repo , options[:maestro_repo]) oConfig.set(:test_box, File.expand_path(options[:test_box])) if options[:test_box] and File.directory?(File.expand_path(options[:test_box])) if options[:key_path] mFound = options[:key_path].match(/^(.*)(\.pub)?$/) if mFound key_path = File.expand_path(mFound[1]) if mFound[2] and not File.exists?(File.expand_path(mFound[1]+mFound[2])) Logging.fatal(1, "'%s' is not a valid keypair files. At least the public key (.pub) is have to exist.") end oConfig.set('keypair_path', key_path) else Logging.fatal(1, "'%s' is not a valid keypair files. At least the public key (.pub) is have to exist.") end end Boot.boot(blueprint, name, options[:build], options[:build_config], options[:branch], options[:boothook], options[:box_name], oConfig) end ################################# Show defaults desc 'show defaults', 'Show list of predefined value you can update in your ~/.forj/config.yaml' 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 show(name) case name when 'defaults' oConfig=ForjConfig.new() oConfig.set('account_name', options[:account_name]) oForjAccount = ForjAccount.new(oConfig) oForjAccount.ac_load() puts 'List of default values: (local refer to your config file. hash refer to your FORJ account data)' puts '-----------------------' puts oConfig.default_dump([oForjAccount.hAccountData]).to_yaml puts '-----------------------' puts "To change default values, update your ~/.forj/config.yaml and add the key/value entry under 'default' section." else Logging.error("object '%s' unknown." % name) end end ################################# DOWN desc 'down', 'delete the Maestro box and all systems installed by the blueprint' long_desc <<-LONGDESC Not yet implemented LONGDESC def down(name) Logging.set_level(Logger::INFO) if options[:verbose] Logging.set_level(Logger::DEBUG) if options[:debug] Down.down(name) end ################################# SSH desc 'ssh', 'connect to your forge thru ssh' long_desc <<-LONGDESC Connect through ssh to an existing instance not yet implemented LONGDESC def ssh(name, server) Logging.set_level(Logger::INFO) if options[:verbose] Logging.set_level(Logger::DEBUG) if options[:debug] Ssh.connect(name, server) end ################################# SETUP method_option :keypair_name, :aliases => '-k', :desc => "config keypair_name : Keypair name attached as default to your FORJ account." method_option :keypair_path, :aliases => '-p', :desc => "config keypair_path : SSH key file (private or public) to attach as default to your FORJ account. It will attach any detected private/public key thanks to the file name (without extension/.pem = private, .pub = public)" 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") Logging.set_level(Logger::INFO) if options[:verbose] Logging.set_level(Logger::DEBUG) if options[:debug] oConfig=ForjConfig.new(options[:config]) oConfig.set('provider', sProvider) oConfig.set('account_name', sAccountName) oConfig.set('keypair_path', options[:keypair_path]) if options[:keypair_path] oConfig.set('keypair_name', options[:keypair_name]) if options[:keypair_name] Setup.setup(oConfig) end end Forj.start