rhc-1.38.7/0000755000004100000410000000000012756270552012431 5ustar www-datawww-datarhc-1.38.7/Rakefile0000644000004100000410000000014312756270552014074 0ustar www-datawww-data#!/usr/bin/ruby require 'rubygems' require 'rake' Dir.glob('tasks/*.rake').each { |r| import r } rhc-1.38.7/bin/0000755000004100000410000000000012756270552013201 5ustar www-datawww-datarhc-1.38.7/bin/rhc0000755000004100000410000000125312756270552013704 0ustar www-datawww-data#!/usr/bin/env ruby require 'rhc/coverage_helper' def get_args ARGV.shift args = "" ARGV.each do|a| if ( a.to_s.strip.length == 0 || a.to_s.strip.match(/\s/) ); a = "'#{a}'" end args += " #{a}" end args end begin Signal.trap("PIPE", "EXIT") if Signal.list["PIPE"] retcode = begin require 'rhc/cli' RHC::CLI.set_terminal RHC::CLI.start(ARGV) rescue Interrupt puts "Interrupted" 128 + 2 rescue SystemExit => e puts e.status end if retcode == nil retcode = 1 # return codes for uncaught signals are 128 + the signal code retcode = 128 + $?.termsig if $?.signaled? and !$?.termsig.nil? end exit retcode end rhc-1.38.7/features/0000755000004100000410000000000012756270552014247 5ustar www-datawww-datarhc-1.38.7/features/members_feature.rb0000644000004100000410000001567112756270552017753 0ustar www-datawww-datarequire 'spec_helper' require 'direct_execution_helper' describe "rhc member scenarios" do context "with an existing domain" do before(:all) do standard_config @domain = has_a_domain end let(:domain){ @domain } context "with no users" do before{ no_members(domain) } it "should not show members in the domain" do r = rhc 'show-domain', domain.name r.status.should == 0 r.stdout.should_not match "Members:" r.stdout.should match "owned by #{domain.owner.name}" end it "should prevent leaving the domain for the owner" do r = rhc 'leave-domain', domain.name r.status.should_not == 1 r.stdout.should match "Leaving domain.*You are the owner of this domain and cannot leave" end it "should add and remove a member" do user = other_users.keys.take(1).first r = rhc 'add-member', user, '-n', domain.name r.status.should == 0 r.stdout.should match "Adding 1 editor to domain" r.stdout.should match "done" client.find_domain(domain.name).members.any?{ |m| m.id == other_users[user].id && m.editor? }.should be_true r = rhc 'show-domain', domain.name r.status.should == 0 r.stdout.should match "Members:" r.stdout.should match "#{user} \\(edit\\)" r = rhc 'remove-member', user, '-n', domain.name r.status.should == 0 r.stdout.should match "Removing 1 member from domain" client.find_domain(domain.name).members.none?{ |m| m.id == other_users[user].id }.should be_true end it "should add and remove two members" do user1, user2 = other_users.keys.take(2) r = rhc 'add-member', user1, user2, '-n', domain.name r.status.should == 0 r.stdout.should match "Adding 2 editors to domain" r.stdout.should match "done" members = client.find_domain(domain.name).members members.any?{ |m| m.id == other_users[user1].id && m.editor? }.should be_true members.any?{ |m| m.id == other_users[user2].id && m.editor? }.should be_true r = rhc 'show-domain', domain.name r.status.should == 0 r.stdout.should match "Members:" r.stdout.should match "#{user1} \\(edit\\)" r.stdout.should match "#{user2} \\(edit\\)" r = rhc 'remove-member', user1, user2, '-n', domain.name r.status.should == 0 r.stdout.should match "Removing 2 members from domain" client.find_domain(domain.name).members.none?{ |m| m.id == other_users[user1].id }.should be_true client.find_domain(domain.name).members.none?{ |m| m.id == other_users[user2].id }.should be_true end it "should add a view and an admin member. and allow users to leave the domain" do user1, user2 = other_users.keys.take(2) r = rhc 'add-member', user1, '--role', 'admin', '-n', domain.name r.status.should == 0 r.stdout.should match "Adding 1 administrator to domain" r.stdout.should match "done" client.find_domain(domain.name).members.any?{ |m| m.id == other_users[user1].id && m.admin? }.should be_true r = rhc 'add-member', user2, '--role', 'view', '-n', domain.name r.status.should == 0 r.stdout.should match "Adding 1 viewer to domain" r.stdout.should match "done" client.find_domain(domain.name).members.any?{ |m| m.id == other_users[user2].id && m.viewer? }.should be_true r = rhc 'show-domain', domain.name r.status.should == 0 r.stdout.should match "Members:" r.stdout.should match "#{user1} \\(admin\\)" r.stdout.should match "#{user2} \\(view\\)" r = rhc 'leave-domain', domain.name, :as => other_users[user2] r.status.should == 0 r.stdout.should match "Leaving domain.*done" end it "should remove all non owners" do user1, user2 = other_users.keys.take(2) r = rhc 'add-member', user1, user2, '-n', domain.name r.status.should == 0 r.stdout.should match "Adding 2 editors to domain" r.stdout.should match "done" members = client.find_domain(domain.name).members members.any?{ |m| m.id == other_users[user1].id && m.editor? }.should be_true members.any?{ |m| m.id == other_users[user2].id && m.editor? }.should be_true r = rhc 'remove-member', '-n', domain.name, '--all' r.status.should == 0 r.stdout.should match "Removing all members from domain.*done" members = client.find_domain(domain.name).members members.select(&:owner).should == members end it "should reject a non-existent user" do r = rhc 'add-member', 'not-a-user', '-n', domain.name r.status.to_i.should == 256 r.stdout.should match "There is no account with login not-a-user." client.find_domain(domain.name).members.length.should == 1 end it "should add a user by id" do user = other_users.values.take(1).first r = rhc 'add-member', user.id, '--ids', '-n', domain.name r.status.should == 0 r.stdout.should match "Adding 1 editor to domain" r.stdout.should match "done" client.find_domain(domain.name).members.any?{ |m| m.id == user.id && m.editor? }.should be_true end end context "with an application" do let(:other_user){ other_users.values.first } before{ has_an_application } before{ has_local_ssh_key(other_user) } it "should allow SSH only for admin and edit roles" do user = other_user.login name = @domain.applications.first.name r = rhc 'add-member', user, '--role', 'admin', '-n', domain.name r.status.should == 0 with_environment(other_user) do r = rhc 'ssh', name, '-n', domain.name, '--ssh', ssh_exec_for_env r.status.should == 0 end r = rhc 'add-member', user, '--role', 'view', '-n', domain.name r.status.should == 0 with_environment(other_user) do r = rhc 'ssh', name, '-n', domain.name, '--ssh', ssh_exec_for_env r.status.to_i.should_not == 0 end r = rhc 'add-member', user, '--role', 'edit', '-n', domain.name r.status.should == 0 with_environment(other_user) do r = rhc 'ssh', name, '-n', domain.name, '--ssh', ssh_exec_for_env r.status.should == 0 end end it "should filter applications by owner" do user = other_user.login name = @domain.applications.first.name r = rhc 'add-member', user, '--role', 'admin', '-n', domain.name r.status.should == 0 with_environment(other_user) do r = rhc 'apps', '--mine' #r.status.should == 0 r.stdout.should match "No applications" r = rhc 'apps' r.status.should == 0 r.stdout.should match /You have access to \d+ applications?/ end end after { @domain.applications.first.destroy } end end endrhc-1.38.7/features/core_feature.rb0000644000004100000410000001422512756270552017243 0ustar www-datawww-datarequire 'spec_helper' require 'direct_execution_helper' describe "rhc core scenarios" do it "reports a version" do r = rhc '--version' r.status.should == 0 r.stdout.should match /rhc \d+\.\d+\.\d+\b/ end it "displays help" do r = rhc 'help' r.status.should == 0 r.stdout.should match "Command line interface for OpenShift" r.stdout.should match "Usage: rhc" r.stdout.should match "Getting started" r.stdout.should match "See 'rhc help options' for a list" end context "with a clean configuration" do before{ use_clean_config } it "walks through a configuration" do r = rhc :setup, :with => setup_args r.stdout.should match 'OpenShift Client Tools' r.stdout.should match 'Checking for git ...' r.stdout.should match 'Checking for applications ...' r.stdout.should match 'Your client tools are now configured.' r.status.should == 0 r = rhc :account r.stdout.should match "on #{ENV['RHC_SERVER']}" r.stdout.should match 'Gears Allowed' r.stdout.should match 'Allowed Gear Sizes' r.stdout.should match 'Gears Used' r.stdout.should match 'SSL Certificates' end it "displays help on default invocation" do r = rhc r.status.should == 0 r.stdout.should match "Command line interface for OpenShift" r.stdout.should match "Usage: rhc" r.stdout.should match "Getting started" r.stdout.should match "See 'rhc help options' for a list" end end context "when creating an app" do when_running 'create-app', 'test1', a_web_cartridge before{ no_applications } after { no_applications } it "returns the proper info and is in the rest api" do status.should == 0 output.should match "Your application 'test1' is now available" output.should match /Gear Size: .*default/ output.should match /Scaling: .*no/ output.should match %r(URL: .*http://test1-) output.should match "Cloned to" apps = client.applications apps.should_not be_empty apps.should include{ |app| app.name == 'test1' } end end context "with an existing app" do before(:all) do standard_config @app = has_an_application end after(:all){ @app.destroy } let(:app){ @app } it "should display domain list" do r = rhc 'domains' r.status.should == 0 r.stdout.should match "Domain #{app.domain_id}" end it "should show app state" do r = rhc 'app-show', app.name, '--state' r.status.should == 0 r.stdout.should match "Cartridge #{a_web_cartridge} is started" end it "should stop and start the app" do r = rhc 'stop-app', app.name r.status.should == 0 r.stdout.should match "#{app.name} stopped" r = rhc 'start-app', app.name r.status.should == 0 r.stdout.should match "#{app.name} started" end it "should show gear status" do r = rhc 'app-show', app.name, '--gears' r.status.should == 0 r.stdout.lines.to_a.length.should == 3 r.stdout.should match app.ssh_string app.cartridges.map(&:name).each do |c| r.stdout.should match c end r.stdout.should match "started" end it "should show gear ssh strings" do r = rhc 'app-show', app.name, '--gears', 'ssh' r.status.should == 0 r.stdout.lines.to_a.length.should == 1 r.stdout.chomp.should == app.ssh_string end context "when the app is cloned" do before(:all) do rhc('git-clone', @app.name).status.should == 0 Dir.exists?(@app.name).should be_true Dir.chdir @app.name end let(:git_config){ `git config --list` } let(:git_remotes){ `git remote -v` } it "will set Git config values" do git_config.should match "rhc.app-id=#{app.id}" git_config.should match "rhc.app-name=#{app.name}" git_config.should match "rhc.domain-name=#{app.domain_name}" end it "will set remote branches correctly" do git_remotes.should match "origin" git_remotes.should_not match "upstream" end it "will infer the current app from the git repository" do r = rhc 'show-app' r.stdout.should match app.name r.stdout.should match app.id r.stdout.should match app.ssh_string r.stdout.should match app.app_url (app.cartridges.map(&:name) + app.cartridges.map(&:display_name)).each{ |n| r.stdout.should match n } r.status.should == 0 end it "will fetch the quotas from the app" do r = rhc 'show-app', '--gears', 'quota' r.stdout.chomp.lines.count.should == (app.gear_count + 2) app.cartridges.map(&:name).each{ |n| r.stdout.should match n } app.cartridges.map(&:gear_storage).each{ |n| r.stdout.should match(RHC::Helpers.human_size(n)) } r.status.should == 0 end it "will ssh to the app and run a command" do r = rhc 'ssh', '--', '--ssh', ENV['GIT_SSH'], 'echo $OPENSHIFT_APP_NAME' r.stdout.should match app.name r.status.should == 0 end end end context "when adding a cartridge" do context "with a scalable app" do before(:each) do standard_config has_gears_available(2) # 1 for the app create, 1 for the scale @app = has_a_scalable_application end after(:each) do debug.puts "cleaning up scalable app" if debug? @app.destroy end let(:app){ @app } it "should add a cartridge with small gear size" do cartridge = a_random_cartridge(['embedded', 'service', 'database']) r = rhc 'add-cartridge', cartridge, '-a', app.name, '--gear-size', 'small' r.stdout.should match /#{cartridge}/ r.stdout.should match /Gears:\s+1 small/ r.status.should == 0 end it "should fail for a cartridge with not allowed gear size" do cartridge = a_random_cartridge(['embedded', 'service', 'database']) r = rhc 'add-cartridge', cartridge, '-a', app.name, '--gear-size', 'medium' r.stdout.should match "The gear size 'medium' is not valid for this domain. Allowed sizes: small." r.status.should_not == 0 end end end end rhc-1.38.7/features/server_feature.rb0000644000004100000410000000543612756270552017625 0ustar www-datawww-datarequire 'spec_helper' require 'direct_execution_helper' require 'rhc/helpers' describe "rhc server scenarios" do context "with standard config" do before(:all){ standard_config } it "should list the default server" do r = ensure_command 'servers' r.stdout.should match /Server 'server1' \(in use\)/ r.stdout.should match /Hostname:\s+#{server}/ r.stdout.should match /Login:\s+#{rhlogin}/ r.stdout.should match /Use Auth Tokens:\s+true/ r.stdout.should match /Insecure:\s+true/ should_list_servers(1) end it "should and and remove servers" do should_add_mock_servers(2) should_list_servers(3) should_remove_server('mock1') should_remove_server('mock2') should_list_servers(1) end end context "with a clean configuration" do before(:all){ use_clean_config } it "should add one working server" do should_list_servers(0) should_add_working_server should_list_servers(1) end end private def ensure_command(*args) r = rhc *args r.status.should == 0 r end def should_list_servers(quantity) r = ensure_command 'servers' if quantity == 0 r.stdout.should match /You don't have any servers configured/ else r.stdout.should match /You have #{RHC::Helpers.pluralize(quantity, 'server')} configured/ end r end def should_add_mock_servers(quantity) Array(1..quantity).each do |i| new_server = "foo#{i}.openshift.com" new_user = "user#{i}" new_nickname = "mock#{i}" r = ensure_command 'server', 'add', new_server, new_nickname, '-l', new_user, '--skip-wizard' r.stdout.should match /Saving server configuration to .*servers\.yml .* done/ r = ensure_command 'servers' r.stdout.should match /Server '#{new_nickname}'$/ r.stdout.should match /Hostname:\s+#{new_server}/ r.stdout.should match /Login:\s+#{new_user}/ end end def should_add_working_server r = rhc 'server', 'add', server, '-l', rhlogin, '--insecure', '--use', :with => ['password', 'yes'] r.stdout.should match /Saving configuration to .*express\.conf .* done/ r.status.should == 0 r = ensure_command 'servers' r.stdout.should match /Server 'server1' \(in use\)/ r.stdout.should match /Hostname:\s+#{server}/ r.stdout.should match /Login:\s+#{rhlogin}/ r.stdout.should match /Use Auth Tokens:\s+true/ r.stdout.should match /Insecure:\s+true/ end def should_remove_server(server) r = ensure_command 'server', 'remove', server r.stdout.should match /Removing.*done/ r end def server ENV['RHC_SERVER'] || 'localhost' end def rhlogin ENV['TEST_USERNAME'] end endrhc-1.38.7/features/assets/0000755000004100000410000000000012756270552015551 5ustar www-datawww-datarhc-1.38.7/features/assets/deploy.tar.gz0000644000004100000410000001376412756270552020207 0ustar www-datawww-dataCETR]Ww۸s~9ׅ]'gQT*/9 M,Vɚ~Ae%-;o%| \~9%'y;(``$C2;U{.|b;bZ( eB=׸ hВtr,rlR  }9Adٔ$cܷgE@xzX?E) p>ddRK@oz''e@+R/އ#"Bh,o=lPT!ӫ-&FoD-(R}omSKQgT'k܅k/?g$A?Rr8{~z9C$GlbE;KgT'cU_B{^~WKH12_jMWx%O?ͥHX-L 1M X1,~EWq8@lQa^Xn[W+^w!M0Y8=$ø|;ox^ߏpg923[2܄ouZ,{jdp?HLJ'W07/ofKV& @nLLҀAs&\!"4Wpw3ӫ1%ئh W?C/%͵M u߀_&@b`n(.LӾ0IRuC{TD}>BDLL w%u`3.B72C`yKՁqϡ%ו.ZchF&p.`ߎ ˺0:JbRNX2ƿ]$T]<GoӀlQ&GB0fQZ {%搄<*UEqAB)=Buzkԋ2kU\h }WkdA}q4*ڮ C$̳ ]>I$WD[L0Hȣ4T-"܏([H}[Ca +{5n4P+솙k?i5ÇADE0L* 9 5H'ϲA)i"܇$>a+vCnl^Uh e[tr9עb!/cm#]!CɎtC>W ^U yPrﵺ"q6$A6~`?ʄ:gvT] x'+h6Z]>|RyqQݰ^'r~8wxr+N(d!L}<nȣ ]c*z.ϙr(<"9D6@âڰU>sϙjaw,Sx9\`M4#tc{\ń)rQu{-"KsQj :q&|Z;eҔ wSPͪO”'6-U6n ;)(.Va*N4U2Ӆ< &qٗr1wWnJ}S=~es|nhl-izh*d#lHuEoN/ZB+}j;S2-̷T^h [^mBz`n %mGVNpQ[*ؘy9;[[/Wr"MY zU[.j]*4)Cyͅ%Qusl Ujs;lyaDkVUo9lrsy{*)$ծJ0#/b/ v# ں"8C~fYtVڕn얈ۉgRS#f҄ yqX]nAXjkbkBeF˵۷.S'!ysӾcjڡ6Y+5rҵjt`L ͹="X^sNk K QŝNݔy.7zoӚLN5'/]&zJ/`Ze(<]w EPz~PUl.6E/kлzf6}Z"K`Ep\kDy+Z iczGhֱw}rx1َ`{j~aqgj/X8Q 6+5hѲBuZ9H';;g̹]W=r잳db7zbW⢸5X|mحV\Cf%j3Mq,n5l"7Y0g.$ Ag@8KK6fBn4u1mEv&jɶS絮Y&ma"U{S@"Fei闻ER[\Ĺj^#j = {Z6*0蛂poe]̗wmK/PC#~فQQ'JC7Ķvbt>sna{2,o~iقk (j0SQp/Cr!J\M cQľd]cX,ʅ'U], q\U0W uqBdBC^CR'𴨘v2`!y[^?4jwԿI5/w6Ѷ;-ΰÀw^{_~r~r=0dL>p0wbq9 ̗ŽHG[? qG>@ U CQq!lx$|mM߽‣,2|Ai}Or)߈Pl#NSw"8/u+??@ ?9Koyl).58[S$=<̦O&ŸwI&Av1 _G@8_{WۛW6 DJQH)IDmzj{gfmL  Z#^vvvdz]c@xW6u|R({a8knTw ax7$u(<w`}W>U ^?.Wn]>lqƿc'v_-BDũ` 5&t]DEJ[?Es}4"Kq,A2K֚Kyh4-_%KZZU%7~U $f2r\[h6zZMZ["`e"];vT;[&*RLSr }S]9nX;V?q%űWϴ 0rMoݙ102#c)72L780Q~ͩ6mMM52(=6:9RQ|I>bUȓ$p. ~EɰB.CŒb\G GEH.QTP51ˈo9嬪S1NÖȤkn"MZX؂-po,%6?L"=T^=C;N%CΕGh[j9P E磌(GkKiEFŁt;5ݸ^;ؠ6nIٵ]ϓZ[ֵb4 |XI~OOӝE4 jiTs퀨|tg00_> / xO ).`%F W!A`&nQ̈́z&C;gj&=k$No|w,w~l]?e1-a^nAwM9_nLW!8!$RoQ\WxYMh>It-׃" ٟm'!mi3 @ഡhHlL),70ߊT"R&Mw߻čżbj 0R|df˺Eh'b88%w-n.3OWfuORas#Q`AX4)Atp%} =7ӔC0ϗ7Kܙ?viwWrms-K8rlz5]{_$" client.reset.find_domain(domain.name).allowed_gear_sizes.should == [] all_sizes = client.user.capabilities.gear_sizes r = rhc 'configure-domain', domain.name, '--allowed-gear-sizes', all_sizes.join(',') r.status.should == 0 r.stdout.should match "Allowed Gear Sizes:\s+#{all_sizes.join(', ')}$" client.reset.find_domain(domain.name).allowed_gear_sizes.should == all_sizes end it "should reject invalid gear size configuration changes" do all_sizes = client.user.capabilities.gear_sizes valid_sizes = client.api.links['ADD_DOMAIN']['optional_params'].inject([]) {|sizes, p| sizes += p['valid_options'] if p['name'] == 'allowed_gear_sizes' } rescue [] disallowed_sizes = valid_sizes - all_sizes r = rhc 'configure-domain', domain.name, '--allowed-gear-sizes', '_not_a_size_' r.status.should_not == 1 r.stdout.should match "Updating domain configuration.*The following gear sizes are invalid: _not_a_size_" client.reset.find_domain(domain.name).allowed_gear_sizes.should == all_sizes if disallowed_sizes.first r = rhc 'configure-domain', domain.name, '--allowed-gear-sizes', disallowed_sizes.first r.status.should_not == 1 r.stdout.should match "Updating domain configuration.*The following gear sizes are not available.*: #{disallowed_sizes.first}" client.reset.find_domain(domain.name).allowed_gear_sizes.should == all_sizes end r = rhc 'configure-domain', domain.name, '--allowed-gear-sizes' r.status.should_not == 1 r.stdout.should match "invalid option: Provide a comma delimited .* --allowed-gear-sizes" client.reset.find_domain(domain.name).allowed_gear_sizes.should == all_sizes end end endrhc-1.38.7/features/app_feature.rb0000644000004100000410000000322612756270552017072 0ustar www-datawww-datarequire 'spec_helper' require 'direct_execution_helper' describe "rhc app scenarios" do context "with an existing app" do before(:all) do standard_config @app = has_an_application end let(:app){ @app } it "should clone successfully" do # The following works around an issue with strict host key checking. # create-app --from-app uses SSH to copy the application. However, # this test uses a new application, so without this workaround, create-app # --from-app will be trying to log into the application for the first # time, and so SSH will not recognize the host key and will prompt for # confirmation, causing the test to hang and eventually time out. To work # around the problem, we tell rhc to initiate an SSH connection using # GIT_SSH (which disables strict host key checking), which will cause SSH # to add the host to ~/.ssh/known_hosts, which will allow the subsequent # create-app --from-app command to succeed. rhc 'ssh', '--ssh', ENV['GIT_SSH'], app.name, '--', 'true' app_name = "clone#{random}" r = rhc 'create-app', app_name, '--from-app', app.name r.stdout.should match /Domain:\s+#{app.domain}/ r.stdout.should match /Cartridges:\s+#{app.cartridges.collect{|c| c.name}.join(', ')}/ r.stdout.should match /From app:\s+#{app.name}/ r.stdout.should match /Gear Size:\s+Copied from '#{app.name}'/ r.stdout.should match /Scaling:\s+#{app.scalable? ? 'yes' : 'no'}/ r.stdout.should match /Setting deployment configuration/ r.stdout.should match /Pulling down a snapshot of application '#{app.name}'/ end end end rhc-1.38.7/features/deployments_feature.rb0000644000004100000410000000761112756270552020657 0ustar www-datawww-datarequire 'spec_helper' require 'direct_execution_helper' require 'httpclient' require 'fileutils' DEPLOYMENT_LIST_ITEM = /([0-2]?[0-9]:[0-5][0-9] (AM|PM), deployment [a-f0-9]{8})/ describe "rhc deployment scenarios" do context "with an existing app" do before(:all) do standard_config @app = has_an_application end let(:app){ @app } it "should display deployment list" do r = list_deployments r.stdout.should match DEPLOYMENT_LIST_ITEM end it "should configure the app for a git ref deployment" do r = configure_app_for_manual_git_deployment r.stdout.should match /Deployment:\s+manual/ r.stdout.should match /Keep Deployments:\s+10/ r.stdout.should match /Deployment Type:\s+git/ r.stdout.should match /Deployment Branch:\s+master/ end it "should configure the app for a binary deployment" do r = configure_app_for_manual_binary_deployment r.stdout.should match /Deployment:\s+manual/ r.stdout.should match /Keep Deployments:\s+10/ r.stdout.should match /Deployment Type:\s+binary/ r.stdout.should match /Deployment Branch:\s+master/ end it "should deploy a git ref" do configure_app_for_manual_git_deployment r = deploy_master r.stdout.should match /Deployment of git ref 'master' in progress for application #{app.name}/ r.stdout.should match /Success/ r = list_deployments r.stdout.should match DEPLOYMENT_LIST_ITEM r.stdout.scan(DEPLOYMENT_LIST_ITEM).length.should > 1 end it "should perform a complete deploy workflow" do configure_app_for_manual_git_deployment edit_simple_change 'Bienvenido a' app_page_content.should match /Welcome to/ app_page_content.should_not match /Bienvenido a/ deploy_master app_page_content.should match /Bienvenido a/ app_page_content.should_not match /Welcome to/ deployment_id = find_inactive_deployment deployment_id.should_not be_nil activate deployment_id app_page_content.should match /Welcome to/ app_page_content.should_not match /Bienvenido a/ end private def configure_app_for_manual_git_deployment ensure_command 'configure-app', app.name, '--no-auto-deploy', '--keep-deployments', 10, '--deployment-type', 'git' end def configure_app_for_manual_binary_deployment ensure_command 'configure-app', app.name, '--no-auto-deploy', '--keep-deployments', 10, '--deployment-type', 'binary' end def list_deployments ensure_command 'deployments', app.name end def deploy(ref) ensure_command 'deploy', ref, '-a', app.name end def deploy_master deploy 'master' end def activate(deployment_id) ensure_command 'activate-deployment', deployment_id, '-a', app.name end def snapshot_deployment ensure_command 'save-snapshot', app.name, '--deployment' end def git_clone ensure_command 'git-clone', app.name, '-r', git_directory Dir.exists?(git_directory).should be_true end def edit_simple_change(content) FileUtils.rm_rf git_directory git_clone Dir.chdir git_directory `git config user.email "you@example.com"` `git config user.name "Your Name"` `sed -i "s/Welcome/#{content}/" index.php` `git commit -a -m "Commit from Feature Tests"` `git push origin master` Dir.chdir '../' FileUtils.rm_rf git_directory end def app_page_content HTTPClient.new.get_content(app.app_url) end def git_directory "#{app.name}_feature_tests_repo" end def find_inactive_deployment r = list_deployments r.stdout.match(/deployment ([a-f0-9]{8})/)[1] end def ensure_command(*args) r = rhc *args r.status.should == 0 r end end end rhc-1.38.7/features/keys_feature.rb0000644000004100000410000000226312756270552017265 0ustar www-datawww-datarequire 'spec_helper' require 'direct_execution_helper' describe "rhc sshkey scenarios" do context "with an existing domain" do before(:all) do standard_config @domain = has_a_domain end let(:domain){ @domain } context "with an application" do before{ has_an_application } it "should add and remove kerberos keys on gear" do app = @domain.applications.first keyname = "key#{rand(1000000000000)}" keycontent = "principal#{rand(1000000000000)}" r = rhc 'sshkey', 'add', keyname, '--type', 'krb5-principal', '--content', keycontent r.status.should == 0 r = rhc 'ssh', app.name, '-n', domain.name, '--ssh', ssh_exec_for_env, '--', 'if [ -f .k5login ]; then cat .k5login; fi' r.status.should == 0 r.stdout.should match(Regexp.new("#{keyname}\n#{keycontent}")) r = rhc 'sshkey', 'remove', keyname r.status.should == 0 r = rhc 'ssh', app.name, '-n', domain.name, '--ssh', ssh_exec_for_env, '--', 'if [ -f .k5login ]; then cat .k5login; fi' r.status.should == 0 r.stdout.should_not match(Regexp.new("#{keyname}\n#{keycontent}")) end end end end rhc-1.38.7/conf/0000755000004100000410000000000012756270552013356 5ustar www-datawww-datarhc-1.38.7/conf/express.conf0000644000004100000410000000016012756270552015713 0ustar www-datawww-data# Remote API server libra_server = 'openshift.redhat.com' # Logging debug = 'false' # Timeout #timeout = '10' rhc-1.38.7/spec/0000755000004100000410000000000012756270552013363 5ustar www-datawww-datarhc-1.38.7/spec/wizard_spec_helper.rb0000644000004100000410000002423212756270552017564 0ustar www-datawww-data module WizardStepsHelper def should_greet_user next_stage.should_not be_nil last_output do |s| s.count("\n").should >= 3 s.should match(/OpenShift Client Tools \(RHC\) Setup Wizard/) end end def should_configure_server(server=nil) server = 'openshift.redhat.com' if server.nil? input_line server next_stage.should_not be_nil last_output do |s| s.should match(/If you have your own OpenShift server, you can specify it now/) s.should match(/Just hit enter to use: #{server}/) s.should match(/Enter server address: |#{server}|/) s.should match(/You can add more servers later using 'rhc server'/) end end def should_challenge_for(username, password) input_line username.to_s if username input_line password.to_s if password next_stage.should_not be_nil last_output do |s| s.send(username ? :should : :should_not, match("Login to ")) s.send(password ? :should : :should_not, match(/Password: [\*]{8}$/)) end end def should_write_config test_config_file(false) end def should_overwrite_config test_config_file(true) end def test_config_file(present) File.exists?(current_config_path).should be present next_stage.should_not be_nil last_output.should match("Saving configuration to #{current_config_path}") File.readable?(current_config_path).should be true if present RHC::Vendor::ParseConfig.new(current_config_path)["libra_server"].should == mock_uri RHC::Servers.new.tap do |s| s.list.length.should == 2 s.list.last.login.should == username s.list.last.use_authorization_tokens.should be false end else RHC::Vendor::ParseConfig.new(current_config_path).tap do |cp| cp["default_rhlogin"].should == username cp["libra_server"].should == mock_uri cp["use_authorization_tokens"].should == 'false' end end end def should_create_an_ssh_keypair setup_mock_ssh keys_should_not_exist next_stage.should_not be_nil keys_should_exist last_output.should match('No SSH keys were found. We will generate a pair of keys') end def should_not_create_an_ssh_keypair next_stage.should_not be_nil last_output.should == '' end def should_upload_default_key input_line 'yes' next_stage.should_not be_nil last_output do |s| s.should match('Since you do not have any keys associated') #s.should match(/Fingerprint\: (?:[a-f0-9]{2}\:){15}/) s.should match("Uploading key 'default' ... ") end end def should_skip_uploading_key input_line 'no' next_stage.should_not be_nil last_output.should match('You can upload your public SSH key at a later time using ') end def should_find_matching_server_key next_stage.should_not be_nil last_output.should == "" end def should_find_git setup_mock_has_git(true) next_stage.should_not be_nil last_output.should match(/Checking for git .*found/) end def should_display_windows_info next_stage.should_not be_nil last_output do |s| s.should match('Git for Windows') s.should match('In order to fully interact with OpenShift you will need to install and configure a git client') end end def should_not_find_git setup_mock_has_git(false) next_stage.should_not be_nil last_output do |s| s.should match(/Checking for git .*needs to be installed/) s.should match("Automated installation of client tools is not supported for your platform") end end def should_not_find_problems FakeFS::File.expect_mode(RHC::Config.ssh_priv_key_file_path, '600') next_stage.should_not be_nil last_output do |s| s.should match(/Checking common problems \.+.+?done/) end end def should_check_remote_server FakeFS::File.expect_mode(RHC::Config.ssh_priv_key_file_path, '600') ssh = Object.new ssh.should_receive(:close) Net::SSH.should_receive(:start).once.and_return(ssh) subject.should_receive(:ssh_key_uploaded?).and_return(true) next_stage.should_not be_nil last_output do |s| s.should match(/Checking common problems \.+.+? done/) end end def should_find_ssh_keys next_stage.should_not be_nil last_output do |s| s.should_not match(/Remote server does not have the corresponding SSH key/) end end def should_not_find_ssh_keys next_stage.should_not be_nil last_output do |s| s.should match(/Remote server does not have the corresponding SSH key/) end end def should_create_a_namespace input_line "thisnamespaceistoobigandhastoomanycharacterstobevalid" input_line "invalidnamespace" input_line "testnamespace" next_stage.should_not be_nil last_output do |s| s.should match(/Checking for a domain .*none/) s.should match(/(?:Too long.*?){2}/m) end subject.send(:options).__hash__[:namespace].should == 'testnamespace' end def should_skip_creating_namespace input_line "" next_stage.should_not be_nil last_output do |s| s.should match(/Checking for a domain .*none/) s.should match("You will not be able to create an application without completing this step.") s.should match("You may create a domain later through 'rhc create-domain'") end subject.send(:options).__hash__[:namespace].should be_nil end def should_find_a_namespace(namespace) next_stage.should_not be_nil last_output.should match(/Checking for a domain .*#{namespace}/) subject.send(:options).__hash__[:namespace].should be_nil end def should_list_types_of_apps_to_create next_stage.should_not be_nil last_output do |s| s.should match('rhc create-app mock_standalone_cart-1') s.should match('rhc create-app mock_standalone_cart-2') end end def should_find_apps(*args) next_stage.should_not be_nil last_output do |s| s.should match("found #{args.length}") args.each do |(name, domain_name)| s.should match("#{name} http://#{name}-#{domain_name}.rhcloud.com") end s.should match("You are using ") s.should match("The following gear sizes are available to you") end end def should_be_done next_stage.should be_true last_output.should match("Your client tools are now configured.") end end module WizardHelper def next_stage @stages ||= subject.stages @stage ||= -1 @current_stage = @stages[@stage+=1] subject.send(@current_stage) end def current_config_path subject.send(:config).config_path end def setup_mock_has_git(bool) subject.stub(:"has_git?") { bool } end def current_ssh_dir subject.send(:config).ssh_dir end def setup_mock_ssh(add_ssh_key=false) FileUtils.mkdir_p current_ssh_dir if add_ssh_key setup_mock_ssh_keys end end def keys_should_exist File.exists?(File.join(current_ssh_dir, "id_rsa")).should be true File.exists?(File.join(current_ssh_dir, "id_rsa.pub")).should be true end def keys_should_not_exist File.exists?(File.join(current_ssh_dir, "id_rsa")).should be false File.exists?(File.join(current_ssh_dir, "id_rsa.pub")).should be false end def priv_key < 'default', :type => 'ssh-rsa', :fingerprint => "0f:97:4b:82:87:bb:c6:dc:40:a3:c1:bc:bb:55:1e:fa"), Sshkey.new(:name => 'cb490595', :type => 'ssh-rsa', :fingerprint => "cb:49:05:95:b4:42:1c:95:74:f7:2d:41:0d:f0:37:3b"), Sshkey.new(:name => '96d90241', :type => 'ssh-rsa', :fingerprint => "96:d9:02:41:e1:cb:0d:ce:e5:3b:fc:da:13:65:3e:32"), Sshkey.new(:name => '73ce2cc1', :type => 'ssh-rsa', :fingerprint => "73:ce:2c:c1:01:ea:79:cc:f6:be:86:45:67:96:7f:e3") ] end def setup_mock_ssh_keys(dir=current_ssh_dir) private_key_file = File.join(dir, "id_rsa") public_key_file = File.join(dir, "id_rsa.pub") File.open(private_key_file, 'w') { |f| f.write priv_key } File.open(public_key_file, 'w') { |f| f.write pub_key } end def setup_different_config path = File.join(File.join(home_dir, '.openshift'), 'express.conf') FileUtils.mkdir_p File.dirname(path) File.open(path, "w") do |file| file.puts < user, :password => password} } let(:options){ (o = Commander::Command::Options.new).default(default_options); o } let(:default_options){ {} } let(:client){ double(:supports_sessions? => false) } its(:username){ should be_nil } its(:username?){ should be_false } its(:password){ should be_nil } its(:options){ should_not be_nil } its(:can_authenticate?){ should be_false } its(:openshift_server){ should == 'openshift.redhat.com' } its( :expired_token_message) { should == "Your authorization token has expired. Please sign in now to continue on #{subject.openshift_server}." } its( :get_token_message) { should == "Please sign in to start a new session to #{subject.openshift_server}." } def resolved(hash) hash.each_pair do |k,v| hash[k] = v.call if v.is_a? Proc end hash end context "with user options" do subject{ described_class.new(options) } its(:username){ should be_nil } its(:username?){ should be_false } its(:password){ should be_nil } its(:options){ should equal(options) } context "that include user info" do let(:default_options){ {:rhlogin => user, :password => password} } its(:username){ should == user } its(:username?){ should be_true } its(:password){ should == password } its(:can_authenticate?){ should be_true } end context "that includes server" do let(:default_options){ {:server => 'test.com'} } its(:openshift_server){ should == 'test.com' } it do subject.should_receive(:ask).with("Login to test.com: ").and_return(user) subject.send(:ask_username).should == user end end context "with --noprompt" do let(:default_options){ {:noprompt => true} } its(:ask_username){ should be_false } its(:ask_password){ should be_false } its(:username?){ should be_false } it("should not retry") do subject.should_not_receive(:ask_username) subject.retry_auth?(double(:status => 401), client).should be_false end end end context "when initialized with a hash" do subject{ described_class.new({:rhlogin => user, :password => password}) } its(:username){ should == user } its(:password){ should == password } end describe "#ask_username" do before{ subject.should_receive(:openshift_server).and_return('test.com') } before{ subject.should_receive(:ask).with("Login to test.com: ").and_return(user) } it do subject.send(:ask_username).should == user subject.send(:username).should == user end context "with a different user" do subject{ described_class.new('other', nil) } it do subject.send(:ask_username).should == user subject.send(:username).should == user end end end describe "#ask_password" do before{ subject.should_receive(:ask).with("Password: ").and_return(password) } it do subject.send(:ask_password).should == password subject.send(:password).should == password end context "with a different password" do subject{ described_class.new(user, 'other') } it do subject.send(:ask_password).should == password subject.send(:password).should == password end end end describe "#to_request" do let(:request){ {} } context "when the request is lazy" do let(:request){ {:lazy_auth => true} } before{ subject.should_receive(:ask_username).never } before{ subject.should_receive(:ask_password).never } it { subject.to_request(request).should == request } end context "when password and user are provided" do subject{ described_class.new(user, password) } it { subject.to_request(request).should equal(request) } it { resolved(subject.to_request(request)).should == auth_hash } context "when the request is lazy" do let(:request){ {:lazy_auth => true} } it { subject.to_request(request).should == auth_hash.merge(request) } end end context "when password is not provided" do subject{ described_class.new(user, nil) } its(:password){ should be_nil } it "should ask for the password" do subject.should_receive(:ask_password).and_return(password) resolved(subject.to_request(request)).should == auth_hash end it "should remember the password" do subject.should_receive(:ask_password).and_return(password) subject.to_request(request) resolved(subject.to_request(request)).should == auth_hash end context "when the request is lazy" do let(:request){ {:lazy_auth => true} } before{ subject.should_receive(:ask_password).never } it { subject.to_request(request).should == auth_hash.merge(request) } end end context "when user is not provided" do subject{ described_class.new(nil, password) } its(:username){ should be_nil } it "should ask for the username" do subject.should_receive(:ask_username).and_return(user) resolved(subject.to_request(request)).should == auth_hash end it "should remember the username" do subject.should_receive(:ask_username).and_return(user) subject.to_request(request) resolved(subject.to_request(request)).should == auth_hash end context "when the request is lazy" do let(:request){ {:lazy_auth => true} } before{ subject.should_receive(:ask_username).never } it { subject.to_request(request).should == auth_hash.merge(request) } end end end describe "#retry_auth?" do context "when the response succeeds" do let(:response){ double(:cookies => {}, :status => 200) } it{ subject.retry_auth?(response, client).should be_false } end context "when the response succeeds with a cookie" do let(:response){ double(:cookies => [double(:name => 'rh_sso', :value => '1')], :status => 200) } it{ subject.retry_auth?(response, client).should be_false } end context "when the response requires authentication" do let(:response){ double(:status => 401) } context "with no user and no password" do subject{ described_class.new(nil, nil) } it("should ask for user and password") do subject.should_receive(:ask_username).and_return(user) subject.should_receive(:ask_password).and_return(password) subject.retry_auth?(response, client).should be_true end end context "with user and no password" do subject{ described_class.new(user, nil) } it("should ask for password only") do subject.should_receive(:ask_password).and_return(password) subject.retry_auth?(response, client).should be_true end it("should ask for password twice") do subject.should_receive(:ask_password).twice.and_return(password) subject.retry_auth?(response, client).should be_true subject.retry_auth?(response, client).should be_true end end context "with user and password" do subject{ described_class.new(user, password) } it("should not prompt for reauthentication") do subject.should_not_receive(:ask_password) subject.should_receive(:error).with("Username or password is not correct") subject.retry_auth?(response, client).should be_false end end end end end describe RHC::Auth::X509 do subject{ described_class.new(options) } let(:default_options){ {} } let(:options){ (o = Commander::Command::Options.new).default(default_options); o } let(:a_cert){ OpenSSL::X509::Certificate.new } let(:a_key){ OpenSSL::PKey::RSA.new } its(:options){ should_not be_nil } its(:can_authenticate?){ should be_true } its(:openshift_server){ should == 'openshift.redhat.com' } its(:expired_token_message) { should == "Your authorization token has expired. Fetching a new token from #{subject.openshift_server}."} its(:get_token_message) { should == "Fetching a new token from #{subject.openshift_server}." } describe "#retry_auth?" do context "should return true if the response was 401" do let(:response){ double(:status => 401) } let(:client){ double } it { subject.retry_auth?(response, client).should == true } end context "should return false if the response was 403" do let(:response){ double } let(:client){ double } let(:response){ double(:status => 403) } it { subject.retry_auth?(response, client).should == false } end end describe "#token_store_user_key" do let(:cert) do file = Tempfile.new('cert') cert = OpenSSL::X509::Certificate.new cert.version = 2 cert.serial = 1 cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA" cert.issuer = cert.subject cert.not_before = Time.now cert.not_after = cert.not_before + 2 * 365 * 24 * 60 * 60 file.write(cert) file.flush file end let(:options){ Commander::Command::Options.new(:ssl_client_cert_file => cert.path) } context "when the client cert is set", :focus => true do it("should return the fingerprint") do subject.token_store_user_key.length.should == 40 end end end describe "#to_request" do let(:request){ {} } let(:auth_hash){ {:client_cert => a_cert, :client_key => a_key} } context "when a certificate exists" do it "should use x509 auth" do OpenSSL::X509::Certificate.should_receive(:new).exactly(1).times.and_return(a_cert) OpenSSL::PKey::RSA.should_receive(:new).exactly(1).times.and_return(a_key) subject.to_request(request).should == auth_hash end end context "when a certificate can't be loaded" do it "should send a debug message and raise an error" do options.should_receive(:ssl_client_cert_file).and_return("a bogus path") subject.should_receive(:debug) expect do subject.to_request(request) end.to raise_error end end context "when a key can't be loaded" do it "should send a debug message and raise an error" do options.should_receive(:ssl_client_key_file).and_return("a bogus path") subject.should_receive(:debug) expect do subject.to_request(request) end.to raise_error end end end end describe RHC::Auth::Token do subject{ described_class.new(options) } let(:token){ 'a_token' } let(:options){ (o = Commander::Command::Options.new).default(default_options); o } let(:default_options){ {} } let(:client){ double(:supports_sessions? => false) } let(:auth){ nil } let(:store){ nil } its(:username){ should be_nil } its(:options){ should_not be_nil } its(:can_authenticate?){ should be_false } its(:openshift_server){ should == 'openshift.redhat.com' } context "with user options" do its(:username){ should be_nil } its(:options){ should equal(options) } context "that include token" do let(:default_options){ {:token => token} } its(:can_authenticate?){ should be_true } end context "that includes server" do let(:default_options){ {:server => 'test.com'} } its(:openshift_server){ should == 'test.com' } end context "with --noprompt" do let(:default_options){ {:noprompt => true} } its(:username){ should be_nil } it("should not retry") do end end end context "when initialized with a hash" do subject{ described_class.new({:token => token}) } its(:token){ should == token } end context "when initialized with a string" do subject{ described_class.new(token) } its(:token){ should == token } end context "when initialized with an auth object" do subject{ described_class.new(nil, auth) } let(:auth){ double(:username => 'foo') } its(:username){ should == 'foo' } end context "when initialized with a store" do subject{ described_class.new(nil, auth, store) } let(:auth){ double(:token_store_user_key => nil) } let(:store){ double } before{ store.should_receive(:get).with(nil, 'openshift.redhat.com').and_return(token) } it("should read the token for the user") do subject.send(:token).should == token end end describe "#save" do subject{ described_class.new(nil, auth, store) } context "when store is set" do let(:auth){ double(:token_store_user_key => 'foo') } let(:store){ double(:get => nil) } it("should call put on store") do subject.should_receive(:openshift_server).and_return('bar') store.should_receive(:put).with('foo', 'bar', token) subject.save(token) end end context "when store is nil" do it("should skip calling store"){ subject.save(token) } end after{ subject.instance_variable_get(:@token).should == token } end describe "#to_request" do let(:request){ {} } subject{ described_class.new(token, auth) } context "when token is provided" do it("should pass bearer token to the server"){ subject.to_request(request).should == {:headers => {'authorization' => "Bearer #{token}"}} } context "when the request is lazy" do let(:request){ {:lazy_auth => true} } it("should pass bearer token to the server"){ subject.to_request(request).should == {:lazy_auth => true, :headers => {'authorization' => "Bearer #{token}"}} } end end context "when a token is not provided when secondary auth is provided" do let(:auth) { double(:can_authenticate? => true, :get_token_message => '') } let(:default_options){ {:use_authorization_tokens => true} } subject{ described_class.new(default_options, auth) } context "without session support" do let(:client){ double('client', :supports_sessions? => false) } it("should delegate to secondary auth") do auth.should_receive(:to_request).with(request, client).and_return(request) subject.should_receive(:token_rejected).never subject.to_request(request, client).should == {} end end context "with session support" do let(:client){ double('client', :supports_sessions? => true) } before do client.should_receive(:new_session).with(:auth => auth).and_return(auth_token) end context "when a token request fails" do let(:auth_token){ nil } it("should delegate to secondary auth") do auth.should_receive(:retry_auth?).once.and_return(true) auth.should_receive(:to_request).once.and_return(request) subject.to_request(request, client).should == {} end end context "when a token request succeeds" do let(:auth_token){ double('auth_token', :token => token) } it("should should use the newly created token") do auth.should_receive(:to_request).never subject.to_request(request, client).should == {:headers => {'authorization' => "Bearer #{token}"}} end end end end context "when a parent auth class is passed" do subject{ described_class.new(nil, auth) } let(:auth){ double } it("should invoke the parent") do auth.should_receive(:to_request).with(request, nil).and_return(request) subject.to_request(request).should == request end end end describe "#retry_auth?" do subject{ described_class.new(token, auth) } context "when the response succeeds" do let(:response){ double(:cookies => {}, :status => 200) } it{ subject.retry_auth?(response, client).should be_false } end context "when the response requires authentication" do let(:response){ double(:status => 401) } context "with no token" do subject{ described_class.new(nil, nil) } it("should return false"){ subject.retry_auth?(response, client).should be_false } end context "when a nested auth object can't authenticate" do let(:auth){ double(:can_authenticate? => false) } it("should raise an error"){ expect{ subject.retry_auth?(response, client) }.to raise_error(RHC::Rest::TokenExpiredOrInvalid) } end context "with a nested auth object" do let(:auth){ double('nested_auth', :can_authenticate? => true) } subject{ described_class.new(options, auth) } it("should not use token auth") do auth.should_receive(:retry_auth?).with(response, client).and_return true subject.retry_auth?(response, client).should be_true end context "when noprompt is requested" do let(:default_options){ {:token => token, :noprompt => true} } it("should raise an error"){ expect{ subject.retry_auth?(response, client) }.to raise_error(RHC::Rest::TokenExpiredOrInvalid) } end context "when authorization tokens are enabled locally" do let(:default_options){ {:use_authorization_tokens => true} } context "without session support" do let(:default_options){ {:use_authorization_tokens => true, :token => 'foo'} } let(:client){ double('client', :supports_sessions? => false) } it("should invoke raise an error on retry because sessions are not supported") do expect{ subject.retry_auth?(response, client) }.to raise_error RHC::Rest::AuthorizationsNotSupported end end context "we expect a warning and a call to client" do let(:auth_token){ nil } let(:client){ double('client', :supports_sessions? => true) } before{ client.should_receive(:new_session).with(:auth => auth).and_return(auth_token) } it("should print a message") do subject.should_receive(:info).with("get token message") auth.should_receive(:retry_auth?).with(response, client).and_return true auth.should_receive(:get_token_message).and_return("get token message") subject.retry_auth?(response, client).should be_true end context "with a token" do let(:default_options){ {:use_authorization_tokens => true, :token => 'foo'} } it("should invoke raise an error on retry because sessions are not supported") do subject.should_receive(:warn).with("expired token message") auth.should_receive(:retry_auth?).with(response, client).and_return true auth.should_receive(:expired_token_message).and_return("expired token message") subject.retry_auth?(response, client).should be_true #expect{ subject.retry_auth?(response, client) }.to raise_error RHC::Rest::AuthorizationsNotSupported end end context "when the token request fails" do before do subject.should_receive(:info).with("get token message") auth.should_receive(:get_token_message).and_return("get token message") end it("should invoke retry on the parent") do auth.should_receive(:retry_auth?).with(response, client).and_return false subject.retry_auth?(response, client).should be_false end end context "when the token request succeeds" do let(:auth_token){ double('auth_token', :token => 'bar') } before do subject.should_receive(:info).with("get token message") auth.should_receive(:get_token_message).and_return("get token message") end it("should save the token and return true") do subject.should_receive(:save).with(auth_token.token).and_return true subject.retry_auth?(response, client).should be_true end end end end end end end end describe RHC::Auth::TokenStore do subject{ described_class.new(dir) } let(:dir){ Dir.mktmpdir } context "when a key is stored" do before{ subject.put('foo', 'bar', 'token') } it("can be retrieved"){ subject.get('foo', 'bar').should == 'token' } end it("should handle missing files"){ subject.get('foo', 'other').should be_nil } it("should put a file on disk"){ expect{ subject.put('test', 'server', 'value') }.to change{ Dir.entries(dir).length }.by(1) } describe "#clear" do before{ subject.put('test', 'server2', 'value2') } it("should return true"){ subject.clear.should be_true } it("should empty the directory"){ expect{ subject.clear }.to change{ Dir.entries(dir).length }.by_at_least(-1) } after{ Dir.entries(dir).length.should == 2 } end end rhc-1.38.7/spec/rhc/rest_spec.rb0000644000004100000410000007751512756270552016472 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/rest' module MockRestResponse attr_accessor :code, :read end describe RHC::Rest::Cartridge do context 'with a name' do before{ subject.name = 'foo' } its(:display_name){ should == 'foo' } context 'when display name is present' do before{ subject.display_name = 'bar' } its(:display_name){ should == 'bar' } end end end describe RHC::Rest::Domain do subject{ RHC::Rest::Client.new(:server => mock_uri) } let(:user_auth){ nil } let(:domain) { subject.domains.first } context "against a 1.2 server" do before{ stub_api_v12; stub_one_domain('bar') } before do stub_api_request(:post, 'broker/rest/domains/bar/applications', false). with(:body => {:name => 'foo', :cartridge => 'bar'}.to_json). to_return(:status => 201, :body => {:type => 'application', :data => {:id => '1'}}.to_json) end it{ domain.add_application('foo', :cartridges => ['bar']).should be_true } it{ expect{ domain.add_application('foo', :cartridges => ['bar', 'other']) }.to raise_error(RHC::Rest::MultipleCartridgeCreationNotSupported) } it{ expect{ domain.add_application('foo', :initial_git_url => 'a_url') }.to raise_error(RHC::Rest::InitialGitUrlNotSupported) } it{ expect{ domain.add_application('foo', :cartridges => [{:url => 'a_url'}]) }.to raise_error(RHC::Rest::DownloadingCartridgesNotSupported) } it{ domain.add_application('foo', :cartridges => 'bar').should be_true } it{ domain.add_application('foo', :cartridge => 'bar').should be_true } it{ domain.add_application('foo', :cartridge => ['bar']).should be_true } end context "against a server that supports initial git urls and downloaded carts" do let(:cartridges){ ['bar'] } before{ stub_api; stub_one_domain('bar', [{:name => 'initial_git_url'},{:name => 'cartridges[][url]'}]) } before do stub_api_request(:post, 'broker/rest/domains/bar/applications', false). with(:body => {:name => 'foo', :cartridges => cartridges}.to_json). to_return(:status => 201, :body => {:type => 'application', :data => {:id => '1'}}.to_json) end it{ domain.add_application('foo', :cartridges => ['bar']).should be_true } it{ domain.add_application('foo', :cartridges => 'bar').should be_true } it{ domain.add_application('foo', :cartridge => 'bar').should be_true } it{ domain.add_application('foo', :cartridge => ['bar']).should be_true } context "with multiple cartridges" do let(:cartridges){ ['bar'] } it{ domain.add_application('foo', :cartridges => cartridges).should be_true } it{ domain.add_application('foo', :cartridge => cartridges).should be_true } end context "with a url" do before do stub_api_request(:post, 'broker/rest/domains/bar/applications', false). with(:body => {:name => 'foo', :initial_git_url => 'a_url', :cartridges => []}.to_json). to_return(:status => 201, :body => {:type => 'application', :data => {:id => '1'}}.to_json) end it{ domain.add_application('foo', :initial_git_url => 'a_url').should be_true } end context "with a cartridge url" do before do stub_api_request(:post, 'broker/rest/domains/bar/applications', false). with(:body => {:name => 'foo', :cartridges => [{:url => 'a_url'}]}.to_json). to_return(:status => 201, :body => {:type => 'application', :data => {:id => '1'}}.to_json) end it{ domain.add_application('foo', :cartridges => [{:url => 'a_url'}]).should be_true } it{ domain.add_application('foo', :cartridge => RHC::Rest::Cartridge.for_url('a_url')).should be_true } it{ domain.add_application('foo', :cartridge => [{'url' => 'a_url'}]).should be_true } end end end module RHC describe Rest do subject{ RHC::Rest::Client.new } describe "#default_verify_callback" do def invoked_with(is_ok, ctx) subject.send(:default_verify_callback).call(is_ok, ctx) end it{ invoked_with(true, nil).should be_true } it{ expect{ invoked_with(false, nil) }.to raise_error(NoMethodError) } context "with a self signed cert" do it{ invoked_with(false, double(:current_cert => double(:issuer => '1', :subject => double(:cmp => 0)))).should be_false } after{ subject.send(:self_signed?).should be_true } end context "with an intermediate signed cert" do it{ invoked_with(false, double(:current_cert => double(:issuer => '2', :subject => double(:cmp => 1)), :error => 1, :error_string => 'a')).should be_false } after{ subject.send(:self_signed?).should be_false } end end # parse_response function describe "#parse_response" do context "with no response type" do let(:object) {{ :links => { :foo => 'bar' } }} it "deserializes to the encapsulated data" do json_response = { :data => object }.to_json subject.send(:parse_response, json_response).should have_same_attributes_as(object) end end context "with an application" do let(:object) {{ :domain_id => 'test_domain', :name => 'test_app', :creation_time => '0000-00-00 00:00:00 -0000', :uuid => 'test_app_1234', :aliases => ['app_alias_1', 'app_alias_2'], :server_identity => 'test_server', :links => { :foo => 'bar' } }} it "deserializes to an application" do json_response = { :type => 'application', :data => object, :messages => [{'text' => 'test message'}]}.to_json app_obj = RHC::Rest::Application.new(object) subject.send(:parse_response, json_response).should have_same_attributes_as(app_obj) end end context "with two applications" do let(:object) {[{ :domain_id => 'test_domain', :name => 'test_app', :creation_time => '0000-00-00 00:00:00 -0000', :uuid => 'test_app_1234', :aliases => ['app_alias_1', 'app_alias_2'], :server_identity => 'test_server', :links => { :foo => 'bar' } }, { :domain_id => 'test_domain_2', :name => 'test_app_2', :creation_time => '0000-00-00 00:00:00 -0000', :uuid => 'test_app_2_1234', :aliases => ['app_alias_3', 'app_alias_4'], :server_identity => 'test_server_2', :links => { :foo => 'bar' } }] } it "deserializes to a list of applications" do json_response = { :type => 'applications', :data => object }.to_json app_obj_1 = RHC::Rest::Application.new(object[0]) app_obj_2 = RHC::Rest::Application.new(object[1]) subject.send(:parse_response, json_response).length.should equal(2) subject.send(:parse_response, json_response)[0].should have_same_attributes_as(app_obj_1) subject.send(:parse_response, json_response)[1].should have_same_attributes_as(app_obj_2) end end context "with a cartridge" do let(:object) {{ :name => 'test_cartridge', :type => 'test_cartridge_type', :links => { :foo => 'bar' } }} it "deserializes to a cartridge" do json_response = { :type => 'cartridge', :data => object }.to_json cart_obj = RHC::Rest::Cartridge.new(object) subject.send(:parse_response, json_response).should have_same_attributes_as(cart_obj) end end context "with two cartridges" do let(:object) {[{ :name => 'test_cartridge', :type => 'test_cartridge_type', :links => { :foo => 'bar' } }, { :name => 'test_cartridge_2', :type => 'test_cartridge_type_2', :links => { :foo => 'bar' } } ]} it "deserializes to a list of cartridges" do json_response = { :type => 'cartridges', :data => object }.to_json cart_obj_1 = RHC::Rest::Cartridge.new(object[0]) cart_obj_2 = RHC::Rest::Cartridge.new(object[1]) subject.send(:parse_response, json_response).length.should equal(2) subject.send(:parse_response, json_response)[0].should have_same_attributes_as(cart_obj_1) subject.send(:parse_response, json_response)[1].should have_same_attributes_as(cart_obj_2) end end context "with a domain" do let(:object) {{ :id => 'test_domain', :links => { :foo => 'bar' } }} it "deserializes to a domain" do json_response = { :type => 'domain', :data => object }.to_json dom_obj = RHC::Rest::Domain.new(object) subject.send(:parse_response, json_response).should have_same_attributes_as(dom_obj) end end context "with two domains" do let(:object) {[{ :id => 'test_domain', :links => { :foo => 'bar' } }, { :id => 'test_domain_2', :links => { :foo => 'bar' } } ]} it "deserializes to a list of domains" do json_response = { :type => 'domains', :data => object }.to_json dom_obj_1 = RHC::Rest::Domain.new(object[0]) dom_obj_2 = RHC::Rest::Domain.new(object[1]) subject.send(:parse_response, json_response).length.should equal(2) subject.send(:parse_response, json_response)[0].should have_same_attributes_as(dom_obj_1) subject.send(:parse_response, json_response)[1].should have_same_attributes_as(dom_obj_2) end end context "with a key" do let(:object) {{ :name => 'test_key', :type => 'test_key_type', :content => 'test_key_content', :links => { :foo => 'bar' } }} it "deserializes to a key" do json_response = { :type => 'key', :data => object }.to_json key_obj = RHC::Rest::Key.new(object) subject.send(:parse_response, json_response).should have_same_attributes_as(key_obj) end end context "with two keys" do let(:object) {[{ :name => 'test_key', :type => 'test_key_type', :content => 'test_key_content', :links => { :foo => 'bar' } }, { :name => 'test_key_2', :type => 'test_key_type_2', :content => 'test_key_content_2', :links => { :foo => 'bar' } } ]} it "deserializes to a list of keys" do json_response = { :type => 'keys', :data => object }.to_json key_obj_1 = RHC::Rest::Key.new(object[0]) key_obj_2 = RHC::Rest::Key.new(object[1]) subject.send(:parse_response, json_response).length.should equal(2) subject.send(:parse_response, json_response)[0].should have_same_attributes_as(key_obj_1) subject.send(:parse_response, json_response)[1].should have_same_attributes_as(key_obj_2) end end context "with a user" do let(:object) {{ :login => 'test_user', :links => { :foo => 'bar' } }} it "deserializes to a user" do json_response = { :type => 'user', :data => object }.to_json user_obj = RHC::Rest::User.new(object) subject.send(:parse_response, json_response).should have_same_attributes_as(user_obj) end end context "with result messages" do let(:object) do { :login => 'test_user', :links => { :foo => 'bar' } } end let(:messages) do [ {:field => nil, :severity => 'info', :text => 'Nil field'}, {:field => 'result', :severity => 'info', :text => 'Result field'}, # < 1.5 API {:field => 'base', :severity => 'result', :text => 'Result severity'}, # >= 1.5 API {:field => 'base', :severity => 'info', :text => 'Non-result message' }, {:field => 'result', :severity => 'debug', :text => 'Debug message' }, {:field => 'base', :severity => 'warning', :text => 'Warning message' }, ] end let(:response) do { :type => 'user', :data => object, :messages => messages }.to_json end it "copies all non-warning and non-info messages to the object" do subject.stub(:debug?).and_return(false) subject.send(:parse_response, response).messages.should == ['Result field', 'Result severity'] end it "includes debug and info when debug true" do subject.stub(:debug?).and_return(true) subject.send(:parse_response, response).messages.should == ['Nil field', 'Result field', 'Result severity', 'Non-result message', 'Debug message'] end end end describe "#new_request" do it{ subject.send(:new_request, :api_version => 2.0).last[4]['accept'].should == 'application/json;version=2.0' } it{ subject.send(:new_request, :headers => {:accept => :xml}, :api_version => 2.0).last[4]['accept'].should == 'application/xml;version=2.0' } context "with the default api version" do before{ subject.should_receive(:current_api_version).and_return('1.0') } it{ subject.send(:new_request, {}).last[4]['accept'].should == 'application/json;version=1.0' } end end # request function describe "#request" do let(:response){ lambda { subject.request(request) } } let(:request){ {:url => mock_href, :method => method, :headers => {:accept => :json} } } let(:method){ :get } if HTTPClient::SSLConfig.method_defined? :ssl_version context "when an invalid ssl version is passed, OpenSSL should reject us" do let(:request){ {:url => "https://openshift.redhat.com", :method => :get, :ssl_version => :SSLv3_server} } before{ WebMock.allow_net_connect! } it("fails to call openssl"){ response.should raise_error(RHC::Rest::SSLConnectionFailed, /called a function you should not call/) } after{ WebMock.disable_net_connect! } end end context "with a successful request" do let(:object) {{ :type => 'domain', :data => { :id => 'test_domain', :links => { :foo => 'bar' } }}} before do return_data = { :body => object.to_json, :status => 200, :headers => { 'Set-Cookie' => "rh_sso=test_ssh_cookie" } } stub_request(:get, mock_href).with{ |req| req.headers['Accept'].should == 'application/json;version=1.0' }.to_return(return_data) end it "sends the response to be deserialized" do dom_obj = RHC::Rest::Domain.new(object) subject.request(request.merge(:payload => {}, :api_version => '1.0', :timeout => 300)).should have_same_attributes_as(dom_obj) end end context "with a nil response" do before do return_data = { :body => nil, :status => 200, :headers => { 'Set-Cookie' => "rh_sso=test_ssh_cookie" } } stub_request(:get, mock_href).to_return(return_data) end it "throws an error" do response.should raise_error(RHC::Rest::ConnectionException, 'An unexpected error occurred: unexpected nil') end end context "with a 204 (No Content) response" do before do return_data = { :body => nil, :status => 204, :headers => { 'Set-Cookie' => "rh_sso=test_ssh_cookie" } } stub_request(:get, mock_href).to_return(return_data) end it "quietly exits" do response.call.should equal(nil) end end context "with a 502 (Bad Gateway) error" do before{ stub_request(method, mock_href).to_return(:status => 502) } context "on a GET request" do it("repeats the call"){ response.should raise_error(RHC::Rest::ConnectionException, /communicating with the server.*temporary/i) } after{ WebMock.should have_requested(method, mock_href).twice } end context "on a POST request" do let(:method){ :post } it("does not repeat the call"){ response.should raise_error(RHC::Rest::ConnectionException, /communicating with the server.*temporary/i) } after{ WebMock.should have_requested(method, mock_href).once } end end context "with a GET request" do it "serializes payload as query parameters" do stub_request(:get, mock_href).with(:query => {:test => '1', :bar => '2'}).to_return(:status => 204) subject.request(request.merge(:payload => {:test => '1', :bar => '2'})).should be_nil end end context "with a POST request" do let(:method){ :post } it "serializes payload as urlencoded body parameters" do stub_request(method, mock_href). with(:headers => {:accept => 'application/json', :content_type => 'application/json'}, :body => {:test => '1', :bar => 2}.to_json). to_return(:status => 204) subject.request(request.merge(:payload => {:test => '1', :bar => 2})).should be_nil end end context "with a request timeout" do before{ stub_request(:get, mock_href).to_timeout } it{ response.should raise_error(RHC::Rest::TimeoutException, /Connection to server timed out. It is possible/) } end context "with a receive timeout" do before{ stub_request(:get, mock_href).to_raise(HTTPClient::ReceiveTimeoutError) } it{ response.should raise_error{ |e| e.on_receive?.should be_true } } end context "with a send timeout" do before{ stub_request(:get, mock_href).to_raise(HTTPClient::SendTimeoutError) } it{ response.should raise_error{ |e| e.on_send?.should be_true } } end context "with a connect timeout" do before{ stub_request(:get, mock_href).to_raise(HTTPClient::ConnectTimeoutError) } it{ response.should raise_error{ |e| e.on_connect?.should be_true } } end context "with a reset server connection" do before{ stub_request(:get, mock_href).to_raise(Errno::ECONNRESET.new('Lost Server Connection')) } it{ response.should raise_error(RHC::Rest::ConnectionException, /The server has closed the connection unexpectedly \(Connection reset by peer - Lost Server Connection\)/) } end context "with a broken server connection" do before{ stub_request(:get, mock_href).to_raise(EOFError.new('Lost Server Connection')) } it{ response.should raise_error(RHC::Rest::ConnectionException, 'Connection to server got interrupted: Lost Server Connection') } end #FIXME: the type of this exception should be a subclass of CertificateValidationFailed context "with a potentially missing cert store" do before{ stub_request(:get, mock_href).to_raise(OpenSSL::SSL::SSLError.new('unable to get local issuer certificate')) } it{ response.should raise_error(RHC::Rest::SSLConnectionFailed, /You may need to specify your system CA certificate file/) } end context "with a self-signed SSL certificate" do before do subject.should_receive(:self_signed?).and_return(true) stub_request(:get, mock_href).to_raise(OpenSSL::SSL::SSLError.new('Unverified SSL Certificate')) end it{ response.should raise_error(RHC::Rest::CertificateVerificationFailed, /The server is using a self-signed certificate/) } end context "with an unverified SSL certificate" do before{ stub_request(:get, mock_href).to_raise(OpenSSL::SSL::SSLError.new('self signed certificate')) } it{ response.should raise_error(RHC::Rest::CertificateVerificationFailed, /The server is using a self-signed certificate/) } end context "with an failed SSL certificate verification" do before{ stub_request(:get, mock_href).to_raise(OpenSSL::SSL::SSLError.new('certificate verify failed')) } it{ response.should raise_error(RHC::Rest::CertificateVerificationFailed, /The server's certificate could not be verified.*test\.domain\.com/) } end context "with a socket error" do before{ stub_request(:get, mock_href).to_raise(SocketError) } it{ response.should raise_error(RHC::Rest::ConnectionException, /unable to connect to the server/i) } end context "with an SSL connection error" do before{ stub_request(:get, mock_href).to_raise(OpenSSL::SSL::SSLError) } it{ response.should raise_error(RHC::Rest::SSLConnectionFailed, /a secure connection could not be established/i) } end context "with an SSL certificate error" do before{ stub_request(:get, mock_href).to_raise(OpenSSL::SSL::SSLError.new('certificate verify failed')) } it{ response.should raise_error(RHC::Rest::CertificateVerificationFailed, /the server's certificate could not be verified/i) } end context "with an SSL version exception" do before{ stub_request(:get, mock_href).to_raise(OpenSSL::SSL::SSLError.new('SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A')) } it{ response.should raise_error(RHC::Rest::SSLVersionRejected, /connection attempt with an older ssl protocol/i) } end context "with a generic exception error" do before{ stub_request(:get, mock_href).to_raise(Exception.new('Generic Error')) } it{ response.should raise_error(RHC::Rest::ConnectionException, "An unexpected error occurred: Generic Error") } end context "with an unauthorized request" do before do return_data = { :body => nil, :status => 401, :headers => { 'Set-Cookie' => "rh_sso=test_ssh_cookie" } } stub_request(:get, mock_href).to_return(return_data) end it("raises not authenticated"){ response.should raise_error(RHC::Rest::UnAuthorizedException, 'Not authenticated') } context "when auth will retry forever" do let(:auth){ double('auth', :to_request => nil) } before{ subject.stub(:auth).and_return(auth); auth.should_receive(:retry_auth?).exactly(4).times.and_return(true) } it("raises not authenticated"){ response.should raise_error(RHC::Rest::UnAuthorizedException, 'Not authenticated') } after{ WebMock.should have_requested(:get, mock_href).times(RHC::Rest::Client::MAX_RETRIES) } end end end # handle_error! function describe "#handle_error!" do let(:json){ nil } let(:body){ "Something failed" } let(:code){ nil } let(:client){ HTTPClient.new(:proxy => proxy) } let(:url){ "http://fake.url" } let(:proxy){ nil } def response double(:status => code, :content => json ? RHC::Json.encode(json) : body) end let(:method) { lambda{ subject.send(:handle_error!, response, url, client) } } context "with a 400 response" do let(:code){ 400 } it{ method.should raise_error(RHC::Rest::ServerErrorException) } context "with a formatted JSON response" do let(:json){ {:messages => [{ :severity => 'error', :text => 'mock error message' }] } } it "raises a client error" do method.should raise_error(RHC::Rest::ClientErrorException, 'mock error message') end end end context "with a 401 response" do let(:code){ 401 } let(:json){ {} } it "raises an 'unauthorized exception' error" do method.should raise_error(RHC::Rest::UnAuthorizedException, 'Not authenticated') end end context "with a 403 response" do let(:code){ 403 } it "raises a request denied error" do method.should raise_error(RHC::Rest::RequestDeniedException) end context "with a formatted JSON response" do let(:json){ { :messages => [{ :severity => 'error', :text => 'mock error message' }] } } it "raises a 'request denied' error" do method.should raise_error(RHC::Rest::RequestDeniedException, 'mock error message') end end end context "with a 404 response" do let(:code){ 404 } it "raises a Not Found error" do method.should raise_error(RHC::Rest::ResourceNotFoundException) end context "with a formatted JSON response" do let(:json){ { :messages => [{ :severity => 'error', :text => 'mock error message' }] } } it "raises a 'resource not found' error" do method.should raise_error(RHC::Rest::ResourceNotFoundException, 'mock error message') end end end context "with a 409 response" do let(:code){ 409 } it "raises a generic server error" do method.should raise_error(RHC::Rest::ServerErrorException) end context "with a formatted JSON response" do let(:json){ { :messages => [{ :severity => 'error', :text => 'mock error message' }] } } it "raises a validation error" do method.should raise_error(RHC::Rest::ValidationException, 'mock error message') end end context "with multiple JSON messages" do let(:json){ { :messages => [{ :field => 'error', :text => 'mock error message 1' }, { :field => 'error', :text => 'mock error message 2' }] } } it "raises a validation error with concatenated messages" do method.should raise_error(RHC::Rest::ValidationException, "The operation did not complete successfully, but the server returned additional information:\n* mock error message 1\n* mock error message 2") end end context "with multiple errors" do let(:json){ { :messages => [ { :severity => 'error', :field => 'error', :text => 'mock 1' }, { :severity => 'error', :text => 'mock 2' }, ] } } it "raises a validation error with concatenated messages" do method.should raise_error(RHC::Rest::ValidationException, "The server reported multiple errors:\n* mock 1\n* mock 2") end end context "with multiple messages and one error" do let(:json){ { :messages => [ { :field => 'error', :text => 'mock 1' }, { :text => 'mock 2' }, { :severity => 'error', :text => 'mock 3' }, ] } } it "raises a validation error with concatenated messages" do method.should raise_error(RHC::Rest::ValidationException, "mock 3") end end context "with an empty JSON response" do let(:json){ {} } it "raises a validation error" do method.should raise_error(RHC::Rest::ServerErrorException) end end end context "with a 422 response" do let(:code){ 422 } it "raises a generic server error" do method.should raise_error(RHC::Rest::ServerErrorException) end context "with a single JSON message" do let(:json){ { :messages => [{ :severity => 'error', :text => 'mock error message' }] } } it "raises a validation error" do method.should raise_error(RHC::Rest::ValidationException, 'mock error message') end end context "with an empty JSON response" do let(:json){ {} } it "raises a validation error" do method.should raise_error(RHC::Rest::ServerErrorException) end end context "with multiple JSON messages" do let(:json){ { :messages => [{ :field => 'error', :text => 'mock error message 1' }, { :field => 'error', :text => 'mock error message 2' }] } } it "raises a validation error with concatenated messages" do method.should raise_error(RHC::Rest::ValidationException, "The operation did not complete successfully, but the server returned additional information:\n* mock error message 1\n* mock error message 2") end end context "with multiple errors" do let(:json){ { :messages => [ { :severity => 'error', :field => 'error', :text => 'mock 1' }, { :severity => 'error', :text => 'mock 2' }, ] } } it "raises a validation error with concatenated messages" do method.should raise_error(RHC::Rest::ValidationException, "The server reported multiple errors:\n* mock 1\n* mock 2") end end context "with multiple messages and one error" do let(:json){ { :messages => [ { :field => 'error', :text => 'mock 1' }, { :text => 'mock 2' }, { :severity => 'error', :text => 'mock 3' }, ] } } it "raises a validation error with concatenated messages" do method.should raise_error(RHC::Rest::ValidationException, "mock 3") end end end context "with a 500 response" do let(:code){ 500 } it "raises a generic server error" do method.should raise_error(RHC::Rest::ServerErrorException, /server did not respond correctly.*verify that you can access the OpenShift server/i) end context "when proxy is set" do let(:proxy) { 'http://foo.com' } it "raises a generic server error with the proxy URL" do method.should raise_error(RHC::Rest::ServerErrorException, /foo\.com/i) end end context "when request url is present" do let(:url){ 'foo.bar' } it "raises a generic server error with the request URL" do method.should raise_error(RHC::Rest::ServerErrorException, /foo\.bar/i) end end context "with a formatted JSON response" do let(:json){ { :messages => [{ :severity => 'error', :text => 'mock error message' }] } } it "raises a server error" do method.should raise_error(RHC::Rest::ServerErrorException, 'mock error message') end end end context "with a 503 response" do let(:code){ 503 } it "raises a 'service unavailable' error" do method.should raise_error(RHC::Rest::ServiceUnavailableException) end context "with a formatted JSON response" do let(:json){ { :messages => [{ :severity => 'error', :text => 'mock error message' }] } } it "raises a 'service unavailable' error" do method.should raise_error(RHC::Rest::ServiceUnavailableException, 'mock error message') end end end context "with an unhandled response code" do let(:code){ 999 } it{ method.should raise_error(RHC::Rest::ServerErrorException) } context "with a formatted JSON response" do let(:json){ { :messages => [{ :severity => 'error', :text => 'mock error message' }] } } it "raises a resource access error" do method.should raise_error(RHC::Rest::ServerErrorException, 'mock error message') end end end end end end rhc-1.38.7/spec/rhc/config_spec.rb0000644000004100000410000004050012756270552016742 0ustar www-datawww-datarequire 'spec_helper' require 'rhc/config' require 'net/http' describe RHC::Config do subject{ RHC::Config } before do ENV['LIBRA_SERVER'] = nil ENV['HTTP_PROXY'] = nil ENV['http_proxy'] = nil mock_terminal RHC::Config.stub(:home_dir).and_return('/home/mock_user') FakeFS.activate! FakeFS::FileSystem.clear end after do FakeFS.deactivate! ENV['HTTP_PROXY'] = nil ENV['http_proxy'] = nil ENV['LIBRA_SERVER'] = nil RHC::Config.send(:instance_variable_set, :@default, nil) end describe "class" do it("should raise when foo is invoked") { expect{ subject.method_missing(:foo) }.to raise_error(NoMethodError) } it("should invoke a method on default") { subject.username.should be subject.default.username } end let(:values){ {} } describe "#use_config" do subject{ RHC::Config.new.tap{ |c| c.stub(:load_config_files) } } context "when an exception is raised" do before{ subject.should_receive(:set_opts_config).with(File.expand_path('foo')).and_raise(Errno::EISDIR.new('foo')) } it("should wrap the error"){ expect{ subject.use_config('foo') }.to raise_error(ArgumentError, /Unable to read configuration file.*foo/) } end end describe "#to_options" do subject do RHC::Config.new.tap do |c| c.stub(:home_dir).and_return('/home/mock_user') c.stub(:load_config_files) c.instance_variable_set(:@opts, values) c.instance_variable_set(:@defaults, nil) end end context "with an non true value for insecure" do let(:values){ {'insecure' => 'untruth'} } its(:to_options){ should == {:insecure => false} } end context "with an invalid timeout" do let(:values){ {'timeout' => 'a'} } it{ expect{ subject.to_options }.to raise_error(ArgumentError) } end context "with standard values" do let(:values) do { 'insecure' => 'true', 'default_rhlogin' => 'user', 'libra_server' => 'test.com', 'password' => 'pass', 'ssl_client_cert_file' => 'file1', 'ssl_ca_file' => 'file2', 'timeout' => '1', 'use_authorization_tokens' => 'true', 'always_auth' => 'false', } end its(:to_options){ should == {:insecure => true, :timeout => 1, :ssl_ca_file => 'file2', :ssl_client_cert_file => 'file1', :rhlogin => 'user', :password => 'pass', :server => 'test.com', :use_authorization_tokens => true, :always_auth => false} } end end context "Config default values with no files" do before{ subject.initialize } its(:has_global_config?){ should be_false } its(:has_local_config?){ should be_false } its(:has_opts_config?){ should be_false } it "should return openshift.redhat.com for the server" do subject['libra_server'].should == "openshift.redhat.com" end end context "Config values with /etc/openshift/express.conf" do it "should have only a global config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, "global.openshift.redhat.com", "global@redhat.com") subject.initialize subject.has_global_config?.should be_true subject.has_local_config?.should be_false subject.has_opts_config?.should be_false end it "should get values from the global config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, "global.openshift.redhat.com", "global@redhat.com", {"random_value" => 12}) subject.initialize subject['libra_server'].should == "global.openshift.redhat.com" subject.default_rhlogin.should == "global@redhat.com" subject['random_value'].should == "12" subject['non_value'].should be_nil end it "should have libra_server fallback to the default if not set in config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, nil, "global@redhat.com") subject.initialize subject['libra_server'].should == "openshift.redhat.com" subject.default_rhlogin.should == "global@redhat.com" end end context "With a mock home dir" do def stub_config config = RHC::Config.new RHC::Config.instance_variable_set(:@default, config) config.stub(:home_dir).and_return(ConfigHelper.home_dir) RHC::Config.stub(:new).and_return(config) RHC::Config.default.should == config config.read_config_files end context "Config values with ~/.openshift/express.conf" do it "should have global and local config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, "global.openshift.redhat.com", "global@redhat.com") ConfigHelper.write_out_config(File.join(ConfigHelper.home_dir,'.openshift', 'express.conf'), "local.openshift.redhat.com","local@redhat.com") stub_config subject.home_conf_path.should == File.join(ConfigHelper.home_dir, '.openshift') subject.local_config_path.should == File.join(ConfigHelper.home_dir, '.openshift', 'express.conf') subject.has_global_config?.should be_true subject.has_local_config?.should be_true subject.has_opts_config?.should be_false end it "should get values from local config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, "global.openshift.redhat.com", "global@redhat.com", {"random_value" => "12"}) ConfigHelper.write_out_config(File.join(ConfigHelper.home_dir,'.openshift', 'express.conf'), "local.openshift.redhat.com", "local@redhat.com", {"random_value" => 11}) stub_config subject['libra_server'].should == "local.openshift.redhat.com" subject.default_rhlogin.should == "local@redhat.com" subject['random_value'].should == "11" subject['non_value'].should be_nil end it "should fallback to the default or global if not set in config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, nil, "global@redhat.com") ConfigHelper.write_out_config(File.join(ConfigHelper.home_dir,'.openshift', 'express.conf'), nil, nil, {"random_value" => 11}) stub_config subject['libra_server'].should == "openshift.redhat.com" subject.default_rhlogin.should == "global@redhat.com" subject['random_value'].should == "11" end end context "Config values with LIBRA_SERVER ENV set" do it "should get values from local config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, "global.openshift.redhat.com", "global@redhat.com", {"random_value" => "12"}) ConfigHelper.write_out_config(File.join(ConfigHelper.home_dir,'.openshift', 'express.conf'), "local.openshift.redhat.com", "local@redhat.com", {"random_value" => 11}) ENV['LIBRA_SERVER'] = "env.openshift.redhat.com" stub_config subject.set_local_config(File.join(ConfigHelper.home_dir,'.openshift', 'express.conf')) subject['libra_server'].should == "env.openshift.redhat.com" subject.default_rhlogin.should == "local@redhat.com" subject['random_value'].should == "11" subject['non_value'].should be_nil end end context "Config values with options set" do it "should have global and local config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, "global.openshift.redhat.com", "global@redhat.com") ConfigHelper.write_out_config(File.join(ConfigHelper.home_dir,'.openshift', 'express.conf'), "local.openshift.redhat.com","local@redhat.com") ConfigHelper.write_out_config(ConfigHelper.opts_config_path, "opts.openshift.redhat.com", "opts@redhat.com") stub_config subject.check_cpath({"config" => ConfigHelper.opts_config_path, "random_val" => "ok"}) subject.has_global_config?.should be_true subject.has_local_config?.should be_true subject.has_opts_config?.should be_true end it "should get values from local config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, "global.openshift.redhat.com", "global@redhat.com", {"random_value" => "12"}) ConfigHelper.write_out_config(File.join(ConfigHelper.home_dir,'.openshift', 'express.conf'), "local.openshift.redhat.com", "local@redhat.com", {"random_value" => 11}) ConfigHelper.write_out_config(ConfigHelper.opts_config_path, "opts.openshift.redhat.com", "opts@redhat.com", {"random_value" => 10}) stub_config subject.check_cpath({"config" => ConfigHelper.opts_config_path, "random_val" => "ok"}) subject['libra_server'].should == "opts.openshift.redhat.com" subject.default_rhlogin.should == "opts@redhat.com" subject['random_value'].should == "10" subject['non_value'].should be_nil end it "should fallback to the default or global or local if not set in config" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, nil, "global@redhat.com") ConfigHelper.write_out_config(File.join(ConfigHelper.home_dir,'.openshift', 'express.conf'), nil, nil, {"random_value" => 11, "local_value" => "local"}) ConfigHelper.write_out_config(ConfigHelper.opts_config_path, nil, nil, {"random_value" => 10}) stub_config subject.check_cpath({"config" => ConfigHelper.opts_config_path, "random_val" => "ok"}) subject['libra_server'].should == "openshift.redhat.com" subject.default_rhlogin.should == "global@redhat.com" subject['random_value'].should == "10" subject['local_value'].should == "local" end end end context "Debug options" do it "should show debug as false because nothing is set" do subject.initialize ConfigHelper.check_legacy_debug({}).should be_false end it "should show debug as 'true' because config is set" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, nil, nil, {"debug" => "true"}) subject.initialize ConfigHelper.check_legacy_debug({}).should == "true" end it "should show debug as false because config is set" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, nil, nil, {"debug" => "false"}) subject.initialize ConfigHelper.check_legacy_debug({}).should be_false end it "should show debug as true because config is set" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, nil, nil, {"debug" => "true"}) subject.initialize ConfigHelper.check_legacy_debug({"debug" => false}).should be_true end it "should show debug as true because opt is set" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, nil, nil, {"debug" => "false"}) subject.initialize ConfigHelper.check_legacy_debug({"debug" => true}).should be_true end end context "Proxy ENV variable parsing" do before do subject.initialize ['http_proxy','HTTP_PROXY'].each do |var| ENV[var] = nil end end it "should return a direct http connection" do subject.using_proxy?.should_not == true end ['http_proxy','HTTP_PROXY'].each do |var| it "should retrun a proxy http connection for #{var}" do ENV[var] = "fakeproxy.foo:8080" # returns a generic class so we check to make sure it is not a # Net::HTTP class and rely on simplecov to make sure the proxy # code path was run subject.using_proxy?.should == true end end context "it should have the correct values" do let(:vars){ subject.proxy_vars } before do ENV['http_proxy'] = "my_user:my_pass@fakeproxy.foo:8080" end { :user => 'my_user', :pass => 'my_pass', :address => 'fakeproxy.foo', :port => 8080 }.each do |var,expected| it "for #{var}" do vars[var].should == expected end end end end context "Configuration file parsing" do it "should exit if config file can't be read" do ConfigHelper.write_out_config(ConfigHelper.global_config_path, "global.openshift.redhat.com", "global@redhat.com") subject.initialize RHC::Vendor::ParseConfig.stub(:new) { raise Errno::EACCES.new("Fake can't read file") } subject.stub(:exit) { |code| code } expect { subject.check_cpath({"config" => "fake.conf"}) }.to raise_error(Errno::EACCES) # write out config file so it exists but is not readable ConfigHelper.write_out_config("fake.conf", "global.openshift.redhat.com", "global@redhat.com") expect { subject.read_config_files }.to raise_error(Errno::EACCES) expect { subject.set_local_config("fake.conf") }.to raise_error(Errno::EACCES) expect { subject.set_opts_config("fake.conf") }.to raise_error(Errno::EACCES) end end end class ConfigHelper @@global_config_path = '/etc/openshift/express.conf' @@home_dir = '/home/mock_user' @@opts_config_path = File.join(@@home_dir, "my.conf") def self.global_config_path @@global_config_path end def self.home_dir @@home_dir end def self.opts_config_path @@opts_config_path end def self.check_legacy_debug(opts) # this simulates how the old rhc code checked for debug # in the future this should all be filtered through the Config module # and an app should just have to use subject.debug? debug = RHC::Config['debug'] == 'false' ? nil : RHC::Config['debug'] debug = true if opts.has_key? 'debug' debug end def self.write_out_config(config_path, server, login, other={}) FileUtils.mkdir_p File.dirname(config_path) File.open(config_path, "w") do |f| f.write "# This is a test file\n\n" f.write("libra_server = #{server}\n") unless server.nil? f.write("default_rhlogin = #{login}\n\n") unless login.nil? other.each { |key, value| f.write("#{key}=#{value}\n") } end end end rhc-1.38.7/spec/rhc/servers_spec.rb0000644000004100000410000002105712756270552017174 0ustar www-datawww-datarequire 'net/http' require 'spec_helper' require 'rhc/config' require 'rhc/servers' describe RHC::Servers do before do FakeFS.activate! FakeFS::FileSystem.clear end after do FakeFS.deactivate! end describe "servers class" do subject do RHC::Servers.new.tap do |s| s.stub(:path).and_return('/home/mock_user/servers.yml') s.stub(:load) s.instance_variable_set(:@servers, [ RHC::Server.new( 'openshift.server.com', :nickname => 'online', :login => 'user1', :use_authorization_tokens => true, :insecure => false) ]) end end context "when finding server" do it "should find by nickname or hostname" do subject.send(:exists?, 'online').should be_true subject.send(:exists?, 'whatever').should be_false subject.send(:hostname_exists?, 'online').should be_false subject.send(:hostname_exists?, 'openshift.server.com').should be_true subject.send(:nickname_exists?, 'online').should be_true subject.send(:nickname_exists?, 'openshift.server.com').should be_false subject.send(:exists?, 'online').hostname.should == 'openshift.server.com' subject.send(:exists?, 'openshift.server.com').nickname.should == 'online' subject.send(:exists?, 'online').insecure.should == false subject.send(:exists?, 'online').use_authorization_tokens.should == true subject.find('online').should be_true subject.find('openshift.server.com').should be_true expect { subject.find('whatever') }.to raise_exception(RHC::ServerNotConfiguredException) end end context "when adding a new server" do before{ subject.add('another.server.com', :nickname => 'another', :login => 'user2', :use_authorization_tokens => false, :insecure => true) } it { subject.instance_variable_get(:@servers).length.should == 2 } it { subject.find('another').should be_true } it { subject.find('another.server.com').should be_true } it { subject.find('another').login.should == 'user2' } it { subject.find('another').insecure.should == true } it { subject.find('another').use_authorization_tokens.should == false } end context "when suggesting a nickname for unknown server" do before{ subject.add('another.server.com') } it { subject.find('server1').should be_true } it { subject.find('another.server.com').should be_true } it { subject.find('another.server.com').nickname.should == 'server1' } end context "when suggesting a nickname for an openshift subdomain" do before{ subject.add('some.openshift.redhat.com') } it { subject.find('some').should be_true } it { subject.find('some.openshift.redhat.com').should be_true } it { subject.find('some.openshift.redhat.com').nickname.should == 'some' } end context "when adding a new server with port and http scheme" do before{ subject.add('http://another.server.com:4000', :nickname => 'another', :login => 'user2') } it { subject.instance_variable_get(:@servers).length.should == 2 } it { subject.find('another').should be_true } it { subject.find('http://another.server.com:4000').should be_true } it { subject.find('another').login.should == 'user2' } it { expect { subject.find('another.server.com').to raise_exception(RHC::ServerNotConfiguredException) } } it { expect { subject.find('https://another.server.com:4000').to raise_exception(RHC::ServerNotConfiguredException) } } it { expect { subject.find('http://another.server.com').to raise_exception(RHC::ServerNotConfiguredException) } } end context "when adding an existing server" do it "should error accordingly" do expect { subject.add('openshift.server.com') }.to raise_exception(RHC::ServerHostnameExistsException) expect { subject.add('openshift.server.com', :nickname => 'another') }.to raise_exception(RHC::ServerHostnameExistsException) expect { subject.add('third.server.com', :nickname => 'online') }.to raise_exception(RHC::ServerNicknameExistsException) end end context "when updating an existing server" do before{ subject.update('online', :hostname => 'openshift2.server.com', :nickname => 'online2', :login => 'user2', :use_authorization_tokens => false, :insecure => true) } it { subject.list.length.should == 1 } it { subject.find('online2').hostname.should == 'openshift2.server.com' } it { subject.find('online2').nickname.should == 'online2' } it { subject.find('online2').login.should == 'user2' } it { subject.find('online2').insecure.should == true } it { subject.find('online2').use_authorization_tokens.should == false } it { expect { subject.find('online') }.to raise_exception(RHC::ServerNotConfiguredException) } end context "when adding or updating a server" do before{ subject.add_or_update('openshift.server.com', :nickname => 'online2', :login => 'user2', :use_authorization_tokens => false, :insecure => true) } it { subject.list.length.should == 1 } it { subject.find('openshift.server.com').hostname.should == 'openshift.server.com' } it { subject.find('openshift.server.com').nickname.should == 'online2' } it { subject.find('openshift.server.com').login.should == 'user2' } it { subject.find('openshift.server.com').insecure.should == true } it { subject.find('openshift.server.com').use_authorization_tokens.should == false } it { expect { subject.find('online') }.to raise_exception(RHC::ServerNotConfiguredException) } end context "when removing an existing server" do before{ subject.remove('online') } it { subject.list.length.should == 0 } it { expect { subject.find('online') }.to raise_exception(RHC::ServerNotConfiguredException) } end context "when finding the default server" do it "should take the first if no default defined" do subject.default.nickname.should == 'online' subject.default.hostname.should == 'openshift.server.com' end end context "when finding the default server" do before do s = subject.add('another.server.com', :nickname => 'another', :login => 'user2', :use_authorization_tokens => false, :insecure => true) s.default = true end it "should take the one marked as default" do subject.default.nickname.should == 'another' subject.default.hostname.should == 'another.server.com' end end context "when sync from config" do before do c = RHC::Config.new c.instance_variable_set(:@opts, RHC::Vendor::ParseConfig.new.tap do |v| v.add('libra_server', 'openshift.server.com') v.add('default_rhlogin', 'user3') end) c.stub(:has_configs_from_files?).and_return(true) subject.sync_from_config(c) end it { subject.list.length.should == 1 } it { subject.default.hostname.should == 'openshift.server.com' } it { subject.default.login.should == 'user3' } end end describe "server class" do subject do RHC::Server.from_yaml_hash({ 'hostname' => 'https://foo.com/bar', 'login' => 'user@foo.com', 'use_authorization_tokens' => 'true', 'insecure' => 'false' }) end context "when creating from yaml hash" do it { subject.hostname.should == 'foo.com' } it { subject.nickname.should == nil } it { subject.login.should == 'user@foo.com' } it { subject.use_authorization_tokens.should be_true } it { subject.insecure.should be_false } end context "when checking server attributes" do it { subject.designation.should == 'foo.com' } it { subject.default?.should be_false } it { subject.to_yaml_hash.should == { 'hostname' => 'foo.com', 'login' => 'user@foo.com', 'use_authorization_tokens' => true, 'insecure' => false } } it { subject.to_config.should be_a(RHC::Vendor::ParseConfig) } it { subject.to_config['default_rhlogin'].should == 'user@foo.com' } it { subject.to_config['libra_server'].should == 'foo.com' } it { subject.to_s.should == 'foo.com' } end context "when openshift online" do let(:server){ RHC::Server.from_yaml_hash({'hostname' => 'https://openshift.redhat.com', 'nickname' => 'online'}) } it { server.hostname.should == 'openshift.redhat.com' } it { server.nickname.should == 'online' } it { server.designation.should == 'online' } it { server.to_s.should == 'online (openshift.redhat.com)' } end end endrhc-1.38.7/spec/rhc/highline_extensions_spec.rb0000644000004100000410000002164412756270552021553 0ustar www-datawww-datarequire 'spec_helper' class OutputTests < SimpleDelegator def initialize(terminal) super @print_num = 0 end [:say, :agree, :ask, :choose].each do |sym| define_method(sym) do |*args, &block| __getobj__.send(sym, *args, &block) end end def next_print_num @print_num += 1 end def output say "section #{next_print_num}" end def output_no_breaks say "section #{next_print_num} " end def section_same_line section { output_no_breaks; say 'word' } end def section_no_breaks section { output_no_breaks } end def section_one_break section { output } end def sections_equal_bottom_top section(:bottom => 1) { output } section(:top => 1) { output } end def sections_larger_bottom section(:bottom => 2) { output } section(:top => 1) { output } end def sections_larger_top section(:bottom => 1) { output } section(:top => 2) { output } end def sections_four_on_three_lines section { output } section(:top => 1) { output_no_breaks } section(:bottom => 1) { output } section(:top => 1) { output } end def outside_newline section(:bottom => -1) { output } say "\n" section(:top => 1) { output } end def section_1_1 section(:top => 1, :bottom => 1) { say "section" } end def section_paragraph paragraph { say "section" } end # call section without output to reset spacing to 0 def reset __getobj__.instance_variable_set(:@margin, 0) end end describe HighLine::Header do it("should join a header"){ described_class.new("ab cd", 0).to_a.should == ["ab cd", '-----'] } it("should wrap a header"){ described_class.new("ab cd", 4).to_a.should == ["ab", "cd", '--'] } it("should wrap an array header"){ described_class.new(["ab cd"], 4).to_a.should == ["ab", "cd", '--'] } it("should combine an array header"){ described_class.new(["ab", "cd"], 5).to_a.should == ["ab cd", '-----'] } it("should wrap on array header boundaries"){ described_class.new(["abcd", "e"], 5).to_a.should == ["abcd", "e", '----'] } it("should indent an array header"){ described_class.new(["ab", "cd"], 4, ' ').to_a.should == ["ab", " cd", '---'] } it("should indent after a wrap"){ described_class.new(["ab cd", "ef gh", "ij"], 4, ' ').to_a.should == ["ab", "cd", " ef", " gh", " ij", '---'] } end describe HighLineExtension do subject{ MockHighLineTerminal.new } it("default_max_width should depend on wrap"){ subject.wrap_at = nil; subject.default_max_width.should be_nil} it("default_max_width should handle indentation"){ subject.wrap_at = 10; subject.indent{ subject.default_max_width.should == 10 - subject.indentation.length } } it "should wrap the terminal" do subject.wrap_at = 10 subject.say "Lorem ipsum dolor sit amet" output = subject.read output.should match "Lorem\nipsum\ndolor sit\namet\n" end it "should wrap the terminal" do subject.wrap_at = 16 subject.say "Lorem ipsum dolor sit amet" output = subject.read output.should == "Lorem ipsum\ndolor sit amet\n" end it "should not wrap the terminal" do subject.wrap_at = 50 subject.say "Lorem ipsum dolor sit amet" output = subject.read output.should == "Lorem ipsum dolor sit amet\n" end it "should wrap the terminal when using color codes" do subject.wrap_at = 10 subject.say subject.color("Lorem ipsum", :red) output = subject.read output.should == "\e[31mLorem\e\[0m\n\e[31mipsum\e[0m\n" end it "should wrap the terminal with other escape characters" do subject.wrap_at = 10 subject.say "Lorem ipsum dolor sit am\eet" output = subject.read output.should == "Lorem\nipsum\ndolor sit\nam\eet\n" end it "should wrap the terminal when words are smaller than wrap length" do subject.wrap_at = 3 subject.say "Antidisestablishmentarianism" output = subject.read output.should == "Antidisestablishmentarianism\n" end it "should terminate an open line if wrapping occurs" do subject.wrap_at = 10 subject.say "Foo " subject.say "Lorem ipsum" output = subject.read output.should match "Foo \nLorem\nipsum\n" end it "should handle an empty table" do subject.table([]).to_a.should == [] end it "should handle a nested empty table" do subject.table([[]]).to_a.should == [] end it "should normalize variables" do subject.table([ ["a", 5], [nil, ['a', 'b']], ]).to_a.should == [ "a 5", " a", " b", ] end it "should wrap a table based on a max width" do subject.table([["abcd efgh", "1234 6789 a"]], :width => 9, :heading => 'Test').to_a.should == [ 'Test', '----', "abcd 1234", "efgh 6789", " a" ] end # FIXME: Ragged edges still left by the algorithm # 104 total width, 1: 32/52, 2: 8/61, 113 total width it "should handle when columns are evenly split" do subject.table([ ["#{'a'*32} #{'b'*19}", "#{'c'*8} "*6+"d"*7] ], :width => 104).to_a.should == [ "a"*32+" "+'b'*19+" "+Array.new(5, 'c'*8).join(' '), ' '*53+"#{'c'*8} "+'d'*7 ] end it "should allocate columns fairly in a table" do subject.table([["abcd", "12345 67890"]], :width => 10).to_a.should == [ "abcd 12345", " 67890", ] end it "should not wrap without a width" do subject.table([["abcd", "12345 67890"]]).to_a.should == [ "abcd 12345 67890", ] end it "should implement each_line on the table" do subject.table([["abcd", "12345 67890"]]).each_line.next.should == "abcd 12345 67890" end it "should display headers" do subject.table([["abcd", "12345 67890"]], :header => ['abcdef', '123'], :width => 12).to_a.should == [ 'abcdef 123', '------ -----', "abcd 12345", " 67890", ] end it "should give the header priority over width when color is involved" do subject.table([["\e[31mabcd\e[0m", "1234567890"]], :header => ['abcdef', '123'], :width => 12).to_a.should == [ 'abcdef 123', '------ ----------', "\e[31mabcd\e[0m 1234567890", ] end it "should add a header to a table" do subject.table([["abcd efgh", "1234 6789 a"]], :width => 9, :heading => "Alongtextstring").to_a.should == [ "Alongtext", "string", "---------", "abcd 1234", "efgh 6789", " a" ] end it "should indent a table" do subject.table([["abcd efgh", "1234 6789 a"]], :indent => ' ', :width => 10).to_a.should == [ " abcd 1234", " efgh 6789", " a" ] end it "should not wrap table when not enough minimum width" do subject.table([["ab cd", "12 34"]], :width => 4).to_a.should == [ "ab cd 12 34", ] end it "should not wrap table cells that are too wide based on a max width" do subject.table([["abcdefgh", "1234567890"]], :width => 8).to_a.should == [ "abcdefgh 1234567890", ] end it "should force wrap a table based on columns" do subject.table([["ab cd", "123"]], :width => [6, 2]).to_a.should == [ "ab 123", "cd", ] end context "sections" do let(:tests) { OutputTests.new(subject) } it "should print out a paragraph with open endline on the same line" do tests.section_same_line subject.read.should == "section 1 word\n" end it "should print out a section without any line breaks" do tests.section_no_breaks subject.read.should == "section 1 \n" end it "should print out a section with trailing line break" do tests.section_one_break subject.read.should == "section 1\n" end it "should print out 2 sections with matching bottom and top margins generating one space between" do tests.sections_equal_bottom_top subject.read.should == "section 1\n\nsection 2\n" end it "should print out 2 sections with larger bottom margin generating two spaces between" do tests.sections_larger_bottom subject.read.should == "section 1\n\n\nsection 2\n" end it "should print out 2 sections with larger top margin generating two spaces between" do tests.sections_larger_top subject.read.should == "section 1\n\n\nsection 2\n" end it "should print out 4 sections and not collapse open sections" do tests.sections_four_on_three_lines subject.read.should == "section 1\n\nsection 2 \nsection 3\n\nsection 4\n" end it "should show the equivalence of paragaph to section(:top => 1, :bottom => 1)" do tests.section_1_1 section_1_1 = tests.read tests = OutputTests.new(MockHighLineTerminal.new) tests.section_paragraph paragraph = tests.read section_1_1.should == paragraph end it "should combine sections" do tests.section_1_1 tests.section_paragraph subject.read.should == "section\n\nsection\n" end it "should not collapse explicit newline sections" do tests.outside_newline subject.read.should == "section 1\n\n\nsection 2\n" end end endrhc-1.38.7/spec/rhc/targz_spec.rb0000644000004100000410000000455012756270552016631 0ustar www-datawww-datarequire 'spec_helper' require 'rhc/tar_gz' describe RHC::TarGz do context 'with simple compressed .tar.gz' do subject { File.expand_path('../assets/targz_sample.tar.gz', __FILE__) } it('should wrap the right filename') { File.basename(subject).should == 'targz_sample.tar.gz' } it('should contain the right files') { RHC::TarGz.contains(subject, 'foo').should be_true } it('should contain the right files') { RHC::TarGz.contains(subject, 'bar').should be_false } it('should contain the right files') { RHC::TarGz.contains(subject, 'test').should be_false } it('should contain the right files') { RHC::TarGz.contains(subject, 'foo').should be_true } it('should contain the right files') { RHC::TarGz.contains(subject, 'bar').should be_false } it('should contain the right files') { RHC::TarGz.contains(subject, 'test').should be_false } end context 'with file extension different than .tar.gz' do subject { File.expand_path('../assets/foo.txt', __FILE__) } it('should never return contains') { RHC::TarGz.contains(subject, 'foo').should be_false } it('should never return contains') { RHC::TarGz.contains(subject, 'foo', true).should be_false } end context 'with corrupted .tar.gz' do subject { File.expand_path('../assets/targz_corrupted.tar.gz', __FILE__) } it('should never return contains') { RHC::TarGz.contains(subject, 'foo').should be_false } it('should never return contains') { RHC::TarGz.contains(subject, 'foo', true).should be_false } end context 'with multiple threads' do subject { File.expand_path('../assets/targz_sample.tar.gz', __FILE__) } it('should be able to handle the same file') { threads = [] 30.times { threads << Thread.new { Thread.current['result'] = RHC::TarGz.contains(subject, 'foo') } threads << Thread.new { Thread.current['result'] = RHC::TarGz.contains(subject, 'foo', true) } } threads.each { |thread| thread.join } threads.each { |thread| thread['result'].should be_true } } end context 'with simple compressed .tar.gz' do subject { File.expand_path('../assets/targz_sample.tar.gz', __FILE__) } it('should read file in chunks') { control = false File.open(subject, 'rb') do |file| file.chunk(1024) do |chunk| control = true end end control.should be_true } end end rhc-1.38.7/spec/rhc/commands/0000755000004100000410000000000012756270552015740 5ustar www-datawww-datarhc-1.38.7/spec/rhc/commands/sshkey_spec.rb0000644000004100000410000001721312756270552020611 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/sshkey' require 'rhc/config' describe RHC::Commands::Sshkey do let!(:rest_client){ MockRestClient.new } before{ user_config } describe 'list' do context "when run with list command" do let(:arguments) { %w[sshkey list --noprompt --config test.conf -l test@test.foo -p password --trace] } it { expect { run }.to exit_with_code(0) } it { run_output.should match(/mockkey1 \(type: ssh-rsa\)/) } it { run_output.should match(/Fingerprint:.*0f:ce:86:80:df:a0:81:ca:db:f1:a7:0c:70:85:ce:00/) } it { run_output.should match(/mockkey3 \(type: krb5-principal\)/) } it { run_output.should match(/Principal:.* mockuser@mockdomain/) } # it { run_output.should match(/Fingerprint:.*Invalid key/) } end end describe 'show' do context "when run with show command" do let(:arguments) { %w[sshkey show mockkey1 --noprompt --config test.conf -l test@test.foo -p password --trace] } it { expect { run }.to exit_with_code(0) } it { run_output.should match(/mockkey1 \(type: ssh-rsa\)/) } end end describe "add" do context "when adding a valid key" do let(:arguments) { %w[sshkey add --noprompt --config test.conf -l test@test.foo -p password foobar id_rsa.pub] } it 'adds the key' do FakeFS do keys = rest_client.sshkeys num_keys = keys.length File.open('id_rsa.pub', 'w') do |f| f << 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCnCOqK7/mmvZ9AtCAerxjAasJ1rSpfuWT4vNm1+O/Fh0Di3chTWjY9a0M2hEnqkqnVG589L9CqCUeT0kdc3Vgw3JEcacSUr1z7tLr9kO+p/D5lSdQYzDGGRFOZ0H6lc/y8iNxWV1VO/sJvKx6cr5zvKIn8Q6GvhVNOxlai0IOb9FJxLGK95GLpZ+elzh8Tc9giy7KfwheAwhV2JoF9uRltE5JP/CNs7w/E29i1Z+jlueuu8RVotLmhSVNJm91Ey7OCtoI1iBE0Wv/SucFe32Qi08RWTM/MaGGz93KQNOVRGjNkosJjPmP1qU6WGBfliDkJAZXB0b6sEcnx1fbVikwZ' end expect { run }.to exit_with_code(0) rest_client.sshkeys.length.should == num_keys + 1 end end end context "when adding a valid key via command line arguments" do let(:arguments) { %w[sshkey add --noprompt --config test.conf -l test@test.foo -p password foobar --type ssh-rsa --content AAAAB3NzaC1yc2EAAAADAQABAAABAQCnCOqK7/mmvZ9AtCAerxjAasJ1rSpfuWT4vNm1+O/Fh0Di3chTWjY9a0M2hEnqkqnVG589L9CqCUeT0kdc3Vgw3JEcacSUr1z7tLr9kO+p/D5lSdQYzDGGRFOZ0H6lc/y8iNxWV1VO/sJvKx6cr5zvKIn8Q6GvhVNOxlai0IOb9FJxLGK95GLpZ+elzh8Tc9giy7KfwheAwhV2JoF9uRltE5JP/CNs7w/E29i1Z+jlueuu8RVotLmhSVNJm91Ey7OCtoI1iBE0Wv/SucFe32Qi08RWTM/MaGGz93KQNOVRGjNkosJjPmP1qU6WGBfliDkJAZXB0b6sEcnx1fbVikwZ] } it 'adds the key' do keys = rest_client.sshkeys num_keys = keys.length expect { run }.to exit_with_code(0) rest_client.sshkeys.length.should == num_keys + 1 end end context "when adding an invalid key" do let(:arguments) { %w[sshkey add --noprompt --config test.conf -l test@test.foo -p password foobar id_rsa.pub] } it "fails to add the key" do FakeFS do keys = rest_client.sshkeys num_keys = keys.length File.open('id_rsa.pub', 'w') do |f| f << 'ssh-rsa AADAQABAAABAQCnCOqK7/mmvZ9AtCAerxjAasJ1rSpfuWT4vNm1+O/Fh0Di3chTWjY9a0M2hEnqkqnVG589L9CqCUeT0kdc3Vgw3JEcacSUr1z7tLr9kO+p/D5lSdQYzDGGRFOZ0H6lc/y8iNxWV1VO/sJvKx6cr5zvKIn8Q6GvhVNOxlai0IOb9FJxLGK95GLpZ+elzh8Tc9giy7KfwheAwhV2JoF9uRltE5JP/CNs7w/E29i1Z+jlueuu8RVotLmhSVNJm91Ey7OCtoI1iBE0Wv/SucFe32Qi08RWTM/MaGGz93KQNOVRGjNkosJjPmP1qU6WGBfliDkJAZXB0b6sEcnx1fbVikwZ' end expect { run }.to exit_with_code(128) expect { run_output.should match(/Name:.* mockkey/) } rest_client.sshkeys.length.should == num_keys end end it "does not throw an error on an empty file" do FakeFS do keys = rest_client.sshkeys num_keys = keys.length File.open('id_rsa.pub', 'w') do |f| f << '' end expect { run }.to exit_with_code(128) expect { run_output.should match(/Name:.* mockkey/) } rest_client.sshkeys.length.should == num_keys end end it "exits with status code Errno::EACCES::Errno" do IO.should_receive(:read).and_return("ssh_foo bar") expect { run }.to exit_with_code(128) end end context "when adding an invalid key via command line arguments" do let(:arguments) { %w[sshkey add --noprompt --config test.conf -l test@test.foo -p password foobar --type ssh-rsa --content abcdef] } it "fails to add the key" do keys = rest_client.sshkeys num_keys = keys.length expect { run }.to exit_with_code(128) expect { run_output.should match(/Name:.* mockkey/) } rest_client.sshkeys.length.should == num_keys end end context "when adding an invalid key with --confirm" do let(:arguments) { %w[sshkey add --noprompt --confirm --config test.conf -l test@test.foo -p password foobar id_rsa.pub] } it "warns and then adds the key" do FakeFS do keys = rest_client.sshkeys num_keys = keys.length File.open('id_rsa.pub', 'w') do |f| f << 'ssh-rsa AADAQABAAABAQCnCOqK7/mmvZ9AtCAerxjAasJ1rSpfuWT4vNm1+O/Fh0Di3chTWjY9a0M2hEnqkqnVG589L9CqCUeT0kdc3Vgw3JEcacSUr1z7tLr9kO+p/D5lSdQYzDGGRFOZ0H6lc/y8iNxWV1VO/sJvKx6cr5zvKIn8Q6GvhVNOxlai0IOb9FJxLGK95GLpZ+elzh8Tc9giy7KfwheAwhV2JoF9uRltE5JP/CNs7w/E29i1Z+jlueuu8RVotLmhSVNJm91Ey7OCtoI1iBE0Wv/SucFe32Qi08RWTM/MaGGz93KQNOVRGjNkosJjPmP1qU6WGBfliDkJAZXB0b6sEcnx1fbVikwZ' end expect { run }.to exit_with_code(0) expect { run_output.should match("key you are uploading is not recognized") } rest_client.sshkeys.length.should == num_keys + 1 end end end context "when adding a nonexistent key" do let(:arguments) { %w[sshkey add --noprompt --config test.conf -l test@test.foo -p password foobar id_rsa.pub] } it "exits with status code Errno::ENOENT::Errno" do expect { run }.to exit_with_code(128) end end context "when attempting to add an existing but inaccessible key" do let(:arguments) { %w[sshkey add --noprompt --config test.conf -l test@test.foo -p password foobar inaccessible_key.pub] } #before :all do # @inaccessible_key = 'inaccessible_key.pub' # File.new(@inaccessible_key, 'w+') # File.chmod(0000, @inaccessible_key) #end #after :all do # File.delete @inaccessible_key #end it "exits with status code Errno::EACCES::Errno" do IO.should_receive(:read).and_raise(Errno::EACCES) expect { run }.to exit_with_code(128) end end context "when adding a key without correct arguments" do let(:arguments) { %w[sshkey add --noprompt --config test.conf -l test@test.foo -p password foobar] } it "exits with argument error" do expect { run }.to exit_with_code(1) end end end describe "remove" do context "when removing an existing key" do let (:arguments) { %w[sshkey remove --noprompt --config test.conf -l test@test.foo -p password mockkey2] } it 'deletes the key' do keys = rest_client.sshkeys num_keys = keys.length expect{ run }.to exit_with_code(0) rest_client.sshkeys.length.should == num_keys - 1 end end context "when removing a nonexistent key" do let (:arguments) { %w[sshkey remove --noprompt --config test.conf -l test@test.foo -p password no_match] } before{ @keys = rest_client.sshkeys } it 'leaves keys untouched' do num_keys = @keys.length expect{ run }.to exit_with_code(0) rest_client.sshkeys.length.should == num_keys end end end end rhc-1.38.7/spec/rhc/commands/server_spec.rb0000644000004100000410000005422412756270552020614 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'wizard_spec_helper' require 'rhc/commands/server' require 'rhc/config' require 'rhc/servers' describe RHC::Commands::Server do let(:rest_client) { MockRestClient.new } let(:default_options){} let(:options){ Commander::Command::Options.new(default_options) } let(:servers){ RHC::Servers.new.tap{|c| c.stub(:home_dir).and_return('/home/mock_user') } } let(:config){ RHC::Config.new.tap{ |c| c.stub(:home_dir).and_return('/home/mock_user') } } before do FakeFS.activate! FakeFS::FileSystem.clear end after do FakeFS.deactivate! end describe "server status" do before(:each){ user_config } describe 'run against a different server' do let(:arguments) { ['server', 'status', '--server', 'foo.com', '-l', 'person', '-p', ''] } context 'when server refuses connection' do before { stub_request(:get, 'https://foo.com/broker/rest/api').with(&user_agent_header).to_raise(SocketError) } it('should output an error') { run_output.should =~ /Connected to foo.com.*Unable to connect to the server/m } it { expect { run }.to exit_with_code(1) } end context 'when API is missing' do before { stub_request(:get, 'https://foo.com/broker/rest/api').with(&user_agent_header).to_return(:status => 404) } it('should output an error') { run_output.should =~ /Connected to foo.com.*server is not responding correctly/m } it { expect { run }.to exit_with_code(1) } end context 'when API is at version 1.2' do before do rest_client.stub(:api_version_negotiated).and_return('1.2') end it('should output an error') { run_output.should =~ /Connected to foo.com.*Using API version 1.2/m } it { expect { run }.to exit_with_code(0) } end end describe 'run against an invalid server url' do let(:arguments) { ['server', 'status', '--server', 'invalid_uri', '-l', 'person', '-p', ''] } it('should output an invalid URI error') { run_output.should match('Invalid URI specified: invalid_uri') } end describe 'run' do let(:arguments) { ['server', 'status'] } before{ rest_client.stub(:auth).and_return(nil) } context 'when no issues' do before { stub_request(:get, 'https://openshift.redhat.com/app/status/status.json').with(&user_agent_header).to_return(:body => {'issues' => []}.to_json) } it('should output success') { run_output.should =~ /All systems running fine/ } it { expect { run }.to exit_with_code(0) } end context 'when 1 issue' do before do stub_request(:get, 'https://openshift.redhat.com/app/status/status.json').with(&user_agent_header).to_return(:body => {'open' => [ {'issue' => { 'created_at' => '2011-05-22T17:31:32-04:00', 'id' => 11, 'title' => 'Root cause', 'updates' => [{ 'created_at' => '2012-05-22T13:48:20-04:00', 'description' => 'Working on update' }] }}]}.to_json) end it { expect { run }.to exit_with_code(1) } it('should output message') { run_output.should =~ /1 open issue/ } it('should output title') { run_output.should =~ /Root cause/ } it('should contain update') { run_output.should =~ /Working on update/ } end end end describe "server list" do context "without express.conf or servers.yml" do let(:arguments) { ['servers'] } it 'should output correctly' do run_output.should =~ /You don't have any servers configured\. Use 'rhc setup' to configure your OpenShift server/ end it { expect { run }.to exit_with_code(0) } end context "with one entry on servers.yml and no express.conf file" do before do stub_servers_yml end let(:arguments) { ['servers'] } it 'should output correctly' do run_output.should =~ /Server 'server1'/ run_output.should =~ /Hostname:\s+openshift1.server.com/ run_output.should =~ /Login:\s+user1/ run_output.should =~ /Use Auth Tokens:\s+true/ run_output.should =~ /Insecure:\s+false/ run_output.should =~ /You have 1 server configured/ end it { expect { run }.to exit_with_code(0) } end context "from express.conf and servers.yml" do let(:local_config_server){ 'openshift.redhat.com' } before do local_config stub_servers_yml end let(:arguments) { ['servers'] } it 'should output correctly' do run_output.should =~ /Server 'online' \(not configured, run 'rhc setup'\)/ run_output.should =~ /Hostname:\s+#{local_config_server}/ run_output.should =~ /Server 'server1'/ run_output.should =~ /Hostname:\s+openshift1.server.com/ run_output.should =~ /Login:\s+user1/ run_output.should =~ /Use Auth Tokens:\s+true/ run_output.should =~ /Insecure:\s+false/ run_output.should =~ /You have 2 servers configured/ end it { expect { run }.to exit_with_code(0) } end context "from express.conf and several entries on servers.yml" do let(:local_config_server){ 'openshift.redhat.com' } let(:entries){ 3 } before do local_config stub_servers_yml(entries) end let(:arguments) { ['servers'] } it 'should output correctly' do run_output.should =~ /Server 'online' \(not configured, run 'rhc setup'\)/ run_output.should =~ /Hostname:\s+#{local_config_server}/ Array(1..entries).each do |i| run_output.should =~ /Server 'server#{i}'/ run_output.should =~ /Hostname:\s+openshift#{i}.server.com/ end end it { expect { run }.to exit_with_code(0) } end context "from express.conf and several entries on servers.yml, matching servers" do let(:local_config_server){ 'openshift1.server.com' } let(:local_config_username){ 'local_config_user' } let(:local_config_password){ 'password' } let(:entries){ 3 } before do local_config stub_servers_yml(entries) end let(:arguments) { ['servers'] } it 'should output correctly' do run_output.should =~ /Server 'server1' \(in use\)/ run_output.should =~ /Hostname:\s+#{local_config_server}/ run_output.should =~ /Login:\s+#{local_config_username}/ Array(1..entries).each do |i| run_output.should =~ /Server 'server#{i}'/ run_output.should =~ /Hostname:\s+openshift#{i}.server.com/ end end it { expect { run }.to exit_with_code(0) } end context "when express.conf server is not present in servers.yml" do let(:local_config_server){ 'some.openshift.mycompany.com' } let(:entries){ 3 } before do local_config stub_servers_yml(entries) end let(:arguments) { ['servers'] } it 'should output correctly' do run_output.should =~ /Server '#{local_config_server}' \(not configured, run 'rhc setup'\)/ run_output.should =~ /Hostname:\s+#{local_config_server}/ Array(1..entries).each do |i| run_output.should =~ /Server 'server#{i}'/ run_output.should =~ /Hostname:\s+openshift#{i}.server.com/ end end it { expect { run }.to exit_with_code(0) } end end describe "server show" do context "from express.conf" do let(:local_config_username){ 'local_config_user' } let(:local_config_password){ 'password' } let(:local_config_server){ 'openshift.redhat.com' } before do local_config end let(:arguments) { ['server', 'show', 'online'] } it 'should output correctly' do run_output.should =~ /Server 'online' \(in use\)/ run_output.should =~ /Hostname:\s+openshift.redhat.com/ run_output.should =~ /Login:\s+local_config_user/ end it { expect { run }.to exit_with_code(0) } end context "from express.conf and servers.yml" do let(:arguments) { ['server', 'show', 'openshift1.server.com'] } before do local_config stub_servers_yml do |s| s.each do |i| i.nickname = nil i.use_authorization_tokens = false i.insecure = true end end end it 'should output correctly' do run_output.should =~ /Server 'openshift1.server.com'/ run_output.should =~ /Hostname:\s+openshift1.server.com/ run_output.should =~ /Login:\s+user1/ run_output.should =~ /Use Auth Tokens:\s+false/ run_output.should =~ /Insecure:\s+true/ end it { expect { run }.to exit_with_code(0) } end context "when trying to show server not configured" do let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } before(:each) do stub_servers_yml local_config end let(:arguments) { ['server', 'show', 'zee'] } it 'should output correctly' do run_output.should =~ /You don't have any server configured with the hostname or nickname 'zee'/ end it { expect { run }.to exit_with_code(166) } end end describe "server add" do context "with no existing config" do let(:new_server){ 'openshift1.server.com' } let(:username){ 'user1' } let(:token){ 'an_existing_token' } let(:arguments) { ['server', 'add', new_server, '-l', username, '--use-authorization-tokens', '--no-insecure', '--token', token, '--use'] } before(:each) { stub_wizard } it 'should create servers.yml' do servers.present?.should be_false servers.list.should == [] servers.hostname_exists?(new_server).should be_false run_output.should =~ /Saving server configuration to.*servers\.yml.*done/ servers.reload servers.present?.should be_true servers.list.should_not == [] servers.hostname_exists?(new_server).should be_true end it { expect { run }.to exit_with_code(0) } end context "with existing express.conf and successfully adding server" do let(:server){ 'openshift1.server.com' } let(:username){ 'user1' } let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } let(:token){ 'an_existing_token' } let(:arguments) { ['server', 'add', server, '-l', username, '--use-authorization-tokens', '--no-insecure', '--token', token, '--use'] } subject{ RHC::ServerWizard.new(config, options, servers) } before(:each) do RHC::Servers.any_instance.stub(:save!) stub_wizard local_config end it 'should output correctly' do run_output.should =~ /Using an existing token for #{username} to login to #{server}/ run_output.should =~ /Saving configuration to.*express\.conf.*done/ run_output.should =~ /Saving server configuration to.*servers\.yml.*done/ end it { expect { run }.to exit_with_code(0) } end context "with existing express.conf trying to add an existing server" do let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } let(:arguments) { ['server', 'add', local_config_server, 'server1', '-l', local_config_username, '--use-authorization-tokens', '--no-insecure'] } before(:each) do local_config end it 'should output correctly' do run_output.should =~ /You already have a server configured with the hostname '#{local_config_server}'/ end it { expect { run }.to exit_with_code(165) } end context "with existing express.conf and servers.yml and adding a new server" do let(:server){ 'openshift.server.com' } let(:username){ 'user3' } let(:server_name){ 'server3' } let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } let(:token){ 'an_existing_token' } let(:arguments) { ['server', 'add', server, server_name, '-l', username, '--use-authorization-tokens', '--no-insecure', '--token', token, '--use'] } subject{ RHC::ServerWizard.new(config, options, servers) } before do stub_wizard local_config stub_servers_yml(2) end it { run_output.should =~ /Using an existing token for #{username} to login to #{server}/ } it { run_output.should =~ /Saving configuration to.*express\.conf.*done/ } it { run_output.should =~ /Saving server configuration to.*servers\.yml.*done/ } it { expect { run }.to exit_with_code(0) } end context "with existing express.conf and servers.yml and adding a new server with port and http scheme" do let(:server){ 'http://my.server.com:4000' } let(:username){ 'user3' } let(:server_name){ 'server3' } let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } let(:token){ 'an_existing_token' } let(:arguments) { ['server', 'add', server, server_name, '-l', username, '--use-authorization-tokens', '--no-insecure', '--token', token, '--use'] } subject{ RHC::ServerWizard.new(config, options, servers) } before do stub_wizard local_config stub_servers_yml(2) end it { run_output.should =~ /Using an existing token for #{username} to login to #{server}/ } it { run_output.should =~ /Saving configuration to.*express\.conf.*done/ } it { run_output.should =~ /Saving server configuration to.*servers\.yml.*done/ } it { expect { run }.to exit_with_code(0) } end context "with existing express.conf and servers.yml and adding a new mock server" do let(:server){ 'openshift.server.com' } let(:username){ 'user3' } let(:server_name){ 'server3' } let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } let(:arguments) { ['server', 'add', server, server_name, '-l', username, '--skip-wizard'] } before do local_config stub_servers_yml(2) end it { run_output.should_not =~ /Using an existing token for #{username} to login to #{server}/ } it { run_output.should_not =~ /Saving configuration to.*express\.conf.*done/ } it { run_output.should =~ /Saving server configuration to.*servers\.yml.*done/ } it { expect { run }.to exit_with_code(0) } end context "with existing express.conf and servers.yml and trying to add an existing server" do let(:local_config_username){ 'local_config_user' } let(:local_config_password){ 'password' } let(:local_config_server){ 'openshift.redhat.com' } before do local_config stub_servers_yml(2) end let(:arguments) { ['server', 'add', 'foo.com', 'server1', '-l', local_config_username, '--use-authorization-tokens', '--no-insecure'] } it 'should output correctly' do run_output.should =~ /You already have a server configured with the nickname 'server1'/ end it { expect { run }.to exit_with_code(164) } end context "with wizard failure" do let(:token){ 'an_existing_token' } let(:arguments) { ['server', 'add', 'failure.server.com', 'failed', '-l', 'failer'] } before do RHC::ServerWizard.any_instance.stub(:run).and_return(false) stub_servers_yml end it { expect { run }.to exit_with_code(1) } end end describe "server remove" do context "when trying to remove the server in use" do let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } before(:each) do stub_servers_yml local_config end let(:arguments) { ['server', 'remove', local_config_server] } it 'should output correctly' do run_output.should =~ /The '#{local_config_server}' server is in use/ end it { expect { run }.to exit_with_code(167) } end context "when removing successfully" do let(:server){ 'openshift5.server.com' } let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } before(:each) do stub_servers_yml(5) local_config end let(:arguments) { ['server', 'remove', server] } it 'should output correctly' do run_output.should =~ /Removing '#{server}'.*done/ end it { expect { run }.to exit_with_code(0) } end context "when trying to remove server not configured" do let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } before(:each) do stub_servers_yml local_config end let(:arguments) { ['server', 'remove', 'zee'] } it 'should output correctly' do run_output.should =~ /You don't have any server configured with the hostname or nickname 'zee'/ end it { expect { run }.to exit_with_code(166) } end end describe "server configure" do context "when configuring existing server" do let(:server){ 'local.server.com' } let(:username){ 'local_username' } let(:local_config_server){ server } let(:local_config_username){ username } let(:local_config_server_new_username){ 'new_username' } let(:local_config_server_new_name){ 'new_name' } let(:token){ 'an_existing_token' } let(:arguments) { ['server', 'configure', local_config_server, '--nickname', local_config_server_new_name, '-l', local_config_server_new_username, '--insecure', '--token', token, '--use'] } subject{ RHC::ServerWizard.new(config, options, servers) } before do stub_wizard local_config stub_servers_yml end it { run_output.should =~ /Saving server configuration to.*servers\.yml.*done/ } it { run_output.should =~ /Saving configuration to.*express\.conf.*done/ } it { run_output.should =~ /Using an existing token for #{local_config_server_new_username} to login to #{server}/ } it { run_output.should =~ /Server '#{local_config_server_new_name}' \(in use\)/ } it { run_output.should =~ /Hostname:\s+#{server}/ } it { run_output.should =~ /Login:\s+#{local_config_server_new_username}/ } it { run_output.should =~ /Insecure:\s+true/ } it { run_output.should =~ /Now using '#{server}'/ } it { expect { run }.to exit_with_code(0) } end context "with existing express.conf and servers.yml and skipping wizard" do let(:server){ 'local.server.com' } let(:username){ 'local_username' } let(:local_config_server){ server } let(:local_config_username){ username } let(:local_config_server_new_username){ 'new_username' } let(:local_config_server_new_name){ 'new_name' } let(:arguments) { ['server', 'configure', local_config_server, '--nickname', local_config_server_new_name, '-l', local_config_server_new_username, '--insecure', '--skip-wizard'] } before do local_config stub_servers_yml end it { run_output.should =~ /Saving server configuration to.*servers\.yml.*done/ } it { run_output.should_not =~ /Saving configuration to.*express\.conf.*done/ } it { run_output.should_not =~ /Using an existing token/ } it { run_output.should =~ /Server '#{local_config_server_new_name}' \(in use\)/ } it { run_output.should_not =~ /Now using '#{server}'/ } it { run_output.should =~ /Login:\s+#{local_config_server_new_username}/ } it { expect { run }.to exit_with_code(0) } end context "when trying to remove server not found" do let(:local_config_server){ 'local.server.com' } let(:local_config_username){ 'local_username' } before(:each) do stub_servers_yml local_config end let(:arguments) { ['server', 'configure', 'zee', '--insecure'] } it 'should output correctly' do run_output.should =~ /You don't have any server configured with the hostname or nickname 'zee'/ end it { expect { run }.to exit_with_code(166) } end end describe "server use" do context "when using an existing server" do let(:server){ 'local.server.com' } let(:username){ 'local_user' } let(:local_config_server){ server } let(:local_config_username){ username } let(:token){ 'an_existing_token' } subject{ RHC::ServerWizard.new(config, options, servers) } before do stub_wizard local_config stub_servers_yml(3) end let(:arguments) { ['server', 'use', 'server3', '--token', token] } it { run_output.should =~ /Using an existing token for .* to login to openshift3\.server\.com/ } it { run_output.should =~ /Saving server configuration to.*servers\.yml.*done/ } it { run_output.should =~ /Saving configuration to.*express\.conf.*done/ } it { run_output.should =~ /Now using 'openshift3\.server\.com'/ } it { expect { run }.to exit_with_code(0) } end context "with wizard failure" do let(:server){ 'local.server.com' } let(:username){ 'local_user' } let(:local_config_server){ server } let(:local_config_username){ username } subject{ RHC::ServerWizard.new(config, options, servers) } before do RHC::ServerWizard.any_instance.stub(:run).and_return(false) local_config stub_servers_yml end let(:arguments) { ['server', 'use', 'local.server.com'] } it { expect { run }.to exit_with_code(1) } end end protected def stub_servers_yml(entries=1, &block) RHC::Servers.any_instance.stub(:present?).and_return(true) RHC::Servers.any_instance.stub(:load).and_return( Array(1..entries).collect do |i| RHC::Server.new("openshift#{i}.server.com", :nickname => "server#{i}", :login => "user#{i}", :use_authorization_tokens => true, :insecure => false, :persisted => true) end.tap{|i| yield i if block_given?}) end def stub_wizard rest_client.stub(:api) rest_client.stub(:user).and_return(double(:login => username)) rest_client.stub(:supports_sessions?).and_return(true) rest_client.stub(:new_session) subject.stub(:new_client_for_options).and_return(rest_client) end end rhc-1.38.7/spec/rhc/commands/ssh_spec.rb0000644000004100000410000002257012756270552020102 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/ssh' describe RHC::Commands::Ssh do let!(:rest_client){ MockRestClient.new } let!(:config){ user_config } before{ RHC::Config.stub(:home_dir).and_return('/home/mock_user') } before{ Kernel.stub(:exec).and_raise(RuntimeError) } describe 'ssh default' do context 'ssh' do let(:arguments) { ['ssh'] } it { run_output.should match('Usage:') } end end describe 'ssh without command' do let(:arguments) { ['app', 'ssh', 'app1'] } context 'when run' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") Kernel.should_receive(:exec).with("ssh", "fakeuuidfortestsapp1@127.0.0.1").and_return(0) end it { run_output.should match("Connecting to fakeuuidfortestsapp") } it { expect{ run }.to exit_with_code(0) } end end describe 'ssh without command including debugging' do let(:arguments) { ['app', 'ssh', 'app1', '--debug'] } context 'when run' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") Kernel.should_receive(:exec).with("ssh", "-vv", "fakeuuidfortestsapp1@127.0.0.1").and_return(0) end # It would be nice if this checked for the debug[123]: messages from standard error but im not sure how to look for that. it { run_output.should match("Connecting to fakeuuidfortestsapp") } it { expect{ run }.to exit_with_code(0) } end end describe 'app ssh with command' do let(:arguments) { ['app', 'ssh', 'app1', 'ls', '/tmp'] } context 'when run' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") Kernel.should_receive(:exec).with("ssh", "fakeuuidfortestsapp1@127.0.0.1", "ls", "/tmp").and_return(0) end it { run_output.should_not match("Connecting to fakeuuidfortestsapp") } it { expect { run }.to exit_with_code(0) } end end describe 'app ssh with --gears' do before{ rest_client.add_domain("mockdomain").add_application("app1", "mock_type", true) } context 'with no command' do let(:arguments) { ['app', 'ssh', 'app1', '--gears'] } it('should display usage info') { run_output.should match("Usage:") } it { expect { run }.to exit_with_code(1) } end context 'with a command' do let(:arguments) { ['app', 'ssh', 'app1', '--gears', '--', 'command', '--trace'] } before{ expect_multi_ssh('command', 'fakegearid0@fakesshurl.com' => 'foo', 'fakegearid1@fakesshurl.com' => 'bar') } it('should print the ssh output') { run_output.should == "[fakegearid0 ] foo\n[fakegearid1 ] bar\n\n" } it('should return successfully') { expect{ run }.to exit_with_code(0) } end context 'with an implicit app name' do before{ subject.class.any_instance.stub(:git_config_get){ |key| 'app1' if key == "rhc.app-name" } } let(:arguments) { ['app', 'ssh', '--gears', '--', 'command', '--trace'] } before{ expect_multi_ssh('command', 'fakegearid0@fakesshurl.com' => 'foo', 'fakegearid1@fakesshurl.com' => 'bar') } it('should print the ssh output') { run_output.should == "[fakegearid0 ] foo\n[fakegearid1 ] bar\n\n" } it('should return successfully') { expect{ run }.to exit_with_code(0) } end context 'with an application id' do let(:arguments) { ['app', 'ssh', '--application-id', rest_client.domains.first.applications.first.id, '--gears', 'command', '--trace'] } before{ expect_multi_ssh('command', 'fakegearid0@fakesshurl.com' => 'foo', 'fakegearid1@fakesshurl.com' => 'bar') } it('should print the ssh output') { run_output.should == "[fakegearid0 ] foo\n[fakegearid1 ] bar\n\n" } it('should return successfully') { expect{ run }.to exit_with_code(0) } end context 'with --raw' do let(:arguments) { ['app', 'ssh', 'app1', '--gears', 'command', '--raw'] } before{ expect_multi_ssh('command', 'fakegearid0@fakesshurl.com' => 'foo', 'fakegearid1@fakesshurl.com' => 'bar') } it('should print the ssh output') { run_output.should == "foo\nbar\n\n" } end context 'with --limit' do let(:arguments) { ['app', 'ssh', 'app1', '--gears', 'command', '--limit', '1'] } before{ expect_multi_ssh('command', 'fakegearid0@fakesshurl.com' => 'foo', 'fakegearid1@fakesshurl.com' => 'bar') } it('should print the ssh output') { run_output.should == "[fakegearid0 ] foo\n[fakegearid1 ] bar\n\n" } end context 'with invalid --limit value' do ['0','-10'].each do |value| let(:arguments) { ['app', 'ssh', 'app1', '--gears', 'command', '--limit', value] } it { run_output.should match('--limit must be an integer greater than zero') } end end context 'with multiline output and --always-prefix' do let(:arguments) { ['app', 'ssh', 'app1', '--gears', 'command', '--always-prefix'] } before{ expect_multi_ssh('command', 'fakegearid0@fakesshurl.com' => "foo\ntest", 'fakegearid1@fakesshurl.com' => "bar\ntest") } it('should print the ssh output') { run_output.should == "[fakegearid0 ] foo\n[fakegearid0 ] test\n[fakegearid1 ] bar\n[fakegearid1 ] test\n\n" } end context 'with multiline output' do let(:arguments) { ['app', 'ssh', 'app1', '--gears', 'command'] } before{ expect_multi_ssh('command', 'fakegearid0@fakesshurl.com' => "foo\ntest", 'fakegearid1@fakesshurl.com' => "bar\ntest") } it('should print the ssh output') { run_output.should == "=== fakegearid0 \nfoo\ntest\n=== fakegearid1 \nbar\ntest\n\n" } end end describe 'app ssh no system ssh' do let(:arguments) { ['app', 'ssh', 'app1'] } context 'when run' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") subject.class.any_instance.stub(:discover_ssh_executable).and_return(nil) end it { run_output.should match("No system SSH available. Please use the --ssh option to specify the path to your SSH executable, or install SSH.") } it { expect { run }.to exit_with_code(1) } end end describe 'app ssh custom ssh' do let(:arguments) { ['app', 'ssh', 'app1', '--ssh', 'path_to_ssh'] } context 'when custom ssh does not exist' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") RHC::Commands::Ssh.any_instance.should_not_receive(:has_ssh?) File.should_receive(:exist?).with("path_to_ssh").at_least(1).and_return(false) File.should_receive(:executable?).with(/.*path_to_ssh/).at_least(1).and_return(false) subject.class.any_instance.stub(:discover_git_executable).and_return('git') end it { run_output.should match("SSH executable 'path_to_ssh' does not exist.") } it { expect { run }.to exit_with_code(1) } end context 'when custom ssh is not executable' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") RHC::Commands::Ssh.any_instance.should_not_receive(:has_ssh?) File.should_receive(:exist?).with("path_to_ssh").once.and_return(true) File.should_receive(:executable?).with(/.*path_to_ssh/).at_least(1).and_return(false) subject.class.any_instance.stub(:discover_git_executable).and_return('git') end it { run_output.should match("SSH executable 'path_to_ssh' is not executable.") } it { expect { run }.to exit_with_code(1) } end context 'when custom ssh exists' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") RHC::Commands::Ssh.any_instance.should_not_receive(:has_ssh?) File.should_receive(:exist?).with("path_to_ssh").once.and_return(true) File.should_receive(:executable?).with("path_to_ssh").once.and_return(true) Kernel.should_receive(:exec).with("path_to_ssh", "fakeuuidfortestsapp1@127.0.0.1").once.times.and_return(0) subject.class.any_instance.stub(:discover_git_executable).and_return('git') end it { run_output.should match("Connecting to fakeuuidfortestsapp") } it { expect { run }.to exit_with_code(0) } end end describe 'app ssh custom ssh with spaces and arguments' do let(:arguments) { ['app', 'ssh', 'app1', '--ssh', '"/path/to /ssh" --with_custom_flag'] } before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") RHC::Commands::Ssh.any_instance.should_not_receive(:has_ssh?) File.should_receive(:exist?).at_least(1).and_return(true) File.should_receive(:executable?).at_least(1).and_return(true) subject.class.any_instance.stub(:discover_git_executable).and_return('git') Kernel.should_receive(:exec).with("/path/to /ssh", "--with_custom_flag", "fakeuuidfortestsapp1@127.0.0.1").once.times.and_return(0) end it { expect { run }.to exit_with_code(0) } end describe 'ssh tests' do let(:arguments) { ['app', 'ssh', 'app1', '-s /bin/blah'] } context 'has_ssh?' do before{ RHC::Commands::Ssh.any_instance.stub(:ssh_version){ raise "Fake Exception" } } before{ RHC::Commands::Ssh.any_instance.stub(:discover_ssh_executable){ nil } } its(:has_ssh?) { should be_false } end end end rhc-1.38.7/spec/rhc/commands/logout_spec.rb0000644000004100000410000000720512756270552020614 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc' require 'rhc/commands/logout' describe RHC::Commands::Logout do describe '#run' do let(:arguments) { ['logout'] } let(:username) { 'foo' } let(:password) { nil } let(:supports_auth) { false } let(:server) { mock_uri } let!(:token_store) { RHC::Auth::TokenStore.new(Dir.mktmpdir) } before{ user_config } before do stub_api(false, supports_auth) challenge{ stub_user } RHC::Auth::TokenStore.stub(:new).and_return(token_store) end context "when calling from the alias" do let(:arguments){ ['account', 'logout', '-h'] } it("should print usage"){ run_output.should match "Usage: rhc logout" } end it("should clear the token cache"){ expect{ run }.to call(:clear).on(token_store) } it("should exit with success"){ expect{ run }.to exit_with_code(0) } it("should display a message"){ run_output.should match("All local sessions removed.") } context "when --all is requested" do let(:arguments) { ['account', 'logout', '--all'] } context "if the server does not implement authorizations" do it("should display a message"){ run_output.should match(/Deleting all authorizations associated with your account.*not supported/) } it("should exit with success"){ expect{ run }.to exit_with_code(0) } end context "if the server implements authorizations" do let(:supports_auth) { true } before{ stub_delete_authorizations } it("should display a message"){ run_output.should match(/Deleting all authorizations associated with your account.*done/) } it("should exit with success"){ expect{ run }.to exit_with_code(0) } end end context "when --token is provided" do let(:arguments) { ['account', 'logout', '--token', 'foo'] } def user_auth; { :token => 'foo' }; end context "if the server does not implement authorizations" do it("should display a message"){ run_output.should match(/Ending session on server.*not supported/) } it("should exit with success"){ expect{ run }.to exit_with_code(0) } end context "if the server implements authorizations" do let(:supports_auth) { true } context "if the server returns successfully" do before{ stub_delete_authorization('foo') } it("should display a message"){ run_output.should match(/Ending session on server.*deleted/) } it("should exit with success"){ expect{ run }.to exit_with_code(0) } it("should clear the token cache"){ expect{ run }.to call(:clear).on(token_store) } end context "if the server rejects the token" do before{ stub_request(:delete, mock_href('broker/rest/user/authorizations/foo', false)).to_return(:status => 401, :body => {}.to_json) } it("should display a message"){ run_output.should match(/Ending session on server.*already closed/) } it("should exit with success"){ expect{ run }.to exit_with_code(0) } it("should clear the token cache"){ expect{ run }.to call(:clear).on(token_store) } end context "if the server returns an unexpected error" do before{ stub_request(:delete, mock_href('broker/rest/user/authorizations/foo', false)).to_return(:status => 500, :body => {}.to_json) } it("should display a message"){ run_output.should match(/Ending session on server.*The server did not respond/) } it("should exit with success"){ expect{ run }.to exit_with_code(0) } it("should clear the token cache"){ expect{ run }.to call(:clear).on(token_store) } end end end end end rhc-1.38.7/spec/rhc/commands/team_spec.rb0000644000004100000410000001276312756270552020236 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/team' describe RHC::Commands::Team do let(:rest_client){ MockRestClient.new } before{ user_config } describe 'default action' do before{ rest_client } context 'when run with no teams' do let(:arguments) { ['team'] } it { expect { run }.to exit_with_code(1) } it { run_output.should match(/To create.*rhc create-team/m) } end context 'when help is shown' do let(:arguments) { ['team', '--noprompt', '--help'] } it { expect { run }.to exit_with_code(0) } it { run_output.should match(/create.*delete.*leave.*list/m) } end end describe 'show' do before{ rest_client } context 'when run with no teams' do let(:arguments) { ['team', 'show'] } it { expect { run }.to exit_with_code(1) } it { run_output.should match(/specify a team name/) } end context 'when run with one team' do let(:arguments) { ['team', 'show', 'oneteam'] } before{ rest_client.add_team("oneteam") } it { expect { run }.to exit_with_code(0) } it { run_output.should match(/oneteam/) } it { run_output.should match(/ID: 123/) } end context 'when run with two teams' do let(:arguments) { ['team', 'show', 'twoteam'] } before do rest_client.add_team("oneteam") rest_client.add_team("twoteam") end it { expect { run }.to exit_with_code(0) } it { run_output.should match(/twoteam/) } it { run_output.should_not match(/oneteam/) } end end describe 'list' do before{ rest_client } let(:arguments) { ['team', 'list'] } context 'when run with no teams' do it { expect { run }.to exit_with_code(0) } it { run_output.should match(/member of 0 teams/) } end context 'when run with one team' do before{ rest_client.add_team("oneteam") } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("You are a member of 1 team\\.") output.should match("oneteam") end context 'when has no members' do before{ rest_client.teams.first.attributes.merge(:members => nil, :id => '123') } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("oneteam") output.should_not match ("Members:") output.should match ("ID: 123") end end context 'when an ID is present and differs from name' do let(:arguments) { ['team', 'list'] } before{ rest_client.teams.first.attributes['id'] = '123' } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("oneteam") output.should match ("ID:.*123") end end end context 'when run with one owned team' do let(:arguments) { ['teams', '--mine'] } before{ t = rest_client.add_team('mine', true); rest_client.stub(:owned_teams).and_return([t]) } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("You have 1 team\\.") output.should match("mine") end end end describe 'create' do before{ rest_client } let(:arguments) { ['team', 'create', 'testname'] } context 'when no issues with ' do it "should create a team" do expect { run }.to exit_with_code(0) rest_client.teams[0].name.should == 'testname' end it { run_output.should match(/Creating.*'testname'.*done/m) } end end describe 'leave' do before{ rest_client.add_team("deleteme") } let(:arguments) { ['team', 'leave', 'deleteme'] } it "should leave the team" do rest_client.teams.first.should_receive(:leave).and_return(RHC::Rest::Membership::Member.new) expect { run }.to exit_with_code(0) end end describe 'delete' do before{ rest_client } let(:arguments) { ['team', 'delete', 'deleteme'] } context 'when no issues with ' do before{ t = rest_client.add_team("deleteme") rest_client.should_receive(:owned_teams).and_return([t]) } it "should delete a team" do expect { run }.to exit_with_code(0) rest_client.teams.empty?.should be_true end end context 'when there is a different team' do before do t = rest_client.add_team("dontdelete") rest_client.should_receive(:owned_teams).and_return([t]) end it "should error out" do expect { run }.to exit_with_code(162) rest_client.teams[0].name.should == 'dontdelete' end it { run_output.should match("Team with name deleteme not found") } end context 'when the server does not allow deleting' do before do t = rest_client.add_team("dontdelete") t.links.delete 'DELETE' t.id = "123" end let(:arguments) { ['team','delete','--team-id','123'] } it "should error out" do expect { run }.to exit_with_code(1) rest_client.teams[0].name.should == 'dontdelete' end it { run_output.should match(/not support/) } end end describe 'help' do let(:arguments) { ['team', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc team") } end end end rhc-1.38.7/spec/rhc/commands/account_spec.rb0000644000004100000410000000253112756270552020734 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc' require 'rhc/commands/account' describe RHC::Commands::Account do describe '#run' do let(:arguments) { ['account'] } let(:username) { 'foo' } let(:password) { 'pass' } let(:server) { mock_uri } before{ user_config } before do stub_api challenge{ stub_user } end it('should display the correct user') { run_output.should =~ /Login #{username}/ } it('should display the correct server') { run_output.should =~ /on #{server}/ } it('should not show') { run_output.should_not =~ /Plan:/ } it('should show the gear capabilities') { run_output.should =~ /Allowed Gear Sizes:\s*small/ } it('should show the consumed gears') { run_output.should =~ /Gears Used:\s*0/ } it('should show the maximum gears') { run_output.should =~ /Gears Allowed:\s*3/ } it { expect { run }.to exit_with_code(0) } context 'with a free plan' do let(:user_plan_id){ 'free' } it('should show') { run_output.should =~ /Plan:\s*Free/ } end context 'with a silver plan' do let(:user_plan_id){ 'silver' } it('should show') { run_output.should =~ /Plan:\s*Silver/ } end context 'with a arbitrary plan' do let(:user_plan_id){ 'other' } it('should show') { run_output.should =~ /Plan:\s*Other/ } end end end rhc-1.38.7/spec/rhc/commands/env_spec.rb0000644000004100000410000005152512756270552020077 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/env' require 'rhc/config' describe RHC::Commands::Env do def exit_with_code_and_message(code, message=nil) expect{ run }.to exit_with_code(code) run_output.should match(message) if message end def exit_with_code_and_without_message(code, message=nil) expect{ run }.to exit_with_code(code) run_output.should_not match(message) if message end def succeed_with_message(message="done") exit_with_code_and_message(0, message) end def succeed_without_message(message="done") exit_with_code_and_without_message(0, message) end let!(:rest_client) { MockRestClient.new } before(:each) do user_config @rest_domain = rest_client.add_domain("mock_domain_0") @rest_app = @rest_domain.add_application("mock_app_0", "ruby-1.8.7") end describe 'env help' do let(:arguments) { ['env', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc env $") } end end describe 'env set --help' do [['env', 'set', '--help'], ['env', 'add', '--help'], ['set-env', '--help'], ['env-set', '--help'] ].each_with_index do |args, i| context "help is run run with arguments #{i}" do let(:arguments) { args } it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc env-set ") } end end end describe 'env unset --help' do [['env', 'unset', '--help'], ['env', 'remove', '--help'], ['unset-env', '--help'], ['env-unset', '--help'] ].each_with_index do |args, i| context "help is run run with arguments #{i}" do let(:arguments) { args } it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc env-unset ") } end end end describe 'env list --help' do [['env', 'list', '--help'], ['list-env', '--help'], ['env-list', '--help'] ].each_with_index do |args, i| context "help is run run with arguments #{i}" do let(:arguments) { args } it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc env-list [--namespace NAME]") } end end end describe 'env show --help' do [['env', 'show', '--help'], ['show-env', '--help'], ['env-show', '--help'] ].each_with_index do |args, i| context "help is run run with arguments #{i}" do let(:arguments) { args } it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc env-show ") } end end end describe 'set env' do [['env', 'set', 'TEST_ENV_VAR=1', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['set-env', 'TEST_ENV_VAR=1', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', '-e', 'TEST_ENV_VAR=1', '--app', 'mock_app_0', '--noprompt', '--confirm' ], ['env', 'set', '--env', 'TEST_ENV_VAR=1', '--app', 'mock_app_0', '--noprompt', '--confirm' ], ['env', 'set', 'FOO_BAR=1', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', 'F1=1', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', 'F_=1', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', '_FOO=1', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', 'FOO=BAR=BAZ', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', 'FOO==', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', 'FOO=Test 1 2 3', '--app', 'mock_app_0', '--noprompt', '--confirm'], ].each_with_index do |args, i| context "when run with single env var #{i}" do let(:arguments) { args } it { succeed_with_message /Setting environment variable\(s\) \.\.\./ } it { succeed_with_message /done/ } it { succeed_without_message /TEST_ENV_VAR=1/ } end end [['env', 'set', 'TEST_ENV_VAR1=1', 'TEST_ENV_VAR2=2', 'TEST_ENV_VAR3=3', '--app', 'mock_app_0', '--noprompt', '--confirm' ], ['set-env', 'TEST_ENV_VAR1=1', 'TEST_ENV_VAR2=2', 'TEST_ENV_VAR3=3', '--app', 'mock_app_0', '--noprompt', '--confirm' ], ['set-env', '-e', 'TEST_ENV_VAR1=1', '-e', 'TEST_ENV_VAR2=2', '-e', 'TEST_ENV_VAR3=3', '--app', 'mock_app_0', '--noprompt', '--confirm' ], ['set-env', '--env', 'TEST_ENV_VAR1=1', '--env', 'TEST_ENV_VAR2=2', '--env', 'TEST_ENV_VAR3=3', '--app', 'mock_app_0', '--noprompt', '--confirm' ] ].each_with_index do |args, i| context "when run with multiple env vars #{i}" do let(:arguments) { args } it { succeed_with_message /Setting environment variable\(s\) \.\.\./ } it { succeed_with_message /done/ } it { succeed_without_message /TEST_ENV_VAR1=1/ } it { succeed_without_message /TEST_ENV_VAR2=2/ } it { succeed_without_message /TEST_ENV_VAR3=3/ } end end [['env', 'set', 'TEST_ENV_VAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['set-env', 'TEST_ENV_VAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', '-e', 'TEST_ENV_VAR', '--app', 'mock_app_0', '--noprompt', '--confirm' ], ['env', 'set', '--env', 'TEST_ENV_VAR', '--app', 'mock_app_0', '--noprompt', '--confirm' ], ].each_with_index do |args, i| context "when run with no env var provided #{i}" do let(:arguments) { args } it "should raise env var not provided exception" do expect{ run }.to exit_with_code(159) run_output.should match(/Environment variable\(s\) not provided\./) run_output.should match(/Please provide at least one environment variable using the syntax VARIABLE=VALUE\./) end end end context 'when run with multiple env vars from file' do let(:arguments) {['env', 'set', File.expand_path('../../assets/env_vars.txt', __FILE__), '--app', 'mock_app_0', '--noprompt', '--confirm' ]} it { succeed_with_message /FOO=123/ } it { succeed_with_message /BAR=456/ } it { succeed_with_message /MY_OPENSHIFT_ENV_VAR/ } it { succeed_with_message /MY_EMPTY_ENV_VAR/ } it { succeed_without_message /ZEE/ } it { succeed_without_message /LOL/ } it { succeed_without_message /MUST NOT BE INCLUDED/ } end context 'when run with multiple env vars from multiple files' do let(:arguments) {['env', 'set', '-e', File.expand_path('../../assets/env_vars.txt', __FILE__), '-e', File.expand_path('../../assets/env_vars_2.txt', __FILE__), '--app', 'mock_app_0', '--noprompt', '--confirm' ]} it { succeed_with_message /FOO=123/ } it { succeed_with_message /BAR=456/ } it { succeed_with_message /MY_OPENSHIFT_ENV_VAR/ } it { succeed_with_message /MY_EMPTY_ENV_VAR/ } it { succeed_with_message /AND=123/ } it { succeed_without_message /ZEE/ } it { succeed_without_message /LOL/ } it { succeed_without_message /MUST NOT BE INCLUDED/ } end context 'when run with empty file' do let(:arguments) {['env', 'set', File.expand_path('../../assets/empty.txt', __FILE__), '--app', 'mock_app_0', '--noprompt', '--confirm' ]} it "should raise env var not provided exception" do expect{ run }.to exit_with_code(159) run_output.should match(/Environment variable\(s\) not found in the provided file\(s\)\./) run_output.should match(/Please provide at least one environment variable using the syntax VARIABLE=VALUE\./) end end context 'when run with --noprompt and without --confirm' do let(:arguments) { ['env', 'set', 'TEST_ENV_VAR=1', '--app', 'mock_app_0', '--noprompt' ] } it("should not ask for confirmation") { expect{ run }.to exit_with_code(0) } it("should output confirmation") { run_output.should_not match("This action requires the --confirm option") } end context 'when run with --noprompt and without --confirm from file' do let(:arguments) { ['env', 'set', File.expand_path('../../assets/env_vars.txt', __FILE__), '--app', 'mock_app_0', '--noprompt' ] } it "should ask for confirmation" do expect{ run }.to exit_with_code(1) end it("should output confirmation") { run_output.should match("This action requires the --confirm option") } end context 'when run against an unsupported server' do before { @rest_app.links.delete 'SET_UNSET_ENVIRONMENT_VARIABLES' @rest_app.links.delete 'LIST_ENVIRONMENT_VARIABLES' } let(:arguments) { ['env', 'set', 'TEST_ENV_VAR=1', '--app', 'mock_app_0', '--noprompt', '--confirm' ] } it "should raise env var not found exception" do expect{ run }.to exit_with_code(158) run_output.should match(/Server does not support environment variables/) end end [['env', 'set', 'FOO.Z=BAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', '2FOO=BAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', 'FO$O=BAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', 'FO%O=BAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', 'FO*O=BAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', 'FOO.=BAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', '=BAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'set', '==', '--app', 'mock_app_0', '--noprompt', '--confirm'], ].each_with_index do |args, i| context "when run with invalid env var names #{i}" do let(:arguments) { args } it "should raise env var not provided exception" do expect{ run }.to exit_with_code(159) run_output.should match(/Environment variable\(s\) not provided\./) run_output.should match(/Please provide at least one environment variable using the syntax VARIABLE=VALUE\./) end end end end describe 'unset env' do [['env', 'unset', 'TEST_ENV_VAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['unset-env', 'TEST_ENV_VAR', '--app', 'mock_app_0', '--noprompt', '--confirm'], ['env', 'unset', '-e', 'TEST_ENV_VAR', '--app', 'mock_app_0', '--noprompt', '--confirm' ], ['env', 'unset', '--env', 'TEST_ENV_VAR', '--app', 'mock_app_0', '--noprompt', '--confirm' ] ].each_with_index do |args, i| context "when run with single env var #{i}" do let(:arguments) { args } it { succeed_with_message /TEST_ENV_VAR/ } it { succeed_with_message /Removing environment variable\(s\) \.\.\./ } it { succeed_with_message /done/ } end end [['env', 'unset', 'TEST_ENV_VAR1', 'TEST_ENV_VAR2', 'TEST_ENV_VAR3', '--app', 'mock_app_0', '--noprompt', '--confirm' ], ['unset-env', 'TEST_ENV_VAR1', 'TEST_ENV_VAR2', 'TEST_ENV_VAR3', '--app', 'mock_app_0', '--noprompt', '--confirm' ] ].each_with_index do |args, i| context "when run with multiple env vars #{i}" do let(:arguments) { args } it { succeed_with_message /TEST_ENV_VAR1/ } it { succeed_with_message /TEST_ENV_VAR2/ } it { succeed_with_message /TEST_ENV_VAR3/ } it { succeed_with_message /Removing environment variable\(s\) \.\.\./ } it { succeed_with_message /done/ } end end context 'when run with --noprompt and without --confirm' do let(:arguments) { ['env', 'unset', 'TEST_ENV_VAR', '--app', 'mock_app_0', '--noprompt' ] } it "should ask for confirmation" do expect{ run }.to exit_with_code(1) end it("should output confirmation") { run_output.should match("This action requires the --confirm option") } end end describe 'list env' do context 'when list with default format' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'list', '--app', 'mock_app_0'] } it { succeed_with_message /FOO=123/ } it { succeed_with_message /BAR=456/ } it "should contain the environment variables" do @rest_app.environment_variables.length.should == 2 end end context 'when list with default format and empty env vars' do let(:arguments) { ['env', 'list', '--app', 'mock_app_0'] } it "should exit with no message" do expect{ run }.to exit_with_code(0) end end context 'when list with quotes format' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'list', '--app', 'mock_app_0', '--quotes'] } it { succeed_with_message /FOO="123"/ } it { succeed_with_message /BAR="456"/ } it "should contain the environment variables" do @rest_app.environment_variables.length.should == 2 end end context 'when list with quotes format and empty env vars' do let(:arguments) { ['env', 'list', '--app', 'mock_app_0', '--quotes'] } it "should exit with no message" do expect{ run }.to exit_with_code(0) end end context 'when list with table format' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'list', '--app', 'mock_app_0', '--table'] } it { succeed_with_message /Name\s+Value/ } it { succeed_with_message /FOO\s+123/ } it { succeed_with_message /BAR\s+456/ } it "should contain the right number of env vars" do @rest_app.environment_variables.length.should == 2 end end context 'when list with table and quotes format' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'list', '--app', 'mock_app_0', '--table', '--quotes'] } it { succeed_with_message /Name\s+Value/ } it { succeed_with_message /FOO\s+"123"/ } it { succeed_with_message /BAR\s+"456"/ } it "should contain the right number of env vars" do @rest_app.environment_variables.length.should == 2 end end context 'when list with table format and empty env vars' do let(:arguments) { ['env', 'list', '--app', 'mock_app_0', '--table'] } it "should exit with no message" do expect{ run }.to exit_with_code(0) end end end describe 'show env' do context 'when show with default format' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'show', 'FOO', '--app', 'mock_app_0'] } it { succeed_with_message /FOO=123/ } it "should not contain env vars not specified to show" do run_output.should_not match(/BAR=456/) end it "should contain the right number of env vars" do @rest_app.environment_variables.length.should == 2 end end context 'when show with default format and not found env var' do let(:arguments) { ['env', 'show', 'FOO', '--app', 'mock_app_0'] } it "should raise env var not found exception" do expect{ run }.to exit_with_code(157) run_output.should match(/Environment variable\(s\) FOO can't be found in application mock_app_0/) end end context 'when show with default format and not found env var' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'show', 'ZEE', '--app', 'mock_app_0'] } it "should contain the right number of env vars" do @rest_app.environment_variables.length.should == 2 end it "should not contain env vars not specified to show" do run_output.should_not match(/FOO=123/) run_output.should_not match(/BAR=456/) end it "should raise env var not found exception" do expect{ run }.to exit_with_code(157) run_output.should match(/Environment variable\(s\) ZEE can't be found in application mock_app_0/) end end context 'when show with quotes format' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'show', 'FOO', '--app', 'mock_app_0', '--quotes'] } it { succeed_with_message /FOO="123"/ } it "should not contain env vars not specified to show" do run_output.should_not match(/BAR="456"/) end it "should contain the right number of env vars" do @rest_app.environment_variables.length.should == 2 end end context 'when show with quotes format and not found env var' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'show', 'ZEE', '--app', 'mock_app_0', '--quotes'] } it "should contain the right number of env vars" do @rest_app.environment_variables.length.should == 2 end it "should not contain env vars not specified to show" do run_output.should_not match(/FOO=123/) run_output.should_not match(/BAR=456/) end it "should raise env var not found exception" do expect{ run }.to exit_with_code(157) run_output.should match(/Environment variable\(s\) ZEE can't be found in application mock_app_0/) end end context 'when show with table format' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'show', 'FOO', '--app', 'mock_app_0', '--table'] } it { succeed_with_message /Name\s+Value/ } it { succeed_with_message /FOO\s+123/ } it "should not contain env vars not specified to show" do run_output.should_not match(/BAR/) end it "should contain the right number of env vars" do @rest_app.environment_variables.length.should == 2 end end context 'when show with table and quotes format' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'show', 'FOO', '--app', 'mock_app_0', '--table', '--quotes'] } it { succeed_with_message /Name\s+Value/ } it { succeed_with_message /FOO\s+"123"/ } it "should not contain env vars not specified to show" do run_output.should_not match(/BAR/) end it "should contain the right number of env vars" do @rest_app.environment_variables.length.should == 2 end end context 'when show with table format and not found env var' do before(:each) do @rest_app.set_environment_variables( [RHC::Rest::EnvironmentVariable.new({:name => 'FOO', :value => '123'}), RHC::Rest::EnvironmentVariable.new({:name => 'BAR', :value => '456'})]) end let(:arguments) { ['env', 'show', 'ZEE', '--app', 'mock_app_0', '--table'] } it "should contain the right number of env vars" do @rest_app.environment_variables.length.should == 2 end it "should not contain env vars not specified to show" do run_output.should_not match(/FOO/) run_output.should_not match(/BAR/) end it "should raise env var not found exception" do expect{ run }.to exit_with_code(157) run_output.should match(/Environment variable\(s\) ZEE can't be found in application mock_app_0/) end end end end rhc-1.38.7/spec/rhc/commands/domain_spec.rb0000644000004100000410000003733712756270552020563 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/domain' describe RHC::Commands::Domain do let(:rest_client){ MockRestClient.new } before{ user_config } describe 'default action' do before{ rest_client } context 'when run with no domains' do let(:arguments) { ['domain'] } it { expect { run }.to exit_with_code(1) } it { run_output.should match(/To create your first domain.*rhc create-domain/m) } end context 'when help is shown' do let(:arguments) { ['domain', '--noprompt', '--help'] } it { expect { run }.to exit_with_code(0) } it { run_output.should match(/configure.*delete.*leave.*list.*rename/m) } end end describe 'show' do before{ rest_client } let(:arguments) { ['domain', 'show'] } context 'when run with no domains' do it { expect { run }.to exit_with_code(1) } it { run_output.should match(/In order to deploy applications, you must create a domain/m) } end context 'when run with one domain no apps' do before{ rest_client.add_domain("onedomain") } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("The domain onedomain exists but has no applications. You can use") end end context 'when run with multiple domain no apps' do before do rest_client.add_domain("firstdomain") rest_client.add_domain("seconddomain") end it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("The domain firstdomain exists but has no applications. You can use") output.should_not match("Applications in seconddomain") end end context 'when run with one domain multiple apps' do before do d = rest_client.add_domain("appdomain") a = d.add_application("app_no_carts", "testframework-1.0") a = d.add_application("app_multi_carts", "testframework-1.0") a.add_cartridge("testcart-1") a.add_cartridge("testcart-2") a.add_cartridge("testcart-3") end it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("app_no_carts") output.should match("app_multi_carts") output.should match("testframework-1.0") output.should match("testcart-1") output.should match("testcart-2") output.should match("testcart-3") end end context 'when run with an app without cartridges' do before do d = rest_client.add_domain("appdomain") a = d.add_application("app_no_carts") end it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("app_no_carts") output.should match(/127.0.0.1\s*$/m) end end end describe 'list' do before{ rest_client } let(:arguments) { ['domain', 'list'] } context 'when run with no domains' do it { expect { run }.to exit_with_code(1) } it { run_output.should match(/In order to deploy applications.*rhc create-domain/) } end context 'when run with one domain no apps' do before{ rest_client.add_domain("onedomain") } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("You have access to 1 domain\\.") output.should match("onedomain") end context 'when has no creation date, members, or allowed_gear_sizes' do before{ rest_client.domains.first.attributes.merge(:creation_date => nil, :allowed_gear_sizes => nil, :members => nil, :id => '123') } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("onedomain") output.should_not match ("Allowed Gear Sizes:") output.should_not match ("Created:") output.should_not match ("Members:") output.should_not match ("ID:.*") end end context 'when an ID is present and differs from name' do let(:arguments) { ['domain', 'list', '--ids'] } before{ rest_client.domains.first.attributes['id'] = '123' } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("onedomain") output.should match ("ID:.*123") end end context 'when an ID is present and identical to name' do let(:arguments) { ['domain', 'list', '--ids'] } before{ rest_client.domains.first.attributes['id'] = 'onedomain' } it { expect { run }.to exit_with_code(0) } it "should not match output" do run_output.should_not match ("ID:.*123") end end context 'when an ID is present and name is not' do let(:arguments) { ['domain', 'list', '--ids'] } before{ rest_client.domains.first.attributes['id'] = 'onedomain' } before{ rest_client.domains.first.instance_variable_set(:@name, nil) } it { expect { run }.to exit_with_code(0) } it "should match output" do run_output.should match ("onedomain") run_output.should_not match ("ID:.*") end end end context 'when run with one owned domain' do let(:arguments) { ['domains', '--mine'] } before{ d = rest_client.add_domain('mine', true); rest_client.stub(:owned_domains).and_return([d]) } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("You have access to 1 domain\\.") output.should match("mine") output.should match("Created") output.should match("Allowed Gear Sizes: small") end end context 'when run with multiple domains and extra domain info' do before do rest_client.add_domain("firstdomain") rest_client.add_domain("seconddomain", true) end it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("You have access to 2 domains") output.should match("seconddomain \\(owned by a_user_name\\)") output.should match("Created") output.should match("Allowed Gear Sizes: small") end end end describe 'create' do before{ rest_client } let(:arguments) { ['domain', 'create', 'testnamespace'] } context 'when no issues with ' do it "should create a domain" do expect { run }.to exit_with_code(0) rest_client.domains[0].name.should == 'testnamespace' end it { run_output.should match(/Creating.*'testnamespace'.*done/m) } end end describe 'rename' do before{ rest_client } let(:arguments) { ['domain', 'rename', 'olddomain', 'alterednamespace'] } context 'when no issues with ' do before{ rest_client.add_domain("olddomain") } it "should update a domain" do expect { run }.to exit_with_code(0) rest_client.domains[0].name.should == 'alterednamespace' end it { run_output.should match(/Renaming domain 'olddomain' to 'alterednamespace'.*done.*?Applications created in this domain will use the new name in their URL./m) } end context 'when there is no domain' do it "should not create a domain" do expect { run }.to exit_with_code(127) rest_client.domains.empty?.should be_true end it { run_output.should match("not found") } end end describe 'update' do before{ rest_client } let(:arguments) { ['domain', 'update', 'olddomain', 'alterednamespace'] } before{ rest_client.add_domain("olddomain") } it "should update a domain" do expect { run }.to exit_with_code(0) rest_client.domains[0].name.should == 'alterednamespace' end it { run_output.should match(/This command is deprecated.*Renaming domain 'olddomain' to 'alterednamespace'.*done.*?Applications created in this domain will use the new name in their URL./m) } end describe 'alter alias has been removed' do let(:arguments) { ['domain', 'alter', 'olddomain', 'alterednamespace'] } it{ expect { run }.to exit_with_code(1) } end describe 'configure' do context "no settings" do before{ rest_client.add_domain("domain1") } let(:arguments) { ['domain', 'configure', '-n', 'domain1'] } it("should succeed"){ expect { run }.to exit_with_code(0) } it("should display the domain config"){ run_output.should match(/Domain domain1 configuration/m) } it("should not display the domain config without gear sizes"){ run_output.should_not match(/Allowed Gear Sizes/) } context "server supports allowed gear sizes" do before{ rest_client.domains.first.should_receive(:allowed_gear_sizes).and_return([]) } it("should display the domain config"){ run_output.should match(/Domain domain1 configuration.*Allowed Gear Sizes:\s+/m) } end context "server supports allowed gear sizes" do before{ rest_client.domains.first.should_receive(:allowed_gear_sizes).and_return(['small', 'medium']) } it("should display the domain config"){ run_output.should match(/Domain domain1 configuration.*Allowed Gear Sizes:\s+small, medium/m) } end end context "when server does not support allowed-gear-sizes" do before do rest_client.add_domain("domain1") rest_client.api.should_receive(:has_param?).with(:add_domain, 'allowed_gear_sizes').and_return(false) end let(:arguments) { ['domain', 'configure', '--allowed-gear-sizes', 'small'] } it("display a message"){ run_output.should match 'The server does not support --allowed-gear-sizes' } end context "against a server that supports gear sizes" do let(:username){ 'test_user' } let(:password){ 'password' } let(:server){ 'test.domain.com' } let(:supports_allowed_gear_sizes?){ true } before{ subject.class.any_instance.stub(:namespace_context).and_return('domain1') } before do stub_api challenge{ stub_one_domain('domain1', nil, mock_user_auth) } end context "with --allowed-gear-sizes singular" do before do stub_api_request(:put, "domains/domain1/update", nil). with(:body => {:allowed_gear_sizes => ['valid']}). to_return({:body => {:type => 'domain', :data => {:name => 'domain1', :allowed_gear_sizes => ['valid']}, :messages => [{:severity => 'info', :text => 'Updated allowed gear sizes'},]}.to_json, :status => 200}) end let(:arguments) { ['domain', 'configure', '--trace', '--allowed-gear-sizes', 'valid'] } it("should succeed"){ expect { run }.to exit_with_code(0) } it("should display the domain config"){ run_output.should match(/Domain domain1 configuration.*Allowed Gear Sizes:\s+valid/m) } end context "with --allowed-gear-sizes multiple" do before do stub_api_request(:put, "domains/domain1/update", nil). with(:body => {:allowed_gear_sizes => ['one', 'two']}). to_return({:body => {:type => 'domain', :data => {:name => 'domain1', :allowed_gear_sizes => ['one', 'two']}, :messages => [{:severity => 'info', :text => 'Updated allowed gear sizes'},]}.to_json, :status => 200}) end let(:arguments) { ['domain', 'configure', '--trace', '--allowed-gear-sizes', 'one,two'] } it("should succeed"){ expect { run }.to exit_with_code(0) } it("should display the domain config"){ run_output.should match(/Domain domain1 configuration.*Allowed Gear Sizes:\s+one, two/m) } end context "with --allowed-gear-sizes" do let(:arguments) { ['domain', 'configure', 'domain1', '--trace', '--allowed-gear-sizes'] } it("raise an invalid option"){ expect{ run }.to raise_error(OptionParser::InvalidOption, /Provide a comma delimited list of valid gear/) } end context "with --allowed-gear-sizes=false" do before do stub_api_request(:put, "domains/domain1/update", nil). with(:body => {:allowed_gear_sizes => ['false']}). to_return({:body => {:type => 'domain', :messages => [{:field => 'allowed_gear_sizes', :exit_code => 10, :severity => 'error', :text => 'The specified gear size is invalid: false'},]}.to_json, :status => 422}) end let(:arguments) { ['domain', 'configure', '--allowed-gear-sizes=false'] } it("should succeed"){ expect { run }.to exit_with_code(1) } it("should display the domain config"){ run_output.should match(/Updating domain configuration.*The specified gear size is invalid/m) } end context "with --no-allowed-gear-sizes" do before do stub_api_request(:put, "domains/domain1/update", nil). with(:body => {:allowed_gear_sizes => []}). to_return({:body => {:type => 'domain', :data => {:name => 'domain1', :allowed_gear_sizes => []}, :messages => [{:severity => 'info', :text => 'Updated allowed gear sizes'},]}.to_json, :status => 200}) end let(:arguments) { ['domain', 'configure', '--no-allowed-gear-sizes'] } it("should succeed"){ expect { run }.to exit_with_code(0) } it("should display the domain config"){ run_output.should match(/Domain domain1 configuration.*Allowed Gear Sizes:\s+/m) } end context "with --allowed-gear-sizes and --no-allowed-gear-sizes" do let(:arguments) { ['domain', 'configure', 'domain1', '--trace', '--no-allowed-gear-sizes', '--allowed-gear-sizes', 'small'] } it("raise an invalid option"){ expect{ run }.to raise_error(OptionParser::InvalidOption, /--allowed-gear-sizes.*--no-allowed-gear-sizes/) } end end end describe 'leave' do before{ rest_client.add_domain("deleteme") } let(:arguments) { ['domain', 'leave', '-n', 'deleteme'] } it "should leave the domain" do rest_client.domains.first.should_receive(:leave).and_return(RHC::Rest::Membership::Member.new) expect { run }.to exit_with_code(0) end end describe 'delete' do before{ rest_client } let(:arguments) { ['domain', 'delete', 'deleteme'] } context 'when no issues with ' do before{ rest_client.add_domain("deleteme") } it "should delete a domain" do expect { run }.to exit_with_code(0) rest_client.domains.empty?.should be_true end end context 'when there is a different domain' do before{ rest_client.add_domain("dontdelete") } it "should error out" do expect { run }.to exit_with_code(127) rest_client.domains[0].name.should == 'dontdelete' end it { run_output.should match("Domain deleteme not found") } end context 'when there are applications on the domain' do before do domain = rest_client.add_domain("deleteme") domain.add_application 'testapp1', 'mock-1.0' end it "should error out" do expect { run }.to exit_with_code(1) rest_client.domains[0].name.should == 'deleteme' end it { run_output.should match("Applications must be empty") } end context 'when delete is forced' do let(:arguments) { ['domain', 'delete', 'deleteme', '--force'] } before do domain = rest_client.add_domain("deleteme") domain.add_application 'testapp1', 'mock-1.0' end it "should delete successfully" do expect { run }.to exit_with_code(0) rest_client.domains.empty?.should be_true end end end describe 'help' do let(:arguments) { ['domain', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc domain") } end end end rhc-1.38.7/spec/rhc/commands/apps_spec.rb0000644000004100000410000000632112756270552020244 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/apps' describe RHC::Commands::Apps do before{ user_config } let!(:rest_client){ MockRestClient.new } describe 'run' do context 'when no domains' do let(:arguments) { ['apps'] } it { expect { run }.to exit_with_code(1) } it { run_output.should match(/In order to deploy applications.*rhc create-domain/) } end context 'with a domain' do let(:arguments){ ['apps'] } let!(:domain){ rest_client.add_domain("first") } it { expect { run }.to exit_with_code(1) } it { run_output.should match(/No applications.*rhc create-app/) } context 'with apps' do let(:arguments) { ['apps'] } before{ domain.add_application('scaled', 'php', true) } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("You have access to 1 application\\.") output.should match(/scaled.*\-\-.*php.*Scaling:.*x2 \(minimum/m) end end context 'with apps in summary mode' do let(:arguments) { ['apps', '--summary' ] } before{ domain.add_application('scaled', 'php', true) } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("You have access to 1 application\\.") output.should match(/scaled.*https.*/m) end end context 'with one owned app' do let(:arguments) { ['apps', '--mine'] } before{ a = domain.add_application('scaled', 'php', true); rest_client.stub(:owned_applications).and_return([a]) } it { expect { run }.to exit_with_code(0) } it "should match output" do output = run_output output.should match("You have 1 application\\.") output.should match(/scaled.*\-\-.*php.*Scaling:.*x2 \(minimum/m) end end end context 'when help is shown' do let(:arguments) { ['apps', '--help'] } it { expect { run }.to exit_with_code(0) } it { run_output.should match(/rhc apps.*Display the list of applications/m) } end context 'when run verbose with custom external cartridges' do let(:arguments) { ['apps', '-v'] } before do @domain = rest_client.add_domain("mockdomain") app = @domain.add_application("app1", "mock_type") cart1 = app.add_cartridge('mock_cart-1') cart1.url = 'https://foo.bar.com' cart1.tags = ['external'] cart1.version = '2' cart1.license = 'GPL' cart1.website = 'http://bar.com' cart1.current_scale = 0 end it { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") } it { run_output.should match(/Gears:\s+1 small/) } it { run_output.should match(/Gears:\s+none \(external service\)/) } it { run_output.should match(/Description:\s+Description of mock_cart-1/) } it { run_output.should match(%r(Website:\s+ http://bar.com)) } it { run_output.should match(/Version:\s+2/) } it { run_output.should match(/License:\s+GPL/) } it { run_output.should match(%r(From:\s+ https://foo.bar.com)) } end end end rhc-1.38.7/spec/rhc/commands/alias_spec.rb0000644000004100000410000003513112756270552020373 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/alias' require 'rhc/config' describe RHC::Commands::Alias do let(:client_links) { mock_response_links(mock_client_links) } let(:domain_0_links) { mock_response_links(mock_domain_links('mock_domain_0')) } let(:domain_1_links) { mock_response_links(mock_domain_links('mock_domain_1')) } let(:app_0_links) { mock_response_links(mock_app_links('mock_domain_0', 'mock_app_0')) } let(:alias_0_links) { mock_response_links(mock_alias_links('mock_domain_0', 'mock_app_0', 'www.foo.bar')) } let!(:rest_client){ MockRestClient.new } before(:each) do user_config domain = rest_client.add_domain("mock_domain_0") domain.add_application("mock_app_0", "ruby-1.8.7").add_alias("www.foo.bar") domain.add_application("mock_app_1", "ruby-1.8.7") stub_api_request(:any, client_links['LIST_DOMAINS']['relative']). to_return({ :body => { :type => 'domains', :data => [{ :id => 'mock_domain_0', :links => mock_response_links(mock_domain_links('mock_domain_0')), }, { :id => 'mock_domain_1', :links => mock_response_links(mock_domain_links('mock_domain_1')), }] }.to_json, :status => 200 }) stub_api_request(:any, domain_0_links['LIST_APPLICATIONS']['relative']). to_return({ :body => { :type => 'applications', :data => [{ :domain_id => 'mock_domain_0', :name => 'mock_app_0', :creation_time => Time.new.to_s, :uuid => 1234, :aliases => ['alias_1', 'alias_2'], :server_identity => 'mock_server_identity', :links => mock_response_links(mock_app_links('mock_domain_0','mock_app_0')), }] }.to_json, :status => 200 }) stub_api_request(:any, app_0_links['ADD_ALIAS']['relative'], false). with(:body => {:event => 'add-alias', :alias => 'www.foo.bar'}). to_return({ :body => { :type => 'application', :data => { :domain_id => 'mock_domain_1', :name => 'mock_app_0', :creation_time => Time.new.to_s, :uuid => 1234, :aliases => ['alias_1', 'alias_2'], :server_identity => 'mock_server_identity', :links => mock_response_links(mock_app_links('mock_domain_1','mock_app_0')), }, :messages => [{:text => "RESULT:\nApplication event 'add-alias' successful"}] }.to_json, :status => 200 }) stub_api_request(:any, app_0_links['REMOVE_ALIAS']['relative'], false). with(:body => {:event => 'remove-alias', :alias => 'www.foo.bar'}). to_return({ :body => { :type => 'application', :data => { :domain_id => 'mock_domain_1', :name => 'mock_app_0', :creation_time => Time.new.to_s, :uuid => 1234, :aliases => ['alias_1', 'alias_2'], :server_identity => 'mock_server_identity', :links => mock_response_links(mock_app_links('mock_domain_1','mock_app_0')), }, :messages => [{:text => "RESULT:\nApplication event 'remove-alias' successful"}] }.to_json, :status => 200 }) stub_api_request(:any, app_0_links['LIST_ALIASES']['relative'], false). to_return({ :body => { :type => 'aliases', :data => [{ :domain_id => 'mock_domain_0', :application_id => 'mock_app_0', :id => 'www.foo.bar', :certificate_added_at => nil, :has_private_ssl_certificate => false, :links => mock_response_links(mock_alias_links('mock_domain_0','mock_app_0', 'www.foo.bar')), }] }.to_json, :status => 200 }) end describe 'alias help' do let(:arguments) { ['alias', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc alias $") } end end describe 'alias add --help' do let(:arguments) { ['alias', 'add', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc alias-add [--namespace NAME]") } end end describe 'alias remove --help' do let(:arguments) { ['alias', 'remove', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc alias-remove [--namespace NAME]") } end end describe 'alias update-cert --help' do let(:arguments) { ['alias', 'update-cert', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc alias-update-cert --certificate FILE --private-key FILE [--passphrase passphrase]") } end end describe 'alias delete-cert --help' do let(:arguments) { ['alias', 'delete-cert', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc alias-delete-cert ") } end end describe 'alias list --help' do let(:arguments) { ['alias', 'list', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc alias-list ") } end end describe 'add alias' do let(:arguments) { ['alias', 'add', 'mock_app_0', 'www.foo.bar' ] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Alias 'www.foo.bar' has been added/m } end describe 'add alias with implicit context' do before{ subject.class.any_instance.stub(:git_config_get){ |key| case key; when 'rhc.app-name' then 'mock_app_0'; when 'rhc.domain-name' then 'mock_domain_0'; end } } let(:arguments) { ['alias', 'add', '--', 'www.foo.bar' ] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Alias 'www.foo.bar' has been added/m } end describe 'remove alias' do before do rest_client.stub(:api_version_negotiated).and_return(1.4) end context 'remove alias successfully' do let(:arguments) { ['alias', 'remove', 'mock_app_0', 'www.foo.bar' ] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Alias 'www.foo.bar' has been removed/m } end context 'remove alias with server api <= 1.3' do let(:arguments) { ['alias', 'remove', 'mock_app_0', 'www.foo.bar' ] } before do rest_client.stub(:api_version_negotiated).and_return(1.3) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Alias 'www.foo.bar' has been removed/m } end end describe 'alias update-cert' do before do rest_client.stub(:api_version_negotiated).and_return(1.4) end context 'add valid certificate with valid private key without pass phrase' do let(:arguments) { ['alias', 'update-cert', 'mock_app_0', 'www.foo.bar', '--certificate', File.expand_path('../../assets/cert.crt', __FILE__), '--private-key', File.expand_path('../../assets/cert_key_rsa', __FILE__) ] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /SSL certificate successfully added/m } end context 'cert file not found' do let(:arguments) { ['alias', 'update-cert', 'mock_app_0', 'www.foo.bar', '--certificate', File.expand_path('../../assets/nothing.foo', __FILE__), '--private-key', File.expand_path('../../assets/cert_key_rsa', __FILE__) ] } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Certificate file not found/m } end context 'private key file not found' do let(:arguments) { ['alias', 'update-cert', 'mock_app_0', 'www.foo.bar', '--certificate', File.expand_path('../../assets/cert.crt', __FILE__), '--private-key', File.expand_path('../../assets/nothing.foo', __FILE__) ] } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Private key file not found/m } end context 'not existing certificate alias' do let(:arguments) { ['alias', 'update-cert', 'mock_app_0', 'www.unicorns.com', '--certificate', File.expand_path('../../assets/cert.crt', __FILE__), '--private-key', File.expand_path('../../assets/cert_key_rsa', __FILE__) ] } it { expect { run }.to exit_with_code(156) } it { run_output.should =~ /Alias www.unicorns.com can't be found in application/m } end context 'fails if server does not support' do let(:arguments) { ['alias', 'update-cert', 'mock_app_0', 'www.foo.bar', '--certificate', File.expand_path('../../assets/cert.crt', __FILE__), '--private-key', File.expand_path('../../assets/cert_key_rsa', __FILE__) ] } before do rest_client.stub(:api_version_negotiated).and_return(1.3) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /The server does not support SSL certificates for custom aliases/m } end context 'invalid certificate file (empty)' do let(:arguments) { ['alias', 'update-cert', 'mock_app_0', 'www.foo.bar', '--certificate', File.expand_path('../../assets/empty.txt', __FILE__), '--private-key', File.expand_path('../../assets/cert_key_rsa', __FILE__) ] } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Invalid certificate file/m } end context 'invalid private key file (empty)' do let(:arguments) { ['alias', 'update-cert', 'mock_app_0', 'www.foo.bar', '--certificate', File.expand_path('../../assets/cert.crt', __FILE__), '--private-key', File.expand_path('../../assets/empty.txt', __FILE__) ] } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Invalid private key file/m } end end describe 'alias delete-cert' do before do rest_client.stub(:api_version_negotiated).and_return(1.4) end context 'delete existing certificate' do let(:arguments) { ['alias', 'delete-cert', 'mock_app_0', 'www.foo.bar', '--confirm'] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /SSL certificate successfully deleted/m } end context 'delete not existing certificate' do let(:arguments) { ['alias', 'delete-cert', 'mock_app_0', 'www.unicorns.com', '--confirm'] } it { expect { run }.to exit_with_code(156) } it { run_output.should =~ /Alias www.unicorns.com can't be found in application mock_app_0/m } end context 'fails if server does not support' do let(:arguments) { ['alias', 'delete-cert', 'mock_app_0', 'www.foo.bar', '--confirm'] } before do rest_client.stub(:api_version_negotiated).and_return(1.3) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /The server does not support SSL certificates for custom aliases/m } end end describe 'alias list' do before do rest_client.stub(:api_version_negotiated).and_return(1.4) end context 'list app with existing certificate' do let(:arguments) { ['alias', 'list', 'mock_app_0'] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Has Certificate?/m } it { run_output.should =~ /Certificate Added/m } it { run_output.should =~ /www.foo.bar/m } end context 'list app without certificates' do let(:arguments) { ['alias', 'list', 'mock_app_1'] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /No aliases associated with the application mock_app_1/m } end context 'simple list is server does not support ssl certs' do let(:arguments) { ['alias', 'list', 'mock_app_0'] } before do rest_client.stub(:api_version_negotiated).and_return(1.3) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /no/m } it { run_output.should =~ /-/m } it { run_output.should =~ /www.foo.bar/m } end end describe 'aliases' do before do rest_client.stub(:api_version_negotiated).and_return(1.4) end context 'app with existing certificate' do let(:arguments) { ['aliases', 'mock_app_0'] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Has Certificate?/m } it { run_output.should =~ /Certificate Added/m } it { run_output.should =~ /www.foo.bar/m } end context 'app without certificates' do let(:arguments) { ['aliases', 'mock_app_1'] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /No aliases associated with the application mock_app_1/m } end context 'simple list is server does not support ssl certs' do let(:arguments) { ['aliases', 'mock_app_0'] } before do rest_client.stub(:api_version_negotiated).and_return(1.3) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /no/m } it { run_output.should =~ /-/m } it { run_output.should =~ /www.foo.bar/m } end end end rhc-1.38.7/spec/rhc/commands/deployment_spec.rb0000644000004100000410000003455212756270552021470 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/deployment' describe RHC::Commands::Deployment do DEPLOYMENT_APP_NAME = 'mock_app_deploy' let!(:rest_client) { MockRestClient.new } before do user_config @rest_app = rest_client.add_domain("mock_domain").add_application(DEPLOYMENT_APP_NAME, 'ruby-1.8.7') @rest_app.stub(:ssh_url).and_return("ssh://user@test.domain.com") @targz_filename = File.dirname(__FILE__) + '/' + DEPLOYMENT_APP_NAME + '.tar.gz' FileUtils.cp(File.expand_path('../../assets/targz_sample.tar.gz', __FILE__), @targz_filename) File.chmod 0644, @targz_filename unless File.executable? @targz_filename end after do File.delete @targz_filename if File.exist? @targz_filename end describe "configure app" do context "manual deployment keeping a history of 10" do let(:arguments) {['app', 'configure', '--app', DEPLOYMENT_APP_NAME, '--no-auto-deploy', '--keep-deployments', '10']} it "should succeed" do expect{ run }.to exit_with_code(0) run_output.should match(/Configuring application '#{DEPLOYMENT_APP_NAME}' .../) run_output.should match(/done/) @rest_app.auto_deploy.should == false @rest_app.keep_deployments.should == 10 run_output.should match(/Your application '#{DEPLOYMENT_APP_NAME}' is now configured as listed above/) end end context "with no configuration options" do let(:arguments) {['app', 'configure', '--app', DEPLOYMENT_APP_NAME]} it "should display the current configuration" do expect{ run }.to exit_with_code(0) run_output.should_not match(/Configuring application '#{DEPLOYMENT_APP_NAME}' .../) run_output.should_not match(/done/) run_output.should match(/Your application '#{DEPLOYMENT_APP_NAME}' is configured as listed above/) end end end describe "deploy" do context "git ref successfully" do before { Net::SSH.should_receive(:start).exactly(3).times.with('test.domain.com', 'user', :compression=>false) } let(:arguments) {['app', 'deploy', 'master', '--app', DEPLOYMENT_APP_NAME]} it "should succeed" do expect{ run }.to exit_with_code(0) run_output.should match(/Deployment of git ref 'master' in progress for application #{DEPLOYMENT_APP_NAME} .../) run_output.should match(/Success/) end end context "binary file successfully" do before do @rest_app.stub(:deployment_type).and_return('binary') ssh = double(Net::SSH) session = double(Net::SSH::Connection::Session) channel = double(Net::SSH::Connection::Channel) exit_status = double(Net::SSH::Buffer) exit_status.stub(:read_long).and_return(0) Net::SSH.should_receive(:start).exactly(3).times.with('test.domain.com', 'user', :compression=>false).and_yield(session) session.should_receive(:open_channel).exactly(3).times.and_yield(channel) channel.should_receive(:exec).exactly(3).times.with("oo-binary-deploy").and_yield(nil, nil) channel.should_receive(:on_data).exactly(3).times.and_yield(nil, 'foo') channel.should_receive(:on_extended_data).exactly(3).times.and_yield(nil, nil, '') channel.should_receive(:on_close).exactly(3).times.and_yield(nil) channel.should_receive(:on_request).exactly(3).times.with("exit-status").and_yield(nil, exit_status) lines = '' File.open(@targz_filename, 'rb') do |file| file.chunk(1024) do |chunk| lines << chunk end end channel.should_receive(:send_data).exactly(3).times.with(lines) channel.should_receive(:eof!).exactly(3).times session.should_receive(:loop).exactly(3).times end let(:arguments) {['app', 'deploy', @targz_filename, '--app', DEPLOYMENT_APP_NAME]} it "should succeed" do expect{ run }.to exit_with_code(0) run_output.should match(/Deployment of file '#{@targz_filename}' in progress for application #{DEPLOYMENT_APP_NAME} .../) run_output.should match(/Success/) end end [URI('http://foo.com/path/to/file/' + DEPLOYMENT_APP_NAME + '.tar.gz'), URI('https://foo.com/path/to/file/' + DEPLOYMENT_APP_NAME + '.tar.gz')].each do |uri| context "url file successfully" do before do @rest_app.stub(:deployment_type).and_return('binary') ssh = double(Net::SSH) session = double(Net::SSH::Connection::Session) channel = double(Net::SSH::Connection::Channel) exit_status = double(Net::SSH::Buffer) exit_status.stub(:read_long).and_return(0) Net::SSH.should_receive(:start).exactly(3).times.with('test.domain.com', 'user', :compression=>false).and_yield(session) session.should_receive(:open_channel).exactly(3).times.and_yield(channel) channel.should_receive(:exec).exactly(3).times.with("oo-binary-deploy").and_yield(nil, nil) channel.should_receive(:on_data).exactly(3).times.and_yield(nil, 'foo') channel.should_receive(:on_extended_data).exactly(3).times.and_yield(nil, nil, '') channel.should_receive(:on_close).exactly(3).times.and_yield(nil) channel.should_receive(:on_request).exactly(3).times.with("exit-status").and_yield(nil, exit_status) lines = '' File.open(@targz_filename, 'rb') do |file| file.chunk(1024) do |chunk| lines << chunk end end stub_request(:get, uri.to_s).to_return(:status => 200, :body => lines, :headers => {}) channel.should_receive(:send_data).exactly(3).times.with(lines) channel.should_receive(:eof!).exactly(3).times session.should_receive(:loop).exactly(3).times end let(:arguments) {['app', 'deploy', uri.to_s, '--app', DEPLOYMENT_APP_NAME]} it "should succeed" do expect{ run }.to exit_with_code(0) run_output.should match(/Deployment of file '#{uri.to_s}' in progress for application #{DEPLOYMENT_APP_NAME} .../) run_output.should match(/Success/) end end end context "binary file with corrupted file" do before do @rest_app.stub(:deployment_type).and_return('binary') ssh = double(Net::SSH) session = double(Net::SSH::Connection::Session) channel = double(Net::SSH::Connection::Channel) exit_status = double(Net::SSH::Buffer) exit_status.stub(:read_long).and_return(255) Net::SSH.should_receive(:start).exactly(3).times.with('test.domain.com', 'user', :compression=>false).and_yield(session) session.should_receive(:open_channel).exactly(3).times.and_yield(channel) channel.should_receive(:exec).exactly(3).times.with("oo-binary-deploy").and_yield(nil, nil) channel.should_receive(:on_data).exactly(3).times.and_yield(nil, 'foo') channel.should_receive(:on_extended_data).exactly(3).times.and_yield(nil, nil, 'Invalid file') channel.should_receive(:on_close).exactly(3).times.and_yield(nil) channel.should_receive(:on_request).exactly(3).times.with("exit-status").and_yield(nil, exit_status) lines = '' File.open(@targz_filename, 'rb') do |file| file.chunk(1024) do |chunk| lines << chunk end end channel.should_receive(:send_data).exactly(3).times.with(lines) channel.should_receive(:eof!).exactly(3).times session.should_receive(:loop).exactly(3).times end let(:arguments) {['app', 'deploy', @targz_filename, '--app', DEPLOYMENT_APP_NAME]} it "should not succeed" do expect{ run }.to exit_with_code(133) run_output.should match(/Deployment of file '#{@targz_filename}' in progress for application #{DEPLOYMENT_APP_NAME} .../) run_output.should match(/Invalid file/) end end context "fails when deploying git ref" do before (:each) { Net::SSH.should_receive(:start).and_raise(Errno::ECONNREFUSED) } let(:arguments) {['app', 'deploy', 'master', '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(1) end end context "fails when deploying binary file" do before (:each) do @rest_app.stub(:deployment_type).and_return('binary') Net::SSH.should_receive(:start).and_raise(Errno::ECONNREFUSED) end let(:arguments) {['app', 'deploy', @targz_filename, '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(1) end end context "fails when deploying binary file" do before (:each) do @rest_app.stub(:deployment_type).and_return('binary') Net::SSH.should_receive(:start).and_raise(SocketError) end let(:arguments) {['app', 'deploy', @targz_filename, '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(1) end end context "fails when deploying url file" do before (:each) do @rest_app.stub(:deployment_type).and_return('binary') Net::SSH.should_receive(:start).and_raise(Errno::ECONNREFUSED) end let(:arguments) {['app', 'deploy', 'http://foo.com/deploy.tar.gz', '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(1) end end context "fails when deploying url file" do before (:each) do @rest_app.stub(:deployment_type).and_return('binary') Net::SSH.should_receive(:start).and_raise(SocketError) end let(:arguments) {['app', 'deploy', 'http://foo.com/deploy.tar.gz', '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(1) end end context 'when run against an unsupported server' do before { @rest_app.links.delete 'UPDATE' @rest_app.links.delete 'DEPLOY' } let(:arguments) {['app', 'deploy', 'master', '--app', DEPLOYMENT_APP_NAME]} it "should raise not supported exception" do expect{ run }.to exit_with_code(132) run_output.should match(/The server does not support deployments/) end end context "ssh authentication failure" do before (:each) { Net::SSH.should_receive(:start).exactly(2).times.and_raise(Net::SSH::AuthenticationFailed) } let(:arguments) {['app', 'deploy', 'master', '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(1) run_output.should match(/Authentication to server test.domain.com with user user failed/) end end context "fails when deploying git reference on an app configured to deployment_type = binary" do before { @rest_app.stub(:deployment_type).and_return('binary') } let(:arguments) {['app', 'deploy', 'master', '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(133) end end context "fails when deploying file on an app configured to deployment_type = git" do before { @rest_app.stub(:deployment_type).and_return('git') } let(:arguments) {['app', 'deploy', @targz_filename, '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(133) end end [URI('http://foo.com/path/to/file/' + DEPLOYMENT_APP_NAME + '.tar.gz'), URI('https://foo.com/path/to/file/' + DEPLOYMENT_APP_NAME + '.tar.gz')].each do |uri| context "fails when deploying url on an app configured to deployment_type = git" do before { @rest_app.stub(:deployment_type).and_return('git') } let(:arguments) {['app', 'deploy', uri.to_s, '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(133) end end end end describe "activate deployment" do context "activates 123456" do before { Net::SSH.should_receive(:start).exactly(3).times.with('test.domain.com', 'user', :compression => false) } let(:arguments) {['deployment', 'activate', '123456', '--app', DEPLOYMENT_APP_NAME]} it "should succeed" do expect{ run }.to exit_with_code(0) run_output.should match(/Activating deployment '123456' on application #{DEPLOYMENT_APP_NAME} .../) run_output.should match(/Success/) end end context "fails with ssh error" do before (:each) { Net::SSH.should_receive(:start).and_raise(Errno::ECONNREFUSED) } let(:arguments) {['deployment', 'activate', '123456', '--app', DEPLOYMENT_APP_NAME]} it "should exit with error" do expect{ run }.to exit_with_code(1) end end end describe "list deployments" do context "simple" do let(:arguments) {['deployment', 'list', DEPLOYMENT_APP_NAME]} it "should succeed" do expect{ run }.to exit_with_code(0) run_output.should match(/Jan 01\, 2000 1\:00 AM\, deployment 0000001/) run_output.should match(/Jan 01\, 2000 2\:00 AM\, deployment 0000002/) run_output.should match(/Jan 01\, 2000 3\:00 AM\, deployment 0000003 \(rolled back\)/) run_output.should match(/Jan 01\, 2000 4\:00 AM\, deployment 0000004 \(rolled back\)/) run_output.should match(/Jan 01\, 2000 5\:00 AM\, deployment 0000003 \(rollback to Jan 01\, 2000 3\:00 AM\, rolled back\)/) run_output.should match(/Jan 01\, 2000 5\:00 AM\, deployment 0000005 \(rolled back\)/) run_output.should match(/Jan 01\, 2000 6\:00 AM\, deployment 0000002 \(rollback to Jan 01\, 2000 2\:00 AM\)/) end end end describe "show deployment" do context "simple" do let(:arguments) {['deployment', 'show', '0000001', '--app', DEPLOYMENT_APP_NAME]} it "should succeed" do expect{ run }.to exit_with_code(0) run_output.should match(/Deployment ID 0000001/) end end context "fails when deployment is not found" do let(:arguments) {['deployment', 'show', 'zee', '--app', DEPLOYMENT_APP_NAME]} it "should succeed" do expect{ run }.to exit_with_code(131) run_output.should match(/Deployment ID 'zee' not found for application #{DEPLOYMENT_APP_NAME}/) end end end describe "show configuration" do context "simple" do let(:arguments) {['app', 'show', '--app', DEPLOYMENT_APP_NAME, '--configuration']} it "should succeed" do expect{ run }.to exit_with_code(0) #run_output.should match(/Deployment ID 1/) end end end endrhc-1.38.7/spec/rhc/commands/cartridge_spec.rb0000644000004100000410000005764212756270552021261 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/cartridge' require 'rhc/config' describe RHC::Commands::Cartridge do def exit_with_code_and_message(code, message = nil) run_output.should match(message) if message expect{ run }.to exit_with_code(code) end def succeed_with_message(message = "done") exit_with_code_and_message(0,message) end def fail_with_message(message,code = 1) exit_with_code_and_message(code, message) end def fail_with_code(code = 1) exit_with_code_and_message(code) end before{ user_config } describe 'run' do let!(:rest_client){ MockRestClient.new } context "with all arguments" do let(:arguments) { ['cartridges', '--trace'] } it { succeed_with_message /mock_cart-1.*mock_cart-2.*unique_mock_cart-1/m } end context "without password" do let(:arguments) { ['cartridges', '--trace'] } it { succeed_with_message /mock_cart-1.*mock_cart-2.*unique_mock_cart-1/m } end end describe 'cartridge list' do let(:arguments){ ['cartridge', 'list'] } let(:username){ nil } let(:password){ nil } let(:server){ mock_uri } let(:user_auth){ false } context 'with valid carts' do before{ stub_api; stub_simple_carts } it{ run_output.should match /mock_standalone_cart\-1\s+Mock1 Cart\s+web/ } it{ run_output.should match /mock_standalone_cart\-2\s+web/ } it{ run_output.should match /mock_embedded_cart\-1\s+Mock1 Embedded Cart\s+addon/ } it{ run_output.should match /premium_cart\-1 \(\*\)\s+Premium Cart\s+web/ } it{ expect{ run }.to exit_with_code(0) } context 'with verbose list' do let(:arguments){ ['cartridge', 'list', '--verbose', '--trace'] } it{ run_output.should match /Mock1 Cart.*\[mock_standalone_cart\-1\] \(web\)/ } it{ run_output.should match /mock_standalone_cart\-2 \(web\)/ } it{ run_output.should match "Mock2 description\n\n" } it{ run_output.should match "Tagged with: scheduled" } it{ run_output.should_not match("Tagged with: cartridge") } it{ run_output.should match /Premium Cart.*\[premium_cart\-1\*\] \(web\)/ } end end end describe 'alias app cartridge' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['app', 'cartridge', 'list'] } context 'when run' do it { succeed_with_message /mock_cart-1.*mock_cart-2.*unique_mock_cart-1/m } end end describe 'cartridge add' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'add', 'mock_cart-1', '--app', 'app1'] } context 'when run' do before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") end it { succeed_with_message /Adding mock_cart-1 to application 'app1' \.\.\. / } it { succeed_with_message /Connection URL:\s+http\:\/\/fake\.url/ } it { succeed_with_message /Prop1:\s+value1/ } it { succeed_with_message /Cartridge added with properties/ } it "should not contain env var info" do run_output.should_not match(/Environment Variables/) end end end describe 'cartridge add' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'add', 'mock_cart-1', '--app', 'app1', '--gear-size', 'small'] } context 'with gear size' do before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type", true) end it { succeed_with_message /Adding mock_cart-1 to application 'app1' \.\.\. / } it { succeed_with_message /Connection URL:\s+http\:\/\/fake\.url/ } it { succeed_with_message /Prop1:\s+value1/ } it { succeed_with_message /Cartridge added with properties/ } it "should not contain env var info" do run_output.should_not match(/Environment Variables/) end it { succeed_with_message /Gears:\s+1 small/ } end context 'with gear size on unsupported server' do let(:arguments) { ['cartridge', 'add', 'mock_cart-1', '--app', 'app1', '--gear-size', 'small'] } before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type", true) @app.stub(:has_param?).with('ADD_CARTRIDGE','environment_variables').and_return(true) @app.stub(:has_param?).with('ADD_CARTRIDGE','gear_size').and_return(false) end it { expect { run }.to exit_with_code(0) } it { run_output.should match(/Server does not support gear sizes for cartridges/) } end end describe 'cartridge add' do let!(:rest_client){ MockRestClient.new } let(:instance) do domain = rest_client.add_domain("mock_domain") @app = domain.add_application("app1", "mock_type") instance = RHC::Commands::Cartridge.new RHC::Commands::Cartridge.stub(:new) { instance } instance end context 'with app context' do let(:arguments) { ['cartridge', 'add', 'mock_cart-1'] } before do instance.stub(:git_config_get) { |key| @app.id if key == "rhc.app-id" } end it{ succeed_with_message } end context 'with named app context' do let(:arguments) { ['cartridge', 'add', 'mock_cart-1'] } before do instance.stub(:git_config_get) { |key| @app.name if key == "rhc.app-name" } end it{ succeed_with_message } end context 'without app context' do let(:arguments) { ['cartridge', 'add', 'mock_cart-1'] } before do instance.should_receive(:git_config_get).with('rhc.domain-name').and_return(nil) instance.should_receive(:git_config_get).with('rhc.app-name').and_return(nil) instance.should_receive(:git_config_get).with('rhc.app-id').and_return('') end it{ fail_with_code } end context 'with unrecognized app context' do let(:arguments) { ['cartridge', 'add', 'mock_cart-1'] } before do instance.should_receive(:git_config_get).with('rhc.domain-name').and_return(nil) instance.should_receive(:git_config_get).with('rhc.app-name').and_return(nil) instance.should_receive(:git_config_get).with('rhc.app-id').and_return('find') end it{ fail_with_code(101) } end end describe 'cartridge add' do let!(:rest_client){ MockRestClient.new } context 'when invoked through an alias' do let(:arguments) { ['app', 'cartridge', 'add', 'unique_mock_cart', '--app', 'app1'] } before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") end it { succeed_with_message } end context 'when cartridge with regex breaking name does not exist' do let(:arguments) { ['cartridge', 'add', '*##', '--app', 'app1'] } before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") end it{ fail_with_code 154 } end context 'when cartridge does not exist' do let(:arguments) { ['cartridge', 'add', 'nomatch_cart', '--app', 'app1'] } before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") end it{ fail_with_code 154 } end context 'when multiple carts match' do let(:arguments) { ['cartridge', 'add', 'mock_cart', '-a', 'app1'] } before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") end it { fail_with_code 155 } end context 'when cart is premium' do let(:arguments) { ['cartridge', 'add', 'premium_cart', '-a', 'app1'] } before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") end it { succeed_with_message /This cartridge costs an additional \$0\.05 per gear after the first 3 gears\./ } end end describe 'cartridge remove' do let!(:rest_client){ MockRestClient.new } context 'when run with --noprompt and without --confirm' do let(:arguments) { ['cartridge', 'remove', 'mock_cart-1', '-a', 'app1', '--noprompt'] } before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") app.add_cartridge('mock_cart-1') end it{ fail_with_message "This action requires the --confirm option" } end context 'when run with confirmation' do let(:arguments) { ['cartridge', 'remove', 'mock_cart-1', '--confirm', '--trace', '-a', 'app1'] } before do domain = rest_client.add_domain("mock_domain") @app = domain.add_application("app1", "mock_type") end it "should remove cartridge" do @app.add_cartridge('mock_cart-1') expect { run }.to exit_with_code(0) # framework cart should be the only one listed @app.cartridges.length.should == 1 end it "should raise cartridge not found exception" do expect { run }.to raise_error RHC::CartridgeNotFoundException end end context 'when run with custom cart url' do let(:arguments) { ['cartridge', 'remove', 'https://foo.bar.com', '--confirm', '-a', 'app1'] } before do @domain = rest_client.add_domain("mockdomain") @app = @domain.add_application("app1", "mock_type") cart1 = @app.add_cartridge('mock_cart-1') cart1.url = 'https://foo.bar.com' end it "should remove cartridge" do expect { run }.to exit_with_code(0) # framework cart should be the only one listed @app.cartridges.length.should == 1 end end context "against a 1.5 server" do let!(:rest_client){ nil } let(:username){ mock_user } let(:password){ 'password' } let(:server){ mock_uri } let(:arguments){ ['remove-cartridge', 'jenkins-1', '-a', 'foo', '--confirm', '--trace'] } before do stub_api(false) challenge{ stub_one_domain('test') } stub_one_application('test', 'foo').with(:query => {:include => 'cartridges'}) stub_application_cartridges('test', 'foo', [{:name => 'php-5.3'}, {:name => 'jenkins-1'}]) end before do stub_api_request(:delete, "broker/rest/domains/test/applications/foo/cartridges/jenkins-1"). to_return({ :body => { :type => nil, :data => nil, :messages => [ {:exit_code => 0, :field => nil, :severity => 'info', :text => 'Removed Jenkins'}, {:exit_code => 0, :field => nil, :severity => 'result', :text => 'Job URL changed'}, ] }.to_json, :status => 200 }) end it("should not display info returned by the server"){ run_output.should_not match "Removed Jenkins" } it("should display prefix returned by the server"){ run_output.should match "Removing jenkins-1" } it("should display results returned by the server"){ run_output.should match "Job URL changed" } it('should exit successfully'){ expect{ run }.to exit_with_code(0) } end end describe 'cartridge status' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'status', 'mock_cart-1', '-a', 'app1'] } before do @domain = rest_client.add_domain("mock_domain") @app = @domain.add_application("app1", "mock_type") @app.add_cartridge('mock_cart-1') end context 'when run' do it { run_output.should match('started') } end context 'when run with cart stopped' do before { @app.find_cartridge('mock_cart-1').stop } it { run_output.should match('stopped') } end end describe 'cartridge start' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'start', 'mock_cart-1', '-a', 'app1'] } context 'when run' do before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") app.add_cartridge('mock_cart-1') end it { run_output.should match(/Starting mock_cart-1 .*done/) } end end describe 'cartridge stop' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'stop', 'mock_cart-1', '-a', 'app1'] } context 'when run' do before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") app.add_cartridge('mock_cart-1') end it { run_output.should match(/Stopping mock_cart-1 .*done/) } end end describe 'cartridge restart' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'restart', 'mock_cart-1', '-a', 'app1'] } context 'when run' do before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") app.add_cartridge('mock_cart-1') end it { run_output.should match(/Restarting mock_cart-1 .*done/) } end end describe 'cartridge reload' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'reload', 'mock_cart-1', '-a', 'app1'] } context 'when run' do before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") app.add_cartridge('mock_cart-1') end it { run_output.should match(/Reloading mock_cart-1 .*done/) } end end describe 'cartridge show' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'show', 'mock_cart-1', '-a', 'app1'] } before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") app.add_cartridge('mock_cart-1') end context 'when run with exactly the same case as how cartridge was created' do it { run_output.should match('Connection URL: http://fake.url') } it { run_output.should match(/Prop1:\s+value1/) } end end describe 'cartridge show' do let(:arguments) { ['cartridge', 'show', 'Mock_Cart-1', '-a', 'app1'] } before do @rc = MockRestClient.new domain = @rc.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") app.add_cartridge('mock_cart-1') end context 'when run with different case from how cartrige was created' do it { run_output.should match('Connection URL: http://fake.url') } it { run_output.should match(/Prop1:\s+value1/) } end end describe 'cartridge show' do let(:arguments) { ['cartridge', 'show', 'premium_cart', '-a', 'app1'] } before do @rc = MockRestClient.new domain = @rc.add_domain("mock_domain") app = domain.add_application("app1", "mock_type") app.cartridges << @rc.cartridges.find {|c| c.name == 'premium_cart'} end context 'when run with a premium cartridge' do it { run_output.should match(/This cartridge costs an additional \$0\.05 per gear after the first 3 gears./) } end end describe 'cartridge show scaled' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'show', 'mock_type', '-a', 'app1'] } context 'when run' do before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type", true) end it { run_output.should match(/Scaling: .*x2 \(minimum/) } it { run_output.should match('minimum: 2') } it { run_output.should match('maximum: available') } end end describe 'cartridge scale' do let!(:rest_client){ MockRestClient.new } let(:arguments) { ['cartridge', 'scale', @cart_type || 'mock_type', '-a', 'app1'] | (@extra_args || []) } let(:current_scale) { 1 } before do domain = rest_client.add_domain("mock_domain") @app = domain.add_application("app1", "mock_type", scalable) @app.cartridges.first.stub(:current_scale).and_return(current_scale) end context 'when run with scalable app' do let(:scalable){ true } it "with no values" do fail_with_message "Must provide either a min or max" end it "with a min value" do @extra_args = ["--min","6"] succeed_with_message "minimum: 6" end it "with an explicit value" do @extra_args = ["6"] succeed_with_message "minimum: 6, maximum: 6" end it "with an invalid explicit value" do @extra_args = ["a6"] fail_with_message "Multiplier must be a positive integer" end it "with an explicit value and a different minimum" do @extra_args = ["6", "--min", "5"] succeed_with_message "minimum: 5, maximum: 6" end it "with a max value" do @extra_args = ["--max","3"] succeed_with_message 'maximum: 3' end it "with an invalid min value" do @extra_args = ["--min","a"] fail_with_message "invalid argument: --min" end it "with an invalid max value" do @extra_args = ["--max","a"] fail_with_message "invalid argument: --max" end context "when the operation times out" do before{ @app.cartridges.first.should_receive(:set_scales).twice.and_raise(RHC::Rest::TimeoutException.new('Timeout', HTTPClient::ReceiveTimeoutError.new)) } it "displays an error" do @extra_args = ["--min","2"] fail_with_message "The server has closed the connection, but your scaling operation is still in progress" end end end context 'when run with a nonscalable app' do let(:scalable){ false } it "with a min value" do @extra_args = ["--min","6"] fail_with_message "Cartridge is not scalable" end end end describe 'cartridge storage' do let!(:rest_client){ MockRestClient.new(RHC::Config, 1.3) } let(:cmd_base) { ['cartridge', 'storage'] } let(:std_args) { ['-a', 'app1'] | (@extra_args || []) } let(:cart_type) { ['mock_cart-1'] } before do domain = rest_client.add_domain("mock_domain") app = domain.add_application("app1", "mock_type", false) app.add_cartridge('mock_cart-1', true) end context 'when run with no arguments' do let(:arguments) { cmd_base | std_args } it ("should show the mock_type"){ run_output.should match('mock_type') } it ('should show mock_cart-1'){ run_output.should match('mock_cart-1') } end context 'when run for a non-existent cartridge' do let(:arguments) { cmd_base | ['bogus_cart'] | std_args } it { fail_with_message("There are no cartridges that match 'bogus_cart'.", 154) } end context 'when run with -c flag' do let(:arguments) { cmd_base | ['-c', 'mock_cart-1'] | std_args} it "should show storage info for the indicated app and cart" do run_output.should match('mock_cart-1') run_output.should_not match('mock_type') end it "should set storage for the indicated app and cart" do @extra_args = ["--set", "6GB"] run_output.should match('6GB') end end context 'when run with valid arguments' do let(:arguments) { cmd_base | cart_type | std_args } it "should show storage info for the indicated app and cart" do @extra_args = ["--show"] run_output.should match('mock_cart-1') run_output.should_not match('mock_type') end it "should add storage for the indicated app and cart" do @extra_args = ["--add", "5GB"] run_output.should match('10GB') end it "should remove storage for the indicated app and cart" do @extra_args = ["--remove", "5GB"] run_output.should match('None') end it "should warn when told to remove more storage than the indicated app and cart have" do @extra_args = ["--remove", "70GB"] fail_with_message('The amount of additional storage to be removed exceeds the total amount in use') end it "should not warn when told to remove more storage than the indicated app and cart have when forced" do @extra_args = ["--remove", "70GB", "--force"] run_output.should match('None') end it "should set storage for the indicated app and cart" do @extra_args = ["--set", "6GB"] run_output.should match('6GB') end it "should work correctly with a bare number value" do @extra_args = ["--set", "6"] run_output.should match('6GB') end end context 'when run with invalid arguments' do let(:arguments) { cmd_base | cart_type | std_args } it "should raise an error when multiple storage operations are provided" do @extra_args = ["--show", "--add", "5GB"] fail_with_message('Only one storage action can be performed at a time') end it "should raise an error when the storage amount is not provided" do @extra_args = ["--set"] fail_with_message('missing argument') end it "should raise an error when the storage amount is invalid" do @extra_args = ["--set", "5ZB"] fail_with_message("The amount format must be a number, optionally followed by 'GB'") end end context 'when run against an outdated broker' do before { rest_client.stub(:api_version_negotiated).and_return(1.2) } let(:arguments) { cmd_base | cart_type | std_args } it 'adding storage should raise a version error' do @extra_args = ["--add", "1GB"] fail_with_message('The server does not support this command \(requires 1.3, found 1.2\).') end end end describe 'cartridge add with env vars' do let!(:rest_client){ MockRestClient.new } before do domain = rest_client.add_domain("mock_domain") @app = domain.add_application("app1", "mock_type") end [['app', 'cartridge', 'add', 'unique_mock_cart', '-e', 'FOO=BAR', '--app', 'app1'], ['app', 'cartridge', 'add', 'unique_mock_cart', '--env', 'FOO=BAR', '--app', 'app1'] ].each_with_index do |args, i| context "when run with single env var #{i}" do let(:arguments) { args } it { succeed_with_message(/Environment Variables:\s+FOO=BAR/) } end end [['app', 'cartridge', 'add', 'unique_mock_cart', '-e', 'FOO1=BAR1', '-e', 'FOO2=BAR2', '--app', 'app1'], ['app', 'cartridge', 'add', 'unique_mock_cart', '--env', 'FOO1=BAR1', '--env', 'FOO2=BAR2', '--app', 'app1'] ].each_with_index do |args, i| context "when run with multiple env vars #{i}" do let(:arguments) { args } it { succeed_with_message(/Environment Variables:\s+FOO1=BAR1, FOO2=BAR2/) } end end [['app', 'cartridge', 'add', 'unique_mock_cart', '-e', File.expand_path('../../assets/env_vars.txt', __FILE__), '--app', 'app1'], ['app', 'cartridge', 'add', 'unique_mock_cart', '--env', File.expand_path('../../assets/env_vars.txt', __FILE__), '--app', 'app1'] ].each_with_index do |args, i| context "when run with env vars from files #{i}" do let(:arguments) { args } it { succeed_with_message(/Environment Variables:\s+BAR=456, FOO=123, MY_EMPTY_ENV_VAR=, MY_OPENSHIFT_ENV_VAR=mongodb:\/\/user:pass@host:port\/\n/) } end end [['app', 'cartridge', 'add', 'unique_mock_cart', '-e', 'FOO=BAR', '--app', 'app1', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password'], ['app', 'cartridge', 'add', 'unique_mock_cart', '--env', 'FOO=BAR', '--app', 'app1', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password'] ].each_with_index do |args, i| context "when run against a server without env vars support #{i}" do let(:arguments) { args } before do @app.stub(:has_param?).with('ADD_CARTRIDGE','environment_variables').and_return(false) @app.stub(:has_param?).with('ADD_CARTRIDGE','gear_size').and_return(true) end it { expect { run }.to exit_with_code(0) } it { run_output.should match(/Server does not support environment variables/) } it { run_output.should_not match(/Environment Variables:\s+FOO=BAR/) } end end end end rhc-1.38.7/spec/rhc/commands/port_forward_spec.rb0000644000004100000410000002465512756270552022023 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/port_forward' describe RHC::Commands::PortForward do let!(:rest_client){ MockRestClient.new } before{ user_config } describe 'run' do let(:arguments) { ['port-forward', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp'] } before :each do @domain = rest_client.add_domain("mockdomain") @app = @domain.add_application 'mockapp', 'mock-1.0' @uri = URI.parse @app.ssh_url @ssh = double(Net::SSH) end context 'when port forwarding for a down appl' do before(:each) do Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh) @ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, '127.0.0.1:3306') @gg = MockRestGearGroup.new(rest_client) @app.should_receive(:gear_groups).and_return([@gg]) @gg.should_receive(:gears).and_return([{'state' => 'stopped', 'id' => 'fakegearid'}]) end it "should error out and suggest restarting the application" do expect { run }.to exit_with_code(1) end it { run_output.should match(/none.*The application is stopped\..*restart/m) } end context 'when port forwarding an app without ports to forward' do before(:each) do Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh) @ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, '127.0.0.1:3306') end it "should error out as no ports to forward" do expect { run }.to exit_with_code(102) rest_client.domains[0].name.should == 'mockdomain' rest_client.domains[0].applications.size.should == 1 rest_client.domains[0].applications[0].name.should == 'mockapp' end it("should report no ports") { run_output.should match("no available ports to forward.") } end context 'when port forwarding an app with permission denied ports' do before(:each) do Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh) @ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'permission denied') end it "should error out as permission denied" do expect { run }.to exit_with_code(129) rest_client.domains[0].name.should == 'mockdomain' rest_client.domains[0].applications.size.should == 1 rest_client.domains[0].applications[0].name.should == 'mockapp' end it { run_output.should match("Permission denied") } end context 'when port forwarding an app with ports to forward' do before(:each) do Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice @ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306') forward = double(Net::SSH::Service::Forward) @ssh.should_receive(:forward).and_return(forward) forward.should_receive(:local).with(3306, '127.0.0.1', 3306) @ssh.should_receive(:loop) end it "should run successfully" do expect { run }.to exit_with_code(0) rest_client.domains[0].name.should == 'mockdomain' rest_client.domains[0].applications.size.should == 1 rest_client.domains[0].applications[0].name.should == 'mockapp' end it { run_output.should match(/Forwarding ports.*Press CTRL-C/m) } end context 'when host is unreachable' do before(:each) do Net::SSH.should_receive(:start).and_raise(Errno::EHOSTUNREACH) end it "should error out" do expect { run }.to exit_with_code(1) rest_client.domains[0].name.should == 'mockdomain' rest_client.domains[0].applications.size.should == 1 rest_client.domains[0].applications[0].name.should == 'mockapp' end it { run_output.should include("Error trying to forward ports.") } end context 'when REST client connection times out' do before(:each) do rest_client.should_receive(:find_domain).and_raise(RHC::Rest::ConnectionException) end it("should error out") { expect { run }.to exit_with_code(1) } it{ run_output.should match("Connection.*failed:") } end context 'when port forwarding an app with ports to forward' do before(:each) do Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice @ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306') forward = double(Net::SSH::Service::Forward) @ssh.should_receive(:forward).and_return(forward) forward.should_receive(:local).with(3306, '127.0.0.1', 3306) @ssh.should_receive(:loop).and_raise(Interrupt.new) end it "should exit when user interrupts" do expect { run }.to exit_with_code(0) rest_client.domains[0].name.should == 'mockdomain' rest_client.domains[0].applications.size.should == 1 rest_client.domains[0].applications[0].name.should == 'mockapp' end it { run_output.should include("Ending port forward") } end context 'when local port is already bound' do before(:each) do Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice @ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306') forward = double(Net::SSH::Service::Forward) @ssh.should_receive(:forward).at_least(2).and_return(forward) forward.should_receive(:local).with(3306, '127.0.0.1', 3306).and_raise(Errno::EACCES) forward.should_receive(:local).with(3307, '127.0.0.1', 3306) @ssh.should_receive(:loop).and_raise(Interrupt.new) end it 'should bind to a higher port' do run_output.should include("3307") end end # Windows 7 reportedly returns EPERM rather than EACCES when the # port is in use by a local service (see # ). context 'when local port is in use by local service on Windows 7' do before(:each) do Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice @ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306') forward = double(Net::SSH::Service::Forward) @ssh.should_receive(:forward).at_least(2).and_return(forward) forward.should_receive(:local).with(3306, '127.0.0.1', 3306).and_raise(Errno::EPERM) forward.should_receive(:local).with(3307, '127.0.0.1', 3306) @ssh.should_receive(:loop).and_raise(Interrupt.new) end it 'should bind to a higher port' do run_output.should include("3307") end end context 'when host refuses connection' do before(:each) do Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice @ssh.should_receive(:exec!).with("rhc-list-ports").and_yield(nil, :stderr, 'mysql -> 127.0.0.1:3306') forward = double(Net::SSH::Service::Forward) @ssh.should_receive(:forward).and_raise(Errno::ECONNREFUSED) end it "should error out" do expect { run }.to exit_with_code(0) end it { run_output.should include("ssh -N") } it { run_output.should include("Error forwarding") } end context 'when port forwarding a scaled app with ports to forward' do let(:haproxy_host_1) { '127.0.0.1' } let(:haproxy_host_2) { '127.0.0.2' } let(:mongo_host) { '51125bb94a-test907742.dev.rhcloud.com' } let(:ipv6_host) { '::1' } before(:each) do Net::SSH.should_receive(:start).with(@uri.host, @uri.user).and_yield(@ssh).twice @ssh.should_receive(:exec!).with("rhc-list-ports"). and_yield(nil, :stderr, "httpd -> #{haproxy_host_1}:8080\nhttpd -> #{haproxy_host_2}:8080\nmongodb -> #{mongo_host}:35541\nmysqld -> #{ipv6_host}:3306") forward = double(Net::SSH::Service::Forward) @ssh.should_receive(:forward).at_least(3).times.and_return(forward) forward.should_receive(:local).with(8080, haproxy_host_1, 8080) forward.should_receive(:local).with(8080, haproxy_host_2, 8080).and_raise(Errno::EADDRINUSE) forward.should_receive(:local).with(8081, haproxy_host_2, 8080) forward.should_receive(:local).with(35541, mongo_host, 35541) forward.should_receive(:local).with(3306, ipv6_host, 3306) @ssh.should_receive(:loop).and_raise(Interrupt.new) end it "should exit when user interrupts" do expect { run }.to exit_with_code(0) rest_client.domains[0].name.should == 'mockdomain' rest_client.domains[0].applications.size.should == 1 rest_client.domains[0].applications[0].name.should == 'mockapp' end it { run_output.should include("Ending port forward") } end context 'when port forwarding a single gear on a scaled app' do let(:gear_host) { 'fakesshurl.com' } let(:gear_user) { 'fakegearid0' } let(:arguments) { ['port-forward', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--gear', @gear_id] } it 'should run successfully' do @gear_id = 'fakegearid0' Net::SSH.should_receive(:start).with(gear_host, gear_user).and_yield(@ssh).twice @ssh.should_receive(:exec!).with("rhc-list-ports --exclude-remote"). and_yield(nil, :stderr, "mongodb -> #{gear_host}:35541") forward = double(Net::SSH::Service::Forward) @ssh.should_receive(:forward).and_return(forward) forward.should_receive(:local).with(35541, gear_host, 35541) @ssh.should_receive(:loop).and_raise(Interrupt.new) expect { run }.to exit_with_code(0) end it 'should fail if the specified gear is missing' do @gear_id = 'notarealgearxxxxx' expect { run }.to exit_with_code(1) run_output.should include('Gear notarealgearxxxxx not found') end it 'should fail if the specified gear has no ssh info' do @gear_id = 'fakegearid0' # Given - gears in gear group should not have ssh info gg = MockRestGearGroup.new(rest_client) @app.stub(:gear_groups).and_return([gg]) gg.stub(:gears).and_return([{'state' => 'started', 'id' => 'fakegearid0'}]) expect { run }.to exit_with_code(1) run_output.should match('The server does not support operations on individual gears.') end end end end rhc-1.38.7/spec/rhc/commands/member_spec.rb0000644000004100000410000011566012756270552020557 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/member' describe RHC::Commands::Member do before{ user_config } describe 'help' do let(:arguments) { ['member', '--help'] } it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match "Usage: rhc member" } it('should output info about roles') { run_output.should match "Developers can collaborate" } end let(:username){ 'test_user' } let(:password){ 'test_password' } let(:server){ 'test.domain.com' } def with_mock_rest_client @rest_client ||= MockRestClient.new end def with_mock_domain @domain ||= with_mock_rest_client.add_domain("mock-domain-0") end def with_mock_app @app ||= begin app = with_mock_domain.add_application("mock-app-0", "ruby-1.8.7") app.stub(:ssh_url).and_return("ssh://user@test.domain.com") app.stub(:supports_members?).and_return(supports_members) app end end def with_mock_team @team ||= with_mock_rest_client.add_team("mock-team-0") end let(:owner){ RHC::Rest::Membership::Member.new(:id => '1', :role => 'admin', :explicit_role => 'admin', :owner => true, :login => 'alice', :type => 'user') } let(:other_admin){ RHC::Rest::Membership::Member.new(:id => '2', :role => 'admin', :explicit_role => 'admin', :login => 'Bob', :type => 'user') } let(:other_editor){ RHC::Rest::Membership::Member.new(:id => '3', :role => 'edit', :explicit_role => 'edit', :name => 'Carol', :login => 'carol', :type => 'user') } let(:other_viewer){ RHC::Rest::Membership::Member.new(:id => '4', :role => 'view', :explicit_role => 'view', :name => 'Doug', :login => 'doug@doug.com', :type => 'user') } let(:other_viewer2){ RHC::Rest::Membership::Member.new(:id => '5', :role => 'view', :explicit_role => 'view', :name => 'ViewerC', :login => 'viewerc@viewer.com', :type => 'user') } let(:other_viewer3){ RHC::Rest::Membership::Member.new(:id => '6', :role => 'view', :explicit_role => 'view', :name => 'ViewerB', :login => 'viewerb@viewer.com', :type => 'user') } let(:other_viewer4){ RHC::Rest::Membership::Member.new(:id => '7', :role => 'view', :explicit_role => 'view', :name => 'ViewerA', :login => 'viewera@viewer.com', :type => 'user') } let(:team_admin){ RHC::Rest::Membership::Member.new(:id => '11', :role => 'admin', :explicit_role => 'admin', :name => 'team1', :type => 'team') } let(:team_editor){ RHC::Rest::Membership::Member.new(:id => '12', :role => 'edit', :explicit_role => 'edit', :name => 'team2', :type => 'team') } let(:team_viewer){ RHC::Rest::Membership::Member.new(:id => '13', :role => 'view', :explicit_role => 'view', :name => 'team3', :type => 'team') } let(:team_admin_member){ RHC::Rest::Membership::Member.new(:id => '21', :role => 'admin', :login => 'memberadmin', :type => 'user', :from => [{'id' => '11', 'type' => 'team'}]) } let(:team_editor_member){ RHC::Rest::Membership::Member.new(:id => '22', :role => 'edit', :login => 'membereditor', :type => 'user', :from => [{'id' => '12', 'type' => 'team'}]) } let(:team_viewer_member){ RHC::Rest::Membership::Member.new(:id => '23', :role => 'view', :login => 'memberviewer', :type => 'user', :from => [{'id' => '13', 'type' => 'team'}]) } let(:team_viewer_member2){ RHC::Rest::Membership::Member.new(:id => '24', :role => 'view', :login => 'memberviewer2', :type => 'user', :from => [{'id' => '13', 'type' => 'team'}]) } let(:team_viewer_member3){ RHC::Rest::Membership::Member.new(:id => '25', :role => 'view', :login => 'memberviewer3', :type => 'user', :from => [{'id' => '13', 'type' => 'team'}]) } let(:team_viewer_and_explicit_member){ RHC::Rest::Membership::Member.new(:id => '26', :role => 'view', :explicit_role => 'view', :login => 'memberviewerexplicitedit', :type => 'user', :from => [{'id' => '13', 'type' => 'team'}]) } let(:app_member_via_domain) { RHC::Rest::Membership::Member.new(:id => '27', :role => 'view', :login => 'app_member_via_domain', :type => 'user', :from => [{'type' => 'domain'}]) } describe 'show-domain' do context 'with members' do let(:arguments) { ['domain', 'show', 'mock-domain-0'] } let(:supports_members){ true } before{ with_mock_domain.add_member(owner).add_member(other_editor).add_member(other_admin).add_member(other_viewer).add_member(other_viewer2).add_member(other_viewer3).add_member(other_viewer4) } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /owned by alice/ } it { run_output.should =~ /Bob\s+\(admin\)/ } it { run_output.should =~ /\s+\(edit\)/ } it { run_output.should =~ /\s+\(view\)/ } it("should order the members by role then by name") { run_output.should =~ /Bob.*admin.*Admins.*Carol.*Editors.*ViewerA.*ViewerB.*ViewerC.*Viewers/m } it("should include the login value") { run_output.should =~ /alice.*Bob.*carol.*doug@doug\.com/m } end context 'without membership support' do let(:arguments) { ['domain', 'show', 'mock-domain-0'] } before{ with_mock_domain } let(:supports_members){ false } it { expect { run }.to exit_with_code(0) } it { run_output.should_not =~ /owned by/ } end end describe 'list-member' do context 'on a domain with no members' do let(:arguments) { ['members', '-n', 'mock-domain-0'] } let(:supports_members){ true } before{ with_mock_domain.init_members } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /does not have any members/ } end context 'on a domain' do let(:arguments) { ['members', '-n', 'mock-domain-0'] } let(:supports_members){ true } before{ with_mock_domain.add_member(owner) } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /alice\s+admin \(owner\)/ } it("should not show the name column") { run_output.should =~ /^Login\s+Role\s+Type$/ } end context 'on a domain with teams not showing all members' do let(:arguments) { ['members', '-n', 'mock-domain-0'] } let(:supports_members){ true } before{ with_mock_domain.add_member(owner).add_member(team_admin).add_member(team_editor).add_member(team_admin_member).add_member(team_editor_member).add_member(team_viewer).add_member(team_viewer_and_explicit_member) } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /alice\s+alice\s+admin \(owner\)\s+user/ } it { run_output.should =~ /team1\s+admin\s+team/ } it { run_output.should_not =~ /memberadmin\s+memberadmin\s+admin \(via team1\)\s+user/ } it { run_output.should =~ /team2\s+edit\s+team/ } it { run_output.should_not =~ /membereditor\s+membereditor\s+edit \(via team2\)\s+user/ } it("should show the name column") { run_output.should =~ /^Name\s+Login\s+Role\s+Type$/ } it("should prompt to use the --all parameter") { run_output.should =~ /--all to display all members/ } end context 'on a domain with teams showing all members' do let(:arguments) { ['members', '-n', 'mock-domain-0', '--all'] } let(:supports_members){ true } before{ with_mock_domain.add_member(owner).add_member(team_admin).add_member(team_editor).add_member(team_admin_member).add_member(team_editor_member).add_member(team_viewer).add_member(team_viewer_and_explicit_member) } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /alice\s+alice\s+admin \(owner\)\s+user/ } it { run_output.should =~ /team1\s+admin\s+team/ } it { run_output.should =~ /memberadmin\s+memberadmin\s+admin \(via team1\)\s+user/ } it { run_output.should =~ /team2\s+edit\s+team/ } it { run_output.should =~ /membereditor\s+membereditor\s+edit \(via team2\)\s+user/ } it("should show the name column") { run_output.should =~ /^Name\s+Login\s+Role\s+Type$/ } end context 'on an application' do let(:arguments) { ['members', 'mock-domain-0/mock-app-0'] } let(:supports_members){ false } before{ with_mock_app } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /The server does not support adding or removing members/ } context "with only implicit members via domain" do let(:supports_members){ true } before{ with_mock_app.add_member(app_member_via_domain) } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /app_member_via_domain\s+view \(via domain\)/ } it("should not show the name column") { run_output.should =~ /^Login\s+Role\s+Type$/ } it("should not show the --all message") { run_output.should_not =~ /--all/ } end context "with only owner" do let(:supports_members){ true } before{ with_mock_app.add_member(owner) } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /alice\s+admin \(owner\)/ } it("should not show the name column") { run_output.should =~ /^Login\s+Role\s+Type$/ } context "with ids" do let(:arguments) { ['members', 'mock-domain-0/mock-app-0', '--ids'] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /alice\s+admin \(owner\) 1/ } it("should not show the name column") { run_output.should =~ /^Login\s+Role\s+ID\s+Type$/ } end end context "with several members" do let(:supports_members){ true } before{ with_mock_app.add_member(owner).add_member(other_editor).add_member(other_admin).add_member(other_viewer) } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /alice\s+admin \(owner\)/ } it { run_output.should =~ /Bob\s+admin/ } it { run_output.should =~ /carol\s+edit/ } it { run_output.should =~ /doug\.com\s+view/ } it("should order the members by role") { run_output.should =~ /admin.*owner.*admin.*edit.*view/m } it("should include the login value") { run_output.should =~ /alice.*Bob.*carol.*doug@doug\.com/m } it("should show the name column") { run_output.should =~ /^Name\s+Login\s+Role\s+Type$/ } end end end describe 'add-member' do before do stub_api challenge{ stub_one_domain('test', nil, mock_user_auth) } end context "when the resource doesn't support membership changes" do let(:arguments) { ['add-member', 'testuser', '-n', 'test'] } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Adding 1 editor to domain .*The server does not support adding or removing members/ } end context "when the client doesn't support teams" do let(:mock_teams_links){ [] } let(:arguments) { ['add-member', 'testteam', '-n', 'test', '--type', 'team'] } it { expect { run }.to exit_with_code(161) } it { run_output.should =~ /Adding 1 editor to domain .*Server does not support teams/ } end context "with supported membership" do let(:supports_members?){ true } context 'with a valid user' do let(:arguments) { ['add-member', 'testuser', '-n', 'test'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'login' => 'testuser', 'role' => 'edit', 'type' => 'user'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Added 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Adding 1 editor to domain .*done/ } end context 'with a valid team' do let(:arguments) { ['add-member', 'testteam', '-n', 'test', '--type', 'team'] } before do challenge do stub_api_request(:get, "broker/rest/teams?owner=@self"). to_return({:body => {:type => 'teams', :data => [{:id => 111, :global => false, :name => 'testteam'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{:role => 'edit', :type => 'team', :id => 111, }]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'id', :index => 0, :severity => 'info', :text => 'Added 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Adding 1 editor to domain .*done/ } end context 'with an invalid type' do let(:arguments) { ['add-member', 'invalidteam', '-n', 'test', '--type', 'foo'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'login' => 'invalidteam', 'role' => 'edit', 'type' => 'foo'}]}). to_return({:body => {:messages => [{:exit_code => 1, :field => 'type', :index => 0, :severity => 'error', :text => "Members of type foo not supported for domain. Supported types are 'user', 'team'."}]}.to_json, :status => 422}) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /type foo not supported for domain/ } end context 'with an invalid team' do let(:arguments) { ['add-member', 'invalidteam', '-n', 'test', '--type', 'team'] } before do challenge do stub_api_request(:get, "broker/rest/teams?owner=@self"). to_return({:body => {:type => 'teams', :data => [{:id => 111, :global => false, :name => 'testteam'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end end it { expect { run }.to exit_with_code(162) } it { run_output.should =~ /Adding 1 editor to domain .*You do not have a team named 'invalidteam'/ } end context 'with multiple partial team matches but one exact match' do let(:arguments) { ['add-member', 'testteam', '-n', 'test', '--type', 'team'] } before do challenge do stub_api_request(:get, "broker/rest/teams?owner=@self"). to_return({:body => {:type => 'teams', :data => [{:id => 111, :global => false, :name => 'testteam'}, {:id => 222, :global => false, :name => 'testteam1'}, {:id => 333, :global => false, :name => 'testteam11'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{:role => 'edit', :type => 'team', :id => 111, }]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'id', :index => 0, :severity => 'info', :text => 'Added 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Adding 1 editor to domain .*done/ } end context 'without an exact team match' do let(:arguments) { ['add-member', 'team', '-n', 'test', '--type', 'team'] } before do challenge do stub_api_request(:get, "broker/rest/teams?owner=@self"). to_return({:body => {:type => 'teams', :data => [{:id => 111, :global => false, :name => 'team1'}, {:id => 111, :global => false, :name => 'team2'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end end it { expect { run }.to exit_with_code(162) } it { run_output.should =~ /Adding 1 editor to domain .*You do not have a team named 'team'. Did you mean one of the following\?\nteam1, team2/ } end context 'with a single exact case insensitive match' do let(:arguments) { ['add-member', 'testteam', '-n', 'test', '--type', 'team'] } before do challenge do stub_api_request(:get, "broker/rest/teams?owner=@self"). to_return({:body => {:type => 'teams', :data => [{:id => 111, :global => false, :name => 'TESTTEAM'}, {:id => 222, :global => false, :name => 'TESTTEAM1'}, {:id => 333, :global => false, :name => 'TESTTEAM2'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{:role => 'edit', :type => 'team', :id => 111, }]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'id', :index => 0, :severity => 'info', :text => 'Added 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Adding 1 editor to domain .*done/ } end context 'with an exact case sensitive match and some exact case insensitive matches' do let(:arguments) { ['add-member', 'testteam', '-n', 'test', '--type', 'team'] } before do challenge do stub_api_request(:get, "broker/rest/teams?owner=@self"). to_return({:body => {:type => 'teams', :data => [{:id => 111, :global => false, :name => 'testteam'}, {:id => 222, :global => false, :name => 'TESTTEAM'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{:role => 'edit', :type => 'team', :id => 111, }]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'id', :index => 0, :severity => 'info', :text => 'Added 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Adding 1 editor to domain .*done/ } end context 'with a team name containing special characters' do let(:arguments) { ['add-member', '*1()', '-n', 'test', '--type', 'team'] } before do challenge do stub_api_request(:get, "broker/rest/teams?owner=@self"). to_return({:body => {:type => 'teams', :data => [{:id => 111, :global => false, :name => '*1()'}, {:id => 222, :global => false, :name => 'another team'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{:role => 'edit', :type => 'team', :id => 111, }]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'id', :index => 0, :severity => 'info', :text => 'Added 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Adding 1 editor to domain .*done/ } end context 'with multiple exact team matches' do let(:arguments) { ['add-member', 'someteam', '-n', 'test', '--type', 'team'] } before do challenge do stub_api_request(:get, "broker/rest/teams?owner=@self"). to_return({:body => {:type => 'teams', :data => [{:id => 111, :global => false, :name => 'someteam'}, {:id => 222, :global => false, :name => 'someteam'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end end it { expect { run }.to exit_with_code(162) } it { run_output.should =~ /Adding 1 editor to domain .*There is more than one team named 'someteam'\. Please use the --ids flag and specify the exact id of the team you want to manage\./ } end context 'with multiple case-insensitive team matches' do let(:arguments) { ['add-member', 'someteam', '-n', 'test', '--type', 'team'] } before do challenge do stub_api_request(:get, "broker/rest/teams?owner=@self"). to_return({:body => {:type => 'teams', :data => [{:id => 111, :global => false, :name => 'SOMETEAM'}, {:id => 222, :global => false, :name => 'SomeTeam'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end end it { expect { run }.to exit_with_code(162) } it { run_output.should =~ /Adding 1 editor to domain .*You do not have a team named 'someteam'. Did you mean one of the following\?\nSOMETEAM, SomeTeam/ } end context 'without a global team' do let(:arguments) { ['add-member', 'testteam', '-n', 'test', '--type', 'team', '--global'] } before do challenge do stub_api_request(:get, "broker/rest/teams?global&search=testteam"). to_return({:body => {:type => 'teams', :data => [], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end end it { expect { run }.to exit_with_code(162) } it { run_output.should =~ /Adding 1 editor to domain .*No global team found with the name 'testteam'\./ } end context 'with an invalid role' do let(:arguments) { ['add-member', 'testuser', '-n', 'test', '--role', 'missing'] } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /The provided role 'missing' is not valid\. Supported values: .*admin/ } end context 'with a missing user' do let(:arguments) { ['add-member', 'testuser', '-n', 'test', '--type', 'user'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'login' => 'testuser', 'role' => 'edit', 'type' => 'user'}]}). to_return({:body => {:messages => [{:exit_code => 132, :field => 'login', :index => 0, :severity => 'error', :text => 'There is no user with a login testuser'},]}.to_json, :status => 422}) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Adding 1 editor to domain.*There is no user with a login testuser/ } end context 'with a missing user id and role' do let(:arguments) { ['add-member', '123', '-n', 'test', '--ids', '--role', 'admin'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'id' => '123', 'role' => 'admin', 'type' => 'user'}]}). to_return({:body => {:messages => [{:exit_code => 132, :field => 'id', :index => 0, :severity => 'error', :text => 'There is no user with the id 123'},]}.to_json, :status => 422}) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Adding 1 administrator to domain.*There is no user with the id 123/ } end end end describe 'update-member' do context "when the resource doesn't support membership changes" do before{ stub_api } context "when updating a domain" do let(:arguments) { ['update-member', 'testuser', '-n', 'test'] } before{ challenge{ stub_one_domain('test', nil, mock_user_auth) } } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Updating 1 editor to domain .*The server does not support adding or removing members/ } end end context "with supported membership" do let(:supports_members?){ true } before do stub_api challenge{ stub_one_domain('test', nil, mock_user_auth) } end context 'with a valid user' do let(:arguments) { ['update-member', 'testuser', '-n', 'test', '-r', 'view'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'login' => 'testuser', 'role' => 'view', 'type' => 'user'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Updated 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Updating 1 viewer to domain .*done/ } end context 'with a valid team' do let(:arguments) { ['update-member', 'testteam', '-n', 'test', '-r', 'view', '--type', 'team'] } before do stub_api_request(:get, "broker/rest/domains/test/members"). to_return({:body => {:type => 'members', :data => [{:id => 1, :name => 'testteam', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing members'},]}.to_json, :status => 200}) stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'id' => 1, 'role' => 'view', 'type' => 'team'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Updated 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Updating 1 viewer to domain .*done/ } end context 'with multiple team exact matches' do let(:arguments) { ['update-member', 'testteam', '-n', 'test', '-r', 'view', '--type', 'team'] } before do stub_api_request(:get, "broker/rest/domains/test/members"). to_return({:body => {:type => 'members', :data => [{:id => 1, :name => 'testteam', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}, {:id => 12, :name => 'testteam', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing members'},]}.to_json, :status => 200}) stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'id' => 1, 'role' => 'view', 'type' => 'team'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Updated 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(163) } it { run_output.should =~ /Updating 1 viewer to domain .*There is more than one member team named 'testteam'/ } end context 'with multiple team case-insensitive matches' do let(:arguments) { ['update-member', 'testteam', '-n', 'test', '-r', 'view', '--type', 'team'] } before do stub_api_request(:get, "broker/rest/domains/test/members"). to_return({:body => {:type => 'members', :data => [{:id => 1, :name => 'TESTTEAM', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}, {:id => 12, :name => 'TestTeam', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing members'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(163) } it { run_output.should =~ /Updating 1 viewer to domain .*No member team found with the name 'testteam'. Did you mean one of the following\?\nTESTTEAM, TestTeam/ } end context 'with a single exact case insensitive match' do let(:arguments) { ['update-member', 'testteam', '-n', 'test', '-r', 'view', '--type', 'team'] } before do stub_api_request(:get, "broker/rest/domains/test/members"). to_return({:body => {:type => 'members', :data => [{:id => 1, :name => 'TESTTEAM', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}, {:id => 2, :name => 'TESTTEAM2', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}, {:id => 3, :name => 'TESTTEAM3', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing members'},]}.to_json, :status => 200}) stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'id' => 1, 'role' => 'view', 'type' => 'team'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Updated 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Updating 1 viewer to domain .*done/ } end context 'with an exact case sensitive match and some exact case insensitive matches' do let(:arguments) { ['update-member', 'testteam', '-n', 'test', '-r', 'view', '--type', 'team'] } before do stub_api_request(:get, "broker/rest/domains/test/members"). to_return({:body => {:type => 'members', :data => [{:id => 1, :name => 'testteam', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}, {:id => 2, :name => 'TESTTEAM', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}, {:id => 3, :name => 'TeStTeAm', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing members'},]}.to_json, :status => 200}) stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'id' => 1, 'role' => 'view', 'type' => 'team'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Updated 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Updating 1 viewer to domain .*done/ } end context 'with a team name containing special characters' do let(:arguments) { ['update-member', '*1()', '-n', 'test', '-r', 'view', '--type', 'team'] } before do stub_api_request(:get, "broker/rest/domains/test/members"). to_return({:body => {:type => 'members', :data => [{:id => 1, :name => '*1()', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'team'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing members'},]}.to_json, :status => 200}) stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'id' => 1, 'role' => 'view', 'type' => 'team'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Updated 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Updating 1 viewer to domain .*done/ } end context 'with a missing user' do let(:arguments) { ['update-member', 'testuser', '-n', 'test', '-r', 'view'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'login' => 'testuser', 'role' => 'view', 'type' => 'user'}]}). to_return({:body => {:messages => [{:exit_code => 132, :field => 'login', :index => 0, :severity => 'error', :text => 'There is no user with a login testuser'},]}.to_json, :status => 422}) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Updating 1 viewer to domain.*There is no user with a login testuser/ } end context 'with a missing team' do let(:arguments) { ['update-member', 'testteam', '-n', 'test', '-r', 'view', '--type', 'team'] } before do stub_api_request(:get, "broker/rest/domains/test/members"). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing teams'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(163) } it { run_output.should =~ /Updating 1 viewer to domain.*No member team found with the name 'testteam'/ } end context 'with a missing team with an identical user name' do let(:arguments) { ['update-member', 'testteam', '-n', 'test', '-r', 'view', '--type', 'team'] } before do stub_api_request(:get, "broker/rest/domains/test/members"). to_return({:body => {:type => 'members', :data => [{:id => 1, :name => 'testteam', :login => 'testteam', :owner => false, :role => 'edit', :explicit_role => 'edit', :type => 'user'}], :messages => [{:exit_code => 0, :field => nil, :index => nil, :severity => 'info', :text => 'Listing members'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(163) } it { run_output.should =~ /Updating 1 viewer to domain.*No member team found with the name 'testteam'/ } end context 'with a missing user id and role' do let(:arguments) { ['update-member', '123', '-n', 'test', '--ids', '-r', 'view'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'id' => '123', 'role' => 'view', 'type' => 'user'}]}). to_return({:body => {:messages => [{:exit_code => 132, :field => 'id', :index => 0, :severity => 'error', :text => 'There is no user with the id 123'},]}.to_json, :status => 422}) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Updating 1 viewer to domain.*There is no user with the id 123/ } end context 'when the user is not a member' do let(:arguments) { ['update-member', 'testuser', '-n', 'test', '-r', 'view'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'login' => 'testuser', 'role' => 'view', 'type' => 'user'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'warning', :text => 'testuser is not a member of this domain.'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Updating 1 viewer to domain.*testuser is not a member of this domain.*done/m } end end end describe 'remove-member' do context "when the resource doesn't support membership changes" do before{ stub_api } context "when adjusting a domain" do let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] } before{ challenge{ stub_one_domain('test', nil, mock_user_auth) } } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Removing 1 member from domain .*The server does not support adding or removing members/ } end end context "with supported membership" do let(:supports_members?){ true } before do stub_api challenge{ stub_one_domain('test', nil, mock_user_auth) } end =begin Scenario removed context "when adjusting an app" do let(:arguments) { ['remove-member', 'testuser', '-n', 'test', '-a', 'app'] } before{ challenge{ stub_one_application('test', 'app') } } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /You can only add or remove members on a domain/ } end =end context 'with a valid member' do let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'login' => 'testuser', 'role' => 'none', 'type' => 'user'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Removed 1 member'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Removing 1 member from domain .*done/ } end context 'with --all' do let(:arguments) { ['remove-member', '--all', '-n', 'test'] } before do stub_api_request(:delete, "broker/rest/domains/test/members"). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'info', :text => 'Removed everyone except owner.'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Removing all members from domain .*done/ } end context 'with a missing user' do let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'login' => 'testuser', 'role' => 'none', 'type' => 'user'}]}). to_return({:body => {:messages => [{:exit_code => 132, :field => 'login', :index => 0, :severity => 'error', :text => 'There is no user with a login testuser'},]}.to_json, :status => 422}) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Removing 1 member from domain.*There is no user with a login testuser/ } end context 'with a missing user id and role' do let(:arguments) { ['remove-member', '123', '-n', 'test', '--ids'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'id' => '123', 'role' => 'none', 'type' => 'user'}]}). to_return({:body => {:messages => [{:exit_code => 132, :field => 'id', :index => 0, :severity => 'error', :text => 'There is no user with the id 123'},]}.to_json, :status => 422}) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Removing 1 member from domain.*There is no user with the id 123/ } end context 'when the user is not a member' do let(:arguments) { ['remove-member', 'testuser', '-n', 'test'] } before do stub_api_request(:patch, "broker/rest/domains/test/members"). with(:body => {:members => [{'login' => 'testuser', 'role' => 'none', 'type' => 'user'}]}). to_return({:body => {:type => 'members', :data => [], :messages => [{:exit_code => 0, :field => 'login', :index => 0, :severity => 'warning', :text => 'testuser is not a member of this domain.'},]}.to_json, :status => 200}) end it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Removing 1 member from domain.*testuser is not a member of this domain.*done/m } end end end endrhc-1.38.7/spec/rhc/commands/tail_spec.rb0000644000004100000410000000551412756270552020235 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/tail' require 'rhc/config' describe RHC::Commands::Tail do let!(:rest_client) { MockRestClient.new } before(:each) do user_config domain = rest_client.add_domain("mock-domain-0") @app = domain.add_application("mock-app-0", "ruby-1.8.7") @app.stub(:ssh_url).and_return("ssh://user@test.domain.com") end describe 'help' do let(:arguments) { ['tail', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc tail") } end end describe 'tail' do let(:arguments) { ['tail', 'mock-app-0'] } context 'when ssh connects' do before (:each) {Net::SSH.should_receive(:start).with('test.domain.com', 'user', :compression => false) } it { expect { run }.to exit_with_code(0) } end context 'is run on an unreachable domain' do before (:each) {Net::SSH.should_receive(:start).and_raise(SocketError) } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /The connection to test.domain.com failed: / } end context 'is refused' do before (:each) {Net::SSH.should_receive(:start).and_raise(Errno::ECONNREFUSED) } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /The server test.domain.com refused a connection with user user/ } end context 'succeeds and exits on Interrupt' do before (:each) { rest_client.stub(:find_domain) { raise Interrupt } } it { expect { run }.to raise_error(Interrupt) } end context 'succeeds when a gear is specified' do before (:each) {Net::SSH.should_receive(:start).with('fakesshurl.com', 'fakegearid0', :compression => false) } let(:arguments) { ['tail', 'mock-app-0', '--gear', 'fakegearid0' ] } it { run_output.should_not =~ /Connecting to fakesshurl.com/ } it { expect { run }.to exit_with_code(0) } end context 'fails when an invalid gear is specified' do let(:arguments) { ['tail', 'mock-app-0', '--gear', 'gearthatdoesnotexist' ] } it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /Gear gearthatdoesnotexist not found/ } end context 'fails when a gear with no ssh info is specified' do let(:arguments) { ['tail', 'mock-app-0', '--gear', 'fakegearid' ] } # Given - gears in gear group should not have ssh info before(:each) do gg = MockRestGearGroup.new(rest_client) @app.stub(:gear_groups).and_return([gg]) gg.stub(:gears).and_return([{'state' => 'started', 'id' => 'fakegearid'}]) end it { expect { run }.to exit_with_code(1) } it { run_output.should =~ /The server does not support operations on individual gears./ } end end endrhc-1.38.7/spec/rhc/commands/app_spec.rb0000644000004100000410000012404312756270552020063 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/app' require 'rhc/config' require 'rhc/servers' require 'resolv' describe RHC::Commands::App do let!(:rest_client){ MockRestClient.new } let!(:config){ user_config } before{ RHC::Config.stub(:home_dir).and_return('/home/mock_user') } before{ RHC::Servers.stub(:home_dir).and_return('/home/mock_user') } before do FakeFS.activate! FakeFS::FileSystem.clear RHC::Helpers.send(:remove_const, :MAX_RETRIES) rescue nil RHC::Helpers.const_set(:MAX_RETRIES, 3) @instance = RHC::Commands::App.new RHC::Commands::App.stub(:new) do @instance.stub(:git_config_get) { "" } @instance.stub(:git_config_set) { "" } Kernel.stub(:sleep) { } @instance.stub(:git_clone_repo) do |git_url, repo_dir| $terminal.instance_variable_get(:@output).puts "Cloning into..." raise RHC::GitException, "Error in git clone" if repo_dir == "giterrorapp" Dir::mkdir(repo_dir) File.expand_path(repo_dir) end @instance.stub(:host_exists?) do |host| host.match("dnserror") ? false : true end @instance end end after(:each) do FakeFS.deactivate! end describe 'app default' do before do FakeFS.deactivate! end context 'app' do let(:arguments) { ['app'] } it { run_output.should match('Usage:') } it { run_output.should match('List of Actions') } it { run_output.should_not match('Options') } end end describe '#gear_group_state' do it("shows single state"){ subject.send(:gear_group_state, ['started']).should == 'started' } it("shows unique states"){ subject.send(:gear_group_state, ['idle', 'idle']).should == 'idle' } it("shows number of started"){ subject.send(:gear_group_state, ['started', 'idle']).should == '1/2 started' } end describe '#check_domain!' do let(:rest_client){ double('RestClient') } let(:domain){ double('Domain', :name => 'test') } before{ subject.stub(:rest_client).and_return(rest_client) } let(:interactive){ false } before{ subject.stub(:interactive?).and_return(interactive) } context "when no options are provided and there is one domain" do before{ rest_client.should_receive(:domains).twice.and_return([domain]) } it("should load the first domain"){ subject.send(:check_domain!).should == domain } after{ subject.send(:options).namespace.should == domain.name } end context "when no options are provided and there are no domains" do before{ rest_client.should_receive(:domains).and_return([]) } it("should load the first domain"){ expect{ subject.send(:check_domain!) }.to raise_error(RHC::Rest::DomainNotFoundException) } after{ subject.send(:options).namespace.should be_nil } end context "when valid namespace is provided" do before{ subject.send(:options)[:namespace] = 'test' } before{ rest_client.should_receive(:find_domain).with('test').and_return(domain) } it("should load the requested domain"){ subject.send(:check_domain!).should == domain } after{ subject.send(:options).namespace.should == 'test' } end context "when interactive and no domains" do let(:interactive){ true } before{ rest_client.should_receive(:domains).twice.and_return([]) } before{ RHC::DomainWizard.should_receive(:new).and_return(double(:run => true)) } it("should raise if the wizard doesn't set the option"){ expect{ subject.send(:check_domain!) }.to raise_error(RHC::Rest::DomainNotFoundException) } after{ subject.send(:options).namespace.should be_nil } end end describe 'app create' do before{ rest_client.add_domain("mockdomain") } context "when we ask for help with the alias" do before{ FakeFS.deactivate! } context do let(:arguments) { ['help', 'create-app'] } it{ run_output.should match "Usage: rhc app-create " } end context do let(:arguments) { ['create-app', '-h'] } it{ run_output.should match "Usage: rhc app-create " } end end context "when run with no arguments" do before{ FakeFS.deactivate! } let(:arguments){ ['create-app'] } it{ run_output.should match "Usage: rhc app-create " } it{ run_output.should match "When creating an application, you must provide a name and a cartridge from the list below:" } it{ run_output.should match "mock_standalone_cart-1" } it{ run_output.should match "Please specify the name of the application" } end context "when dealing with config" do subject{ described_class.new(Commander::Command::Options.new(options)) } let(:wizard){ s = double('Wizard'); RHC::EmbeddedWizard.should_receive(:new).and_return(s); s } let(:options){ nil } let(:interactive){ true } before{ subject.should_receive(:interactive?).at_least(1).times.and_return(interactive) } before{ subject.stub(:check_sshkeys!) } it("should run the wizard"){ expect{ subject.create('name', ['mock_standalone_cart-1']) }.to call(:run).on(wizard).and_stop } context "when has config" do let(:options){ {:server => 'test', :rhlogin => 'foo'} } before{ subject.send(:config).should_receive(:has_local_config?).and_return(true) } it("should not run the wizard"){ expect{ subject.create('name', ['mock_standalone_cart-1']) }.to not_call(:new).on(RHC::EmbeddedWizard) } end context "when has no config" do before{ subject.send(:config).should_receive(:has_local_config?).and_return(false) } it("should run the wizard"){ expect{ subject.create('name', ['mock_standalone_cart-1']) }.to call(:new).on(RHC::EmbeddedWizard).and_stop } end context "when not interactive" do let(:interactive){ false } it("should not run the wizard"){ expect{ subject.create('name', ['mock_standalone_cart-1']) }.to not_call(:new).on(RHC::EmbeddedWizard) } end end context "when dealing with ssh keys" do before(:all){ mock_terminal } subject{ described_class.new(options) } let(:wizard){ s = double('Wizard'); RHC::SSHWizard.should_receive(:new).and_return(s); s } let(:options){ Commander::Command::Options.new(:server => 'foo.com', :rhlogin => 'test') } let(:interactive){ true } before{ subject.should_receive(:interactive?).at_least(1).times.and_return(interactive) } before{ subject.should_receive(:check_config!) } it("should run the wizard"){ expect{ subject.create('name', ['mock_standalone_cart-1']) }.to call(:run).on(wizard).and_stop } context "when not interactive" do let(:interactive){ false } it("should not run the wizard"){ expect{ subject.create('name', ['mock_standalone_cart-1']) }.to not_call(:new).on(RHC::SSHWizard) } end end context "when in full interactive mode with no keys, domain, or config" do let!(:config){ base_config } before{ RHC::Config.any_instance.stub(:has_local_config?).and_return(false) } before{ described_class.any_instance.stub(:interactive?).and_return(true) } before{ described_class.any_instance.stub(:discover_git_executable).and_return('git') } before{ rest_client.domains.clear } before{ rest_client.sshkeys.delete_if {|k| !k.is_ssh? } } let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1'] } # skips login stage and insecure check because of mock rest client, doesn't check keys it { run_output(['mydomain', 'y', 'mykey']).should match(/This wizard.*Checking for a domain.*You will not be able to create an application without completing this step.*Your domain 'mydomain' has been successfully created.*Creating application.*Your public SSH key.*Uploading key 'mykey'.*Your application 'app1' is now available.*Cloned to/m) } end context 'when run without a cart' do before{ FakeFS.deactivate! } let(:arguments) { ['app', 'create', 'app1'] } it { run_output.should match(/mock_standalone_cart-1.*Every application needs a web cartridge/m) } end context 'when run with a valid cart' do let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1'] } it { expect { run }.to exit_with_code(0) } it { run_output.should match("Success") } it { run_output.should match("Cartridges: mock_standalone_cart-1\n") } it { run_output.should_not match(/Environment Variables:/) } end context 'when Hosts resolver raises an Exception' do let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1'] } before :each do resolver = Object.new Resolv::Hosts.should_receive(:new).and_return(resolver) resolver.should_receive(:getaddress).with('app1-mockdomain.fake.foo').and_raise(ArgumentError) end it { expect { run }.to exit_with_code(0) } it { run_output.should match("Success") } end context 'when run with multiple carts' do let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1', 'mock_cart-1'] } it { expect { run }.to exit_with_code(0) } it { run_output.should match("Success") } it { run_output.should match("Cartridges: mock_standalone_cart-1, mock_cart-1\n") } it { run_output.should_not match(/Environment Variables:/) } after{ rest_client.domains.first.applications.first.cartridges.find{ |c| c.name == 'mock_cart-1' }.should be_true } end context 'when run with a cart URL' do let(:arguments) { ['app', 'create', 'app1', 'http://foo.com', 'mock_cart-1'] } it { expect { run }.to exit_with_code(0) } it { run_output.should match("Success") } it { run_output.should match("Cartridges: http://foo.com, mock_cart-1\n") } it { run_output.should_not match(/Environment Variables:/) } after{ rest_client.domains.first.applications.first.cartridges.find{ |c| c.url == 'http://foo.com' }.should be_true } end context 'when run with a git url' do let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1', '--from-code', 'git://url'] } it { expect { run }.to exit_with_code(0) } it { run_output.should match("Success") } it { run_output.should match("Git remote: git:fake.foo/git/app1.git\n") } it { run_output.should match("Source Code: git://url\n") } after{ rest_client.domains.first.applications.first.initial_git_url.should == 'git://url' } end context 'when no cartridges are returned' do before do domain = rest_client.domains.first end context 'without trace' do let(:arguments) { ['app', 'create', 'app1', 'nomatch_cart'] } it("should display the list of cartridges") { run_output.should match(/Short Name.*mock_standalone_cart-2/m) } end context 'with trace' do let(:arguments) { ['app', 'create', 'app1', 'nomatch_cart', '--trace'] } it { expect { run }.to raise_error(RHC::CartridgeNotFoundException, "There are no cartridges that match 'nomatch_cart'.") } end end end describe 'cart matching behavior' do before{ rest_client.add_domain("mockdomain") } context 'multiple web matches' do let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart', '--trace', '--noprompt'] } it { expect { run }.to raise_error(RHC::MultipleCartridgesException) } end context 'when only a single cart can match' do let(:arguments) { ['app', 'create', 'app1', 'unique', '--trace', '--noprompt'] } it('picks the cart') { run_output.should match('Using mock_unique_standalone_cart-1') } end context 'when I pick a web cart and an ambiguous non web cart' do let(:arguments) { ['app', 'create', 'app1', 'mock_standalone_cart-1', 'unique', '--trace', '--noprompt'] } it('picks the non web cart') { run_output.should match('Using unique_mock_cart-1') } end context 'when I pick very ambiguous carts' do let(:arguments) { ['app', 'create', 'app1', 'mock', 'embcart-', '--noprompt'] } it('shows only web carts') { run_output.should match("There are multiple cartridges matching 'mock'") } end context 'when I pick only embedded carts' do let(:arguments) { ['app', 'create', 'app1', 'mock_cart', '--trace', '--noprompt'] } it { expect { run }.to raise_error(RHC::CartridgeNotFoundException, /Every application needs a web cartridge/) } end context 'when I pick multiple embedded carts' do let(:arguments) { ['app', 'create', 'app1', 'unique_standalone', 'mock_cart', '--trace', '--noprompt'] } it { expect { run }.to raise_error(RHC::MultipleCartridgesException, /There are multiple cartridges matching 'mock_cart'/) } end context 'when I pick multiple standalone carts' do let(:arguments) { ['app', 'create', 'app1', 'unique_standalone', 'mock_standalone_cart', '--trace', '--noprompt'] } it { expect { run }.to raise_error(RHC::MultipleCartridgesException, /There are multiple cartridges matching 'mock_standalone_cart'/) } end context 'when I pick a custom URL cart' do let(:arguments) { ['app', 'create', 'app1', 'http://foo.com', '--trace', '--noprompt'] } it('tells me about custom carts') { run_output.should match("The cartridge 'http://foo.com' will be downloaded") } it('lists the cart using the short_name') { run_output.should match(%r(Cartridges:\s+http://foo.com$)) } end context 'when I pick a custom URL cart and a web cart' do let(:arguments) { ['app', 'create', 'app1', 'http://foo.com', 'embcart-1', '--trace', '--noprompt'] } it('tells me about custom carts') { run_output.should match("The cartridge 'http://foo.com' will be downloaded") } it('lists the carts using the short_name') { run_output.should match(%r(Cartridges:\s+http://foo.com, embcart-1$)) } end end describe 'app create enable-jenkins' do let(:arguments) { ['app', 'create', 'app1', '--trace', 'mock_unique_standalone_cart', '--enable-jenkins'] } context 'when run' do before do @domain = rest_client.add_domain("mockdomain") end it "should create a jenkins app and a regular app with an embedded jenkins client" do #puts run_output expect { run }.to exit_with_code(0) jenkins_app = rest_client.find_application(@domain.name,"jenkins") jenkins_app.cartridges[0].name.should == "jenkins-1" app = rest_client.find_application(@domain.name,"app1") app.find_cartridge("jenkins-client-1") end end end describe 'app create enable-jenkins with --no-dns' do let(:arguments) { ['app', 'create', 'app1', 'mock_unique_standalone_cart', '--trace', '--enable-jenkins', '--no-dns'] } context 'when run' do before do domain = rest_client.add_domain("mockdomain") end it { expect { run }.to_not raise_error } end end describe 'app create enable-jenkins with same name as app' do let(:arguments) { ['app', 'create', 'app1', 'mock_unique_standalone_cart', '--trace', '--enable-jenkins', 'app1'] } context 'when run' do before do domain = rest_client.add_domain("mockdomain") end it { expect { run }.to raise_error(ArgumentError, /You have named both your main application and your Jenkins application/) } end end describe 'app create enable-jenkins with existing jenkins' do let(:arguments) { ['app', 'create', 'app1', 'mock_unique_standalone_cart', '--trace', '--enable-jenkins', 'jenkins2'] } context 'when run' do before do @domain = rest_client.add_domain("mockdomain") @domain.add_application("jenkins", "jenkins-1") end it "should use existing jenkins" do expect { run }.to exit_with_code(0) expect { rest_client.find_application(@domain.name,"jenkins") }.to_not raise_error expect { rest_client.find_application(@domain.name,"jenkins2") }.to raise_error(RHC::Rest::ApplicationNotFoundException) end end end describe 'app create jenkins fails to install warnings' do let(:arguments) { ['app', 'create', 'app1', 'mock_unique_standalone_cart', '--enable-jenkins'] } before do @domain = rest_client.add_domain("mockdomain") end context 'when run with error in jenkins setup' do before do @instance.stub(:add_jenkins_app) { raise Exception } end it "should print out jenkins warning" do run_output.should match("Jenkins failed to install") end end context 'when run with error in jenkins-client setup' do before do @instance.stub(:add_jenkins_cartridge) { raise Exception } end it "should print out jenkins warning" do run_output.should match("Jenkins client failed to install") end end context 'when run without jenkins cartridge available on server' do before do @instance.stub(:all_cartridges) { rest_client.cartridges.delete_if { |item| item.name =~ /\Ajenkins/i } } end it "should exit with jenkins error" do run_output.should match("There is no installed cartridge that exposes Jenkins") end end end describe 'app create jenkins install with retries' do let(:arguments) { ['app', 'create', 'app1', 'mock_unique_standalone_cart', '--enable-jenkins'] } context 'when run with server error in jenkins-client setup' do before do @domain = rest_client.add_domain("mockdomain") @instance.stub(:add_jenkins_cartridge) { raise RHC::Rest::ServerErrorException.new("Server error", 157) } end it "should fail embedding jenkins cartridge" do Kernel.should_receive(:sleep).and_return(true) run_output.should match("Jenkins client failed to install") end end end describe 'dns app create warnings' do let(:arguments) { ['app', 'create', 'app1', 'mock_unique_standalone_cart'] } context 'when run' do before do @domain = rest_client.add_domain("dnserror") end it { run_output.should match("unable to lookup your hostname") } end end describe 'app create git warnings' do let(:arguments) { ['app', 'create', 'app1', 'mock_unique_standalone_cart'] } before do @domain = rest_client.add_domain("mockdomain") @instance.stub(:git_clone_application) { raise RHC::GitException } @instance.stub(:check_sshkeys!) @instance.stub(:discover_git_executable).and_return('git') end context 'when run with error in git clone' do it "should print out git warning" do run_output.should match("We were unable to clone your application's git repo") end end context 'when run without git installed' do before do @instance.stub(:has_git?) { false } end it "should print out git warning" do run_output.should match("You do not have git installed") end end context 'when run with windows and no nslookup bug' do before do RHC::Helpers.stub(:windows?) { true } @instance.stub(:run_nslookup) { true } @instance.stub(:run_ping) { true } @instance.stub(:has_git?) { true } end it "should print out git warning" do run_output.should match(" We were unable to clone your application's git repo") end end context 'when run with windows nslookup bug' do before do RHC::Helpers.stub(:windows?) { true } @instance.stub(:run_nslookup) { true } @instance.stub(:run_ping) { false } @instance.stub(:has_git?) { true } end it "should print out windows warning" do run_output.should match("This may also be related to an issue with Winsock on Windows") end end end describe 'app create prompt for sshkeys' do let(:arguments) { ['app', 'create', 'app1', 'mock_unique_standalone_cart', '--config', '/tmp/test.conf', '-l', 'test@test.foo', '-p', 'password'] } before (:each) do @domain = rest_client.add_domain("mockdomain") # fakefs is activated Dir.mkdir('/tmp/') File.open('/tmp/test.conf', 'w') do |f| f.write("rhlogin=test@test.foo") end # don't run wizard here because we test this elsewhere wizard_instance = RHC::SSHWizard.new(rest_client, RHC::Config.new, Commander::Command::Options.new) wizard_instance.stub(:ssh_key_uploaded?) { true } RHC::SSHWizard.stub(:new) { wizard_instance } RHC::Config.stub(:should_run_ssh_wizard?) { false } end context 'when run' do it { expect { run }.to exit_with_code(0) } end end describe 'app create from another app' do before(:each) do FakeFS.deactivate! @domain = rest_client.add_domain("mockdomain") @app = @domain.add_application("app1", :cartridge=>"mock_standalone_cart-1") @app.add_alias('myfoo.com') @cart1 = @app.add_cartridge('mock_cart-1') @cart2 = @app.add_cartridge('mock_cart-2') @cart2.gear_profile = 'medium' @instance.stub(:save_snapshot) @instance.stub(:restore_snapshot) @app.stub(:region){'aRegion'} rest_client.api.stub(:supports?).with(:LIST_REGIONS){true} end context 'when run and the broker allows region selection' do before do rest_client.stub(:regions) do region = double() region.stub(:allow_selection){true} [region] end end let(:arguments) { ['app', 'create', 'clone', '--from-app', 'app1', '--no-git'] } it { expect { run }.to exit_with_code(0) } it "should clone successfully" do run_output.should match(/Cartridges:\s+mock_standalone_cart-1, mock_cart-1, mock_cart-2/) run_output.should match(/Gear Size:\s+Copied from 'app1'/) run_output.should match(/Setting deployment configuration/) run_output.should match(/Region:\s+aRegion\s+\(copied from 'app1'/) run_output.should match(/done/) end end context 'when source app has a region and the broker does not allow region selection' do context 'and region flag is not specified' do let(:arguments) { ['app', 'create', 'app2', '--from-app', 'app1', '--no-git'] } it "should clone successfully" do expect { run }.to exit_with_code(0) run_output.should match(/Server does not allow selecting regions. Region is being ignored./) run_output.should match(/Cartridges:\s+mock_standalone_cart-1, mock_cart-1, mock_cart-2/) run_output.should match(/Gear Size:\s+Copied from 'app1'/) run_output.should_not match(/Region:\s+aRegion\s+\(copied from 'app1'/) run_output.should match(/Setting deployment configuration/) run_output.should match(/done/) end end context 'and region flag is specified' do let(:arguments) { ['app', 'create', 'app2', '--from-app', 'app1', '--region','foo','--no-git'] } it "should clone successfully" do run_output.should match(/Server does not allow selecting regions. Region is being ignored./) run_output.should match(/Cartridges:\s+mock_standalone_cart-1, mock_cart-1, mock_cart-2/) run_output.should match(/Gear Size:\s+Copied from 'app1'/) run_output.should_not match(/Region:\s+aRegion\s+\(copied from 'app1'/) run_output.should match(/Setting deployment configuration/) run_output.should match(/done/) expect { run }.to exit_with_code(0) end end end context 'when cloning a scalable app as not scalable' do before do @scaled = @domain.add_application("scaled", "mock_standalone_cart-1", true) @scaled.cartridges.each do |c| c.scales_from = 2 c.scales_to = -1 end end let(:arguments) { ['app', 'create', 'clone', '--from-app', 'scaled', '--no-git', '--no-scaling'] } it "should result in only one gear" do expect { run }.to exit_with_code(0) @domain.applications.size.should == 3 @domain.applications.select{|a| a.name == 'clone'}.size.should == 1 @domain.applications.select{|a| a.name == 'clone'}.first.cartridges.size.should == 1 @domain.applications.select{|a| a.name == 'clone'}.first.cartridges.first.scales_from.should == 1 @domain.applications.select{|a| a.name == 'clone'}.first.cartridges.first.scales_to.should == 1 end end context 'alias already registered' do let(:arguments) { ['app', 'create', 'clone', '--from-app', 'app1', '--no-git'] } before do RHC::Rest::Mock::MockRestApplication.any_instance.stub(:aliases).and_return(['www.foo.com']) end it { expect { run }.to exit_with_code(0) } it "should warn" do run_output.should match(/The application 'app1' has aliases set which were not copied/) end end context 'when run against unsupported server' do let(:arguments) { ['app', 'create', 'clone', '--from-app', 'app1', '--no-git'] } before { @domain.should_receive(:has_param?).with('ADD_APPLICATION','cartridges[][name]').and_return(false) } it { expect { run }.to exit_with_code(134) } it "should fail" do run_output.should match(/The server does not support creating apps based on others/) end end end describe 'app delete' do let(:arguments) { ['app', 'delete', '--trace', '-a', 'app1', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password'] } context 'when run' do before{ @domain = rest_client.add_domain("mockdomain") } it "should raise cartridge not found exception when no apps exist" do expect { run }.to raise_error RHC::Rest::ApplicationNotFoundException end context "with an app" do before{ @app = @domain.add_application("app1", "mock_type") } it "should not remove app when no is sent as input" do expect { run(["no"]) }.to raise_error(RHC::ConfirmationError) @domain.applications.length.should == 1 @domain.applications[0] == @app end it "should remove app when yes is sent as input" do expect { run(["yes"]) }.to exit_with_code(0) @domain.applications.length.should == 0 end context "with --noprompt but without --confirm" do let(:arguments) { ['app', 'delete', 'app1', '--noprompt', '--trace'] } it "should not remove the app" do expect { run(["no"]) }.to raise_error(RHC::ConfirmationError) @domain.applications.length.should == 1 end end context "with --noprompt and --confirm" do let(:arguments) { ['app', 'delete', 'app1', '--noprompt', '--confirm'] } it "should remove the app" do expect { run }.to exit_with_code(0) @domain.applications.length.should == 0 end end end end context "against a 1.5 server" do let!(:rest_client){ nil } let(:username){ mock_user } let(:password){ 'password' } let(:server){ mock_uri } let(:arguments){ ['delete-app', 'foo', '--confirm', '--trace'] } before do stub_api challenge{ stub_one_domain('test') } stub_one_application('test', 'foo') end before do stub_api_request(:delete, "broker/rest/domains/test/applications/foo"). to_return({ :body => { :type => nil, :data => nil, :messages => [ {:exit_code => 0, :field => nil, :severity => 'info', :text => 'Removed foo'}, {:exit_code => 0, :field => nil, :severity => 'result', :text => 'Job URL changed'}, ] }.to_json, :status => 200 }) end it("should display info returned by the server"){ run_output.should match "Deleting application 'foo'" } it("should display results returned by the server"){ run_output.should match "Job URL changed" } it('should exit successfully'){ expect{ run }.to exit_with_code(0) } end end describe 'app show' do let(:arguments) { ['app', 'show', 'app1'] } context 'when run with the same case as created' do before do FakeFS.deactivate! @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") end it("should output an app") { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") } it { run_output.should match(/Gears:\s+1 small/) } end context 'when run with scaled app' do before do @domain = rest_client.add_domain("mockdomain") app = @domain.add_application("app1", "mock_type", true) cart1 = app.add_cartridge('mock_cart-1') cart2 = app.add_cartridge('mock_cart-2') cart2.gear_profile = 'medium' end it { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") } it { run_output.should match(/Scaling:.*x2/) } it { run_output.should match(/Gears:\s+Located with mock_type/) } it { run_output.should match(/Gears:\s+1 medium/) } end context 'when run with premium cartridge with single rate' do before do @domain = rest_client.add_domain("mockdomain") app = @domain.add_application("app1", "mock_type", true) cart1 = app.add_cartridge('mock_premium_cart-1') cart1.usage_rates = {0.01 => []} end it { run_output.should match(/This cartridge costs an additional \$0.01 per gear after the first 3 gears\./) } end context 'when run with premium cartridge with multiple rates' do before do @domain = rest_client.add_domain("mockdomain") app = @domain.add_application("app1", "mock_type", true) cart1 = app.add_cartridge('mock_premium_cart-2') cart1.usage_rates = {0.01 => ['plan1','plan2', 'plan3'], 0.02 => ['plan4'], 0.03 => []} end it { run_output.should match(/This cartridge costs an additional \$0\.01 per gear after the first 3 gears on the Plan1, Plan2 and Plan3 plans\./) } it { run_output.should match(/This cartridge costs an additional \$0\.02 per gear after the first 3 gears on the Plan4 plan\./) } it { run_output.should match(/This cartridge costs an additional \$0\.03 per gear after the first 3 gears\./) } end context 'when run with custom app' do before do @domain = rest_client.add_domain("mockdomain") app = @domain.add_application("app1", "mock_type", true) cart1 = app.add_cartridge('mock_cart-1') cart1.url = 'https://foo.bar.com' end it { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") } it { run_output.should match(/Scaling:.*x2/) } it { run_output.should match(/Gears:\s+Located with mock_type/) } it { run_output.should match(/Gears:\s+1 small/) } it { run_output.should match(%r(From:\s+ https://foo.bar.com)) } end context 'when run with app with custom external cartridges' do before do @domain = rest_client.add_domain("mockdomain") app = @domain.add_application("app1", "mock_type") cart1 = app.add_cartridge('mock_cart-1') cart1.url = 'https://foo.bar.com' cart1.tags = ['external'] cart1.version = '2' cart1.license = 'GPL' cart1.website = 'http://bar.com' cart1.current_scale = 0 end context 'verbosely' do let(:arguments) { ['app', 'show', 'app1', '-v'] } it { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") } it { run_output.should match(/Gears:\s+1 small/) } it { run_output.should match(/Gears:\s+none \(external service\)/) } it { run_output.should match(/Description:\s+Description of mock_cart-1/) } it { run_output.should match(%r(Website:\s+ http://bar.com)) } it { run_output.should match(/Version:\s+2/) } it { run_output.should match(/License:\s+GPL/) } it { run_output.should match(%r(From:\s+ https://foo.bar.com)) } end context 'not verbosely' do it { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") } it { run_output.should match(/Gears:\s+1 small/) } it { run_output.should match(/Gears:\s+none \(external service\)/) } it { run_output.should_not match(/Description:\s+Description of mock_cart-1/) } it { run_output.should match(%r(Website:\s+ http://bar.com)) } it { run_output.should_not match(/Version:\s+2/) } it { run_output.should_not match(/License:\s+GPL/) } it { run_output.should match(%r(From:\s+ https://foo.bar.com)) } end end end describe 'app show' do let(:arguments) { ['app', 'show', 'APP1'] } context 'when run with the different case from created' do before do @rc = MockRestClient.new @domain = @rc.add_domain("mockdomain") @domain.add_application("app1", "mock_type") end it { run_output.should match("app1 @ https://app1-mockdomain.fake.foo/") } end end describe 'app show --state' do let(:arguments) { ['app', 'show', 'app1', '--state'] } context 'when run' do before do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") end it { run_output.should match("started") } end end describe 'app show --gears' do context do let(:arguments) { ['app', 'show', 'app1', '--gears', '--raw'] } context 'when run' do before do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") end it { run_output.should match(/ID\s+State\s+Cartridges\s+Size\s+SSH URL/) } it { run_output.should match("fakegearid0 started mock_type small fakegearid0@fakesshurl.com") } it { expect{ run }.to exit_with_code(0) } end context 'with regions and zones' do before do @domain = rest_client.add_domain("mockdomain") @app = @domain.add_application("app1", "mock_type") @app.gears.each{|g| g['region'] = 'south'; g['zone'] = 'west'} end it { run_output.should match(/ID\s+State\s+Cartridges\s+Size\s+Region\s+Zone\s+SSH URL/) } it { run_output.should match(/fakegearid0\s+started\s+mock_type\s+small\s+south\s+west\s+fakegearid0@fakesshurl.com/) } it { expect{ run }.to exit_with_code(0) } end end context do let(:arguments) { ['app', 'show', 'app1', '--gears'] } context 'with cartridge that exposes an endpoint' do before do @domain = rest_client.add_domain("mockdomain") # An application has to be scalable to expose an endpoint. @app = @domain.add_application("app1", "mock_type", true) @app.gears.first['endpoints'] = [{ 'cartridge_name' => 'mock_cart-1' }] end it { (run_output.match(/fakegearid0\s+([^\s]+)\s+small\s+fakegearid0@fakesshurl\.com/)[1]).should be_colorized("started", :green) } end end end describe 'app show --gears quota' do let(:arguments) { ['app', 'show', 'app1', '--gears', 'quota'] } context 'when run' do before do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type", true) expect_multi_ssh('echo "$(du --block-size=1 -s 2>/dev/null | cut -f 1)"', 'fakegearid0@fakesshurl.com' => '1734334', 'fakegearid1@fakesshurl.com' => '1934348') end it { run_output.should match(/Gear.*Cartridges.*Used.*fakegearid0.*1\.7 MB.*1 GB.*fakegearid1.*1\.9 MB/m) } it { expect{ run }.to exit_with_code(0) } end end describe 'app show --gears ssh' do let(:arguments) { ['app', 'show', 'app1', '--gears', 'ssh'] } context 'when run' do before do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type", true) end it { run_output.should == "fakegearid0@fakesshurl.com\nfakegearid1@fakesshurl.com\n\n" } it { expect{ run }.to exit_with_code(0) } end end describe 'app show --gears badcommand' do let(:arguments) { ['app', 'show', 'app1', '--gears', 'badcommand'] } context 'when run' do before{ rest_client.add_domain("mockdomain").add_application("app1", "mock_type", true) } it { run_output.should match(/The operation badcommand is not supported/m) } it { expect{ run }.to exit_with_code(1) } end end describe 'app actions' do before do domain = rest_client.add_domain("mockdomain") app = domain.add_application("app1", "mock_type") app.add_cartridge('mock_cart-1') end context 'app start' do let(:arguments) { ['app', 'start', '-a', 'app1'] } it { run_output.should match('start') } it { expect{ run }.to exit_with_code(0) } end context 'app scale-up' do let(:arguments) { ['app', 'scale-up', '-a', 'app1'] } it { run_output.should match('scaled up') } it { expect{ run }.to exit_with_code(0) } end context 'app scale-down' do let(:arguments) { ['app', 'scale-down', '-a', 'app1'] } it { run_output.should match('scaled down') } it { expect{ run }.to exit_with_code(0) } end context 'app stop' do let(:arguments) { ['app', 'stop', 'app1'] } it { run_output.should match('stop') } it { expect{ run }.to exit_with_code(0) } end context 'app force stop' do let(:arguments) { ['app', 'force-stop', 'app1'] } it { run_output.should match('force') } it { expect{ run }.to exit_with_code(0) } end context 'app restart' do let(:arguments) { ['app', 'restart', 'app1'] } it { run_output.should match('restart') } it { expect{ run }.to exit_with_code(0) } end context 'app reload' do let(:arguments) { ['app', 'reload', 'app1'] } it { run_output.should match('reload') } it { expect{ run }.to exit_with_code(0) } end context 'app tidy' do let(:arguments) { ['app', 'tidy', 'app1'] } it { run_output.should match('cleaned') } it { expect{ run }.to exit_with_code(0) } end context 'app enable-ha' do let(:arguments) { ['app', 'enable-ha', 'app1'] } it { run_output.should match('is now highly available') } it { expect{ run }.to exit_with_code(0) } end end describe 'app enable-ha' do before do @domain = rest_client.add_domain("mockdomain") @app = @domain.add_application("app1", "mock_type") @app.add_cartridge('mock_cart-1') @app.links.delete 'MAKE_HA' end let(:arguments) { ['app', 'enable-ha', 'app1'] } it "should raise ha not supported exception" do run_output.should match(/The server does not support high availability/) expect{ run }.to exit_with_code(135) end end describe "#create_app" do it("should list cartridges when a server error happens") do subject.should_receive(:list_cartridges) domain = double domain.stub(:add_application).and_raise(RHC::Rest::ValidationException.new('Foo', :cartridges, 109)) expect{ subject.send(:create_app, 'name', 'jenkins-1', domain) }.to raise_error(RHC::Rest::ValidationException) end end describe 'create app with env vars' do before{ @domain = rest_client.add_domain("mockdomain") } [['app', 'create', 'app1', 'mock_standalone_cart-1', '-e', 'FOO=BAR'], ['app', 'create', 'app1', 'mock_standalone_cart-1', '--env', 'FOO=BAR'], ['app', 'create', 'app1', 'mock_standalone_cart-1', 'FOO=BAR'] ].each_with_index do |args, i| context "when run with single env var #{i}" do let(:arguments) { args } before { @domain.should_receive(:has_param?).with('ADD_APPLICATION','environment_variables').and_return(true) } it { expect { run }.to exit_with_code(0) } it { run_output.should match("Success") } it { run_output.should match(/Cartridges:\s+mock_standalone_cart-1\n/) } it { run_output.should match(/Environment Variables:\s+FOO=BAR\n/) } end end [['app', 'create', 'app1', 'mock_standalone_cart-1', '-e', 'VAR1=VAL1', '-e', 'VAR2=VAL2', '-e', 'VAR3=VAL3'], ['app', 'create', 'app1', 'mock_standalone_cart-1', '--env', 'VAR1=VAL1', '--env', 'VAR2=VAL2', '--env', 'VAR3=VAL3'], ['app', 'create', 'app1', 'mock_standalone_cart-1', 'VAR1=VAL1', 'VAR2=VAL2', 'VAR3=VAL3'], ['app', 'create', 'app1', 'mock_standalone_cart-1', 'VAR1=VAL1', 'VAR2=VAL2', '-e', 'VAR3=VAL3'], ['app', 'create', 'app1', 'mock_standalone_cart-1', 'VAR1=VAL1', '--env', 'VAR2=VAL2', '-e', 'VAR3=VAL3'] ].each_with_index do |args, i| context "when run with multiple env vars #{i}" do let(:arguments) { args } before { @domain.should_receive(:has_param?).with('ADD_APPLICATION','environment_variables').and_return(true) } it { expect { run }.to exit_with_code(0) } it { run_output.should match("Success") } it { run_output.should match(/Cartridges:\s+mock_standalone_cart-1\n/) } it { run_output.should match(/Environment Variables:\s+VAR1=VAL1, VAR2=VAL2, VAR3=VAL3\n/) } end end [['app', 'create', 'app1', 'mock_standalone_cart-1', '-e', 'FOO=BAR'], ['app', 'create', 'app1', 'mock_standalone_cart-1', '--env', 'FOO=BAR'], ['app', 'create', 'app1', 'mock_standalone_cart-1', 'FOO=BAR'] ].each_with_index do |args, i| context "when run against a server without env vars support #{i}" do let(:arguments) { args } before { @domain.should_receive(:has_param?).with('ADD_APPLICATION','environment_variables').and_return(false) } it { expect { run }.to exit_with_code(0) } it { run_output.should match("Success") } it { run_output.should match(/Cartridges:\s+mock_standalone_cart-1\n/) } it { run_output.should match("Server does not support environment variables") } it { run_output.should_not match(/Environment Variables:\s+FOO=BAR\n/) } end end [['app', 'create', 'app1', 'mock_standalone_cart-1', '-e', 'FOOBAR'], ['app', 'create', 'app1', 'mock_standalone_cart-1', '--env', 'FOOBAR'], ['app', 'create', 'app1', 'mock_standalone_cart-1', '-eFOOBAR'] ].each_with_index do |args, i| context "when run with syntactically incorrect env vars #{i}" do let(:arguments) { args } it { expect { run }.to exit_with_code(159) } it { run_output.should match(/Environment variable\(s\) not provided.\nPlease provide at least one environment variable using the syntax VARIABLE=VALUE\. VARIABLE can only contain letters, digits and underscore \('_'\) and can't begin with a digit\./) } end end end end rhc-1.38.7/spec/rhc/commands/setup_spec.rb0000644000004100000410000001077512756270552020451 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/setup' # just test the command runner as we already have extensive wizard tests describe RHC::Commands::Setup do subject{ RHC::Commands::Setup } let(:instance){ subject.new } let!(:config){ base_config } before{ described_class.send(:public, *described_class.protected_instance_methods) } before{ FakeFS::FileSystem.clear } before{ RHC::Config.stub(:home_dir).and_return('/home/mock_user') } before{ RHC::Servers.any_instance.stub(:load) } describe '#run' do it{ expects_running('setup').should call(:run).on(instance).with(no_args) } let(:arguments) { ['setup', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password'] } before(:each) do @wizard = double('wizard') @wizard.stub(:run).and_return(true) RHC::RerunWizard.stub(:new){ @wizard } end context 'when no issues' do it "should exit 0" do expect { run }.to exit_with_code(0) end end context 'when there is an issue' do it "should exit 1" do @wizard.stub(:run).and_return(false) expect { run }.to exit_with_code(1) end end end it{ expects_running('setup').should call(:run).on(instance).with(no_args) } it{ command_for('setup', '--clean').options.clean.should be_true } it{ command_for('setup').options.server.should == 'openshift.redhat.com' } it{ command_for('setup').options.create_token.should be_nil } it{ command_for('setup', '--server', 'foo.com').options.server.should == 'foo.com' } it{ command_for('setup', '--no-create-token').options.create_token.should == false } it{ command_for('setup', '--create-token').options.create_token.should == true } context "when config has use_authorization_tokens=false" do let!(:config){ base_config{ |c, d| d.add('use_authorization_tokens', 'false') } } it{ command_for('setup').options.use_authorization_tokens.should == false } end context "when config has use_authorization_tokens=true" do let!(:config){ base_config{ |c, d| d.add('use_authorization_tokens', 'true') } } it{ command_for('setup').options.use_authorization_tokens.should be_true } end =begin context 'when libra_server is set' do before{ ENV.stub(:[]).with('LIBRA_SERVER').and_return('bar.com') } it{ command_for('setup').config['libra_server'].should == 'bar.com' } it{ command_for('setup').options.server.should == 'bar.com' } it{ command_for('setup', '--server', 'foo.com').options.server.should == 'foo.com' } =end end context 'when --clean is used' do let!(:config){ base_config{ |config, defaults| defaults.add 'libra_server', 'test.com' } } it("should ignore a config value"){ command_for('setup', '--clean').options.server.should == 'openshift.redhat.com' } end context 'when -d is passed' do let(:arguments) { ['setup', '-d', '-l', 'test@test.foo'] } # 'y' for the password prompt let(:input) { ['', 'y', '', ''] } let!(:rest_client){ MockRestClient.new } it("succeeds"){ FakeFS{ expect { run input }.to exit_with_code 0 } } it("the output includes debug output") do FakeFS{ run_output( input ).should match 'DEBUG' } end end context 'when -l is used to specify the user name' do let(:arguments) { ['setup', '-l', 'test@test.foo'] } # 'y' for the password prompt let(:input) { ['', 'y', '', ''] } let!(:rest_client){ MockRestClient.new } it("succeeds"){ FakeFS{ expect { run input }.to exit_with_code 0 } } it("sets the user name to the value given by the command line") do FakeFS{ run_output( input ).should match 'test@test.foo' } end end describe 'help' do let(:arguments) { ['setup', '--help'] } context 'help is run' do it "should display help" do @wizard.stub(:run).and_return(true) expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Connects to an OpenShift server to get you started. Will") } end end describe '--autocomplete' do let(:arguments) { ['setup', '--autocomplete'] } before do path = File.join(Gem.loaded_specs['rhc'].full_gem_path, "autocomplete") FakeFS::FileUtils.mkdir_p(path) FakeFS::FileUtils.touch(File.join(path, "rhc_bash")) end context 'is passed' do it('should output information') { FakeFS{ run_output.should match("To enable tab-completion") } } it('should output the gem path') { FakeFS{ run_output.should match File.join(RHC::Config.home_conf_dir, 'bash_autocomplete') } } end end end rhc-1.38.7/spec/rhc/commands/snapshot_spec.rb0000644000004100000410000002016012756270552021135 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/snapshot' require 'rhc/config' require 'rhc/tar_gz' describe RHC::Commands::Snapshot do APP_NAME = 'mockapp' let!(:rest_client) { MockRestClient.new } before do user_config @app = rest_client.add_domain("mockdomain").add_application APP_NAME, 'mock-1.0' @ssh_uri = URI.parse @app.ssh_url @targz_filename = APP_NAME + '.tar.gz' FileUtils.cp(File.expand_path('../../assets/targz_sample.tar.gz', __FILE__), @targz_filename) File.chmod 0644, @targz_filename unless File.executable? @targz_filename end after do File.delete @targz_filename if File.exist? @targz_filename end describe 'snapshot without an action' do let(:arguments) {['snapshot', '--trace', '--noprompt']} it('should raise') { expect{ run }.to raise_error(ArgumentError, /Please specify an action to take/) } end describe 'snapshot save' do let(:arguments) {['snapshot', 'save', '--app', 'mockapp', '-d']} context 'when saving a snapshot' do before do subject.class.any_instance.should_receive(:exec).with("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'snapshot' > #{@app.name}.tar.gz").and_return([0, 'some save output']) end it { expect { run }.to exit_with_code(0) } it { run_output.should_not match 'some save output' } end context 'when failing to save a snapshot' do before do subject.class.any_instance.should_receive(:exec).with("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'snapshot' > #{@app.name}.tar.gz").and_return([1, 'some save failures']) end it { expect { run }.to exit_with_code(130) } it { run_output.should match 'some save failures' } end context 'when saving a snapshot on windows' do before do RHC::Helpers.stub(:windows?) do ; true; end RHC::Helpers.stub(:jruby?) do ; false ; end RHC::Helpers.stub(:linux?) do ; false ; end ssh = double(Net::SSH) Net::SSH.should_receive(:start).with(@ssh_uri.host, @ssh_uri.user).and_yield(ssh) subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' } subject.class.any_instance.stub(:discover_git_executable){ 'git' } ssh.should_receive(:exec!).with("snapshot").and_yield(nil, :stdout, 'foo').and_yield(nil, :stderr, 'foo') end it { expect { run }.to exit_with_code(0) } it { run_output.should match("done") } end context 'when timing out on windows' do before do RHC::Helpers.stub(:windows?) do ; true; end RHC::Helpers.stub(:jruby?) do ; false ; end RHC::Helpers.stub(:linux?) do ; false ; end ssh = double(Net::SSH) Net::SSH.should_receive(:start).with(@ssh_uri.host, @ssh_uri.user).and_raise(Timeout::Error) subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' } subject.class.any_instance.stub(:discover_git_executable){ 'git' } end it { expect { run }.to exit_with_code(130) } end describe 'snapshot save deployment' do let(:arguments) {['snapshot', 'save', '--app', 'mockapp', '--deployment', '-d']} context 'when saving a deployment snapshot' do before do subject.class.any_instance.should_receive(:exec).with("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'gear archive-deployment' > #{@app.name}.tar.gz").and_return([0, 'some save output']) end it { expect { run }.to exit_with_code(0) } it { run_output.should_not match 'some save output' } end end end describe 'snapshot save with invalid ssh executable' do let(:arguments) {['snapshot', 'save', '--trace', '--noprompt', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--ssh', 'path_to_ssh']} it('should raise') { expect{ run }.to raise_error(RHC::InvalidSSHExecutableException, /SSH executable 'path_to_ssh' does not exist./) } end describe 'snapshot save when ssh is not executable' do let(:arguments) {['snapshot', 'save', '--trace', '--noprompt', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--ssh', @targz_filename]} it('should raise') { expect{ run }.to raise_error(RHC::InvalidSSHExecutableException, /SSH executable '#{@targz_filename}' is not executable./) } end describe 'snapshot restore' do let(:arguments) {['snapshot', 'restore', '--app', 'mockapp', '-d']} context 'when restoring a snapshot' do before do File.stub(:exists?).and_return(true) RHC::TarGz.stub(:contains).and_return(true) subject.class.any_instance.should_receive(:exec).with("cat '#{@app.name}.tar.gz' | ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'restore INCLUDE_GIT'").and_return([0, 'some restore output']) end it('should succeed') { expect { run }.to exit_with_code(0) } it { run_output.should_not match 'some restore output' } end context 'when restoring a snapshot and failing to ssh' do before do File.stub(:exists?).and_return(true) RHC::TarGz.stub(:contains).and_return(true) subject.class.any_instance.should_receive(:exec).with("cat '#{@app.name}.tar.gz' | ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'restore INCLUDE_GIT'").and_return([1, 'some restore failures']) end it { expect { run }.to exit_with_code(130) } it { run_output.should match 'some restore failures' } end context 'when restoring a snapshot on windows' do before do RHC::Helpers.stub(:windows?) do ; true; end RHC::Helpers.stub(:jruby?) do ; false ; end RHC::Helpers.stub(:linux?) do ; false ; end ssh = double(Net::SSH) session = double(Net::SSH::Connection::Session) channel = double(Net::SSH::Connection::Channel) Net::SSH.should_receive(:start).with(@ssh_uri.host, @ssh_uri.user).and_return(session) session.should_receive(:open_channel).and_yield(channel) channel.should_receive(:exec).with("restore INCLUDE_GIT").and_yield(nil, nil) channel.should_receive(:on_data).and_yield(nil, 'foo') channel.should_receive(:on_extended_data).and_yield(nil, nil, 'foo') channel.should_receive(:on_close).and_yield(nil) lines = '' subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' } subject.class.any_instance.stub(:discover_git_executable){ 'git' } File.open(File.expand_path('../../assets/targz_sample.tar.gz', __FILE__), 'rb') do |file| file.chunk(1024) do |chunk| lines << chunk end end channel.should_receive(:send_data).with(lines) channel.should_receive(:eof!) session.should_receive(:loop) end it { expect { run }.to exit_with_code(0) } end context 'when timing out on windows' do before do RHC::Helpers.stub(:windows?) do ; true; end RHC::Helpers.stub(:jruby?) do ; false ; end RHC::Helpers.stub(:linux?) do ; false ; end ssh = double(Net::SSH) Net::SSH.should_receive(:start).with(@ssh_uri.host, @ssh_uri.user).and_raise(Timeout::Error) subject.class.any_instance.stub(:discover_ssh_executable){ 'ssh' } subject.class.any_instance.stub(:discover_git_executable){ 'git' } end it { expect { run }.to exit_with_code(130) } end end describe 'snapshot restore file not found' do let(:arguments) {['snapshot', 'restore', 'mockapp', '-f', 'foo.tar.gz']} context 'when restoring a snapshot' do it { expect { run }.to exit_with_code(130) } end end describe 'snapshot restore with invalid ssh executable' do let(:arguments) {['snapshot', 'restore', '--trace', '--noprompt', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--ssh', 'path_to_ssh']} it('should raise') { expect{ run }.to raise_error(RHC::InvalidSSHExecutableException, /SSH executable 'path_to_ssh' does not exist./) } end describe 'snapshot save when ssh is not executable' do let(:arguments) {['snapshot', 'restore', '--trace', '--noprompt', '-l', 'test@test.foo', '-p', 'password', '--app', 'mockapp', '--ssh', @targz_filename]} it('should raise') { expect{ run }.to raise_error(RHC::InvalidSSHExecutableException, /SSH executable '#{@targz_filename}' is not executable./) } end end rhc-1.38.7/spec/rhc/commands/region_spec.rb0000644000004100000410000000411312756270552020561 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/region' require 'rhc/config' describe RHC::Commands::Region do before{ user_config } describe 'region list' do let(:arguments){ ['region', 'list'] } let(:username){ nil } let(:password){ nil } let(:server){ mock_uri } let(:user_auth){ false } context 'with server regions' do before do stub_api stub_simple_regions end it{ run_output.should match /Server test\.domain\.com$/ } it{ run_output.should match /Region 'north' \(uuid: region0001\)$/ } it{ run_output.should match /Description:\s+Servers in the north of US$/ } it{ run_output.should match /Available Zones:\s+east, west$/ } it{ run_output.should match /Region 'south' \(uuid: region0002\) \(default\)/ } it{ run_output.should match /Available Zones: east$/ } it{ expect{ run }.to exit_with_code(0) } end context 'when allowing region selection' do before do stub_api stub_simple_regions(false, true) end it{ run_output.should match /To create an app in a specific region use/ } it{ expect{ run }.to exit_with_code(0) } end context 'when not allowing region selection' do before do stub_api stub_simple_regions(false, false) end it{ run_output.should match /Regions can't be explicitly provided by users/ } it{ expect{ run }.to exit_with_code(0) } end context 'without server regions' do before do stub_api stub_simple_regions(true) end it{ run_output.should_not match /Available Zones$/ } it{ run_output.should match /Server doesn't have any regions or zones configured/ } it{ expect{ run }.to exit_with_code(169) } end context 'regions not supported on server' do let!(:rest_client){ MockRestClient.new } it{ run_output.should_not match /Available Zones$/ } it{ run_output.should match /Server does not support regions and zones/ } it{ expect{ run }.to exit_with_code(168) } end end end rhc-1.38.7/spec/rhc/commands/authorization_spec.rb0000644000004100000410000001467512756270552022214 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc' require 'rhc/commands/authorization' describe RHC::Commands::Authorization do def self.with_authorization let(:username) { 'foo' } let(:password) { 'pass' } let(:server) { mock_uri } before{ user_config } before{ stub_api(false, true) } end def self.without_authorization let(:username) { 'foo' } let(:password) { 'pass' } let(:server) { mock_uri } before{ user_config } before{ stub_api(false, false) } end def self.expect_an_unsupported_message context "without authorizations" do without_authorization it('should warn that the server doesn\'t support auth'){ run_output.should =~ /The server does not support setting, retrieving, or authenticating with authorization tokens/ } it{ expect{ run }.to exit_with_code(1) } end end describe '#run' do let(:arguments) { ['authorizations'] } context "with authorizations" do with_authorization before{ challenge{ stub_authorizations } } it('should display the note') { run_output.should =~ /an_authorization/ } it('should display the token') { run_output.should =~ /Token:\s+a_token_value/ } it('should display the expiration') { run_output.should =~ /Expires In:\s+1 minute/ } it('should display the creation date') { run_output.should =~ /Created:\s+#{RHC::Helpers.date('2013-02-21T01:00:01Z')}/ } it('should display the scopes') { run_output.should =~ /Scopes:\s+session read/ } it{ expect{ run }.to exit_with_code(0) } end expect_an_unsupported_message end describe '#run' do let(:arguments) { ['authorization']} context 'given no arguments' do it('should display help'){ run_output.should =~ /An authorization token grants access to the OpenShift REST API.*To see all your authorizations/m } it 'should ask for an argument' do expect{ run }.to exit_with_code(1) end end end describe '#list' do let(:arguments) { ['authorization', 'list'] } context "with authorizations" do with_authorization before{ challenge{ stub_authorizations } } it('should display the note') { run_output.should =~ /an_authorization/ } it('should display the token') { run_output.should =~ /Token:\s+a_token_value/ } it('should display the expiration') { run_output.should =~ /Expires In:\s+1 minute/ } it('should display the creation date') { run_output.should =~ /Created:\s+#{RHC::Helpers.date('2013-02-21T01:00:01Z')}/ } it('should display the scopes') { run_output.should =~ /Scopes:\s+session read/ } it{ expect{ run }.to exit_with_code(0) } end expect_an_unsupported_message end describe "#delete" do let(:arguments) { ['authorization', 'delete', 'foo', 'bar'] } context "with authorizations" do with_authorization before{ challenge{ stub_delete_authorization('foo') } } before{ challenge{ stub_delete_authorization('bar') } } it('should display success') { run_output.should =~ /Deleting auth.*done/ } it{ expect{ run }.to exit_with_code(0) } after{ a_request(:delete, mock_href('broker/rest/user/authorizations/foo', true)).should have_been_made } after{ a_request(:delete, mock_href('broker/rest/user/authorizations/bar', true)).should have_been_made } end context "without a token in the command line" do let(:arguments) { ['authorization', 'delete'] } it('should display success') { run_output.should =~ /You must specify one or more tokens to delete/ } it{ expect{ run }.to exit_with_code(1) } end expect_an_unsupported_message end describe "#delete_all" do let(:arguments) { ['authorization', 'delete-all'] } context "with authorizations" do with_authorization before{ challenge{ stub_delete_authorizations } } it('should display success') { run_output.should =~ /Deleting all auth.*done/ } it{ expect{ run }.to exit_with_code(0) } after{ a_request(:delete, mock_href('broker/rest/user/authorizations', true)).should have_been_made } end expect_an_unsupported_message end describe "#scope_help" do let(:rest_client){ double(:authorization_scope_list => [['scope_1', 'A description'], ['scope_2', 'Another description']]) } before{ subject.should_receive(:rest_client).and_return(rest_client) } it{ capture{ subject.send(:scope_help) }.should =~ /scope_1.*A description/ } it{ capture{ subject.send(:scope_help) }.should =~ /scope_2.*Another description/ } it{ capture{ subject.send(:scope_help) }.should =~ /You may pass multiple scopes/ } end describe "#add" do let(:arguments) { ['authorization', 'add'] } context "with authorizations" do using_command_instance with_authorization before{ instance.should_receive(:scope_help) } it('should display the scope help') { command_output.should =~ /When adding an authorization.*to see more options/m } it{ expect{ run_command }.to exit_with_code(0) } end expect_an_unsupported_message context "with empty scope options" do let(:arguments) { ['authorization', 'add', '--scopes', ' ', '--note', 'a_note', '--expires-in', '300'] } using_command_instance with_authorization before{ instance.should_receive(:scope_help) } it('should display the scope help') { command_output.should =~ /When adding an authorization.*to see more options/m } it{ expect{ run_command }.to exit_with_code(0) } end context "with options" do let(:arguments) { ['authorization', 'add', '--scope', 'foo,bar', '--note', 'a_note', '--expires-in', '300'] } with_authorization before{ challenge{ stub_add_authorization(:note => 'a_note', :scope => 'foo,bar', :expires_in => '300') } } it('should display success') { run_output.should =~ /Adding authorization.*done/ } it('should display the note') { run_output.should =~ /a_note/ } it('should display the token') { run_output.should =~ /Token:\s+a_token_value/ } it('should display the expiration') { run_output.should =~ /Expires In:\s+5 minutes/ } it('should display the creation date') { run_output.should =~ /Created:\s+#{RHC::Helpers.date(mock_date_1)}/ } it('should display the scopes') { run_output.should =~ /Scopes:\s+foo bar/ } it{ expect{ run }.to exit_with_code(0) } expect_an_unsupported_message end end end rhc-1.38.7/spec/rhc/commands/threaddump_spec.rb0000644000004100000410000000714512756270552021443 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/threaddump' require 'rhc/config' describe RHC::Commands::Threaddump do let(:client_links) { mock_response_links(mock_client_links) } let(:domain_0_links) { mock_response_links(mock_domain_links('mock_domain_0')) } let(:domain_1_links) { mock_response_links(mock_domain_links('mock_domain_1')) } let(:app_0_links) { mock_response_links(mock_app_links('mock_domain_0', 'mock_app_0')) } let!(:rest_client){ MockRestClient.new } before(:each) do user_config rest_client.add_domain("mock_domain_0").add_application("mock_app_0", "ruby-1.8.7") stub_api_request(:any, client_links['LIST_DOMAINS']['relative']). to_return({ :body => { :type => 'domains', :data => [{ :id => 'mock_domain_0', :links => mock_response_links(mock_domain_links('mock_domain_0')), }, { :id => 'mock_domain_1', :links => mock_response_links(mock_domain_links('mock_domain_1')), }] }.to_json, :status => 200 }) stub_api_request(:any, domain_0_links['LIST_APPLICATIONS']['relative']). to_return({ :body => { :type => 'applications', :data => [{ :domain_id => 'mock_domain_0', :name => 'mock_app_0', :creation_time => Time.new.to_s, :uuid => 1234, :aliases => ['alias_1', 'alias_2'], :server_identity => 'mock_server_identity', :links => mock_response_links(mock_app_links('mock_domain_0','mock_app_0')), }] }.to_json, :status => 200 }) stub_api_request(:post, app_0_links['THREAD_DUMP']['relative'], false). to_return({ :body => { :type => 'application', :data => { :domain_id => 'mock_domain_1', :name => 'mock_app_0', :creation_time => Time.new.to_s, :uuid => 1234, :aliases => ['alias_1', 'alias_2'], :server_identity => 'mock_server_identity', :links => mock_response_links(mock_app_links('mock_domain_1','mock_app_0')), }, :messages => [{:text => 'Application test thread dump complete.: Success', :severity => 'result'}] }.to_json, :status => 200 }) end describe 'help' do let(:arguments) { ['threaddump', '--help'] } context 'help is run' do it "should display help" do expect { run }.to exit_with_code(0) end it('should output usage') { run_output.should match("Usage: rhc threaddump") } end end describe 'threaddump' do let(:arguments) { ['threaddump', 'mock_app_0'] } it { expect { run }.to exit_with_code(0) } it { run_output.should =~ /Application test thread dump complete/ } end describe 'threaddump no args' do let(:arguments) { ['threaddump'] } context 'args not supplied' do it { expect { run }.to exit_with_code(1) } end end end rhc-1.38.7/spec/rhc/commands/scp_spec.rb0000644000004100000410000000765412756270552020100 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/scp' describe RHC::Commands::Scp do let!(:rest_client){ MockRestClient.new } let!(:config){ user_config } before{ RHC::Config.stub(:home_dir).and_return('/home/mock_user') } before{ Kernel.stub(:exec).and_raise(RuntimeError) } describe 'scp default' do context 'scp' do let(:arguments) { ['scp'] } it { run_output.should match('Usage:') } end end describe 'scp with invalid option' do let (:arguments) {['app', 'scp', 'app1', 'invalid_command', 'file.txt', 'app-root/data']} context 'when run' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") end it { run_output.should match("'invalid_command' is not a valid argument for this command. Please use upload or download.") } end end describe 'local file or path does not exist' do let (:arguments) {['app', 'scp', 'app1', 'upload', 'file.txt', 'app-root/data']} context 'when run' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") File.should_receive(:exist?).with("file.txt").once.and_return(false) File.should_receive(:exist?).with("git").at_least(1).and_return(true) end it { run_output.should match("Local file, file_path, or directory could not be found.") } end end describe 'scp connections' do let (:arguments) {['app', 'scp', 'app1', 'upload', 'file.txt', 'app-root/data']} context 'connection refused' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") File.should_receive(:exist?).with("file.txt").once.and_return(true) File.should_receive(:exist?).with("git").at_least(1).and_return(true) Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(Errno::ECONNREFUSED) end it { run_output.should match("The server fakeuuidfortestsapp1 refused a connection with user 127.0.0.1. The application may be unavailable.") } end context 'socket error' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") File.should_receive(:exist?).with("file.txt").once.and_return(true) File.should_receive(:exist?).with("git").at_least(1).and_return(true) Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(SocketError) end it { run_output.should match("The connection to 127.0.0.1 failed: SocketError") } end context 'authentication error' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") File.should_receive(:exist?).with("file.txt").once.and_return(true) File.should_receive(:exist?).with("git").at_least(1).and_return(true) Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(Net::SSH::AuthenticationFailed) end it { run_output.should match("Authentication to server 127.0.0.1 with user fakeuuidfortestsapp1 failed") } end context 'unknown error' do before(:each) do @domain = rest_client.add_domain("mockdomain") @domain.add_application("app1", "mock_type") File.should_receive(:exist?).with("file.txt").once.and_return(true) File.should_receive(:exist?).with("git").at_least(1).and_return(true) Net::SCP.should_receive("upload!".to_sym).with("127.0.0.1", "fakeuuidfortestsapp1","file.txt","app-root/data").and_raise(Net::SCP::Error.new("SCP error message")) end it { run_output.should match("An unknown error occurred: SCP error message") } end end end rhc-1.38.7/spec/rhc/commands/git_clone_spec.rb0000644000004100000410000001073712756270552021252 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/commands/git_clone' require 'fileutils' describe RHC::Commands::GitClone do before(:each) do FakeFS.activate! FakeFS::FileSystem.clear user_config @instance = RHC::Commands::GitClone.new RHC::Commands::GitClone.stub(:new) do @instance.stub(:git_config_get) { "" } @instance.stub(:git_config_set) { "" } Kernel.stub(:sleep) { } @instance.stub(:host_exists?) do |host| host.match("dnserror") ? false : true end @instance end end let!(:rest_client){ MockRestClient.new } before(:each) do @domain = rest_client.add_domain("mockdomain") @app = @domain.add_application("app1", "mock_unique_standalone_cart") end after(:each) do FakeFS.deactivate! end describe 'git-clone' do let(:arguments) { ['app', 'git-clone', 'app1'] } context 'when run without git installed' do before do @instance.stub(:discover_git_executable){ nil } end it "should print out git warning" do run_output.should match("You do not have git installed") end end context "stubbing git_clone_repo" do context "reports success successfully" do before do @instance.stub(:git_clone_repo) do |git_url, repo_dir| Dir::mkdir(repo_dir) say "Cloned" true end @instance.stub(:discover_git_executable){ 'git' } end it { expect { run }.to exit_with_code(0) } it { run_output.should match("Cloned") } context 'when app has an initial git url' do before do @app2 = @domain.add_application("app2", "mock_unique_standalone_cart", nil, "default", "git://test") @instance.stub(:git_remote_add) do |remote_name, remote_url| say "Added remote #{remote_name} pointing to #{remote_url}" true end end let(:arguments) { ['git-clone', 'app2'] } it { run_output.should match("Added remote upstream pointing to git://test") } end end context "reports success successfully on windows" do before do RHC::Helpers.stub(:windows?) do ; true; end RHC::Helpers.stub(:jruby?) do ; false ; end RHC::Helpers.stub(:linux?) do ; false ; end @instance.stub(:git_clone_repo) do |git_url, repo_dir| Dir::mkdir(repo_dir) say "Cloned" true end @instance.stub(:discover_git_executable){ 'git.exe' } end it { expect { run }.to exit_with_code(0) } it { run_output.should match("Cloned") } context 'when app has an initial git url' do before do @app2 = @domain.add_application("app2", "mock_unique_standalone_cart", nil, "default", "git://test") @instance.stub(:git_remote_add) do |remote_name, remote_url| say "Added remote #{remote_name} pointing to #{remote_url}" true end end let(:arguments) { ['git-clone', 'app2'] } it { run_output.should match("Added remote upstream pointing to git://test") } end end context "testing git_clone_deploy_hooks" do before do @instance.stub(:git_clone_repo) do |git_url, repo_dir| FileUtils.mkdir_p "#{repo_dir}/.git/hooks" FileUtils.mkdir_p "#{repo_dir}/.openshift/git_hooks" FileUtils.touch "#{repo_dir}/.openshift/git_hooks/pre_commit" @instance.git_clone_deploy_hooks(repo_dir) say "Copied" if File.exists?("#{repo_dir}/.git/hooks/pre_commit") true end @instance.stub(:discover_git_executable){ 'git' } # Get around the FakeFS bug (defunkt/fakefs#177) by # stubbing the #cp call to inject a expected fs entry FileUtils.stub(:cp) do |hook, dir| FakeFS::FileSystem.add( File.join(dir, File.basename(hook)), FakeFS::FileSystem.find(hook)) end end it { expect { run }.to exit_with_code(0) } it { run_output.should match("Copied") } end context "reports failure" do before do @instance.stub(:git_clone_repo).and_raise(RHC::GitException) @instance.stub(:discover_git_executable){ 'git' } end it { expect { run }.to exit_with_code(216) } it { run_output.should match("Git returned an error") } end end end end rhc-1.38.7/spec/rhc/cli_spec.rb0000644000004100000410000001654712756270552016262 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/wizard' describe RHC::CLI do shared_examples_for 'a global help page' do let(:arguments) { @arguments or raise "no arguments" } it('should contain the program description') { run_output.should =~ /Command line interface for OpenShift/ } it('should describe getting started') { run_output.should =~ /Getting started:/ } it('should describe basic command') { run_output.should =~ /Working with apps:/ } it('should mention the help command') { run_output.should =~ /See 'rhc help '/ } it('should mention the help options command') { run_output.should =~ /rhc help options/ } end shared_examples_for 'a first run wizard' do let(:arguments) { @arguments or raise "no arguments" } let!(:wizard){ RHC::Wizard.new } before{ RHC::Wizard.should_receive(:new).and_return(wizard) } it('should create and run a new wizard') { expect{ run }.to call(:run).on(wizard) } end shared_examples_for 'a help page' do let(:arguments) { @arguments or raise "no arguments" } it('should contain the program description') { run_output.should =~ /Command line interface for OpenShift/ } it('should contain the global options') { run_output.should =~ /Global Options/ } it('should provide a --config switch') { run_output.should =~ /\-\-config FILE/ } end shared_examples_for 'a list of all commands' do let(:arguments) { @arguments or raise "no arguments" } it('should contain a message') { run_output.should =~ /Showing all commands/ } it('should contain command help') { run_output.should =~ /Create an application./ } it('should contain aliases') { run_output.should =~ /\(also/ } end shared_examples_for 'a list of commands' do let(:arguments) { @arguments or raise "no arguments" } it('should contain a message') { run_output.should =~ /Showing commands matching '/ } it('should contain command help') { run_output.should =~ /Create an application./ } it('should contain aliases') { run_output.should =~ /\(also/ } end shared_examples_for 'a command-line options help page' do let(:arguments) { @arguments or raise "no arguments" } it('should contain an introduction') { run_output.should =~ /The following options can be passed to any/ } it('should reference the configuration') { run_output.should match(".openshift/express.conf") } it('should describe the --config switch') { run_output.should =~ /\-\-config FILE/ } it('should describe the --ssl-version switch') { run_output.should =~ /\-\-ssl\-version VERSION/ } end shared_examples_for 'an invalid command' do let(:arguments) { @arguments } it('should contain the invalid command message') { run_output.should =~ /is not recognized/ } it('should contain the arguments') { run_output.should include(@arguments[0]) } it('should reference --help') { run_output.should =~ / help\b/ } end shared_examples_for 'version output' do let(:arguments) { @arguments } it 'should contain version output' do run_output.should =~ /rhc \d+\.\d+(:?\.d+)?/ end end before{ base_config } describe "--version" do context "by itself" do before :each do @arguments = ['--version'] end it_should_behave_like 'version output' end end describe '#start' do before{ RHC::Wizard.stub(:has_configuration?).and_return(true) } context 'with no arguments' do before(:each) { @arguments = [] } it_should_behave_like 'a global help page' context "without a config file" do it_should_behave_like 'a global help page' end end context 'with an ambiguous option' do let(:arguments){ ['help', '-s'] } it('should describe an ambiguous error'){ run_output.should match("The option -s is ambiguous. You will need to specify the entire option.") } end context 'with an invalid command' do before(:each) { @arguments = ['invalidcommand'] } it_should_behave_like 'an invalid command' end context 'with --help and invalid command' do before(:each) { @arguments = ['invalidcommand', '--help'] } it_should_behave_like 'an invalid command' end context 'with help and invalid command' do before(:each) { @arguments = ['help', 'invalidcommand'] } it_should_behave_like 'an invalid command' end context 'with help commands' do before(:each) { @arguments = ['help', 'commands'] } it_should_behave_like 'a list of all commands' end context 'with help and possible command matches' do before(:each) { @arguments = ['help', 'app c'] } it_should_behave_like 'a list of commands' end context 'with help and a single matching command segment' do let(:arguments){ ['help', 'app creat'] } it("prints the usage for the command"){ run_output.should match('Usage: rhc app-create <') } it("prints part of the description for the command"){ run_output.should match('OpenShift runs the components of your') } it("prints a local option"){ run_output.should match('--namespace NAME') } end context 'with --help' do before(:each){ @arguments = ['--help'] } it_should_behave_like 'a global help page' context 'without a config file' do before{ RHC::Wizard.stub(:has_configuration?).and_return(false) } it_should_behave_like 'a global help page' end end context 'with -h' do before(:each){ @arguments = ['-h'] } it_should_behave_like 'a global help page' end context 'with help' do before(:each){ @arguments = ['help'] } it_should_behave_like 'a global help page' end context 'with help options' do before(:each){ @arguments = ['help', 'options'] } it_should_behave_like 'a command-line options help page' end end describe 'option provided twice' do before{ user_config } let!(:rest_client){ MockRestClient.new } before(:each) do domain = rest_client.add_domain("mock_domain") @app = domain.add_application("app1", "mock_type") end context 'when run with -a provided twice, last being the valid one' do let(:arguments) { ['cartridge', 'remove', 'mock_cart-1', '--confirm', '--trace','-a', 'app2', '-a', 'app1', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password'] } it "should remove cartridge" do @app.add_cartridge('mock_cart-1') expect { run }.to exit_with_code(0) end end context 'when run with -a provided twice, last not being the valid one' do let(:arguments) { ['cartridge', 'remove', 'mock_cart-1', '--confirm', '--trace','-a', 'app1', '-a', 'app2', '--noprompt', '--config', 'test.conf', '-l', 'test@test.foo', '-p', 'password'] } it "should raise an application not found exception" do expect{ run }.to raise_error(RHC::Rest::ApplicationNotFoundException) end end end describe '#set_terminal' do before(:each) { mock_terminal } it('should update $terminal.wrap_at') do $stdout.should_receive(:tty?).twice.and_return(true) HighLine::SystemExtensions.should_receive(:terminal_size).and_return([5]) expect { RHC::CLI.set_terminal }.to change($terminal, :wrap_at) end it('should not update $terminal.page_at') do $stdout.should_receive(:tty?).twice.and_return(true) expect { RHC::CLI.set_terminal }.to_not change($terminal, :page_at) end end end rhc-1.38.7/spec/rhc/assets/0000755000004100000410000000000012756270552015441 5ustar www-datawww-datarhc-1.38.7/spec/rhc/assets/cert.crt0000644000004100000410000000244512756270552017115 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIIDoDCCAogCCQDzF8AJCHnrbjANBgkqhkiG9w0BAQUFADCBkTELMAkGA1UEBhMC VVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxDzANBgNVBAoMBnJl ZGhhdDESMBAGA1UECwwJb3BlbnNoaWZ0MRIwEAYDVQQDDAlvcGVuc2hpZnQxKDAm BgkqhkiG9w0BCQEWGWluZm9Ab3BlbnNoaWZ0LnJlZGhhdC5jb20wHhcNMTMwMjE5 MjExMTQ4WhcNMTQwMjE5MjExMTQ4WjCBkTELMAkGA1UEBhMCVVMxCzAJBgNVBAgM AkNBMRIwEAYDVQQHDAlTdW5ueXZhbGUxDzANBgNVBAoMBnJlZGhhdDESMBAGA1UE CwwJb3BlbnNoaWZ0MRIwEAYDVQQDDAlvcGVuc2hpZnQxKDAmBgkqhkiG9w0BCQEW GWluZm9Ab3BlbnNoaWZ0LnJlZGhhdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB DwAwggEKAoIBAQDAEbH4MCi3iIDP1HS+/Xwu8SjdSc5WJX6htV7hJpmFZ8HohV/8 ba0v6aM9IJIIt+sIe2J62t/9G3leOdIHBxeACN4fV2l/iA/fvxvlnFKeD7sHm9Oc Yj1H6YYJ57sIOf/oLDpJl6l3Rw8VC3+3W0/lzlVpA8qt7fpkiW7XQJCPplUSrdVC 3okQ2T5NAod5+wVIOqELgE5bLX1LRs5VPsjytHkJ7rKXs55FHR3kpsoImn5xD0Ky 6lRn8cIMolQoyN5HIGr8f5P+07hrHibve8jje/DKTssb5yEUAEmh6iGHQsRAnsUW QoIEUOLqQCu9re2No4G52Kl2xQIjyJF7rCfxAgMBAAEwDQYJKoZIhvcNAQEFBQAD ggEBAGHrya/ZkiAje2kHsOajXMlO2+y1iLfUDcRLuEWpUa8sI5EM4YtemQrsupFp 8lVYG5C4Vh8476oF9t8Wex5eH3ocwbSvPIUqE07hdmrubiMq4wxFVRYq7g9lHAnx l+bABuN/orbAcPcGAGg7AkXVoAc3Fza/ZcgMcw7NOtDTEss70V9OdgCfQUJL0KdO hCO8bQ1EaEiq6zEh8RpZe8mu+f/GYATX1I+eJUc6F6cn83oJjE9bqAVzk7TzTHeK EBKN50C14wWtXeG7n2+ugaVO+0xnvHeUrQBLHSRyOHqxXrQQ5XmzcaBiyI0f2IQM Hst1BVXyX0n/L/ZoYYsv5juJmDo= -----END CERTIFICATE-----rhc-1.38.7/spec/rhc/assets/empty.txt0000644000004100000410000000000012756270552017326 0ustar www-datawww-datarhc-1.38.7/spec/rhc/assets/foo.txt0000644000004100000410000000000412756270552016757 0ustar www-datawww-datafoo rhc-1.38.7/spec/rhc/assets/targz_sample.tar.gz0000644000004100000410000000017112756270552021257 0ustar www-datawww-data-ZO1 1Ԟ"GH4=6) j`a#b^3z{K4G湦|*!|59Rieu,ku9>T |V(rhc-1.38.7/spec/rhc/assets/env_vars_2.txt0000644000004100000410000000001012756270552020235 0ustar www-datawww-dataAND=123 rhc-1.38.7/spec/rhc/assets/targz_corrupted.tar.gz0000644000004100000410000000000412756270552022000 0ustar www-datawww-datafoo rhc-1.38.7/spec/rhc/assets/cert_key_rsa0000644000004100000410000000321212756270552020034 0ustar www-datawww-data-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAwBGx+DAot4iAz9R0vv18LvEo3UnOViV+obVe4SaZhWfB6IVf /G2tL+mjPSCSCLfrCHtietrf/Rt5XjnSBwcXgAjeH1dpf4gP378b5ZxSng+7B5vT nGI9R+mGCee7CDn/6Cw6SZepd0cPFQt/t1tP5c5VaQPKre36ZIlu10CQj6ZVEq3V Qt6JENk+TQKHefsFSDqhC4BOWy19S0bOVT7I8rR5Ce6yl7OeRR0d5KbKCJp+cQ9C supUZ/HCDKJUKMjeRyBq/H+T/tO4ax4m73vI43vwyk7LG+chFABJoeohh0LEQJ7F FkKCBFDi6kArva3tjaOBudipdsUCI8iRe6wn8QIDAQABAoIBAG/on4JVRRQSw8LU LiWt+jI7ryyoOUH2XL8JtzuGSwLwvomlVJT2rmbxQXx3Qr8zsgziHzIn30RRQrkF BXu0xRuDjzBBtSVqeJ1Mc4uoNncEAVxgjb5bewswZDnXPCGB8bosMtX4OPRXgdEo PwTtfjMOsrMaU3hd5Xu4m81tQA2BvwOlx8aYDyH0jeTnervc5uRGbeTBQG4Bu40E rWNmXvgNq2EzTAwbbN6Ma97gw9KgXnM4Nlh29Fxb5TBeUU9lkzuTZAZIDXKIm7AG UwMbj/A038yAumYQtThTE/3e4W3rn7F2Vko900bC4aAC1KQOAzjIeQqzqkVxWTWq 4SUFQAECgYEA/ODwifOTuI6hdZK6JRgc4wp6Rc0fkqHuxLzABXoIGuSVlWyimqIN ZySAkpo5EW6DNraRJxNCOBmWeGPEhHGrea+JPiPEwCK0F7SxvSmg3jzNzw3Es31T ecET7eDwuSOY9v4XDzLyiXXkEUUReD7Ng2hEYL+HaQrl5jWj4lxgq/ECgYEAwnCb Krz7FwX8AqtFAEi6uUrc12k1xYKQfrwSxbfdK2vBBUpgB71Iq/fqP+1BittEljDG 8f4jEtMBFfEPhLzGIHaI3UiHUHXS4GetA77TRgR8lnKKpj1FcMIY2iKU479707O5 Q08pgWRUDQ8BVg2ePgbo5QjLMc/rv7UF3AHvPAECgYB/auAIwqDGN6gHU/1TP4ke pWLi1O55tfpXSzv+BnUbB96PQgPUop7aP7xBIlBrBiI7aVZOOBf/qHT3CF421geu 8tHWa7NxlIrl/vgn9lfGYyDYmXlpb1amXLEsBVGGF/e1TGZWFDe9J5fZU9HvosVu 1xTNIvSZ6xHYI2MGZcGYIQKBgEYeebaV5C7PV6xWu1F46O19U9rS9DM//H/XryVi Qv4vo7IWuj7QQe7SPsXC98ntfPR0rqoCLf/R3ChfgGsr8H8wf/bc+v9HHj8S5E/f dy1e3Nccg2ej3PDm7jNsGSlwmmUkAQGHAL7KwYzcBm1UB+bycvZ1j2FtS+UckPpg MDgBAoGALD8PkxHb4U4DtbNFSYRrUdvS9heav/yph3lTMfifNkOir36io6v8RPgb D2bHKKZgmYlTgJrxD45Er9agC5jclJO35QRU/OfGf3GcnABkBI7vlvUKADAo65Sq weZkdJnbrIadcvLOHOzkKC9m+rxFTC9VoN1dwK2zwYvUXfa1VJA= -----END RSA PRIVATE KEY-----rhc-1.38.7/spec/rhc/assets/env_vars.txt0000644000004100000410000000016312756270552020025 0ustar www-datawww-dataFOO=123 BAR=456 MY_OPENSHIFT_ENV_VAR=mongodb://user:pass@host:port/ MY_EMPTY_ENV_VAR= ZEE LOL MUST NOT BE INCLUDED rhc-1.38.7/spec/rhc/rest_client_spec.rb0000644000004100000410000010236312756270552020016 0ustar www-datawww-datarequire 'base64' require 'spec_helper' require 'stringio' require 'rest_spec_helper' require 'rhc/rest' require 'uri' module RHC module Rest describe Client do after{ ENV['http_proxy'] = nil } after{ ENV['HTTP_PROXY'] = nil } it 'should set the proxy protocol if it is missing' do ENV['http_proxy'] = 'foo.bar.com:8081' expect{ RHC::Rest::Client.new.send(:httpclient_for, {}) }.to raise_error(ArgumentError) end it 'should not alter the proxy protocol if it is present' do ENV['http_proxy'] = 'http://foo.bar.com:8081' RHC::Rest::Client.new.send(:httpclient_for, {}).proxy.to_s.should == URI.parse(ENV['http_proxy']).to_s end it 'should not affect the proxy protocol if nil' do ENV['http_proxy'] = nil RHC::Rest::Client.new.send(:httpclient_for, {}).proxy.should be_nil ENV['http_proxy'].should be_nil end let(:endpoint){ mock_href } let(:username){ nil } let(:password){ nil } let(:use_debug){ false } let(:client) do respond_to?(:spec_versions) ? RHC::Rest::Client.new(endpoint, username, password, use_debug, spec_versions) : RHC::Rest::Client.new(endpoint, username, password, use_debug) end let(:client_links) { mock_response_links(mock_client_links) } let(:domain_0_links) { mock_response_links(mock_domain_links('mock_domain_0')) } let(:domain_1_links) { mock_response_links(mock_domain_links('mock_domain_1')) } let(:app_0_links) { mock_response_links(mock_app_links('mock_domain_0', 'mock_app')) } let(:user_links) { mock_response_links(mock_user_links) } let(:key_links) { mock_response_links(mock_key_links) } let(:api_links) { client_links } context "#new" do before do stub_api_request(:get, ''). to_return({ :body => { :data => client_links, :supported_api_versions => [1.0, 1.1] }.to_json, :status => 200 }) stub_api_request(:get, 'api_error'). to_raise(HTTPClient::BadResponseError.new('API Error')) stub_api_request(:get, 'other_error'). to_raise(StandardError.new('Other Error')) end it "returns a client object from the required arguments" do credentials = Base64.strict_encode64(mock_user + ":" + mock_pass) client.api.send(:links).should == client_links end context "against an endpoint that won't connect" do let(:endpoint){ mock_href('api_error') } it "raises an error message" do expect{ client.api }.to raise_error end end context "against an endpoint that has a generic error" do let(:endpoint){ mock_href('other_error') } it "raises a generic error for any other error condition" do expect{ client.api }.to raise_error(RHC::Rest::ConnectionException, "An unexpected error occurred: Other Error") end end end describe "#new" do context "when server supports API versions [1.0, 1.1]" do before :each do stub_api_request(:get, ''). with(:headers => {'Accept' => 'application/json'}). to_return({ :status => 200, :body => { :data => client_links, :version => '1.0', :supported_api_versions => [1.0, 1.1] }.to_json }) stub_api_request(:get, ''). with(:headers => {'Accept' => 'application/json;version=1.0'}). to_return({ :status => 200, :body => { :data => client_links, :version => '1.0', :supported_api_versions => [1.0, 1.1] }.to_json }) stub_api_request(:get, ''). with(:headers => {'Accept' => 'application/json;version=1.1'}). to_return({ :status => 200, :body => { :data => client_links, :version => '1.1', :supported_api_versions => [1.0, 1.1] }.to_json }) stub_api_request(:get, ''). with(:headers => {'Accept' => /application\/json;version=(1.2|1.3)/}). to_raise(StandardError.new('Bad Version')) stub_api_request(:get, 'api_error'). to_raise(HTTPClient::BadResponseError.new('API Error')) stub_api_request(:get, 'other_error'). to_raise(StandardError.new('Other Error')) end context "when client is instantiated with [1.0, 1.1] as the preferred API versions" do let(:spec_versions){ [1.0, 1.1] } it "settles on 1.1 as the API version" do client.api.api_version_negotiated.should == 1.1 end end context "when client is instantiated with [1.1, 1.0] as the preferred API versions" do let(:spec_versions){ [1.1, 1.0] } it "settles on 1.0 as the API version" do client.api.api_version_negotiated.should == 1.0 end end context "when client is instantiated with [1.2, 1.3] as the preferred API versions" do let(:spec_versions){ [1.2, 1.3] } it "fails to negotiate an agreeable API version" do client.api.api_version_negotiated.should be_nil end end context "when client is instantiated with [1.1, 1.0, 1.3] as the preferred API versions" do let(:spec_versions){ [1.1, 1.0, 1.3] } it "settles on 1.0 as the API version" do client.api.api_version_negotiated.should == 1.0 end end end end context "with an instantiated client " do before do stub_api_request(:get, ''). to_return({ :body => { :data => api_links, :supported_api_versions => [1.0, 1.1] }.to_json, :status => 200 }) end context "#add_domain" do before do stub_api_request(:any, api_links['ADD_DOMAIN']['relative']). to_return({ :body => { :type => 'domain', :supported_api_versions => [1.0, 1.1], :data => { :id => 'mock_domain', :links => mock_response_links(mock_domain_links('mock_domain')), } }.to_json, :status => 200 }) end it "returns a domain object" do domain = client.add_domain('mock_domain') domain.class.should == RHC::Rest::Domain domain.name.should == 'mock_domain' domain.send(:links).should == mock_response_links(mock_domain_links('mock_domain')) end end context "#update_members" do subject{ RHC::Rest::Application.new } it "raises when the update link is disabled" do subject.should_receive(:supports_members?).and_return(true) expect{ subject.update_members([]) }.to raise_error(RHC::ChangeMembersOnResourceNotSupported) end end context "#domains" do before(:each) do stub_api_request(:any, api_links['LIST_DOMAINS']['relative']). to_return({ :body => { :type => 'domains', :data => [{ :id => 'mock_domain_0', :links => mock_response_links(mock_domain_links('mock_domain_0')), }, { :id => 'mock_domain_1', :links => mock_response_links(mock_domain_links('mock_domain_1')), }] }.to_json, :status => 200 }). to_return({ :body => { :type => 'domains', :data => [] }.to_json, :status => 200 }) end it "returns a list of existing domains" do domains = client.domains domains.length.should equal(2) (0..1).each do |idx| domains[idx].class.should == RHC::Rest::Domain domains[idx].name.should == "mock_domain_#{idx}" domains[idx].send(:links).should == mock_response_links(mock_domain_links("mock_domain_#{idx}")) end end it "returns an empty list when no domains exist" do # Disregard the first response; this is for the previous expectiation. domains = client.domains client.instance_variable_set(:@domains, nil) domains = client.domains domains.length.should equal(0) end end context "#find_domain" do context "when server does not support SHOW_DOMAIN" do before do stub_api_request(:any, api_links['LIST_DOMAINS']['relative']). to_return({ :body => { :type => 'domains', :data => [{ :id => 'mock_domain_0', :links => mock_response_links(mock_domain_links('mock_domain_0')), }, { :id => 'mock_domain_1', :links => mock_response_links(mock_domain_links('mock_domain_1')), }] }.to_json, :status => 200 }) end it "returns a domain object for matching domain IDs" do match = nil expect { match = client.find_domain('mock_domain_0') }.to_not raise_error match.name.should == 'mock_domain_0' match.class.should == RHC::Rest::Domain end it "returns a domain object for matching case-insensitive domain IDs" do match = nil expect { match = client.find_domain('MOCK_DOMAIN_0') }.to_not raise_error match.name.should == 'mock_domain_0' match.class.should == RHC::Rest::Domain end it "raise an error when no matching domain IDs can be found" do expect { client.find_domain('mock_domain_2') }.to raise_error(RHC::Rest::DomainNotFoundException) end end context "when server supports SHOW_DOMAIN" do let(:api_links){ client_links.merge!(mock_response_links([['SHOW_DOMAIN', 'domains/:name', 'get']])) } before do stub_api_request(:any, api_links['SHOW_DOMAIN']['relative'].gsub(/:name/, 'mock_domain_0')). to_return({ :body => { :type => 'domain', :data => { :id => 'mock_domain_0', :links => mock_response_links(mock_domain_links('mock_domain_0')), } }.to_json, :status => 200 }) stub_api_request(:any, api_links['SHOW_DOMAIN']['relative'].gsub(/:name/, 'mock_domain_%^&')). to_return({ :body => { :type => 'domain', :data => { :id => 'mock_domain_%^&', :links => mock_response_links(mock_domain_links('mock_domain_0')), } }.to_json, :status => 200 }) stub_api_request(:any, api_links['SHOW_DOMAIN']['relative'].gsub(/:name/, 'mock_domain_2')). to_return({ :body => {:messages => [{:exit_code => 127}, {:severity => 'warning', :text => 'A warning'}]}.to_json, :status => 404 }) end it "returns a domain object for matching domain IDs" do match = nil expect { match = client.find_domain('mock_domain_0') }.to_not raise_error match.name.should == 'mock_domain_0' match.class.should == RHC::Rest::Domain end it "encodes special characters" do match = nil expect { match = client.find_domain('mock_domain_%^&') }.to_not raise_error match.name.should == 'mock_domain_%^&' match.class.should == RHC::Rest::Domain end it "raise an error when no matching domain IDs can be found" do expect{ client.find_domain('mock_domain_2') }.to raise_error(RHC::Rest::DomainNotFoundException) end it "prints a warning when an error is returned" do client.should_receive(:warn).with('A warning') expect{ client.find_domain('mock_domain_2') }.to raise_error(RHC::Rest::DomainNotFoundException) end end end context "when server supports LIST_DOMAINS_BY_OWNER" do let(:api_links){ client_links.merge!(mock_response_links([['LIST_DOMAINS_BY_OWNER', 'domains', 'get']])) } before do stub_api_request(:any, "#{api_links['LIST_DOMAINS_BY_OWNER']['relative']}?owner=@self"). to_return({ :body => { :type => 'domains', :data => [{ :id => 'mock_domain_0', :links => mock_response_links(mock_domain_links('mock_domain_0')), }] }.to_json, :status => 200 }) end it "returns owned domains when called" do match = nil expect { match = client.owned_domains }.to_not raise_error match.length.should == 1 match.first.name.should == 'mock_domain_0' match.first.class.should == RHC::Rest::Domain end end context "when server supports LIST_APPLICATIONS_BY_OWNER" do let(:api_links){ client_links.merge!(mock_response_links([['LIST_APPLICATIONS_BY_OWNER', 'applications', 'get']])) } before do stub_api_request(:any, "#{api_links['LIST_APPLICATIONS_BY_OWNER']['relative']}?owner=@self"). to_return({ :body => { :type => 'applications', :data => [{ :name => 'mock_application_0', :links => mock_response_links(mock_app_links('mock_domain_0', 'mock_application_0')), }] }.to_json, :status => 200 }) end it "returns owned applications when called" do match = nil expect { match = client.owned_applications }.to_not raise_error match.length.should == 1 match.first.name.should == 'mock_application_0' match.first.class.should == RHC::Rest::Application end end context "find_application" do let(:mock_domain){ 'mock_domain_0' } let(:mock_app){ 'mock_app' } let(:missing){ 'no_match' } before(:each) do stub_one_application(mock_domain, mock_app) stub_one_application(mock_domain, missing, { :type => nil, :data => nil, :messages => [{ :exit_code => 101, :field => nil, :severity => 'error', :text => "Application #{missing} not found" }], :status => 'not_found' }, 404) stub_one_application(missing, mock_app, { :type => nil, :data => nil, :messages => [{ :exit_code => 127, :field => nil, :severity => 'error', :text => "Domain #{missing} not found" }], :status => 'not_found' }, 404) end it "encodes application id correctly" do url = client.link_show_application_by_domain_name("~[", "~]") URI.parse(url) url.should == "https://test.domain.com/domains/~%5B/applications/~%5D" end it "returns application object for nested application IDs" do match = client.find_application(mock_domain, mock_app) match.class.should == RHC::Rest::Application match.name.should == mock_app match.domain_id.should == mock_domain match.send(:links).should == mock_response_links(mock_app_links(mock_domain, mock_app)) end it "Raises an exception when no matching applications can be found" do expect { client.find_application(mock_domain, missing) }.to raise_error(RHC::Rest::ApplicationNotFoundException) end it "Raises an exception when no matching domain can be found" do expect { client.find_application(missing, mock_app) }.to raise_error(RHC::Rest::DomainNotFoundException) end end context "#find_application_by_id" do context "when server does not support SHOW_APPLICATION" do let(:server){ mock_uri } let(:endpoint){ "https://#{server}/broker/rest/api"} before do stub_api stub_one_domain('test') stub_one_application('test', 'app1') end it "returns an app object for matching IDs" do match = nil expect { match = client.find_application_by_id(1) }.to_not raise_error match.id.should == 1 match.class.should == RHC::Rest::Application end it "raise an error when no matching app IDs can be found" do expect { client.find_application_by_id('2') }.to raise_error(RHC::Rest::ApplicationNotFoundException) end end context "when server supports SHOW_APPLICATION" do let(:api_links){ mock_response_links([['SHOW_APPLICATION', 'application/:id', 'get']]) } before do stub_api_request(:any, api_links['SHOW_APPLICATION']['relative'].gsub(/:id/, 'app_0')). to_return({ :body => { :type => 'application', :data => { :id => 'app_0', :links => mock_response_links(mock_app_links('app_0')), } }.to_json, :status => 200 }) stub_api_request(:any, api_links['SHOW_APPLICATION']['relative'].gsub(/:id/, 'app_1')). to_return({ :body => {:messages => [{:exit_code => 101}]}.to_json, :status => 404 }) end it "returns an app object for matching IDs" do match = nil expect { match = client.find_application_by_id('app_0') }.to_not raise_error match.id.should == 'app_0' match.class.should == RHC::Rest::Application end it "raise an error when no matching IDs can be found" do expect { client.find_application_by_id('app_1') }.to raise_error(RHC::Rest::ApplicationNotFoundException) end it "should fetch application ids" do client.api client.should_receive(:request).with(:url => "#{api_links['SHOW_APPLICATION']['href'].gsub(/:id/, 'app_2')}", :method => "GET", :payload => {}).and_return(1) client.find_application_by_id('app_2').should == 1 end it "should fetch application gear groups" do client.api client.should_receive(:request).with(:url => "#{api_links['SHOW_APPLICATION']['href'].gsub(/:id/, 'app_2')}/gear_groups", :method => "GET", :payload => {}).and_return(1) client.find_application_by_id_gear_groups('app_2').should == 1 end end end describe RHC::Rest::Cartridge do subject do RHC::Rest::Cartridge.new({ :name => 'foo', :links => mock_response_links([ ['GET', 'broker/rest/cartridge', 'get'] ])}, client) end context "when several messages are present" do before do stub_api_request(:get, 'broker/rest/cartridge', true). with(:query => {:include => 'status_messages'}). to_return(:body => { :type => 'cartridge', :data => { :status_messages => [{:message => 'Test'}] } }.to_json) end its(:status){ should == [{'message' => 'Test'}] } end end context "#cartridges" do before(:each) do stub_api_request(:any, api_links['LIST_CARTRIDGES']['relative']). to_return({ :body => { :type => 'cartridges', :data => [{ :name => 'mock_cart_0', :type => 'mock_cart_0_type', :links => mock_response_links(mock_cart_links('mock_cart_0')), }, { :name => 'mock_cart_1', :type => 'mock_cart_1_type', :links => mock_response_links(mock_cart_links('mock_cart_1')), }] }.to_json, :status => 200 }). to_return({ :body => { :type => 'cartridges', :data => [] }.to_json, :status => 200 }) end it "returns a list of existing cartridges" do carts = client.cartridges carts.length.should equal(2) (0..1).each do |idx| carts[idx].class.should == RHC::Rest::Cartridge carts[idx].name.should == "mock_cart_#{idx}" carts[idx].type.should == "mock_cart_#{idx}_type" carts[idx].send(:links).should == mock_response_links(mock_cart_links("mock_cart_#{idx}")) end end it "caches cartridges on the client" do # Disregard the first response; this is for the previous expectiation. old = client.cartridges.length client.cartridges.length.should equal(old) client.instance_variable_set(:@cartridges, nil) client.cartridges.length.should equal(0) end end context "#find_cartridges" do before(:each) do stub_api_request(:any, api_links['LIST_CARTRIDGES']['relative']). to_return({ :body => { :type => 'cartridges', :data => [{ :name => 'mock_cart_0', :type => 'mock_cart_0_type', :links => mock_response_links(mock_cart_links('mock_cart_0')), }, { :name => 'mock_cart_1', :type => 'mock_cart_1_type', :links => mock_response_links(mock_cart_links('mock_cart_1')), }, { :name => 'mock_nomatch_cart_0', :type => 'mock_nomatch_cart_0_type', :links => mock_response_links(mock_cart_links('mock_nomatch_cart_0')), } ] }.to_json, :status => 200 }) end it "returns a list of cartridge objects for matching cartridges" do matches = client.find_cartridges('mock_cart_0') matches.length.should equal(1) matches[0].class.should == RHC::Rest::Cartridge matches[0].name.should == 'mock_cart_0' matches[0].type.should == 'mock_cart_0_type' matches[0].send(:links).should == mock_response_links(mock_cart_links('mock_cart_0')) end it "returns an empty list when no matching cartridges can be found" do matches = client.find_cartridges('no_match') matches.length.should equal(0) end it "returns multiple cartridge matches" do matches = client.find_cartridges :regex => "mock_cart_[0-9]" matches.length.should equal(2) end end context "#user" do before(:each) do stub_api_request(:any, api_links['GET_USER']['relative']). to_return({ :body => { :type => 'user', :data => { :login => mock_user, :links => mock_response_links(mock_user_links) } }.to_json, :status => 200 }) end it "returns the user object associated with this client connection" do user = client.user user.class.should == RHC::Rest::User user.login.should == mock_user user.send(:links).should == mock_response_links(mock_user_links) end end context "#find_key" do before(:each) do stub_api_request(:any, api_links['GET_USER']['relative']). to_return({ :body => { :type => 'user', :data => { :login => mock_user, :links => mock_response_links(mock_user_links) } }.to_json, :status => 200 }) stub_api_request(:any, user_links['LIST_KEYS']['relative']). to_return({ :body => { :type => 'keys', :data => [{ :name => 'mock_key_0', :type => 'mock_key_0_type', :content => '123456789:0', :links => mock_response_links(mock_key_links('mock_key_0')) }, { :name => 'mock_key_1', :type => 'mock_key_1_type', :content => '123456789:1', :links => mock_response_links(mock_key_links('mock_key_1')) }] }.to_json, :status => 200 }) end it "returns a list of key objects for matching keys" do key = nil expect { key = client.find_key('mock_key_0') }.to_not raise_error key.class.should == RHC::Rest::Key key.name.should == 'mock_key_0' key.type.should == 'mock_key_0_type' key.content.should == '123456789:0' key.send(:links).should == mock_response_links(mock_key_links('mock_key_0')) end it "raise an error when no matching keys can be found" do expect { client.find_key('no_match') }.to raise_error(RHC::KeyNotFoundException) end end context "#delete_key" do before(:each) do stub_api_request(:any, api_links['GET_USER']['relative']). to_return({ :body => { :type => 'user', :data => { :login => mock_user, :links => mock_response_links(mock_user_links) } }.to_json, :status => 200 }) stub_api_request(:any, user_links['LIST_KEYS']['relative']). to_return({ :body => { :type => 'keys', :data => [{ :name => 'mock_key_0', :type => 'mock_key_0_type', :content => '123456789:0', :links => mock_response_links(mock_key_links('mock_key_0')) }, { :name => 'mock_key_1', :type => 'mock_key_1_type', :content => '123456789:1', :links => mock_response_links(mock_key_links('mock_key_1')) }] }.to_json, :status => 200 }) stub_api_request(:post, key_links['DELETE']['relative']). to_return({ :body => {}.to_json, :status => 200 }) end it "should delete keys" do expect { client.delete_key('mock_key_0') }.to be_true end it 'raises an error if nonexistent key is requested' do expect { client.find_key('no_match') }.to raise_error(RHC::KeyNotFoundException) end end end context "when server supports API versions 1.0 and 1.1" do before :each do stub_api_request(:get, ''). to_return({ :body => { :data => api_links, :supported_api_versions => [1.0, 1.1] }.to_json, :status => 200 }) end context "when client supports API version 1.1" do let(:spec_versions){ [1.1] } describe "#api_version_negotiated" do it "returns 1.1" do client.api.api_version_negotiated.to_s.should == '1.1' end end end context "when client supports only API version 1.2" do let(:spec_versions){ [1.2] } describe "#api_version_negotiated" do it 'returns nil' do client.api.api_version_negotiated.should be_nil end end end context "when client supports only API version 0.9" do describe "#new" do let(:spec_versions){ [0.9] } it "warns user that it is outdated" do capture do client.api @output.rewind @output.read.should =~ /client version may be outdated/ end end end end end describe "#supports_sessions?" do before{ subject.should_receive(:api).at_least(2).times.and_return(double) } context "with ADD_AUTHORIZATION link" do before{ subject.api.should_receive(:supports?).with('ADD_AUTHORIZATION').and_return(true) } its(:supports_sessions?){ should be_true } end context "without ADD_AUTHORIZATION link" do before{ subject.api.should_receive(:supports?).with('ADD_AUTHORIZATION').and_return(false) } its(:supports_sessions?){ should be_false } end end describe "#authorizations" do before do stub_api_request(:get, '').to_return({:body => { :data => mock_response_links(mock_api_with_authorizations), :supported_api_versions => [1.0, 1.1] }.to_json, :status => 200 }) stub_authorizations end it{ client.authorizations.first.token.should == 'a_token_value' } it{ client.authorizations.first.note.should == 'an_authorization' } it{ client.authorizations.first.expires_in_seconds.should == 60 } end end end end module RHC module Rest describe HTTPClient do end describe WWWAuth::DeferredCredential do subject{ described_class.new(nil, nil) } its(:user){ should be_nil } its(:passwd){ should be_nil } context "with a username and password" do subject{ described_class.new(username, password) } let(:username){ 'a_user' } let(:password){ 'a_password' } its(:user){ should == username } its(:passwd){ should == password } its(:to_str){ should == ["#{username}:#{password}"].pack('m').tr("\n", '') } end context "with a deferred username and password" do subject{ described_class.new(username, password) } let(:username){ lambda{ 'a_user' } } let(:password){ lambda{ 'a_password' } } its(:user){ should == username.call } its(:passwd){ should == password.call } its(:to_str){ should == ["#{username.call}:#{password.call}"].pack('m').tr("\n", '') } end end end end rhc-1.38.7/spec/rhc/wizard_spec.rb0000644000004100000410000007543112756270552017010 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'rhc/wizard' require 'rhc/vendor/parseconfig' require 'rhc/config' require 'rhc/servers' require 'ostruct' require 'rest_spec_helper' require 'wizard_spec_helper' require 'tmpdir' # Allow to define the id method OpenStruct.__send__(:define_method, :id) { @table[:id] } if RUBY_VERSION.to_f == 1.8 describe RHC::Wizard do def mock_config RHC::Config.stub(:home_dir).and_return('/home/mock_user') RHC::Servers.stub(:home_dir).and_return('/home/mock_user') end let(:options){ (o = Commander::Command::Options.new).default(default_options); o } let(:config){ RHC::Config.new.tap{ |c| c.stub(:home_dir).and_return('/home/mock_user') } } let(:servers){ RHC::Servers.new.tap{|c| c.stub(:home_dir).and_return('/home/mock_user') } } let(:default_options){ {} } describe "#finalize_stage" do subject{ described_class.new(config, options) } before{ subject.should_receive(:say).with(/The OpenShift client tools have been configured/) } it{ subject.send(:finalize_stage).should be_true } end describe "#token_store" do subject{ described_class.new(config, options) } it{ subject.send(:token_store).should be_a(RHC::Auth::TokenStore) } end describe "#has_configuration?" do subject{ described_class } before{ File.should_receive(:exists?).with(RHC::Config.local_config_path).at_least(1).times.and_return(true) } its(:has_configuration?){ should be_true } end describe "#setup_test_stage" do subject{ described_class.new(config, options) } it "should rescue problems" do subject.should_receive(:all_test_methods).and_return([:_test]) subject.should_receive(:_test).and_raise(StandardError.new("An error message")) capture{ subject.send(:setup_test_stage) }.should match "An error message" end end describe "#test_private_key_mode" do it "should raise when the file is in the wrong mode" do FakeFS do mock_config FileUtils.mkdir_p(RHC::Config.ssh_dir) File.open(RHC::Config.ssh_priv_key_file_path, 'w'){} File.expect_mode(RHC::Config.ssh_priv_key_file_path, 0666) expect{ subject.send(:test_private_key_mode) }.to raise_error(StandardError) end end end describe "#test_ssh_connectivity" do subject{ described_class.new(config, options) } let(:app) do app = Object.new app.should_receive(:ssh_url).at_least(1).and_return('ssh://uuid@foo.com') app.stub(:host).and_return('foo.com') app end let(:ssh) do ssh = Object.new ssh.should_receive(:close) ssh end let(:interrupt){ Interrupt.new('interrupted') } it "should not attempt an SSH connection" do subject.should_receive(:ssh_key_uploaded?).and_return(true) subject.should_receive(:applications).and_return([]) subject.send(:test_ssh_connectivity).should be_true end it "should attempt an SSH connection to the first app" do subject.should_receive(:ssh_key_uploaded?).and_return(true) subject.should_receive(:applications).and_return([app]) Net::SSH.should_receive(:start).with("foo.com", "uuid", {:timeout => 60}).and_return(ssh) subject.send(:test_ssh_connectivity).should be_true end it "should handle a failed connection" do subject.should_receive(:ssh_key_uploaded?).and_return(true) subject.should_receive(:applications).and_return([app]) Net::SSH.should_receive(:start).and_raise(StandardError.new('an_error')) expect{ subject.send(:test_ssh_connectivity) }.to raise_error(RuntimeError, /An SSH connection could not be established to foo.com/) end it "should handle an interrupted connection" do subject.should_receive(:ssh_key_uploaded?).and_return(true) subject.should_receive(:applications).and_return([app]) subject.should_receive(:debug_error).with(interrupt) Net::SSH.should_receive(:start).and_raise(interrupt) expect{ subject.send(:test_ssh_connectivity) }.to raise_error(RuntimeError, /Connection attempt to foo.com was interrupted/) end end describe "#core_auth" do subject{ described_class.new(config, options) } it "should use x509 if certificates are present" do options.should_receive(:ssl_client_cert_file).and_return("a cert") options.should_receive(:ssl_client_key_file).and_return("a key") RHC::Auth::X509.should_receive(:new).exactly(1).times.with(options) subject.send(:core_auth) end end describe "#login_stage" do let(:user){ 'test_user' } let(:password){ 'test pass' } let(:rest_client){ double } let(:auth){ subject.send(:auth) } let(:user_obj){ double(:login => user) } let(:wizard){RHC::Wizard.new(config, options) } def expect_client_test(with_sessions=false) wizard.should_receive(:new_client_for_options).ordered.and_return(rest_client) rest_client.should_receive(:api).ordered rest_client.should_receive(:user).ordered.and_return(user_obj) rest_client.should_receive(:supports_sessions?).ordered.and_return(with_sessions) end def expect_raise_from_api(error) wizard.should_receive(:new_client_for_options).ordered.and_return(rest_client) rest_client.should_receive(:api).ordered.and_raise(error) end it "should prompt for user and password" do expect_client_test wizard.send(:login_stage).should be_true wizard.send(:options).rhlogin.should == user end context "with token" do let(:token){ 'a_test_value' } let(:default_options){ {:token => token, :rhlogin => user} } before{ wizard.should_receive(:say).with(/Using an existing token for #{user} to login to /).ordered } it "should continue without prompt" do expect_client_test wizard.send(:login_stage).should be_true end end context "with credentials" do let(:server){ mock_uri } let(:default_options){ {:rhlogin => user, :password => password, :server => server} } before{ wizard.should_receive(:say).with(/Using #{user} to login to /).ordered } it "should warn about a self signed cert error" do expect_raise_from_api(RHC::Rest::SelfSignedCertificate.new('reason', 'message')) wizard.should_receive(:warn).with(/server's certificate is self-signed/).ordered wizard.should_receive(:openshift_online_server?).ordered.and_return(true) wizard.should_receive(:warn).with(/server between you and OpenShift/).ordered wizard.send(:login_stage).should be_nil end it "should warn about a cert error for Online" do expect_raise_from_api(RHC::Rest::CertificateVerificationFailed.new('reason', 'message')) wizard.should_receive(:warn).with(/server's certificate could not be verified/).ordered wizard.should_receive(:openshift_online_server?).ordered.and_return(true) wizard.should_receive(:warn).with(/server between you and OpenShift/).ordered wizard.send(:login_stage).should be_nil end it "should warn about a cert error for custom server and continue" do expect_raise_from_api(RHC::Rest::CertificateVerificationFailed.new('reason', 'message')) wizard.should_receive(:warn).with(/server's certificate could not be verified/).ordered wizard.should_receive(:openshift_online_server?).ordered.and_return(false) wizard.should_receive(:warn).with(/bypass this check/).ordered wizard.should_receive(:agree).with(/Connect without checking/).ordered.and_return(true) expect_client_test wizard.send(:login_stage).should be_true options.insecure.should be_true end it "should warn about a cert error for custom server and be cancelled" do expect_raise_from_api(RHC::Rest::CertificateVerificationFailed.new('reason', 'message')) wizard.should_receive(:warn).with(/server's certificate could not be verified/).ordered wizard.should_receive(:openshift_online_server?).ordered.and_return(false) wizard.should_receive(:warn).with(/bypass this check/).ordered wizard.should_receive(:agree).with(/Connect without checking/).ordered.and_return(false) wizard.send(:login_stage).should be_nil options.insecure.should be_false end context "when the config has enabled tokens" do let(:default_options){ {:rhlogin => user, :password => password, :server => server, :use_authorization_tokens => true} } let(:store){ double } before{ RHC::Auth::TokenStore.stub(:new).and_return(store) } before{ expect_client_test(true) } it "should check for an existing token" do store.should_receive(:get).and_return(nil) wizard.should_receive(:info).with(/OpenShift can create and store a token on disk/).ordered wizard.should_receive(:agree).with(/Generate a token now?/).ordered.and_return(false) wizard.send(:login_stage).should be_true options.token.should be_nil end end context "with a server that supports tokens" do before{ expect_client_test(true) } let(:token){ 'a_test_value' } let(:auth_token){ double(:token => token, :expires_in_seconds => 100) } let(:store){ double } before{ RHC::Auth::TokenStore.stub(:new).and_return(store) } it "should not generate a token if the user does not request it" do store.should_not_receive(:get) wizard.should_receive(:info).with(/OpenShift can create and store a token on disk/).ordered wizard.should_receive(:agree).with(/Generate a token now?/).ordered.and_return(false) wizard.send(:login_stage).should be_true options.token.should be_nil end context "when the option to create tokens is passed" do shared_examples "a wizard creating tokens without prompts" do it "should create the token without prompting" do store.should_not_receive(:get) wizard.should_receive(:info).with(/OpenShift can create and store a token on disk/).ordered wizard.should_receive(:agree).never wizard.should_receive(:say).with(/Generating an authorization token for this client /).ordered rest_client.should_receive(:new_session).ordered.and_return(auth_token) store.should_receive(:put).with(user, server, token).ordered.and_return(true) wizard.should_receive(:new_client_for_options).ordered.and_return(rest_client) rest_client.should_receive(:user).ordered.and_return(true) wizard.should_receive(:success).with(/lasts 1 minute/).ordered wizard.send(:login_stage).should be_true options.token.should == token end end describe '--create-token' do it_behaves_like 'a wizard creating tokens without prompts' do let(:default_options){ {:rhlogin => user, :password => password, :server => server, :create_token => true} } end end describe '--use-authorization-tokens' do before { store.should_receive(:get) } it_behaves_like 'a wizard creating tokens without prompts' do #create token would normally be set in rhc/commands/server if not specified #and running from the command line let(:default_options){ {:rhlogin => user, :password => password, :server => server, :use_authorization_tokens => true, :create_token =>true} } end end end it "should generate a token if the user requests it" do store.should_not_receive(:get) wizard.should_receive(:info).with(/OpenShift can create and store a token on disk/).ordered wizard.should_receive(:agree).with(/Generate a token now?/).ordered.and_return(true) wizard.should_receive(:say).with(/Generating an authorization token for this client /).ordered rest_client.should_receive(:new_session).ordered.and_return(auth_token) store.should_receive(:put).with(user, server, token).ordered.and_return(true) wizard.should_receive(:new_client_for_options).ordered.and_return(rest_client) rest_client.should_receive(:user).ordered.and_return(true) wizard.should_receive(:success).with(/lasts 1 minute/).ordered wizard.send(:login_stage).should be_true options.token.should == token end end context "when the user doesn't want to use tokens" do let(:default_options){ {:rhlogin => user, :password => password, :server => server, :use_authorization_tokens => false, :create_token => false} } before do wizard.should_receive(:new_client_for_options).ordered.and_return(rest_client) rest_client.should_receive(:api).ordered rest_client.should_receive(:user).ordered.and_return(user_obj) end it "should skip token generation" do wizard.should_receive(:say).with(/Skipping token generation/) wizard.should_receive(:agree).never wizard.send(:login_stage).should be_true options.token.should be_nil end end end end #TODO: Implement more stage level specs context "when the wizard is run" do subject{ RHC::RerunWizard.new(config, options) } before(:each) do mock_terminal FakeFS.activate! FakeFS::FileSystem.clear mock_config RHC::Config.initialize end after(:all) do FakeFS.deactivate! end #after{ FileUtils.rm_rf(@tmpdir) if @tmpdir } let(:home_dir){ '/home/mock_user' }#@tmpdir = Dir.mktmpdir } let(:config){ RHC::Config.new.tap{ |c| c.stub(:home_dir).and_return(home_dir) } } let(:options){ (o = Commander::Command::Options.new).default(default_options); o } let(:servers){ RHC::Servers.new.tap{|c| c.stub(:home_dir).and_return(home_dir) } } let(:default_options){ {:server => mock_uri} } let(:username){ mock_user } let(:password){ 'password' } let(:user_auth){ {:user => username, :password => password} } describe "#run" do context "when a stage returns nil" do before{ subject.stub(:greeting_stage).and_return(nil) } it "should exit after that stage" do subject.should_receive(:login_stage).never subject.run.should be_nil end end end context "with no settings" do before do stub_api(false) challenge{ stub_user } stub_no_keys challenge{ stub_no_domains } stub_simple_carts(false) end it "should execute the minimal path" do should_greet_user should_configure_server(mock_uri) should_challenge_for(username, password) should_write_config should_create_an_ssh_keypair should_skip_uploading_key should_find_git should_not_find_problems should_skip_creating_namespace should_list_types_of_apps_to_create should_be_done end context "on windows systems" do before{ subject.stub(:windows?).and_return(true) } it "should display windows info" do should_greet_user should_configure_server(mock_uri) should_challenge_for(username, password) should_write_config should_create_an_ssh_keypair should_skip_uploading_key should_display_windows_info end end context "when the user enters a domain and uploads a key" do before do stub_add_key stub_api_request(:post, 'broker/rest/domains', user_auth). with(:body => /(thisnamespaceistoobig|invalidnamespace)/). to_return({ :status => 409, :body => { :messages => [{:field => 'id', :severity => 'ERROR', :text => 'Too long', :exit_code => 123}] }.to_json }) stub_create_domain('testnamespace') end it "should create the domain" do should_greet_user should_configure_server(mock_uri) should_challenge_for(username, password) should_write_config should_create_an_ssh_keypair should_upload_default_key should_find_git should_not_find_problems should_create_a_namespace should_list_types_of_apps_to_create should_be_done end end context "when the user inputs incorrect authentication" do before{ stub_api_request(:get, 'broker/rest/user', :user => username, :password => 'invalid').to_return(:status => 401).times(1).to_return(simple_user(username)) } it "should prompt them again" do should_greet_user should_configure_server(mock_uri) input_line username input_line 'invalid' input_line password next_stage.should_not be_nil last_output do |s| s.should match("Login to ") s.should match("Username or password is not correct") s.scan("Password: *").length.should == 2 end end end context "when the default key is not uploaded" do before{ stub_one_key('a'); stub_update_key('a') } it "should prompt for the new key" do should_greet_user should_configure_server(mock_uri) should_challenge_for(username, password) should_write_config should_create_an_ssh_keypair input_line 'yes' input_line 'a' next_stage last_output do |s| s.should match(/a \(type: ssh-rsa\)/) s.should match("Fingerprint: #{rsa_key_fingerprint_public}") s.should match(" name |a|") end end end context "when a multiple keys exist but is not the same" do before{ setup_mock_ssh(true) } before do stub_one_key('a_key') stub_add_key_error('invalid```--', 'Invalid key name') stub_add_key('another_key') end it "should give the user a name the key" do should_greet_user should_configure_server(mock_uri) should_challenge_for(username, password) should_write_config should_not_create_an_ssh_keypair input_line 'yes' input_line 'invalid```--' input_line 'another_key' next_stage last_output do |s| s.should match(/a_key \(type: ssh-rsa\)/) s.should match("Fingerprint: #{rsa_key_fingerprint_public}") s.should match(" name |a_key|") s.should match("Invalid key name") s.should match("Uploading key 'another_key'") end end end context "when the default key already exists on the server" do before{ setup_mock_ssh(true) } before{ stub_mock_ssh_keys } it "should prompt for the new key" do should_greet_user should_configure_server(mock_uri) should_challenge_for(username, password) should_write_config should_not_create_an_ssh_keypair should_find_matching_server_key end end end context "with login and existing domain and app" do let(:default_options){ {:rhlogin => username, :server => mock_uri} } subject{ RHC::RerunWizard.new(config, options) } before do stub_api false challenge{ stub_user } stub_no_keys stub_add_key stub_api_request(:post, 'broker/rest/domains', user_auth). with(:body => /(thisnamespaceistoobig|invalidnamespace)/). to_return({ :status => 409, :body => { :messages => [{:field => 'id', :severity => 'ERROR', :text => 'Too long', :exit_code => 123}] }.to_json }) challenge{ stub_one_domain('testnamespace') } stub_one_application('testnamespace', 'test1') stub_simple_carts end it "should skip steps that have already been completed" do should_greet_user should_configure_server(mock_uri) should_challenge_for(nil, password) should_write_config should_create_an_ssh_keypair should_upload_default_key should_not_find_git should_check_remote_server should_find_a_namespace('testnamespace') should_find_apps(['test1', 'testnamespace']) should_be_done end context "with different config" do let(:config_option){ setup_different_config } let(:default_options){ {:rhlogin => username, :server => mock_uri, :config => config_option} } it "should overwrite the config" do should_greet_user should_configure_server(mock_uri) should_challenge_for(nil, password) should_overwrite_config end end end context "with SSHWizard" do let(:default_options){ {:rhlogin => username, :password => password} } let(:auth){ RHC::Auth::Basic.new(options) } let(:rest_client){ RHC::Rest::Client.new(:server => mock_uri, :auth => auth) } subject{ RHC::SSHWizard.new(rest_client, config, options) } before do stub_api false challenge{ stub_user } end context "with no server keys" do before{ stub_no_keys } before{ stub_add_key } it "should generate and upload keys since the user does not have them" do input_line "yes" input_line 'default' input_line "" should_create_an_ssh_keypair should_upload_default_key #last_output.should match("Uploading key 'default'") end context "with default keys created" do before{ setup_mock_ssh(true) } it "should upload the default key" do should_not_create_an_ssh_keypair should_upload_default_key end end end context "with the server having the default key" do before{ setup_mock_ssh(true) } before{ stub_mock_ssh_keys } it "should pass through since the user has keys already" do subject.run.should be_true last_output.should == "" end end end context "Check odds and ends" do before(:each) { mock_config } let(:wizard){ RerunWizardDriver.new } it "should cause ssh_key_upload? to catch NoMethodError and call the fallback to get the fingerprint" do Net::SSH::KeyFactory.should_receive(:load_public_key).exactly(4).times.and_raise(NoMethodError) wizard.should_receive(:ssh_keygen_fallback).exactly(4).times wizard.should_receive(:ssh_keys).at_least(1).times.and_return(wizard.get_mock_key_data) wizard.send(:ssh_key_uploaded?) end it "should cause upload_ssh_key to catch NoMethodError and call the fallback to get the fingerprint" do Net::SSH::KeyFactory.should_receive(:load_public_key).exactly(5).times.and_raise(NoMethodError) wizard.stub(:ssh_keys).at_least(1).times.and_return(wizard.get_mock_key_data) wizard.should_receive(:ssh_keygen_fallback).exactly(5).times.and_return(double(:name => 'default', :fingerprint => 'AA:BB:CC:DD:EE:FF', :type => 'ssh-rsa' )) input_line 'y' wizard.send(:upload_ssh_key_stage).should be_false last_output.should match("Your public SSH key at .* is invalid or unreadable\.") end it "should cause upload_ssh_key to catch NotImplementedError and return false" do Net::SSH::KeyFactory.should_receive(:load_public_key).exactly(5).times.and_raise(NoMethodError) wizard.should_receive(:ssh_keys).at_least(1).times.and_return(wizard.get_mock_key_data) input_line 'y' wizard.send(:upload_ssh_key_stage).should be_false output = last_output output.should match("Your public SSH key at .* is invalid or unreadable\.") end it "should find a unique name" do wizard.should_receive(:ssh_keys).at_least(1).times.and_return(wizard.get_mock_key_data) wizard.send(:find_unique_key_name, 'cb490595').should == 'cb4905951' wizard.send(:find_unique_key_name, 'default').should == 'default1' wizard.send(:find_unique_key_name, 'abc').should == 'abc' end it "should match ssh key fallback fingerprint to net::ssh fingerprint" do # we need to write to a live file system so ssh-keygen can find it RHC::Servers.any_instance.stub(:save!) FakeFS.deactivate! Dir.mktmpdir do |dir| setup_mock_ssh_keys(dir) pub_ssh = File.join dir, "id_rsa.pub" fallback_fingerprint = wizard.send :ssh_keygen_fallback, pub_ssh internal_fingerprint, short_name = wizard.get_key_fingerprint pub_ssh fallback_fingerprint.should == internal_fingerprint end FakeFS.activate! end context "with the first run wizard" do let(:wizard){ FirstRunWizardDriver.new } it "prints the exception message when a domain error occurs" do msg = "Resource conflict" wizard.rest_client.stub(:add_domain) { raise RHC::Rest::ValidationException, msg } input_line "testnamespace" # try to add a namespace input_line '' # the above input will raise exception. # we now skip configuring namespace. wizard.send(:ask_for_namespace) output = last_output output.should match msg end it "should update the key correctly" do key_name = 'default' key_data = wizard.get_mock_key_data wizard.ssh_keys = key_data wizard.stub(:ssh_key_triple_for_default_key) { pub_key.chomp.split } wizard.stub(:fingerprint_for_default_key) { "" } # this value is irrelevant wizard.rest_client = double('RestClient').tap{ |o| o.stub(:find_key) { key_data.detect { |k| k.name == key_name } } } wizard.send(:upload_ssh_key, key_name) output = last_output output.should match 'Updating' end it 'should pick a usable SSH key name' do File.exists?('1').should be_false key_name = 'default' key_data = wizard.get_mock_key_data Socket.stub(:gethostname) { key_name } input_line("\n") # to accept default key name wizard.ssh_keys = key_data wizard.stub(:ssh_key_triple_for_default_key) { pub_key.chomp.split } wizard.stub(:fingerprint_for_default_key) { "" } # this value is irrelevant wizard.rest_client = double('RestClient').tap{ |o| o.stub(:add_key) { true } } wizard.send(:upload_ssh_key, "other") output = last_output # since the clashing key name is short, we expect to present # a key name with "1" attached to it. output.should match "|" + key_name + "1" + "|" File.exists?('1').should be_false end end end end module WizardDriver attr_accessor :mock_user, :rest_client def initialize(*args) if args.empty? args = [RHC::Config.new, Commander::Command::Options.new, RHC::Servers.new] args[1].default(args[0].to_options) end super *args raise "No options" if options.nil? @mock_user = 'mock_user@foo.bar' @current_wizard_stage = nil @platform_windows = false #self.stub(:openshift_server).and_return('fake.foo') end def run_next_stage if @current_wizard_stage.nil? @current_wizard_stage = 0 else return false if @current_wizard_stage >= stages.length + 1 @current_wizard_stage += 1 end self.send stages[@current_wizard_stage] end # Set up @rest_client so that we can stub subsequent REST calls def stub_rhc_client_new @rest_client = RestSpecHelper::MockRestClient.new end def setup_mock_config(rhlogin=@mock_user) FileUtils.mkdir_p File.dirname(RHC::Config.local_config_path) File.open(RHC::Config.local_config_path, "w") do |file| file.puts < app, :app_url => url == :default ? "http://#{app}-#{domain}.#{openshift_server}/" : url, :u => true ) end @rest_client.stub(:domains) { [OpenStruct.new(:id => domain, :applications => apps_ary)] } end def windows=(bool) @platform_windows = bool end def windows? @platform_windows end def get_key_fingerprint(path=RHC::Config.ssh_pub_key_file_path) # returns the fingerprint and the short name used as the default # key name fingerprint = Net::SSH::KeyFactory.load_public_key(path).fingerprint short_name = fingerprint[0, 12].gsub(/[^0-9a-zA-Z]/,'') return fingerprint, short_name end def ssh_keys=(data) @ssh_keys = data end class Sshkey < OpenStruct def update(type, content) self.type = type self.content = content end def type @table[:type] end def type=(type) @table[:type] = type end def is_ssh? type != 'krb5-principal' end end def get_mock_key_data [ Sshkey.new(:name => 'default', :type => 'ssh-rsa', :fingerprint => "0f:97:4b:82:87:bb:c6:dc:40:a3:c1:bc:bb:55:1e:fa"), Sshkey.new(:name => 'cb490595', :type => 'ssh-rsa', :fingerprint => "cb:49:05:95:b4:42:1c:95:74:f7:2d:41:0d:f0:37:3b"), Sshkey.new(:name => '96d90241', :type => 'ssh-rsa', :fingerprint => "96:d9:02:41:e1:cb:0d:ce:e5:3b:fc:da:13:65:3e:32"), Sshkey.new(:name => '73ce2cc1', :type => 'ssh-rsa', :fingerprint => "73:ce:2c:c1:01:ea:79:cc:f6:be:86:45:67:96:7f:e3") ] end def config_path config.path end def openshift_server super end # def config(local_conf_path=nil) # @config.set_local_config(local_conf_path, false) if local_conf_path # @config # end end class FirstRunWizardDriver < RHC::Wizard include WizardDriver end class RerunWizardDriver < RHC::RerunWizard include WizardDriver end class SSHWizardDriver < RHC::SSHWizard include WizardDriver def initialize super RestSpecHelper::MockRestClient.new, RHC::Config.new, Commander::Command::Options.new end end end class ServerWizardDriver < RHC::Wizard include WizardDriver end describe RHC::DomainWizard do context "with a rest client" do let(:rest_client){ double } it{ described_class.new(nil, nil, rest_client).rest_client.should == rest_client } it{ subject.stages == [:config_namespace_stage] } it{ expect{ described_class.new(nil, nil, rest_client).send(:config_namespace, '') }.to call(:add_domain).on(rest_client).and_stop } end end rhc-1.38.7/spec/rhc/rest_application_spec.rb0000644000004100000410000002512012756270552021036 0ustar www-datawww-datarequire 'spec_helper' require 'rest_spec_helper' require 'base64' module RHC module Rest describe Application do let (:client) { RHC::Rest::Client.new('test.domain.com', 'test_user', 'test pass') } let (:app_links) { mock_response_links(mock_app_links('mock_domain','mock_app')) } let (:app_aliases) { ['alias1','alias2'] } let (:app_obj) { args = { 'domain_id' => 'mock_domain', 'name' => 'mock_app', 'creation_time' => Time.now.to_s, 'uuid' => 1234, 'aliases' => app_aliases, 'server_identity' => mock_uri, 'links' => app_links } args.merge!(attributes) if defined?(attributes) RHC::Rest::Application.new(args, client) } context "#new" do it "returns an application object" do app = app_obj app.should be_an_instance_of RHC::Rest::Application app.send(:links).length.should equal(app_links.length) end end describe "#ssh_string" do context "with valid url" do subject{ described_class.new('ssh_url' => "ssh://foo@bar.com/path") } its(:ssh_string){ should == "foo@bar.com" } end context "with bad url" do subject{ described_class.new('ssh_url' => "ssh://") } its(:ssh_string){ should == "ssh://" } end end describe "#host" do context "with bad url" do subject{ described_class.new('app_url' => "http://") } its(:app_url){ should == "http://" } its(:host){ should be_nil } end context "with http url" do subject{ described_class.new('app_url' => "http://bar.com/path") } its(:app_url){ should == "http://bar.com/path" } its(:host){ should == "bar.com" } end end context "#add_cartridge" do context "with a name" do before{ stub_api_request(:any, app_links['ADD_CARTRIDGE']['relative'], false).with(:body => {:name => 'mock_cart_0'}.to_json).to_return(mock_cartridge_response) } it "accepts a string" do cart = app_obj.add_cartridge('mock_cart_0') cart.should be_an_instance_of RHC::Rest::Cartridge cart.name.should == 'mock_cart_0' end it "accepts an object" do cart = app_obj.add_cartridge(double(:name => 'mock_cart_0', :url => nil)) cart.should be_an_instance_of RHC::Rest::Cartridge cart.name.should == 'mock_cart_0' end it "accepts a hash" do cart = app_obj.add_cartridge(:name => 'mock_cart_0') cart.should be_an_instance_of RHC::Rest::Cartridge cart.name.should == 'mock_cart_0' end end context "with a URL cart" do before{ stub_api_request(:any, app_links['ADD_CARTRIDGE']['relative'], false).with(:body => {:url => 'http://foo.com'}.to_json).to_return(mock_cartridge_response(1, true)) } it "raises without a param" do app_obj.should_receive(:has_param?).with('ADD_CARTRIDGE','url').and_return(false) expect{ app_obj.add_cartridge({:url => 'http://foo.com'}) }.to raise_error(RHC::Rest::DownloadingCartridgesNotSupported) end it "accepts a hash" do app_obj.should_receive(:has_param?).with('ADD_CARTRIDGE','url').and_return(true) cart = app_obj.add_cartridge({:url => 'http://foo.com'}) cart.should be_an_instance_of RHC::Rest::Cartridge cart.name.should == 'mock_cart_0' cart.url.should == 'http://a.url/0' cart.short_name.should == 'mock_cart_0' cart.display_name.should == 'mock_cart_0' cart.only_in_new?.should be_true cart.only_in_existing?.should be_false end it "accepts an object" do app_obj.should_receive(:has_param?).with('ADD_CARTRIDGE','url').and_return(true) cart = app_obj.add_cartridge(double(:url => 'http://foo.com')) cart.should be_an_instance_of RHC::Rest::Cartridge cart.name.should == 'mock_cart_0' cart.url.should == 'http://a.url/0' cart.short_name.should == 'mock_cart_0' cart.display_name.should == 'mock_cart_0' cart.only_in_new?.should be_true cart.only_in_existing?.should be_false end end end context "#aliases" do context "when the server returns an array of strings" do it{ app_obj.aliases.first.should be_an_instance_of RHC::Rest::Alias } it("converts to an object"){ app_obj.aliases.map(&:id).should == app_aliases } end context "when the server returns an object" do let(:app_aliases){ [{'id' => 'alias1'}, {'id' => 'alias2'}] } it{ app_obj.aliases.first.should be_an_instance_of RHC::Rest::Alias } it{ app_obj.aliases.map(&:id).should == ['alias1', 'alias2'] } end context "when the server doesn't return aliases" do let(:app_aliases){ nil } context "when the client supports LIST_ALIASES" do before{ stub_api_request(:any, app_links['LIST_ALIASES']['relative'], false).to_return(mock_alias_response(2)) } it{ app_obj.aliases.first.should be_an_instance_of RHC::Rest::Alias } it{ app_obj.aliases.map(&:id).should == ['www.alias0.com', 'www.alias1.com'] } end context "when the client doesn't support LIST_ALIASES" do before{ app_links['LIST_ALIASES'] = nil } it{ app_obj.aliases.should == [] } end end end context "#cartridges" do let(:num_carts){ 0 } before do stub_api_request(:any, app_links['LIST_CARTRIDGES']['relative'], false). to_return(mock_cartridge_response(num_carts)) end context "with carts" do let(:num_carts){ 2 } it "returns a list of all cartridges in the current application" do app = app_obj carts = app.cartridges carts.length.should == 2 (0..1).each do |idx| carts[idx].should be_an_instance_of RHC::Rest::Cartridge carts[idx].name.should == "mock_cart_#{idx}" end end end context "without carts" do it "returns an empty list" do app = app_obj carts = app.cartridges carts.length.should == 0 end end context "with carts included in initial reponse" do let(:attributes){ {:cartridges => RHC::Json.decode(mock_cartridge_response(2)[:body])['data'] }} it "returns a list of all cartridges in the current application" do app = app_obj carts = app.cartridges carts.length.should == 2 (0..1).each do |idx| carts[idx].should be_an_instance_of RHC::Rest::Cartridge carts[idx].name.should == "mock_cart_#{idx}" end end end end context "#gear_groups" do before do stub_api_request(:any, app_links['GET_GEAR_GROUPS']['relative'], false). to_return(mock_gear_groups_response()) end it "returns a list of all gear groups the current application" do app = app_obj gear_groups = app.gear_groups gear_groups.length.should equal(1) gear_groups[0].should be_an_instance_of RHC::Rest::GearGroup end end # These application control tests are subtle; the key lies in making sure the # webmock specifies the expected body that is sent in the request. # This is currently of the form "event=foo" shared_examples_for "a control method" do before do @control_method = control_data[:method] @control_call = [@control_method] if control_data.has_key?(:arg) @control_call << control_data[:arg] end @control_event = control_data.has_key?(:event) ? control_data[:event] : @control_method.to_s @control_link = control_data.has_key?(:link) ? control_data[:link].upcase : @control_method.to_s.upcase @control_output = control_data.has_key?(:result) ? control_data[:result] : @control_event @with_payload = control_data.has_key?(:payload) ? control_data[:payload] : true if @with_payload stub_api_request(:any, app_links[@control_link]['relative'], false). with(:body => { 'event' => @control_event }). # This is the critical part to_return({ :body => { :data => @control_event }.to_json, :status => 200 }) else stub_api_request(:any, app_links[@control_link]['relative'], false). to_return({ :body => { :data => @control_event }.to_json, :status => 200 }) end end it "sends the control request to the server" do app = app_obj expect { app.send(*@control_call) }.to_not raise_error app.send(*@control_call).should == @control_output end end context "#start" do let(:control_data) { { :method => :start } } it_should_behave_like "a control method" end context "#stop" do context " and the request is not forced (force == false)" do let(:control_data) { { :method => :stop } } it_should_behave_like "a control method" end context " and the request is forced (force == true)" do let(:control_data) { { :method => :stop, :arg => true, :event => 'force-stop', :link => 'stop' } } it_should_behave_like "a control method" end end context "#restart" do let(:control_data) { { :method => :restart } } it_should_behave_like "a control method" end context "#delete" do let(:control_data) { { :method => :delete, :payload => false } } it_should_behave_like "a control method" end context "#destroy" do let(:control_data) { { :method => :destroy, :event => 'delete', :link => 'delete', :payload => false } } it_should_behave_like "a control method" end context "#scale_up" do let(:control_data) { { :method => :scale_up, :event => 'scale-up', :link => 'scale_up', :payload => false } } it_should_behave_like "a control method" end context "#scale_down" do let(:control_data) { { :method => :scale_down, :event => 'scale-down', :link => 'scale_down', :payload => false } } it_should_behave_like "a control method" end end end end rhc-1.38.7/spec/rhc/helpers_spec.rb0000644000004100000410000005454612756270552017156 0ustar www-datawww-datarequire 'spec_helper' require 'rhc' require 'rhc/ssh_helpers' require 'rhc/scp_helpers' require 'rhc/cartridge_helpers' require 'rhc/git_helpers' require 'rhc/core_ext' require 'rhc/config' require 'rhc/rest/mock' require 'date' require 'resolv' require 'ostruct' class AllRhcHelpers include RHC::Helpers include RHC::SSHHelpers include RHC::SCPHelpers include RHC::CartridgeHelpers def config @config ||= RHC::Config.new end def options @options ||= OpenStruct.new(:server => nil) end end describe AllRhcHelpers do before do mock_terminal user_config end its(:openshift_server) { should == 'openshift.redhat.com' } its(:openshift_url) { should == 'https://openshift.redhat.com' } it("should display slashes"){ subject.system_path('foo/bar').should == 'foo/bar' } context "on windows" do it("should display backslashes"){ with_constants({:ALT_SEPARATOR => '\\'}, File) { subject.system_path('foo/bar').should == 'foo\\bar' } } it("should handle drives"){ with_constants({:ALT_SEPARATOR => '\\'}, File) { subject.system_path('C:/foo/bar').should == 'C:\\foo\\bar' } } end it("should pluralize many") { subject.pluralize(3, 'fish').should == '3 fishs' } it("should not pluralize one") { subject.pluralize(1, 'fish').should == '1 fish' } it("should decode json"){ subject.decode_json("{\"a\" : 1}").should == {'a' => 1} } it("should parse custom headers") { subject.parse_headers(nil).should == {} subject.parse_headers("").should == {} subject.parse_headers("A").should == {"A" => ""} subject.parse_headers("A:B").should == {"A" => "B"} subject.parse_headers("A: B").should == {"A" => "B"} subject.parse_headers(["A: B", "A: C"]).should == {"A" => "C"} subject.parse_headers(["A: B", "C: D"]).should == {"A" => "B", "C" => "D"} subject.parse_headers(["A:B:C"]).should == {"A" => "B:C"} } shared_examples_for "colorized output" do it("should be colorized") do message = "this is #{_color} -" output = capture{ subject.send(method,message) } output.chomp.should be_colorized(message,_color) end it("should return true"){ subject.send(method,'anything').should be_true } end context "success output" do let(:_color){ :green } let(:method){ :success } it_should_behave_like "colorized output" end context "warn output" do let(:_color){ :yellow } let(:method){ :warn } it_should_behave_like "colorized output" end context "info output" do let(:_color){ :cyan } let(:method){ :info } it_should_behave_like "colorized output" end it("should invoke debug from debug_error"){ expect{ subject.debug_error(double(:class => "Mock", :message => 'msg', :backtrace => [])) }.to call(:debug).on($terminal).with("msg (Mock)\n ") } it("should draw a table") do subject.table([[10,2], [3,40]]) do |i| i.map(&:to_s) end.to_a.should == ['10 2','3 40'] end context "error output" do let(:_color){ :red } let(:method){ :error } it_should_behave_like "colorized output" end it("should output a table") do subject.send(:format_no_info, 'test').to_a.should == ['This test has no information to show'] end it "should parse an RFC3339 date" do d = subject.datetime_rfc3339('2012-06-24T20:48:20-04:00') d.day.should == 24 d.month.should == 6 d.year.should == 2012 end describe "#human_size" do it{ subject.human_size(nil).should == 'unknown' } it{ subject.human_size(1).should == '1 B' } it{ subject.human_size(500).should == '500 B' } it{ subject.human_size(1000).should == '1 KB' } it{ subject.human_size(500000).should == '500 KB' } it{ subject.human_size(1000*1000).should == '1 MB' } it{ subject.human_size(1000*1000*1000).should == '1 GB' } it{ subject.human_size(1000*1000*1000*1000).should == '1 TB' } end describe "#distance_of_time_in_words" do it{ subject.distance_of_time_in_words(0, 1).should == 'less than 1 minute' } it{ subject.distance_of_time_in_words(0, 60).should == '1 minute' } it{ subject.distance_of_time_in_words(0, 130).should == '2 minutes' } it{ subject.distance_of_time_in_words(0, 50*60).should == 'about 1 hour' } it{ subject.distance_of_time_in_words(0, 3*60*60).should == 'about 3 hours' } it{ subject.distance_of_time_in_words(0, 25*60*60).should == 'about 1 day' } it{ subject.distance_of_time_in_words(0, 3*24*60*60).should == '3 days' } it{ subject.distance_of_time_in_words(0, 40*24*60*60).should == 'about 1 month' } it{ subject.distance_of_time_in_words(0, 10*30*24*60*60).should == 'about 10 months' } end context 'using the current time' do let(:date){ Time.local(2008,1,2,1,1,0) } let(:today){ Date.new(2008,1,2) } before{ Date.stub(:today).and_return(today) } let(:rfc3339){ '%Y-%m-%dT%H:%M:%S%z' } it("should output the time for a date that is today") do subject.date(date.strftime(rfc3339)).should =~ /^[0-9]/ end it("should exclude the year for a date that is this year") do subject.date(date.strftime(rfc3339)).should_not match(date.year.to_s) end it("should output the year for a date that is not this year") do older = Date.today - 1*365 subject.date(older.strftime(rfc3339)).should match(older.year.to_s) end it("should handle invalid input") do subject.date('Unknown date').should == 'Unknown date' end context 'when the year is different' do let(:today){ Date.new(2007,1,2) } it{ subject.date(date.strftime(rfc3339)).should match(date.year.to_s) } end context 'when the year of the day is different' do let(:today){ Date.new(2008,1,1) } it{ subject.date(date.strftime(rfc3339)).should_not match(date.year.to_s) } end end context 'with LIBRA_SERVER environment variable' do before do ENV['LIBRA_SERVER'] = 'test.com' user_config end its(:openshift_server) { should == 'test.com' } its(:openshift_url) { should == 'https://test.com' } after { ENV['LIBRA_SERVER'] = nil } end context 'with --server environment variable' do before do subject.options.server = "test.com" end its(:openshift_server) { should == 'test.com' } its(:openshift_url) { should == 'https://test.com' } after { ENV['LIBRA_SERVER'] = nil } end context "without RHC::Config" do subject do Class.new(Object){ include RHC::Helpers }.new end it("should raise on config"){ expect{ subject.config }.to raise_error } end context "with a bad timeout value" do context "on the command line" do let(:arguments){ ['help', '--timeout=string'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match("invalid argument: --timeout=string") } end context "that is a negative integer" do let(:arguments){ ['help', '--timeout=0'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match("must be a positive integer") } end context "via the config" do before{ base_config{ |c, d| d.add 'timeout', 'string' } } let(:arguments){ ['help'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match(/The configuration file.*invalid setting: invalid value for Integer/) } end end context "with a valid client cert file" do let(:arguments){ ['help', '--ssl-client-cert-file=spec/keys/example.pem'] } it{ expect{ run }.to exit_with_code(0) } end context "with a missing client cert file" do context "on the command line" do let(:arguments){ ['help', '--ssl-client-cert-file=not_a_file'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match("The certificate 'not_a_file' cannot be loaded: No such") } end context "via the config" do before{ base_config{ |c, d| d.add 'ssl_client_cert_file', 'not_a_file' } } let(:arguments){ ['help'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match("The certificate 'not_a_file' cannot be loaded: No such") } end end context "with a missing client key file" do context "on the command line" do let(:arguments){ ['help', '--ssl-client-key-file=not_a_file'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match("The RSA key 'not_a_file' cannot be loaded: No such") } end context "via the config" do before{ base_config{ |c, d| d.add 'ssl_client_key_file', 'not_a_file' } } let(:arguments){ ['help'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match("The RSA key 'not_a_file' cannot be loaded: No such") } end end context 'with a valid --ssl-version' do let(:arguments){ ['help', '--ssl-version=sslv3'] } context 'on an older version of HTTPClient' do before{ HTTPClient::SSLConfig.stub(:method_defined?).with(:ssl_version).and_return(false) } it('should print an error') { run_output.should =~ /You are using an older version of the httpclient.*--ssl-version/ } it('should error out') { expect{ run }.to exit_with_code(1) } end context 'a newer version of HTTPClient' do before{ HTTPClient::SSLConfig.stub(:method_defined?).with(:ssl_version).and_return(true) } it('should not print an error') { run_output.should_not =~ /You are using an older version of the httpclient.*--ssl-version/ } it('should error out') { expect{ run }.to exit_with_code(0) } end end context "with an invalid SSLVersion" do context "on the command line" do let(:arguments){ ['help', '--ssl-version=ssl'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match("The provided SSL version 'ssl' is not valid. Supported values: ") } end context "via the config" do before{ base_config{ |c, d| d.add 'ssl_version', 'ssl' } } let(:arguments){ ['help'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match("The provided SSL version 'ssl' is not valid. Supported values: ") } end end context "with an valid ssl CA file" do let(:arguments){ ['help', '--ssl-ca-file=spec/keys/example.pem'] } it{ expect{ run }.to exit_with_code(0) } end context "with an invalid ssl CA file" do let(:arguments){ ['help', '--ssl-ca-file=not_a_file'] } it{ expect{ run }.to exit_with_code(1) } it{ run_output.should match("The certificate 'not_a_file' cannot be loaded: No such file or directory ") } end context "#get_properties" do it{ subject.send(:get_properties, double(:plan_id => 'free'), :plan_id).should == [[:plan_id, 'Free']] } context "when an error is raised" do let(:bar){ double.tap{ |s| s.should_receive(:foo).and_raise(::Exception) } } it{ subject.send(:get_properties, bar, :foo).should == [[:foo, '']] } end end describe "#exec" do it{ subject.send(:exec, 'echo foo').should == [0, "foo\n"] } end context "Git Helpers" do subject{ Class.new(Object){ include RHC::Helpers; include RHC::GitHelpers; def debug?; false; end }.new } before{ subject.stub(:git_version){ raise "Fake Exception" } } its(:has_git?) { should be_false } context "without git" do before{ subject.stub(:git_cmd){ "nonexistent_git" } } its(:has_git?) { should be_false } it { subject.git_config_get('key').should == nil } end context "git clone repo" do let(:stdout){ 'fake git clone' } let(:exit_status){ 0 } let!(:spawn) do out, err = stdout, stderr Open4.should_receive(:spawn).and_return(exit_status) do |cmd, opts| opts['stdout'] << out if out opts['stderr'] << err if err exit_status end true end it { capture{ subject.git_clone_repo("url", "repo").should == File.expand_path('repo') } } it { capture_all{ subject.git_clone_repo("url", "repo") }.should match("fake git clone") } context "does not succeed" do let(:stderr){ 'fatal: error' } let(:exit_status){ 1 } it { capture{ expect{ subject.git_clone_repo("url", "repo") }.to raise_error(RHC::GitException) } } it { capture_all{ subject.git_clone_repo("url", "repo") rescue nil }.should match("fake git clone") } it { capture_all{ subject.git_clone_repo("url", "repo") rescue nil }.should match("fatal: error") } end context "directory is missing" do let(:stderr){ "fatal: destination path 'foo' already exists and is not an empty directory." } let(:exit_status){ 1 } it { capture{ expect{ subject.git_clone_repo("url", "repo") }.to raise_error(RHC::GitDirectoryExists) } } end context "permission denied" do let(:stderr){ "Permission denied (publickey,gssapi-mic)." } let(:exit_status){ 1 } it { capture{ expect{ subject.git_clone_repo("url", "repo") }.to raise_error(RHC::GitPermissionDenied) } } end end end context "SSH Key Helpers" do it "should generate an ssh key then return nil when it tries to create another" do FakeFS do FakeFS::FileSystem.clear subject.generate_ssh_key_ruby.should match("\.ssh/id_rsa\.pub") subject.generate_ssh_key_ruby == nil end end it "should print an error when finger print fails" do Net::SSH::KeyFactory.should_receive(:load_public_key).with('1').and_raise(Net::SSH::Exception.new("An error")) subject.should_receive(:error).with('An error') subject.fingerprint_for_local_key('1').should be_nil end it "should catch exceptions from fingerprint failures" do Net::SSH::KeyFactory.should_receive(:load_public_key).with('1').and_raise(StandardError.new("An error")) subject.fingerprint_for_local_key('1').should be_nil end it "should handle a block in multi_ssh calls" do expect_multi_ssh('foo', 'fakegearid0@fakesshurl.com' => 'bar') subject.run_on_gears('foo', [RHC::Rest::Mock::MockRestGearGroup.new], :as => :gear){ |gear, data, group| data.should == 'bar'; 'test' }.should == ['test'] end it "should handle a block in multi_ssh calls" do expect_multi_ssh('foo', 'fakegearid0@fakesshurl.com' => 'bar') capture{ subject.table_from_gears('foo', [RHC::Rest::Mock::MockRestGearGroup.new], :header => ['cart','col']) }.should match /cart.*col\n-+.*fakegearid0.*bar/m end it "should handle a run_on_gears error for unrecognized type" do expect_multi_ssh('foo', {}) expect{ subject.run_on_gears('foo', RHC::Rest::Mock::MockRestGearGroup.new.gears) }.to raise_error(RuntimeError) end it "should handle an error for unrecognized type" do expect_multi_ssh('foo', {'fakegearid0@fakesshurl.com' => 'bar'}, true) subject.run_on_gears('foo', [RHC::Rest::Mock::MockRestGearGroup.new]) end it "should rescue load errors from ssh-multi" do RHC::SSHHelpers::MultipleGearTask.any_instance.should_receive(:require).and_raise(LoadError) expect{ RHC::SSHHelpers::MultipleGearTask.new(nil,nil,nil).send(:requires_ssh_multi!) }.to raise_error RHC::OperationNotSupportedException, /must install Net::SSH::Multi/ end end describe "#wrap" do it{ "abc".wrap(1).should == "a\nb\nc" } end describe "#textwrap_ansi" do it{ "".textwrap_ansi(80).should == [] } it{ "\n".textwrap_ansi(80).should == ["",""] } it{ "a".textwrap_ansi(1).should == ['a'] } it{ "ab".textwrap_ansi(1).should == ['a','b'] } it{ "ab".textwrap_ansi(2).should == ['ab'] } it{ "ab cd".textwrap_ansi(4).should == ['ab', 'cd'] } it{ " ab".textwrap_ansi(2).should == [' a','b'] } it{ "a b".textwrap_ansi(1).should == ['a','b'] } it{ "a w b".textwrap_ansi(2).should == ['a','w','b'] } it{ "a w b".textwrap_ansi(3).should == ['a w','b'] } it{ "a\nb".textwrap_ansi(1).should == ['a','b'] } it{ "\e[1m".textwrap_ansi(1).should == ["\e[1m\e[0m"] } it{ "\e[31;1m".textwrap_ansi(1).should == ["\e[31;1m\e[0m"] } it{ "\e[1ma".textwrap_ansi(1).should == ["\e[1ma\e[0m"] } it{ "a\e[12m".textwrap_ansi(1).should == ["a\e[12m\e[0m"] } it{ "a\e[12m\e[34mb".textwrap_ansi(1).should == ["a\e[12m\e[34m\e[0m","\e[12m\e[34mb\e[0m"] } it{ "\e[12;34ma".textwrap_ansi(1).should == ["\e[12;34ma\e[0m"] } it{ "\e[1m\e[1m".textwrap_ansi(1).should == ["\e[1m\e[1m\e[0m"] } it{ "\e[1m \e[1m".textwrap_ansi(1).should == ["\e[1m\e[0m", "\e[1m\e[1m\e[0m"] } it{ "\e[1ma\nb".textwrap_ansi(80).should == ["\e[1ma","b"] } it{ "ab".textwrap_ansi(1,false).should == ['ab'] } it{ " abc".textwrap_ansi(3,false).should == [' abc'] } it{ "abcd".textwrap_ansi(3,false).should == ['abcd'] } it{ "abcd\e[1m".textwrap_ansi(3,false).should == ["abcd\e[1m\e[0m"] } it{ "abcd efg a".textwrap_ansi(3,false).should == ['abcd', 'efg', 'a'] } it('next line'){ "abcd e a".textwrap_ansi(5,false).should == ['abcd', 'e a'] } it{ "abcd efgh a".textwrap_ansi(3,false).should == ['abcd', 'efgh', 'a'] } it{ " abcd efg a".textwrap_ansi(3,false).should == [' abcd', 'efg', 'a'] } end describe "#strip_ansi" do it{ "\e[1m \e[1m".strip_ansi.should == " " } it{ "\eiei0".strip_ansi.should == "\eiei0" } it{ "\e[iei0]".strip_ansi.should == "\e[iei0]" } end context "Resolv helper" do let(:resolver) { Object.new } let(:existent_host) { 'real_host' } let(:nonexistent_host) { 'fake_host' } before do Resolv::Hosts.stub(:new) { resolver } resolver.stub(:getaddress).with(existent_host) { existent_host } resolver.stub(:getaddress).with(nonexistent_host){ Resolv::ResolvError } end context "when hosts file has the desired host" do it "does not raise error" do expect { subject.hosts_file_contains?(existent_host) }.to_not raise_error end end context "when hosts file does not have the desired host" do it "does not raise error" do expect { subject.hosts_file_contains?(nonexistent_host) }.to_not raise_error end end end context "cartridge helpers" do before{ mock_terminal } describe '#check_cartridges' do let(:cartridges){ [] } let(:find_cartridges){ [] } context "with a generic object" do it { expect{ subject.send(:check_cartridges, 'foo', :from => cartridges) }.to raise_error(RHC::CartridgeNotFoundException, 'There are no cartridges that match \'foo\'.') } end end describe '#match_cart' do context 'with a nil cart' do let(:cart){ OpenStruct.new(:name => nil, :description => nil, :tags => nil) } it{ subject.send(:match_cart, cart, 'foo').should be_false } end context 'with simple strings' do let(:cart){ OpenStruct.new(:name => 'FOO-more_max any', :description => 'bar word', :tags => [:baz]) } it{ subject.send(:match_cart, cart, 'foo').should be_true } it{ subject.send(:match_cart, cart, 'fo').should be_true } it{ subject.send(:match_cart, cart, 'oo').should be_true } it{ subject.send(:match_cart, cart, 'bar').should be_true } it{ subject.send(:match_cart, cart, 'word').should be_true } it{ subject.send(:match_cart, cart, 'bar word').should be_true } it{ subject.send(:match_cart, cart, 'wor').should be_false } it{ subject.send(:match_cart, cart, 'baz').should be_true } it{ subject.send(:match_cart, cart, 'more max').should be_true } it{ subject.send(:match_cart, cart, 'foo more max any').should be_true } it{ subject.send(:match_cart, cart, 'foo_more max-any').should be_true } end end end describe "#collect_env_vars" do it { subject.collect_env_vars('FOO=BAR').first.to_hash.should == { :name => 'FOO', :value => 'BAR' } } it { subject.collect_env_vars('FOO2=BAR2').first.to_hash.should == { :name => 'FOO2', :value => 'BAR2' } } it { subject.collect_env_vars('FOO_BAR=ZEE').first.to_hash.should == { :name => 'FOO_BAR', :value => 'ZEE' } } it { subject.collect_env_vars('_FOO=BAR').first.to_hash.should == { :name => '_FOO', :value => 'BAR' } } it { subject.collect_env_vars('FOO=').first.to_hash.should == { :name => 'FOO', :value => '' } } it { subject.collect_env_vars('FOO==').first.to_hash.should == { :name => 'FOO', :value => '=' } } it { subject.collect_env_vars('FOO=BAR=ZEE').first.to_hash.should == { :name => 'FOO', :value => 'BAR=ZEE' } } it { subject.collect_env_vars('foo25_=BAR=\][#%*').first.to_hash.should == { :name => 'foo25_', :value => 'BAR=\][#%*' } } it { subject.collect_env_vars('FOO=Test 1 2 3').first.to_hash.should == { :name => 'FOO', :value => 'Test 1 2 3' } } it { subject.collect_env_vars('2FOO=BAR').empty?.should be_true } it { subject.collect_env_vars('FOO.2=BAR').empty?.should be_true } it { subject.collect_env_vars('FOO BAR=ZEE').empty?.should be_true } it { subject.collect_env_vars('FOO*BAR=ZEE').empty?.should be_true } it { subject.collect_env_vars('FOO&BAR=ZEE').empty?.should be_true } it { subject.collect_env_vars('FOO:BAR=ZEE').empty?.should be_true } it { subject.collect_env_vars('FOO@BAR=ZEE').empty?.should be_true } it { subject.collect_env_vars('FOO!BAR=ZEE').empty?.should be_true } end end describe RHC::Helpers::StringTee do let(:other){ StringIO.new } subject{ RHC::Helpers::StringTee.new(other) } context "It should copy output" do before{ subject << 'foo' } its(:string) { should == 'foo' } it("should tee to other") { other.string.should == 'foo' } end end describe Object do context 'present?' do specify('nil') { nil.present?.should be_false } specify('empty array') { [].present?.should be_false } specify('array') { [1].present?.should be_true } specify('string') { 'a'.present?.should be_true } specify('empty string') { ''.present?.should be_false } end context 'presence' do specify('nil') { nil.presence.should be_nil } specify('empty array') { [].presence.should be_nil } specify('array') { [1].presence.should == [1] } specify('string') { 'a'.presence.should == 'a' } specify('empty string') { ''.presence.should be_nil } end context 'blank?' do specify('nil') { nil.blank?.should be_true } specify('empty array') { [].blank?.should be_true } specify('array') { [1].blank?.should be_false } specify('string') { 'a'.blank?.should be_false } specify('empty string') { ''.blank?.should be_true } end end describe OpenURI do context 'redirectable?' do specify('http to https') { OpenURI.redirectable?(URI.parse('http://foo.com'), URI.parse('https://foo.com')).should be_true } specify('https to http') { OpenURI.redirectable?(URI.parse('https://foo.com'), URI.parse('http://foo.com')).should be_false } end end rhc-1.38.7/spec/rhc/command_spec.rb0000644000004100000410000006022512756270552017121 0ustar www-datawww-datarequire 'spec_helper' require 'rhc/commands/base' require 'rhc/exceptions' require 'rest_spec_helper' require 'tempfile' describe RHC::Commands::Base do let!(:config){ base_config } before{ c = RHC::Commands.instance_variable_get(:@commands); @saved_commands = c && c.dup || nil } after do (Kernel.send(:remove_const, subject) if subject.is_a?(Class)) rescue nil RHC::Commands.instance_variable_set(:@commands, @saved_commands) end describe '#object_name' do subject { described_class } its(:object_name) { should == 'base' } context 'when the class is at the root' do subject do Kernel.module_eval do class StaticRootClass < RHC::Commands::Base; def run; 1; end; end end StaticRootClass end its(:object_name) { should == 'static-root-class' } end context 'when the class is nested in a module' do subject do Kernel.module_eval do module Nested; class StaticRootClass < RHC::Commands::Base; def run; 1; end; end; end end Nested::StaticRootClass end its(:object_name) { should == 'static-root-class' } end end describe '#inherited' do let(:instance) { subject.new } let(:commands) { RHC::Commands.send(:commands) } context 'when dynamically instantiating without an object name' do subject { const_for(Class.new(RHC::Commands::Base) { def run; 1; end }) } it("should raise") { expect { subject }.to raise_exception( RHC::Commands::Base::InvalidCommand, /object_name/i ) } end context 'when dynamically instantiating with object_name' do subject { const_for(Class.new(RHC::Commands::Base) { object_name :test; def run(args, options); 1; end }) } it("should register itself") { expect { subject }.to change(commands, :length).by(1) } it("should have an object name") { subject.object_name.should == 'test' } it("should run with wizard") do FakeFS do wizard_run = false RHC::Wizard.stub(:new) do |config| RHC::Wizard.unstub!(:new) w = RHC::Wizard.new(config) w.stub(:run) { wizard_run = true } w end expects_running('test').should call(:run).on(instance).with(no_args) wizard_run.should be_false stderr.should match("You have not yet configured the OpenShift client tools") end end end context 'when statically defined' do subject do Kernel.module_eval do module Nested class Static < RHC::Commands::Base suppress_wizard def run(args, options); 1; end end end end Nested::Static end it("should register itself") { expect { subject }.to change(commands, :length).by(1) } it("should have an object name of the class") { subject.object_name.should == 'static' } it("invokes the right method") { expects_running('static').should call(:run).on(instance).with(no_args) } end context 'when a command calls exit' do subject do Kernel.module_eval do class Failing < RHC::Commands::Base def run exit 2 end end end Failing end it("invokes the right method") { expects_running('failing').should call(:run).on(instance).with(no_args) } it{ expects_running('failing').should exit_with_code(2) } end context 'when statically defined with no default method' do subject do Kernel.module_eval do class Static < RHC::Commands::Base suppress_wizard def test; 1; end argument :testarg, "Test arg", ["--testarg testarg"] summary "Test command execute" alias_action :exe, :deprecated => true def execute(testarg); 1; end argument :args, "Test arg list", ['--tests ARG'], :type => :list, :default => lambda{ |d,a| d[a] = 'a1' } summary "Test command execute-list" def execute_list(args); 1; end argument :arg1, "Test arg", ['--test'], :optional => true, :default => 1 argument :arg2, "Test arg list", ['--test2'], :type => :list, :optional => true argument :arg3, "Test arg list", ['--test3'], :type => :list, :optional => true summary "Test command execute-vararg" def execute_vararg(arg1, arg2, arg3); 1; end argument :arg1, "Test arg", ['--test'], :allow_nil => true, :default => 'def' argument :arg2, "Test arg list", ['--test2'], :type => :list, :optional => true summary "Test command execute-vararg-2" def execute_vararg_2(arg1, arg2, arg3); 1; end =begin # Replace me with a default test case RHC::Helpers.global_option '--test-context', 'Test', :context => :context_var def execute_implicit end argument :testarg, "Test arg", ["--testarg testarg"], :context => :context_var summary "Test command execute" def execute_context_arg(testarg); 1; end =end def raise_error raise StandardError.new("test exception") end def raise_exception raise Exception.new("test exception") end protected def context_var "contextual" end end end Static end it("should register itself") { expect { subject }.to change(commands, :length).by(7) } it("should have an object name of the class") { subject.object_name.should == 'static' } context 'and when test is called' do it { expects_running('static-test').should call(:test).on(instance).with(no_args) } end context 'and when execute is called with argument' do it { expects_running('static-execute', 'simplearg').should call(:execute).on(instance).with('simplearg') } end context 'and when execute is called with argument switch' do it { expects_running('static-execute', '--testarg', 'switcharg').should call(:execute).on(instance).with('switcharg') } end context 'and when execute is called with same argument and switch' do it { expects_running('statis-execute', 'duparg', '--testarg', 'duparg2').should exit_with_code(1) } end context 'and when the provided option is ambiguous' do it { expects_running('static-execute', '-t', '--trace').should raise_error(OptionParser::AmbiguousOption) } end context 'and when execute is called with too many arguments' do it { expects_running('static-execute', 'arg1', 'arg2').should exit_with_code(1) } end context 'and when execute is called with a missing argument' do it { expects_running('static-execute').should exit_with_code(1) } end context 'and when execute_list is called' do it('should expose a default') { expects_running('static-execute-list', '--trace').should call(:execute_list).on(instance).with(['a1']) } it('should handle a default') { expects_running('static-execute-list').should call(:execute_list).on(instance).with(['a1']) } it { expects_running('static-execute-list', '1', '2', '3').should call(:execute_list).on(instance).with(['1', '2', '3']) } it { expects_running('static-execute-list', '1', '2', '3').should call(:execute_list).on(instance).with(['1', '2', '3']) } it('should raise an error') { expects_running('static-execute-list', '--trace', '1', '--', '2', '3').should raise_error(ArgumentError) } it('should make the option an array') { expects_running('static-execute-list', '--tests', '1').should call(:execute_list).on(instance).with(['1']) } it('should make the option available') { command_for('static-execute-list', '1', '2', '3').send(:options).tests.should == ['1','2','3'] } end context 'and when execute_vararg is called' do it{ expects_running('static-execute-vararg').should call(:execute_vararg).on(instance).with(1, [], []) } it{ expects_running('static-execute-vararg', '1', '2', '3').should call(:execute_vararg).on(instance).with('1', ['2', '3'], []) } it("handles a list separator"){ expects_running('static-execute-vararg', '1', '2', '--', '3').should call(:execute_vararg).on(instance).with('1', ['2'], ['3']) } it{ command_for('static-execute-vararg', '1', '2', '--', '3').send(:options).test.should == '1' } it{ command_for('static-execute-vararg', '1', '2', '--', '3').send(:options).test2.should == ['2'] } it{ command_for('static-execute-vararg', '1', '2', '--', '3').send(:options).test3.should == ['3'] } it{ command_for('static-execute-vararg', '--', '2', '3').send(:options).test.should == 1 } it{ command_for('static-execute-vararg', '--', '2', '3').send(:options).test2.should == ['2', '3'] } it{ command_for('static-execute-vararg', '--', '2', '3').send(:options).test3.should == [] } it{ command_for('static-execute-vararg', '--', '--', '3').send(:options).test.should == 1 } it('should exclude the right'){ command_for('static-execute-vararg', '--', '--', '3').send(:options).test2.should == [] } it{ command_for('static-execute-vararg', '--', '--', '3').send(:options).test3.should == ['3'] } end context 'and when execute_vararg_2 is called' do it('should get 2 arguments'){ expects_running('static-execute-vararg-2', '1', '2', '3').should call(:execute_vararg_2).on(instance).with('1', ['2', '3']) } it('should have default argument'){ expects_running('static-execute-vararg-2', '--', '2', '3').should call(:execute_vararg_2).on(instance).with('def', ['2', '3']) } it{ command_for('static-execute-vararg-2', '1', '2', '3').send(:options).test.should == '1' } it{ command_for('static-execute-vararg-2', '1', '2', '3').send(:options).test2.should == ['2', '3'] } end context 'and when an error is raised in a call' do it { expects_running('static-raise-error').should raise_error(StandardError, "test exception") } end context 'and when an exception is raised in a call' do it { expects_running('static-raise-exception').should raise_error(Exception, "test exception") } end context 'and when an exception is raised in a call with --trace option' do it { expects_running('static-raise-exception', "--trace").should raise_error(Exception, "test exception") } end context 'and when deprecated alias is called' do it("prints a warning") do expects_running('static', 'exe', "arg").should call(:execute).on(instance).with('arg') stderr.should match("Warning: This command is deprecated. Please use 'rhc static-execute' instead.") end end context 'and when deprecated alias is called with DISABLE_DEPRECATED env var' do before { ENV['DISABLE_DEPRECATED'] = '1' } after { ENV['DISABLE_DEPRECATED'] = nil } it("raises an error") { expects_running('static', 'exe', 'arg', '--trace').should raise_error(RHC::DeprecatedError) } end end end describe "find_team" do let(:instance){ subject } let(:rest_client){ subject.send(:rest_client) } let(:options){ subject.send(:options) } def expects_method(*args) expect{ subject.send(:find_team, *args) } end it("should raise without option"){ expects_method(nil).to raise_error(ArgumentError, /You must specify a team name with -t, or a team id with --team-id/) } it("should handle team_id option"){ options[:team_id] = 'team_id_o'; expects_method.to call(:find_team_by_id).on(rest_client).with('team_id_o', {}) } it("should handle team_name option"){ options[:team_name] = 'team_o'; expects_method.to call(:find_team).on(rest_client).with('team_o', {}) } it("should handle team_name param"){ options[:team_name] = 'team_o'; expects_method.to call(:find_team).on(rest_client).with('team_o', {}) } end describe "find_domain" do let(:instance){ subject } let(:rest_client){ subject.send(:rest_client) } let(:options){ subject.send(:options) } def expects_method(*args) expect{ subject.send(:find_domain, *args) } end before{ subject.stub(:namespace_context).and_return(nil) } it("should raise without params"){ expects_method(nil).to raise_error(ArgumentError, /You must specify a domain with -n/) } it("should handle namespace param"){ options[:namespace] = 'domain_o'; expects_method.to call(:find_domain).on(rest_client).with('domain_o') } context "with a context" do before{ subject.stub(:namespace_context).and_return('domain_s') } it("should handle namespace param"){ expects_method.to call(:find_domain).on(rest_client).with('domain_s') } end end describe "find_app" do let(:instance){ subject } let(:rest_client){ subject.send(:rest_client) } let(:options){ subject.send(:options) } def expects_method(*args) expect{ subject.send(:find_app, *args) } end before{ subject.stub(:namespace_context).and_return('domain_s') } it("should raise without params"){ expects_method(nil).to raise_error(ArgumentError, /You must specify an application with -a/) } context "when looking for an app" do it("should raise without app") { expects_method.to raise_error(ArgumentError, /You must specify an application with -a, or run this command/) } it("should handle namespace param"){ options[:namespace] = 'domain_o'; expects_method.to raise_error(ArgumentError, /You must specify an application with -a, or run this command/) } it("should accept app param") { options[:app] = 'app_o'; expects_method.to call(:find_application).on(rest_client).with('domain_s', 'app_o', {}) } it("should split app param") { options[:app] = 'domain_o/app_o'; expects_method.to call(:find_application).on(rest_client).with('domain_o', 'app_o', {}) } it("should find gear groups") { options[:app] = 'domain_o/app_o'; expects_method(:with_gear_groups => true, :include => :cartridges).to call(:find_application_gear_groups).on(rest_client).with('domain_o', 'app_o', {:include => :cartridges}) } end end describe "find_membership_container" do let(:instance){ subject } let(:rest_client){ subject.send(:rest_client) } let(:options){ subject.send(:options) } before{ subject.stub(:namespace_context).and_return('domain_s') } def expects_method(*args) expect{ subject.send(:find_membership_container, *args) } end it("should prompt for domain, app, or team") { expects_method.to raise_error(ArgumentError, /You must specify a domain with -n, an application with -a, or a team with -t/) } it("should prompt for domain, or team") { expects_method(:writable => true).to raise_error(ArgumentError, /You must specify a domain with -n, or a team with -t/) } it("should assume domain with -n") { options[:namespace] = 'domain_o'; expects_method.to call(:find_domain).on(rest_client).with('domain_o') } it("should infer -n when -a is available"){ options[:app] = 'app_o'; expects_method.to call(:find_application).on(rest_client).with('domain_s', 'app_o') } it("should split -a param") { options[:app] = 'domain_o/app_o'; expects_method.to call(:find_application).on(rest_client).with('domain_o', 'app_o') } it("should split target arg") { options[:target] = 'domain_o/app_o'; expects_method.to call(:find_application).on(rest_client).with('domain_o', 'app_o') } it("should find team by name") { options[:team_name] = 'team_o'; expects_method.to call(:find_team).on(rest_client).with('team_o') } it("should find team by id") { options[:team_id] = 'team_id_o'; expects_method.to call(:find_team_by_id).on(rest_client).with('team_id_o') } context "when an app context is available" do before{ subject.instance_variable_set(:@local_git_config, {:app => 'app_s'}) } it("should ignore the app context"){ options[:namespace] = 'domain_o'; expects_method(nil).to call(:find_domain).on(rest_client).with('domain_o') } end end describe "rest_client" do let(:instance){ subject } let(:options){ subject.send(:options) } before{ RHC::Rest::Client.any_instance.stub(:api_version_negotiated).and_return(1.4) } context "when initializing the object" do let(:auth){ double('auth') } let(:basic_auth){ double('basic_auth') } let(:x509_auth){ double('x509_auth') } before{ RHC::Auth::Basic.stub(:new).with{ |arg| arg.should == instance.send(:options) }.and_return(basic_auth) } before{ RHC::Auth::X509.stub(:new).with{ |arg| arg.should == instance.send(:options) }.and_return(x509_auth) } before{ RHC::Auth::Token.stub(:new).with{ |arg, arg2, arg3| [arg, arg2, arg3].should == [instance.send(:options), basic_auth, instance.send(:token_store)] }.and_return(auth) } context "with no options" do before{ subject.should_receive(:client_from_options).with(:auth => basic_auth) } it("should create only a basic auth object"){ subject.send(:rest_client) } end context "with x509" do before do options.should_receive(:ssl_client_cert_file).and_return("a cert") options.should_receive(:ssl_client_key_file).and_return("a key") subject.should_receive(:client_from_options).with(:auth => x509_auth) end it("should create an x509 auth object"){ subject.send(:rest_client) } end context "with use_authorization_tokens" do before{ subject.send(:options).use_authorization_tokens = true } before{ subject.should_receive(:client_from_options).with(:auth => auth) } it("should create a token auth object"){ subject.send(:rest_client) } end it { subject.send(:rest_client).should be_a(RHC::Rest::Client) } it { subject.send(:rest_client).should equal subject.send(:rest_client) } end context "from a command line" do subject{ Class.new(RHC::Commands::Base){ object_name :test; def run; 0; end } } let(:instance) { subject.new } let(:rest_client){ command_for(*arguments).send(:rest_client) } let(:basic_auth){ auth = rest_client.send(:auth); auth.is_a?(RHC::Auth::Basic) ? auth : auth.send(:auth) } let(:stored_token){ nil } before{ instance.send(:token_store).stub(:get).and_return(nil) unless stored_token } context "with credentials" do let(:arguments){ ['test', '-l', 'foo', '-p', 'bar'] } it { expect{ rest_client.user }.to call(:user).on(rest_client) } end context "without password" do let(:username){ 'foo' } let(:password){ 'bar' } let(:arguments){ ['test', '-l', username, '--server', mock_uri] } before{ stub_api; challenge{ stub_user(:user => username, :password => password) } } before{ basic_auth.should_receive(:ask).and_return(password) } it("asks for password") { rest_client.user } end context "without name or password" do let(:username){ 'foo' } let(:password){ 'bar' } let(:arguments){ ['test', '--server', mock_uri] } before{ stub_api; challenge{ stub_user(:user => username, :password => password) } } before{ basic_auth.should_receive(:ask).ordered.and_return(username) } before{ basic_auth.should_receive(:ask).ordered.and_return(password) } it("asks for password") { rest_client.user } end context "with token" do let(:username){ 'foo' } let(:token){ 'a_token' } let(:arguments){ ['test', '--token', token, '--server', mock_uri] } before{ stub_api(:token => token); stub_user(:token => token) } it("calls the server") { rest_client.user } end context "with username and a stored token" do let(:username){ 'foo' } let(:stored_token){ 'a_token' } let(:arguments){ ['test', '-l', username, '--server', mock_uri] } before{ stub_api; stub_user(:token => stored_token) } context "when tokens are not allowed" do it("calls the server") { rest_client.send(:auth).is_a? RHC::Auth::Basic } it("does not have a token set") { command_for(*arguments).send(:token_for_user).should be_nil } end context "when tokens are allowed" do let!(:config){ base_config{ |c, d| d.add('use_authorization_tokens', 'true') } } before{ instance.send(:token_store).should_receive(:get).with{ |user, server| user.should == username; server.should == instance.send(:openshift_server) }.and_return(stored_token) } it("has token set") { command_for(*arguments).send(:token_for_user).should == stored_token } it("calls the server") { rest_client.user } end end context "with username and tokens enabled" do let!(:config){ base_config{ |c, d| d.add('use_authorization_tokens', 'true') } } let(:username){ 'foo' } let(:auth_token){ double(:token => 'a_token') } let(:arguments){ ['test', '-l', username, '--server', mock_uri] } before{ instance.send(:token_store).should_receive(:get).with{ |user, server| user.should == username; server.should == instance.send(:openshift_server) }.and_return(nil) } before{ stub_api(false, true); stub_api_request(:get, 'broker/rest/user', false).to_return{ |request| request.headers['Authorization'] =~ /Bearer\s\w+/ ? simple_user(username) : {:status => 401} } } it("should attempt to create a new token") do rest_client.should_receive(:new_session).ordered.and_return(auth_token) rest_client.user end end context "with tokens enabled and a certificate" do let(:config){ base_config{ |c, d| d.add('use_authorization_tokens', 'true') } } let(:cert) do file = Tempfile.new('cert') cert = OpenSSL::X509::Certificate.new cert.version = 2 cert.serial = 1 cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA" cert.issuer = cert.subject cert.not_before = Time.now cert.not_after = cert.not_before + 2 * 365 * 24 * 60 * 60 file.write(cert) file.flush file end let(:arguments){ ['test', '--server', mock_uri, '--ssl-client-cert-file', cert.path] } it("it should generate a certificate fingerprint") do command = command_for(*arguments) command.should_receive(:certificate_fingerprint).with(cert.path) command.send(:token_for_user) end end context "with username and tokens enabled against a server without tokens" do let!(:config){ base_config{ |c, d| d.add('use_authorization_tokens', 'true') } } let(:username){ 'foo' } let(:arguments){ ['test', '-l', username, '--server', mock_uri] } before{ instance.send(:token_store).should_receive(:get).with{ |user, server| user.should == username; server.should == instance.send(:openshift_server) }.and_return(nil) } before do stub_api(false, false) stub_api_request(:get, 'broker/rest/user', false).to_return{ |request| request.headers['Authorization'] =~ /Basic/ ? simple_user(username) : {:status => 401, :headers => {'WWW-Authenticate' => 'Basic realm="openshift broker"'} } } stub_api_request(:get, 'broker/rest/user', {:user => username, :password => 'password'}).to_return{ simple_user(username) } end it("should prompt for password") do basic_auth.should_receive(:ask).once.and_return('password') rest_client.user end end end end end describe Commander::Command::Options do it{ subject.foo = 'bar'; subject.foo.should == 'bar' } it{ subject.foo = 'bar'; subject.respond_to?(:foo).should be_true } it{ subject.foo = lambda{ 'bar' }; subject.foo.should == 'bar' } it{ subject.foo = lambda{ 'bar' }; subject[:foo].should == 'bar' } it{ subject.foo = lambda{ 'bar' }; subject['foo'].should == 'bar' } it{ subject.foo = lambda{ 'bar' }; subject.__hash__[:foo].should be_a Proc } it{ subject[:foo] = lambda{ 'bar' }; subject.foo.should == 'bar' } it{ subject['foo'] = lambda{ 'bar' }; subject.foo.should == 'bar' } it{ subject.is_a?(Commander::Command::Options).should be_true } it{ expect{ subject.barf? }.to raise_error(NoMethodError) } it{ Commander::Command::Options.new(:foo => 1).foo.should == 1 } end rhc-1.38.7/spec/rhc/json_spec.rb0000644000004100000410000000332612756270552016453 0ustar www-datawww-datarequire 'spec_helper' require 'rhc/json' describe RHC::Json do context 'with simple decoded hash as a string' do subject { RHC::Json.decode '{"abc":[123,-456.789e0],"def":[456,-456.789e0],"ghi":"ghj"}' } its(:length) { should == 3 } it('should contain key') { subject.has_key?("abc").should be_true } it('should contain key') { subject.has_key?("def").should be_true } it('should contain key') { subject.has_key?("ghi").should be_true } it('should not contain invalid key') { subject.has_key?("ghj").should be_false } it('should contain value for key') { subject.has_value?("ghj").should be_true } it('should contain array value') { subject["abc"].is_a?(Array).should be_true } it('should contain array with two elements') { subject["abc"].length.should == 2 } it('should contain array with an integer') { subject["abc"][0].should == 123 } it('should contain array with a float') { subject["abc"][1].should == -456.789e0 } end context 'with simple hash' do subject { RHC::Json.encode({"key" => "value"}) } it('should encode to proper json') { subject.should == '{"key":"value"}' } it('should encode and decode to the same hash') { RHC::Json.decode(subject).should == {"key" => "value"} } it('should decode and encode to the same string') { RHC::Json.encode(RHC::Json.decode('{"x":"y"}')).should == '{"x":"y"}' } it('should decode symbol keys') { RHC::Json.decode('{"key":"ok"}', {:symbolize_keys => true}).has_key?(:key).should be_true } it('should decode symbol keys') { RHC::Json.decode('{"key":"ok"}', {:symbolize_keys => true})[:key].should == "ok" } it('should encode symbol keys') { RHC::Json.encode({:key => "ok"}).should == '{"key":"ok"}' } end end rhc-1.38.7/spec/keys/0000755000004100000410000000000012756270552014336 5ustar www-datawww-datarhc-1.38.7/spec/keys/example.pem0000644000004100000410000000257312756270552016503 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIID4DCCAsgCCQDLI3UAX4mf4DANBgkqhkiG9w0BAQUFADCBsTELMAkGA1UEBhMC VVMxFzAVBgNVBAgMDk5vcnRoIENhcm9saW5hMRAwDgYDVQQHDAdSYWxlaWdoMRAw DgYDVQQKDAdSZWQgSGF0MRcwFQYDVQQLDA5PcGVuU2hpZnQgVGVzdDEiMCAGA1UE AwwZdGVzdC5vcGVuc2hpZnQucmVkaGF0LmNvbTEoMCYGCSqGSIb3DQEJARYZdGVz dEBvcGVuc2hpZnQucmVkaGF0LmNvbTAeFw0xMjEyMjIyMTI1MDFaFw0xMzEyMjIy MTI1MDFaMIGxMQswCQYDVQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmEx EDAOBgNVBAcMB1JhbGVpZ2gxEDAOBgNVBAoMB1JlZCBIYXQxFzAVBgNVBAsMDk9w ZW5TaGlmdCBUZXN0MSIwIAYDVQQDDBl0ZXN0Lm9wZW5zaGlmdC5yZWRoYXQuY29t MSgwJgYJKoZIhvcNAQkBFhl0ZXN0QG9wZW5zaGlmdC5yZWRoYXQuY29tMIIBIjAN BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA08M8AZMgpYY+BJ6Of2LVf+vGKCmP 0nMSGe9ESy2LDlgF5TRhg38XyryvkzBI1IBc/0+VuDxh5IbxGFUkBNsdVz9xfKKB b1LBxVI9QuhlKyY0V+VsUAwMLOQGh7YnThBrLlSE4+oWha5mnfnC4qRaeI3LOOYe jCaVH3ee/+gzcGKf5IW+cSbF97/++38AHHIW8x87sBfZBkP05ZiVKW1zsMS1HZMa wc2JfvZaJZZqzezphwzfPxS11MbucceZEIqj1u47qT6957dZ/uDpmS+uPZBMS/FZ xsnHpUIiAAP1Dm4jQXON62NfrR54Jr0O7/Z4XV42GF6R0Z2xEuoP1CDUmwIDAQAB MA0GCSqGSIb3DQEBBQUAA4IBAQBx9MAVgYBSWGlc1Ja5vJYeYOc1eRUAGljWQN1Y t3/iEenIu8ZLEYKqUQEFzDvjUhaVOyW4XpoLR6SQdcvOGt54aYQ5a5sRPmXtaJjm 9ic3lJuZ+c4He4APl7j0xQ0IQPNwbqkIe6MFxISq0/4+iJDQgOTmoXm5WKKP1b48 8s3xyJaky2LEKoUvng81/TjvOE6i7MPSGBaT64kGm1DwYjLGm8C7Q8e6fDpTAR9H ukfpcIRls7mrqtP4vvhf5+NngJ6Ubo7hrcrWLijGub1+QAo8XlXxp9258sED+Vyx 0596NONiYyu3h7eyD4BcawfSE0dPm0bNcq+XTMnhK3tVriTB -----END CERTIFICATE----- rhc-1.38.7/spec/keys/server.pem0000644000004100000410000000220712756270552016350 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIIDKzCCApSgAwIBAgICOcMwDQYJKoZIhvcNAQEFBQAwgbExCzAJBgNVBAYTAi0t MRIwEAYDVQQIDAlTb21lU3RhdGUxETAPBgNVBAcMCFNvbWVDaXR5MRkwFwYDVQQK DBBTb21lT3JnYW5pemF0aW9uMR8wHQYDVQQLDBZTb21lT3JnYW5pemF0aW9uYWxV bml0MRkwFwYDVQQDDBBpcC0xMC0xMi0xMjMtMjI5MSQwIgYJKoZIhvcNAQkBFhVy b290QGlwLTEwLTEyLTEyMy0yMjkwHhcNMTIxMjE2MDUyNzA3WhcNMTMxMjE2MDUy NzA3WjCBsTELMAkGA1UEBhMCLS0xEjAQBgNVBAgMCVNvbWVTdGF0ZTERMA8GA1UE BwwIU29tZUNpdHkxGTAXBgNVBAoMEFNvbWVPcmdhbml6YXRpb24xHzAdBgNVBAsM FlNvbWVPcmdhbml6YXRpb25hbFVuaXQxGTAXBgNVBAMMEGlwLTEwLTEyLTEyMy0y MjkxJDAiBgkqhkiG9w0BCQEWFXJvb3RAaXAtMTAtMTItMTIzLTIyOTCBnzANBgkq hkiG9w0BAQEFAAOBjQAwgYkCgYEA5yEY0irLovUbcFoCnhon8zo4yp68+UBBtJ3A 0apkSHhU+P29/YgKTbMoSu0xGTiDnih77KY3jq5SS3O7kKMrqA8otqp96Hl4iB+X KNa9njZIP5FS3RmS2nOzrlNQvSwOjK4cHJYkzH6ozIBOgz3qaCNC8skmNCdb0LmZ TX9lYssCAwEAAaNQME4wHQYDVR0OBBYEFIoxHQJuFHsreItT4b6Vx9XB5rrtMB8G A1UdIwQYMBaAFIoxHQJuFHsreItT4b6Vx9XB5rrtMAwGA1UdEwQFMAMBAf8wDQYJ KoZIhvcNAQEFBQADgYEAHDAQGgA5PmauN0GKJuUbHkH/L7cBgxs6B2Rnbx8r+zJM a3OdnEhJRwhfXEsw47Uo7Uo78auCPNO5VQCMxmSwejJ7BbsWuqpKpKL/UsZv3qeJ pyXCQnzYnYsuTe1b72AZ7ek6S4s5Utc5L+sqWOM+WcKODC5yxrEQPrtMjXKNXZY= -----END CERTIFICATE----- rhc-1.38.7/spec/keys/example_private.pem0000644000004100000410000000321712756270552020231 0ustar www-datawww-data-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA08M8AZMgpYY+BJ6Of2LVf+vGKCmP0nMSGe9ESy2LDlgF5TRh g38XyryvkzBI1IBc/0+VuDxh5IbxGFUkBNsdVz9xfKKBb1LBxVI9QuhlKyY0V+Vs UAwMLOQGh7YnThBrLlSE4+oWha5mnfnC4qRaeI3LOOYejCaVH3ee/+gzcGKf5IW+ cSbF97/++38AHHIW8x87sBfZBkP05ZiVKW1zsMS1HZMawc2JfvZaJZZqzezphwzf PxS11MbucceZEIqj1u47qT6957dZ/uDpmS+uPZBMS/FZxsnHpUIiAAP1Dm4jQXON 62NfrR54Jr0O7/Z4XV42GF6R0Z2xEuoP1CDUmwIDAQABAoIBAQCrb/ani7Y1a13Q HZFdneUcwANXxnYSOWihKGzQtBlMzoRPUD3V2FGpp2I5NjDwIM+pPoQGeJQEEB8l xN/5Mm8pkEpPTbyeJJCNMAw6m6wz56Uet7UdhPNicGS9AAIIGBC9nF1Nqtj69vtK z8Yv+EDqMlkhQmPesOmvZQeLRDBIvYUdKFMc2tvBBqAkVUbcpRICUmHbAjyGs3qi +K3eBym9Hhv5ICLNAsvSHUwb/vVN0uvr5qMrcl8kFWBsNgCac6dj3eAMU90olV4x BjmRBln30RlwbVbUKownVqpyCi+JCEENQ74Dcg9eQkpyTC/csL+7E1IeaLoz8aKA uAGhF6SRAoGBAP5jtNY70HHPDjyB4ySkuaJKvt316MASqOp79Q4vvrAPVAuX2LlX ce0WzFid5ZssfTBQ5QnX3gDGnJ+daZS3QwgquDEfyR8rdkxnhfcjrl79+vLom394 vUMA8dS/ctk2lN7UQq3qXkqg2cBkJ2sd0FkjYgRmdWcVysKEOrxiCi7DAoGBANUa cSEoLfluI4XPfeXe6IYlfdnQkk+0uyUD+XnKPj3Sh8YEh0xx/24fAIY9gEtTSiXK E+a0iVoRcF6Kr/1jj4BYoZTG67RqsJWDr8ISnMES+dfQoKOWQg4GTPbCvMlluBwH dPCgDS3xgXw7MmJTsnvn69nVQTIzFZFZ5GAJ1pVJAoGAGpvFQ49YGz9kJtITNzb/ r8kPs9nP8Z9CCdzTYht+X54K1XeZlLFf2kPDNhW1+YgqxA4CFwh8USp1IYDulT6i BU2qnIDNobQDGLTPX21dBNSeFiBce3Xeng6QPQeVdMDvy2r6WoSkPjwa6rPPJ7Yj n99ClmE9MH4oCjaYijHbDM8CgYBb3mtY7PZs6oNb42iWGbPKaQ1JQnZg8DwhqAX7 8ClHA6TOxBMD7L0+XxUdyhOt5Xk4s1ZDBh+UeaIkz8sxBKPPo+X8uojQnZIE0uGJ W/bB8YlN9b3a6CMP4r15P9idRkHJq5lJRuaHoO6+fNKquwReEEyH+zyBBK7Om58t m/ArKQKBgQCcamRni3FOi6kK8YT/cv4Tobx4e8qwOJcX+93E8DdrB2UP3MLfkwA5 thThxl4Lub50bgtUJ5yfNDS0nCZed/Ax/SqzizvCCJbw7BqSOBbJv/BSsql3k1FD hoBg8a1zkB8dDQNoKj2Om5NM7U+rL1Ujp9uW/qcLaejuks3C441FGw== -----END RSA PRIVATE KEY----- rhc-1.38.7/spec/spec_helper.rb0000644000004100000410000003743212756270552016212 0ustar www-datawww-datarequire 'coverage_helper' require 'webmock/rspec' require 'fakefs/safe' require 'rbconfig' if ENV['PRY'] require 'pry' require 'pry-debugger' end # Environment reset ENV['http_proxy'] = nil ENV['HTTP_PROXY'] = nil ENV['OPENSHIFT_CONFIG'] = nil class FakeFS::Mode def initialize(mode_s) @s = mode_s end def to_s(*args) @s end end class FakeFS::Stat attr_reader :mode def initialize(mode_s) @mode = FakeFS::Mode.new(mode_s) end end # chmod isn't implemented in the released fakefs gem # but is in git. Once the git version is released we # should remove this and actively check permissions class FakeFS::File def self.chmod(*args) # noop end def self.stat(path) FakeFS::Stat.new(mode(path)) end def self.mode(path) @modes && @modes[path] || '664' end def self.expect_mode(path, mode) (@modes ||= {})[path] = mode end # FakeFS incorrectly assigns this to '/' remove_const(:PATH_SEPARATOR) rescue nil const_set(:PATH_SEPARATOR, ":") const_set(:ALT_SEPARATOR, '') rescue nil def self.executable?(path) # if the file exists we will assume it is executable # for testing purposes self.exists?(path) end end IO.instance_eval{ alias :read_orig :read } module FakeFS class << self alias_method :activate_orig, :activate! alias_method :deactivate_orig, :deactivate! def activate! IO.instance_eval{ def self.read(path); FakeFS::File.read(path); end } activate_orig end def deactivate! IO.instance_eval{ alias :read :read_orig } deactivate_orig end end end require 'rhc/cli' class MockHighLineTerminal < HighLineExtension def self.use_color? true end def initialize(input=StringIO.new, output=StringIO.new) super @last_read_pos = 0 end ## # read # # seeks to the last read in the IO stream and reads # the data from that position so we don't repeat # reads or get empty data due to writes moving # the caret to the end of the stream def read @output.seek(@last_read_pos) result = @output.read @last_read_pos = @output.pos result end ## # write_line # # writes a line of data to the end of the # input stream appending a newline so # highline knows to stop processing and then # resets the caret position to the last read def write_line(str) reset_pos = @input.pos # seek end so we don't overwrite anything @input.seek(0, IO::SEEK_END) result = @input.write "#{str}\n" @input.seek(reset_pos) result end def close_write @input.close_write end end include WebMock::API require 'httpclient' require 'webmock/http_lib_adapters/httpclient_adapter' # # Patched from WebMock 1.11, needs to be upstreamed # class WebMockHTTPClient def do_get(req, proxy, conn, stream = false, &block) request_signature = build_request_signature(req, :reuse_existing) WebMock::RequestRegistry.instance.requested_signatures.put(request_signature) if webmock_responses[request_signature] webmock_response = webmock_responses.delete(request_signature) response = build_httpclient_response(webmock_response, stream, &block) @request_filter.each do |filter| # CHANGED r = filter.filter_response(req, response) res = do_get(req, proxy, conn, stream, &block) if r == :retry # END CHANGES end res = conn.push(response) WebMock::CallbackRegistry.invoke_callbacks( {:lib => :httpclient}, request_signature, webmock_response) res elsif WebMock.net_connect_allowed?(request_signature.uri) # in case there is a nil entry in the hash... webmock_responses.delete(request_signature) res = if stream do_get_stream_without_webmock(req, proxy, conn, &block) else do_get_block_without_webmock(req, proxy, conn, &block) end res = conn.pop conn.push(res) if WebMock::CallbackRegistry.any_callbacks? webmock_response = build_webmock_response(res) WebMock::CallbackRegistry.invoke_callbacks( {:lib => :httpclient, :real_request => true}, request_signature, webmock_response) end res else raise WebMock::NetConnectNotAllowedError.new(request_signature) end end end def stderr $stderr.rewind # some systems might redirect warnings to stderr [$stderr,$terminal].map(&:read).delete_if{|x| x.strip.empty?}.join(' ') end module Commander::UI alias :enable_paging_old :enable_paging def enable_paging end end module CommandExampleHelpers # # Allow this example to stub command methods # by guaranteeing the instance exists prior # to command execution. Use with methods on # CommandHelpers # def using_command_instance subject{ described_class } let(:instance) { described_class.new } end end # # Helper methods for executing commands and # stubbing/mocking methods # module CommandHelpers def command_runner(*args) mock_terminal new_command_runner *args do instance #ensure instance is created before subject :new is mocked subject.stub(:new).and_return(instance) RHC::Commands.to_commander end end def run!(*args) command_runner(*args).run! end def expects_running(*args, &block) r = command_runner(*args) lambda { r.run! } end def command_for(*args) command = nil RHC::Commands.stub(:execute){ |cmd, method, args| command = cmd; 0 } command_runner(*args).run! command end # # These methods assume the example has declared # let(:arguments){ [] } # def run_command(args=arguments) mock_terminal input.each { |i| $terminal.write_line(i) } if respond_to?(:input) $terminal.close_write run!(*args) end def command_output(args=arguments) run_command(args) rescue SystemExit => e "#{@output.string}\n#{$stderr.string}#{e}" else "#{@output.string}\n#{$stderr.string}" end # # These methods bypass normal command stubbing. # Should really be used when stubbing the whole # path. Most specs should use run_instance. # def run(input=[]) mock_terminal input.each { |i| $terminal.write_line(i) } $terminal.close_write RHC::CLI.start(arguments) end def run_output(input=[]) run(input) rescue SystemExit => e "#{@output.string}\n#{$stderr.string}#{e}" else "#{@output.string}\n#{$stderr.string}" end end module ClassSpecHelpers include Commander::Delegates def const_for(obj=nil) if obj Object.const_set(const_for, obj) else "#{example.full_description}".split(" ").map{|word| word.capitalize}.join.gsub(/[^\w]/, '') end end def with_constants(constants, base=Object, &block) constants.each do |constant, val| base.const_set(constant, val) end block.call ensure constants.each do |constant, val| base.send(:remove_const, constant) end end def new_command_runner *args, &block Commander::Runner.instance_variable_set :"@singleton", RHC::CommandRunner.new(args) program :name, 'test' program :version, '1.2.3' program :description, 'something' program :help_formatter, RHC::HelpFormatter #create_test_command yield if block Commander::Runner.instance end def mock_terminal @input = StringIO.new @output = StringIO.new $stdout = @output $stderr = (@error = StringIO.new) $terminal = MockHighLineTerminal.new @input, @output end def input_line(s) $terminal.write_line s end def last_output(&block) if block_given? yield $terminal.read else $terminal.read end end def capture(&block) old_stdout = $stdout old_stderr = $stderr old_terminal = $terminal @input = StringIO.new @output = StringIO.new $stdout = @output $stderr = (@error = StringIO.new) $terminal = MockHighLineTerminal.new @input, @output yield @output.string ensure $stdout = old_stdout $stderr = old_stderr $terminal = old_terminal end def capture_all(&block) old_stdout = $stdout old_stderr = $stderr old_terminal = $terminal @input = StringIO.new @output = StringIO.new $stdout = @output $stderr = @output $terminal = MockHighLineTerminal.new @input, @output yield @output.string ensure $stdout = old_stdout $stderr = old_stderr $terminal = old_terminal end # # usage: stub_request(...).with(&user_agent_header) # def user_agent_header lambda do |request| #User-Agent is not sent to mock by httpclient #request.headers['User-Agent'] =~ %r{\Arhc/\d+\.\d+.\d+ \(.*?ruby.*?\)} true end end def base_config(&block) config = RHC::Config.new config.stub(:load_config_files) config.stub(:load_servers) defaults = config.instance_variable_get(:@defaults) yield config, defaults if block_given? RHC::Config.stub(:default).and_return(config) RHC::Config.stub(:new).and_return(config) config end def user_config user = respond_to?(:username) ? self.username : 'test_user' password = respond_to?(:password) ? self.password : 'test pass' server = respond_to?(:server) ? self.server : nil base_config do |config, defaults| defaults.add 'default_rhlogin', user defaults.add 'password', password defaults.add 'libra_server', server if server end.tap do |c| opts = c.to_options opts[:rhlogin].should == user opts[:password].should == password opts[:server].should == server if server end end def local_config user = respond_to?(:local_config_username) ? self.local_config_username : 'test_user' password = respond_to?(:local_config_password) ? self.local_config_password : 'test pass' server = respond_to?(:local_config_server) ? self.local_config_server : nil c = base_config local_config = RHC::Vendor::ParseConfig.new local_config.add('default_rhlogin', user) if user local_config.add('password', password) if password local_config.add('libra_server', server) if server c.instance_variable_set(:@local_config, local_config) c.instance_variable_set(:@servers, RHC::Servers.new) opts = c.to_options opts[:rhlogin].should == user opts[:password].should == password opts[:server].should == server if server end def expect_multi_ssh(command, hosts, check_error=false) require 'net/ssh/multi' session = double('Multi::SSH::Session') described_class.any_instance.stub(:requires_ssh_multi!) channels = hosts.inject({}) do |h, (k,v)| c = double(:properties => {}, :connection => double(:properties => {})) h[k] = c h end session.should_receive(:use).exactly(hosts.count).times.with do |host, opts| opts[:properties].should_not be_nil opts[:properties][:gear].should_not be_nil hosts.should have_key(host) channels[host].connection.properties.merge!(opts[:properties]) true end session.stub(:_command).and_return(command) session.stub(:_hosts).and_return(hosts) session.stub(:_channels).and_return(channels) session.instance_eval do def exec(arg1, *args, &block) arg1.should == _command _hosts.to_a.sort{ |a,b| a[0] <=> b[0] }.each do |(k,v)| block.call(_channels[k], :stdout, v) end end end session.should_receive(:loop) unless hosts.empty? Net::SSH::Multi.should_receive(:start).and_yield(session).with do |opts| opts.should have_key(:on_error) capture_all{ opts[:on_error].call('test') }.should =~ /Unable to connect to gear test/ if check_error true end session end end module ExitCodeMatchers RSpec::Matchers.define :exit_with_code do |code| actual = nil match do |block| begin actual = block.call rescue SystemExit => e actual = e.status end actual and actual == code end failure_message_for_should do |block| "expected block to call exit(#{code}) but exit" + (actual.nil? ? " not called" : "(#{actual}) was called") end failure_message_for_should_not do |block| "expected block not to call exit(#{code})" end description do "expect block to call exit(#{code})" end end end module CommanderInvocationMatchers InvocationMatch = Class.new(RuntimeError) RSpec::Matchers.define :call do |method| chain :on do |object| @object = object end chain :with do |*args| @args = args end chain :and_stop do @stop = true end match do |block| e = @object.should_receive(method) e.and_raise(InvocationMatch) if @stop e.with(*@args) if @args begin block.call true rescue InvocationMatch => e true rescue SystemExit => e false end end description do "expect block to invoke '#{method}' on #{@object} with #{@args}" end end RSpec::Matchers.define :not_call do |method| chain :on do |object| @object = object end chain :with do |*args| @args = args end match do |block| e = @object.should_not_receive(method) e.with(*@args) if @args begin block.call true rescue SystemExit => e false end end description do "expect block to invoke '#{method}' on #{@object} with #{@args}" end end end module ColorMatchers COLORS = { :green => 32, :yellow => 33, :cyan => 36, :red => 31, :clear => 0 } RSpec::Matchers.define :be_colorized do |msg,color| match do |actual| actual == colorized_message(msg,color) end failure_message_for_should do |actual| failure_message(actual,msg,color) end failure_message_for_should_not do |actual| failure_message(actual,msg,color) end def failure_message(actual,msg,color) "expected: #{colorized_message(msg,color).inspect}\n" + " got: #{actual.inspect}" end def ansi_code(color) "\e[#{ColorMatchers::COLORS[color]}m" end def colorized_message(msg,color) [ ansi_code(color), msg, ansi_code(:clear) ].join('') end end end def mac? RbConfig::CONFIG['host_os'] =~ /^darwin/ end def mock_save_snapshot_file(app) @ssh_uri = URI.parse app.ssh_url mock_popen3("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'snapshot' > #{@app.name}.tar.gz", nil, "Pulling down a snapshot of application '#{app.name}' to #{app.name}.tar.gz", nil) end def mock_restore_snapshot_file(app) @ssh_uri = URI.parse app.ssh_url File.stub(:exists?).and_return(true) RHC::TarGz.stub(:contains).and_return(true) in_mock, out_mock, err_mock = mock_popen3("ssh #{@ssh_uri.user}@#{@ssh_uri.host} 'restore INCLUDE_GIT'", nil, "Restoring from snapshot #{app.name}.tar.gz to application '#{app.name}'", nil) lines = '' File.open(File.expand_path('../rhc/assets/targz_sample.tar.gz', __FILE__), 'rb') do |file| file.chunk(4096) do |chunk| lines << chunk end end in_mock.stub(:write) in_mock.stub(:close_write) end def mock_popen3(cmd, std_in, std_out, std_err) in_mock = double('std in') out_mock = double('std out') err_mock = double('std err') in_mock.stub(:gets).and_return(std_in) out_mock.stub(:gets).and_return(std_out) err_mock.stub(:gets).and_return(std_err) Open3.stub(:popen3) Open3.should_receive(:popen3).with(cmd).and_yield(in_mock, out_mock, err_mock) return in_mock, out_mock, err_mock end RSpec.configure do |config| config.include(ExitCodeMatchers) config.include(CommanderInvocationMatchers) config.include(ColorMatchers) config.include(ClassSpecHelpers) config.include(CommandHelpers) config.extend(CommandExampleHelpers) config.backtrace_clean_patterns = [] if ENV['FULL_BACKTRACE'] end module TestEnv extend ClassSpecHelpers class << self attr_accessor :instance, :subject def instance=(i) self.subject = i.class @instance = i end end end rhc-1.38.7/spec/direct_execution_helper.rb0000644000004100000410000002542412756270552020613 0ustar www-datawww-datarequire 'open4' require 'rhc/helpers' require 'tmpdir' $source_bin_rhc = File.expand_path('bin/rhc') SimpleCov.minimum_coverage = 0 # No coverage testing for features # # RHC_DEBUG=true TEST_INSECURE=1 TEST_USERNAME=test1 TEST_PASSWORD=password \ # RHC_SERVER=hostname \ # bundle exec rspec features/*_feature.rb # module RhcExecutionHelper class Result < Struct.new(:args, :status, :stdout, :stderr) def to_s "Ran #{args.inspect} and got #{status}\n#{'-'*50}\n#{stdout}#{'-'*50}\n#{stderr}" end def successful? status == 0 end end def when_running(*args) subject{ rhc *args } let(:output){ subject.stdout } let(:status){ subject.status } before{ standard_config } end def a_web_cartridge if ENV['RHC_TARGET'] == 'rhel' || !File.exist?("/etc/fedora-release") 'php-5.3' else 'php-5.5' end end def a_random_cartridge(with_tags=nil, for_user=nil) c = for_user ? for_user.client : client c.cartridges.select{|cartridge| with_tags.nil? || (with_tags - cartridge.tags).empty?}.shuffle.first.name end def rhc(*args) opts = args.pop if args.last.is_a? Hash opts ||= {} if user = opts[:as] args << '--rhlogin' args << user.login if user.attributes.has_key? :token args << '--token' args << user.attributes[:token] elsif user.attributes.has_key? :password args << '--password' args << user.attributes[:password] end elsif !server_supports_sessions? args << '--password' args << ENV['TEST_PASSWORD'] end oldenv = if opts[:env] old = ENV.to_hash ENV.update(opts[:env]) old end execute_command(args.unshift(rhc_executable), opts[:with]) ensure ENV.replace(oldenv) if oldenv end def execute_command(args, stdin="", tty=true) stdin = stdin.join("\n") if stdin.is_a? Array stdout, stderr = if debug? [debug, debug].map{ |t| RHC::Helpers::StringTee.new(t) } else [StringIO.new, StringIO.new] end args.map!(&:to_s) status = Open4.spawn(args, 'stdout' => stdout, 'stderr' => stderr, 'stdin' => stdin, 'quiet' => true) stdout, stderr = [stdout, stderr].map(&:string) Result.new(args, status, stdout, stderr).tap do |r| debug.puts "\n[#{example_description}] #{r}" if debug? end end def rhc_executable ENV['RHC_TEST_SYSTEM'] ? 'rhc' : $source_bin_rhc end def client @client ||= (@environment && @environment[:client]) || begin WebMock.allow_net_connect! opts = {:server => ENV['RHC_SERVER']} if token = RHC::Auth::TokenStore.new(File.expand_path("~/.openshift")).get(ENV['TEST_USERNAME'], ENV['RHC_SERVER']) opts[:token] = token else opts[:user] = ENV['TEST_USERNAME'] opts[:password] = ENV['TEST_PASSWORD'] end opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if ENV['TEST_INSECURE'] == '1' env = RHC::Rest::Client.new(opts) @environment[:client] = env if @environment env end end def base_client(user, password) opts = {:server => ENV['RHC_SERVER']} opts[:user] = user opts[:password] = password opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if ENV['TEST_INSECURE'] == '1' RHC::Rest::Client.new(opts) end def no_applications(constraint=nil) debug.puts "Removing applications that match #{constraint}" if debug? apps = client.reset.applications apps.each do |app| next if constraint && !(app.name =~ constraint) debug.puts " removing #{app.name}" if debug? app.destroy end end def other_users $other_users ||= begin (ENV['TEST_OTHER_USERS'] || "other1:a,other2:b,other3:c,other4:d").split(',').map{ |s| s.split(':') }.inject({}) do |h, (u, p)| register_user(u,p) unless ENV['REGISTER_USER'].nil? h[u] = base_client(u, p).user h[u].attributes[:password] = p h end end end def no_members(object) object.delete_members object.members.length.should == 1 end def has_gears_available(gear_count=1, for_user=nil) debug.puts "Ensuring user has at least #{gear_count} gears free" if debug? c = for_user ? for_user.client : client max_gears = c.user.max_gears consumed_gears = c.user.consumed_gears raise "User has max_gears=#{max_gears}, cannot make #{gear_count} gears available" if max_gears < gear_count max_consumed = max_gears - gear_count if consumed_gears <= max_consumed debug.puts " user can use up to #{max_consumed} gears and is only using #{consumed_gears}" if debug? return end delete_count = 0 c.owned_applications.sort_by(&:gear_count).reverse.each do |app| if (consumed_gears - delete_count) > max_consumed debug.puts " deleting app to free #{app.gear_count} gears" if debug? delete_count += app.gear_count debug.puts " user is now using #{consumed_gears - delete_count} gears" if debug? app.destroy end end end def has_an_application(for_user=nil) c = for_user ? for_user.client : client debug.puts "Creating or reusing an app" if debug? apps = c.applications apps.find{|app| !app.scalable?} or begin domain = has_a_domain(for_user) debug.puts " creating a new application" if debug? c.domains.first.add_application("test#{random}", :cartridges => [a_web_cartridge]) end end def has_a_scalable_application(for_user=nil) c = for_user ? for_user.client : client debug.puts "Creating or reusing a scalable app" if debug? apps = c.applications apps.find(&:scalable?) or begin domain = has_a_domain(for_user) debug.puts " creating a new scalable application" if debug? c.domains.first.add_application("scalable#{random}", :cartridges => [a_web_cartridge], :scale => true) end end def has_a_domain(for_user=nil) c = for_user ? for_user.client : client debug.puts "Creating or reusing a domain" if debug? domain = c.domains.first or begin debug.puts " creating a new domain" if debug? c.add_domain("test#{random}") end end def setup_args(opts={}) c = opts[:client] || client args = [] args << (opts[:server] || ENV['RHC_SERVER'] || 'localhost') args << 'yes' if (ENV['TEST_INSECURE'] == '1' || false) args << (opts[:login] || ENV['TEST_USERNAME']) args << (opts[:password] || ENV['TEST_PASSWORD']) args << 'yes' if server_supports_sessions?(c) args << 'yes' # generate a key, temp dir will never have one args << (opts[:login] || ENV['TEST_USERNAME']) if (c.find_key('default').present? rescue false) # same key name as username args << (opts[:domain_name] || "d#{random}") if (c.domains.empty? rescue true) args end def has_local_ssh_key(user) with_environment(user) do r = rhc :setup, :with => setup_args(:login => user.login, :password => user.attributes[:password], :domain_name => "\n") r.status.should == 0 user end end def ssh_exec_for_env @environment[:ssh_exec] end def use_clean_config environment FileUtils.rm_rf(File.join(@environment[:dir], ".openshift")) client.reset end def standard_config environment(:standard) do r = rhc :setup, :with => setup_args raise "Unable to configure standard config" if r.status != 0 end client.reset end def debug? @debug ||= !!ENV['RHC_DEBUG'] end def debug (*args) @debug_stream ||= begin if debug? if ENV['RHC_DEBUG'] == 'true' STDERR else File.open(ENV['RHC_DEBUG'], 'w') end else StringIO.new end end end def random @environment[:id] end def server_supports_sessions?(c=client) @environment && c.supports_sessions? end def with_environment(user, &block) previous = @environment @environment, @client = nil env = ENV.to_hash ENV['TEST_RANDOM_USER'] = nil ENV['TEST_USERNAME'] = user.login ENV['TEST_PASSWORD'] = user.attributes[:password] environment("custom_#{user.login}") yield ensure @environment = previous ENV.replace(env) end private def example_description if respond_to?(:example) && example example.metadata[:full_description] else self.class.example.metadata[:full_description] end end def environment(id=nil) unless @environment is_new = false e = Environments.get(id){ is_new = true} update_env(e) dir = Dir.mktmpdir('rhc_features_test') at_exit{ FileUtils.rm_rf(dir) } unless ENV['RHC_DEBUG_DIRS'] Dir.chdir(dir) @client = e[:client] @environment = e yield if block_given? && is_new end @environment end def update_env(config) ENV['HOME'] = config[:dir] ENV['RHC_SERVER'] ||= 'openshift.redhat.com' if ENV['TEST_RANDOM_USER'] { 'TEST_USERNAME' => "test_user_#{config[:id]}", 'TEST_PASSWORD' => "password", }.each_pair{ |k,v| ENV[k] = v } else ENV['TEST_USERNAME'] or raise "No TEST_USERNAME set" ENV['TEST_PASSWORD'] or raise "No TEST_PASSWORD set" end register_user(ENV['TEST_USERNAME'],ENV['TEST_PASSWORD']) unless ENV['REGISTER_USER'].nil? ENV['GIT_SSH'] = config[:ssh_exec] end def register_user(user,password) if File.exists?("/etc/openshift/plugins.d/openshift-origin-auth-mongo.conf") command = "bash -c 'unset GEM_HOME; unset GEM_PATH; oo-register-user -l admin -p admin --username #{user} --userpass #{password}'" if Object.const_defined?('Bundler') Bundler::with_clean_env do system command end else system command end elsif File.exists?("/etc/openshift/plugins.d/openshift-origin-auth-remote-user.conf") system "/usr/bin/htpasswd -b /etc/openshift/htpasswd #{user} #{password}" else #ignore print "Unknown auth plugin. Not registering user #{user}/#{password}." print "Modify #{__FILE__}:239 if user registration is required." cmd = nil end end end module Environments def self.get(id, &block) (@environments ||= {})[id] ||= begin dir = Dir.mktmpdir('rhc_features') at_exit{ FileUtils.rm_rf(dir) } unless ENV['RHC_DEBUG_DIRS'] id = Random.rand(1000000) ssh_exec = create_ssh_exec(dir) yield if block_given? {:dir => dir, :id => id, :ssh_exec => ssh_exec} end end def self.create_ssh_exec(dir, for_user=nil) ssh_exec = File.join(dir, "ssh_exec#{for_user ? "_#{for_user}" : ""}") IO.write(ssh_exec, "#!/bin/sh\nssh -o StrictHostKeyChecking=no -i #{dir}/.ssh/id_rsa \"$@\"") FileUtils.chmod("u+x", ssh_exec) ssh_exec end end RSpec.configure do |config| config.include(RhcExecutionHelper) config.extend(RhcExecutionHelper) end rhc-1.38.7/spec/coverage_helper.rb0000644000004100000410000000561712756270552017053 0ustar www-datawww-dataunless RUBY_VERSION < '1.9' require 'simplecov' # Patch to get correct coverage count, filed # https://github.com/colszowka/simplecov/issues/146 upstream. class SimpleCov::Result def missed_lines return @missed_lines if defined? @missed_lines @missed_lines = 0 @files.each do |file| @missed_lines += file.missed_lines.count end @missed_lines end def print_missed_lines @files.each do |file| file.missed_lines.each do |line| STDERR.puts "MISSED #{file.filename}:#{line.number}" end end end end unless SimpleCov.respond_to? :minimum_coverage= module SimpleCov def self.minimum_coverage=(value) @minimum_coverage = value end def self.minimum_coverage @minimum_coverage || -1 end end SimpleCov.at_exit do begin SimpleCov.result.format! SimpleCov.result.print_missed_lines if SimpleCov.result.covered_percent.between?(98.0,99.9999999) if SimpleCov.result.covered_percent < SimpleCov.minimum_coverage STDERR.puts "Coverage not above #{SimpleCov.minimum_coverage}, build failed." exit 1 end rescue Exception => e STDERR.puts "Exception at exit, #{e.message}" end end else SimpleCov.at_exit do begin SimpleCov.result.format! SimpleCov.result.print_missed_lines if SimpleCov.result.covered_percent.between?(98.0,99.9999999) rescue Exception => e STDERR.puts "Exception at exit, #{e.message}" end end end SimpleCov.minimum_coverage = 100 SimpleCov.start do coverage_dir 'coverage/spec/' # Filters - these files will be ignored. add_filter 'lib/rhc/vendor/' # vendored files should be taken directly and only # namespaces changed add_filter 'lib/rhc/rest/' # REST coverage is not yet 100% add_filter 'lib/bin/' # This is just for safety; simplecov isn't picking these up. add_filter 'features/' # Don't report on the files that run the cucumber tests add_filter 'lib/rhc-feature-coverage-helper.rb' add_filter 'spec/' # Don't report on the files that run the spec tests # Groups - general categories of test areas add_group('Commands') { |src_file| src_file.filename.include?(File.join(%w[lib rhc commands])) } add_group('RHC Lib') { |src_file| src_file.filename.include?(File.join(%w[lib rhc])) } add_group('REST') { |src_file| src_file.filename.include?(File.join(%w[lib rhc/rest])) } add_group('Test') { |src_file| src_file.filename.include?(File.join(%w[features])) or src_file.filename.include?(File.join(%w[spec])) } # Note, the #:nocov: coverage exclusion should only be used on external functions # that cannot be nondestructively tested in a developer environment. end end rhc-1.38.7/spec/rest_spec_helper.rb0000644000004100000410000000134012756270552017234 0ustar www-datawww-datarequire 'webmock/rspec' require 'rhc/rest' require 'rhc/rest/mock' require 'rhc/exceptions' require 'base64' RSpec::Matchers.define :have_same_attributes_as do |expected| match do |actual| (actual.instance_variables == expected.instance_variables) && (actual.instance_variables.map { |i| instance_variable_get(i) } == expected.instance_variables.map { |i| instance_variable_get(i) }) end end # ruby 1.8 does not have strict_encode if RUBY_VERSION.to_f == 1.8 module Base64 def strict_encode64(value) encode64(value).delete("\n") end end end module RestSpecHelper include RHC::Rest::Mock::Helpers include RHC::Rest::Mock end RSpec.configure do |configuration| include(RestSpecHelper) end rhc-1.38.7/autocomplete/0000755000004100000410000000000012756270552015132 5ustar www-datawww-datarhc-1.38.7/autocomplete/rhc_bash0000644000004100000410000014661412756270552016642 0ustar www-datawww-data# # This is the bash auto completion script for the rhc command # _rhc() { local cur opts prev COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" if [ $COMP_CWORD -eq 1 ]; then if [[ "$cur" == -* ]]; then opts="--always-prefix --clean --config --debug --header --insecure --limit --mock --noprompt --password --raw --rhlogin --server --ssl-ca-file --ssl-client-cert-file --ssl-client-key-file --ssl-version --timeout --token" elif [ -z $cur ]; then opts="account alias alias-add alias-delete-cert alias-list alias-remove alias-update-cert app app-configure app-create app-delete app-deploy app-enable-ha app-force-stop app-reload app-restart app-scale-down app-scale-up app-show app-start app-stop app-tidy apps authorization authorization-add authorization-delete authorization-delete-all authorization-list cartridge cartridge-add cartridge-list cartridge-reload cartridge-remove cartridge-restart cartridge-scale cartridge-show cartridge-start cartridge-status cartridge-stop cartridge-storage deployment deployment-activate deployment-list deployment-show domain domain-configure domain-create domain-delete domain-leave domain-list domain-rename domain-show env env-list env-set env-show env-unset git-clone help logout member member-add member-list member-remove member-update port-forward region region-list scp server server-add server-configure server-list server-remove server-show server-status server-use setup snapshot snapshot-restore snapshot-save ssh sshkey sshkey-add sshkey-list sshkey-remove sshkey-show tail team team-create team-delete team-leave team-list team-show threaddump" else opts="account account-logout activate-deployment add-alias add-authorization add-cartridge add-member add-server add-sshkey alias alias-add alias-delete-cert alias-list alias-remove alias-update-cert aliases app app-configure app-create app-delete app-deploy app-enable-ha app-env app-force-stop app-reload app-restart app-scale-down app-scale-up app-scp app-show app-snapshot app-ssh app-start app-stop app-tidy apps authorization authorization-add authorization-delete authorization-delete-all authorization-list authorizations cartridge cartridge-add cartridge-list cartridge-reload cartridge-remove cartridge-restart cartridge-scale cartridge-show cartridge-start cartridge-status cartridge-stop cartridge-storage cartridges configure-app configure-domain configure-server create-app create-domain create-team delete-all-authorization delete-app delete-authorization delete-cert-alias delete-domain delete-team deploy deploy-app deployment deployment-activate deployment-list deployment-show deployments domain domain-configure domain-create domain-delete domain-leave domain-list domain-rename domain-show domains enable-ha-app env env-add env-list env-remove env-set env-show env-unset envs force-stop-app git-clone help leave-domain leave-team list-alias list-authorization list-cartridge list-deployment list-domain list-env list-member list-region list-server list-sshkey list-team logout member member-add member-list member-remove member-update members port-forward region region-list regions reload-app reload-cartridge remove-alias remove-cartridge remove-member remove-server remove-sshkey rename-domain restart-app restart-cartridge restore-snapshot save-snapshot scale-cartridge scale-down-app scale-up-app scp server server-add server-configure server-list server-remove server-show server-status server-use servers set-env setup show-app show-cartridge show-deployment show-domain show-env show-server show-sshkey show-team snapshot snapshot-restore snapshot-save ssh sshkey sshkey-add sshkey-list sshkey-remove sshkey-show sshkeys start-app start-cartridge status-cartridge status-server stop-app stop-cartridge storage-cartridge tail team team-create team-delete team-leave team-list team-show teams threaddump tidy-app unset-env update-cert-alias update-member use-server" fi else prev="${COMP_WORDS[@]:0:COMP_CWORD}" SAVE_IFS=$IFS IFS=" " case "${prev[*]}" in "rhc account") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc account logout") if [[ "$cur" == -* ]]; then opts="--all" else opts="" fi ;; "rhc account-logout") if [[ "$cur" == -* ]]; then opts="--all" else opts="" fi ;; "rhc activate-deployment") if [[ "$cur" == -* ]]; then opts="--app --application-id --id --namespace" else opts="" fi ;; "rhc add-alias") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc add-authorization") if [[ "$cur" == -* ]]; then opts="--expires-in --note --scopes" else opts="" fi ;; "rhc add-cartridge") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --env --gear-size --namespace" else opts="" fi ;; "rhc add-member") if [[ "$cur" == -* ]]; then opts="--global --ids --namespace --role --team-id --team-name --type" else opts="" fi ;; "rhc add-server") if [[ "$cur" == -* ]]; then opts="--insecure --nickname --no-insecure --no-use-authorization-tokens --rhlogin --server --skip-wizard --ssl-ca-file --ssl-client-cert-file --ssl-client-key-file --ssl-version --timeout --use --use-authorization-tokens" else opts="" fi ;; "rhc add-sshkey") if [[ "$cur" == -* ]]; then opts="--confirm --content --type" else opts="" fi ;; "rhc alias") if [[ "$cur" == -* ]]; then opts="" else opts="add delete-cert list remove update-cert" fi ;; "rhc alias add") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc alias delete-cert") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --namespace" else opts="" fi ;; "rhc alias list") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc alias remove") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc alias update-cert") if [[ "$cur" == -* ]]; then opts="--app --application-id --certificate --namespace --passphrase --private-key" else opts="" fi ;; "rhc alias-add") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc alias-delete-cert") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --namespace" else opts="" fi ;; "rhc alias-list") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc alias-remove") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc alias-update-cert") if [[ "$cur" == -* ]]; then opts="--app --application-id --certificate --namespace --passphrase --private-key" else opts="" fi ;; "rhc aliases") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app") if [[ "$cur" == -* ]]; then opts="" else opts="configure create delete deploy enable-ha env force-stop reload restart scale-down scale-up show snapshot start stop tidy" fi ;; "rhc app configure") if [[ "$cur" == -* ]]; then opts="--app --application-id --auto-deploy --deployment-branch --deployment-type --keep-deployments --namespace --no-auto-deploy" else opts="" fi ;; "rhc app create") if [[ "$cur" == -* ]]; then opts="--app --dns --enable-jenkins --env --from-app --from-code --gear-size --git --namespace --no-dns --no-git --no-keys --no-scaling --region --repo --scaling --type" else opts="" fi ;; "rhc app delete") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --namespace" else opts="" fi ;; "rhc app deploy") if [[ "$cur" == -* ]]; then opts="--app --application-id --force-clean-build --hot-deploy --namespace --no-force-clean-build --no-hot-deploy --ref" else opts="" fi ;; "rhc app enable-ha") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app env") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc app force-stop") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app reload") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app restart") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app scale-down") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app scale-up") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app scp") if [[ "$cur" == -* ]]; then opts="--app --application-id --local-path --namespace --remote-path --transfer-direction" else opts="" fi ;; "rhc app show") if [[ "$cur" == -* ]]; then opts="--app --application-id --configuration --gears --namespace --state --verbose" else opts="" fi ;; "rhc app snapshot") if [[ "$cur" == -* ]]; then opts="--app --application-id --deployment --filepath --namespace --ssh" else opts="" fi ;; "rhc app ssh") if [[ "$cur" == -* ]]; then opts="--app --application-id --command --gears --limit --namespace --raw --ssh" else opts="" fi ;; "rhc app start") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app stop") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app tidy") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app-configure") if [[ "$cur" == -* ]]; then opts="--app --application-id --auto-deploy --deployment-branch --deployment-type --keep-deployments --namespace --no-auto-deploy" else opts="" fi ;; "rhc app-create") if [[ "$cur" == -* ]]; then opts="--app --dns --enable-jenkins --env --from-app --from-code --gear-size --git --namespace --no-dns --no-git --no-keys --no-scaling --region --repo --scaling --type" else opts="" fi ;; "rhc app-delete") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --namespace" else opts="" fi ;; "rhc app-deploy") if [[ "$cur" == -* ]]; then opts="--app --application-id --force-clean-build --hot-deploy --namespace --no-force-clean-build --no-hot-deploy --ref" else opts="" fi ;; "rhc app-enable-ha") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app-env") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc app-force-stop") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app-reload") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app-restart") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app-scale-down") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app-scale-up") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app-scp") if [[ "$cur" == -* ]]; then opts="--app --application-id --local-path --namespace --remote-path --transfer-direction" else opts="" fi ;; "rhc app-show") if [[ "$cur" == -* ]]; then opts="--app --application-id --configuration --gears --namespace --state --verbose" else opts="" fi ;; "rhc app-snapshot") if [[ "$cur" == -* ]]; then opts="--app --application-id --deployment --filepath --namespace --ssh" else opts="" fi ;; "rhc app-ssh") if [[ "$cur" == -* ]]; then opts="--app --application-id --command --gears --limit --namespace --raw --ssh" else opts="" fi ;; "rhc app-start") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app-stop") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc app-tidy") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc apps") if [[ "$cur" == -* ]]; then opts="--mine --summary --verbose" else opts="" fi ;; "rhc authorization") if [[ "$cur" == -* ]]; then opts="" else opts="add delete delete-all list" fi ;; "rhc authorization add") if [[ "$cur" == -* ]]; then opts="--expires-in --note --scopes" else opts="" fi ;; "rhc authorization delete") if [[ "$cur" == -* ]]; then opts="--auth-token" else opts="" fi ;; "rhc authorization delete-all") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc authorization list") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc authorization-add") if [[ "$cur" == -* ]]; then opts="--expires-in --note --scopes" else opts="" fi ;; "rhc authorization-delete") if [[ "$cur" == -* ]]; then opts="--auth-token" else opts="" fi ;; "rhc authorization-delete-all") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc authorization-list") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc authorizations") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc cartridge") if [[ "$cur" == -* ]]; then opts="" else opts="add list reload remove restart scale show start status stop storage" fi ;; "rhc cartridge add") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --env --gear-size --namespace" else opts="" fi ;; "rhc cartridge list") if [[ "$cur" == -* ]]; then opts="--verbose" else opts="" fi ;; "rhc cartridge reload") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge remove") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --confirm --namespace" else opts="" fi ;; "rhc cartridge restart") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge scale") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --max --min --namespace" else opts="" fi ;; "rhc cartridge show") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge start") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge status") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge stop") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge storage") if [[ "$cur" == -* ]]; then opts="--add --app --application-id --cartridge --force --namespace --remove --set --show" else opts="" fi ;; "rhc cartridge-add") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --env --gear-size --namespace" else opts="" fi ;; "rhc cartridge-list") if [[ "$cur" == -* ]]; then opts="--verbose" else opts="" fi ;; "rhc cartridge-reload") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge-remove") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --confirm --namespace" else opts="" fi ;; "rhc cartridge-restart") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge-scale") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --max --min --namespace" else opts="" fi ;; "rhc cartridge-show") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge-start") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge-status") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge-stop") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc cartridge-storage") if [[ "$cur" == -* ]]; then opts="--add --app --application-id --cartridge --force --namespace --remove --set --show" else opts="" fi ;; "rhc cartridges") if [[ "$cur" == -* ]]; then opts="--verbose" else opts="" fi ;; "rhc configure-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --auto-deploy --deployment-branch --deployment-type --keep-deployments --namespace --no-auto-deploy" else opts="" fi ;; "rhc configure-domain") if [[ "$cur" == -* ]]; then opts="--allowed-gear-sizes --namespace --no-allowed-gear-sizes" else opts="" fi ;; "rhc configure-server") if [[ "$cur" == -* ]]; then opts="--hostname --insecure --nickname --no-insecure --no-use-authorization-tokens --rhlogin --server --skip-wizard --ssl-ca-file --ssl-client-cert-file --ssl-client-key-file --ssl-version --timeout --use --use-authorization-tokens" else opts="" fi ;; "rhc create-app") if [[ "$cur" == -* ]]; then opts="--app --dns --enable-jenkins --env --from-app --from-code --gear-size --git --namespace --no-dns --no-git --no-keys --no-scaling --region --repo --scaling --type" else opts="" fi ;; "rhc create-domain") if [[ "$cur" == -* ]]; then opts="--allowed-gear-sizes --namespace --no-allowed-gear-sizes" else opts="" fi ;; "rhc create-team") if [[ "$cur" == -* ]]; then opts="--team-name" else opts="" fi ;; "rhc delete-all-authorization") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc delete-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --namespace" else opts="" fi ;; "rhc delete-authorization") if [[ "$cur" == -* ]]; then opts="--auth-token" else opts="" fi ;; "rhc delete-cert-alias") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --namespace" else opts="" fi ;; "rhc delete-domain") if [[ "$cur" == -* ]]; then opts="--force --namespace" else opts="" fi ;; "rhc delete-team") if [[ "$cur" == -* ]]; then opts="--team-id --team-name" else opts="" fi ;; "rhc deploy") if [[ "$cur" == -* ]]; then opts="--app --application-id --force-clean-build --hot-deploy --namespace --no-force-clean-build --no-hot-deploy --ref" else opts="" fi ;; "rhc deploy-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --force-clean-build --hot-deploy --namespace --no-force-clean-build --no-hot-deploy --ref" else opts="" fi ;; "rhc deployment") if [[ "$cur" == -* ]]; then opts="" else opts="activate list show" fi ;; "rhc deployment activate") if [[ "$cur" == -* ]]; then opts="--app --application-id --id --namespace" else opts="" fi ;; "rhc deployment list") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc deployment show") if [[ "$cur" == -* ]]; then opts="--app --application-id --id --namespace" else opts="" fi ;; "rhc deployment-activate") if [[ "$cur" == -* ]]; then opts="--app --application-id --id --namespace" else opts="" fi ;; "rhc deployment-list") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc deployment-show") if [[ "$cur" == -* ]]; then opts="--app --application-id --id --namespace" else opts="" fi ;; "rhc deployments") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc domain") if [[ "$cur" == -* ]]; then opts="" else opts="configure create delete leave list rename show" fi ;; "rhc domain configure") if [[ "$cur" == -* ]]; then opts="--allowed-gear-sizes --namespace --no-allowed-gear-sizes" else opts="" fi ;; "rhc domain create") if [[ "$cur" == -* ]]; then opts="--allowed-gear-sizes --namespace --no-allowed-gear-sizes" else opts="" fi ;; "rhc domain delete") if [[ "$cur" == -* ]]; then opts="--force --namespace" else opts="" fi ;; "rhc domain leave") if [[ "$cur" == -* ]]; then opts="--namespace" else opts="" fi ;; "rhc domain list") if [[ "$cur" == -* ]]; then opts="--ids --mine" else opts="" fi ;; "rhc domain rename") if [[ "$cur" == -* ]]; then opts="--namespace" else opts="" fi ;; "rhc domain show") if [[ "$cur" == -* ]]; then opts="--namespace" else opts="" fi ;; "rhc domain-configure") if [[ "$cur" == -* ]]; then opts="--allowed-gear-sizes --namespace --no-allowed-gear-sizes" else opts="" fi ;; "rhc domain-create") if [[ "$cur" == -* ]]; then opts="--allowed-gear-sizes --namespace --no-allowed-gear-sizes" else opts="" fi ;; "rhc domain-delete") if [[ "$cur" == -* ]]; then opts="--force --namespace" else opts="" fi ;; "rhc domain-leave") if [[ "$cur" == -* ]]; then opts="--namespace" else opts="" fi ;; "rhc domain-list") if [[ "$cur" == -* ]]; then opts="--ids --mine" else opts="" fi ;; "rhc domain-rename") if [[ "$cur" == -* ]]; then opts="--namespace" else opts="" fi ;; "rhc domain-show") if [[ "$cur" == -* ]]; then opts="--namespace" else opts="" fi ;; "rhc domains") if [[ "$cur" == -* ]]; then opts="--ids --mine" else opts="" fi ;; "rhc enable-ha-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc env") if [[ "$cur" == -* ]]; then opts="" else opts="add list remove set show unset" fi ;; "rhc env add") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc env list") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace --quotes --table" else opts="" fi ;; "rhc env remove") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc env set") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc env show") if [[ "$cur" == -* ]]; then opts="--app --application-id --env --namespace --quotes --table" else opts="" fi ;; "rhc env unset") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc env-add") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc env-list") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace --quotes --table" else opts="" fi ;; "rhc env-remove") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc env-set") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc env-show") if [[ "$cur" == -* ]]; then opts="--app --application-id --env --namespace --quotes --table" else opts="" fi ;; "rhc env-unset") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc envs") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace --quotes --table" else opts="" fi ;; "rhc force-stop-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc git-clone") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace --repo" else opts="" fi ;; "rhc help") if [[ "$cur" == -* ]]; then opts="" else opts="account account-logout activate-deployment add-alias add-authorization add-cartridge add-member add-server add-sshkey alias alias-add alias-delete-cert alias-list alias-remove alias-update-cert aliases app app-configure app-create app-delete app-deploy app-enable-ha app-env app-force-stop app-reload app-restart app-scale-down app-scale-up app-scp app-show app-snapshot app-ssh app-start app-stop app-tidy apps authorization authorization-add authorization-delete authorization-delete-all authorization-list authorizations cartridge cartridge-add cartridge-list cartridge-reload cartridge-remove cartridge-restart cartridge-scale cartridge-show cartridge-start cartridge-status cartridge-stop cartridge-storage cartridges configure-app configure-domain configure-server create-app create-domain create-team delete-all-authorization delete-app delete-authorization delete-cert-alias delete-domain delete-team deploy deploy-app deployment deployment-activate deployment-list deployment-show deployments domain domain-configure domain-create domain-delete domain-leave domain-list domain-rename domain-show domains enable-ha-app env env-add env-list env-remove env-set env-show env-unset envs force-stop-app git-clone leave-domain leave-team list-alias list-authorization list-cartridge list-deployment list-domain list-env list-member list-region list-server list-sshkey list-team logout member member-add member-list member-remove member-update members port-forward region region-list regions reload-app reload-cartridge remove-alias remove-cartridge remove-member remove-server remove-sshkey rename-domain restart-app restart-cartridge restore-snapshot save-snapshot scale-cartridge scale-down-app scale-up-app scp server server-add server-configure server-list server-remove server-show server-status server-use servers set-env setup show-app show-cartridge show-deployment show-domain show-env show-server show-sshkey show-team snapshot snapshot-restore snapshot-save ssh sshkey sshkey-add sshkey-list sshkey-remove sshkey-show sshkeys start-app start-cartridge status-cartridge status-server stop-app stop-cartridge storage-cartridge tail team team-create team-delete team-leave team-list team-show teams threaddump tidy-app unset-env update-cert-alias update-member use-server" fi ;; "rhc leave-domain") if [[ "$cur" == -* ]]; then opts="--namespace" else opts="" fi ;; "rhc leave-team") if [[ "$cur" == -* ]]; then opts="--team-id --team-name" else opts="" fi ;; "rhc list-alias") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc list-authorization") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc list-cartridge") if [[ "$cur" == -* ]]; then opts="--verbose" else opts="" fi ;; "rhc list-deployment") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc list-domain") if [[ "$cur" == -* ]]; then opts="--ids --mine" else opts="" fi ;; "rhc list-env") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace --quotes --table" else opts="" fi ;; "rhc list-member") if [[ "$cur" == -* ]]; then opts="--all --app --ids --namespace --target --team-id --team-name" else opts="" fi ;; "rhc list-region") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc list-server") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc list-sshkey") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc list-team") if [[ "$cur" == -* ]]; then opts="--mine" else opts="" fi ;; "rhc logout") if [[ "$cur" == -* ]]; then opts="--all" else opts="" fi ;; "rhc member") if [[ "$cur" == -* ]]; then opts="" else opts="add list remove update" fi ;; "rhc member add") if [[ "$cur" == -* ]]; then opts="--global --ids --namespace --role --team-id --team-name --type" else opts="" fi ;; "rhc member list") if [[ "$cur" == -* ]]; then opts="--all --app --ids --namespace --target --team-id --team-name" else opts="" fi ;; "rhc member remove") if [[ "$cur" == -* ]]; then opts="--all --ids --namespace --team-id --team-name --type" else opts="" fi ;; "rhc member update") if [[ "$cur" == -* ]]; then opts="--ids --namespace --role --type" else opts="" fi ;; "rhc member-add") if [[ "$cur" == -* ]]; then opts="--global --ids --namespace --role --team-id --team-name --type" else opts="" fi ;; "rhc member-list") if [[ "$cur" == -* ]]; then opts="--all --app --ids --namespace --target --team-id --team-name" else opts="" fi ;; "rhc member-remove") if [[ "$cur" == -* ]]; then opts="--all --ids --namespace --team-id --team-name --type" else opts="" fi ;; "rhc member-update") if [[ "$cur" == -* ]]; then opts="--ids --namespace --role --type" else opts="" fi ;; "rhc members") if [[ "$cur" == -* ]]; then opts="--all --app --ids --namespace --target --team-id --team-name" else opts="" fi ;; "rhc port-forward") if [[ "$cur" == -* ]]; then opts="--app --application-id --gear --namespace" else opts="" fi ;; "rhc region") if [[ "$cur" == -* ]]; then opts="" else opts="list" fi ;; "rhc region list") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc region-list") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc regions") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc reload-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc reload-cartridge") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc remove-alias") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc remove-cartridge") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --confirm --namespace" else opts="" fi ;; "rhc remove-member") if [[ "$cur" == -* ]]; then opts="--all --ids --namespace --team-id --team-name --type" else opts="" fi ;; "rhc remove-server") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc remove-sshkey") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc rename-domain") if [[ "$cur" == -* ]]; then opts="--namespace" else opts="" fi ;; "rhc restart-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc restart-cartridge") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc restore-snapshot") if [[ "$cur" == -* ]]; then opts="--app --application-id --filepath --namespace --ssh" else opts="" fi ;; "rhc save-snapshot") if [[ "$cur" == -* ]]; then opts="--app --application-id --deployment --filepath --namespace --ssh" else opts="" fi ;; "rhc scale-cartridge") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --max --min --namespace" else opts="" fi ;; "rhc scale-down-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc scale-up-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc scp") if [[ "$cur" == -* ]]; then opts="--app --application-id --local-path --namespace --remote-path --transfer-direction" else opts="" fi ;; "rhc server") if [[ "$cur" == -* ]]; then opts="" else opts="add configure list remove show status use" fi ;; "rhc server add") if [[ "$cur" == -* ]]; then opts="--insecure --nickname --no-insecure --no-use-authorization-tokens --rhlogin --server --skip-wizard --ssl-ca-file --ssl-client-cert-file --ssl-client-key-file --ssl-version --timeout --use --use-authorization-tokens" else opts="" fi ;; "rhc server configure") if [[ "$cur" == -* ]]; then opts="--hostname --insecure --nickname --no-insecure --no-use-authorization-tokens --rhlogin --server --skip-wizard --ssl-ca-file --ssl-client-cert-file --ssl-client-key-file --ssl-version --timeout --use --use-authorization-tokens" else opts="" fi ;; "rhc server list") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc server remove") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc server show") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc server status") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc server use") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc server-add") if [[ "$cur" == -* ]]; then opts="--insecure --nickname --no-insecure --no-use-authorization-tokens --rhlogin --server --skip-wizard --ssl-ca-file --ssl-client-cert-file --ssl-client-key-file --ssl-version --timeout --use --use-authorization-tokens" else opts="" fi ;; "rhc server-configure") if [[ "$cur" == -* ]]; then opts="--hostname --insecure --nickname --no-insecure --no-use-authorization-tokens --rhlogin --server --skip-wizard --ssl-ca-file --ssl-client-cert-file --ssl-client-key-file --ssl-version --timeout --use --use-authorization-tokens" else opts="" fi ;; "rhc server-list") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc server-remove") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc server-show") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc server-status") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc server-use") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc servers") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc set-env") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc setup") if [[ "$cur" == -* ]]; then opts="--autocomplete --clean --create-token --no-create-token --server" else opts="" fi ;; "rhc show-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --configuration --gears --namespace --state --verbose" else opts="" fi ;; "rhc show-cartridge") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc show-deployment") if [[ "$cur" == -* ]]; then opts="--app --application-id --id --namespace" else opts="" fi ;; "rhc show-domain") if [[ "$cur" == -* ]]; then opts="--namespace" else opts="" fi ;; "rhc show-env") if [[ "$cur" == -* ]]; then opts="--app --application-id --env --namespace --quotes --table" else opts="" fi ;; "rhc show-server") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc show-sshkey") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc show-team") if [[ "$cur" == -* ]]; then opts="--team-id --team-name" else opts="" fi ;; "rhc snapshot") if [[ "$cur" == -* ]]; then opts="" else opts="restore save" fi ;; "rhc snapshot restore") if [[ "$cur" == -* ]]; then opts="--app --application-id --filepath --namespace --ssh" else opts="" fi ;; "rhc snapshot save") if [[ "$cur" == -* ]]; then opts="--app --application-id --deployment --filepath --namespace --ssh" else opts="" fi ;; "rhc snapshot-restore") if [[ "$cur" == -* ]]; then opts="--app --application-id --filepath --namespace --ssh" else opts="" fi ;; "rhc snapshot-save") if [[ "$cur" == -* ]]; then opts="--app --application-id --deployment --filepath --namespace --ssh" else opts="" fi ;; "rhc ssh") if [[ "$cur" == -* ]]; then opts="--app --application-id --command --gears --limit --namespace --raw --ssh" else opts="" fi ;; "rhc sshkey") if [[ "$cur" == -* ]]; then opts="" else opts="add list remove show" fi ;; "rhc sshkey add") if [[ "$cur" == -* ]]; then opts="--confirm --content --type" else opts="" fi ;; "rhc sshkey list") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc sshkey remove") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc sshkey show") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc sshkey-add") if [[ "$cur" == -* ]]; then opts="--confirm --content --type" else opts="" fi ;; "rhc sshkey-list") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc sshkey-remove") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc sshkey-show") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc sshkeys") if [[ "$cur" == -* ]]; then opts="" else opts="" fi ;; "rhc start-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc start-cartridge") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc status-cartridge") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc status-server") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; "rhc stop-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc stop-cartridge") if [[ "$cur" == -* ]]; then opts="--app --application-id --cartridge --namespace" else opts="" fi ;; "rhc storage-cartridge") if [[ "$cur" == -* ]]; then opts="--add --app --application-id --cartridge --force --namespace --remove --set --show" else opts="" fi ;; "rhc tail") if [[ "$cur" == -* ]]; then opts="--app --application-id --files --gear --namespace --opts" else opts="" fi ;; "rhc team") if [[ "$cur" == -* ]]; then opts="" else opts="create delete leave list show" fi ;; "rhc team create") if [[ "$cur" == -* ]]; then opts="--team-name" else opts="" fi ;; "rhc team delete") if [[ "$cur" == -* ]]; then opts="--team-id --team-name" else opts="" fi ;; "rhc team leave") if [[ "$cur" == -* ]]; then opts="--team-id --team-name" else opts="" fi ;; "rhc team list") if [[ "$cur" == -* ]]; then opts="--mine" else opts="" fi ;; "rhc team show") if [[ "$cur" == -* ]]; then opts="--team-id --team-name" else opts="" fi ;; "rhc team-create") if [[ "$cur" == -* ]]; then opts="--team-name" else opts="" fi ;; "rhc team-delete") if [[ "$cur" == -* ]]; then opts="--team-id --team-name" else opts="" fi ;; "rhc team-leave") if [[ "$cur" == -* ]]; then opts="--team-id --team-name" else opts="" fi ;; "rhc team-list") if [[ "$cur" == -* ]]; then opts="--mine" else opts="" fi ;; "rhc team-show") if [[ "$cur" == -* ]]; then opts="--team-id --team-name" else opts="" fi ;; "rhc teams") if [[ "$cur" == -* ]]; then opts="--mine" else opts="" fi ;; "rhc threaddump") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc tidy-app") if [[ "$cur" == -* ]]; then opts="--app --application-id --namespace" else opts="" fi ;; "rhc unset-env") if [[ "$cur" == -* ]]; then opts="--app --application-id --confirm --env --namespace" else opts="" fi ;; "rhc update-cert-alias") if [[ "$cur" == -* ]]; then opts="--app --application-id --certificate --namespace --passphrase --private-key" else opts="" fi ;; "rhc update-member") if [[ "$cur" == -* ]]; then opts="--ids --namespace --role --type" else opts="" fi ;; "rhc use-server") if [[ "$cur" == -* ]]; then opts="--server" else opts="" fi ;; esac IFS=$SAVE_IFS fi COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 } complete -o default -F _rhc rhc rhc-1.38.7/lib/0000755000004100000410000000000012756270551013176 5ustar www-datawww-datarhc-1.38.7/lib/rhc.rb0000644000004100000410000000166612756270551014310 0ustar www-datawww-data# require 'rhc/version' #FIXME gem should know version # Only require external gem dependencies here require 'logger' require 'pp' require 'pry' if ENV['PRY'] # Extend core methods require 'rhc/core_ext' module RHC autoload :AutoComplete, 'rhc/autocomplete' autoload :Auth, 'rhc/auth' autoload :CartridgeHelpers, 'rhc/cartridge_helpers' autoload :CommandRunner, 'rhc/command_runner' autoload :Commands, 'rhc/commands' autoload :Config, 'rhc/config' autoload :GitHelpers, 'rhc/git_helpers' autoload :Helpers, 'rhc/helpers' autoload :HelpFormatter, 'rhc/help_formatter' autoload :Rest, 'rhc/rest' autoload :SSHHelpers, 'rhc/ssh_helpers' autoload :TarGz, 'rhc/tar_gz' autoload :VERSION, 'rhc/version' end require 'rhc/exceptions' require 'commander' require 'commander/delegates' require 'highline/system_extensions' require 'rhc/highline_extensions' rhc-1.38.7/lib/rhc/0000755000004100000410000000000012756270552013753 5ustar www-datawww-datarhc-1.38.7/lib/rhc/core_ext.rb0000644000004100000410000001160412756270551016111 0ustar www-datawww-data# From Rails core_ext/object.rb require 'rhc/json' require 'open-uri' require 'httpclient' class Object def present? !blank? end def blank? respond_to?(:empty?) ? empty? : !self end def presence present? ? self : nil end # Avoid a conflict if to_json is already defined unless Object.new.respond_to? :to_json #:nocov: def to_json(options=nil) RHC::Json.encode(self) end #:nocov: end end class Array # From rails def split(value = nil) using_block = block_given? inject([[]]) do |results, element| if (using_block && yield(element)) || (value == element) results << [] else results.last << element end results end end end class File def chunk(chunk_size=1024) yield read(chunk_size) until eof? end end class String # Wrap string by the given length, and join it with the given character. # The method doesn't distinguish between words, it will only work based on # the length. def wrap(wrap_length=80, char="\n") scan(/.{#{wrap_length}}|.+/).join(char) end def strip_heredoc indent = scan(/^[ \t]*(?=\S)/).min.size || 0 gsub(/^[ \t]{#{indent}}/, ''). gsub(/(\b|\S)[^\S\n]*\n(\S)/m, '\1 \2'). gsub(/\n+\Z/, ''). gsub(/\n{3,}/, "\n\n") end ANSI_ESCAPE_SEQUENCE = /\e\[(\d{1,2}(?:;\d{1,2})*[@-~])/ ANSI_ESCAPE_MATCH = '\e\[\d+(?:;\d+)*[@-~]' CHAR_SKIP_ANSI = "(?:(?:#{ANSI_ESCAPE_MATCH})+.?|.(?:#{ANSI_ESCAPE_MATCH})*)" # # Split the given string at limit, treating ANSI escape sequences as # zero characters in length. Will insert an ANSI reset code (\e[0m) # at the end of each line containing an ANSI code, assuming that a # reset was not in the wrapped segment. # # All newlines are preserved. # # Lines longer than limit without natural breaks will be forcibly # split at the exact limit boundary. # # Returns an Array # def textwrap_ansi(limit, breakword=true) re = breakword ? / ( # Match substrings that end in whitespace shorter than limit #{CHAR_SKIP_ANSI}{1,#{limit}} # up to limit (?:\s+|$) # require the limit to end on whitespace | # Match substrings equal to the limit #{CHAR_SKIP_ANSI}{1,#{limit}} ) /x : / ( # Match substrings that end in whitespace shorter than limit #{CHAR_SKIP_ANSI}{1,#{limit}} (?:\s|$) # require the limit to end on whitespace | # Match all continguous whitespace strings #{CHAR_SKIP_ANSI}+? (?:\s|$) (?:\s+|$)? ) /x escapes = [] split("\n",-1).inject([]) do |a, line| if line.length < limit a << line else line.scan(re) do |segment, other| if escapes.present? a << escapes.map{ |s| "\e[#{s}"}.join a[-1] << segment.rstrip else a << segment.rstrip end segment.scan(ANSI_ESCAPE_SEQUENCE).map{ |e| e.first }.each do |e| case e when '0m' then escapes.clear else escapes << e end end a[-1] << "\e[0m" if escapes.present? end end a end end def strip_ansi gsub(ANSI_ESCAPE_SEQUENCE, '') end end unless HTTP::Message.method_defined? :ok? #:nocov: class HTTP::Message def ok? HTTP::Status.successful?(status) end end #:nocov: end unless DateTime.method_defined? :to_time #:nocov: class DateTime def to_time Time.parse(to_s) end end #:nocov: end # # Allow http => https redirection, see # http://bugs.ruby-lang.org/issues/859 to 1.8.7 for rough # outline of change. # module OpenURI def self.redirectable?(uri1, uri2) # :nodoc: # This test is intended to forbid a redirection from http://... to # file:///etc/passwd. # However this is ad hoc. It should be extensible/configurable. uri1.scheme.downcase == uri2.scheme.downcase || (/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:https?|ftp)\z/i =~ uri2.scheme) end end class Hash def stringify_keys! transform_keys!(:to_s) end def symbolize_keys! transform_keys!(:to_sym) end def slice!(*args) s = [] args.inject([]) do |a, k| s << [k, delete(k)] if has_key?(k) end s end def slice(*args) args.inject({}) do |h, k| h[k] = self[k] if has_key?(k) h end end def reverse_merge!(other_hash) # right wins if there is no left merge!( other_hash ){|key,left,right| left } end protected def transform_keys!(operation) keys.each do |key| v = delete(key) if v.is_a? Hash v.transform_keys!(operation) elsif v.is_a? Array v.each{ |value| value.transform_keys!(operation) if value.is_a? Hash } end self[(key.send(operation) rescue key) || key] = v end self end end rhc-1.38.7/lib/rhc/server_helpers.rb0000644000004100000410000000202512756270551017326 0ustar www-datawww-datarequire 'rhc/servers' module RHC module ServerHelpers def openshift_server to_host(openshift_raw_server) end def openshift_online_server? openshift_server =~ openshift_online_server_regex end def openshift_online_server 'openshift.redhat.com' end def openshift_online_server_regex /^#{openshift_online_server.gsub(/\./, '\.')}$/i end def openshift_url "https://#{openshift_server}" end def openshift_rest_endpoint uri = to_uri(openshift_raw_server) uri.path = '/broker/rest/api' if uri.path.blank? || uri.path == '/' uri end def libra_server_env ENV['LIBRA_SERVER'] end def rhc_server_env ENV['RHC_SERVER'] end protected def openshift_raw_server server = (options.server rescue nil) || ENV['LIBRA_SERVER'] || (config['libra_server'] rescue nil) || openshift_online_server @servers ||= RHC::Servers.new (@servers.find(server).hostname rescue nil) || server end end end rhc-1.38.7/lib/rhc/help_formatter.rb0000644000004100000410000000303112756270551017307 0ustar www-datawww-datarequire 'commander/help_formatters/base' module RHC class HelpFormatter < Commander::HelpFormatter::Terminal def template(name) ERB.new(File.read(File.join(File.dirname(__FILE__), 'usage_templates', "#{name}.erb")), nil, '-') end def render template(:help).result RunnerHelpBindings.new(@runner).get_binding end def render_command_syntax command template(:command_syntax_help).result command.get_binding end def render_options runner template(:options_help).result RunnerHelpBindings.new(runner).get_binding end end class RunnerHelpBindings < SimpleDelegator include RHC::Helpers def commands __getobj__.instance_variable_get(:@commands) end def get_binding binding end end class CommandHelpBindings include RHC::Helpers def initialize(command, instance_commands, runner) @command = command @actions = if command.root? instance_commands.sort_by{ |c| c[0] }.collect do |command_name, command_class| next if command_class.summary.nil? m = /^#{command.name}[\-]([^ ]+)/.match(command_name) # if we have a match and it is not an alias then we can use it m and command_name == command_class.name ? {:name => m[1], :summary => command_class.summary || ""} : nil end else [] end @actions.compact! @global_options = runner.options @runner = runner end def program(*args) @runner.program *args end end end rhc-1.38.7/lib/rhc/ssh_helpers.rb0000644000004100000410000004233012756270551016620 0ustar www-datawww-data### # ssh_key_helpers.rb - methods to help manipulate ssh keys # # Copyright 2012 Red Hat, Inc. and/or its affiliates. # # 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 'net/ssh' require 'rhc/vendor/sshkey' require 'httpclient' module RHC module SSHHelpers class MultipleGearTask def initialize(command, over, opts={}) requires_ssh_multi! @command = command @over = over @opts = opts end def run(&block) out = nil Net::SSH::Multi.start( :concurrent_connections => @opts[:limit], :on_error => lambda{ |server| $stderr.puts RHC::Helpers.color("Unable to connect to gear #{server}", :red) } ) do |session| @over.each do |item| case item when RHC::Rest::GearGroup item.gears.each do |gear| session.use ssh_host_for(gear), :properties => {:gear => gear, :group => item} end #when RHC::Rest::Gear # session.use ssh_host_for(item), :properties => {:gear => item} #end else raise "Cannot establish an SSH session to this type" end end session.exec @command, &( case when @opts[:raw] lambda { |ch, dest, data| (dest == :stdout ? $stdout : $stderr).puts data } when @opts[:as] == :table out = [] lambda { |ch, dest, data| label = label_for(ch) data.chomp.each_line do |line| row = out.find{ |row| row[0] == label } || (out << [label, []])[-1] row[1] << line end } when @opts[:as] == :gear lambda { |ch, dest, data| (ch.connection.properties[:gear]['data'] ||= "") << data } else width = 0 lambda { |ch, dest, data| label = label_for(ch) io = dest == :stdout ? $stdout : $stderr data.chomp! if data.each_line.to_a.count < 2 io.puts "[#{label}] #{data}" elsif @opts[:always_prefix] data.each_line do |line| io.puts "[#{label}] #{line}" end else io.puts "=== #{label}" io.puts data end } end) session.loop end if block_given? && !@opts[:raw] case when @opts[:as] == :gear out = [] @over.each do |item| case item when RHC::Rest::GearGroup then item.gears.each{ |gear| out << yield(gear, gear['data'], item) } #when RHC::Rest::Gear then out << yield(gear, gear['data'], nil) end end end end out end protected def ssh_host_for(gear) RHC::Helpers.ssh_string(gear['ssh_url']) or raise NoPerGearOperations end def label_for(channel) channel.properties[:label] ||= begin group = channel.connection.properties[:group] "#{key_for(channel)} #{group.cartridges.map{ |c| c['name'] }.join('+')}" end end def key_for(channel) channel.connection.properties[:gear]['id'] end def requires_ssh_multi! begin require 'net/ssh/multi' rescue LoadError raise RHC::OperationNotSupportedException, "You must install Net::SSH::Multi to use the --gears option. Most systems: 'gem install net-ssh-multi'" end end end def run_on_gears(command, gears, opts={}, &block) debug "Executing #{command} on each of #{gears.inspect}" MultipleGearTask.new(command, gears, {:limit => options.limit, :always_prefix => options.always_prefix, :raw => options.raw}.merge(opts)).run(&block) end def table_from_gears(command, groups, opts={}, &block) cells = run_on_gears(command, groups, {:as => :table}.merge(opts), &block) cells.each{ |r| r.concat(r.pop.first.split(opts[:split_cells_on])) } if !block_given? && opts[:split_cells_on] say table cells, opts unless options.raw end def ssh_command_for_op(operation) #case operation raise RHC::OperationNotSupportedException, "The operation #{operation} is not supported." #end end # Public: Run ssh command on remote host # # host - The String of the remote hostname to ssh to. # username - The String username of the remote user to ssh as. # command - The String command to run on the remote host. # compression - Use compression in ssh, set to false if sending files. # request_pty - Request for pty, set to false when pipe a file. # block - Will yield this block and send the channel if provided. # # Examples # # ssh_ruby('myapp-t.rhcloud.com', # '109745632b514e9590aa802ec015b074', # 'rhcsh tail -f $OPENSHIFT_LOG_DIR/*"') # # => true # # Returns true on success def ssh_ruby(host, username, command, compression=false, request_pty=false, &block) debug "Opening Net::SSH connection to #{host}, #{username}, #{command}" exit_status = 0 options = {:compression => compression} options[:verbose] = :debug if debug? Net::SSH.start(host, username, options) do |session| #:nocov: channel = session.open_channel do |channel| if request_pty channel.request_pty do |ch, success| say "pty could not be obtained" unless success end end channel.exec(command) do |ch, success| channel.on_data do |ch, data| print data end channel.on_extended_data do |ch, type, data| print data end channel.on_close do |ch| debug "Terminating ... " end channel.on_request("exit-status") do |ch, data| exit_status = data.read_long end yield channel if block_given? channel.eof! end end session.loop #:nocov: end raise RHC::SSHCommandFailed.new(exit_status) if exit_status != 0 rescue Errno::ECONNREFUSED => e debug_error e raise RHC::SSHConnectionRefused.new(host, username) rescue Net::SSH::AuthenticationFailed => e debug_error e raise RHC::SSHAuthenticationFailed.new(host, username) rescue SocketError => e debug_error e raise RHC::ConnectionFailed, "The connection to #{host} failed: #{e.message}" end # Public: Run ssh command on remote host and pipe the specified # file contents to the command input # # host - The String of the remote hostname to ssh to. # username - The String username of the remote user to ssh as. # command - The String command to run on the remote host. # filename - The String path to file to send. # def ssh_send_file_ruby(host, username, command, filename) filename = File.expand_path(filename) ssh_ruby(host, username, command) do |channel| File.open(filename, 'rb') do |file| file.chunk(1024) do |chunk| channel.send_data chunk end end end end # Public: Run ssh command on remote host and pipe the specified # url contents to the command input # # host - The String of the remote hostname to ssh to. # username - The String username of the remote user to ssh as. # command - The String command to run on the remote host. # content_url - The url with the content to pipe to command. # def ssh_send_url_ruby(host, username, command, content_url) content_url = URI.parse(URI.encode(content_url.to_s)) ssh_ruby(host, username, command) do |channel| HTTPClient.new.get_content(content_url) do |chunk| channel.send_data chunk end end end def save_snapshot(app, filename, for_deployment=false, ssh_executable=nil) ssh_uri = URI.parse(app.ssh_url) ssh_executable = check_ssh_executable! ssh_executable snapshot_cmd = for_deployment ? 'gear archive-deployment' : 'snapshot' ssh_cmd = "#{ssh_executable} #{ssh_uri.user}@#{ssh_uri.host} '#{snapshot_cmd}' > #{filename}" ssh_stderr = " 2>/dev/null" debug ssh_cmd say "Pulling down a snapshot of application '#{app.name}' to #{filename} ... " begin if !RHC::Helpers.windows? status, output = exec(ssh_cmd + (debug? ? '' : ssh_stderr)) if status != 0 debug output raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}" end else Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh| File.open(filename, 'wb') do |file| ssh.exec! snapshot_cmd do |channel, stream, data| if stream == :stdout file.write(data) else debug data end end end end end rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e debug e.backtrace raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}" end success 'done' end def restore_snapshot(app, filename, ssh_executable=nil) include_git = RHC::Helpers.windows? ? true : RHC::TarGz.contains(filename, './*/git') ssh_uri = URI.parse(app.ssh_url) ssh_executable = check_ssh_executable! options.ssh ssh_cmd = "cat '#{filename}' | #{ssh_executable} #{ssh_uri.user}@#{ssh_uri.host} 'restore#{include_git ? ' INCLUDE_GIT' : ''}'" ssh_stderr = " 2>/dev/null" debug ssh_cmd say "Restoring from snapshot #{filename} to application '#{app.name}' ... " begin if !RHC::Helpers.windows? status, output = exec(ssh_cmd + (debug? ? '' : ssh_stderr)) if status != 0 debug output raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}" end else ssh = Net::SSH.start(ssh_uri.host, ssh_uri.user) ssh.open_channel do |channel| channel.exec("restore#{include_git ? ' INCLUDE_GIT' : ''}") do |ch, success| channel.on_data do |ch, data| debug data end channel.on_extended_data do |ch, type, data| debug data end channel.on_close do |ch| debug "Terminating..." end File.open(filename, 'rb') do |file| file.chunk(4096) do |chunk| channel.send_data chunk end end channel.eof! end end ssh.loop end rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e debug e.backtrace raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}" end success 'done' end # Public: Generate an SSH key and store it in ~/.ssh/id_rsa # # type - The String type RSA or DSS. # bits - The Integer value for number of bits. # comment - The String comment for the key # # Examples # # generate_ssh_key_ruby # # => /home/user/.ssh/id_rsa.pub # # Returns nil on failure or public key location as a String on success def generate_ssh_key_ruby(type="RSA", bits = 2048, comment = "OpenShift-Key") key = RHC::Vendor::SSHKey.generate(:type => type, :bits => bits, :comment => comment) ssh_dir = RHC::Config.ssh_dir priv_key = RHC::Config.ssh_priv_key_file_path pub_key = RHC::Config.ssh_pub_key_file_path if File.exists?(priv_key) say "SSH key already exists: #{priv_key}. Reusing..." return nil else unless File.exists?(ssh_dir) FileUtils.mkdir_p(ssh_dir) File.chmod(0700, ssh_dir) end File.open(priv_key, 'w') {|f| f.write(key.private_key)} File.chmod(0600, priv_key) File.open(pub_key, 'w') {|f| f.write(key.ssh_public_key)} ssh_add end pub_key end # For Net::SSH versions (< 2.0.11) that does not have # Net::SSH::KeyFactory.load_public_key, we drop to shell to get # the key's fingerprint def ssh_keygen_fallback(path) fingerprint = `ssh-keygen -lf #{path} 2>&1`.split(' ')[1] if $?.exitstatus != 0 error "Unable to compute SSH public key finger print for #{path}" end fingerprint end def fingerprint_for_local_key(key) Net::SSH::KeyFactory.load_public_key(key).fingerprint rescue NoMethodError, NotImplementedError => e ssh_keygen_fallback key nil rescue OpenSSL::PKey::PKeyError, Net::SSH::Exception => e error e.message nil rescue => e debug e.message nil end def fingerprint_for_default_key fingerprint_for_local_key(RHC::Config.ssh_pub_key_file_path) end # for an SSH public key specified by 'key', return a triple # [type, content, comment] # which is basically the space-separated list of the SSH public key content def ssh_key_triple_for(key) begin IO.read(key).chomp.split rescue Errno::ENOENT => e raise ::RHC::KeyFileNotExistentException.new("File '#{key}' does not exist.") rescue Errno::EACCES => e raise ::RHC::KeyFileAccessDeniedException.new("Access denied to '#{key}'.") end end def ssh_key_triple_for_default_key ssh_key_triple_for(RHC::Config.ssh_pub_key_file_path) end # check the version of SSH that is installed def ssh_version(cmd=discover_ssh_executable) `"#{cmd}" -V 2>&1`.strip end # return whether or not SSH is installed def has_ssh? discover_ssh_executable.present? end # try my best to discover a ssh executable def discover_ssh_executable @ssh_executable ||= begin guessing_locations = ['ssh'] #:nocov: if RHC::Helpers.windows? # looks for ssh.exe from msysgit or plink.exe from PuTTY, either on path or specific locations guessing_locations << discover_windows_executables do |base| [ 'ssh.exe', "#{base}\\Git\\bin\\ssh.exe", "#{base}\\ssh.exe", 'plink.exe', "#{base}\\PuTTY\\plink.exe", "#{base}\\plink.exe", 'putty.exe', "#{base}\\PuTTY\\putty.exe", "#{base}\\putty.exe" ] end end #:nocov: # make sure commands can be executed and finally pick the first one guessing_locations.flatten.uniq.select do |cmd| (check_ssh_executable!(cmd).present? rescue false) && (begin # putty -V exit as 1 cmd =~ /plink\.exe/i || cmd =~ /putty\.exe/i || (begin ssh_version(cmd) $?.success? end) rescue ; false ; end) end.collect{|cmd| cmd =~ / / ? '"' + cmd + '"' : cmd}.first end end # return supplied ssh executable, if valid (executable, searches $PATH). # if none was supplied, return installed ssh, if any. def check_ssh_executable!(path) if not path discover_ssh_executable.tap do |ssh_cmd| raise RHC::InvalidSSHExecutableException.new("No system SSH available. Please use the --ssh option to specify the path to your SSH executable, or install SSH.#{windows? ? ' We recommend this free application: Git for Windows - a basic git command line and GUI client http://msysgit.github.io/.' : ''}") unless ssh_cmd or has_ssh? end else bin_path = split_path(path)[0] raise RHC::InvalidSSHExecutableException.new("SSH executable '#{bin_path}' does not exist.") unless File.exist?(bin_path) or File.exist?(path) or exe?(bin_path) raise RHC::InvalidSSHExecutableException.new("SSH executable '#{bin_path}' is not executable.") unless File.executable?(path) or File.executable?(bin_path) or exe?(bin_path) path end end private def ssh_add if exe?('ssh-add') #:nocov: `ssh-add 2>&1` #:nocov: end end end end rhc-1.38.7/lib/rhc/rest/0000755000004100000410000000000012756270551014727 5ustar www-datawww-datarhc-1.38.7/lib/rhc/rest/application.rb0000644000004100000410000002614112756270551017563 0ustar www-datawww-datarequire 'uri' module RHC module Rest class Application < Base include Membership define_attr :domain_id, :name, :creation_time, :uuid, :git_url, :app_url, :gear_profile, :framework, :scalable, :health_check_path, :embedded, :gear_count, :ha, :ssh_url, :building_app, :cartridges, :initial_git_url, :auto_deploy, :deployment_branch, :deployment_type, :keep_deployments, :deployments :region alias_method :domain_name, :domain_id # Query helper to say consistent with cartridge def scalable? scalable end def ha? ha end def id attributes['id'] || uuid end def uuid if (client.api_version_negotiated >= 1.6) attributes['id'] else attributes['uuid'] end end def domain domain_id end def scalable_carts return [] unless scalable? carts = cartridges.select(&:scalable?) scales_with = carts.map(&:scales_with) carts.delete_if{|x| scales_with.include?(x.name)} end def add_cartridge(cart, options={}) debug "Adding cartridge #{name}" clear_attribute :cartridges cart = if cart.is_a? String {:name => cart} elsif cart.respond_to? :[] cart else c = cart.url ? {:url => cart.url} : {:name => cart.name} if cart.respond_to?(:environment_variables) && cart.environment_variables.present? c[:environment_variables] = cart.environment_variables end if cart.respond_to?(:gear_size) && cart.gear_size.present? c[:gear_size] = cart.gear_size end cart = c end if cart.respond_to?(:[]) and cart[:url] and !has_param?('ADD_CARTRIDGE', 'url') raise RHC::Rest::DownloadingCartridgesNotSupported, "The server does not support downloading cartridges." end rest_method( "ADD_CARTRIDGE", cart, options ) end def cartridges @cartridges ||= unless (carts = attributes['cartridges']).nil? carts.map{|x| Cartridge.new(x, client) } else debug "Getting all cartridges for application #{name}" rest_method "LIST_CARTRIDGES" end end def gear_info { :gear_count => gear_count, :gear_profile => gear_profile } unless gear_count.nil? end def gear_groups debug "Getting all gear groups for application #{name}" rest_method "GET_GEAR_GROUPS" end def gears gear_groups.map{ |group| group.gears }.flatten end def gear_ssh_url(gear_id) gear = gears.find{ |g| g['id'] == gear_id } raise ArgumentError, "Gear #{gear_id} not found" if gear.nil? gear['ssh_url'] or raise NoPerGearOperations end def region gears.first['region'] rescue nil end def tidy debug "Starting application #{name}" rest_method 'TIDY', :event => "tidy" end def enable_ha raise RHC::HighAvailabilityNotSupportedException.new unless supports? "MAKE_HA" rest_method 'MAKE_HA', :event => "make-ha" end def scale_up rest_method 'SCALE_UP', :event => "scale-up" end def scale_down rest_method 'SCALE_DOWN', :event => "scale-down" end def start debug "Starting application #{name}" rest_method 'START', :event => "start" end def stop(force=false) debug "Stopping application #{name} force-#{force}" if force payload = {:event=> "force-stop"} else payload = {:event=> "stop"} end rest_method "STOP", payload end def restart debug "Restarting application #{name}" rest_method "RESTART", :event => "restart" end def destroy debug "Deleting application #{name}" rest_method "DELETE" end alias :delete :destroy def reload debug "Reload application #{name}" rest_method "RELOAD", :event => "reload" end def threaddump debug "Running thread dump for #{name}" rest_method "THREAD_DUMP", :event => "thread-dump" end def environment_variables debug "Getting all environment variables for application #{name}" if supports? "LIST_ENVIRONMENT_VARIABLES" rest_method "LIST_ENVIRONMENT_VARIABLES" else raise RHC::EnvironmentVariablesNotSupportedException.new end end def find_environment_variable(env_var_name) find_environment_variables(env_var_name).first end def find_environment_variables(env_var_names=nil) return environment_variables if env_var_names.nil? env_var_names = [env_var_names].flatten debug "Finding environment variable(s) #{env_var_names.inspect} in app #{@name}" env_vars = environment_variables.select { |item| env_var_names.include? item.name } raise RHC::EnvironmentVariableNotFoundException.new("Environment variable(s) #{env_var_names.join(', ')} can't be found in application #{name}.") if env_vars.empty? env_vars end # @param [Array] Array of RHC::Rest::EnvironmentVariable to be set def set_environment_variables(env_vars=[]) debug "Adding environment variable(s) #{env_vars.inspect} for #{name}" if supports? "SET_UNSET_ENVIRONMENT_VARIABLES" rest_method "SET_UNSET_ENVIRONMENT_VARIABLES", :environment_variables => env_vars.map{|item| item.to_hash} else raise RHC::EnvironmentVariablesNotSupportedException.new end end # @param [Array] Array of env var names like ['FOO', 'BAR'] def unset_environment_variables(env_vars=[]) debug "Removing environment variable(s) #{env_vars.inspect} for #{name}" if supports? "SET_UNSET_ENVIRONMENT_VARIABLES" rest_method "SET_UNSET_ENVIRONMENT_VARIABLES", :environment_variables => env_vars.map{|item| {:name => item}} else raise RHC::EnvironmentVariablesNotSupportedException.new end end def supports_add_cartridge_with_env_vars? has_param?('ADD_CARTRIDGE', 'environment_variables') end def supports_add_cartridge_with_gear_size? has_param?('ADD_CARTRIDGE', 'gear_size') end def deployments debug "Listing deployments for application #{name}" raise RHC::DeploymentsNotSupportedException if !supports? "LIST_DEPLOYMENTS" rest_method("LIST_DEPLOYMENTS").sort end def deployment_activations items = [] # building an array of activations with their deployments deployments.each do |deployment| deployment.activations.each do |activation| items << {:activation => activation, :deployment => deployment} end end items.sort! {|a,b| a[:activation].created_at <=> b[:activation].created_at } first_activation = {} items.each do |item| deployment = item[:deployment] activation = item[:activation] # set the currently active (last activation by date) item[:active] = item == items.last # mark rollbacks (activations whose deployment had previous activations) if rollback_to = first_activation[deployment.id] item[:rollback] = true item[:rollback_to] = rollback_to # mark rolled back (all in between a rollback and its original deployment) items.each {|i| i[:rolled_back] = true if i[:activation].created_at > rollback_to && i[:activation].created_at < activation.created_at } else first_activation[deployment.id] = activation.created_at end end items end def configure(options={}) debug "Running update for #{name} with options #{options.inspect}" if supports? "UPDATE" rest_method "UPDATE", options else raise RHC::DeploymentsNotSupportedException end end def add_alias(app_alias) debug "Running add_alias for #{name}" rest_method "ADD_ALIAS", :event => "add-alias", :alias => app_alias end def remove_alias(app_alias) debug "Running remove_alias for #{name}" if (client.api_version_negotiated >= 1.4) find_alias(app_alias).destroy else rest_method "REMOVE_ALIAS", :event => "remove-alias", :alias => app_alias end end def aliases debug "Getting all aliases for application #{name}" @aliases ||= begin aliases = attributes['aliases'] if aliases.nil? or not aliases.is_a?(Array) supports?('LIST_ALIASES') ? rest_method("LIST_ALIASES") : [] else aliases.map do |a| Alias.new(a.is_a?(String) ? {'id' => a} : a, client) end end end end def find_alias(name, options={}) debug "Finding alias #{name} in app #{@name}" if name.is_a?(Hash) options = name name = options[:name] end aliases.each { |a| return a if a.is_a?(String) || a.id == name.downcase } raise RHC::AliasNotFoundException.new("Alias #{name} can't be found in application #{@name}.") end #Find Cartridge by name def find_cartridge(sought, options={}) debug "Finding cartridge #{sought} in app #{name}" type = options[:type] cartridges.each { |cart| return cart if cart.name == sought and (type.nil? or cart.type == type) } suggested_msg = "" valid_cartridges = cartridges.select {|c| type.nil? or c.type == type} unless valid_cartridges.empty? suggested_msg = "\n\nValid cartridges:" valid_cartridges.each { |cart| suggested_msg += "\n#{cart.name}" } end raise RHC::CartridgeNotFoundException.new("Cartridge #{sought} can't be found in application #{name}.#{suggested_msg}") end #Find Cartridges by name or regex def find_cartridges(name, options={}) if name.is_a?(Hash) options = name name = options[:name] end type = options[:type] regex = options[:regex] debug "Finding cartridge #{name || regex} in app #{@name}" filtered = Array.new cartridges.each do |cart| if regex filtered.push(cart) if cart.name.match(/(?i:#{regex})/) and (type.nil? or cart.type == type) else filtered.push(cart) if cart.name.downcase == name.downcase and (type.nil? or cart.type == type) end end filtered end def host @host ||= URI.parse(app_url).host rescue nil end def ssh_string RHC::Helpers.ssh_string(ssh_url) end def <=>(other) c = name.downcase <=> other.name.downcase return c unless c == 0 domain_id <=> other.domain_id end end end end rhc-1.38.7/lib/rhc/rest/domain.rb0000644000004100000410000000634312756270551016531 0ustar www-datawww-datamodule RHC module Rest class Domain < Base include Membership define_attr :id, # Domain name for API version < 1.6, domain unique id otherwise :name, # Available from API version 1.6 onwards :allowed_gear_sizes, # Available from API version 1.3 onwards on compatible servers :creation_time, # Available from API version 1.3 onwards on compatible servers :suffix def id id_and_name.first end def name id_and_name.last end def id_and_name id = @id || attributes['id'] name = @name || attributes['name'] if name.present? if id == name [nil, name] else [id, name] end else [nil, id] end end #Add Application to this domain # options # cartridge # template # scale # gear_profile def add_application(name, options) debug "Adding application #{name} to domain #{id}" payload = {:name => name} options.each{ |key, value| payload[key.to_sym] = value } cartridges = Array(payload.delete(:cartridge)).concat(Array(payload.delete(:cartridges))).map do |cart| if cart.is_a? String or cart.respond_to? :[] cart else cart.url ? {:url => cart.url} : cart.name end end.compact.uniq if cartridges.any?{ |c| c.is_a?(Hash) and c[:url] } and !has_param?('ADD_APPLICATION', 'cartridges[][url]') raise RHC::Rest::DownloadingCartridgesNotSupported, "The server does not support downloading cartridges." end if client.api_version_negotiated >= 1.3 payload[:cartridges] = cartridges else raise RHC::Rest::MultipleCartridgeCreationNotSupported, "The server only supports creating an application with a single web cartridge." if cartridges.length > 1 payload[:cartridge] = cartridges.first end if payload[:initial_git_url] and !has_param?('ADD_APPLICATION', 'initial_git_url') raise RHC::Rest::InitialGitUrlNotSupported, "The server does not support creating applications from a source repository." end options = {:timeout => options[:scale] && 0 || nil} rest_method "ADD_APPLICATION", payload, options end def applications(options = {}) debug "Getting all applications for domain #{id}" rest_method "LIST_APPLICATIONS", options end def rename(new_id) debug "Updating domain #{id} to #{new_id}" # 5 minute timeout as this may take time if there are a lot of apps rest_method "UPDATE", {:id => new_id}, {:timeout => 0} end alias :update :rename def configure(payload, options={}) self.attributes = rest_method("UPDATE", payload, options).attributes self end def destroy(force=false) debug "Deleting domain #{id}" rest_method "DELETE", :force => force end alias :delete :destroy def supports_add_application_with_env_vars? has_param?('ADD_APPLICATION', 'environment_variables') end end end end rhc-1.38.7/lib/rhc/rest/gear_group.rb0000644000004100000410000000070212756270551017405 0ustar www-datawww-datamodule RHC module Rest class GearGroup < Base define_attr :gears, :cartridges, :gear_profile, :additional_gear_storage, :base_gear_storage def name(gear) gear['name'] ||= "#{group.cartridges.collect{ |c| c['name'] }.join('+')}:#{gear['id']}" end def quota return nil unless base_gear_storage ((additional_gear_storage || 0) + base_gear_storage) * 1024 * 1024 * 1024 end end end end rhc-1.38.7/lib/rhc/rest/cartridge.rb0000644000004100000410000001102412756270551017216 0ustar www-datawww-datamodule RHC module Rest class Cartridge < Base HIDDEN_TAGS = [:framework, :web_framework, :cartridge].map(&:to_s) define_attr :type, :name, :display_name, :properties, :gear_profile, :status_messages, :scales_to, :scales_from, :scales_with, :current_scale, :supported_scales_to, :supported_scales_from, :tags, :description, :collocated_with, :base_gear_storage, :additional_gear_storage, :url, :environment_variables, :gear_size, :automatic_updates, :version, :license, :website, :description def scalable? supported_scales_to != supported_scales_from end def custom? url.present? end def only_in_new? type == 'standalone' end def only_in_existing? type == 'embedded' end def automatic_updates? v = attribute(:automatic_updates) if v.nil? v = !(tags.include?('no_updates') || custom?) end v end def external? tags.include?('external') end def shares_gears? Array(collocated_with).present? end def collocated_with Array(attribute(:collocated_with)) end def tags Array(attribute(:tags)) end def gear_storage (base_gear_storage + additional_gear_storage) * 1024 * 1024 * 1024 end def additional_gear_storage attribute(:additional_gear_storage).to_i rescue 0 end def display_name attribute(:display_name) || name || url_basename end # # Use this value when the user should interact with this cart via CLI arguments # def short_name name || url end def usage_rate? rates = usage_rates !(rates.nil? || rates.empty?) end def usage_rates rate = attribute(:usage_rate_usd).to_f rescue 0.0 if rate > 0 {rate => []} elsif attribute(:usage_rates).present? attribute(:usage_rates).inject({}) do |plans_by_rate, rate| if (usd = rate['usd'].to_f rescue 0.0) > 0 (plans_by_rate[usd] ||= []) << rate['plan_id'] end plans_by_rate end end end def scaling { :current_scale => current_scale, :scales_from => scales_from, :scales_to => scales_to, :gear_profile => gear_profile, } if scalable? end def property(type, key) key, type = key.to_s, type.to_s properties.select{ |p| p['type'] == type }.find{ |p| p['name'] == key } end def status debug "Getting cartridge #{name}'s status" result = rest_method "GET", :include => "status_messages" result.status_messages end def start debug "Starting cartridge #{name}" rest_method "START", :event => "start" end def stop debug "Stopping cartridge #{name}" rest_method "STOP", :event => "stop" end def restart debug "Restarting cartridge #{name}" rest_method "RESTART", :event => "restart" end def reload debug "Reloading cartridge #{name}" rest_method "RESTART", :event => "reload" end def destroy debug "Deleting cartridge #{name}" rest_method "DELETE" end alias :delete :destroy def set_scales(values) values.delete_if{|k,v| v.nil? } debug "Setting scales = %s" % values.map{|k,v| "#{k}: #{v}"}.join(" ") rest_method "UPDATE", values end def set_storage(values) debug "Setting additional storage: #{values[:additional_gear_storage]}GB" rest_method "UPDATE", values end def connection_info info = property(:cart_data, :connection_url) || property(:cart_data, :job_url) || property(:cart_data, :monitoring_url) info ? (info["value"] || '').rstrip : nil end def <=>(other) return -1 if other.type == 'standalone' && type != 'standalone' return 1 if type == 'standalone' && other.type != 'standalone' name <=> other.name end def url_basename uri = URI.parse(url) name = uri.fragment name = Rack::Utils.parse_nested_query(uri.query)['name'] if name.blank? && uri.query name = File.basename(uri.path) if name.blank? && uri.path.present? && uri.path != '/' name.presence || url rescue url end def self.for_url(url) new 'url' => url end end end end rhc-1.38.7/lib/rhc/rest/team.rb0000644000004100000410000000116112756270551016201 0ustar www-datawww-datamodule RHC module Rest class Team < Base include Membership define_attr :id, :name, :global def global? global end def <=>(team) return self.name <=> team.name end def to_s self.name end def destroy(force=false) debug "Deleting team #{name} (#{id})" raise RHC::OperationNotSupportedException.new("The server does not support deleting this resource.") unless supports? 'DELETE' rest_method "DELETE" end alias :delete :destroy def default_member_role 'view' end end end endrhc-1.38.7/lib/rhc/rest/environment_variable.rb0000644000004100000410000000036412756270551021470 0ustar www-datawww-datamodule RHC module Rest class EnvironmentVariable < Base define_attr :name, :value def to_hash { :name => name, :value => value } end def <=>(other) name <=> other.name end end end end rhc-1.38.7/lib/rhc/rest/deployment.rb0000644000004100000410000000074312756270551017440 0ustar www-datawww-datarequire 'rhc/rest/activation' module RHC module Rest class Deployment < Base define_attr :id, :ref, :sha1, :artifact_url, :hot_deploy, :created_at, :force_clean_build, :activations def activations @activations ||= attributes['activations'].map{|activation| Activation.new({:created_at => RHC::Helpers.datetime_rfc3339(activation)}, client)}.sort end def <=>(other) other.created_at <=> created_at end end end end rhc-1.38.7/lib/rhc/rest/membership.rb0000644000004100000410000000701512756270551017412 0ustar www-datawww-datamodule RHC::Rest module Membership class Member < Base define_attr :name, :login, :id, :type, :from, :role, :owner, :explicit_role def owner? !!owner end def admin? role == 'admin' end def editor? role == 'edit' end def viewer? role == 'view' end def team? type == 'team' end def name attributes['name'] || login end def type attributes['type'] || 'user' end def explicit_role? explicit_role.present? end def from Array(attributes['from']) end def grant_from?(type, id) from.detect {|f| f['type'] == type && f['id'] == id} end def teams(members) team_ids = from.inject([]) {|ids, f| ids << f['id'] if f['type'] == 'team'; ids } members.select {|m| m.team? && team_ids.include?(m.id) } end def to_s if name == login "#{login} (#{role})" elsif login "#{name} <#{login}> (#{role})" else "#{name} (#{role})" end end def <=>(other) [role_weight, type, name, id] <=> [other.role_weight, other.type, other.name, other.id] end def role_weight if owner? 0 else case role when 'admin' then 1 when 'edit' then 2 when 'view' then 3 else 4 end end end end def self.included(other) end def supports_members? supports? 'LIST_MEMBERS' end def supports_update_members? supports? 'UPDATE_MEMBERS' end def default_member_role 'edit' end def members @members ||= if (members = attributes['members']).nil? debug "Getting all members for #{id}" raise RHC::MembersNotSupported unless supports_members? rest_method 'LIST_MEMBERS' else members.map{ |m| Member.new(m, client) } end end def compact_members arr = members.reject(&:owner?) rescue [] if arr.length > 5 arr = arr.sort_by(&:name) admin, arr = arr.partition(&:admin?) edit, arr = arr.partition(&:editor?) view, arr = arr.partition(&:viewer?) admin << "Admins" if admin.present? edit << "Editors" if edit.present? view << "Viewers" if view.present? arr.map!(&:to_s) admin.concat(edit).concat(view).concat(arr) elsif arr.present? arr.sort_by{ |m| [m.role_weight, m.name] }.join(', ') end end def update_members(members, options={}) raise "Members must be an array" unless members.is_a?(Array) raise RHC::MembersNotSupported unless supports_members? raise RHC::ChangeMembersOnResourceNotSupported unless supports_update_members? @members = (attributes['members'] = rest_method('UPDATE_MEMBERS', {:members => members}, options)) end def delete_members(options={}) raise RHC::MembersNotSupported unless supports_members? rest_method "LIST_MEMBERS", nil, {:method => :delete}.merge(options) ensure @members = attributes['members'] = nil end def leave(options={}) raise RHC::MembersNotSupported.new("The server does not support leaving this resource.") unless supports? 'LEAVE' rest_method "LEAVE", nil, options ensure @members = attributes['members'] = nil end def owner members.find(&:owner?) rescue RHC::MembersNotSupported nil end end endrhc-1.38.7/lib/rhc/rest/client.rb0000644000004100000410000007640012756270551016541 0ustar www-datawww-datarequire 'rhc/json' require 'rhc/helpers' require 'uri' require 'logger' require 'httpclient' require 'benchmark' require 'set' module RHC module Rest # # These are methods that belong to the API object but are # callable from the client for convenience. # module ApiMethods def add_domain(id, payload={}) debug "Adding domain #{id} with options #{payload.inspect}" @domains = nil payload.delete_if{ |k,v| k.nil? or v.nil? } api.rest_method "ADD_DOMAIN", {:id => id}.merge(payload) end def domains debug "Getting all domains" @domains ||= api.rest_method "LIST_DOMAINS" end def owned_domains debug "Getting owned domains" if link = api.link_href(:LIST_DOMAINS_BY_OWNER) @owned_domains ||= api.rest_method 'LIST_DOMAINS_BY_OWNER', :owner => '@self' else domains end end def applications(options={}) debug "Getting applications" if link = api.link_href(:LIST_APPLICATIONS) api.rest_method :LIST_APPLICATIONS, options else self.domains.map{ |d| d.applications(options) }.flatten end end def owned_applications(options={}) debug "Getting owned applications" if link = api.link_href(:LIST_APPLICATIONS_BY_OWNER) @owned_applications ||= api.rest_method 'LIST_APPLICATIONS_BY_OWNER', :owner => '@self' else owned_domains_names = owned_domains.map{|d| d.name} @owned_applications ||= applications(options).select{|app| owned_domains_names.include?(app.domain)} end end def cartridges debug "Getting all cartridges" @cartridges ||= api.rest_method("LIST_CARTRIDGES", nil, :lazy_auth => true) end def regions debug "Getting all regions and zones available" if supports_regions_and_zones? @regions ||= api.rest_method("LIST_REGIONS") else raise RHC::RegionsAndZonesNotSupportedException end end def user debug "Getting user info" @user ||= api.rest_method "GET_USER" end def add_team(name, payload={}) debug "Adding team #{name} with options #{payload.inspect}" @teams = nil payload.delete_if{ |k,v| k.nil? or v.nil? } if api.supports? 'ADD_TEAM' api.rest_method "ADD_TEAM", {:name => name}.merge(payload) else raise RHC::TeamsNotSupportedException end end def teams(opts={}) debug "Getting teams you are a member of" if link = api.link_href(:LIST_TEAMS) @teams ||= api.rest_method("LIST_TEAMS", opts) else raise RHC::TeamsNotSupportedException end end def owned_teams(opts={}) debug "Getting owned teams" if link = api.link_href(:LIST_TEAMS_BY_OWNER) @owned_teams ||= api.rest_method("LIST_TEAMS_BY_OWNER", opts.merge({:owner => '@self'})) else raise RHC::TeamsNotSupportedException end end def search_teams(search, global=false) debug "Searching teams" if link = api.link_href(:SEARCH_TEAMS) api.rest_method "SEARCH_TEAMS", :search => search, :global => global else raise RHC::TeamsNotSupportedException end end def search_owned_teams(search) debug "Searching owned teams" owned_teams.select{|team| team.name.downcase =~ /#{Regexp.escape(search)}/i} end def find_team(name, options={}) matching_teams = if options[:global] search_teams(name, true).select { |t| t.name == name } elsif options[:owned] owned_teams.select { |t| t.name == name } else teams.select{ |t| t.name == name } end if matching_teams.blank? raise TeamNotFoundException.new("Team with name #{name} not found") elsif matching_teams.length > 1 raise TeamNotFoundException.new("Multiple teams with name #{name} found. Use --team-id to select the team by id.") else matching_teams.first end end def find_team_by_id(id, options={}) precheck_team_id(id) if api.supports? :show_team request(:url => link_show_team_by_id(id), :method => "GET", :payload => options) else teams.find{ |t| t.id == id } end or raise TeamNotFoundException.new("Team with id #{id} not found") end #Find Domain by namespace def find_domain(id) debug "Finding domain #{id}" if link = api.link_href(:SHOW_DOMAIN, ':name' => id) request(:url => link, :method => "GET") else domains.find{ |d| d.name.downcase == id.downcase } end or raise DomainNotFoundException.new("Domain #{id} not found") end def find_application(domain, application, options={}) precheck_domain_id(domain) precheck_application_id(application) request(:url => link_show_application_by_domain_name(domain, application), :method => "GET", :payload => options) end def find_application_gear_groups(domain, application, options={}) precheck_domain_id(domain) precheck_application_id(application) request(:url => link_show_application_by_domain_name(domain, application, "gear_groups"), :method => "GET", :payload => options) end def find_application_gear_groups_endpoints(domain, application, options={}) precheck_domain_id(domain) precheck_application_id(application) request(:url => link_show_application_by_domain_name(domain, application, "gear_groups"), :method => "GET", :payload => options.merge(:include => 'endpoints')) end def find_application_aliases(domain, application, options={}) precheck_domain_id(domain) precheck_application_id(application) request(:url => link_show_application_by_domain_name(domain, application, "aliases"), :method => "GET", :payload => options) end def find_application_by_id(id, options={}) precheck_application_id(id) if api.supports? :show_application request(:url => link_show_application_by_id(id), :method => "GET", :payload => options) else applications.find{ |a| a.id == id } end or raise ApplicationNotFoundException.new("Application with id #{id} not found") end def find_application_by_id_gear_groups(id, options={}) precheck_application_id(id) if api.supports? :show_application request(:url => link_show_application_by_id(id, 'gear_groups'), :method => "GET", :payload => options) else applications.find{ |a| return a.gear_groups if a.id == id } end or raise ApplicationNotFoundException.new("Application with id #{id} not found") end # Catch domain ids which we can't make API calls for def precheck_domain_id(domain) raise DomainNotFoundException.new("Domain not specified") if domain.blank? raise DomainNotFoundException.new("Domain #{domain} not found") if ['.','..'].include?(domain) end def precheck_application_id(application) raise ApplicationNotFoundException.new("Application not specified") if application.blank? raise ApplicationNotFoundException.new("Application #{application} not found") if ['.','..'].include?(application) end def precheck_team_id(team) raise TeamNotFoundException.new("Team not specified") if team.blank? raise TeamNotFoundException.new("Team #{team} not found") if ['.','..'].include?(team) end def link_show_application_by_domain_name(domain, application, *args) if link = api.link_href(:SHOW_APPLICATION_BY_DOMAIN, {':domain_name' => domain, ':name' => application}, *args) link else # Pre-1.5 API ( [api.links['LIST_DOMAINS']['href']] + ([domain, "applications", application] + args).map{|s| URI.escape(s, RHC::Rest::Base::URI_ESCAPE_REGEX) } ).join("/") end end def link_show_application_by_id(id, *args) api.link_href(:SHOW_APPLICATION, {':id' => id}, *args) end def link_show_domain_by_name(domain, *args) api.link_href(:SHOW_DOMAIN, ':id' => domain) end def link_show_team_by_id(id, *args) api.link_href(:SHOW_TEAM, {':id' => id}, *args) end #Find Cartridge by name or regex def find_cartridges(name) debug "Finding cartridge #{name}" if name.is_a?(Hash) regex = name[:regex] type = name[:type] name = name[:name] end filtered = Array.new cartridges.each do |cart| if regex filtered.push(cart) if cart.name.match(regex) and (type.nil? or cart.type == type) else filtered.push(cart) if (name.nil? or cart.name == name) and (type.nil? or cart.type == type) end end return filtered end #find Key by name def find_key(name) debug "Finding key #{name}" user.find_key(name) or raise RHC::KeyNotFoundException.new("Key #{name} does not exist") end def sshkeys debug "Finding all keys for #{user.login}" user.keys end def add_key(name, key, content) debug "Adding key #{key} for #{user.login}" user.add_key name, key, content end def delete_key(name) debug "Deleting key '#{name}'" key = find_key(name) key.destroy end def supports_sessions? api.supports? 'ADD_AUTHORIZATION' end def supports_regions_and_zones? api.supports? :LIST_REGIONS end # # allows_region_selection? determines if the broker will allow a client # to specify the region for an app # @returns true if allowed; nil otherwise # def allows_region_selection? supported_regions = regions rescue [] supported_regions.any? { |region| region.allow_selection == true } end def authorizations raise AuthorizationsNotSupported unless supports_sessions? api.rest_method 'LIST_AUTHORIZATIONS' end # # Returns nil if creating sessions is not supported, raises on error, otherwise # returns an Authorization object. # def new_session(options={}) if supports_sessions? api.rest_method('ADD_AUTHORIZATION', { :scope => 'session', :note => "RHC/#{RHC::VERSION::STRING} (from #{Socket.gethostname rescue 'unknown'} on #{RUBY_PLATFORM})", :reuse => true }, options) end end def add_authorization(options={}) raise AuthorizationsNotSupported unless supports_sessions? api.rest_method('ADD_AUTHORIZATION', options, options) end def delete_authorizations raise AuthorizationsNotSupported unless supports_sessions? api.rest_method('LIST_AUTHORIZATIONS', nil, {:method => :delete}) end def delete_authorization(token) raise AuthorizationsNotSupported unless supports_sessions? api.rest_method('SHOW_AUTHORIZATION', nil, {:method => :delete, :params => {':id' => token}}) end def authorization_scope_list raise AuthorizationsNotSupported unless supports_sessions? link = api.links['ADD_AUTHORIZATION'] scope = link['optional_params'].find{ |h| h['name'] == 'scope' } scope['description'].scan(/(?!\n)\*(.*?)\n(.*?)(?:\n|\Z)/m).inject([]) do |h, (a, b)| h << [a.strip, b.strip] if a.present? && b.present? h end end def reset (instance_variables - [ :@end_point, :@debug, :@preferred_api_versions, :@auth, :@options, :@headers, :@last_options, :@httpclient, :@self_signed, :@current_api_version, :@api ]).each{ |sym| instance_variable_set(sym, nil) } self end end class Client < Base include ApiMethods # Keep the list of supported API versions here # The list may not necessarily be sorted; we will select the last # matching one supported by the server. # See #api_version_negotiated CLIENT_API_VERSIONS = [1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7] MAX_RETRIES = 5 def initialize(*args) options = args[0].is_a?(Hash) && args[0] || {} @end_point, @debug, @preferred_api_versions = if options.empty? options[:user] = args.delete_at(1) options[:password] = args.delete_at(1) args else [ options.delete(:url) || (options[:server] && "https://#{options.delete(:server)}/broker/rest/api"), options.delete(:debug), options.delete(:preferred_api_versions) ] end @preferred_api_versions ||= CLIENT_API_VERSIONS @debug ||= false @auth = options.delete(:auth) @api_always_auth = options.delete(:api_always_auth) self.headers.merge!(options.delete(:headers)) if options[:headers] self.options.merge!(options) debug "Connecting to #{@end_point}" end def url @end_point end def api_always_auth @api_always_auth end def api @api ||= RHC::Rest::Api.new(self, @preferred_api_versions).tap do |api| self.current_api_version = api.api_version_negotiated end end def api_version_negotiated api current_api_version end def attempt(retries, &block) (0..retries).each do |i| yield i < (retries-1), i end raise "Too many retries, giving up." end def request(options, &block) attempt(MAX_RETRIES) do |more, i| begin client, args = new_request(options.dup) auth = options[:auth] || self.auth response = nil debug "Request #{args[0].to_s.upcase} #{args[1]}#{"?#{args[2].map{|a| a.join('=')}.join(' ')}" if args[2] && args[0] == 'GET'}" time = Benchmark.realtime{ response = client.request(*(args << true)) } debug " code %s %4i ms" % [response.status, (time*1000).to_i] if response next if more && retry_proxy(response, i, args, client) auth.retry_auth?(response, self) and next if more && auth handle_error!(response, args[1], client) unless response.ok? return (if block_given? yield response else parse_response(response.content) unless response.nil? or response.code == 204 end) rescue HTTPClient::BadResponseError => e if e.res debug "Response: #{e.res.status} #{e.res.headers.inspect}\n#{e.res.content}\n-------------" if debug? next if more && retry_proxy(e.res, i, args, client) auth.retry_auth?(e.res, self) and next if more && auth handle_error!(e.res, args[1], client) end raise ConnectionException.new( "An unexpected error occurred when connecting to the server: #{e.message}") rescue HTTPClient::TimeoutError => e raise TimeoutException.new( "Connection to server timed out. "\ "It is possible the operation finished without being able "\ "to report success. Use 'rhc domain show' or 'rhc app show' "\ "to see the status of your applications.", e) rescue EOFError => e raise ConnectionException.new( "Connection to server got interrupted: #{e.message}") rescue OpenSSL::SSL::SSLError => e raise SelfSignedCertificate.new( 'self signed certificate', "The server is using a self-signed certificate, which means that a secure connection can't be established '#{args[1]}'.\n\n"\ "You may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") if self_signed? raise case e.message when /self signed certificate/ CertificateVerificationFailed.new( e.message, "The server is using a self-signed certificate, which means that a secure connection can't be established '#{args[1]}'.\n\n"\ "You may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") when /certificate verify failed/ CertificateVerificationFailed.new( e.message, "The server's certificate could not be verified, which means that a secure connection can't be established to the server '#{args[1]}'.\n\n"\ "If your server is using a self-signed certificate, you may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") when /unable to get local issuer certificate/ SSLConnectionFailed.new( e.message, "The server's certificate could not be verified, which means that a secure connection can't be established to the server '#{args[1]}'.\n\n"\ "You may need to specify your system CA certificate file with --ssl-ca-file=. If your server is using a self-signed certificate, you may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") when /^SSL_connect returned=1 errno=0 state=SSLv2\/v3 read server hello A/ SSLVersionRejected.new( e.message, "The server has rejected your connection attempt with an older SSL protocol. Pass --ssl-version=sslv3 on the command line to connect to this server.") when /^SSL_CTX_set_cipher_list:: no cipher match/ SSLVersionRejected.new( e.message, "The server has rejected your connection attempt because it does not support the requested SSL protocol version.\n\n"\ "Check with the administrator for a valid SSL version to use and pass --ssl-version= on the command line to connect to this server.") else SSLConnectionFailed.new( e.message, "A secure connection could not be established to the server (#{e.message}). You may disable secure connections to your server with the -k (or --insecure) option '#{args[1]}'.\n\n"\ "If your server is using a self-signed certificate, you may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") end rescue SocketError, Errno::ECONNREFUSED => e raise ConnectionException.new( "Unable to connect to the server (#{e.message})."\ "#{client.proxy.present? ? " Check that you have correctly specified your proxy server '#{client.proxy}' as well as your OpenShift server '#{args[1]}'." : " Check that you have correctly specified your OpenShift server '#{args[1]}'."}") rescue Errno::ECONNRESET => e raise ConnectionException.new( "The server has closed the connection unexpectedly (#{e.message}). Your last operation may still be running on the server; please check before retrying your last request.") rescue RHC::Rest::Exception raise rescue => e debug_error(e) raise ConnectionException, "An unexpected error occurred: #{e.message}", e.backtrace end end end protected include RHC::Helpers attr_reader :auth attr_accessor :current_api_version def headers @headers ||= { :accept => :json } end def user_agent RHC::Helpers.user_agent end def options @options ||= { } end def httpclient_for(options, auth=nil) user, password, token = options.delete(:user), options.delete(:password), options.delete(:token) if !@httpclient || @last_options != options @httpclient = RHC::Rest::HTTPClient.new(:agent_name => user_agent).tap do |http| debug "Created new httpclient" http.cookie_manager = nil http.debug_dev = $stderr if ENV['HTTP_DEBUG'] options.select{ |sym, value| http.respond_to?("#{sym}=") }.each{ |sym, value| http.send("#{sym}=", value) } ssl = http.ssl_config options.select{ |sym, value| ssl.respond_to?("#{sym}=") }.each{ |sym, value| ssl.send("#{sym}=", value) } ssl.add_trust_ca(options[:ca_file]) if options[:ca_file] ssl.verify_callback = default_verify_callback @last_options = options end end if auth && auth.respond_to?(:to_httpclient) auth.to_httpclient(@httpclient, options) else @httpclient.www_auth.basic_auth.set(@end_point, user, password) if user @httpclient.www_auth.oauth2.set_token(@end_point, token) if token end @httpclient end def default_verify_callback lambda do |is_ok, ctx| @self_signed = false unless is_ok cert = ctx.current_cert if cert && (cert.subject.cmp(cert.issuer) == 0) @self_signed = true debug "SSL Verification failed -- Using self signed cert" else debug "SSL Verification failed -- Preverify: #{is_ok}, Error: #{ctx.error_string} (#{ctx.error})" end return false end true end end def self_signed? @self_signed end def new_request(options) options.reverse_merge!(self.options) options[:connect_timeout] ||= options[:timeout] || 120 options[:receive_timeout] ||= options[:timeout] || 0 options[:send_timeout] ||= options[:timeout] || 0 options[:timeout] = nil auth = (options[:auth] || self.auth) unless options[:no_auth] if auth auth.to_request(options, self) end headers = (self.headers.to_a + (options.delete(:headers) || []).to_a).inject({}) do |h,(k,v)| v = "application/#{v}" if k == :accept && v.is_a?(Symbol) h[k.to_s.downcase.gsub(/_/, '-')] = v h end modifiers = [] version = options.delete(:api_version) || current_api_version modifiers << ";version=#{version}" if version query = options.delete(:query) || {} payload = options.delete(:payload) if options[:method].to_s.upcase == 'GET' query = payload payload = nil else headers['content-type'] ||= begin payload = payload.to_json unless payload.nil? || payload.is_a?(String) "application/json#{modifiers.join}" end end query = nil if query.blank? if headers['accept'] && modifiers.present? headers['accept'] << modifiers.join end # remove all unnecessary options options.delete(:lazy_auth) options.delete(:no_auth) options.delete(:accept) args = [options.delete(:method), options.delete(:url), query, payload, headers, true] [httpclient_for(options, auth), args] end def retry_proxy(response, i, args, client) if response.status == 502 debug "ERROR: Received bad gateway from server, will retry once if this is a GET" return true if i == 0 && args[0] == :get raise ConnectionException.new( "An error occurred while communicating with the server. This problem may only be temporary."\ "#{client.proxy.present? ? " Check that you have correctly specified your proxy server '#{client.proxy}' as well as your OpenShift server '#{args[1]}'." : " Check that you have correctly specified your OpenShift server '#{args[1]}'."}") end end def parse_response(response) result = RHC::Json.decode(response) type = result['type'] data = result['data'] || {} parse_messages result, data case type when 'domains' data.map{ |json| Domain.new(json, self) } when 'domain' Domain.new(data, self) when 'authorization' Authorization.new(data, self) when 'authorizations' data.map{ |json| Authorization.new(json, self) } when 'applications' data.map{ |json| Application.new(json, self) } when 'application' Application.new(data, self) when 'cartridges' data.map{ |json| Cartridge.new(json, self) } when 'cartridge' Cartridge.new(data, self) when 'user' User.new(data, self) when 'keys' data.map{ |json| Key.new(json, self) } when 'key' Key.new(data, self) when 'gear_groups' data.map{ |json| GearGroup.new(json, self) } when 'aliases' data.map{ |json| Alias.new(json, self) } when 'environment-variables' data.map{ |json| EnvironmentVariable.new(json, self) } when 'deployments' data.map{ |json| Deployment.new(json, self) } when 'team' Team.new(data, self) when 'teams' data.map{ |json| Team.new(json, self) } when 'member' RHC::Rest::Membership::Member.new(data, self) when 'members' data.map{ |json| RHC::Rest::Membership::Member.new(json, self) } when 'regions' data.map{ |json| Region.new(json, self) } else data end end def parse_messages(result, data) raw = (result || {})['messages'] || [] raw.delete_if do |m| m.delete_if{ |k,v| k.nil? || v.blank? } if m.is_a? Hash m.blank? end warnings, messages, raw = Array(raw).inject([[],[],[]]) do |a, m| severity, field, text = m.values_at('severity', 'field', 'text') text = (text || "").gsub(/\A\n+/m, "").rstrip case severity when 'warning' a[0] << text when 'debug' a[2] << m a[1] << text if debug? when 'info' a[2] << m a[1] << text if debug? || field == 'result' else a[2] << m a[1] << text end a end if data.is_a?(Array) data.each do |d| d['messages'] = messages d['warnings'] = warnings end elsif data.is_a?(Hash) data['messages'] = messages data['warnings'] = warnings end warnings.each do |warning| unless (@warning_map ||= Set.new).include?(warning) @warning_map << warning warn warning end end if respond_to? :warn raw end def raise_generic_error(url, client) raise ServerErrorException.new(generic_error_message(url, client), 129) end def generic_error_message(url, client) "The server did not respond correctly. This may be an issue "\ "with the server configuration or with your connection to the "\ "server (such as a Web proxy or firewall)."\ "#{client.proxy.present? ? " Please verify that your proxy server is working correctly (#{client.proxy}) and that you can access the OpenShift server #{url}" : " Please verify that you can access the OpenShift server #{url}"}" end def handle_error!(response, url, client) messages = [] parse_error = nil begin result = RHC::Json.decode(response.content) messages = parse_messages(result, {}) rescue => e debug "Response did not include a message from server: #{e.message}" end case response.status when 400 raise_generic_error(url, client) if messages.empty? message, keys = messages_to_fields(messages) raise ValidationException.new(message || "The operation could not be completed.", keys) when 401 raise UnAuthorizedException, "Not authenticated" when 403 raise RequestDeniedException, messages_to_error(messages) || "You are not authorized to perform this operation." when 404 if messages.length == 1 case messages.first['exit_code'] when 127 raise DomainNotFoundException, messages_to_error(messages) || generic_error_message(url, client) when 101 raise ApplicationNotFoundException, messages_to_error(messages) || generic_error_message(url, client) end end raise ResourceNotFoundException, messages_to_error(messages) || generic_error_message(url, client) when 409 raise_generic_error(url, client) if messages.empty? message, keys = messages_to_fields(messages) raise ValidationException.new(message || "The operation could not be completed.", keys) when 422 raise_generic_error(url, client) if messages.empty? message, keys = messages_to_fields(messages) raise ValidationException.new(message || "The operation was not valid.", keys) when 400 raise ClientErrorException, messages_to_error(messages) || "The server did not accept the requested operation." when 500 raise ServerErrorException, messages_to_error(messages) || generic_error_message(url, client) when 503 raise ServiceUnavailableException, messages_to_error(messages) || generic_error_message(url, client) else raise ServerErrorException, messages_to_error(messages) || "Server returned an unexpected error code: #{response.status}" end raise_generic_error end private def messages_to_error(messages) errors, remaining = messages.partition{ |m| (m['severity'] || "").upcase == 'ERROR' } if errors.present? if errors.length == 1 errors.first['text'] else "The server reported multiple errors:\n* #{errors.map{ |m| m['text'] || "An unknown server error occurred.#{ " (exit code: #{m['exit_code']}" if m['exit_code']}}" }.join("\n* ")}" end elsif remaining.present? "The operation did not complete successfully, but the server returned additional information:\n* #{remaining.map{ |m| m['text'] || 'No message'}.join("\n* ")}" end end def messages_to_fields(messages) keys = messages.group_by{ |m| m['field'] }.keys.compact.sort.map(&:to_sym) rescue [] [messages_to_error(messages), keys] end end end end rhc-1.38.7/lib/rhc/rest/mock.rb0000644000004100000410000011627012756270551016214 0ustar www-datawww-datamodule RHC::Rest::Mock def self.start RHC::Helpers.warn "Running in mock mode" require 'webmock' WebMock.disable_net_connect! MockRestClient.class_eval do include WebMock::API include Helpers def user_agent_header end def user_auth {:user => nil, :password => nil} end end MockRestUser.class_eval do def add_key(*args) attributes['links'] ||= {} links['ADD_KEY'] = {'href' => 'https://test.domain.com/broker/rest/user/keys', 'method' => 'POST'} super end end MockRestClient.new.tap do |c| d = c.add_domain("test1") app = d.add_application('app1', 'carttype1') app.cartridges[0].display_name = "A display name" app.add_cartridge('mockcart2') app2 = d.add_application('app2', 'carttype2', true) c.stub_add_key_error('test', 'this failed') end end module Helpers def mock_date_1 '2013-02-21T01:00:01Z' end def mock_user "test_user" end def mock_user_auth respond_to?(:user_auth) ? self.user_auth : {:user => username, :password => password} end def credentials_for(with_auth) if with_auth == true [respond_to?(:username) ? self.username : mock_user, respond_to?(:password) ? self.password : mock_pass] elsif with_auth with_auth.values_at(:user, :password) end end def example_allows_members? respond_to?(:supports_members?) && supports_members? end def example_allows_gear_sizes? respond_to?(:supports_allowed_gear_sizes?) && supports_allowed_gear_sizes? end def expect_authorization(with_auth) username, password = credentials_for(with_auth) lambda{ |r| !username || (r.headers['Authorization'] == "Basic #{["#{username}:#{password}"].pack('m').tr("\n", '')}") } end def stub_api_request(method, uri, with_auth=true) api = stub_request(method, mock_href(uri, with_auth)) api.with(&lambda{ |r| request.headers['Authorization'] == "Bearer #{with_auth[:token]}" }) if with_auth.respond_to?(:[]) && with_auth[:token] api.with(&expect_authorization(with_auth)) api.with(&user_agent_header) if @challenge stub_request(method, mock_href(uri, false)).to_return(:status => 401, :headers => {'www-authenticate' => 'basic realm="openshift broker"'}) end api end def challenge(&block) @challenge = true yield ensure @challenge = false end def stub_api(auth=false, authorizations=false) stub_api_request(:get, 'broker/rest/api', auth). to_return({ :body => { :data => mock_response_links(authorizations ? mock_api_with_authorizations : mock_real_client_links), :supported_api_versions => [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7], }.to_json }) end def stub_api_v12(auth=false) stub_api_request(:get, 'broker/rest/api', auth). to_return({ :body => { :data => mock_response_links(mock_real_client_links), :supported_api_versions => [1.0, 1.1, 1.2], }.to_json }) end def stub_user(auth=mock_user_auth) stub_api_request(:get, 'broker/rest/user', auth).to_return(simple_user(username)) end def stub_add_key(name='default') stub_api_request(:post, 'broker/rest/user/keys', mock_user_auth). with(:body => hash_including({:name => name, :type => 'ssh-rsa'})). to_return({:status => 201, :body => {}.to_json}) end def stub_update_key(name) stub_api_request(:put, "broker/rest/user/keys/#{name}", mock_user_auth). with(:body => hash_including({:type => 'ssh-rsa'})). to_return({:status => 200, :body => {}.to_json}) end def stub_add_key_error(name, message, code=422) stub_api_request(:post, "broker/rest/user/keys", mock_user_auth). with(:body => hash_including({:name => name, :type => 'ssh-rsa'})). to_return({:status => code, :body => {:messages => [ {:text => message, :field => 'name', :severity => 'error'}, {:text => "A warning from the server", :field => nil, :severity => 'warning'}, ] }.to_json}) end def stub_create_domain(name) stub_api_request(:post, 'broker/rest/domains', mock_user_auth). with(:body => hash_including({:id => name})). to_return(new_domain(name)) end def stub_authorizations stub_api_request(:get, 'broker/rest/user/authorizations', mock_user_auth). to_return({ :status => 200, :body => { :type => 'authorizations', :data => [ { :note => 'an_authorization', :token => 'a_token_value', :created_at => mock_date_1, :expires_in_seconds => 60, :scopes => 'session read' } ] }.to_json }) end def stub_delete_authorizations stub_api_request(:delete, 'broker/rest/user/authorizations', mock_user_auth). to_return(:status => 204) end def stub_delete_authorization(token) stub_api_request(:delete, "broker/rest/user/authorizations/#{token}", mock_user_auth). to_return(:status => 204) end def stub_add_authorization(params) stub_api_request(:post, 'broker/rest/user/authorizations', mock_user_auth). with(:body => hash_including(params)). to_return(new_authorization(params)) end def stub_no_keys stub_api_request(:get, 'broker/rest/user/keys', mock_user_auth).to_return(empty_keys) end def stub_mock_ssh_keys(name='test') stub_api_request(:get, 'broker/rest/user/keys', mock_user_auth). to_return({ :body => { :type => 'keys', :data => [ { :name => name, :type => pub_key.split[0], :content => pub_key.split[1], # :links => mock_response_links([ # ['UPDATE', "broker/rest/user/keys/#{name}", 'put'] # ]), } ], }.to_json }) end def stub_one_key(name) stub_api_request(:get, 'broker/rest/user/keys', mock_user_auth). to_return({ :body => { :type => 'keys', :data => [ { :name => name, :type => 'ssh-rsa', :content => rsa_key_content_public, :links => mock_response_links([ ['UPDATE', "broker/rest/user/keys/#{name}", 'put'] ]), } ], }.to_json }) end def stub_no_domains(with_auth=mock_user_auth) stub_api_request(:get, 'broker/rest/domains', with_auth).to_return(empty_domains) end def stub_one_domain(name, optional_params=nil, with_auth=mock_user_auth) stub_api_request(:get, 'broker/rest/domains', with_auth). to_return({ :body => { :type => 'domains', :data => [{:id => name, :links => mock_response_links(mock_domain_links(name).concat([ ['LIST_APPLICATIONS', "broker/rest/domains/#{name}/applications", 'get'], ['ADD_APPLICATION', "broker/rest/domains/#{name}/applications", 'post', ({:optional_params => optional_params} if optional_params)], (['LIST_MEMBERS', "broker/rest/domains/#{name}/members", 'get'] if example_allows_members?), (['UPDATE_MEMBERS', "broker/rest/domains/#{name}/members", 'patch'] if example_allows_members?), ].compact))}], }.to_json }) end def stub_one_application(domain_name, name, *args) stub_api_request(:get, "broker/rest/domains/#{domain_name}/applications", mock_user_auth). to_return({ :body => { :type => 'applications', :data => [{ :domain_id => domain_name, :id => 1, :name => name, :ssh_url => "ssh://12345@#{name}-#{domain_name}.rhcloud.com", :app_url => "http://#{name}-#{domain_name}.rhcloud.com", :links => mock_response_links([ ]), }], }.to_json }) stub_relative_application(domain_name,name, *args) end def stub_relative_application(domain_name, app_name, body = {}, status = 200) url = client_links['LIST_DOMAINS']['relative'] rescue "broker/rest/domains" stub_api_request(:any, "#{url}/#{domain_name}/applications/#{app_name}"). to_return({ :body => { :type => 'application', :data => { :domain_id => domain_name, :name => app_name, :id => 1, :links => mock_response_links(mock_app_links(domain_name,app_name)), } }.merge(body).to_json, :status => status }) end def stub_application_cartridges(domain_name, app_name, cartridges, status = 200) url = client_links['LIST_DOMAINS']['relative'] rescue "broker/rest/domains" stub_api_request(:any, "#{url}/#{domain_name}/applications/#{app_name}/cartridges"). to_return({ :body => { :type => 'cartridges', :data => cartridges.map{ |c| c.is_a?(String) ? {:name => c} : c }.map do |cart| cart[:links] ||= mock_response_links(mock_cart_links(domain_name,app_name, cart[:name])) cart end }.to_json, :status => status }) end def stub_simple_carts(with_auth=mock_user_auth) stub_api_request(:get, 'broker/rest/cartridges', with_auth).to_return(simple_carts) end def stub_simple_regions(empty=false, allow_selection=true, with_auth=mock_user_auth) stub_api_request(:get, 'broker/rest/regions', with_auth).to_return(simple_regions(empty, allow_selection)) end def define_exceptional_test_on_wizard RHC::Wizard.module_eval <<-EOM private def test_and_raise raise end EOM end def empty_keys empty_response_list('keys') end def empty_domains empty_response_list('domains') end def empty_response_list(type) { :body => { :type => type, :data => [], }.to_json } end def new_domain(name) { :status => 201, :body => { :type => 'domain', :data => { :id => name, :links => mock_response_links([ ]) }, }.to_json } end def simple_carts { :body => { :type => 'cartridges', :data => [ {:name => 'mock_standalone_cart-1', :type => 'standalone', :tags => ['cartridge'], :display_name => 'Mock1 Cart'}, {:name => 'mock_standalone_cart-2', :type => 'standalone', :description => 'Mock2 description'}, {:name => 'mock_embedded_cart-1', :type => 'embedded', :tags => ['scheduled'], :display_name => 'Mock1 Embedded Cart'}, {:name => 'premium_cart-1', :type => 'standalone', :tags => ['premium'], :display_name => 'Premium Cart', :usage_rate_usd => '0.02'}, ], }.to_json } end def simple_regions(empty=false, allow_selection=true) { :body => { :type => 'regions', :data => empty ? [] : [ {:id => 'region0001', :default => false, :name => 'north', :allow_selection => allow_selection, :description => 'Servers in the north of US', :zones => [{:name => 'west', :created_at => '2014-01-01T01:00:00Z', :updated_at => '2014-01-01T01:00:00Z'}, {:name => 'east', :created_at => '2014-01-01T01:00:00Z', :updated_at => '2014-01-01T01:00:00Z'}]}, {:id => 'region0002', :default => true, :name => 'south', :allow_selection => allow_selection, :zones => [{:name => 'east', :created_at => '2014-01-01T01:00:00Z', :updated_at => '2014-01-01T01:00:00Z'}]}, ], }.to_json } end def simple_user(login) { :body => { :type => 'user', :data => { :login => login, :plan_id => respond_to?(:user_plan_id) ? self.user_plan_id : nil, :consumed_gears => respond_to?(:user_consumed_gears) ? self.user_consumed_gears : 0, :max_gears => respond_to?(:user_max_gears) ? self.user_max_gears : 3, :capabilities => respond_to?(:user_capabilities) ? self.user_capabilities : {:gear_sizes => ['small', 'medium']}, :links => mock_response_links([ ['ADD_KEY', "broker/rest/user/keys", 'POST'], ['LIST_KEYS', "broker/rest/user/keys", 'GET'], ]) }, }.to_json } end def new_authorization(params) { :status => 201, :body => { :type => 'authorization', :data => { :note => params[:note], :token => 'a_token_value', :scopes => (params[:scope] || "userinfo").gsub(/,/, ' '), :expires_in => (params[:expires_in] || 60).to_i, :expires_in_seconds => (params[:expires_in] || 60).to_i, :created_at => mock_date_1, }, }.to_json } end def mock_pass "test pass" end def mock_uri "test.domain.com" end # Creates consistent hrefs for testing def mock_href(relative="", with_auth=false) server = respond_to?(:server) ? self.server : mock_uri user, pass = credentials_for(with_auth) uri_string = if user "#{user}:#{pass}@#{server}" else server end "https://#{uri_string}/#{relative}" end def mock_response_links(links) link_set = {} links.each do |link| options = link[3] || {} link_set[link[0]] = { 'href' => mock_href(link[1]), 'method' => link[2], 'relative' => link[1]}.merge(options) end link_set end def mock_app_links(domain_id='test_domain',app_id='test_app') [['ADD_CARTRIDGE', "domains/#{domain_id}/apps/#{app_id}/carts/add", 'post', {'optional_params' => [{'name' => 'environment_variables'}]} ], ['LIST_CARTRIDGES', "broker/rest/domains/#{domain_id}/applications/#{app_id}/cartridges", 'get' ], ['GET_GEAR_GROUPS', "domains/#{domain_id}/apps/#{app_id}/gear_groups", 'get' ], ['START', "domains/#{domain_id}/apps/#{app_id}/start", 'post'], ['STOP', "domains/#{domain_id}/apps/#{app_id}/stop", 'post'], ['RESTART', "domains/#{domain_id}/apps/#{app_id}/restart", 'post'], ['SCALE_UP', "broker/rest/application/#{app_id}/events", 'scale-up'], ['SCALE_DOWN', "broker/rest/application/#{app_id}/events", 'scale-down'], ['THREAD_DUMP', "domains/#{domain_id}/apps/#{app_id}/event", 'post'], ['ADD_ALIAS', "domains/#{domain_id}/apps/#{app_id}/event", 'post'], ['REMOVE_ALIAS', "domains/#{domain_id}/apps/#{app_id}/event", 'post'], ['LIST_ALIASES', "domains/#{domain_id}/apps/#{app_id}/aliases", 'get'], ['LIST_ENVIRONMENT_VARIABLES', "domains/#{domain_id}/apps/#{app_id}/event", 'post'], ['SET_UNSET_ENVIRONMENT_VARIABLES', "domains/#{domain_id}/apps/#{app_id}/event", 'post'], ['DELETE', "broker/rest/domains/#{domain_id}/applications/#{app_id}", 'delete'], (['LIST_MEMBERS', "domains/#{domain_id}/apps/#{app_id}/members", 'get'] if example_allows_members?), ['UPDATE', "broker/rest/domain/#{domain_id}/application/#{app_id}", 'put'], ['LIST_DEPLOYMENTS', "broker/rest/domain/#{domain_id}/application/#{app_id}/deployments", 'get' ], ['UPDATE_DEPLOYMENTS', "broker/rest/domain/#{domain_id}/application/#{app_id}/deployments", 'post' ], ['ACTIVATE', "broker/rest/domain/#{domain_id}/application/#{app_id}/events", 'post'], ['DEPLOY', "broker/rest/domain/#{domain_id}/application/#{app_id}/deployments", 'post'], ['MAKE_HA', "broker/rest/application/#{app_id}/events", 'make-ha'] ].compact end def mock_cart_links(domain_id='test_domain',app_id='test_app',cart_id='test_cart') [['START', "domains/#{domain_id}/apps/#{app_id}/carts/#{cart_id}/start", 'post'], ['STOP', "domains/#{domain_id}/apps/#{app_id}/carts/#{cart_id}/stop", 'post'], ['RESTART', "domains/#{domain_id}/apps/#{app_id}/carts/#{cart_id}/restart", 'post'], ['DELETE', "broker/rest/domains/#{domain_id}/applications/#{app_id}/cartridges/#{cart_id}", 'DELETE']] end def mock_client_links mock_teams_links.concat([ ['GET_USER', 'user', 'get' ], ['ADD_DOMAIN', 'domains/add', 'post'], ['LIST_DOMAINS', 'domains', 'get' ], ['LIST_CARTRIDGES', 'cartridges', 'get' ] ]) end def mock_real_client_links mock_teams_links.concat([ ['GET_USER', "broker/rest/user", 'GET'], ['LIST_DOMAINS', "broker/rest/domains", 'GET'], ['ADD_DOMAIN', "broker/rest/domains", 'POST', ({'optional_params' => [{'name' => 'allowed_gear_sizes'}]} if example_allows_gear_sizes?)].compact, ['LIST_CARTRIDGES', "broker/rest/cartridges", 'GET'], ['LIST_REGIONS', "broker/rest/regions", 'GET'], ]) end def mock_teams_links [['SEARCH_TEAMS', "broker/rest/teams", 'GET'], ['LIST_TEAMS', "broker/rest/teams", 'GET'], ['LIST_TEAMS_BY_OWNER', "broker/rest/teams", 'GET']] end def mock_api_with_authorizations mock_real_client_links.concat([ ['LIST_AUTHORIZATIONS', "broker/rest/user/authorizations", 'GET'], ['ADD_AUTHORIZATION', "broker/rest/user/authorizations", 'POST'], ['SHOW_AUTHORIZATION', "broker/rest/user/authorizations/:id", 'GET'], ]) end def mock_team_links(team_id='test_team') [['GET', "team/#{team_id}", 'get' ], ['ADD_MEMBER', "team/#{team_id}/members", 'post', {'optional_params' => [{'name' => 'id'}, {'name' => 'login'}], 'required_params' => [{'name' => 'role'}]} ], ['LIST_MEMBERS', "team/#{team_id}/update", 'get' ], ['UPDATE_MEMBERS', "team/#{team_id}/delete", 'patch', {'optional_params' => [{'name' => 'id'}, {'name' => 'login'}, {'name' => 'members'}] } ], ['LEAVE', "team/#{team_id}/delete", 'delete' ], ['DELETE', "team/#{team_id}/delete", 'delete' ]] end def mock_domain_links(domain_id='test_domain') [['ADD_APPLICATION', "domains/#{domain_id}/apps/add", 'post', {'optional_params' => [{'name' => 'environment_variables'}, {'name' => 'cartridges[][name]'}, {'name' => 'cartridges[][url]'}]} ], ['LIST_APPLICATIONS', "domains/#{domain_id}/apps", 'get' ], ['UPDATE', "domains/#{domain_id}/update", 'put'], ['DELETE', "domains/#{domain_id}/delete", 'post']] end def mock_key_links(key_id='test_key') [['UPDATE', "user/keys/#{key_id}/update", 'post'], ['DELETE', "user/keys/#{key_id}/delete", 'post']] end def mock_user_links [['ADD_KEY', 'user/keys/add', 'post'], ['LIST_KEYS', 'user/keys/', 'get' ]] end def mock_alias_links(domain_id='test_domain',app_id='test_app',alias_id='test.foo.com') [['DELETE', "domains/#{domain_id}/apps/#{app_id}/aliases/#{alias_id}/delete", 'post'], ['GET', "domains/#{domain_id}/apps/#{app_id}/aliases/#{alias_id}", 'get' ], ['UPDATE', "domains/#{domain_id}/apps/#{app_id}/aliases/#{alias_id}/update", 'post' ]] end def mock_cartridge_response(cart_count=1, url=false) carts = [] while carts.length < cart_count carts << { :name => "mock_cart_#{carts.length}", :url => url ? "http://a.url/#{carts.length}" : nil, :type => carts.empty? ? 'standalone' : 'embedded', :links => mock_response_links(mock_cart_links('mock_domain','mock_app',"mock_cart_#{carts.length}")) } end carts = carts[0] if cart_count == 1 type = cart_count == 1 ? 'cartridge' : 'cartridges' return { :body => { :type => type, :data => carts }.to_json, :status => 200 } end def mock_alias_response(count=1) aliases = count.times.inject([]) do |arr, i| arr << {:id => "www.alias#{i}.com"} end return { :body => { :type => count == 1 ? 'alias' : 'aliases', :data => aliases }.to_json, :status => 200 } end def mock_gear_groups_response() groups = [{}] type = 'gear_groups' return { :body => { :type => type, :data => groups }.to_json, :status => 200 } end end class MockRestClient < RHC::Rest::Client include Helpers def initialize(config=RHC::Config, version=1.0) obj = self if RHC::Rest::Client.respond_to?(:stub) RHC::Rest::Client.stub(:new) { obj } else RHC::Rest::Client.instance_eval do @obj = obj def new(*args) @obj end end end @domains = [] @teams = [] @user = MockRestUser.new(self, config.username) @api = MockRestApi.new(self, config) @version = version end def api @api end def user @user end def domains @domains end def teams(opts={}) @teams end def api_version_negotiated @version end def cartridges premium_embedded = MockRestCartridge.new(self, "premium_cart", "embedded") premium_embedded.usage_rates = {0.05 => []} [MockRestCartridge.new(self, "mock_cart-1", "embedded"), # code should sort this to be after standalone MockRestCartridge.new(self, "mock_standalone_cart-1", "standalone"), MockRestCartridge.new(self, "mock_standalone_cart-2", "standalone"), MockRestCartridge.new(self, "mock_unique_standalone_cart-1", "standalone"), MockRestCartridge.new(self, "jenkins-1", "standalone", nil, ['ci']), MockRestCartridge.new(self, "mock_cart-2", "embedded"), MockRestCartridge.new(self, "unique_mock_cart-1", "embedded"), MockRestCartridge.new(self, "jenkins-client-1", "embedded", nil, ['ci_builder']), MockRestCartridge.new(self, "embcart-1", "embedded"), MockRestCartridge.new(self, "embcart-2", "embedded"), premium_embedded ] end def add_team(name, extra=false) t = MockRestTeam.new(self, name) if extra t.attributes['members'] = [{'owner' => true, 'name' => 'a_user_name'}] end @teams << t t end def add_domain(id, extra=false) d = MockRestDomain.new(self, id) if extra d.attributes['creation_time'] = '2013-07-21T15:00:44Z' d.attributes['allowed_gear_sizes'] = ['small'] d.add_member(RHC::Rest::Membership::Member.new(:id => '1', :role => 'admin', :explicit_role => 'admin', :owner => true, :login => 'a_user_name', :type => 'user')) end @domains << d d end def sshkeys @user.keys end def add_key(name, type, content) @user.add_key(name, type, content) end def delete_key(name) @user.keys.delete_if { |key| key.name == name } end # Need to mock this since we are not registering HTTP requests when adding apps to the mock domain def find_application(domain, name, options = {}) find_domain(domain).applications.each do |app| return app if app.name.downcase == name.downcase end raise RHC::Rest::ApplicationNotFoundException.new("Application #{name} does not exist") end def find_application_gear_groups_endpoints(domain, name, options = {}) find_domain(domain).applications.each do |app| return app.gear_groups if app.name.downcase == name.downcase end raise RHC::Rest::ApplicationNotFoundException.new("Application #{name} does not exist") end def find_application_gear_groups(domain, name, options = {}) find_domain(domain).applications.each do |app| return app.gear_groups if app.name.downcase == name.downcase end raise RHC::Rest::ApplicationNotFoundException.new("Application #{name} does not exist") end def find_application_by_id(id, options={}) @domains.each{ |d| d.applications.each{ |a| return a if a.id == id } } raise RHC::Rest::ApplicationNotFoundException.new("Application with id #{id} does not exist") end def find_application_by_id_gear_groups(id, options={}) @domains.each{ |d| d.applications.each{ |a| return a.gear_groups if a.id == id } } raise RHC::Rest::ApplicationNotFoundException.new("Application with id #{id} does not exist") end end class MockRestApi < RHC::Rest::Api include Helpers def initialize(client, config) @client = client @client_api_versions = RHC::Rest::Client::CLIENT_API_VERSIONS @server_api_versions = @client_api_versions self.attributes = {:links => mock_response_links(mock_client_links)} end end class MockRestUser < RHC::Rest::User include Helpers def initialize(client, login) super({}, client) @login = login @keys = [ MockRestKey.new(client, 'mockkey1', 'ssh-rsa', 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDNK8xT3O+kSltmCMsSqBfAgheB3YFJ9Y0ESJnFjFASVxH70AcCQAgdQSD/r31+atYShJdP7f0AMWiQUTw2tK434XSylnZWEyIR0V+j+cyOPdVQlns6D5gPOnOtweFF0o18YulwCOK8Q1H28GK8qyWhLe0FcMmxtKbbQgaVRvQdXZz4ThzutCJOyJm9xVb93+fatvwZW76oLLvfFJcJSOK2sgW7tJM2A83bm4mwixFDF7wO/+C9WA+PgPKJUIjvy1gZjBhRB+3b58vLOnYhPOgMNruJwzB+wJ3pg8tLJEjxSbHyyoi6OqMBs4BVV7LdzvwTDxEjcgtHVvaVNXgO5iRX'), MockRestKey.new(client, 'mockkey2', 'ssh-dsa', 'AAAAB3NzaC1kc3MAAACBAPaaFj6Xjrjd8Dc4AAkJe0HigqaXMxj/87xHoV+nPgerHIceJWhPUWdW40lSASrgpAV9Eq4zzD+L19kgYdbMw0vSX5Cj3XtNOsow9MmMxFsYjTxCv4eSs/rLdGPaYZ5GVRPDu8tN42Bm8lj5o+ky3HzwW+mkQMZwcADQIgqtn6QhAAAAFQCirDfIMf/JoMOFf8CTnsTKWw/0zwAAAIAIQp6t2sLIp1d2TBfd/qLjOJA10rPADcnhBzWB/cd/oFJ8a/2nmxeSPR5Ov18T6itWqbKwvZw2UC0MrXoYbgcfVNP/ym1bCd9rB5hu1sg8WO4JIxA/47PZooT6PwTKVxHuENEzQyJL2o6ZJq+wuV0taLvm6IaM5TAZuEJ2p4TC/gAAAIBpLcVXZREa7XLY55nyidt/+UC+PxpjhPHOHbzL1OvWEaumN4wcJk/JZPppgXX9+WDkTm1SD891U0cXnGMTP0OZOHkOUHF2ZcfUe7p9kX4WjHs0OccoxV0Lny6MC4DjalJyaaEbijJHSUX3QlLcBOlPHJWpEpvWQ9P8AN4PokiGzA=='), MockRestKey.new(client, 'mockkey3', 'krb5-principal', 'mockuser@mockdomain') ] end def keys @keys end def add_key(name, type, content) @keys << MockRestKey.new(client, name, type, content) end end class MockRestTeam < RHC::Rest::Team include Helpers def initialize(client, name, id="123") super({}, client) @id = id @name = name @members = [] self.attributes = {:links => mock_response_links(mock_team_links(id))} end def destroy raise RHC::OperationNotSupportedException.new("The server does not support deleting this resource.") unless supports? 'DELETE' client.teams.delete_if { |t| t.name == @name } end def init_members @members ||= [] attributes['members'] ||= [] self end def add_member(member) (@members ||= []) << member (attributes['members'] ||= []) << member.attributes self end end class MockRestDomain < RHC::Rest::Domain include Helpers def initialize(client, id) super({}, client) @name = id @applications = [] self.attributes = {:links => mock_response_links(mock_domain_links(id))} init_members end def rename(id) @name = id self end def destroy(force=false) raise RHC::Rest::ClientErrorException.new("Applications must be empty.") unless @applications.empty? or force.present? client.domains.delete_if { |d| d.name == @name } @applications = nil end def add_application(name, type=nil, scale=nil, gear_profile='default', git_url=nil, region=nil) if type.is_a?(Hash) scale = type[:scale] gear_profile = type[:gear_profile] git_url = type[:initial_git_url] tags = type[:tags] region = type[:region] type = Array(type[:cartridges] || type[:cartridge]) end a = MockRestApplication.new(client, name, type, self, scale, gear_profile, git_url, nil, region) builder = @applications.find{ |app| app.cartridges.map(&:name).any?{ |s| s =~ /^jenkins-[\d\.]+$/ } } a.building_app = builder.name if builder @applications << a a.add_message("Success") a end def applications(*args) @applications end def init_members @members ||= [] attributes['members'] ||= [] self end def add_member(member) (@members ||= []) << member (attributes['members'] ||= []) << member.attributes self end end class MockRestGearGroup < RHC::Rest::GearGroup include Helpers def initialize(client=nil, carts=['name' => 'fake_geargroup_cart-0.1'], count=1) super({}, client) @cartridges = carts @gears = count.times.map do |i| {'state' => 'started', 'id' => "fakegearid#{i}", 'ssh_url' => "ssh://fakegearid#{i}@fakesshurl.com"} end @gear_profile = 'small' @base_gear_storage = 1 end end class MockRestAlias < RHC::Rest::Alias include Helpers def initialize(client, id, has_private_ssl_certificate=false, certificate_added_at=nil) super({}, client) @id = id @has_private_ssl_certificate = has_private_ssl_certificate @certificate_added_at = certificate_added_at end def add_certificate(ssl_certificate_content, private_key_content, pass_phrase) if (client.api_version_negotiated >= 1.4) @has_private_ssl_certificate = true @certificate_added_at = Time.now else raise RHC::Rest::SslCertificatesNotSupported, "The server does not support SSL certificates for custom aliases." end end def delete_certificate if (client.api_version_negotiated >= 1.4) @has_private_ssl_certificate = false @certificate_added_at = nil else raise RHC::Rest::SslCertificatesNotSupported, "The server does not support SSL certificates for custom aliases." end end def destroy puts @application.inspect puts self.inspect @application.aliases.delete self end end class MockRestApplication < RHC::Rest::Application include Helpers attr_writer :region def fakeuuid "fakeuuidfortests#{@name}" end def initialize(client, name, type, domain, scale=nil, gear_profile='default', initial_git_url=nil, environment_variables=nil, region=nil) super({}, client) @name = name @domain = domain @cartridges = [] @creation_time = Date.new(2000, 1, 1).strftime('%Y-%m-%dT%H:%M:%S%z') @uuid = fakeuuid @initial_git_url = initial_git_url @git_url = "git:fake.foo/git/#{@name}.git" @app_url = "https://#{@name}-#{@domain.name}.fake.foo/" @ssh_url = "ssh://#{@uuid}@127.0.0.1" @aliases = [] @environment_variables = environment_variables || [] @gear_profile = gear_profile @auto_deploy = true @keep_deployments = 1 if scale @scalable = true end @region = region self.attributes = {:links => mock_response_links(mock_app_links('mock_domain_0', 'mock_app_0')), :messages => []} self.gear_count = 5 types = Array(type) cart = add_cartridge(types.first, true) if types.first if scale cart.supported_scales_to = (cart.scales_to = -1) cart.supported_scales_from = (cart.scales_from = 2) cart.current_scale = 2 cart.scales_with = "haproxy-1.4" prox = add_cartridge('haproxy-1.4') prox.collocated_with = [types.first] prox.tags = ['web_proxy'] end types.drop(1).each{ |c| add_cartridge(c, false) } @framework = types.first end def destroy @domain.applications.delete self end def add_cartridge(cart, embedded=true, environment_variables=nil) name, url = if cart.is_a? String [cart, nil] elsif cart.respond_to? :[] [cart[:name] || cart['name'], cart[:url] || cart['url']] elsif RHC::Rest::Cartridge === cart [cart.name, cart.url] end type = embedded ? "embedded" : "standalone" c = MockRestCartridge.new(client, name, type, self) if url c.url = url c.name = c.url_basename end #set_environment_variables(environment_variables) c.properties << {'name' => 'prop1', 'value' => 'value1', 'description' => 'description1' } @cartridges << c c.messages << "Cartridge added with properties" c end def id @uuid || attributes['uuid'] || attributes['id'] end def gear_groups # we don't have heavy interaction with gear groups yet so keep this simple @gear_groups ||= begin if @scalable cartridges.map{ |c| MockRestGearGroup.new(client, [c.name], c.current_scale) if c.name != 'haproxy-1.4' }.compact else [MockRestGearGroup.new(client, cartridges.map{ |c| {'name' => c.name} }, 1)] end end end def cartridges @cartridges end def start @app end def stop(*args) @app end def restart @app end def reload @app end def tidy @app end def enable_ha if supports? "MAKE_HA" @app else raise RHC::HighAvailabilityNotSupportedException.new end end def scale_up @app end def scale_down @app end def add_alias(app_alias) @aliases << MockRestAlias.new(@client, app_alias) end def remove_alias(app_alias) @aliases.delete_if {|x| x.id == app_alias} end def aliases @aliases end def environment_variables if supports? "LIST_ENVIRONMENT_VARIABLES" @environment_variables || [] else raise RHC::EnvironmentVariablesNotSupportedException.new end end def set_environment_variables(env_vars=[]) if supports? "SET_UNSET_ENVIRONMENT_VARIABLES" environment_variables.concat env_vars else raise RHC::EnvironmentVariablesNotSupportedException.new end end def unset_environment_variables(env_vars=[]) if supports? "SET_UNSET_ENVIRONMENT_VARIABLES" env_vars.each { |item| environment_variables.delete_if { |env_var| env_var.name == item } } else raise RHC::EnvironmentVariablesNotSupportedException.new end end def init_members @members ||= [] attributes['members'] ||= [] self end def add_member(member) (@members ||= []) << member (attributes['members'] ||= []) << member.attributes self end def configure(options={}) options.each {|key,value| self.instance_variable_set("@#{key.to_s}", value)} end def auto_deploy @auto_deploy || false end def keep_deployments @keep_deployments end def region @region end def deployments base_time1 = Time.local(2000,1,1,1,0,0).strftime('%Y-%m-%dT%H:%M:%S%z') base_time2 = Time.local(2000,1,1,2,0,0).strftime('%Y-%m-%dT%H:%M:%S%z') base_time3 = Time.local(2000,1,1,3,0,0).strftime('%Y-%m-%dT%H:%M:%S%z') base_time4 = Time.local(2000,1,1,4,0,0).strftime('%Y-%m-%dT%H:%M:%S%z') base_time5 = Time.local(2000,1,1,5,0,0).strftime('%Y-%m-%dT%H:%M:%S%z') base_time6 = Time.local(2000,1,1,6,0,0).strftime('%Y-%m-%dT%H:%M:%S%z') [ MockRestDeployment.new(self, '0000001', 'master', '0000001', nil, false, base_time1, false, [base_time1]), MockRestDeployment.new(self, '0000002', 'master', '0000002', nil, false, base_time2, false, [base_time2, base_time6]), MockRestDeployment.new(self, '0000003', 'master', '0000003', nil, false, base_time3, false, [base_time3, base_time5]), MockRestDeployment.new(self, '0000004', 'master', '0000004', nil, false, base_time4, false, [base_time4]), MockRestDeployment.new(self, '0000005', 'master', '0000005', nil, false, base_time5, false, [base_time5]), ] end end class MockRestCartridge < RHC::Rest::Cartridge include Helpers attr_accessor :usage_rates def initialize(client, name, type, app=nil, tags=[], properties=[{'type' => 'cart_data', 'name' => 'connection_url', 'value' => "http://fake.url" }], description=nil) super({}, client) @name = name @description = description || "Description of #{name}" @type = type @app = app @tags = tags @properties = properties.each(&:stringify_keys!) @status_messages = [{"message" => "started", "gear_id" => "123"}] @scales_from = 1 @scales_to = 1 @current_scale = 1 @gear_profile = 'small' @additional_gear_storage = 5 @usage_rates = {} end def destroy @app.cartridges.delete self end def status @status_messages end def start @status_messages = [{"message" => "started", "gear_id" => "123"}] @app end def stop @status_messages = [{"message" => "stopped", "gear_id" => "123"}] @app end def restart @status_messages = [{"message" => "started", "gear_id" => "123"}] @app end def reload @app end def set_scales(values) values.delete_if{|k,v| v.nil? } @scales_from = values[:scales_from] if values[:scales_from] @scales_to = values[:scales_to] if values[:scales_to] self end def set_storage(values) @additional_gear_storage = values[:additional_gear_storage] if values[:additional_gear_storage] self end end class MockRestKey < RHC::Rest::Key include Helpers def initialize(client, name, type, content) super({}, client) @name = name @type = type @content = content end end class MockRestDeployment < RHC::Rest::Deployment def initialize(client, id, ref, sha1, artifact_url, hot_deploy, created_at, force_clean_build, activations) super({}, client) @id = id @ref = ref @sha1 = sha1 @artifact_url = artifact_url @hot_deploy = hot_deploy @created_at = created_at @force_clean_build = force_clean_build @activations = activations end def activations @activations.map{|activation| MockRestActivation.new(client, RHC::Helpers.datetime_rfc3339(activation))}.sort end end class MockRestActivation < RHC::Rest::Activation def initialize(client, created_at) super({}, client) @created_at = created_at end end end rhc-1.38.7/lib/rhc/rest/activation.rb0000644000004100000410000000025412756270551017416 0ustar www-datawww-datamodule RHC module Rest class Activation < Base define_attr :created_at def <=>(other) other.created_at <=> created_at end end end end rhc-1.38.7/lib/rhc/rest/api.rb0000644000004100000410000000602012756270551016023 0ustar www-datawww-datamodule RHC module Rest class Api < Base attr_reader :server_api_versions, :client_api_versions def initialize(client, preferred_api_versions=[]) super(nil, client) # API version negotiation @server_api_versions = [] debug "Client supports API versions #{preferred_api_versions.join(', ')}" @client_api_versions = preferred_api_versions @server_api_versions, @current_api_version, links = api_info({ :url => client.url, :method => :get, :accept => :json, :no_auth => !client.api_always_auth }) debug "Server supports API versions #{@server_api_versions.join(', ')}" if api_version_negotiated debug " Using API version #{api_version_negotiated}" unless client_api_version_current? debug "Client API version #{api_version_negotiated} is not current. Refetching API" # need to re-fetch API @server_api_versions, @current_api_version, links = api_info({ :url => client.url, :method => :get, :accept => :json, :api_version => api_version_negotiated, :no_auth => !client.api_always_auth }) end else warn_about_api_versions end attributes['links'] = links rescue RHC::Rest::ResourceNotFoundException => e raise ApiEndpointNotFound.new( "The OpenShift server is not responding correctly. Check "\ "that '#{client.url}' is the correct URL for your server. "\ "The server may be offline or misconfigured.") end ### API version related methods def api_version_match? ! api_version_negotiated.nil? end # return the API version that the server and this client can agree on def api_version_negotiated client_api_versions.reverse. # choose the last API version listed detect { |v| @server_api_versions.include? v } end def client_api_version_current? current_api_version == api_version_negotiated end def current_api_version @current_api_version end def warn_about_api_versions if !api_version_match? warn "WARNING: API version mismatch. This client supports #{client_api_versions.join(', ')} but server at #{URI.parse(client.url).host} supports #{@server_api_versions.join(', ')}." warn "The client version may be outdated; please consider updating 'rhc'. We will continue, but you may encounter problems." end end protected include RHC::Helpers private # execute +req+ with RestClient, and return [server_api_versions, links] def api_info(req) client.request(req) do |response| json_response = ::RHC::Json.decode(response.content) [ json_response['supported_api_versions'], json_response['api_version'] || json_response['version'].to_f, json_response['data'] ] end end end end end rhc-1.38.7/lib/rhc/rest/user.rb0000644000004100000410000000136112756270551016233 0ustar www-datawww-datarequire 'ostruct' module RHC module Rest class User < Base define_attr :id, :login, :plan_id, :max_gears, :consumed_gears, :max_domains def add_key(name, content, type) debug "Add key #{name} of type #{type} for user #{login}" rest_method "ADD_KEY", :name => name, :type => type, :content => content end def keys debug "Getting all keys for user #{login}" rest_method "LIST_KEYS" end #Find Key by name def find_key(name) keys.detect { |key| key.name == name } end def max_domains attributes['max_domains'] || 1 end def capabilities @capabilities ||= OpenStruct.new attribute('capabilities') end end end end rhc-1.38.7/lib/rhc/rest/httpclient.rb0000644000004100000410000000752612756270551017444 0ustar www-datawww-datarequire 'httpclient' class HTTPClient class SSPINegotiateAuth def get_with_rescue(*args) get_without_rescue(*args) rescue unless @warned @warned = true RHC::Helpers.warn "Could not enable Kerberos authentication" RHC::Helpers.warn $!.message.sub('gss_init_sec_context did not return GSS_S_COMPLETE: Unspecified GSS failure. Minor code may provide more information', '').strip rescue nil end nil end alias_method :get_without_rescue, :get if method_defined? :get alias_method :get, :get_with_rescue end end module RHC module Rest # # An instance of HTTPClient that will support deferred # Basic credentials and allow token challenges. # class HTTPClient < ::HTTPClient def initialize(*args) super @www_auth = WWWAuth.new @request_filter = [proxy_auth, www_auth] end end # # Support three altered authentication behaviors # # * Allow a bearer token to be provided for a server # * Allow the user and password attributes to be lazily # evaluated when the credentials are needed, rather than # up front. # * If a BASIC auth request has been rejected, do not # retry. # class WWWAuth < HTTPClient::WWWAuth attr_reader :oauth2 def initialize super @oauth2 = OAuth2.new @authenticator.unshift(@oauth2) deferred = DeferredBasic.new @authenticator.map!{ |o| o == @basic_auth ? deferred : o } @basic_auth = deferred end class OAuth2 include ::HTTPClient::Util attr_reader :scheme def initialize @cred = nil @auth = {} @set = false @scheme = "Bearer" end def reset_challenge end def set(uri, user, password) @set = true if uri.nil? @cred = password else @auth[urify(uri)] = password end end def set_token(uri, token) set(uri, nil, token) end def set? @set == true end def get(req) target_uri = req.header.request_uri return @cred if @cred hash_find_value(@auth) { |uri, cred| uri_part_of(target_uri, uri) } end def challenge(uri, param_str = nil) false end end class DeferredCredential def initialize(user, password) @user, @password = user, password end def user (@user.call if @user.respond_to?(:call)) or @user end def passwd (@password.call if @password.respond_to?(:call)) or @password end # # Pretend to be a string # def to_str ["#{user}:#{passwd}"].pack('m').tr("\n", '') end [:sub].each do |sym| define_method(sym) { |*args|; to_str.send(sym, *args); } end end class DeferredBasic < ::HTTPClient::BasicAuth # Set authentication credential. # uri == nil for generic purpose (allow to use user/password for any URL). def set(uri, user, passwd) @set = true if uri.nil? @cred = DeferredCredential.new(user, passwd) else uri = uri_dirname(urify(uri)) @auth[uri] = DeferredCredential.new(user, passwd) end end def challenge(uri, param_str = nil) return false if caller.any?{ |s| s =~ /webmock.*httpclient_adapter.*build_request_signature/ } uri = urify(uri) # httpclient < 2.4.0 uses @challengeable, >= 2.4.0 uses @challenge challenges = @challenge || @challengeable challenged = challenges[uri] challenges[uri] = true !challenged end end end end endrhc-1.38.7/lib/rhc/rest/key.rb0000644000004100000410000000205712756270551016050 0ustar www-datawww-datamodule RHC module Rest class Key < Base define_attr :name, :type, :content def is_ssh? type != 'krb5-principal' end def is_kerberos? type == 'krb5-principal' end def update(type, content) debug "Updating key #{self.name}" rest_method "UPDATE", :type => type, :content => content end def destroy debug "Deleting key #{self.name}" rest_method "DELETE" end alias :delete :destroy def principal content if is_kerberos? end def fingerprint @fingerprint ||= begin public_key = Net::SSH::KeyFactory.load_data_public_key("#{type} #{content}") public_key.fingerprint rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError 'Invalid key' end if is_ssh? end def visible_to_ssh? is_ssh? and Net::SSH::Authentication::Agent.connect.identities. find{ |i| fingerprint == i.fingerprint }.present? rescue false end end end end rhc-1.38.7/lib/rhc/rest/region.rb0000644000004100000410000000077712756270551016552 0ustar www-datawww-datamodule RHC::Rest class Region < Base define_attr :id, :name, :description, :default, :allow_selection def default? !!default end def allow_selection? !!allow_selection end def uuid client.api_version_negotiated >= 1.6 ? attributes['id'] : attributes['uuid'] end def zones @zones ||= attributes['zones'].map{|z| z['name']}.sort end def <=>(other) return self.name <=> other.name end def to_s self.name end end end rhc-1.38.7/lib/rhc/rest/alias.rb0000644000004100000410000000231312756270551016344 0ustar www-datawww-datamodule RHC module Rest class Alias < Base define_attr :id, :has_private_ssl_certificate, :certificate_added_at def has_private_ssl_certificate? has_private_ssl_certificate end def destroy debug "Deleting alias #{self.id}" rest_method :delete end alias :delete :destroy def add_certificate(ssl_certificate_content, private_key_content, pass_phrase) debug "Running add_certificate for alias #{@id}" raise RHC::Rest::SslCertificatesNotSupported, "The server does not support SSL certificates for custom aliases." unless supports? :update foo = rest_method :update, { :ssl_certificate => ssl_certificate_content, :private_key => private_key_content, :pass_phrase => pass_phrase } end def delete_certificate debug "Running delete_certificate for alias #{@id}" raise RHC::Rest::SslCertificatesNotSupported, "The server does not support SSL certificates for custom aliases." unless supports? :update rest_method :update, {} end def <=>(a) return self.name <=> a.name end def to_s self.id end end end endrhc-1.38.7/lib/rhc/rest/attributes.rb0000644000004100000410000000134112756270551017441 0ustar www-datawww-datamodule RHC::Rest::Attributes def attributes @attributes end def attributes=(attr=nil) @attributes = (attr || {}).stringify_keys! end def attribute(name) instance_variable_get("@#{name}") || attributes[name.to_s] end def clear_attribute(name) v = instance_variable_set("@#{name}", nil) attributes.delete(name.to_s) or v end end module RHC::Rest::AttributesClass def define_attr(*names) names.map(&:to_sym).each do |name| define_method(name) do attribute(name) end define_method("#{name}=") do |value| instance_variable_set(:"@#{name}", nil) attributes[name.to_s] = value end end end def model_name name.split("::").last end end rhc-1.38.7/lib/rhc/rest/base.rb0000644000004100000410000000411512756270551016167 0ustar www-datawww-datarequire 'uri' module RHC module Rest class Base include Attributes extend AttributesClass define_attr :messages URI_ESCAPE_REGEX = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]") def initialize(attrs=nil, client=nil) @attributes = (attrs || {}).stringify_keys! @attributes['messages'] ||= [] @client = client end def add_message(msg) messages << msg end def rest_method(link_name, payload={}, options={}) link = link(link_name) raise "No link defined for #{link_name}" unless link url = link['href'] url = url.gsub(/:\w+/) { |s| URI.escape(options[:params][s], URI_ESCAPE_REGEX) || s } if options[:params] method = options[:method] || link['method'] result = client.request(options.merge({ :url => url, :method => method, :payload => payload, })) if result.is_a?(Hash) && (result['messages'] || result['errors']) attributes['messages'] = Array(result['messages']) result = self end result end def links attributes['links'] || {} end def supports?(sym) !!link(sym) end def has_param?(sym, name) if l = link(sym) (l['required_params'] || []).any?{ |p| p['name'] == name} or (l['optional_params'] || []).any?{ |p| p['name'] == name} end end def link_href(sym, params=nil, resource=nil, &block) if (l = link(sym)) && (h = l['href']) h = h.gsub(/:\w+/){ |s| params[s].nil? ? s : URI.escape(params[s], URI_ESCAPE_REGEX) } if params h = "#{h}/#{resource}" if resource return h end yield if block_given? end protected attr_reader :client def link(sym) (links[sym.to_s] || links[sym.to_s.upcase]) end def debug(msg, obj=nil) client.debug("#{msg}#{obj ? " #{obj}" : ''}") if client && client.debug? end def debug? client && client.debug? end end end endrhc-1.38.7/lib/rhc/rest/authorization.rb0000644000004100000410000000032112756270551020150 0ustar www-datawww-datamodule RHC module Rest class Authorization < Base define_attr :token, :note, :expires_in, :expires_in_seconds, :scopes, :created_at alias_method :creation_time, :created_at end end end rhc-1.38.7/lib/rhc/servers.rb0000644000004100000410000001334112756270551015772 0ustar www-datawww-datarequire 'yaml' require 'fileutils' require 'rhc/helpers' require 'rhc/server_helpers' module RHC class Server include RHC::ServerHelpers attr_accessor :hostname, :nickname, :login attr_accessor :use_authorization_tokens, :insecure, :timeout attr_accessor :ssl_version, :ssl_client_cert_file, :ssl_client_key_file, :ssl_ca_file attr_accessor :default attr_accessor :persisted def self.from_yaml_hash(hash) hash.symbolize_keys! Server.new(hash.delete(:hostname), hash.merge(:persisted => true)) end def initialize(hostname, args={}) @hostname = RHC::Servers.to_host(hostname) @nickname = args[:nickname] @login = args[:login] @use_authorization_tokens = RHC::Helpers.to_boolean(args[:use_authorization_tokens], true) @insecure = RHC::Helpers.to_boolean(args[:insecure], true) @timeout = Integer(args[:timeout]) if args[:timeout].present? @ssl_version = RHC::Helpers.parse_ssl_version(args[:ssl_version]) @ssl_client_cert_file = args[:ssl_client_cert_file] @ssl_client_key_file = args[:ssl_client_key_file] @ssl_ca_file = args[:ssl_ca_file] @default = args[:default] @persisted = args[:persisted] end def default? !!@default end def persisted? !!@persisted end def designation @nickname || @hostname end def to_yaml_hash {}.tap do |h| instance_variables.each do |k| h[k.to_s.delete('@')] = instance_variable_get(k) end end.reject{|k, v| v.nil? || k == 'default' || k == 'persisted'}.inject({}){|h, (k, v)| h[k] = v.is_a?(String) || v.is_a?(Symbol) ? v.to_s : v; h } end def to_config RHC::Vendor::ParseConfig.new.tap do |config| h = to_yaml_hash h['default_rhlogin'] = h.delete('login') h['libra_server'] = h.delete('hostname') h.each{|k, v| config.add(k, v)} end end def to_s @nickname ? "#{@nickname} (#{@hostname})" : @hostname end def <=>(other) designation <=> other.designation end end class Servers include RHC::ServerHelpers attr_reader :servers def initialize(config=nil) @servers ||= load || [] sync_from_config(config) end def reload(config=nil) @servers = load || [] sync_from_config(config) self end def path File.join(RHC::Config.home_dir, '.openshift', "#{ENV['OPENSHIFT_SERVERS'].presence || 'servers'}.yml") end def present? File.exists?(path) end def self.to_host(hostname) uri = RHC::Helpers.to_uri(hostname) uri.scheme == 'https' && uri.port == URI::HTTPS::DEFAULT_PORT ? uri.host : hostname end def add(hostname, args={}) raise RHC::ServerHostnameExistsException.new(hostname) if hostname_exists?(hostname) raise RHC::ServerNicknameExistsException.new(args[:nickname]) if args[:nickname] && nickname_exists?(args[:nickname]) args[:nickname] = suggest_server_nickname(Servers.to_host(hostname)) unless args[:nickname].present? Server.new(hostname, args).tap{ |server| @servers << server } end def update(server, args={}) find(server).tap do |s| args.each do |k, v| s.send("#{k}=", v) unless v.nil? end end end def add_or_update(hostname, args={}) update(hostname, args) rescue add(hostname, args) end def remove(server) @servers.delete(find(server)) end def list @servers || [] end def find(server) exists?(server).tap{|s| raise RHC::ServerNotConfiguredException.new(server) unless s } end def nickname_exists?(nickname) list.select{|s| s.nickname.present? && s.nickname == nickname}.first end def hostname_exists?(hostname) hostname = Servers.to_host(hostname) list.select{|s| s.hostname == hostname}.first end def exists?(server) hostname_exists?(server) || nickname_exists?(server) end def default list.select(&:default?).first || list.first end def sync_from_config(config) unless config.nil? || !config.has_configs_from_files? o = config.to_options add_or_update( o[:server], :login => o[:rhlogin], :use_authorization_tokens => o[:use_authorization_tokens], :insecure => o[:insecure], :timeout => o[:timeout], :ssl_version => o[:ssl_version], :ssl_client_cert_file => o[:ssl_client_cert_file], :ssl_client_key_file => o[:ssl_client_key_file], :ssl_ca_file => o[:ssl_ca_file]) list.each do |server| server.default = server.hostname == o[:server] server.persisted = true if !present? end end end def save! FileUtils.mkdir_p File.dirname(path) File.open(path, 'w') do |c| c.puts list.collect{|s| s.persisted = true; {'server' => s.to_yaml_hash}}.to_yaml end self end def backup FileUtils.cp(path, "#{path}.bak") if File.exists? path end protected def load (YAML.load_file(path) || [] rescue []).collect do |e| Server.from_yaml_hash e['server'] end end def suggest_server_nickname(hostname) suggestion = (case hostname when openshift_online_server_regex 'online' when /^(.*)\.#{openshift_online_server.gsub(/\./, '\.')}$/i $1 else 'server' + ((list.compact.map{|i| i.match(/^server(\d+)$/)}.compact.map{|i| i[1]}.map(&:to_i).max + 1).to_s rescue '1') end) s = nickname_exists?(suggestion) s.present? && s.hostname != hostname ? nil : suggestion end end end rhc-1.38.7/lib/rhc/vendor/0000755000004100000410000000000012756270551015247 5ustar www-datawww-datarhc-1.38.7/lib/rhc/vendor/okjson.rb0000644000004100000410000004022212756270551017077 0ustar www-datawww-data# encoding: UTF-8 # # Copyright 2011, 2012 Keith Rarick # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # See https://github.com/kr/okjson for updates. require 'stringio' # Some parts adapted from # http://golang.org/src/pkg/json/decode.go and # http://golang.org/src/pkg/utf8/utf8.go module RHC module Vendor module OkJson extend self # Decodes a json document in string s and # returns the corresponding ruby value. # String s must be valid UTF-8. If you have # a string in some other encoding, convert # it first. # # String values in the resulting structure # will be UTF-8. def decode(s) ts = lex(s) v, ts = textparse(ts) if ts.length > 0 raise Error, 'trailing garbage' end v end # Parses a "json text" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. # Note: this is almost the same as valparse, # except that it does not accept atomic values. def textparse(ts) if ts.length < 0 raise Error, 'empty' end typ, _, val = ts[0] case typ when '{' then objparse(ts) when '[' then arrparse(ts) else raise Error, "unexpected #{val.inspect}" end end # Parses a "value" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. def valparse(ts) if ts.length < 0 raise Error, 'empty' end typ, _, val = ts[0] case typ when '{' then objparse(ts) when '[' then arrparse(ts) when :val,:str then [val, ts[1..-1]] else raise Error, "unexpected #{val.inspect}" end end # Parses an "object" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. def objparse(ts) ts = eat('{', ts) obj = {} if ts[0][0] == '}' return obj, ts[1..-1] end k, v, ts = pairparse(ts) obj[k] = v if ts[0][0] == '}' return obj, ts[1..-1] end loop do ts = eat(',', ts) k, v, ts = pairparse(ts) obj[k] = v if ts[0][0] == '}' return obj, ts[1..-1] end end end # Parses a "member" in the sense of RFC 4627. # Returns the parsed values and any trailing tokens. def pairparse(ts) (typ, _, k), ts = ts[0], ts[1..-1] if typ != :str raise Error, "unexpected #{k.inspect}" end ts = eat(':', ts) v, ts = valparse(ts) [k, v, ts] end # Parses an "array" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. def arrparse(ts) ts = eat('[', ts) arr = [] if ts[0][0] == ']' return arr, ts[1..-1] end v, ts = valparse(ts) arr << v if ts[0][0] == ']' return arr, ts[1..-1] end loop do ts = eat(',', ts) v, ts = valparse(ts) arr << v if ts[0][0] == ']' return arr, ts[1..-1] end end end def eat(typ, ts) if ts[0][0] != typ raise Error, "expected #{typ} (got #{ts[0].inspect})" end ts[1..-1] end # Scans s and returns a list of json tokens, # excluding white space (as defined in RFC 4627). def lex(s) ts = [] while s.length > 0 typ, lexeme, val = tok(s) if typ == nil raise Error, "invalid character at #{s[0,10].inspect}" end if typ != :space ts << [typ, lexeme, val] end s = s[lexeme.length..-1] end ts end # Scans the first token in s and # returns a 3-element list, or nil # if s does not begin with a valid token. # # The first list element is one of # '{', '}', ':', ',', '[', ']', # :val, :str, and :space. # # The second element is the lexeme. # # The third element is the value of the # token for :val and :str, otherwise # it is the lexeme. def tok(s) case s[0] when ?{ then ['{', s[0,1], s[0,1]] when ?} then ['}', s[0,1], s[0,1]] when ?: then [':', s[0,1], s[0,1]] when ?, then [',', s[0,1], s[0,1]] when ?[ then ['[', s[0,1], s[0,1]] when ?] then [']', s[0,1], s[0,1]] when ?n then nulltok(s) when ?t then truetok(s) when ?f then falsetok(s) when ?" then strtok(s) when Spc then [:space, s[0,1], s[0,1]] when ?\t then [:space, s[0,1], s[0,1]] when ?\n then [:space, s[0,1], s[0,1]] when ?\r then [:space, s[0,1], s[0,1]] else numtok(s) end end def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end def numtok(s) m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s) if m && m.begin(0) == 0 if m[3] && !m[2] [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))] elsif m[2] [:val, m[0], Float(m[0])] else [:val, m[0], Integer(m[0])] end else [] end end def strtok(s) m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s) if ! m raise Error, "invalid string literal at #{abbrev(s)}" end [:str, m[0], unquote(m[0])] end def abbrev(s) t = s[0,10] p = t['`'] t = t[0,p] if p t = t + '...' if t.length < s.length '`' + t + '`' end # Converts a quoted json string literal q into a UTF-8-encoded string. # The rules are different than for Ruby, so we cannot use eval. # Unquote will raise an error if q contains control characters. def unquote(q) q = q[1...-1] a = q.dup # allocate a big enough string rubydoesenc = false # In ruby >= 1.9, a[w] is a codepoint, not a byte. if a.class.method_defined?(:force_encoding) a.force_encoding('UTF-8') rubydoesenc = true end r, w = 0, 0 while r < q.length c = q[r] case true when c == ?\\ r += 1 if r >= q.length raise Error, "string literal ends with a \"\\\": \"#{q}\"" end case q[r] when ?",?\\,?/,?' a[w] = q[r] r += 1 w += 1 when ?b,?f,?n,?r,?t a[w] = Unesc[q[r]] r += 1 w += 1 when ?u r += 1 uchar = begin hexdec4(q[r,4]) rescue RuntimeError => e raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}" end r += 4 if surrogate? uchar if q.length >= r+6 uchar1 = hexdec4(q[r+2,4]) uchar = subst(uchar, uchar1) if uchar != Ucharerr # A valid pair; consume. r += 6 end end end if rubydoesenc a[w] = '' << uchar w += 1 else w += ucharenc(a, w, uchar) end else raise Error, "invalid escape char #{q[r]} in \"#{q}\"" end when c == ?", c < Spc raise Error, "invalid character in string literal \"#{q}\"" else # Copy anything else byte-for-byte. # Valid UTF-8 will remain valid UTF-8. # Invalid UTF-8 will remain invalid UTF-8. # In ruby >= 1.9, c is a codepoint, not a byte, # in which case this is still what we want. a[w] = c r += 1 w += 1 end end a[0,w] end # Encodes unicode character u as UTF-8 # bytes in string a at position i. # Returns the number of bytes written. def ucharenc(a, i, u) case true when u <= Uchar1max a[i] = (u & 0xff).chr 1 when u <= Uchar2max a[i+0] = (Utag2 | ((u>>6)&0xff)).chr a[i+1] = (Utagx | (u&Umaskx)).chr 2 when u <= Uchar3max a[i+0] = (Utag3 | ((u>>12)&0xff)).chr a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr a[i+2] = (Utagx | (u&Umaskx)).chr 3 else a[i+0] = (Utag4 | ((u>>18)&0xff)).chr a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr a[i+3] = (Utagx | (u&Umaskx)).chr 4 end end def hexdec4(s) if s.length != 4 raise Error, 'short' end (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3]) end def subst(u1, u2) if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3 return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself end return Ucharerr end def surrogate?(u) Usurr1 <= u && u < Usurr3 end def nibble(c) case true when ?0 <= c && c <= ?9 then c.ord - ?0.ord when ?a <= c && c <= ?z then c.ord - ?a.ord + 10 when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10 else raise Error, "invalid hex code #{c}" end end # Encodes x into a json text. It may contain only # Array, Hash, String, Numeric, true, false, nil. # (Note, this list excludes Symbol.) # X itself must be an Array or a Hash. # No other value can be encoded, and an error will # be raised if x contains any other value, such as # Nan, Infinity, Symbol, and Proc, or if a Hash key # is not a String. # Strings contained in x must be valid UTF-8. def encode(x) case x when Hash then objenc(x) when Array then arrenc(x) else raise Error, 'root value must be an Array or a Hash' end end def valenc(x) case x when Hash then objenc(x) when Array then arrenc(x) when String then strenc(x) when Numeric then numenc(x) when true then "true" when false then "false" when nil then "null" else raise Error, "cannot encode #{x.class}: #{x.inspect}" end end def objenc(x) '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}' end def arrenc(a) '[' + a.map{|x| valenc(x)}.join(',') + ']' end def keyenc(k) case k when String then strenc(k) else raise Error, "Hash key is not a string: #{k.inspect}" end end def strenc(s) t = StringIO.new t.putc(?") r = 0 # In ruby >= 1.9, s[r] is a codepoint, not a byte. rubydoesenc = s.class.method_defined?(:encoding) while r < s.length case s[r] when ?" then t.print('\\"') when ?\\ then t.print('\\\\') when ?\b then t.print('\\b') when ?\f then t.print('\\f') when ?\n then t.print('\\n') when ?\r then t.print('\\r') when ?\t then t.print('\\t') else c = s[r] case true when rubydoesenc begin c.ord # will raise an error if c is invalid UTF-8 t.write(c) rescue t.write(Ustrerr) end when Spc <= c && c <= ?~ t.putc(c) else n = ucharcopy(t, s, r) # ensure valid UTF-8 output r += n - 1 # r is incremented below end end r += 1 end t.putc(?") t.string end def numenc(x) if ((x.nan? || x.infinite?) rescue false) raise Error, "Numeric cannot be represented: #{x}" end "#{x}" end # Copies the valid UTF-8 bytes of a single character # from string s at position i to I/O object t, and # returns the number of bytes copied. # If no valid UTF-8 char exists at position i, # ucharcopy writes Ustrerr and returns 1. def ucharcopy(t, s, i) n = s.length - i raise Utf8Error if n < 1 c0 = s[i].ord # 1-byte, 7-bit sequence? if c0 < Utagx t.putc(c0) return 1 end raise Utf8Error if c0 < Utag2 # unexpected continuation byte? raise Utf8Error if n < 2 # need continuation byte c1 = s[i+1].ord raise Utf8Error if c1 < Utagx || Utag2 <= c1 # 2-byte, 11-bit sequence? if c0 < Utag3 raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max t.putc(c0) t.putc(c1) return 2 end # need second continuation byte raise Utf8Error if n < 3 c2 = s[i+2].ord raise Utf8Error if c2 < Utagx || Utag2 <= c2 # 3-byte, 16-bit sequence? if c0 < Utag4 u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx) raise Utf8Error if u <= Uchar2max t.putc(c0) t.putc(c1) t.putc(c2) return 3 end # need third continuation byte raise Utf8Error if n < 4 c3 = s[i+3].ord raise Utf8Error if c3 < Utagx || Utag2 <= c3 # 4-byte, 21-bit sequence? if c0 < Utag5 u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx) raise Utf8Error if u <= Uchar3max t.putc(c0) t.putc(c1) t.putc(c2) t.putc(c3) return 4 end raise Utf8Error rescue Utf8Error t.write(Ustrerr) return 1 end class Utf8Error < ::StandardError end class Error < ::StandardError end Utagx = 0x80 # 1000 0000 Utag2 = 0xc0 # 1100 0000 Utag3 = 0xe0 # 1110 0000 Utag4 = 0xf0 # 1111 0000 Utag5 = 0xF8 # 1111 1000 Umaskx = 0x3f # 0011 1111 Umask2 = 0x1f # 0001 1111 Umask3 = 0x0f # 0000 1111 Umask4 = 0x07 # 0000 0111 Uchar1max = (1<<7) - 1 Uchar2max = (1<<11) - 1 Uchar3max = (1<<16) - 1 Ucharerr = 0xFFFD # unicode "replacement char" Ustrerr = "\xef\xbf\xbd" # unicode "replacement char" Usurrself = 0x10000 Usurr1 = 0xd800 Usurr2 = 0xdc00 Usurr3 = 0xe000 Spc = ' '[0] Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t} end end endrhc-1.38.7/lib/rhc/vendor/parseconfig.rb0000644000004100000410000001262612756270551020103 0ustar www-datawww-data# modified version of parseconfig rubygem module # # Author:: BJ Dierkes # Copyright:: Copyright (c) 2006,2012 BJ Dierkes # License:: MIT # URL:: https://github.com/derks/ruby-parseconfig # # This class was written to simplify the parsing of configuration # files in the format of "param = value". Please review the # demo files included with this package. # # For further information please refer to the './doc' directory # as well as the ChangeLog and README files included. # # Note: A group is a set of parameters defined for a subpart of a # config file module RHC module Vendor class ParseConfig Version = '1.0.2' attr_accessor :config_file, :params, :groups # Initialize the class with the path to the 'config_file' # The class objects are dynamically generated by the # name of the 'param' in the config file. Therefore, if # the config file is 'param = value' then the itializer # will eval "@param = value" # def initialize(config_file=nil) @config_file = config_file @params = {} @groups = [] if(self.config_file) self.validate_config() self.import_config() end end # Validate the config file, and contents def validate_config() if !File.readable?(self.config_file) raise Errno::EACCES, "#{self.config_file} is not readable" end # FIX ME: need to validate contents/structure? end # Import data from the config to our config object. def import_config() # The config is top down.. anything after a [group] gets added as part # of that group until a new [group] is found. group = nil File.open(self.config_file) { |f| f.each do |line| line.strip! unless (/^\#/.match(line)) if(/\s*=\s*/.match(line)) param, value = line.split(/\s*=\s*/, 2) var_name = "#{param}".chomp.strip value = value.chomp.strip new_value = '' if (value) if value =~ /^['"](.*)['"]$/ new_value = $1 else new_value = value end else new_value = '' end if group self.add_to_group(group, var_name, new_value) else self.add(var_name, new_value) end elsif(/^\[(.+)\]$/.match(line).to_a != []) group = /^\[(.+)\]$/.match(line).to_a[1] self.add(group, {}) end end end } end # This method will provide the value held by the object "@param" # where "@param" is actually the name of the param in the config # file. # # DEPRECATED - will be removed in future versions # def get_value(param) puts "ParseConfig Deprecation Warning: get_value() is deprecated. Use " + \ "config['param'] or config['group']['param'] instead." return self.params[param] end # This method is a shortcut to accessing the @params variable def [](param) return self.params[param] end # This method returns all parameters/groups defined in a config file. def get_params() return self.params.keys end # List available sub-groups of the config. def get_groups() return self.groups end # This method adds an element to the config object (not the config file) # By adding a Hash, you create a new group def add(param_name, value) if value.class == Hash if self.params.has_key?(param_name) if self.params[param_name].class == Hash self.params[param_name].merge!(value) elsif self.params.has_key?(param_name) if self.params[param_name].class != value.class raise ArgumentError, "#{param_name} already exists, and is of different type!" end end else self.params[param_name] = value end if ! self.groups.include?(param_name) self.groups.push(param_name) end else self.params[param_name] = value end end # Add parameters to a group. Note that parameters with the same name # could be placed in different groups def add_to_group(group, param_name, value) if ! self.groups.include?(group) self.add(group, {}) end self.params[group][param_name] = value end # Writes out the config file to output_stream def write(output_stream=STDOUT) self.params.each do |name,value| if value.class.to_s != 'Hash' if value.scan(/\w+/).length > 1 output_stream.puts "#{name} = \"#{value}\"" else output_stream.puts "#{name} = #{value}" end end end output_stream.puts "\n" self.groups.each do |group| output_stream.puts "[#{group}]" self.params[group].each do |param, value| if value.scan(/\w+/).length > 1 output_stream.puts "#{param} = \"#{value}\"" else output_stream.puts "#{param} = #{value}" end end output_stream.puts "\n" end end end end end rhc-1.38.7/lib/rhc/vendor/zliby.rb0000644000004100000410000004452112756270551016733 0ustar www-datawww-datarequire 'stringio' module RHC module Vendor module Zlib ZLIBY_VERSION = "0.0.5" ZLIB_VERSION = "1.2.3" VERSION = "0.6.0" #For compatibility with Ruby-core Zlib MAXBITS = 15 MAXLCODES = 286 MAXDCODES = 30 MAXCODES = (MAXLCODES+MAXDCODES) FIXLCODES = 288 MAX_WBITS = 15 Z_DEFLATED = 8 def self.adler32 string="", adler=1 if adler > (2**128) - 1 then raise RangeError.new end accum1 = adler & 0xffff accum2 = (adler >> 16) & 0xffff len = string.length x = -1 while len > 0 tlen = len > 5552 ? 5552 : len len -= tlen while tlen >0 x += 1 accum1 += string[x] accum2 += accum1 tlen -= 1 end accum1 %= 65521 accum2 %= 65521 end accum2 << 16 | accum1 end def self.crc_table [0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035, 249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049, 498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639, 325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317, 997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, 901097722, 1119000684, 3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665, 651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303, 671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565, 1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059, 2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297, 1802195444, 476864866, 2238001368, 4066508878, 1812370925, 453092731, 2181625025, 4111451223, 1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405, 1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995, 1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649, 1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015, 1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989, 3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, 3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377, 4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879, 4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637, 3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859, 3624741850, 2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135, 1181335161, 3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815, 3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221, 2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371, 2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881, 2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115, 1873836001, 414664567, 2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701, 2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035, 2932959818, 3654703836, 1088359270, 936918000, 2847714899, 3736837829, 1202900863, 817233897, 3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431, 3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117] end def self.crc32 string="", crc=0 if crc > 2**128 - 1 then raise RangeError.new end crc = crc ^ 0xffffffff string.each_byte do |byte| index = (crc ^ byte) & 0xff crc = (crc >> 8) ^ crc_table[index] end crc ^ 0xffffffff end class ZStream def initialize @input_buffer = [] @output_buffer = [] @out_pos = -1 @in_pos = -1 @bit_bucket = 0 @bit_count = 0 end #Returns the adler-32 checksum of the input data. def adler end #Returns the number of bytes read. Normally 0 since all bytes are read at once. def avail_in @input_buffer.length - @in_pos end #Returns number of free bytes in the output buffer. As the output buffer is self expanding this normally returns 0. def avail_out @output_buffer.length - @out_pos end #Allocates size bytes in output buffer. If size < avail_out it truncates the buffer. def avail_out= size size.times do if size > avail_out @output_buffer.push nil else @output_buffer.pop end end end #Closes stream. Further operations will raise Zlib::StreamError def close @closed = true end #True if stream closed, otherwise False. def closed? @closed end #Best guess of input data, one of Zlib::BINARY, Zlib::ASCII, or Zlib::UNKNOWN def data_type end #See close def end close end #See closed? def ended? closed? end #Finishes the stream, flushes output buffer, implemented by child classes def finish close end #True if stream is finished, otherwise False def finished? if @finished.nil? then false else @finished end end #Flushes input buffer and returns the data therein. def flush_next_in @in_pos = @input_buffer.length @finished = true ret = @input_buffer.pack("c*") @input_buffer = [] ret end #Flushes the output buffer and returns all the data def flush_next_out @out_pos = @output_buffer.length @finished = true ret = @output_buffer.pack("c*") @output_buffer = [] ret end #Reset stream. Input and Output buffers are reset. def reset @out_pos = -1 @in_pos = -1 @input_buffer = [] @output_buffer = [] end #See finished. def stream_end? finished? end #Size of input buffer. def total_in @input_buffer.length end #Size of output buffer. def total_out @output_buffer.length end private #returns need bits from the input buffer # == Format Notes # bits are stored LSB to MSB def get_bits need val = @bit_bucket while @bit_count < need val |= (@input_buffer[@in_pos+=1] << @bit_count) @bit_count += 8 end @bit_bucket = val >> need @bit_count -= need val & ((1 << need) - 1) end public end #== DEFLATE Decompression #Implements decompression of a RFC-1951[ftp://ftp.rfc-editor.org/in-notes/rfc1951.txt] compatible stream. class Inflate < ZStream def initialize window_bits=MAX_WBITS @w_bits = window_bits if @w_bits < 0 then @rawdeflate = true @w_bits *= -1 end super() @zstring = "" end #Appends data to the input stream def <<(string) @zstring << string inflate end #Sets the inflate dictionary def set_dictionary string @dict = string reset end #==Example # f = File.open "example.z" # i = Inflate.new # i.inflate f.read def inflate zstring=nil @zstring = zstring unless zstring.nil? #We can't use unpack, IronRuby doesn't have it yet. @zstring.each_byte {|b| @input_buffer << b} unless @rawdeflate then compression_method_and_flags = @input_buffer[@in_pos+=1] flags = @input_buffer[@in_pos+=1] #CMF and FLG, when viewed as a 16-bit unsigned integer stored inMSB order (CMF*256 + FLG), is a multiple of 31 if ((compression_method_and_flags << 0x08) + flags) % 31 != 0 then raise Zlib::DataError.new("incorrect header check") end #CM = 8 denotes the "deflate" compression method with a window size up to 32K. (RFC's only specify CM 8) compression_method = compression_method_and_flags & 0x0F if compression_method != Z_DEFLATED then raise Zlib::DataError.new("unknown compression method") end #For CM = 8, CINFO is the base-2 logarithm of the LZ77 window size,minus eight (CINFO=7 indicates a 32K window size) compression_info = compression_method_and_flags >> 0x04 if (compression_info + 8) > @w_bits then raise Zlib::DataError.new("invalid window size") end preset_dictionary_flag = ((flags & 0x20) >> 0x05) == 1 compression_level = (flags & 0xC0) >> 0x06 if preset_dictionary_flag and @dict.nil? then raise Zlib::NeedDict.new "Preset dictionary needed!" end #TODO: Add Preset dictionary support if preset_dictionary_flag then @dict_crc = @input_buffer[@in_pos+=1] << 24 | @input_buffer[@in_pos+=1] << 16 | @input_buffer[@in_pos+=1] << 8 | @input_buffer[@in_pos+=1] end end last_block = false #Begin processing DEFLATE stream until last_block last_block = (get_bits(1) == 1) block_type = get_bits(2) case block_type when 0 then no_compression when 1 then fixed_codes when 2 then dynamic_codes when 3 then raise Zlib::DataError.new("invalid block type") end end finish end #Finishes inflating and flushes the buffer def finish output = "" inflate unless @output_buffer.length > 0 @output_buffer.each {|c| output << c } super output end private def no_compression @bit_bucket = 0 @bit_count = 0 if @in_pos + 4 > @input_buffer.length then raise Zlib::DataError.new("not enough input to read length code") end length = @input_buffer[@in_pos+=1] | (@input_buffer[@in_pos+=1] << 8) if (~length & 0xff != @input_buffer[@in_pos+=1]) || (((~length >> 8) & 0xff) != @input_buffer[@in_pos+=1]) then raise Zlib::DataError.new("invalid stored block lengths") end if @in_pos + length > @input_buffer.length then raise Zlib::DataError.new("ran out of input") end length.times do @output_buffer[@out_pos += 1] = @input_buffer[@in_pos += 1] end end def fixed_codes if @fixed_length_codes.nil? && @fixed_distance_codes.nil? then generate_huffmans end codes @fixed_length_codes, @fixed_distance_codes end def dynamic_codes order = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15] nlen = get_bits(5) + 257 ndist = get_bits(5) + 1 ncode = get_bits(4) + 4 lengths=[] dynamic_length_codes = Zlib::Inflate::HuffmanTree.new dynamic_distance_codes = Zlib::Inflate::HuffmanTree.new if (nlen > MAXLCODES || ndist > MAXDCODES) then raise Zlib::DataError.new("too many length or distance codes") end idx = 0 while idx < ncode lengths[order[idx]] = get_bits(3) idx += 1 end while idx < 19 lengths[order[idx]] = 0 idx += 1 end err = construct_tree dynamic_length_codes, lengths, 18 if err != 0 then raise Zlib::DataError.new("code lengths codes incomplete") end idx = 0 while idx < (nlen + ndist) symbol = decode(dynamic_length_codes) if symbol < 16 then lengths[idx] = symbol idx += 1; else len = 0 if symbol == 16 then if idx == 0 then raise Zlib::DataError.new("repeat lengths with no first length") end len = lengths[idx - 1] symbol = 3 + get_bits(2) elsif symbol == 17 then symbol = 3 + get_bits(3) elsif symbol == 18 then symbol = 11 + get_bits(7) else raise Zlib::DataError.new("invalid repeat length code") end if (idx + symbol) > (nlen + ndist) then raise Zlib::DataError.new("repeat more than specified lengths") end until symbol == 0 lengths[idx] = len idx+=1 symbol -= 1 end end end err = construct_tree dynamic_length_codes, lengths, nlen-1 if err < 0 || (err > 0 && (nlen - dynamic_length_codes.count[0] != 1)) then raise Zlib::DataError.new("invalid literal/length code lengths") end nlen.times { lengths.delete_at 0 } #We do this since we don't have pointer arithmetic in ruby err = construct_tree dynamic_distance_codes, lengths, ndist-1 if err < 0 || (err > 0 && (ndist - dynamic_distance_codes.count[0] != 1)) then raise Zlib::DataError.new("invalid distance code lengths") end codes dynamic_length_codes, dynamic_distance_codes end def generate_huffmans lengths = [] #literal/length table for idx in (000..143) lengths[idx] = 8 end for idx in (144..255) lengths[idx] = 9 end for idx in (256..279) lengths[idx] = 7 end for idx in (280..287) lengths[idx] = 8 end @fixed_length_codes = Zlib::Inflate::HuffmanTree.new construct_tree @fixed_length_codes, lengths, 287 for idx in (00..29) lengths[idx] = 5 end @fixed_distance_codes = Zlib::Inflate::HuffmanTree.new construct_tree @fixed_distance_codes, lengths, 29 end def codes length_codes, distance_codes lens = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258] lext = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0] dists = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577] dext = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13] symbol = 0 until symbol == 256 symbol = decode(length_codes) if symbol < 0 then return symbol end if symbol < 256 then @output_buffer[@out_pos+=1] = symbol end if symbol > 256 then symbol -= 257 if symbol >= 29 then raise Zlib::DataError.new("invalid literal/length or distance code in fixed or dynamic block") end len = lens[symbol] + get_bits(lext[symbol]) symbol = decode(distance_codes) if symbol < 0 then return symbol end dist = dists[symbol] + get_bits(dext[symbol]) if dist > @output_buffer.length then raise Zlib::DataError.new("distance is too far back in fixed or dynamic block") end while len > 0 @output_buffer[@out_pos+=1] = @output_buffer[@out_pos - dist] len -= 1 end end end return 0 end def decode huffman_tree code = 0 first = 0 index = 0 for len in (1..15) code |= get_bits(1) count = huffman_tree.count[len] if code < (first + count) then return huffman_tree.symbol[index + (code - first)] end index += count first += count first <<= 1 code <<= 1 end -9 end def construct_tree huffman_tree, lengths, n_symbols offs = [] for len in (000..MAXBITS) huffman_tree.count[len] = 0 end for symbol in (000..n_symbols) huffman_tree.count[lengths[symbol]] += 1 end if huffman_tree.count[0] == n_symbols then return 0 end left = 1 for len in (1..MAXBITS) left <<= 1 left -= huffman_tree.count[len]; if left < 0 then return left end end offs[1] = 0 for len in (1..(MAXBITS-1)) offs[len+1] = offs[len] + huffman_tree.count[len] end for symbol in (0..n_symbols) if lengths[symbol] != 0 then huffman_tree.symbol[offs[lengths[symbol]]] = symbol offs[lengths[symbol]] += 1 end end left end class HuffmanTree attr_accessor :count, :symbol def initialize @count = [] @symbol = [] end end class << self def inflate zstring d = self.new d.inflate zstring end end end class GzipFile def initialize @input_buffer = [] @output_buffer = [] @out_pos = -1 @in_pos = -1 end def close end class Error < Exception end end class GzipReader < GzipFile OSES = ['FAT filesystem', 'Amiga', 'VMS (or OpenVMS)', 'Unix', 'VM/CMS', 'Atari TOS', 'HPFS fileystem (OS/2, NT)', 'Macintosh', 'Z-System', 'CP/M', 'TOPS-20', 'NTFS filesystem (NT)', 'QDOS', 'Acorn RISCOS', 'unknown'] def initialize io #Add a helper method to check bits ::Fixnum.module_eval do def isbitset? bit_to_check if self & (2 ** bit_to_check) == (2 ** bit_to_check) then true else false end end end super() @io = io io.read.each_byte {|b| @input_buffer << b} if @input_buffer[@in_pos+=1] != 0x1f || @input_buffer[@in_pos+=1] != 0x8b then raise Zlib::GzipFile::Error.new("not in gzip format") end if @input_buffer[@in_pos+=1] != 0x08 then raise Zlib::GzipFile::Error.new("unknown compression method") end flg = @input_buffer[@in_pos+=1] @ftext = flg.isbitset? 0 @fhcrc = flg.isbitset? 1 @fextra = flg.isbitset? 2 @fname = flg.isbitset? 3 @fcomment = flg.isbitset? 4 @mtime = Time.at(@input_buffer[@in_pos+=1] | (@input_buffer[@in_pos+=1] << 8) | (@input_buffer[@in_pos+=1] << 16) | (@input_buffer[@in_pos+=1] << 24)) @xfl = @input_buffer[@in_pos+=1] @os = OSES[@input_buffer[@in_pos+=1]] if @fextra then @xlen = (@input_buffer[@in_pos+=1] | (@input_buffer[@in_pos+=1] << 8)) @xtra_field = [] @xlen.times {@xtra_field << @input_buffer[@in_pos+=1]} end if @fname then @original_name = "" until @original_name["\0"].nil? == false @original_name.concat(@input_buffer[@in_pos+=1]) end @original_name.chop! end if @fcomment then @comment = "" until @comment["\0"].nil? == false @comment.concat(@input_buffer[@in_pos+=1]) end @comment.chop! end if @fhcrc then @header_crc = @input_buffer[@in_pos+=1] | (@input_buffer[@in_pos+=1] << 8) end @contents = "" until @in_pos == @input_buffer.length-1 @contents.concat(@input_buffer[@in_pos+=1]) end #we do raw deflate, no headers @zstream = Zlib::Inflate.new -MAX_WBITS @inflated = StringIO.new(@zstream.inflate @contents) end def read length=nil @inflated.read length end def eof? @inflated.eof? end def pos @inflated.pos end def rewind @inflated.rewind @io.seek 0, IO::SEEK_SET end class << self def open filename io = File.open filename, 'rb' gz = self.new io if block_given? then yield gz else gz end end end end #Generic Error class Error < Exception end #Dictionary Needed class NeedDict < Error end #Invalid Data class DataError < Error end end end end rhc-1.38.7/lib/rhc/vendor/sshkey.rb0000644000004100000410000002077712756270551017117 0ustar www-datawww-data# modified version of SSHKey rubygem module # https://github.com/bensie/sshkey # # Copyright (c) 2011 James Miller # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. require 'openssl' require 'base64' require 'digest/md5' require 'digest/sha1' module RHC module Vendor class SSHKey SSH_TYPES = {"rsa" => "ssh-rsa", "dsa" => "ssh-dss"} SSH_CONVERSION = {"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"]} attr_reader :key_object, :comment, :type attr_accessor :passphrase class << self # Generate a new keypair and return an SSHKey object # # The default behavior when providing no options will generate a 2048-bit RSA # keypair. # # ==== Parameters # * options<~Hash>: # * :type<~String> - "rsa" or "dsa", "rsa" by default # * :bits<~Integer> - Bit length # * :comment<~String> - Comment to use for the public key, defaults to "" # * :passphrase<~String> - Encrypt the key with this passphrase # def generate(options = {}) type = options[:type] || "rsa" # JRuby modulus size must range from 512 to 1024 default_bits = type == "rsa" ? 2048 : 1024 bits = options[:bits] || default_bits cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC") if options[:passphrase] case type.downcase when "rsa" then new(OpenSSL::PKey::RSA.generate(bits).to_pem(cipher, options[:passphrase]), options) when "dsa" then new(OpenSSL::PKey::DSA.generate(bits).to_pem(cipher, options[:passphrase]), options) else raise "Unknown key type: #{type}" end end # Validate an existing SSH public key # # Returns true or false depending on the validity of the public key provided # # ==== Parameters # * ssh_public_key<~String> - "ssh-rsa AAAAB3NzaC1yc2EA...." # def valid_ssh_public_key?(ssh_public_key) ssh_type, encoded_key = ssh_public_key.split(" ") type = SSH_TYPES.invert[ssh_type] prefix = [0,0,0,7].pack("C*") decoded = Base64.decode64(encoded_key) # Base64 decoding is too permissive, so we should validate if encoding is correct return false unless Base64.encode64(decoded).gsub("\n", "") == encoded_key return false unless decoded.sub!(/^#{prefix}#{ssh_type}/, "") unpacked = decoded.unpack("C*") data = [] index = 0 until unpacked[index].nil? datum_size = from_byte_array unpacked[index..index+4-1], 4 index = index + 4 datum = from_byte_array unpacked[index..index+datum_size-1], datum_size data << datum index = index + datum_size end SSH_CONVERSION[type].size == data.size rescue false end # Fingerprints # # Accepts either a public or private key # # MD5 fingerprint for the given SSH key def md5_fingerprint(key) if key.match(/PRIVATE/) new(key).md5_fingerprint else Digest::MD5.hexdigest(decoded_key(key)).gsub(fingerprint_regex, '\1:\2') end end alias_method :fingerprint, :md5_fingerprint # SHA1 fingerprint for the given SSH key def sha1_fingerprint(key) if key.match(/PRIVATE/) new(key).sha1_fingerprint else Digest::SHA1.hexdigest(decoded_key(key)).gsub(fingerprint_regex, '\1:\2') end end private def from_byte_array(byte_array, expected_size = nil) num = 0 raise "Byte array too short" if !expected_size.nil? && expected_size != byte_array.size byte_array.reverse.each_with_index do |item, index| num += item * 256**(index) end num end def decoded_key(key) Base64.decode64(key.chomp.gsub(/ssh-[dr]s[as] /, '')) end def fingerprint_regex /(.{2})(?=.)/ end end # Create a new SSHKey object # # ==== Parameters # * private_key - Existing RSA or DSA private key # * options<~Hash> # * :comment<~String> - Comment to use for the public key, defaults to "" # * :passphrase<~String> - If the key is encrypted, supply the passphrase # def initialize(private_key, options = {}) @passphrase = options[:passphrase] @comment = options[:comment] || "" begin @key_object = OpenSSL::PKey::RSA.new(private_key, passphrase) @type = "rsa" rescue @key_object = OpenSSL::PKey::DSA.new(private_key, passphrase) @type = "dsa" end end # Fetch the RSA/DSA private key # # rsa_private_key and dsa_private_key are aliased for backward compatibility def private_key key_object.to_pem end alias_method :rsa_private_key, :private_key alias_method :dsa_private_key, :private_key # Fetch the encrypted RSA/DSA private key using the passphrase provided # # If no passphrase is set, returns the unencrypted private key def encrypted_private_key return private_key unless passphrase key_object.to_pem(OpenSSL::Cipher::Cipher.new("AES-128-CBC"), passphrase) end # Fetch the RSA/DSA public key # # rsa_public_key and dsa_public_key are aliased for backward compatibility def public_key key_object.public_key.to_pem end alias_method :rsa_public_key, :public_key alias_method :dsa_public_key, :public_key # SSH public key def ssh_public_key [SSH_TYPES[type], Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), comment].join(" ").strip end # Fingerprints # # MD5 fingerprint for the given SSH public key def md5_fingerprint Digest::MD5.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2') end alias_method :fingerprint, :md5_fingerprint # SHA1 fingerprint for the given SSH public key def sha1_fingerprint Digest::SHA1.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2') end private # SSH Public Key Conversion # # All data type encoding is defined in the section #5 of RFC #4251. # String and mpint (multiple precision integer) types are encoded this way: # 4-bytes word: data length (unsigned big-endian 32 bits integer) # n bytes: binary representation of the data # For instance, the "ssh-rsa" string is encoded as the following byte array # [0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'] def ssh_public_key_conversion out = [0,0,0,7].pack("C*") out += SSH_TYPES[type] SSH_CONVERSION[type].each do |method| byte_array = to_byte_array(key_object.public_key.send(method).to_i) out += encode_unsigned_int_32(byte_array.length).pack("c*") out += byte_array.pack("C*") end return out end def encode_unsigned_int_32(value) out = [] out[0] = value >> 24 & 0xff out[1] = value >> 16 & 0xff out[2] = value >> 8 & 0xff out[3] = value & 0xff return out end def to_byte_array(num) result = [] begin result << (num & 0xff) num >>= 8 end until (num == 0 || num == -1) && (result.last[7] == num[7]) result.reverse end end end end rhc-1.38.7/lib/rhc/json.rb0000644000004100000410000000215012756270551015246 0ustar www-datawww-datarequire 'rhc/vendor/okjson' module RHC module Json def self.decode(string, options={}) string = string.read if string.respond_to?(:read) result = RHC::Vendor::OkJson.decode(string) options[:symbolize_keys] ? symbolize_keys(result) : result end def self.encode(object, options={}) RHC::Vendor::OkJson.valenc(stringify_keys(object)) end def self.symbolize_keys(object) modify_keys(object) do |key| key.is_a?(String) ? key.to_sym : key end end def self.stringify_keys(object) modify_keys(object) do |key| key.is_a?(Symbol) ? key.to_s : key end end def self.modify_keys(object, &modifier) case object when Array object.map do |value| modify_keys(value, &modifier) end when Hash object.inject({}) do |result, (key, value)| new_key = modifier.call(key) new_value = modify_keys(value, &modifier) result.merge! new_key => new_value end else object end end end class JsonError < ::StandardError; end end rhc-1.38.7/lib/rhc/commands/0000755000004100000410000000000012756270551015553 5ustar www-datawww-datarhc-1.38.7/lib/rhc/commands/logout.rb0000644000004100000410000000312512756270551017412 0ustar www-datawww-datamodule RHC::Commands class Logout < Base suppress_wizard summary "End the current session" description <<-DESC Logout ends your current session on the server and then removes all of the local session files. If you are using multiple servers and configurations this will remove all of your local session files. The --all option will terminate all authorizations on your account. Any previously generated authorizations will be deleted and external tools that integrate with your account will no longer be able to log in. DESC option '--all', "Remove all authorizations on your account." alias_action 'account logout', :root_command => true def run if options.all rest_client.user # force authentication say "Deleting all authorizations associated with your account ... " begin rest_client.delete_authorizations success "done" rescue RHC::Rest::AuthorizationsNotSupported info "not supported" end elsif token_for_user options.noprompt = true say "Ending session on server ... " begin rest_client.delete_authorization(token_for_user) success "deleted" rescue RHC::Rest::AuthorizationsNotSupported info "not supported" rescue RHC::Rest::TokenExpiredOrInvalid info "already closed" rescue => e debug_error(e) warn e.message end end 0 ensure token_store.clear success "All local sessions removed." end end end rhc-1.38.7/lib/rhc/commands/ssh.rb0000644000004100000410000000460212756270551016677 0ustar www-datawww-datarequire 'rhc/commands/base' require 'resolv' require 'rhc/git_helpers' require 'rhc/cartridge_helpers' module RHC::Commands class Ssh < Base suppress_wizard summary "SSH into the specified application" description <<-DESC Connect to your application using SSH. This will connect to your primary gear (the one with your Git repository and web cartridge) by default. To SSH to other gears run 'rhc show-app --gears' to get a list of their SSH hosts. You may run a specific SSH command by passing one or more arguments, or use a different SSH executable or pass options to SSH with the '--ssh' option. To use the '--ssh' flag with an SSH executable path containing a space, wrap the executable path with double quotes: --ssh '"C:\\path with spaces\\ssh"' DESC syntax "[--ssh path_to_ssh_executable] [--gears] [ --] " takes_application :argument => true argument :command, "Command to run in the application's SSH session", ['--command COMMAND'], :type => :list, :optional => true option ["--ssh PATH"], "Path to your SSH executable or additional options" option ["--gears"], "Execute this command on all gears in the app. Requires a command." option ["--limit INTEGER"], "Limit the number of simultaneous SSH connections opened with --gears (default: 5).", :type => Integer, :default => 5 option ["--raw"], "Output only the data returned by each host, no hostname prefix." alias_action 'app ssh', :root_command => true def run(_, command) raise ArgumentError, "--gears requires a command" if options.gears && command.blank? raise ArgumentError, "--limit must be an integer greater than zero" if options.limit && options.limit < 1 ssh = check_ssh_executable! options.ssh if options.gears run_on_gears(command.join(' '), find_app(:with_gear_groups => true)) 0 else rest_app = find_app $stderr.puts "Connecting to #{rest_app.ssh_string.to_s} ..." unless command.present? debug "Using user specified SSH: #{options.ssh}" if options.ssh command_line = [RHC::Helpers.split_path(ssh), ('-vv' if debug?), rest_app.ssh_string.to_s, command].flatten.compact debug "Invoking Kernel.exec with #{command_line.inspect}" Kernel.send(:exec, *command_line) end end protected include RHC::SSHHelpers end end rhc-1.38.7/lib/rhc/commands/domain.rb0000644000004100000410000001524612756270551017357 0ustar www-datawww-datarequire 'rhc/commands/base' module RHC::Commands class Domain < Base summary "Add or rename the container for your apps" syntax "" description <<-DESC All OpenShift applications must belong to a domain. The name of the domain will be used as part of the public URL for an application. For example, when creating a domain with the name "test", any applications created in that domain will have the public URL: http://-test.rhcloud.com Each account may have access to one or more domains shared by others. Depending on your plan or configuration, you may be able to create more than one domain. To create your first domain, run 'rhc setup' or 'rhc create-domain'. DESC default_action :help summary "Create a new container for applications." syntax "" description <<-DESC A domain is a container for your applications. Each account may have one or more domains (depending on plan), and you may collaborate on applications by adding members to your domain. The name of the domain is called its "namespace", and becomes part of the application public URLs. For example, when creating a domain with the name "test", all applications in that domain will have the public URL: http://-test.rhcloud.com The domain owner may limit the gear sizes available to applications by using the '--allowed-gear-sizes' option. If '--no-allowed-gear-sizes' is set, no applications can be created in the domain. Older servers may not support this option. DESC option ['--no-allowed-gear-sizes'], 'Do not allow any gear sizes in this domain.', :optional => true option ['--allowed-gear-sizes [SIZES]'], 'A comma-delimited list of the gear sizes that will be allowed in this domain.', :optional => true argument :namespace, "New domain name (letters and numbers, max 16 chars)", ["-n", "--namespace NAME"] def create(namespace) say "Creating domain '#{namespace}' ... " rest_client.add_domain(namespace, :allowed_gear_sizes => check_allowed_gear_sizes) success "done" info "You may now create an application using the 'rhc create-app' command" 0 end summary "Rename a domain (will change application urls)" syntax " " argument :old_namespace, "Existing domain name", [] argument :new_namespace, "New domain name (letters and numbers, max 16 chars)", ["-n", "--namespace NAME"] alias_action :update, :deprecated => true def rename(old_namespace, new_namespace) domain = rest_client.find_domain(old_namespace) say "Renaming domain '#{domain.name}' to '#{new_namespace}' ... " domain.rename(new_namespace) success "done" info "Applications created in this domain will use the new name in their URL." 0 end summary "Change one or more configuration settings on the domain" syntax "" option ['--no-allowed-gear-sizes'], 'Do not allow any gear sizes in this domain.', :optional => true option ['--allowed-gear-sizes [SIZES]'], "A comma-delimited list of gear sizes allowed in this domain. To see available sizes, run 'rhc account'.", :optional => true takes_domain :argument => true def configure(_) domain = find_domain payload = {} payload[:allowed_gear_sizes] = check_allowed_gear_sizes unless options.allowed_gear_sizes.nil? and options.no_allowed_gear_sizes.nil? if payload.present? say "Updating domain configuration ... " domain.configure(payload) success "done" end paragraph do say format_table("Domain #{domain.name} configuration", get_properties(domain, :allowed_gear_sizes), :delete => true) end 0 end summary "Display a domain and its applications" takes_domain :argument => true def show(_) domain = find_domain warn "In order to deploy applications, you must create a domain with 'rhc setup' or 'rhc create-domain'." and return 1 unless domain applications = domain.applications(:include => :cartridges) display_domain(domain, applications, true) if applications.present? success "You have #{pluralize(applications.length, 'application')} in your domain." else success "The domain #{domain.name} exists but has no applications. You can use 'rhc create-app' to create a new application." end 0 end summary "Display all domains you have access to" option ['--mine'], "Display only domains you own" option ['--ids'], "Display the unique id of the domain (deprecated, domain IDs are now displayed by default)" alias_action :domains, :root_command => true def list domains = rest_client.send(options.mine ? :owned_domains : :domains) warn "In order to deploy applications, you must create a domain with 'rhc setup' or 'rhc create-domain'." and return 1 unless domains.present? warn "The --ids option is deprecated. Domain IDs are displayed by default." if options.ids domains.each do |d| display_domain(d, nil, true) end success "You have access to #{pluralize(domains.length, 'domain')}." 0 end summary "Delete a domain" syntax "" takes_domain :argument => true option ["-f", "--force"], "Force the action" def delete(_) domain = find_domain say "Deleting domain '#{domain.name}' ... " domain.destroy(options.force.present?) success "deleted" 0 end summary "Leave a domain (remove your membership)" syntax "" argument :namespace, "Name of the domain", ["-n", "--namespace NAME"] def leave(namespace) domain = rest_client.find_domain(namespace) say "Leaving domain ... " result = domain.leave success "done" result.messages.each{ |s| paragraph{ say s } } 0 end protected def check_allowed_gear_sizes raise OptionParser::InvalidOption, "--allowed-gear-sizes and --no-allowed-gear-sizes cannot both be specified" unless options.allowed_gear_sizes.nil? or options.no_allowed_gear_sizes.nil? sizes = options.no_allowed_gear_sizes.nil? ? options.allowed_gear_sizes : false raise OptionParser::InvalidOption, "The server does not support --allowed-gear-sizes" unless sizes.nil? || rest_client.api.has_param?(:add_domain, 'allowed_gear_sizes') if sizes.is_a? String sizes.split(',').map(&:strip).map(&:presence) elsif sizes == false [] elsif sizes raise OptionParser::InvalidOption, "Provide a comma delimited list of valid gear sizes to --allowed-gear-sizes" end end end end rhc-1.38.7/lib/rhc/commands/member.rb0000644000004100000410000004020712756270551017352 0ustar www-datawww-datarequire 'rhc/commands/base' module RHC::Commands class Member < Base summary "Manage membership on domains and teams" syntax "" description <<-DESC Domain Membership Developers can collaborate on applications by adding people or teams to domains as members. Each member has a role (admin, edit, or view), and those roles determine what the user can do with the domain and the applications contained within. Domain Member Roles view - able to see the domain and its apps, but not make any changes edit - create, update, and delete applications, and has Git and SSH access admin - can update membership of a domain The default role granted to domain members is 'edit' - use the '--role' argument to specify a different role. When adding and removing members, you can use their 'login' value (typically their email or a short unique name for them), or their 'id'. Both login and ID are visible via the 'rhc account' command. To see existing members of a domain or application, use: rhc members -n DOMAIN_NAME [-a APP_NAME] To change the role for a domain member, simply call the update-member command with the new role. You cannot change the role of the owner. Team Membership People who typically share the same role can be added to a team. The team can then be added as a member of a domain, and all of the people in the team will inherit the team's role on the domain. If a person is a member of multiple teams which are members of a domain, or is also added as a domain member individually, their effective role is the higher of their individual role or their teams' roles on the domain. Team Member Roles view - able to see information about the team and its members, and has access to all domains the team is a member of To see existing members of a team, use: rhc members -t TEAM_NAME DESC syntax "" default_action :help summary "List members of a domain, application, or team" syntax [ "[/] [--all]", "-n DOMAIN_NAME [--all]", "-n DOMAIN_NAME -a APP_NAME [--all]", nil, "-t TEAM_NAME" ] description <<-DESC Show the existing members of a domain, application, or team. To show the members of a domain or application, you can pass the name of your domain with '-n', the name of your application with '-a', or combine them in the first argument to the command like: rhc members [/] To show the members of a team, you can pass the name of the team with '-t': rhc members -t TEAM_NAME The owner is always listed first. To see the unique ID of members, pass '--ids'. DESC option ['--ids'], "Display the IDs of each member", :optional => true option ['--all'], "Display all members, including members of teams", :optional => true takes_membership_container :argument => true alias_action :members, :root_command => true def list(_) target = find_membership_container members = target.members if options.all show_members = members.sort else show_members = members.select do |m| if m.owner? true elsif m.explicit_role? true elsif m.from.any? {|f| f["type"] != "team" } true else false end end.sort end show_name = show_members.any?{ |m| m.name.presence && m.name != m.login } show_login = show_members.any?{ |m| m.login.presence } if show_members.present? say table(show_members.map do |member| [ ((member.name || "") if show_name), ((member.login || "") if show_login), role_description(member, member.teams(members)), (member.id if options.ids), member.type ].compact end, :header => [ ('Name' if show_name), ('Login' if show_login), 'Role', ("ID" if options.ids), "Type" ].compact) else info "The #{target.class.model_name.downcase} #{target.name} does not have any members." end if show_members.count < members.count paragraph do info "Pass --all to display all members, including members of teams." end end 0 end summary "Add a member to a domain or team" syntax [ "-n DOMAIN_NAME [--role view|edit|admin] ...", "-n DOMAIN_NAME [--role view|edit|admin] ... --type team [--global]", "-n DOMAIN_NAME [--role view|edit|admin] ... --ids [--type user|team]", nil, "-t TEAM_NAME ...", "-t TEAM_NAME ... --ids", ] description <<-DESC Domain Membership Add members to a domain by passing a user login, team name, or ID for each member. The login and ID for each account are displayed in 'rhc account'. To change the role for an existing domain member, use the 'rhc member update' command. Domain Member Roles view - able to see information about the domain and its apps, but not make any changes edit - create, update, and delete applications, and has Git and SSH access admin - can update membership of a domain The default role granted to domain members is 'edit'. Use the '--role' argument for 'view' or 'admin'. Team Membership Add users to a team by passing a user login, or ID for each member. Team Member Roles view - able to see information about the team and its members, and has access to all domains the team is a member of Examples rhc add-member sally joe -n mydomain Gives the accounts with logins 'sally' and 'joe' edit access on mydomain rhc add-member bob --role admin -n mydomain Gives the account with login 'bob' admin access on mydomain rhc add-member team1 --type team --role admin -n mydomain Gives your team named 'team1' admin access on mydomain rhc add-member steve -t team1 Adds the account with login 'steve' as a member of your team named 'team1' DESC takes_membership_container :writable => true option ['--ids'], "Add member(s) by ID", :optional => true option ['-r', '--role ROLE'], "The role to give to each member - view, edit, or admin (default is 'edit' for domains, 'view' for teams)", :type => Role, :optional => true option ['--type TYPE'], "Type of member(s) being added - user or team (default is 'user').", :optional => true option ['--global'], "Add global-scoped teams as members. Must be used with '--type team'.", :optional => true argument :members, "A list of members (user logins, team names, or IDs) to add. Pass --ids to treat this as a list of IDs.", [], :type => :list def add(members) target = find_membership_container :writable => true role = get_role_option(options, target) type = get_type_option(options) global = !!options.global raise ArgumentError, 'You must pass at least one member to this command.' unless members.present? raise ArgumentError, "The --global option can only be used with '--type team'." if global && !team?(type) say "Adding #{pluralize(members.length, role_name(role))} to #{target.class.model_name.downcase} ... " members = search_teams(members, global).map{|member| member.id} if team?(type) && !options.ids target.update_members(changes_for(members, role, type)) success "done" 0 end summary "Update a member on a domain" syntax [ "-n DOMAIN_NAME --role view|edit|admin ...", "-n DOMAIN_NAME --role view|edit|admin ... --type team", "-n DOMAIN_NAME --role view|edit|admin ... --ids [--type user|team]", ] description <<-DESC Updates members on a domain by passing a user login, team name, or ID for each member. You can use the 'rhc members' command to list the existing members of your domain. You cannot change the role of the owner. Roles view - able to see information about the domain and its apps, but not make any changes edit - create, update, and delete applications, and has Git and SSH access admin - can update membership of a domain Examples rhc update-member -n mydomain --role view bob Adds or updates the user with login 'bob' to 'view' role on mydomain rhc update-member -n mydomain --role admin team1 --type team Updates the team member with name 'team1' to the 'admin' role on mydomain rhc update-member -n mydomain --role admin team1_id --ids --type team Adds or updates the team with ID 'team1_id' to the 'admin' role on mydomain DESC takes_domain option ['--ids'], "Update member(s) by ID", :optional => true option ['-r', '--role ROLE'], "The role to give to each member - view, edit, or admin", :type => Role, :optional => false option ['--type TYPE'], "Type of member(s) being updated - user or team (default is 'user').", :optional => true argument :members, "A list of members (user logins, team names, or IDs) to update. Pass --ids to treat this as a list of IDs.", [], :type => :list def update(members) target = find_domain role = get_role_option(options, target) type = get_type_option(options) raise ArgumentError, 'You must pass at least one member to this command.' unless members.present? say "Updating #{pluralize(members.length, role_name(role))} to #{target.class.model_name.downcase} ... " members = search_team_members(target.members, members).map{|member| member.id} if team?(type) && !options.ids target.update_members(changes_for(members, role, type)) success "done" 0 end summary "Remove a member from a domain or team" syntax [ "-n DOMAIN_NAME ...", "-n DOMAIN_NAME ... --type team", "-n DOMAIN_NAME ... --ids [--type user|team]", nil, "-t TEAM_NAME ...", "-t TEAM_NAME ... --ids", ] description <<-DESC Remove members from a domain by passing a user login, team name, or ID for each member you wish to remove. View the list of existing members with rhc members -n DOMAIN_NAME Remove members from a team by passing a user login, or ID for each member you wish to remove. View the list of existing members with rhc members -t TEAM_NAME Pass '--all' to remove all members but the owner. DESC takes_membership_container :writable => true option ['--ids'], "Remove member(s) by ID." option ['--all'], "Remove all members" option ['--type TYPE'], "Type of member(s) being removed - user or team (default is 'user').", :optional => true argument :members, "A list of members (user logins, team names, or IDs) to remove. Pass --ids to treat this as a list of IDs.", [], :type => :list def remove(members) target = find_membership_container :writable => true type = get_type_option(options) if options.all say "Removing all members from #{target.class.model_name.downcase} ... " target.delete_members success "done" else raise ArgumentError, 'You must pass at least one member to this command.' unless members.present? say "Removing #{pluralize(members.length, 'member')} from #{target.class.model_name.downcase} ... " members = search_team_members(target.members, members).map{|member| member.id} if team?(type) && !options.ids target.update_members(changes_for(members, 'none', type)) success "done" end 0 end protected def get_role_option(options, target) options.role || target.default_member_role end def get_type_option(options) type = options.__hash__[:type] type || 'user' end def changes_for(members, role, type) members.map do |m| h = {:role => role, :type => type} h[options.ids || team?(type) ? :id : :login] = m h end end def team?(type) type == 'team' end def search_teams(team_names, global=false) r = [] team_names.each do |team_name| teams_for_name = global ? rest_client.search_teams(team_name, global) : rest_client.search_owned_teams(team_name) team_for_name = nil suggestions = nil if (exact_matches = teams_for_name.select {|t| t.name == team_name }).present? if exact_matches.length == 1 team_for_name = exact_matches.first else raise RHC::TeamNotFoundException.new("There is more than one team named '#{team_name}'. " + "Please use the --ids flag and specify the exact id of the team you want to manage.") end elsif (case_insensitive_matches = teams_for_name.select {|t| t.name =~ /^#{Regexp.escape(team_name)}$/i }).present? if case_insensitive_matches.length == 1 team_for_name = case_insensitive_matches.first else suggestions = case_insensitive_matches end else suggestions = teams_for_name end if team_for_name r << team_for_name elsif suggestions.present? msg = global ? "No global team found with the name '#{team_name}'." : "You do not have a team named '#{team_name}'." raise RHC::TeamNotFoundException.new(msg + " Did you mean one of the following?\n#{suggestions[0..50].map(&:name).join(", ")}") else msg = global ? "No global team found with the name '#{team_name}'." : "You do not have a team named '#{team_name}'." raise RHC::TeamNotFoundException.new(msg) end end r.flatten end def search_team_members(members, names) r = [] team_members = members.select(&:team?) names.each do |name| team_for_name = nil suggestions = nil if (exact_matches = team_members.select{|team| team.name == name }).present? if exact_matches.length == 1 team_for_name = exact_matches.first else raise RHC::MemberNotFoundException.new("There is more than one member team named '#{name}'. " + "Please use the --ids flag and specify the exact id of the team you want to manage.") end elsif (case_insensitive_matches = team_members.select{|team| team.name =~ /^#{Regexp.escape(name)}$/i}).present? if case_insensitive_matches.length == 1 team_for_name = case_insensitive_matches.first else suggestions = case_insensitive_matches end else suggestions = team_members.select{|t| t.name =~ /#{Regexp.escape(name)}/i} end if team_for_name r << team_for_name elsif suggestions.present? raise RHC::MemberNotFoundException.new("No member team found with the name '#{name}'. " + "Did you mean one of the following?\n#{suggestions[0..50].map(&:name).join(", ")}") else raise RHC::MemberNotFoundException.new("No member team found with the name '#{name}'.") end end r.flatten end def role_description(member, teams=[]) if member.owner? "#{member.role} (owner)" elsif member.explicit_role != member.role && member.from.all? {|f| f['type'] == 'domain'} "#{member.role} (via domain)" elsif member.explicit_role != member.role && teams.present? && (teams_with_role = teams.select{|t| t.role == member.role }).present? "#{member.role} (via #{teams_with_role.map(&:name).sort.join(', ')})" else member.role end end end end rhc-1.38.7/lib/rhc/commands/cartridge.rb0000644000004100000410000003546112756270551020055 0ustar www-datawww-datarequire 'rhc/commands/base' require 'rhc/cartridge_helpers' module RHC::Commands class Cartridge < Base summary "Manage your application cartridges" syntax "" description <<-DESC Cartridges add functionality to OpenShift applications. Each application has one web cartridge to listen for HTTP requests, and any number of addon cartridges. Addons may include databases like MySQL and Mongo, administrative tools like phpMyAdmin, or build clients like Jenkins. Most cartridges that listen for incoming network traffic are placed on one or more gears (a small server instance). Other cartridges may be available across all of the gears of an application to listen for changes (like Jenkins) or provide environment variables. Use the 'cartridges' command to see a list of all available cartridges. Add a new cartridge to your application with 'add-cartridge'. OpenShift also supports downloading cartridges - pass a URL in place of the cartridge name and we'll download and install that cartridge into your app. Keep in mind that these cartridges receive no security updates. Note that not all OpenShift servers allow downloaded cartridges. For scalable applications, use the 'cartridge-scale' command on the web cartridge to set the minimum and maximum scale. Commands that affect a cartridge within an application will affect all gears the cartridge is installed to. DESC default_action :help summary "List available cartridges" syntax '' option ["-v", "--verbose"], "Display more details about each cartridge" alias_action :"app cartridge list", :root_command => true, :deprecated => true alias_action :"cartridges", :root_command => true def list carts = rest_client.cartridges.sort_by{ |c| "#{c.type == 'standalone' && 1}_#{c.tags.include?('experimental') ? 1 : 0}_#{(c.display_name || c.name).downcase}" } pager if options.verbose carts.each do |c| paragraph do name = c.name name += '*' if c.usage_rate? name = c.display_name != c.name && "#{color(c.display_name, :cyan)} [#{name}]" || name tags = c.tags - RHC::Rest::Cartridge::HIDDEN_TAGS say header([name, "(#{c.only_in_existing? ? 'addon' : 'web'})"]) say c.description paragraph{ say "Tagged with: #{tags.sort.join(', ')}" } if tags.present? paragraph{ say format_usage_message(c) } if c.usage_rate? paragraph{ warn "Does not receive automatic security updates" } unless c.automatic_updates? end end else say table(carts.collect do |c| [[c.name, c.usage_rate? ? " (*)" : "", c.automatic_updates? ? '' : ' (!)'].join(''), c.display_name, c.only_in_existing? ? 'addon' : 'web', ] end) end paragraph{ say "Note: Web cartridges can only be added to new applications." } paragraph{ say "(*) denotes a cartridge with additional usage costs." } if carts.any?(&:usage_rate?) paragraph{ say "(!) denotes a cartridge that will not receive automatic security updates." } unless options.verbose || carts.none?(&:automatic_updates?) 0 end summary "Add a cartridge to your application" syntax " [--namespace NAME] [--app NAME]" takes_application option ["-e", "--env VARIABLE=VALUE"], "Environment variable(s) to be set on this cartridge, or path to a file containing environment variables", :type => :list option ["-g", "--gear-size SIZE"], "Gear size controls how much memory and CPU your cartridge can use" argument :cart_type, "The type of the cartridge you are adding (run 'rhc cartridge list' to obtain a list of available cartridges)", ["-c", "--cartridge cart_type"] alias_action :"app cartridge add", :root_command => true, :deprecated => true def add(cart_type) cart = check_cartridges(cart_type, :from => not_standalone_cartridges).first say "Adding #{cart.short_name} to application '#{options.app}' ... " say format_usage_message(cart) if cart.usage_rate? rest_app = find_app(:include => :cartridges) supports_env_vars = rest_app.supports_add_cartridge_with_env_vars? supports_gear_size = rest_app.supports_add_cartridge_with_gear_size? cart.environment_variables = collect_env_vars(options.env).map { |item| item.to_hash } if options.env && supports_env_vars cart.gear_size = options.gear_size if options.gear_size && supports_gear_size rest_cartridge = rest_app.add_cartridge(cart) success "done" rest_cartridge.environment_variables = cart.environment_variables if cart.environment_variables.present? paragraph{ display_cart(rest_cartridge) } paragraph{ say "Use 'rhc env --help' to manage environment variable(s) on this cartridge and application." } if cart.environment_variables.present? paragraph{ warn "Server does not support environment variables." if options.env && !supports_env_vars } paragraph{ warn "Server does not support gear sizes for cartridges." if options.gear_size && !supports_gear_size } paragraph{ rest_cartridge.messages.each { |msg| success msg } } 0 end summary "Show useful information about a cartridge" syntax " [--namespace NAME] [--app NAME]" takes_application argument :cartridge, "The name of the cartridge", ["-c", "--cartridge cart_type"] def show(cartridge) rest_app = find_app(:include => :cartridges) rest_cartridge = check_cartridges(cartridge, :from => rest_app.cartridges).first display_cart(rest_cartridge) 0 end summary "Remove a cartridge from your application" syntax " [--namespace NAME] [--app NAME]" argument :cartridge, "The name of the cartridge you are removing", ["-c", "--cartridge cartridge"] takes_application option ["--confirm"], "Pass to confirm removing the cartridge" alias_action :"app cartridge remove", :root_command => true, :deprecated => true def remove(cartridge) rest_app = find_app(:include => :cartridges) rest_cartridge = check_cartridges(cartridge, :from => rest_app.cartridges).first external_zero_gears = external_zero_gears_cartridge?(rest_cartridge) confirm_action "Removing a cartridge is a destructive operation that may result in loss of data associated with the cartridge.\n\nAre you sure you wish to remove #{rest_cartridge.name} from '#{rest_app.name}'?" say "Removing #{rest_cartridge.name} from '#{rest_app.name}' ... " rest_cartridge.destroy success "removed" paragraph{ rest_cartridge.messages.each { |msg| success msg } } paragraph{ warn 'There may be external resources or accounts associated with this external cartridge that need to be removed manually.' } if external_zero_gears 0 end summary "Start a cartridge" syntax " [--namespace NAME] [--app NAME]" argument :cart_type, "The name of the cartridge you are starting", ["-c", "--cartridge cartridge"] takes_application alias_action :"app cartridge start", :root_command => true, :deprecated => true def start(cartridge) cartridge_action(cartridge, :start, 'Starting %s ... ') 0 end summary "Stop a cartridge" syntax " [--namespace NAME] [--app NAME]" argument :cart_type, "The name of the cartridge you are stopping", ["-c", "--cartridge cartridge"] takes_application alias_action :"app cartridge stop", :root_command => true, :deprecated => true def stop(cartridge) cartridge_action(cartridge, :stop, 'Stopping %s ... ') 0 end summary "Restart a cartridge" syntax " [--namespace NAME] [--app NAME]" argument :cart_type, "The name of the cartridge you are restarting", ["-c", "--cartridge cartridge"] takes_application alias_action :"app cartridge restart", :root_command => true, :deprecated => true def restart(cartridge) cartridge_action(cartridge, :restart, 'Restarting %s ... ') 0 end summary "Get current the status of a cartridge" syntax " [--namespace NAME] [--app NAME]" argument :cart_type, "The name of the cartridge you are getting the status of", ["-c", "--cartridge cartridge"] takes_application alias_action :"app cartridge status", :root_command => true, :deprecated => true def status(cartridge) rest_app = find_app(:include => :cartridges) rest_cartridge = check_cartridges(cartridge, :from => rest_app.cartridges).first results { rest_cartridge.status.each{ |msg| say msg['message'] } } 0 end summary "Reload the cartridge's configuration" syntax " [--namespace NAME] [--app NAME]" argument :cart_type, "The name of the cartridge you are reloading", ["-c", "--cartridge cartridge"] takes_application alias_action :"app cartridge reload", :root_command => true, :deprecated => true def reload(cartridge) cartridge_action(cartridge, :reload, 'Reloading %s ... ') 0 end summary "Set the scale range for a cartridge" description <<-DESC Each cartridge capable of scaling may have a minimum and a maximum set, although within that range each type of cartridge may make decisions to autoscale. Web cartridges will scale based on incoming request traffic - see https://www.openshift.com/developers/scaling for more information. Non web cartridges such as databases may require specific increments of scaling (1, 3, 5) in order to properly function. Please consult the cartridge documentation for more on specifics of scaling. Set both values the same to guarantee a scale value. You may specify both values with the argument 'multiplier' or use '--min' and '--max' independently. Scaling may take several minutes or more if the server must provision multiple gears. Your operation will continue in the background if your client is disconnected. DESC syntax " [multiplier] [--namespace NAME] [--app NAME] [--min min] [--max max]" argument :cartridge, "The name of the cartridge you are scaling", ["-c", "--cartridge cartridge"] argument :multiplier, "The number of instances of this cartridge you need", [], :optional => true, :hide => true takes_application option ["--min min", Integer], "Minimum scaling value" option ["--max max", Integer], "Maximum scaling value" def scale(cartridge, multiplier) options.default(:min => Integer(multiplier), :max => Integer(multiplier)) if multiplier rescue raise ArgumentError, "Multiplier must be a positive integer." raise RHC::MissingScalingValueException unless options.min || options.max rest_app = find_app(:include => :cartridges) rest_cartridge = check_cartridges(cartridge, :from => rest_app.cartridges).first raise RHC::CartridgeNotScalableException unless rest_cartridge.scalable? warn "This operation will run until the application is at the minimum scale and may take several minutes." say "Setting scale range for #{rest_cartridge.name} ... " cart = rest_cartridge.set_scales({ :scales_from => options.min, :scales_to => options.max }) success "done" paragraph{ display_cart(cart) } 0 rescue RHC::Rest::TimeoutException => e raise unless e.on_receive? info "The server has closed the connection, but your scaling operation is still in progress. Please check the status of your operation via 'rhc show-app'." 1 end summary 'View/manipulate storage on a cartridge' syntax ' -a app [--show] [--add|--remove|--set amount] [--namespace NAME]' argument :cart_type, "The name of the cartridge", ["-c", "--cartridge cart_type"], :type => :list takes_application option ["--show"], "Show the current base and additional storage capacity" option ["--add amount"], "Add the indicated amount to the additional storage capacity" option ["--remove amount"], "Remove the indicated amount from the additional storage capacity" option ["--set amount"], "Set the specified amount of additional storage capacity" option ["-f", "--force"], "Force the action" def storage(cartridge) cartridges = Array(cartridge) rest_client(:min_api => 1.3).api rest_app = find_app(:include => :cartridges) # Pull the desired action # actions = options.__hash__.keys & [:show, :add, :remove, :set] # Ensure that only zero or one action was selected raise RHC::AdditionalStorageArgumentsException if actions.length > 1 operation = actions.first || :show amount = options.__hash__[operation] # Perform a storage change action if requested if operation == :show results do if cartridges.length == 0 display_cart_storage_list rest_app.cartridges else check_cartridges(cartridge, :from => rest_app.cartridges).each do |cart| display_cart_storage_info cart, cart.display_name end end end else raise RHC::MultipleCartridgesException, 'Exactly one cartridge must be specified for this operation' if cartridges.length != 1 rest_cartridge = check_cartridges(cartridge, :from => rest_app.cartridges).first amount = amount.match(/^(\d+)(GB)?$/i) raise RHC::AdditionalStorageValueException if amount.nil? # If the amount is specified, find the regex match and convert to a number amount = amount[1].to_i total_amount = rest_cartridge.additional_gear_storage if operation == :add total_amount += amount elsif operation == :remove if amount > total_amount && !options.force raise RHC::AdditionalStorageRemoveException else total_amount = [total_amount - amount, 0].max end else total_amount = amount end say "Set storage on cartridge ... " cart = rest_cartridge.set_storage(:additional_gear_storage => total_amount) success "set to #{total_amount}GB" paragraph{ display_cart_storage_info cart } end 0 end private include RHC::CartridgeHelpers def cartridge_action(cartridge, action, message=nil) rest_app = find_app(:include => :cartridges) rest_cartridge = check_cartridges(cartridge, :from => rest_app.cartridges).first say message % [rest_cartridge.name] if message result = rest_cartridge.send(action) resp = [result, rest_cartridge, rest_app] if message success "done" result.messages.each{ |s| paragraph{ say s } } end resp end def external_zero_gears_cartridge?(rest_cartridge) rest_cartridge.external? && rest_cartridge.current_scale == 0 end end end rhc-1.38.7/lib/rhc/commands/apps.rb0000644000004100000410000000232612756270551017046 0ustar www-datawww-datarequire 'rhc/commands/base' module RHC::Commands class Apps < Base summary "List all your applications" description "Display the list of applications that you own. Includes information about each application." option ['--mine'], "Display only applications you own" option ["-s", "--summary"], "Display a summary about the applications you own." option ["-v", "--verbose"], "Display additional details about the application's cartridges." def run applications = (options.mine ? rest_client.owned_applications(:include => :cartridges) : rest_client.applications(:include => :cartridges)).sort info "In order to deploy applications, you must create a domain with 'rhc setup' or 'rhc create-domain'." and return 1 if applications.empty? && rest_client.domains.empty? info "No applications. Use 'rhc create-app'." and return 1 if applications.nil? || applications.empty? if options.summary display_app_summary(applications) else applications.each{|a| display_app(a, a.cartridges, nil, options.verbose) } end success "You have#{options.mine ? '' : ' access to'} #{pluralize(applications.length, 'application')}." 0 end end end rhc-1.38.7/lib/rhc/commands/threaddump.rb0000644000004100000410000000050612756270551020236 0ustar www-datawww-datarequire 'rhc/commands/base' module RHC::Commands class Threaddump < Base summary "Trigger a thread dump for JBoss and Ruby apps" syntax "" takes_application :argument => true def run(app) rest_app = find_app rest_app.threaddump.messages.each { |m| say m } 0 end end end rhc-1.38.7/lib/rhc/commands/team.rb0000644000004100000410000000567712756270551017045 0ustar www-datawww-datarequire 'rhc/commands/base' module RHC::Commands class Team < Base summary "Create or delete a team" syntax "" description <<-DESC People who typically share the same role can be added to a team. The team can then be added as a member of a domain, and all of the people in the team will inherit the team's role on the domain. If a person is a member of multiple teams which are members of a domain, or is also added as a domain member individually, their effective role is the higher of their individual role or their teams' roles on the domain. To create a team, run 'rhc create-team'. To add members to an existing team, use the 'rhc add-member' command. To list members of an existing team, use the 'rhc members' command. DESC default_action :help summary "Create a new team" syntax "" description <<-DESC People who typically share the same role can be added to a team. The team can then be added as a member of a domain, and all of the people in the team will inherit the team's role on the domain. If a person is a member of multiple teams which are members of a domain, or is also added as a domain member individually, their effective role is the higher of their individual role or their teams' roles on the domain. DESC argument :team_name, "New team name (min 2 chars, max 250 chars)", ["-t", "--team-name NAME"] def create(name) say "Creating team '#{name}' ... " rest_client.add_team(name) success "done" info "You may now add team members using the 'rhc add-member' command" 0 end summary "Display a team and its members" syntax "" takes_team :argument => true def show(_) team = find_team display_team(team, true) 0 end summary "Display all teams you are a member of" option ['--mine'], "Display only teams you own" alias_action :teams, :root_command => true def list teams = rest_client.send(options.mine ? :owned_teams : :teams, {:include => "members"}) teams.each do |t| display_team(t, true) end if options.mine success "You have #{pluralize(teams.length, 'team')}." else success "You are a member of #{pluralize(teams.length, 'team')}." end 0 end summary "Delete a team" syntax "" takes_team :argument => true def delete(_) team = find_team(:owned => true) say "Deleting team '#{team.name}' ... " team.destroy success "deleted" 0 end summary "Leave a team (remove your membership)" syntax " [-t TEAM_NAME] [--team-id TEAM_ID]" takes_team :argument => true def leave(_) team = find_team say "Leaving team ... " result = team.leave success "done" result.messages.each{ |s| paragraph{ say s } } 0 end end end rhc-1.38.7/lib/rhc/commands/deployment.rb0000644000004100000410000000570612756270551020270 0ustar www-datawww-datarequire 'rhc/commands/base' require 'rhc/deployment_helpers' module RHC::Commands class Deployment < Base include RHC::DeploymentHelpers summary "Commands for deploying and managing deployments of an application" description <<-DESC By default OpenShift applications prepare, distribute, and activate deployments on every git push. Alternatively, a user may choose to disable automatic deployments and use this 'rhc deployment' set of commands to fully control the deployment lifecycle. Use these commands to deploy manually from a git reference or from a binary file, list and display deployments and also activate existing deployments. Check also 'rhc configure-app' to configure your application to deploy manually. DESC syntax "" default_action :help summary "List the existing deployments of an application" description <<-DESC List all existing deployments of a given application. Check the 'rhc configure-app' command to configure how many deployments are preserved in history. DESC syntax "" takes_application :argument => true alias_action :"deployments", :root_command => true def list(app) rest_app = find_app deployment_activations = rest_app.deployment_activations raise RHC::DeploymentNotFoundException, "No deployments found for application #{app}." if !deployment_activations.present? pager display_deployment_list(deployment_activations) 0 end summary "Show details of the given deployment" syntax " --app NAME [--namespace NAME]" description <<-DESC Display details of the given deployment id. DESC takes_application argument :id, "The deployment ID to show", ["--id ID"], :optional => false def show(id) rest_app = find_app item = rest_app.deployment_activations.reverse_each.detect{|item| item[:deployment].id == id} raise RHC::DeploymentNotFoundException, "Deployment ID '#{id}' not found for application #{rest_app.name}." if !item.present? display_deployment(item) paragraph { say "Use 'rhc show-app #{rest_app.name} --configuration' to check your deployment configurations." } 0 end summary "Activate an existing deployment" description <<-DESC Switch between existing deployments. This command allows you to rollback from one deployment to a previous one or activate subsequent deployments. Check the 'rhc configure-app' command to configure how many deployments are preserved in history. DESC syntax " --app NAME [--namespace NAME]" takes_application argument :id, "The deployment ID to activate on the application", ["--id ID"], :optional => false def activate(id) rest_app = find_app raise RHC::DeploymentsNotSupportedException.new if !rest_app.supports? "DEPLOY" activate_deployment(rest_app, id) 0 end end end rhc-1.38.7/lib/rhc/commands/snapshot.rb0000644000004100000410000000451112756270551017740 0ustar www-datawww-datarequire 'rhc/commands/base' module RHC::Commands class Snapshot < Base summary "Save the current state of your application locally" syntax "" description <<-DESC Snapshots allow you to export the current state of your OpenShift application into an archive on your local system, and then to restore it later. The snapshot archive contains the Git repository, dumps of any attached databases, and any other information that the cartridges decide to export. WARNING: Both 'save' and 'restore' will stop the application and then restart after the operation completes. DESC alias_action :"app snapshot", :root_command => true default_action :help summary "Save a snapshot of your app to disk" syntax " [--filepath FILE] [--ssh path_to_ssh_executable]" takes_application :argument => true option ["-f", "--filepath FILE"], "Local path to save tarball (default: ./$APPNAME.tar.gz)" option ["--deployment"], "Snapshot as a deployable file which can be deployed with 'rhc deploy'" option ["--ssh PATH"], "Full path to your SSH executable with additional options" alias_action :"app snapshot save", :root_command => true, :deprecated => true def save(app) rest_app = find_app raise RHC::DeploymentsNotSupportedException.new if options.deployment && !rest_app.supports?("DEPLOY") filename = options.filepath ? options.filepath : "#{rest_app.name}.tar.gz" save_snapshot(rest_app, filename, options.deployment, options.ssh) 0 end summary "Restores a previously saved snapshot" syntax " [--filepath FILE] [--ssh path_to_ssh_executable]" takes_application :argument => true option ["-f", "--filepath FILE"], "Local path to restore tarball" option ["--ssh PATH"], "Full path to your SSH executable with additional options" alias_action :"app snapshot restore", :root_command => true, :deprecated => true def restore(app) rest_app = find_app filename = options.filepath ? options.filepath : "#{rest_app.name}.tar.gz" if File.exists? filename restore_snapshot(rest_app, filename, options.ssh) else raise RHC::SnapshotRestoreException.new "Archive not found: #{filename}" end 0 end protected include RHC::SSHHelpers end endrhc-1.38.7/lib/rhc/commands/port_forward.rb0000644000004100000410000001664012756270551020617 0ustar www-datawww-datarequire 'uri' module RHC::Commands class ForwardingSpec include RHC::Helpers include Enumerable # class to represent how SSH port forwarding should be performed attr_accessor :port_from attr_reader :remote_host, :port_to, :host_from, :service attr_writer :bound def initialize(service, remote_host, port_to, port_from = nil) @service = service @remote_host = remote_host @port_to = port_to @host_from = '127.0.0.1' @port_from = port_from || port_to # match ports if possible @bound = false end def to_cmd_arg # string to be used in a direct SSH command "-L #{port_from}:#{remote_host}:#{port_to}" end def to_fwd_args # array of arguments to be passed to Net::SSH::Service::Forward#local [port_from.to_i, remote_host, port_to.to_i] end def bound? @bound end # :nocov: These are for sorting. No need to test for coverage. def <=>(other) if bound? && !other.bound? -1 elsif !bound? && other.bound? 1 else order_by_attrs(other, :service, :remote_host, :port_from) end end def order_by_attrs(other, *attrs) # compare self and "other" by examining their "attrs" in order # attrs should be an array of symbols to which self and "other" # respond when sent. while attribute = attrs.shift do if self.send(attribute) != other.send(attribute) return self.send(attribute) <=> other.send(attribute) end end 0 end # :nocov: private :order_by_attrs end class PortForward < Base UP_TO_256 = /25[0-5]|2[0-4][0-9]|[01]?(?:[0-9][0-9]?)/ UP_TO_65535 = /6553[0-5]|655[0-2][0-9]|65[0-4][0-9][0-9]|6[0-4][0-9][0-9][0-9]|[0-5]?(?:[0-9][0-9]{0,3})/ # 'host' part is a bit lax; we rely on 'rhc-list-ports' to hand us a reasonable output # about the host information, be it numeric or FQDN in IPv4 or IPv6. HOST_AND_PORT = /(.+):(#{UP_TO_65535})\b/ summary "Forward remote ports to the workstation" syntax "" takes_application :argument => true option ["-g", "--gear ID"], "Gear ID you are port forwarding to (optional)" option ["-s", "--service [SERVICE,]"], "A CSV list of services to port forward (optional)" def run(app) rest_app = find_app ssh_uri = URI.parse(options.gear ? rest_app.gear_ssh_url(options.gear) : rest_app.ssh_url) say "Using #{ssh_uri}..." if options.debug forwarding_specs = [] begin say "Checking available ports ... " Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh| # If a specific gear is targeted, do not include remote (e.g. database) ports list_ports_cmd = "rhc-list-ports#{options.gear ? ' --exclude-remote' : ''}" ssh.exec! list_ports_cmd do |channel, stream, data| if stream == :stderr data.each_line do |line| line.chomp! # FIXME: This is really brittle; there must be a better way # for the server to tell us that permission (what permission?) # is denied. raise RHC::PermissionDeniedException.new "Permission denied." if line =~ /permission denied/i # ...and also which services are available for the application # for us to forward ports for. if line =~ /\A\s*(\S+) -> #{HOST_AND_PORT}\z/ and (options.service.nil? or options.service.empty? or options.service.split(',').include? $1) debug fs = ForwardingSpec.new($1, $2, $3.to_i) forwarding_specs << fs else debug line end end end end if forwarding_specs.length == 0 # check if the gears have been stopped if rest_app.gear_groups.all?{ |gg| gg.gears.all?{ |g| g["state"] == "stopped" } } warn "none" error "The application is stopped. Please restart the application and try again." return 1 else warn "none" raise RHC::NoPortsToForwardException.new "There are no available ports to forward for this application. Your application may be stopped or idled." end end success "done" begin Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh| say "Forwarding ports ..." forwarding_specs.each do |fs| given_up = nil while !fs.bound? && !given_up begin args = fs.to_fwd_args debug args.inspect ssh.forward.local(*args) fs.bound = true rescue Errno::EADDRINUSE, Errno::EACCES, Errno::EPERM => e warn "#{e} while forwarding port #{fs.port_from}. Trying local port #{fs.port_from+1}" fs.port_from += 1 rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e given_up = true end end end bound_ports = forwarding_specs.select(&:bound?) if bound_ports.length > 0 paragraph{ say "To connect to a service running on OpenShift, use the Local address" } paragraph do say table( bound_ports.map do |fs| [fs.service, "#{fs.host_from}:#{fs.port_from}", " => ", "#{fs.remote_host}:#{fs.port_to.to_s}"] end, :header => ["Service", "Local", " ", "OpenShift"] ) end end # for failed port forwarding attempts failed_port_forwards = forwarding_specs.select { |fs| !fs.bound? } if failed_port_forwards.length > 0 ssh_cmd_arg = failed_port_forwards.map { |fs| fs.to_cmd_arg }.join(" ") ssh_cmd = "ssh -N #{ssh_cmd_arg} #{ssh_uri.user}@#{ssh_uri.host}" warn "Error forwarding some port(s). You can try to forward manually by running:\n#{ssh_cmd}" else say "Press CTRL-C to terminate port forwarding" end unless forwarding_specs.any?(&:bound?) warn "No ports have been bound" return end ssh.loop { true } end rescue Interrupt say " Ending port forward" return 0 end end rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e ssh_cmd = ["ssh","-N"] unbound_fs = forwarding_specs.select { |fs| !fs.bound? } ssh_cmd += unbound_fs.map { |fs| fs.to_cmd_arg } ssh_cmd += ["#{ssh_uri.user}@#{ssh_uri.host}"] raise RHC::PortForwardFailedException.new("#{e.message + "\n" if options.debug}Error trying to forward ports. You can try to forward manually by running:\n" + ssh_cmd.join(" ")) end 0 rescue RHC::Rest::ConnectionException => e error "Connection to #{openshift_server} failed: #{e.message}" 1 end end end # mock for windows if defined?(UNIXServer) != 'constant' or UNIXServer.class != Class then class UNIXServer; end; end rhc-1.38.7/lib/rhc/commands/tail.rb0000644000004100000410000000334012756270551017031 0ustar www-datawww-datarequire 'rhc/commands/base' require 'rhc/config' require 'rhc/ssh_helpers' module RHC::Commands class Tail < Base include RHC::SSHHelpers summary "Tail the logs of an application" syntax "" takes_application :argument => true option ["-o", "--opts options"], "Options to pass to the server-side (linux based) tail command (applicable to tail command only) (-f is implicit. See the linux tail man page full list of options.) (Ex: --opts '-n 100')" option ["-f", "--files files"], "File glob relative to app (default /logs/*) (optional)" option ["-g", "--gear ID"], "Tail only a specific gear" #option ["-c", "--cartridge name"], "Tail only a specific cartridge" alias_action :"app tail", :root_command => true, :deprecated => true def run(app_name) rest_app = find_app(:include => :cartridges) ssh_url = options.gear ? rest_app.gear_ssh_url(options.gear) : rest_app.ssh_url tail('*', URI(ssh_url), options) 0 end private #Application log file tailing def tail(cartridge_name, ssh_url, options) debug "Tail in progress for cartridge #{cartridge_name}" host = ssh_url.host uuid = ssh_url.user file_glob = options.files ? options.files : "#{cartridge_name}/log*/*" remote_cmd = "tail#{options.opts ? ' --opts ' + Base64::encode64(options.opts).chomp : ''} #{file_glob}" ssh_cmd = "ssh -t #{uuid}@#{host} '#{remote_cmd}'" begin #Use ssh -t to tail the logs debug ssh_cmd ssh_ruby(host, uuid, remote_cmd, false, true) rescue warn "You can tail this application directly with:\n#{ssh_cmd}" raise end end end end rhc-1.38.7/lib/rhc/commands/git_clone.rb0000644000004100000410000000302312756270551020041 0ustar www-datawww-datarequire 'rhc/commands/base' require 'rhc/git_helpers' module RHC::Commands class GitClone < Base summary "Clone and configure an application's repository locally" description "This is a convenience wrapper for 'git clone' with the added", "benefit of adding configuration data such as the application's", "UUID to the local repository. It also automatically", "figures out the Git url from the application name so you don't", "have to look it up." syntax " [--namespace NAME]" takes_application :argument => true option ["-r", "--repo dir"], "Path to the Git repository (defaults to ./$app_name)" alias_action 'app git-clone', :deprecated => true, :root_command => true # TODO: Implement default values for arguments once ffranz has added context arguments # argument :directory, "The name of a new directory to clone into", [], :default => nil def run(app_name) if has_git? rest_app = find_app dir = git_clone_application(rest_app) success "Your application Git repository has been cloned to '#{system_path(dir)}'" 0 else error "You do not have git installed. In order to fully interact with OpenShift you will need to install and configure a git client.#{RHC::Helpers.windows? ? ' We recommend this free application: Git for Windows - a basic git command line and GUI client http://msysgit.github.io/.' : ''}" 2 end end private include RHC::GitHelpers end end rhc-1.38.7/lib/rhc/commands/account.rb0000644000004100000410000000143112756270551017533 0ustar www-datawww-datamodule RHC::Commands class Account < Base suppress_wizard summary "Display details about your OpenShift account" description <<-DESC Shows who you are logged in to the server as and the capabilities available to you on this server. To access more details about your account please visit the website. DESC def run user = rest_client.user say format_table \ ["Login #{user.login} on #{openshift_server}"], get_properties(user, :id, :plan_id, :consumed_gears, :max_gears, :max_domains). concat(get_properties(user.capabilities, :gear_sizes)). push(['SSL Certificates:', user.capabilities.private_ssl_certificates ? 'yes' : 'no']), :delete => true 0 end end end rhc-1.38.7/lib/rhc/commands/region.rb0000644000004100000410000000162112756270551017363 0ustar www-datawww-datarequire 'rhc/commands/base' module RHC::Commands class Region < Base summary "Display the regions and zones available on the OpenShift server" default_action :list summary "List the regions and zones available on the OpenShift server" alias_action :"regions", :root_command => true def list regions = rest_client.regions raise RHC::NoRegionConfiguredException if regions.empty? paragraph{ say "Server #{options.server}" } regions.sort.each do |region| display_region(region) end paragraph do if regions.find{|r| r.allow_selection?}.blank? warn "Regions can't be explicitly provided by users and will be automatically selected by the system." else say "To create an app in a specific region use 'rhc create-app --region '." end end 0 end end end rhc-1.38.7/lib/rhc/commands/alias.rb0000644000004100000410000001276212756270551017201 0ustar www-datawww-datarequire 'rhc/commands/base' require 'rhc/config' module RHC::Commands class Alias < Base summary "Add or remove a custom domain name for an app" syntax "" description <<-DESC Each application may have one or more custom domain names (known as aliases) mapped to it. You may then configure your custom DNS entry CNAME to point to your OpenShift application to serve web requests from that name. Each alias may have a single SSL certificate associated with the name to handle SSL traffic. See the 'add' command for more info. DESC default_action :help summary "Add a custom domain name for the application" syntax " [--namespace NAME]" takes_application :argument => true argument :app_alias, "Custom domain name for the application", [] alias_action :"app add-alias", :root_command => true, :deprecated => true def add(app, app_alias) rest_app = find_app rest_app.add_alias(app_alias) success "Alias '#{app_alias}' has been added." 0 end summary "Remove a custom domain name for the application" syntax " [--namespace NAME]" takes_application :argument => true argument :app_alias, "Custom domain name for the application", [] alias_action :"app remove-alias", :root_command => true, :deprecated => true def remove(app, app_alias) rest_app = find_app rest_app.remove_alias(app_alias) success "Alias '#{app_alias}' has been removed." 0 end summary "Add or change the SSL certificate for an existing alias" description <<-DESC Add or update the SSL certificate for your custom domain alias to allow secure HTTPS communication with your app. Certificate files must be Base64 PEM-encoded and typically have a .crt or .pem extension. You may combine multiple certificates and certificate chains in a single file. The RSA or DSA private key must always be provided in a separate file. Pass phrase for the certificate private key is required if the provided private key is encrypted. DESC syntax " --certificate FILE --private-key FILE [--passphrase PASSPHRASE]" takes_application :argument => true argument :app_alias, "Custom domain name for the application (required)", [] option ["--certificate FILE"], "SSL certificate filepath (file in .crt or .pem format)", :required => true option ["--private-key FILE"], "Private key filepath for the given SSL certificate", :required => true option ["--passphrase PASSPHRASE"], "Private key pass phrase, required if the private key is encrypted", :required => false def update_cert(app, app_alias) certificate_file_path = options.certificate raise ArgumentError, "Certificate file not found: #{certificate_file_path}" if !File.exist?(certificate_file_path) || !File.file?(certificate_file_path) private_key_file_path = options.private_key raise ArgumentError, "Private key file not found: #{private_key_file_path}" if !File.exist?(private_key_file_path) || !File.file?(private_key_file_path) certificate_content = File.read(certificate_file_path) raise ArgumentError, "Invalid certificate file: #{certificate_file_path} is empty" if certificate_content.to_s.strip.length == 0 private_key_content = File.read(private_key_file_path) raise ArgumentError, "Invalid private key file: #{private_key_file_path} is empty" if private_key_content.to_s.strip.length == 0 rest_app = find_app rest_alias = rest_app.find_alias(app_alias) if rest_client.api_version_negotiated >= 1.4 rest_alias.add_certificate(certificate_content, private_key_content, options.passphrase) success "SSL certificate successfully added." 0 else raise RHC::Rest::SslCertificatesNotSupported, "The server does not support SSL certificates for custom aliases." end end summary "Delete the SSL certificate from an existing alias" syntax " " takes_application :argument => true argument :app_alias, "Custom domain name for the application (required)", [] option ["--confirm"], "Pass to confirm deleting the application" def delete_cert(app, app_alias) rest_app = find_app rest_alias = rest_app.find_alias(app_alias) if rest_client.api_version_negotiated >= 1.4 confirm_action "#{color("This is a non-reversible action! Your SSL certificate will be permanently deleted from application '#{app}'.", :yellow)}\n\nAre you sure you want to delete the SSL certificate?" rest_alias.delete_certificate success "SSL certificate successfully deleted." 0 else raise RHC::Rest::SslCertificatesNotSupported, "The server does not support SSL certificates for custom aliases." end end summary "List the aliases on an application" syntax "" takes_application :argument => true alias_action "aliases", :root_command => true def list(app) rest_app = find_app items = rest_app.aliases.map do |a| a.is_a?(String) ? [a, 'no', '-'] : [a.id, a.has_private_ssl_certificate? ? 'yes' : 'no', a.has_private_ssl_certificate? ? Date.parse(a.certificate_added_at) : '-'] end if items.empty? info "No aliases associated with the application #{app}." else say table(items, :header => ["Alias", "Has Certificate?", "Certificate Added"]) end 0 end end end rhc-1.38.7/lib/rhc/commands/setup.rb0000644000004100000410000000456312756270551017250 0ustar www-datawww-datarequire 'rhc/commands/base' require 'rhc/wizard' require 'rhc/config' module RHC::Commands class Setup < Base suppress_wizard summary "Connects to OpenShift and sets up your keys and domain" description <<-DESC Connects to an OpenShift server to get you started. Will help you configure your SSH keys, set up a domain, and check for any potential problems with Git or SSH. Any options you pass to the setup command will be stored in a .openshift/express.conf file in your home directory. If you run setup at a later time, any previous configuration will be reused. Pass the --clean option to ignore your saved configuration and only use options you pass on the command line. Pass --config FILE to use default values from another config (the values will still be written to .openshift/express.conf). If the server supports authorization tokens, you may pass the --create-token option to instruct the wizard to generate a key for you. If you would like to enable tab-completion in Bash shells, pass --autocomplete for more information. DESC option ["--server HOSTNAME"], "Hostname of an OpenShift server", :default => :server_context, :required => true option ['--clean'], "Ignore any saved configuration options" option ['--[no-]create-token'], "Create an authorization token for this server" option ['--autocomplete'], "Instructions for enabling tab-completion" def run if options.autocomplete src = File.join(File.join(Gem.loaded_specs['rhc'].full_gem_path, "autocomplete"), "rhc_bash") dest = File.join(RHC::Config.home_conf_dir, "bash_autocomplete") FileUtils.mkdir_p(RHC::Config.home_conf_dir) FileUtils.cp(src, dest) say <<-LINE.strip_heredoc To enable tab-completion for RHC under Bash shells, add the following command to your .bashrc or .bash_profile file: . #{dest} Save your shell and then restart. Type "rhc" and then hit the TAB key twice to trigger completion of your command. Tab-completion is not available in the Windows terminal. LINE return 0 end raise OptionParser::InvalidOption, "Setup can not be run with the --noprompt option" if options.noprompt RHC::RerunWizard.new(config, options).run ? 0 : 1 end end end rhc-1.38.7/lib/rhc/commands/scp.rb0000644000004100000410000000552412756270551016673 0ustar www-datawww-datarequire 'rhc/commands/base' require 'rhc/scp_helpers' module RHC::Commands class Scp < Base suppress_wizard summary "SCP a file to or from your application" description <<-DESC Transfer files to and from your applications using SCP. This will transfer files to and from your primary gear (the one with the Git repository and web cartridge) by default. Examples: Uploading a file from your working directory to your app-root/data directory rhc scp myapp upload somefile.txt app-root/data Downloading a file from your app-root/data directory to your working directory rhc scp myapp download ./ app-root/data/somebigarchive.tar.gz DESC syntax "[ --] " takes_application :argument => true argument :action, "Transfer direction: upload|download", ["-t", "--transfer-direction upload|download"], :optional => false argument :local_path, "Local filesystem path", ["-f", "--local-path file_path"], :optional => false argument :remote_path, "Remote filesystem path", ["-r", "--remote-path file_path"], :optional => false alias_action 'app scp', :root_command => true def run(_, action, local_path, remote_path) rest_app = find_app ssh_opts = rest_app.ssh_url.gsub("ssh://","").split("@") raise RHC::ArgumentNotValid.new("'#{action}' is not a valid argument for this command. Please use upload or download.") unless action == 'download' || action == 'upload' raise RHC::FileOrPathNotFound.new("Local file, file_path, or directory could not be found.") unless File.exist?(local_path) begin start_time = Time.now last_sent = nil Net::SCP.send("#{action}!".to_sym, ssh_opts[1], ssh_opts[0], (action == 'upload' ? local_path : remote_path), (action == 'upload' ? remote_path : local_path)) do |ch, name, sent, total| #:nocov: if sent != last_sent last_sent = sent complete = total == 0 ? 100 : ((sent.to_f/total.to_f)*100).to_i $stderr.print "\r #{action}ing #{name}: #{complete}% complete. #{sent}/#{total} bytes transferred " + (sent == total ? "in #{Time.now - start_time} seconds \n" : "") end #:nocov: end rescue Errno::ECONNREFUSED raise RHC::SSHConnectionRefused.new(ssh_opts[0], ssh_opts[1]) rescue SocketError => e raise RHC::ConnectionFailed, "The connection to #{ssh_opts[1]} failed: #{e.message}" rescue Net::SSH::AuthenticationFailed => e debug_error e raise RHC::SSHAuthenticationFailed.new(ssh_opts[1], ssh_opts[0]) rescue Net::SCP::Error => e debug_error e raise RHC::RemoteFileOrPathNotFound.new("An unknown error occurred: #{e.message}") end end protected include RHC::SCPHelpers end end rhc-1.38.7/lib/rhc/commands/env.rb0000644000004100000410000001262112756270551016672 0ustar www-datawww-datarequire 'rhc/commands/base' module RHC::Commands class Env < Base summary "Manages user-defined environment variables set on a given application" syntax "" description <<-DESC Manages the user-defined environment variables set on a given application. To see a list of all environment variables use the command 'rhc list-env '. Note that some predefined cartridge-level environment variables can also be overriden, but most variables provided by gears are read-only. Type 'rhc set-env --help' for more details. DESC default_action :help alias_action :"app env", :root_command => true summary "Set one or more environment variable(s) to your application" description <<-DESC Set one or more environment variable(s) to your application. Operands of the form 'VARIABLE=VALUE' set the environment variable VARIABLE to value VALUE. Example: rhc set-env VARIABLE1=VALUE1 VARIABLE2=VALUE2 -a myapp Environment variables can also be set from a file containing one or more VARIABLE=VALUE pairs (one per line). Example: rhc set-env /path/to/file -a myapp VALUE may be empty, in that case 'VARIABLE='. Setting a variable to an empty value is different from unsetting it. Some default cartridge-level variables can be overriden, but variables provided by gears are read-only. DESC syntax " [... ] [--namespace NAME] [--app NAME]" argument :env, "Environment variable name and value pair separated by an equal (=) sign, e.g. VARIABLE=VALUE", ["-e", "--env VARIABLE=VALUE"], :optional => false, :type => :list takes_application option ["--confirm"], "Pass to confirm setting the environment variable(s)" alias_action :add def set(env) rest_app = find_app with_file = env.index {|item| File.file? item} env_vars = [] env.each {|item| env_vars.concat(collect_env_vars(item))} raise RHC::EnvironmentVariableNotProvidedException.new( (with_file ? "Environment variable(s) not found in the provided file(s).\n" : "Environment variable(s) not provided.\n") << "Please provide at least one environment variable using the syntax VARIABLE=VALUE. VARIABLE can only contain letters, digits and underscore ('_') and can't begin with a digit.") if env_vars.empty? if with_file env_vars.each {|item| default_display_env_var(item.name, item.value)} confirm_action "Do you want to set these environment variables on '#{rest_app.name}?" end say 'Setting environment variable(s) ... ' rest_app.set_environment_variables(env_vars) success 'done' 0 end summary "Remove one or more environment variable(s) currently set to your application" description <<-DESC Remove one or more environment variable(s) currently set to your application. Setting a variable to an empty value is different from unsetting it. When unsetting a default cartridge- level variable previously overriden, the variable will be set back to its default value. DESC syntax " [... ] [--namespace NAME] [--app NAME]" argument :env, "Name of the environment variable(s), e.g. VARIABLE", ["-e", "--env VARIABLE"], :optional => false, :type => :list takes_application option ["--confirm"], "Pass to confirm removing the environment variable" alias_action :remove def unset(env) rest_app = find_app warn 'Removing environment variables is a destructive operation that may result in loss of data.' env.each do |e| default_display_env_var(e) end confirm_action "Are you sure you wish to remove the environment variable(s) above from application '#{rest_app.name}'?" say 'Removing environment variable(s) ... ' rest_app.unset_environment_variables(env) success 'done' 0 end summary "List all environment variables set on the application" description <<-DESC List all user-defined environment variables set on the application. Gear-level variables overriden by the 'rhc set-env' command will also be listed. DESC syntax " [--namespace NAME]" takes_application :argument => true option ["--table"], "Format the output list as a table" option ["--quotes"], "Format the output list with double quotes for env var values" alias_action :"envs", :root_command => true def list(app) rest_app = find_app rest_env_vars = rest_app.environment_variables pager display_env_var_list(rest_env_vars, { :table => options.table, :quotes => options.quotes }) 0 end summary "Show the value of one or more environment variable(s) currently set to your application" syntax " [... ] [--namespace NAME] [--app NAME]" argument :env, "Name of the environment variable(s), e.g. VARIABLE", ["-e", "--env VARIABLE"], :optional => false, :type => :list takes_application option ["--table"], "Format the output list as a table" option ["--quotes"], "Format the output list with double quotes for env var values" def show(env) rest_app = find_app rest_env_vars = rest_app.find_environment_variables(env) pager display_env_var_list(rest_env_vars, { :table => options.table, :quotes => options.quotes }) 0 end end end rhc-1.38.7/lib/rhc/commands/base.rb0000644000004100000410000001323112756270551017012 0ustar www-datawww-datarequire 'commander' require 'commander/delegates' require 'rhc/helpers' require 'rhc/wizard' require 'rhc/config' require 'rhc/commands' require 'rhc/exceptions' require 'rhc/context_helper' class RHC::Commands::Base attr_writer :options, :config def initialize(options=Commander::Command::Options.new, config=RHC::Config.new) @options, @config = options, config end protected include RHC::Helpers include RHC::ContextHelpers attr_reader :options, :config # Return a client object capable of making calls # to the OpenShift API that transforms intent # and options, to remote calls, and then handle # the output (or failures) into exceptions and # formatted object output. Most interactions # should be through this call pattern. def rest_client(opts={}) @rest_client ||= begin core_auth = if (options.ssl_client_cert_file && options.ssl_client_key_file) RHC::Auth::X509.new(options) else RHC::Auth::Basic.new(options) end # Specifying a username and password on the CLI trumps token # authentication. auth = if options.rhlogin && options.password RHC::Auth::Basic.new(options) elsif (options.use_authorization_tokens || options.token) RHC::Auth::Token.new(options, core_auth, token_store) else core_auth end debug "Authenticating with #{auth.class}" client_from_options(:auth => auth) end if opts[:min_api] && opts[:min_api].to_f > @rest_client.api_version_negotiated.to_f raise RHC::ServerAPINotSupportedException.new(opts[:min_api], @rest_client.api_version_negotiated) end @rest_client end def token_store @token_store ||= RHC::Auth::TokenStore.new(config.home_conf_path) end def help(*args) raise ArgumentError, "Please specify an action to take" end class InvalidCommand < StandardError ; end def self.method_added(method) return if self == RHC::Commands::Base return if private_method_defined? method return if protected_method_defined? method prefix = self.object_name method_name = method.to_s == 'run' ? nil : method.to_s.gsub("_", "-") name = [prefix, method_name].compact raise InvalidCommand, "Either object_name must be set or a non default method defined" if name.empty? aliases.each{ |a| a[:action].unshift(prefix) unless a[:root_command] } if prefix RHC::Commands.add((@options || {}).merge({ :name => name, :class => self, :method => method })); @options = nil end def self.object_name(value=nil) @object_name ||= begin value ||= if self.name && !self.name.empty? self.name.split('::').last end value.to_s.split(/(?=[A-Z])/).join('-').downcase if value end end def self.description(*args) o = args.join(' ') options[:description] = o.strip_heredoc end def self.summary(value) options[:summary] = value end def self.syntax(value) options[:syntax] = value end #def self.deprecated(msg) # options[:deprecated] = msg #end def self.suppress_wizard @suppress_wizard = true end def self.suppress_wizard? @suppress_wizard end # # Provide an alias to the command. The alias will not be shown in help, but will # be available in autocompletion and at execution time. # # Supported options: # # :deprecated - if true, a warning will be displayed when the command is executed # :root_command - if true, do not prepend the object name to the command # def self.alias_action(action, options={}) options[:action] = action.is_a?(Array) ? action : action.to_s.split(' ') aliases << options end def self.option(switches, description, options={}) options_metadata << {:switches => switches, :description => description, :required => options[:required], :covered_by => options[:covered_by], :deprecated => options[:deprecated], :type => options[:type], :hide => options[:hide], :default => options[:default], } end def self.argument(name, description, switches=[], options={}) arg_type = options[:type] option_symbol = Commander::Runner.switch_to_sym(switches.last) args_metadata << {:name => name, :description => description, :switches => switches, :option_symbol => option_symbol, :covered_by => options[:covered_by], :optional => options[:optional], :default => options[:default], :allow_nil => options[:allow_nil], :hide => options[:hide], :type => arg_type} end def self.default_action(action) options[:default] = action unless action == :help name = self.object_name raise InvalidCommand, "object_name must be set" if name.empty? RHC::Commands.add((@options || {}).merge({ :name => name, :class => self, :method => options[:default] })); end private def self.options_metadata options[:options] ||= [] end def self.args_metadata options[:args] ||= [] end def self.aliases options[:aliases] ||= [] end def self.options @options ||= {} end end rhc-1.38.7/lib/rhc/commands/app.rb0000644000004100000410000010124412756270551016662 0ustar www-datawww-datarequire 'rhc/commands/base' require 'resolv' require 'rhc/git_helpers' require 'rhc/cartridge_helpers' require 'rhc/deployment_helpers' require 'optparse' require 'tmpdir' module RHC::Commands class App < Base summary "Commands for creating and managing applications" description <<-DESC Creates and controls an OpenShift application. To see the list of all applications use the rhc domain show command. Note that delete is not reversible and will stop your application and then remove the application and repo from the remote server. No local changes are made. DESC syntax "" default_action :help suppress_wizard summary "Create an application" description <<-DESC Create an application. Every OpenShift application must have one web cartridge which serves web requests, and can have a number of other cartridges which provide capabilities like databases, scheduled jobs, or continuous integration. You can see a list of all valid cartridge types by running 'rhc cartridge list'. OpenShift also supports downloading cartridges - pass a URL in place of the cartridge name and we'll download and install that cartridge into your app. Keep in mind that these cartridges receive no security updates. Note that not all OpenShift servers allow downloaded cartridges. When your application is created, a URL combining the name of your app and the name of your domain will be registered in DNS. A copy of the code for your application will be checked out locally into a folder with the same name as your application. Note that different types of applications may require different folder structures - check the README provided with the cartridge if you have questions. OpenShift runs the components of your application on small virtual servers called "gears". Each account or plan is limited to a number of gears which you can use across multiple applications. Some accounts or plans provide access to gears with more memory or more CPU. Run 'rhc account' to see the number and sizes of gears available to you. When creating an application the --gear-size parameter may be specified to change the gears used. DESC syntax " [... ] [... VARIABLE=VALUE] [-n namespace]" option ["-n", "--namespace NAME"], "Namespace for the application" option ["-g", "--gear-size SIZE"], "Gear size controls how much memory and CPU your cartridges can use." option ["-s", "--[no-]scaling"], "Enable scaling for the web cartridge." option ["-r", "--repo DIR"], "Path to the Git repository (defaults to ./$app_name)" option ["-e", "--env VARIABLE=VALUE"], "Environment variable(s) to be set on this app, or path to a file containing environment variables", :type => :list option ["--from-app NAME"], "Create based on another application. All content and configurations will be copied from the original app." option ["--from-code URL"], "URL to a Git repository that will become the initial contents of the application" option ["--region REGION"], "The region where the application gears will be located" option ["--[no-]git"], "Skip creating the local Git repository." option ["--[no-]dns"], "Skip waiting for the application DNS name to resolve. Must be used in combination with --no-git" option ['--no-keys'], "Skip checking SSH keys during app creation", :hide => true option ["--enable-jenkins [NAME]"], "Enable Jenkins builds for this application (will create a Jenkins application if not already available). The default name will be 'jenkins' if not specified." argument :name, "Name for your application", ["-a", "--app NAME"], :optional => true argument :cartridges, "The web framework this application should use", ["-t", "--type CARTRIDGE"], :optional => true, :type => :list def create(name, cartridges) check_config! check_name!(name) arg_envs, cartridges = cartridges.partition{|item| item.match(env_var_regex_pattern)} rest_domain = check_domain! rest_app = nil repo_dir = nil if options.from_app raise RHC::AppCloneNotSupportedException, "The server does not support creating apps based on others (rhc create-app --from-app)." if (!rest_domain.has_param?('ADD_APPLICATION', 'cartridges[][name]') || !rest_domain.has_param?('ADD_APPLICATION', 'cartridges[][url]')) raise ArgumentError, "Option --from-code is incompatible with --from-app. When creating an app based on another resource you can either specify a Git repository URL with --from-code or an existing app name with --from-app." if options.from_code raise ArgumentError, "Option --no-dns is incompatible with --from-app. We need to propagate the new app DNS to be able to configure it." if options.dns == false raise ArgumentError, "Do not specify cartridges when creating an app based on another one. All cartridges will be copied from the original app." if !(cartridges || []).empty? from_app = find_app(:app => options.from_app) arg_envs = from_app.environment_variables.collect {|env| "#{env.name}=#{env.value}"} + arg_envs cartridges = from_app.cartridges.reject{|c| c.tags.include?('web_proxy')}.collect{|c| c.custom? ? c.url : c.name} end cartridges = check_cartridges(cartridges, &require_one_web_cart) options.default \ :dns => true, :git => true raise ArgumentError, "You have named both your main application and your Jenkins application '#{name}'. In order to continue you'll need to specify a different name with --enable-jenkins or choose a different application name." if jenkins_app_name == name && enable_jenkins? cart_names = cartridges.collect do |c| c.usage_rate? ? "#{c.short_name} (addtl. costs may apply)" : c.short_name end.join(', ') env = collect_env_vars(arg_envs.concat(Array(options.env))) if options.env && env.empty? raise RHC::EnvironmentVariableNotProvidedException.new( "Environment variable(s) not provided.\n" + "Please provide at least one environment variable using the syntax VARIABLE=VALUE. VARIABLE can only contain letters, digits and underscore ('_') and can't begin with a digit.") end if env.present? && !rest_domain.supports_add_application_with_env_vars? env = [] warn "Server does not support environment variables." end scaling = options.scaling region = options.region gear_profile = options.gear_size ha = nil raise RHC::RegionsAndZonesNotSupportedException if region.present? && !rest_client.supports_regions_and_zones? if from_app scaling = from_app.scalable if scaling.nil? region = from_app.region if region.nil? gear_profile = from_app.gear_profile if gear_profile.nil? ha = from_app.ha? if !from_app.ha.nil? if region.present? && !rest_client.allows_region_selection? region = nil warn 'Server does not allow selecting regions. Region is being ignored.' end cartridges = from_app.cartridges.reject{|c| c.tags.include?('web_proxy')}.collect do |cartridge| { :name => (cartridge.name if !cartridge.custom?), :url => (cartridge.url if cartridge.custom?), :gear_size => options.gear_size || cartridge.gear_profile, :additional_gear_storage => (cartridge.additional_gear_storage if cartridge.additional_gear_storage > 0), :scales_from => (cartridge.scales_from if scaling && cartridge.scalable?), :scales_to => (cartridge.scales_to if scaling && cartridge.scalable?) }.reject{|k,v| v.nil? } end end paragraph do header "Application Options" say table([["Domain:", options.namespace], ["Cartridges:", cart_names], (["Source Code:", options.from_code] if options.from_code), (["From app:", from_app.name] if from_app), ["Gear Size:", options.gear_size || (from_app ? "Copied from '#{from_app.name}'" : "default")], ["Scaling:", (scaling ? "yes" : "no") + (from_app && options.scaling.nil? ? " (copied from '#{from_app.name}')" : '')], (["HA:", (ha ? "yes" : "no") + (from_app ? " (copied from '#{from_app.name}')" : '')] if ha.present?), (["Environment Variables:", env.map{|item| "#{item.name}=#{item.value}"}.join(', ')] if env.present?), (["Region:", region + (from_app && options.region.nil? ? " (copied from '#{from_app.name}')" : '')] if region), ].compact ) end paragraph do say "Creating application '#{name}' ... " # create the main app rest_app = create_app(name, cartridges, rest_domain, gear_profile, scaling, options.from_code, env, options.auto_deploy, options.keep_deployments, options.deployment_branch, options.deployment_type, region, ha) success "done" paragraph{ indent{ success rest_app.messages.map(&:strip) } } end build_app_exists = rest_app.building_app if enable_jenkins? unless build_app_exists paragraph do say "Setting up a Jenkins application ... " begin build_app_exists = add_jenkins_app(rest_domain) success "done" paragraph{ indent{ success build_app_exists.messages.map(&:strip) } } rescue Exception => e warn "not complete" add_issue("Jenkins failed to install - #{e}", "Installing jenkins and jenkins-client", "rhc create-app jenkins jenkins-1", "rhc add-cartridge jenkins-client -a #{rest_app.name}") end end end paragraph do messages = [] add_jenkins_client_to(rest_app, messages) paragraph{ indent{ success messages.map(&:strip) } } end if build_app_exists end debug "Checking SSH keys through the wizard" check_sshkeys! unless options.no_keys if options.dns paragraph do say "Waiting for your DNS name to be available ... " if dns_propagated? rest_app.host success "done" else warn "failure" add_issue("We were unable to lookup your hostname (#{rest_app.host}) in a reasonable amount of time and can not clone your application.", "Clone your git repo", "rhc git-clone #{rest_app.name}") output_issues(rest_app) return 0 end end end if from_app say "Setting deployment configuration ... " rest_app.configure({:auto_deploy => from_app.auto_deploy, :keep_deployments => from_app.keep_deployments , :deployment_branch => from_app.deployment_branch, :deployment_type => from_app.deployment_type}) success 'done' snapshot_filename = temporary_snapshot_filename(from_app.name) save_snapshot(from_app, snapshot_filename) restore_snapshot(rest_app, snapshot_filename) File.delete(snapshot_filename) if File.exist?(snapshot_filename) paragraph { warn "The application '#{from_app.name}' has aliases set which were not copied. Please configure the aliases of your new application manually." } unless from_app.aliases.empty? end if options.git section(:now => true, :top => 1, :bottom => 1) do begin if has_git? repo_dir = git_clone_application(rest_app) else warn "You do not have git installed, so your application's git repo will not be cloned" end rescue RHC::GitException => e warn "#{e}" unless RHC::Helpers.windows? and windows_nslookup_bug?(rest_app) add_issue("We were unable to clone your application's git repo - #{e}", "Clone your git repo", "rhc git-clone #{rest_app.name}") end end end end output_issues(rest_app) if issues? paragraph do say "Your application '#{rest_app.name}' is now available." paragraph do indent do say table [ ['URL:', rest_app.app_url], ['SSH to:', rest_app.ssh_string], ['Git remote:', rest_app.git_url], (['Cloned to:', repo_dir] if repo_dir) ].compact end end end paragraph{ say "Run 'rhc show-app #{name}' for more details about your app." } 0 end summary "Delete an application from the server" description "Deletes your application and all of its data from the server.", "Use with caution as this operation is permanent." syntax " [--namespace NAME]" takes_application :argument => true option ["--confirm"], "Pass to confirm deleting the application" alias_action :destroy, :deprecated => true def delete(app) rest_app = find_app confirm_action "#{color("This is a non-reversible action! Your application code and data will be permanently deleted if you continue!", :yellow)}\n\nAre you sure you want to delete the application '#{app}'?" say "Deleting application '#{rest_app.name}' ... " rest_app.destroy success "deleted" paragraph{ rest_app.messages.each{ |s| success s } } 0 end summary "Start the application" syntax " [--namespace NAME]" takes_application :argument => true def start(app) app_action :start results { say "#{app} started" } 0 end summary "Stop the application" syntax " [--namespace NAME]" takes_application :argument => true def stop(app) app_action :stop results { say "#{app} stopped" } 0 end summary "Scale up the application's web cartridge" syntax " [--namespace NAME]" takes_application :argument => true def scale_up(app) app_action :scale_up results { say "#{app} scaled up" } 0 end summary "Scale down the application's web cartridge" syntax " [--namespace NAME]" takes_application :argument => true def scale_down(app) app_action :scale_down results { say "#{app} scaled down" } 0 end summary "Stops all application processes" syntax " [--namespace NAME]" takes_application :argument => true def force_stop(app) app_action :stop, true results { say "#{app} force stopped" } 0 end summary "Restart the application" syntax " [--namespace NAME]" takes_application :argument => true def restart(app) app_action :restart results { say "#{app} restarted" } 0 end summary "Reload the application's configuration" syntax " [--namespace NAME]" takes_application :argument => true def reload(app) app_action :reload results { say "#{app} config reloaded" } 0 end summary "Clean out the application's logs and tmp directories and tidy up the git repo on the server" syntax " [--namespace NAME]" takes_application :argument => true def tidy(app) app_action :tidy results { say "#{app} cleaned up" } 0 end summary "Make the application highly available" syntax " [--namespace NAME]" takes_application :argument => true def enable_ha(app) app_action :enable_ha results { say "#{app} is now highly available" } 0 end summary "Show information about an application" description <<-DESC Display the properties of an application, including its URL, the SSH connection string, and the Git remote URL. Will also display any cartridges, their scale, and any values they expose. The '--state' option will retrieve information from each cartridge in the application, which may include cartridge specific text. The '--configuration' option will display configuration values set in the application. Use 'rhc configure-app' to configure. To see information about the individual gears within an application, use '--gears', including whether they are started or stopped and their SSH host strings. Passing '--gears quota' will show the free and maximum storage on each gear. If you want to run commands against individual gears, use: rhc ssh --gears '' to run and display the output from each gear. DESC syntax " [--namespace NAME]" takes_application :argument => true option ["--state"], "Get the current state of the cartridges in this application" option ["--configuration"], "Get the current configuration values set in this application" option ["--gears [quota|ssh]"], "Show information about the cartridges on each gear in this application. Pass 'quota' to see per gear disk usage and limits. Pass 'ssh' to print only the SSH connection strings of each gear." option ["-v", "--verbose"], "Display more details about the application's cartridges" def show(app_name) if options.state find_app(:with_gear_groups => true).each do |gg| say "Cartridge #{gg.cartridges.collect { |c| c['name'] }.join(', ')} is #{gear_group_state(gg.gears.map{ |g| g['state'] })}" end elsif options.gears && options.gears != true groups = find_app(:with_gear_groups => true) case options.gears when 'quota' opts = {:as => :gear, :split_cells_on => /\s*\t/, :header => ['Gear', 'Cartridges', 'Used', 'Limit'], :align => [nil, nil, :right, :right]} table_from_gears('echo "$(du --block-size=1 -s 2>/dev/null | cut -f 1)"', groups, opts) do |gear, data, group| [gear['id'], group.cartridges.collect{ |c| c['name'] }.join(' '), (human_size(data.chomp) rescue 'error'), human_size(group.quota)] end when 'ssh' groups.each{ |group| group.gears.each{ |g| say (ssh_string(g['ssh_url']) or raise NoPerGearOperations) } } else run_on_gears(ssh_command_for_op(options.gears), groups) end elsif options.gears domain, app = discover_domain_and_app gear_info = rest_client.find_application_gear_groups_endpoints(domain, app).map do |group| group.gears.map do |gear| color_cart = if gear['endpoints'].present? e = gear['endpoints'].collect{ |c| c['cartridge_name'] }.uniq lambda { |c| e.include?(c) ? color(c, :green) : c } else lambda { |c| c } end [ gear['id'], gear['state'] == 'started' ? color(gear['state'], :green) : color(gear['state'], :yellow), group.cartridges.collect{ |c| color_cart.call(c['name']) }.join(' '), group.gear_profile, gear['region'], gear['zone'], ssh_string(gear['ssh_url']) ] end end.flatten(1) explicit_regions = gear_info.select{|i| !i[4].nil?}.present? explicit_zones = gear_info.select{|i| !i[5].nil?}.present? say table(gear_info.map(&:compact), :header => ['ID', 'State', 'Cartridges', 'Size', explicit_regions ? 'Region' : nil, explicit_zones ? 'Zone' : nil, 'SSH URL'].compact) elsif options.configuration display_app_configurations(find_app) paragraph { say "Use 'rhc configure-app' to change the configuration values of this application." } else app = find_app(:include => :cartridges) display_app(app, app.cartridges, nil, options.verbose) end 0 end summary "Deploy a git reference or binary file of an application" syntax " --app NAME [--namespace NAME]" description <<-DESC By default OpenShift applications prepare, distribute, and activate deployments on every git push. Alternatively, a user may choose to disable automatic deployments and use 'rhc deploy' and 'rhc deployment' commands to fully control the deployment lifecycle. Use this command to prepare, distribute and deploy manually from a git reference (commit id, tag or branch) or from a binary file. Check also 'rhc configure-app' to configure your application to deploy manually and set the number of deployments to keep in history. DESC takes_application argument :ref, "Git tag, branch or commit id or path to binary file to be deployed", ["--ref REF"], :optional => false option "--[no-]hot-deploy", "Perform hot deployment according to the specified argument rather than checking for the presence of the hot_deploy marker in the application git repo" option "--[no-]force-clean-build", "Perform a clean build according to the specified argument rather than checking for the presence of the force_clean_build marker in the application git repo" alias_action :"deploy", :root_command => true def deploy(ref) rest_app = find_app raise RHC::DeploymentsNotSupportedException.new if !rest_app.supports? "DEPLOY" deploy_artifact(rest_app, ref, options.hot_deploy, options.force_clean_build) 0 end summary "Configure several properties that apply to an application" syntax " [--[no-]auto-deploy] [--keep-deployments INTEGER] [--deployment-branch BRANCH] [--deployment-type TYPE] [--namespace NAME]" takes_application :argument => true option ["--[no-]auto-deploy"], "Build and deploy automatically when pushing to the git repo. Defaults to true." option ["--keep-deployments INTEGER", Integer], "Number of deployments to preserve. Defaults to 1." option ["--deployment-branch BRANCH"], "Which branch should trigger an automatic deployment, if automatic deployment is enabled with --auto-deploy. Defaults to master." option ["--deployment-type git|binary"], "Type of deployment the application accepts ('git' or 'binary'). Defaults to git." def configure(app_name) rest_app = find_app app_options = {} app_options[:auto_deploy] = options.auto_deploy if !options.auto_deploy.nil? app_options[:keep_deployments] = options.keep_deployments if options.keep_deployments app_options[:deployment_branch] = options.deployment_branch if options.deployment_branch app_options[:deployment_type] = options.deployment_type if options.deployment_type if app_options.present? paragraph do say "Configuring application '#{app_name}' ... " rest_app.configure(app_options) success "done" end end paragraph { display_app(find_app, nil, [:auto_deploy, :keep_deployments, :deployment_type, :deployment_branch]) } paragraph { say "Your application '#{rest_app.name}' is #{app_options.empty? ? '' : 'now '}configured as listed above." } paragraph { say "Use 'rhc show-app #{rest_app.name} --configuration' to check your configuration values any time." } if app_options.present? 0 end private include RHC::GitHelpers include RHC::CartridgeHelpers include RHC::SSHHelpers include RHC::DeploymentHelpers MAX_RETRIES = 7 DEFAULT_DELAY_THROTTLE = 2.0 def require_one_web_cart lambda{ |carts| match, ambiguous = carts.partition{ |c| not c.is_a?(Array) } selected_web = match.any?{ |c| not c.only_in_existing? } possible_web = ambiguous.flatten.any?{ |c| not c.only_in_existing? } if not (selected_web or possible_web) section(:bottom => 1){ list_cartridges(standalone_cartridges) } raise RHC::CartridgeNotFoundException, "Every application needs a web cartridge to handle incoming web requests. Please provide the short name of one of the carts listed above." end if selected_web carts.map! &other_carts_only elsif possible_web && ambiguous.length == 1 carts.map! &web_carts_only end } end def check_sshkeys! return unless interactive? RHC::SSHWizard.new(rest_client, config, options).run end def check_name!(name) return unless name.blank? paragraph{ say "When creating an application, you must provide a name and a cartridge from the list below:" } paragraph{ list_cartridges(standalone_cartridges) } raise ArgumentError, "Please specify the name of the application and the web cartridge to install" end def check_config! return if not interactive? or (!options.clean && config.has_local_config?) or (options.server && (options.rhlogin || options.token)) RHC::EmbeddedWizard.new(config, options).run end def check_domain! if options.namespace rest_client.find_domain(options.namespace) else if rest_client.domains.empty? raise RHC::Rest::DomainNotFoundException, "No domains found. Please create a domain with 'rhc create-domain ' before creating applications." unless interactive? RHC::DomainWizard.new(config, options, rest_client).run end domain = rest_client.domains.first raise RHC::Rest::DomainNotFoundException, "No domains found. Please create a domain with 'rhc create-domain ' before creating applications." unless domain options.namespace = domain.name domain end end def gear_group_state(states) return states[0] if states.length == 1 || states.uniq.length == 1 "#{states.select{ |s| s == 'started' }.count}/#{states.length} started" end def app_action(action, *args) rest_app = find_app result = rest_app.send action, *args result end def create_app(name, cartridges, rest_domain, gear_profile=nil, scale=nil, from_code=nil, environment_variables=nil, auto_deploy=nil, keep_deployments=nil, deployment_branch=nil, deployment_type=nil, region=nil, ha=nil) app_options = {:cartridges => Array(cartridges)} app_options[:gear_profile] = gear_profile if gear_profile app_options[:scale] = scale if scale app_options[:initial_git_url] = from_code if from_code app_options[:debug] = true if @debug app_options[:environment_variables] = environment_variables.map{|i| i.to_hash}.group_by{|i| i[:name]}.values.map(&:last) if environment_variables.present? app_options[:auto_deploy] = auto_deploy if !auto_deploy.nil? app_options[:keep_deployments] = keep_deployments if keep_deployments app_options[:deployment_branch] = deployment_branch if deployment_branch app_options[:deployment_type] = deployment_type if deployment_type app_options[:region] = region if region app_options[:ha] = ha if ha debug "Creating application '#{name}' with these options - #{app_options.inspect}" rest_domain.add_application(name, app_options) rescue RHC::Rest::Exception => e if e.code == 109 paragraph{ say "Valid cartridge types:" } paragraph{ list_cartridges(standalone_cartridges) } end raise end def add_jenkins_app(rest_domain) create_app(jenkins_app_name, jenkins_cartridge_name, rest_domain) end def add_jenkins_cartridge(rest_app) rest_app.add_cartridge(jenkins_client_cartridge_name) end def add_jenkins_client_to(rest_app, messages) say "Setting up Jenkins build ... " successful, attempts, exit_code, exit_message = false, 1, 157, nil while (!successful && exit_code == 157 && attempts < MAX_RETRIES) begin cartridge = add_jenkins_cartridge(rest_app) successful = true success "done" messages.concat(cartridge.messages) rescue RHC::Rest::ServerErrorException => e if (e.code == 157) # error downloading Jenkins /jnlpJars/jenkins-cli.jar attempts += 1 debug "Jenkins server could not be contacted, sleep and then retry: attempt #{attempts}\n #{e.message}" Kernel.sleep(10) end exit_code = e.code exit_message = e.message rescue Exception => e # timeout and other exceptions exit_code = 1 exit_message = e.message end end unless successful warn "not complete" add_issue("Jenkins client failed to install - #{exit_message}", "Install the jenkins client", "rhc add-cartridge jenkins-client -a #{rest_app.name}") end end def dns_propagated?(host, sleep_time=2) # # Confirm that the host exists in DNS # debug "Start checking for application dns @ '#{host}'" found = false # Allow DNS to propagate Kernel.sleep 5 # Now start checking for DNS host_found = hosts_file_contains?(host) or 1.upto(MAX_RETRIES) { |i| host_found = host_exists?(host) break found if host_found say " retry # #{i} - Waiting for DNS: #{host}" Kernel.sleep sleep_time.to_i sleep_time *= DEFAULT_DELAY_THROTTLE } debug "End checking for application dns @ '#{host} - found=#{host_found}'" host_found end def enable_jenkins? # legacy issue, commander 4.0.x will place the option in the hash with nil value (BZ878407) options.__hash__.has_key?(:enable_jenkins) end def jenkins_app_name if options.enable_jenkins.is_a? String options.enable_jenkins end || "jenkins" end def jenkins_cartridge_name jenkins_cartridges.last.name end def jenkins_client_cartridge_name jenkins_client_cartridges.last.name end def run_nslookup(host) # :nocov: `nslookup #{host}` $?.exitstatus == 0 # :nocov: end def run_ping(host) # :nocov: `ping #{host} -n 2` $?.exitstatus == 0 # :nocov: end def windows_nslookup_bug?(rest_app) windows_nslookup = run_nslookup(rest_app.host) windows_ping = run_ping(rest_app.host) if windows_nslookup and !windows_ping # this is related to BZ #826769 issue = < reason, :commands_header => commands_header, :commands => commands} @issues << issue end def format_issues(indent) return nil unless issues? indentation = " " * indent reasons = "" steps = "" @issues.each_with_index do |issue, i| reasons << "#{indentation}#{i+1}. #{issue[:reason].strip}\n" steps << "#{indentation}#{i+1}. #{issue[:commands_header].strip}\n" issue[:commands].each { |cmd| steps << "#{indentation} $ #{cmd}\n" } end [reasons, steps] end def issues? not @issues.nil? end def temporary_snapshot_filename(app_name) "#{Dir.tmpdir}/#{app_name}_temp_clone.tar.gz" end end end rhc-1.38.7/lib/rhc/commands/authorization.rb0000644000004100000410000001117412756270551021004 0ustar www-datawww-datamodule RHC::Commands class Authorization < Base summary "Manage your authorization tokens" syntax "" description <<-DESC An authorization token grants access to the OpenShift REST API with a set of privileges called 'scopes' for a limited time. You can add an optional note to each authorization token to assist you in remembering why it was created. To see all your authorizations, run 'rhc authorizations'. To view the list of scopes supported by this server, run the 'rhc add-authorization' command with no arguments. These commands manage your authorization tokens on the server - if you want to clear your authorization tokens from the current machine use 'rhc logout' DESC default_action :help summary "Show the authorization tokens for your account" description <<-DESC Shows the full list of authorization tokens on your account. You can add, edit, or delete authorizations with subcommands. An authorization token grants access to the OpenShift REST API with a set of privileges called 'scopes' for a limited time. You can add an optional note to each authorization token to assist you in remembering what is available. DESC alias_action 'authorizations', :root_command => true def list rest_client.authorizations.each{ |auth| paragraph{ display_authorization(auth, token_for_user) } } or info "No authorizations" 0 end option "--scopes SCOPES", "A comma delimited list of scopes (e.g. 'scope1,scope2')" option "--note NOTE", "A description of this authorization (optional)" option "--expires-in SECONDS", "The number of seconds before this authorization expires (optional)" summary "Add an authorization to your account" syntax "--scopes SCOPES [--note NOTE] [--expires-in SECONDS]" description <<-DESC Add an authorization to your account. An authorization token grants access to the OpenShift REST API with a set of privileges called 'scopes' for a limited time. You can add an optional note to each authorization token to assist you in remembering what is available. To view the list of scopes supported by this server, run this command without any options. You may pass multiple scopes to the --scopes option inside of double quotes (--scopes \"scope1 scope2\") or by separating them with commas (--scopes scope1,scope2). The server will enforce a maximum and default expiration that may differ for each scope. If you request an expiration longer than the server maximum, you will be given the default value. DESC def add unless options.scopes.to_s.strip.present? say "When adding an authorization, you must specify which permissions clients will have." scope_help say "Run 'rhc authorization add --help' to see more options" return 0 end say "Adding authorization ... " auth = rest_client.add_authorization(:scope => options.scopes, :note => options.note, :expires_in => options.expires_in) success "done" paragraph{ display_authorization(auth) } 0 end summary "Delete one or more authorization tokens" syntax " [...]" description <<-DESC Delete one or more of the authorization tokens associated with your account. After deletion, any clients using the token will no longer have access to OpenShift and will need to reauthenticate. DESC argument :auth_token, "The token you wish to delete", ['--auth-token TOKEN'], :type => :list def delete(tokens) raise ArgumentError, "You must specify one or more tokens to delete" if tokens.blank? say "Deleting authorization ... " tokens.each{ |token| rest_client.delete_authorization(token) } success "done" 0 end summary "Delete all authorization tokens from your account" description <<-DESC Delete all the authorization tokens associated with your account. After deletion, any clients using those tokens will need to reauthenticate. DESC def delete_all say "Deleting all authorizations ... " rest_client.delete_authorizations success "done" 0 end protected def scope_help descriptions = rest_client.authorization_scope_list paragraph{ say table(descriptions, :header => ['Scope', 'Description']) } paragraph{ say "You may pass multiple scopes to the --scopes option inside of double quotes (--scopes \"scope1 scope2\") or by separating them with commas (--scopes scope1,scope2)." } end end end rhc-1.38.7/lib/rhc/commands/server.rb0000644000004100000410000002664212756270551017420 0ustar www-datawww-datarequire 'rhc/servers' module RHC::Commands class Server < Base suppress_wizard summary "Manage your configured servers and check the status of services" description <<-DESC The 'rhc server' commands allow users to add multiple OpenShift servers to interact with the rhc commands and easily switch between them. For example, if an user's company has installations of OpenShift Origin (development) and Enterprise (production) and the user also has a personal OpenShift Online account: rhc add-server openshift.redhat.com online -l personal@email.com rhc add-server origin.openshift.mycompany.com development -l user@company.com rhc add-server enterprise.openshift.mycompany.com production -l user@company.com Then, to switch between the servers: rhc use-server online rhc use-server development rhc use-server production To list all servers configured: rhc servers DESC default_action :help summary "Display information about the status of the OpenShift server" syntax "" description <<-DESC Retrieves any open issues or notices about the operation of the OpenShift service and displays them in the order they were opened. When connected to an OpenShift Enterprise or Origin server, will only display the version of the API that it is connecting to. DESC argument :server, "Server hostname or nickname to check. If not provided the default server will be used.", ["--server SERVER"], :optional => true def status(server=nil) options.server = server.hostname if server && server = (server_configs.find(server) rescue nil) say "Connected to #{openshift_server}" if openshift_online_server? status = rest_client.request(:method => :get, :url => "#{openshift_url}/app/status/status.json", :lazy_auth => true){ |res| decode_json(res.content) } open = status['open'] (success 'All systems running fine' and return 0) if open.blank? open.each do |i| i = i['issue'] say color("%-3s %s" % ["##{i['id']}", i['title']], :bold) items = i['updates'].map{ |u| [date(u['created_at']), u['description']] } items.unshift [date(i['created_at']), 'Opened'] say table(items, :align => [nil,:left], :join => ' ') end say "\n" warn pluralize(open.length, "open issue") open.length #exit with the count of open items else success "Using API version #{rest_client.api_version_negotiated}" 0 end end summary "Add a new server" description <<-DESC Add and configure a new OpenShift server that will be available to use through rhc commands. When adding a new server users can optionally provide a 'nickname' that will allow to easily switch between servers. DESC syntax " [] [--rhlogin LOGIN] [--[no-]use-authorization-tokens] [--[no-]insecure] [--use] [--skip-wizard] [--timeout SECONDS] [--ssl-ca-file FILE] [--ssl-client-cert-file FILE] [--ssl-version VERSION]" argument :hostname, "Hostname of the server you are adding", ["--server HOSTNAME"] argument :nickname, "Optionally provide a nickname to the server you are adding (e.g. 'development', 'production', 'online')", ["--nickname NICKNAME"], :optional => true option ["-l", "--rhlogin LOGIN"], "Change the default OpenShift login used on this server" option ["--[no-]use-authorization-tokens"], "Server will attempt to create and use authorization tokens to connect to the server" option ["--[no-]insecure"], "If true, certificate errors will be ignored" option ["--use"], "If provided, the server being added will be set as default (same as 'rhc server use')" option ["--skip-wizard"], "If provided, the wizard will be skipped and a session token will not be estabilished" option ["--timeout SECONDS"], "The default timeout for operations on this server", :type => Integer option ["--ssl-ca-file FILE"], "An SSL certificate CA file (may contain multiple certs) to be used on this server", :type => CertificateFile, :optional => true option ["--ssl-client-cert-file FILE"], "An SSL x509 client certificate file to be used on this server", :type => CertificateFile, :optional => true option ["--ssl-client-key-file FILE"], "An RSA client certificate key", :type => CertificateKey, :optional => true option ["--ssl-version VERSION"], "The version of SSL to use to be used on this server", :type => SSLVersion, :optional => true def add(hostname, nickname) raise ArgumentError, "The --use and --skip-wizard options cannot be used together." if options.use && options.skip_wizard attrs = [:login, :use_authorization_tokens, :insecure, :timeout, :ssl_version, :ssl_client_cert_file, :ssl_client_key_file, :ssl_ca_file] server = server_configs.add(hostname, attrs.inject({:nickname => nickname}){ |h, (k, v)| h[k] = options[k == :login ? :rhlogin : k]; h }) unless options.skip_wizard (wizard_to_server(server.hostname, options.use, attrs.inject({}){ |h, (k, v)| h[k] = server.send(k); h }) ? 0 : 1).tap do |r| paragraph { success "Now using '#{server.hostname}'" } if options.use && r == 0 end else say "Saving server configuration to #{system_path(server_configs.path)} ... " server_configs.save! success "done" 0 end end summary "List all configured servers" alias_action :"servers", :root_command => true def list servers = config.has_configs_from_files? ? server_configs.list : [] servers.sort.each do |server| say display_server(server) end paragraph do case servers.length when 0 warn "You don't have any servers configured. Use 'rhc setup' to configure your OpenShift server." when 1 say "You have 1 server configured. Use 'rhc server add' to add a new server." else say "You have #{servers.length} servers configured. Use 'rhc server use ' to switch between them." end end 0 end summary "Change the default server" syntax "" argument :server, "Server hostname or nickname to use", ["--server SERVER"] def use(server) server = server_configs.find(server) attrs = [:login, :use_authorization_tokens, :insecure, :timeout, :ssl_version, :ssl_client_cert_file, :ssl_client_key_file, :ssl_ca_file] if wizard_to_server(server.hostname, true, attrs.inject({}){ |h, (k, v)| h[k] = server.send(k); h }) paragraph { success "Now using '#{server.hostname}'" } 0 else 1 end end summary "Remove a server" syntax "" argument :server, "Server hostname or nickname to be removed", ["--server SERVER"] def remove(server) server = server_configs.find(server) say "Removing '#{server.hostname}' ... " if server.default? raise RHC::ServerInUseException.new("The '#{server.designation}' server is in use. Please switch to another server before removing it.") else server_configs.remove(server.hostname) server_configs.save! end success "done" 0 end summary "Update server attributes" syntax " [--hostname HOSTNAME] [--nickname NICKNAME] [--rhlogin LOGIN] [--[no-]use-authorization-tokens] [--[no-]insecure] [--use] [--skip-wizard] [--timeout SECONDS] [--ssl-ca-file FILE] [--ssl-client-cert-file FILE] [--ssl-version VERSION]" argument :server, "Server hostname or nickname to be configured", ["--server SERVER"] option ["--hostname HOSTNAME"], "Change the hostname of this server" option ["--nickname NICKNAME"], "Change the nickname of this server" option ["-l", "--rhlogin LOGIN"], "Change the default OpenShift login used on this server" option ["--[no-]use-authorization-tokens"], "Server will attempt to create and use authorization tokens to connect to the server" option ["--[no-]insecure"], "If true, certificate errors will be ignored" option ["--use"], "If provided, the server being configured will be set as default (same as 'rhc server use')" option ["--skip-wizard"], "If provided, the wizard will be skipped and a session token will not be estabilished" option ["--timeout SECONDS"], "The default timeout for operations on this server", :type => Integer option ["--ssl-ca-file FILE"], "An SSL certificate CA file (may contain multiple certs) to be used on this server", :type => CertificateFile, :optional => true option ["--ssl-client-cert-file FILE"], "An SSL x509 client certificate file to be used on this server", :type => CertificateFile, :optional => true option ["--ssl-client-key-file FILE"], "An RSA client certificate key", :type => CertificateKey, :optional => true option ["--ssl-version VERSION"], "The version of SSL to use to be used on this server", :type => SSLVersion, :optional => true def configure(server) raise ArgumentError, "The --use and --skip-wizard options cannot be used together." if options.use && options.skip_wizard server = server_configs.find(server) attrs = [:hostname, :nickname, :login, :use_authorization_tokens, :insecure, :timeout, :ssl_version, :ssl_client_cert_file, :ssl_client_key_file, :ssl_ca_file].inject({}){ |h, (k, v)| v = options[k == :login ? :rhlogin : k]; h[k] = (v.nil? ? server.send(k) : v); h } raise RHC::ServerNicknameExistsException.new(options.nickname) if options.nickname && server_configs.nickname_exists?(options.nickname) && server_configs.find(options.nickname).hostname != server.hostname server = server_configs.update(server.hostname, attrs) unless options.skip_wizard wizard_to_server(attrs[:hostname], options.use, attrs.reject{|k, v| k == :hostname || k == :nickname}) else say "Saving server configuration to #{system_path(server_configs.path)} ... " server_configs.save! success "done" 0 end paragraph{ say display_server(server) } paragraph { success "Now using '#{server.hostname}'" } if options.use 0 end summary "Display the configuration of the given server" syntax "" argument :server, "Server hostname or nickname to be displayed", ["--server SERVER"] def show(server) server = server_configs.find(server) say display_server(server) paragraph{ say "Use 'rhc servers' to display all your servers." } if server_configs.list.length > 1 0 end protected def wizard_to_server(hostname, set_default, args) options['server'] = hostname options['rhlogin'] = args[:login] if args[:login] options['use_authorization_tokens'] = args[:use_authorization_tokens] unless args[:use_authorization_tokens].nil? options['create_token'] = args[:use_authorization_tokens] unless args[:use_authorization_tokens].nil? options['insecure'] = args[:insecure] unless args[:insecure].nil? options['timeout'] = args[:timeout] options['ssl_version'] = args[:ssl_version] options['ssl_client_cert_file'] = args[:ssl_client_cert_file] options['ssl_client_key_file'] = args[:ssl_client_key_file] options['ssl_ca_file'] = args[:ssl_ca_file] RHC::ServerWizard.new(config, options, server_configs, set_default).run end def server_configs @servers ||= RHC::Servers.new(config) end end end rhc-1.38.7/lib/rhc/commands/sshkey.rb0000644000004100000410000000700212756270551017405 0ustar www-datawww-datarequire 'rhc/commands/base' module RHC::Commands class Sshkey < Base include RHC::SSHHelpers summary 'Add and remove keys for Git and SSH' syntax '' description <<-DESC OpenShift uses public keys to securely access your application source code and to control access to your application gears via SSH. Your account may have one or more public SSH keys associated with it, and any computer with the private SSH key will be able to download code from Git or SSH to the application. Depending on your operating system, you may have to ensure that both Git and the local SSH installation have access to your keys. Running the 'setup' command is any easy way to get your first key created and uploaded. DESC default_action :list summary 'Display all the SSH keys for your account' syntax '' alias_action :"sshkeys", :root_command => true def list keys = rest_client.sshkeys.each{ |key| paragraph{ display_key(key) } } success "You have #{keys.length} SSH keys associated with your account." 0 end summary 'Show the SSH key with the given name' syntax '' argument :name, 'SSH key to display', [] def show(name) key = rest_client.find_key(name) display_key(key) 0 end summary 'Add SSH key to your account' syntax ' ' argument :name, 'Name for this key', [] argument :key, 'SSH public key filepath', [], :optional => true option ['--confirm'], 'Bypass key validation' option ['--type TYPE'], 'Provide the key type directly if no key file is given' option ['--content CONTENT'], 'Provide the key content directly if no key file is given' def add(name, key_path=nil) if key_path type, content, comment = ssh_key_triple_for(key_path) elsif options[:type].present? and options[:content].present? type = options[:type] content = options[:content] else raise ArgumentError, "You must either provide a key file, or the key type and content" end if type == 'krb5-principal' # TODO: validate krb5? else # validate the user input before sending it to the server begin Net::SSH::KeyFactory.load_data_public_key "#{type} #{content}" rescue NotImplementedError, OpenSSL::PKey::PKeyError, Net::SSH::Exception => e debug e.inspect if options.confirm warn 'The key you are uploading is not recognized. You may not be able to authenticate to your application through Git or SSH.' else raise ::RHC::KeyDataInvalidException.new("File '#{key_path}' does not appear to be a recognizable key file (#{e}). You may specify the '--confirm' flag to add the key anyway.") if key_path raise ::RHC::KeyDataInvalidException.new("The provided type and content does not appear to be a recognizable key (#{e}). You may specify the '--confirm' flag to add the key anyway.") end end end rest_client.add_key(name, content, type) results { say key_path ? "SSH key #{key_path} has been added as '#{name}'" : "SSH key '#{name}' has been added" } 0 end summary 'Remove SSH key from your account' syntax '' alias_action :delete, :deprecated => true argument :name, 'Name of SSH key to remove' def remove(name) say "Removing the key '#{name} ... " rest_client.delete_key(name) success "removed" 0 end end end rhc-1.38.7/lib/rhc/auth/0000755000004100000410000000000012756270551014713 5ustar www-datawww-datarhc-1.38.7/lib/rhc/auth/token_store.rb0000644000004100000410000000201712756270551017574 0ustar www-datawww-datarequire 'base64' module RHC::Auth class TokenStore def initialize(dir) @dir = dir end def get(login, server) self[key(login,server)] end def put(login, server, token) self[key(login,server)] = token end def clear Dir[File.join(@dir, "token_*")]. each{ |f| File.delete(f) unless File.directory?(f) }. present? end private def path(key) File.join(@dir, filename(key)) end def filename(key) "token_#{Base64.encode64(Digest::MD5.digest(key)).gsub(/[^\w\@]/,'')}" end def []=(key, value) file = path(key) FileUtils.mkdir_p File.dirname(file) File.open(file, 'w'){ |f| f.write(value) } File.chmod(0600, file) value end def [](key) s = IO.read(path(key)).presence s = s.strip.gsub(/[\n\r\t]/,'') if s s rescue Errno::ENOENT nil end def key(login, server) "#{login || ''}@#{server}" end end end rhc-1.38.7/lib/rhc/auth/x509.rb0000644000004100000410000000177512756270551015757 0ustar www-datawww-datamodule RHC::Auth class X509 def initialize(*args) @options = args[0] || Commander::Command::Options.new end def to_request(request, client=nil) request[:client_cert] = certificate_file(options.ssl_client_cert_file) request[:client_key] = certificate_key(options.ssl_client_key_file) request end def token_store_user_key certificate_fingerprint(options.ssl_client_cert_file) end def retry_auth?(response, client) # This is really only hit in the case of token auth falling back to x509. # x509 auth doesn't usually get 401s. if response && response.status != 401 false else true end end def can_authenticate? true end def expired_token_message "Your authorization token has expired. " + get_token_message end def get_token_message "Fetching a new token from #{openshift_server}." end protected include RHC::Helpers attr_reader :options end end rhc-1.38.7/lib/rhc/auth/token.rb0000644000004100000410000000567412756270551016374 0ustar www-datawww-datamodule RHC::Auth class Token def initialize(opt, auth=nil, store=nil) if opt.is_a?(String) @token = opt else @options = opt || Commander::Command::Options.new @token = options[:token] @no_interactive = options[:noprompt] @allows_tokens = options[:use_authorization_tokens] end @auth = auth @store = store read_token end def to_request(request, client=nil) if !token and auth and @allows_tokens and client and client.supports_sessions? debug "Attempting to generate token" token_rejected(nil, client) end if token debug "Using token authentication" (request[:headers] ||= {})['authorization'] = "Bearer #{token}" elsif auth debug "Bypassing token auth" auth.to_request(request, client) end request end def retry_auth?(response, client) if response && response.status != 401 false else token_rejected(response, client) end end def username auth && auth.respond_to?(:username) && auth.username || options[:username] end def token_store_user_key auth && auth.respond_to?(:token_store_user_key) && auth.token_store_user_key || username end def save(token) store.put(token_store_user_key, openshift_server, token) if store @token = token end def can_authenticate? token || auth && auth.can_authenticate? end protected include RHC::Helpers attr_reader :options, :token, :auth, :store def token_rejected(response, client) has_token = !!token @token = nil unless auth && auth.can_authenticate? if has_token raise RHC::Rest::TokenExpiredOrInvalid, "Your authorization token is expired or invalid." end debug "Cannot authenticate via token or password, exiting" return false end if has_token if cannot_retry? raise RHC::Rest::TokenExpiredOrInvalid, "Your authorization token is expired or invalid." end if not client.supports_sessions? raise RHC::Rest::AuthorizationsNotSupported end end @can_get_token = client.supports_sessions? && @allows_tokens if has_token warn auth.expired_token_message elsif @can_get_token info auth.get_token_message end return auth.retry_auth?(response, client) unless @can_get_token debug "Creating a new authorization token" if auth_token = client.new_session(:auth => auth) @fetch_once = true save(auth_token.token) true else auth.retry_auth?(response, client) end end def read_token @token ||= store.get(token_store_user_key, openshift_server) if store end def cannot_retry? !@fetch_once && @no_interactive end end end rhc-1.38.7/lib/rhc/auth/basic.rb0000644000004100000410000000367312756270551016332 0ustar www-datawww-datamodule RHC::Auth class Basic def initialize(*args) if args[0].is_a?(String) or args.length > 1 @username, @password = args else @options = args[0] || Commander::Command::Options.new @username = options[:rhlogin] @password = options[:password] @no_interactive = options[:noprompt] end @skip_interactive = !@password.nil? end def to_request(request, client=nil) request[:user] ||= lambda{ username || (request[:lazy_auth] != true && ask_username) || nil } request[:password] ||= lambda{ password || (username? && request[:lazy_auth] != true && ask_password) || nil } request end def retry_auth?(response, client) if response && response.status != 401 false else credentials_rejected end end def can_authenticate? username? and not (password.nil? and @skip_interactive and @no_interactive) end def expired_token_message "Your authorization token has expired. Please sign in now to continue on #{openshift_server}." end def get_token_message "Please sign in to start a new session to #{openshift_server}." end def token_store_user_key username end attr_reader :username protected include RHC::Helpers attr_reader :options, :password def credentials_rejected error "Username or password is not correct" if username? && password unless @skip_interactive or @no_interactive ask_username unless username? ask_password true end end def ask_username @username = ask("Login to #{openshift_server}: ") unless @no_interactive end def ask_password @password = ask("Password: ") { |q| q.echo = '*' q.whitespace = :chomp } unless @no_interactive end def username? username.present? end end end rhc-1.38.7/lib/rhc/cartridge_helpers.rb0000644000004100000410000001044112756270552017766 0ustar www-datawww-datarequire 'uri' module RHC module CartridgeHelpers protected def check_cartridges(names, opts={}, &block) cartridge_names = Array(names).map{ |s| s.strip if s && s.length > 0 }.compact from = opts[:from] || all_cartridges cartridge_names.map do |name| next from.find{ |c| c.url.present? && cartridge_url_downcase(c.url) == name} || use_cart(RHC::Rest::Cartridge.for_url(name), name) if name =~ %r(\Ahttps?://)i name = name.downcase from.find{ |c| c.name.downcase == name } || begin carts = from.select{ |c| match_cart(c, name) } if carts.empty? paragraph { list_cartridges(from) } raise RHC::CartridgeNotFoundException, "There are no cartridges that match '#{name}'." elsif carts.length == 1 use_cart(carts.first, name) else carts.sort!.instance_variable_set(:@for, name) carts end end end.tap do |carts| yield carts if block_given? end.each do |carts| if carts.is_a? Array name = carts.instance_variable_get(:@for) paragraph { list_cartridges(carts) } raise RHC::MultipleCartridgesException, "There are multiple cartridges matching '#{name}'. Please provide the short name of the correct cart." end end end def use_cart(cart, for_cartridge_name) if cart.name.blank? and cart.custom? info "The cartridge '#{cart.url}' will be downloaded and installed" else info "Using #{cart.name}#{cart.display_name ? " (#{cart.display_name})" : ''} for '#{for_cartridge_name}'" end cart end def match_cart(cart, search) search = search.to_s.downcase.gsub(/[_\-\s]/,' ') [ cart.name, (cart.tags || []).join(' '), ].compact.any?{ |s| s.present? && s.downcase.gsub(/[_\-\s]/,' ').include?(search) } || search.length > 2 && [ cart.description ].compact.any?{ |s| s.present? && !s.downcase.match(/\b#{Regexp.escape(search)}\b/).nil? } end def web_carts_only lambda{ |cart| next cart unless cart.is_a? Array name = cart.instance_variable_get(:@for) matching = cart.select{ |c| not c.only_in_existing? } if matching.size == 1 use_cart(matching.first, name) else matching.instance_variable_set(:@for, name) matching end } end def other_carts_only lambda{ |cart| next cart unless cart.is_a? Array name = cart.instance_variable_get(:@for) matching = cart.select{ |c| not c.only_in_new? } if matching.size == 1 use_cart(matching.first, name) else matching.instance_variable_set(:@for, name) matching end } end def standalone_cartridges @standalone_cartridges ||= all_cartridges.select{ |c| c.type == 'standalone' } end def not_standalone_cartridges @not_standalone_cartridges ||= all_cartridges.select{ |c| c.type != 'standalone' } end def all_cartridges @all_cartridges = rest_client.cartridges end def list_cartridges(cartridges) carts = cartridges.map{ |c| [c.name, c.display_name || ''] }.sort{ |a,b| a[1].downcase <=> b[1].downcase } carts.unshift ['==========', '========='] carts.unshift ['Short Name', 'Full name'] say table(carts) end def filter_jenkins_cartridges(tag) cartridges = all_cartridges.select { |c| (c.tags || []).include?(tag) && c.name =~ /\Ajenkins/i }.sort raise RHC::JenkinsNotInstalledOnServer if cartridges.empty? cartridges end def jenkins_cartridges @jenkins_cartridges ||= filter_jenkins_cartridges('ci') end def jenkins_client_cartridges @jenkins_client_cartridges ||= filter_jenkins_cartridges('ci_builder') end def cartridge_url_downcase(url) url = URI(url) url.scheme = url.scheme.downcase rescue url.scheme url.host = url.host.downcase rescue url.host url.path = url.path.downcase rescue url.path url.to_s end end end rhc-1.38.7/lib/rhc/cli.rb0000644000004100000410000000250012756270552015044 0ustar www-datawww-datarequire 'rhc' require 'rhc/commands' module RHC # # Run and execute a command line session with the RHC tools. # # You can invoke the CLI with: # bundle exec ruby -e 'require "rhc/cli"; RHC::CLI.start(ARGV);' -- # # from the gem directory. # module CLI extend Commander::Delegates def self.set_terminal $terminal.wrap_at = HighLine::SystemExtensions.terminal_size.first rescue 80 if $stdout.tty? $terminal.wrap_at = nil if $terminal.wrap_at == 0 #$terminal.page_at = :auto if $stdin.tty? and $stdout.tty? # FIXME: ANSI terminals are not default on windows but we may just be # hitting a bug in highline if windows does support another method. # This is a safe fix for now but needs more research. HighLine::use_color = false if RHC::Helpers.windows? or not $stdout.tty? end def self.start(args) runner = RHC::CommandRunner.new(args) Commander::Runner.instance_variable_set :@singleton, runner program :name, 'rhc' program :description, 'Command line interface for OpenShift.' program :version, RHC::VERSION::STRING program :help_formatter, RHC::HelpFormatter program :int_message, " Interrupted\n" RHC::Commands.load.to_commander run! || 0 end end end rhc-1.38.7/lib/rhc/highline_extensions.rb0000644000004100000410000002643712756270551020361 0ustar www-datawww-datarequire 'delegate' # # Add specific improved functionality # class HighLineExtension < HighLine attr_writer :debug [:ask, :agree].each do |sym| define_method(sym) do |*args, &block| separate_blocks r = super(*args, &block) @last_line_open = false r end end if HighLine::CHARACTER_MODE == 'stty' def raw_no_echo_mode @state = `stty -g 2>/dev/null` `stty raw -echo -icanon isig 2>&1` end def restore_mode `stty #{@state} 2>&1` end end def debug(msg) $stderr.puts "DEBUG: #{msg}" if debug? end def debug_error(e) debug "#{e.message} (#{e.class})\n #{e.backtrace.join("\n ")}" end def debug? @debug end # OVERRIDE def say(msg) if msg.respond_to? :to_str separate_blocks statement = msg.to_str return statement unless statement.present? template = ERB.new(statement, nil, "%") statement = template.result(binding) if @wrap_at statement = statement.chomp.textwrap_ansi(@wrap_at, false) if @last_line_open && statement.length > 1 @last_line_open = false @output.puts end statement = statement.join("#{indentation}\n") end statement = send(:page_print, statement) unless @page_at.nil? @output.print(indentation) unless @last_line_open @last_line_open = if statement[-1, 1] == " " or statement[-1, 1] == "\t" @output.print(statement) @output.flush #statement.strip_ansi.length + (@last_line_open || 0) true else @output.puts(statement) false end elsif msg.respond_to? :each separate_blocks @output.print if @last_line_open @last_line_open = false color = msg.color if msg.respond_to? :color @output.print HighLine::Style(color).code if color msg.each do |s| @output.print indentation @output.puts s end @output.print HighLine::CLEAR if color @output.flush end msg end # given an array of arrays "items", construct an array of strings that can # be used to print in tabular form. def table(items, opts={}, &block) items = items.map(&block) if block_given? opts[:width] ||= default_max_width Table.new(items, opts) end def table_args(indent=nil, *args) opts = {} opts[:indent] = indent opts[:width] = [default_max_width, *args] opts end def default_max_width @wrap_at ? @wrap_at - indentation.length : nil end def header(str, opts={}, &block) say Header.new(str, default_max_width, ' ', (opts[:color])) if block_given? indent &block end end #:nocov: # Backport from Highline 1.6.16 unless HighLine.method_defined? :indent # # Outputs indentation with current settings # def indentation @indent_size ||= 2 @indent_level ||= 0 return ' '*@indent_size*@indent_level end # # Executes block or outputs statement with indentation # def indent(increase=1, statement=nil, multiline=nil) @indent_size ||= 2 @indent_level ||= 0 @indent_level += increase multi = @multi_indent @multi_indent = multiline unless multiline.nil? if block_given? yield self else say(statement) end ensure @multi_indent = multi @indent_level -= increase end end #:nocov: ## # section # # highline helper mixin which correctly formats block of say and ask # output to have correct margins. section remembers the last margin # used and calculates the relitive margin from the previous section. # For example: # # section(bottom=1) do # say "Hello" # end # # section(top=1) do # say "World" # end # # Will output: # # > Hello # > # > World # # with only one newline between the two. Biggest margin wins. # # params: # top - top margin specified in lines # bottom - bottom margin specified in line # def section(params={}, &block) top = params[:top] || 0 bottom = params[:bottom] || 0 # the first section cannot take a newline top = 0 unless @margin @margin = [top, @margin || 0].max separate_blocks if params[:now] value = block.call say "\n" if @last_line_open @margin = [bottom, @margin].max value end ## # paragraph # # highline helper which creates a section with margins of 1, 1 # def paragraph(&block) section(:top => 1, :bottom => 1, &block) end def pager #:nocov: return if RHC::Helpers.windows? return unless @output.tty? read, write = IO.pipe unless Kernel.fork # Child process STDOUT.reopen(write) STDERR.reopen(write) if STDERR.tty? read.close write.close return end # Parent process, become pager STDIN.reopen(read) read.close write.close ENV['LESS'] = 'FSRX' # Don't page if the input is short enough Kernel.select [STDIN] # Wait until we have input before we start the pager pager = ENV['PAGER'] || 'less' exec pager rescue exec "/bin/sh", "-c", pager #:nocov: end private def separate_blocks if (@margin ||= 0) > 0 && !@last_line_open @output.print "\n" * @margin @margin = 0 end end end # # An element capable of being split into multiple logical rows # module RowBased extend Forwardable def_delegators :rows, :each, :to_a, :join alias_method :each_line, :each end class HighLine::Header < Struct.new(:text, :width, :indent, :color) include RowBased protected def rows @rows ||= begin if !width || width == 0 [text.is_a?(Array) ? text.compact.join(' ') : text] elsif text.is_a? Array text.compact! widths = text.map{ |s| s.strip_ansi.length } chars, join, indented = 0, 1, (indent || '').length narrow = width - indented text.zip(widths).inject([]) do |rows, (section, w)| if rows.empty? if w > width rows.concat(section.textwrap_ansi(width)) else rows << section.dup chars += w end else if w + chars + join > narrow rows.concat(section.textwrap_ansi(narrow).map{ |s| "#{indent}#{s}" }) chars = 0 elsif chars == 0 rows << "#{indent}#{section}" chars += w + indented else rows[-1] << " #{section}" chars += w + join end end rows end else text.textwrap_ansi(width) end end.tap do |rows| rows << '-' * (rows.map{ |s| s.strip_ansi.length }.max || 0) end end end # # Represent a columnar layout of items with wrapping and flexible layout. # class HighLine::Table include RowBased def initialize(items=nil,options={},&mapper) @items, @options, @mapper = items, options, mapper end def color opts[:color] end protected attr_reader :items def opts @options end def align opts[:align] || [] end def joiner opts[:join] || ' ' end def indent opts[:indent] || '' end def heading @heading ||= opts[:heading] ? HighLine::Header.new(opts[:heading], max_width, indent) : nil end def source_rows @source_rows ||= begin (@mapper ? (items.map &@mapper) : items).each do |row| row.map! do |col| case col when Array then col.join("\n") when String then col else col.to_s end end end end end def headers @headers ||= opts[:header] ? [Array(opts[:header])] : [] end def columns @columns ||= source_rows.map(&:length).max || 0 end def column_widths @column_widths ||= begin widths = Array.new(columns){ Width.new(0,0,0) } (source_rows + headers).each do |row| row.each_with_index do |col, i| w = widths[i] s = col.strip_ansi word_length = s.scan(/\b\S+/).inject(0){ |l, word| l = word.length if l <= word.length; l } w.min = word_length unless w.min > word_length w.max = s.length unless w.max > s.length end end widths end end Width = Struct.new(:min, :max, :set) def allocate_widths_for(available) available -= (columns-1) * joiner.length + indent.length return column_widths.map{ |w| w.max } if available >= column_widths.inject(0){ |sum, w| sum + w.max } || column_widths.inject(0){ |sum, w| sum + w.min } > available fair = available / columns column_widths.each do |w| if w.set > 0 available -= w.set next end w.set = if w.max <= fair available -= w.max w.max else 0 end end remaining = column_widths.inject(0) do |sum, w| if w.set == 0 sum += w.max available -= w.min end sum end fair = available.to_f / remaining.to_f column_widths. each do |w| if w.set == 0 alloc = (w.max * fair).to_i overflow = alloc + w.min - w.max if overflow > 0 alloc -= overflow end available -= alloc w.set = alloc + w.min end end. each do |w| next unless available > 0 gap = w.max - w.set if gap > 0 left = [gap,available].min available -= left w.set += left end end.map(&:set) end def widths @widths ||= begin case w = opts[:width] when Array column_widths.zip(w[1..-1]).each do |width, col| width.set = col || 0 width.max = width.set if width.set > width.max end allocate_widths_for(w.first || 0) when Integer allocate_widths_for(w) else column_widths.map{ |w| w.max } end end end def max_width @max_width ||= opts[:width].is_a?(Array) ? opts[:width].first : (opts[:width] ? opts[:width] : 0) end def header_rows @header_rows ||= begin headers << widths.map{ |w| '-' * w } if headers.present? headers end end def rows @rows ||= begin body = (header_rows + source_rows).inject([]) do |a,row| row = row.zip(widths).map{ |column,w| w && w > 0 ? column.textwrap_ansi(w, false) : [column] } (row.map(&:length).max || 0).times do |i| s = [] row.each_with_index do |lines, j| cell = lines[i] l = cell ? cell.strip_ansi.length : 0 s << if align[j] == :right "#{' '*(widths[j]-l) if l < widths[j]}#{cell}" else "#{cell}#{' '*(widths[j]-l) if l < widths[j]}" end end a << "#{indent}#{s.join(joiner).rstrip}" end a end body = heading.to_a.concat(body) if heading body end end end $terminal = HighLineExtension.new $terminal.indent_size = 2 if $terminal.respond_to? :indent_size rhc-1.38.7/lib/rhc/helpers.rb0000644000004100000410000004460312756270552015751 0ustar www-datawww-datarequire 'commander/user_interaction' require 'rhc/version' require 'rhc/config' require 'rhc/output_helpers' require 'rhc/server_helpers' require 'rbconfig' require 'csv' require 'resolv' OptionParser.accept(URI) {|s,| URI.parse(s) if s} module RHC module Helpers private def self.global_option(*args, &block) RHC::Commands.global_option *args, &block end end module Helpers # helpers always have Commander UI available include Commander::UI include Commander::UI::AskForClass include RHC::OutputHelpers include RHC::ServerHelpers extend self def decode_json(s) RHC::Vendor::OkJson.decode(s) end def system_path(path) return path.gsub(File::SEPARATOR, File::ALT_SEPARATOR) if File.const_defined?('ALT_SEPARATOR') and File::ALT_SEPARATOR.present? path end PREFIX = %W(TB GB MB KB B).freeze def human_size( s ) return "unknown" unless s s = s.to_f i = PREFIX.length - 1 while s > 500 && i > 0 i -= 1 s /= 1000 end ((s > 9 || s.modulo(1) < 0.1 ? '%d' : '%.1f') % s) + ' ' + PREFIX[i] end def date(s) return nil unless s.present? now = Date.today d = datetime_rfc3339(s).to_time if now.year == d.year return d.strftime('%l:%M %p').strip if now.yday == d.yday d.strftime('%b %d %l:%M %p') else d.strftime('%b %d, %Y %l:%M %p') end rescue ArgumentError "Unknown date" end def distance_of_time_in_words(from_time, to_time = 0) from_time = from_time.to_time if from_time.respond_to?(:to_time) to_time = to_time.to_time if to_time.respond_to?(:to_time) distance_in_minutes = (((to_time - from_time).abs)/60).round distance_in_seconds = ((to_time - from_time).abs).round case distance_in_minutes when 0..1 return distance_in_minutes == 0 ? "less than 1 minute" : "#{distance_in_minutes} minute" when 2..44 then "#{distance_in_minutes} minutes" when 45..89 then "about 1 hour" when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours" when 1440..2519 then "about 1 day" when 2520..43199 then "#{(distance_in_minutes.to_f / 1440.0).round} days" when 43200..86399 then "about 1 month" else "about #{(distance_in_minutes.to_f / 43200.0).round} months" end end def datetime_rfc3339(s) DateTime.strptime(s, '%Y-%m-%dT%H:%M:%S%z') # Replace with d = DateTime.rfc3339(s) end # # Web related requests # def user_agent "rhc/#{RHC::VERSION::STRING} (ruby #{RUBY_VERSION}; #{RUBY_PLATFORM})#{" (API #{RHC::Rest::Client::CLIENT_API_VERSIONS})"}" end # # Global config # global_option '-l', '--rhlogin LOGIN', "OpenShift login" global_option '-p', '--password PASSWORD', "OpenShift password" global_option '--token TOKEN', "An authorization token for accessing your account." global_option '-d', '--debug', "Turn on debugging", :hide => true global_option '--server HOSTNAME', String, 'An OpenShift server hostname (default: openshift.redhat.com)' global_option '-k', '--insecure', "Allow insecure SSL connections. Potential security risk.", :hide => true global_option '--header HEADER', String,"Custom header to pass to server", :hide => true global_option '--limit INTEGER', Integer, "Maximum number of simultaneous operations to execute.", :hide => true global_option '--raw', "Do not format the output from the requested operations.", :hide => true global_option '--always-prefix', "Include the gear prefix on all output from the server.", :hide => true OptionParser.accept(SSLVersion = Class.new){ |s| parse_ssl_version(s) } global_option '--ssl-version VERSION', SSLVersion, "The version of SSL to use", :hide => true do |value| raise RHC::Exception, "You are using an older version of the httpclient gem which prevents the use of --ssl-version. Please run 'gem update httpclient' to install a newer version (2.2.6 or newer)." unless HTTPClient::SSLConfig.method_defined? :ssl_version end global_option '--ssl-ca-file FILE', "An SSL certificate CA file (may contain multiple certs)", :hide => true do |value| debug certificate_file(value) end global_option '--ssl-client-cert-file FILE', "An SSL x509 client certificate file", :hide => true do |value| debug certificate_file(value) end global_option '--ssl-client-key-file FILE', "An RSA client certificate key", :hide => true do |value| debug certificate_key(value) end global_option('--timeout SECONDS', Integer, 'The timeout for operations') do |value| raise RHC::Exception, "Timeout must be a positive integer" unless value > 0 end global_option '--always-auth', "Always use authentication when making OpenShift API requests.", :hide => true global_option '--noprompt', "Suppress all interactive operations command", :hide => true do $terminal.page_at = nil end global_option '--config FILE', "Path of a different config file (default: #{system_path("~/.openshift/express.conf")})", :hide => true global_option '--clean', "Ignore any saved configuration options", :hide => true global_option '--mock', "Run in mock mode", :hide => true do #:nocov: require 'rhc/rest/mock' RHC::Rest::Mock.start #:nocov: end ROLES = {'view' => 'viewer', 'edit' => 'editor', 'admin' => 'administrator'} OptionParser.accept(Role = Class.new) do |s| s.downcase! (ROLES.has_key?(s) && s) or raise OptionParser::InvalidOption.new(nil, "The provided role '#{s}' is not valid. Supported values: #{ROLES.keys.join(', ')}") end OptionParser.accept(CertificateFile = Class.new) {|s| certificate_file(s); s} OptionParser.accept(CertificateKey = Class.new) {|s| certificate_key(s); s} def role_name(s) ROLES[s.downcase] end def to_host(s) s =~ %r(^http(?:s)?://) ? URI(s).host : s end def to_uri(s) begin URI(s =~ %r(^http(?:s)?://) ? s : "https://#{s}") rescue URI::InvalidURIError raise RHC::InvalidURIException.new(s) end end def ssh_string(ssh_url) return nil if ssh_url.blank? uri = URI.parse(ssh_url) "#{uri.user}@#{uri.host}" rescue => e RHC::Helpers.debug_error(e) ssh_url end def ssh_string_parts(ssh_url) uri = URI.parse(ssh_url) [uri.host, uri.user] end def token_for_user return options.token if options.token return nil unless options.use_authorization_tokens token_store_user_key = if options.ssl_client_cert_file certificate_fingerprint(options.ssl_client_cert_file) else options.rhlogin end return nil if token_store_user_key.nil? token_store.get(token_store_user_key, options.server) end def parse_headers(headers) Hash[ Array(headers).map do |header| key, value = header.split(/: */, 2) [key, value || ""] if key.presence end.compact ] end def client_from_options(opts) RHC::Rest::Client.new({ :url => openshift_rest_endpoint.to_s, :debug => options.debug, :headers => parse_headers(options.header), :timeout => options.timeout, :warn => BOUND_WARNING, :api_always_auth => options.always_auth }.merge!(ssl_options).merge!(opts)) end def ssl_options { :ssl_version => options.ssl_version, :ca_file => options.ssl_ca_file && File.expand_path(options.ssl_ca_file), :verify_mode => options.insecure ? OpenSSL::SSL::VERIFY_NONE : nil, }.delete_if{ |k,v| v.nil? } end def certificate_file(file) file && OpenSSL::X509::Certificate.new(IO.read(File.expand_path(file))) rescue => e debug e raise OptionParser::InvalidOption.new(nil, "The certificate '#{file}' cannot be loaded: #{e.message} (#{e.class})") end def certificate_fingerprint(file) Digest::SHA1.hexdigest(certificate_file(file).to_der) end def certificate_key(file) file && OpenSSL::PKey::RSA.new(IO.read(File.expand_path(file))) rescue => e debug e raise OptionParser::InvalidOption.new(nil, "The RSA key '#{file}' cannot be loaded: #{e.message} (#{e.class})") end def parse_ssl_version(version) OpenSSL::SSL::SSLContext::METHODS.find{ |m| m.to_s.downcase == version.to_s.downcase } or raise OptionParser::InvalidOption.new(nil, "The provided SSL version '#{version}' is not valid. Supported values: #{OpenSSL::SSL::SSLContext::METHODS.map(&:to_s).map(&:downcase).join(', ')}") unless version.nil? end # # Output helpers # def interactive? $stdin.tty? and $stdout.tty? and not options.noprompt end def debug(*args) $terminal.debug(*args) end def debug_error(*args) $terminal.debug_error(*args) end def debug? $terminal.debug? end def exec(cmd) output = Kernel.send(:`, cmd) [$?.exitstatus, output] end def disable_deprecated? ENV['DISABLE_DEPRECATED'] == '1' end def deprecated_command(correct, short=false) deprecated("This command is deprecated. Please use '#{correct}' instead.", short) end def deprecated(msg,short = false) raise DeprecatedError.new(msg % ['an error','a warning',0]) if disable_deprecated? warn "Warning: #{msg}\n" % ['a warning','an error',1] end # # By default, agree should take a single character in interactive # def agree(*args, &block) #args.push(interactive?.presence) if args.length == 1 block = lambda do |q| q.validate = /\A(?:y|yes|n|no)\Z/i end unless block_given? super *args, &block end def confirm_action(question) return if options.confirm return if !options.noprompt && paragraph{ agree "#{question} (yes|no): " } raise RHC::ConfirmationError end def success(msg, *args) say color(msg, :green), *args end def info(msg, *args) say color(msg, :cyan), *args end def warn(msg, *args) say color(msg, :yellow), *args end def error(msg, *args) say color(msg, :red), *args end # OVERRIDE: Replaces default commander behavior def color(item, *args) unless (options.raw rescue false) if item.is_a? Array item.map{ |i| $terminal.color(i, *args) } else $terminal.color(item, *args) end else item end end [:pager, :indent, :paragraph, :section, :header, :table, :table_args].each do |sym| define_method(sym) do |*args, &block| $terminal.send(sym, *args, &block) end end def pluralize(count, s) count == 1 ? "#{count} #{s}" : "#{count} #{s}s" end # This will format table headings for a consistent look and feel # If a heading isn't explicitly defined, it will attempt to look up the parts # If those aren't found, it will capitalize the string def table_heading(value) # Set the default proc to look up undefined values headings = Hash.new do |hash,key| items = key.to_s.split('_') # Look up each piece individually hash[key] = items.length > 1 ? # Recusively look up the heading for the parts items.map{|x| headings[x.to_sym]}.join(' ') : # Capitalize if this part isn't defined items.first.capitalize end # Predefined headings (or parts of headings) headings.merge!({ :creation_time => "Created", :expires_in_seconds => "Expires In", :uuid => "ID", :id => 'ID', :current_scale => "Current", :scales_from => "Minimum", :scales_to => "Maximum", :gear_sizes => "Allowed Gear Sizes", :consumed_gears => "Gears Used", :max_gears => "Gears Allowed", :max_domains => "Domains Allowed", :compact_members => "Members", :gear_info => "Gears", :plan_id => "Plan", :url => "URL", :ssh_string => "SSH", :connection_info => "Connection URL", :gear_profile => "Gear Size", :visible_to_ssh? => 'Available', :downloaded_cartridge_url => 'From', :auto_deploy => 'Deployment', :sha1 => 'SHA1', :ref => 'Git Reference', :use_authorization_tokens => 'Use Auth Tokens', :ssl_ca_file => 'SSL Cert CA File', :ssl_version => 'SSL Version', :ssl_client_cert_file => 'SSL x509 Client Cert File', :ssl_client_key_file => 'SSL x509 Client Key File', :zones => 'Available Zones' }) headings[value] end class StringTee < StringIO attr_reader :tee def initialize(other) @tee = other super() end def <<(buf) tee << buf super end end ## # results # # highline helper which creates a paragraph with a header # to distinguish the final results of a command from other output # def results(&block) section(:top => 1, :bottom => 0) do say "RESULT:" yield end end # Platform helpers def jruby? ; RUBY_PLATFORM =~ /java/i end def windows? ; RUBY_PLATFORM =~ /win(32|dows|ce)|djgpp|(ms|cyg|bcc)win|mingw32/i end def unix? ; !jruby? && !windows? end def mac? ; RbConfig::CONFIG['host_os'] =~ /^darwin/ end # # Check if host exists # def host_exists?(host) # :nocov: # Patch for BZ840938 to support Ruby 1.8 on machines without /etc/resolv.conf dns = Resolv::DNS.new((Resolv::DNS::Config.default_config_hash || {})) resources = dns.getresources(host, Resolv::DNS::Resource::IN::A) debug("Checking for #{host} from Resolv::DNS: #{resources.inspect}") if debug? resources.present? # :nocov: end def hosts_file_contains?(host) with_tolerant_encoding do begin resolver = Resolv::Hosts.new result = resolver.getaddress host debug("Checking for #{host} from Resolv::Hosts: #{result.inspect}") if debug? result rescue => e debug "Application could not be resolved through Hosts file: #{e.message}(#{e.class})\n #{e.backtrace.join("\n ")}\n Attempting DNS resolution." end end end def with_tolerant_encoding(&block) # :nocov: if RUBY_VERSION.to_f >= 1.9 orig_default_internal = Encoding.default_internal Encoding.default_internal = 'ISO-8859-1' else orig_default_kcode = $KCODE $KCODE = 'N' end yield ensure if RUBY_VERSION.to_f >= 1.9 Encoding.default_internal = orig_default_internal else $KCODE = orig_default_kcode end # :nocov: end # Run a command and export its output to the user. Output is not capturable # on all platforms. def run_with_tee(cmd) status, stdout, stderr = nil if windows? || jruby? #:nocov: TODO: Test block system(cmd) status = $?.exitstatus #:nocov: else stdout, stderr = [$stdout, $stderr].map{ |t| StringTee.new(t) } status = Open4.spawn(cmd, 'stdout' => stdout, 'stderr' => stderr, 'quiet' => true) stdout, stderr = [stdout, stderr].map(&:string) end [status, stdout, stderr] end def env_var_regex_pattern /^([a-zA-Z_][\w]*)=(.*)$/ end def collect_env_vars(items) return nil if items.blank? env_vars = [] Array(items).each do |item| if match = item.match(env_var_regex_pattern) name, value = match.captures env_vars << RHC::Rest::EnvironmentVariable.new({ :name => name, :value => value }) elsif File.file? item File.readlines(item).each do |line| if match = line.match(env_var_regex_pattern) name, value = match.captures env_vars << RHC::Rest::EnvironmentVariable.new({ :name => name, :value => value }) end end end end env_vars end def to_boolean(s, or_nil=false) return nil if s.nil? && or_nil s.is_a?(String) ? !!(s =~ /^(true|t|yes|y|1)$/i) : s end # split spaces but preserve sentences between quotes def split_path(s, keep_quotes=false) #:nocov: if keep_quotes s.split(/\s(?=(?:[^"]|"[^"]*")*$)/) elsif RUBY_VERSION.to_f < 1.9 CSV::parse_line(s, fs = ' ') else #ruby 1.9 or newer CSV::parse_line(s, :col_sep => ' ') end #:nocov: end def discover_windows_executables(&block) #:nocov: if RHC::Helpers.windows? base_path = [] guessing_locations = [] # Look for the base path int the ProgramFiles env var... if program_files = ENV['ProgramFiles']; program_files.present? && File.exists?(program_files) base_path << program_files end # ... and in win32ole drives (drive root or Program Files) begin require 'win32ole' WIN32OLE.new("Scripting.FileSystemObject").tap do |file_system| file_system.Drives.each do |drive| base_path << [ "#{drive.DriveLetter}:\\Program Files (x86)", "#{drive.DriveLetter}:\\Program Files", "#{drive.DriveLetter}:\\Progra~1", "#{drive.DriveLetter}:" ] end end rescue end # executables array from block executable_groups = [] base_path.flatten.uniq.each do |base| executable_groups << yield(base) end # give proper priorities unless executable_groups.empty? length = executable_groups.first.length for i in 1..length do executable_groups.each do |group| guessing_locations << group[i - 1] end end end guessing_locations.flatten.uniq.select {|cmd| File.exist?(cmd) && File.executable?(cmd)} else [] end #:nocov: end def exe?(executable) ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory| File.executable?(File.join(directory, executable.to_s)) end end BOUND_WARNING = self.method(:warn).to_proc end end rhc-1.38.7/lib/rhc/version.rb0000644000004100000410000000016012756270551015761 0ustar www-datawww-datamodule RHC module VERSION #:nocov: STRING = Gem.loaded_specs['rhc'].version.to_s rescue '0.0.0' end end rhc-1.38.7/lib/rhc/auth.rb0000644000004100000410000000027612756270551015245 0ustar www-datawww-datamodule RHC::Auth autoload :Basic, 'rhc/auth/basic' autoload :X509, 'rhc/auth/x509' autoload :Token, 'rhc/auth/token' autoload :TokenStore, 'rhc/auth/token_store' end rhc-1.38.7/lib/rhc/config.rb0000644000004100000410000002743112756270551015553 0ustar www-datawww-datarequire 'fileutils' require 'rhc/vendor/parseconfig' require 'rhc/core_ext' require 'rhc/servers' require 'rhc/server_helpers' module RHC module ConfigEnv def conf_name "#{ENV['OPENSHIFT_CONFIG'].presence || 'express'}.conf" end def home_conf_dir File.join(home_dir, '.openshift') end def local_config_path File.join(home_conf_dir, conf_name) end def ssh_dir File.join(home_dir, '.ssh') end def ssh_priv_key_file_path File.join(ssh_dir, 'id_rsa') end def ssh_pub_key_file_path File.join(ssh_dir, 'id_rsa.pub') end end # # Responsible for encapsulating the loading and retrieval of OpenShift # configuration files and converting them to commandline option # equivalents. It also provides the converse option - converting a set # of commandline options back into a config file. # # In general, the values stored in the config should be identical (require # little or no type conversion) to their option form. As new global # options are added, only this class should have to change to persist that # option. # # During normal use, a new Config object should load the appropriate # settings and those settings should be converted into commandline option # defaults. # # TODO: Encapsulate config writing to the home location # TODO: Allow the config object to initialized with a path # TODO: Remove deprecated methods, remove extra sources. # class Config include ConfigEnv include RHC::ServerHelpers # Option name [config_key type comment_string_for_config] # if nil, == key nil == string won't be written to file if nil OPTIONS = { :server => ['libra_server', nil, 'The default OpenShift server to connect to'], :rhlogin => ['default_rhlogin', nil, 'Your OpenShift login name'], :password => nil, :use_authorization_tokens => [nil, :boolean, 'If true, the server will attempt to create and use authorization tokens to connect to the server'], :timeout => [nil, :integer, 'The default timeout for network operations'], :insecure => [nil, :boolean, "If true, certificate errors will be ignored.\nWARNING: This may allow others to eavesdrop on your communication with OpenShift."], :ssl_version => [nil, nil, 'The SSL protocol version to use when connecting to this server'], :ssl_client_cert_file => [nil, :path_to_file, 'A client certificate file for use with your server'], :ssl_client_key_file => [nil, :path_to_file, 'The corresponding key for the client certificate'], :ssl_ca_file => [nil, :path_to_file, 'A file containing CA one or more certificates'], :always_auth => [nil, :boolean, 'If true, the client will use an authenticated connection for all requests. Useful for certain client certificate configurations.'], } def self.options_to_config(options, args=OPTIONS.keys) OPTIONS.select{|k,v| args ? args.include?(k) : true}.inject([]) do |arr, (name, opts)| opts ||= [] next arr unless opts[2] value = options[name] arr.concat(opts[2].each_line.to_a.map(&:strip).map{ |s| "# #{s}" }) arr << "#{value.nil? ? '#' : ''}#{opts[0] || name}=#{self.type_to_config(opts[1], value)}" arr << "" arr end.unshift(!args.nil? && args.length < OPTIONS.length ? ["# Check servers.yml for detailed server configuration", ""] : nil).flatten.compact.join("\n") end def self.type_to_config(type, value) case type when :integer, :boolean value.nil? ? "<#{type}>" : value else value.nil? ? "<#{type || 'string'}>" : value end end # DEPRECATED - will be removed when old commands are gone def self.default @default ||= RHC::Config.new end # DEPRECATED - will be removed when old commands are gone def self.method_missing(method, *args, &block) if default.respond_to?(method) default.send(method, *args, &block) else raise NoMethodError, method end end # DEPRECATED - will be removed when old commands are gone def self.initialize @default = nil default end # DEPRECATED - will be removed when old commands are gone def initialize set_defaults end # DEPRECATED - will be removed when old commands are gone def read_config_files load_config_files end # DEPRECATED - will be removed when old commands are gone def set_defaults @defaults = RHC::Vendor::ParseConfig.new() @opts = RHC::Vendor::ParseConfig.new() # option switches that override config file @env_config = RHC::Vendor::ParseConfig.new() @global_config = nil @local_config = nil @opts_config = nil # config file passed in the options @additional_config = nil @default_proxy = nil @defaults.add('libra_server', openshift_online_server) @env_config.add('libra_server', libra_server_env) if libra_server_env @env_config.add('libra_server', rhc_server_env) if rhc_server_env @opts_config_path = nil end def to_options OPTIONS.inject({}) do |h, (name, opts)| opts = Array(opts) value = self[opts[0] || name.to_s] unless value.nil? value = case opts[1] when :integer Integer(value) when :boolean value.is_a?(TrueClass) || !!(value =~ /^\s*(y|yes|1|t|true)\s*$/i) else value unless value.blank? end h[name] = value end h end end def backup if File.exists? path backup = "#{path}.bak" FileUtils.cp(path, backup) end end def save!(options, fields=nil) File.open(path, 'w') do |f| f.puts self.class.options_to_config( options, fields ) end @opts, @opts_config, @env_config, @additional_config, @local_config, @global_config = nil load_config_files self end def [](key) lazy_init configs = configs_cascade result = nil c = nil configs.each_with_index do |conf, i| result = conf[key] if !conf.nil? c = conf break if !result.nil? end result end # individual configs will be evaluated in the following cascading order def configs_cascade [ @opts, @opts_config, @env_config, @additional_config, @local_config, @global_config, @defaults ] end # DEPRECATED - will be removed when old commands are gone def get_value(key) self[key] end # DEPRECATED - underlying value and command option needs to be migrated to login def username self['default_rhlogin'] end # DEPRECATED - will be removed when old commands are gone def set_local_config(conf_path, must_exist=true) conf_path = File.expand_path(conf_path) @config_path = conf_path if @opts_config_path.nil? @local_config = RHC::Vendor::ParseConfig.new(conf_path) rescue Errno::EACCES => e raise Errno::EACCES.new "Could not open config file: #{e.message}" if must_exist end # DEPRECATED - needs to be renamed to something cleaner def set_opts_config(conf_path) @opts_config_path = File.expand_path(conf_path) @config_path = @opts_config_path @opts_config = RHC::Vendor::ParseConfig.new(@opts_config_path) if File.exists?(@opts_config_path) rescue Errno::EACCES => e raise Errno::EACCES.new "Could not open config file: #{e.message}" end def use_config(path) path = File.expand_path(path) set_opts_config(path) rescue => e raise ArgumentError, "Unable to read configuration file: #{e.message}", $!.backtrace end # DEPRECATED - will be removed when old commands are gone def check_cpath(opts) unless opts["config"].nil? opts_config_path = File.expand_path(opts["config"]) if !File.readable?(opts_config_path) raise Errno::EACCES.new "Could not open config file: #{@opts_config_path}" else set_opts_config(opts_config_path) end end end # DEPRECATED - may be made private def global_config_path linux_cfg = '/etc/openshift/' + conf_name File.exists?(linux_cfg) ? linux_cfg : File.join(File.expand_path(File.dirname(__FILE__) + "/../../conf"), conf_name) end def has_global_config? lazy_init !@global_config.nil? end def has_local_config? lazy_init !@local_config.nil? end def has_opts_config? !@opts_config.nil? end def has_additional_config? lazy_init !@additional_config.nil? end def has_configs_from_files? has_local_config? || has_opts_config? || has_additional_config? end # DEPRECATED - should be moved to Helpers def should_run_ssh_wizard? not File.exists? ssh_priv_key_file_path end ## # config_path # # authoritive configuration path # this is used to determine where config options should be written to # when a script modifies the config such as in rhc setup def config_path @config_path ||= local_config_path end def path config_path end def home_dir RHC::Config.home_dir end def home_conf_path home_conf_dir end # DEPRECATED - will be removed when old commands are gone def default_rhlogin get_value('default_rhlogin') end # DEPRECATED - will be removed when old commands are gone def default_proxy @default_proxy ||= ( proxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] if proxy if proxy !~ /^(\w+):\/\// then proxy = "http://#{proxy}" end ENV['http_proxy'] = proxy proxy_uri = URI.parse(ENV['http_proxy']) Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password) else Net::HTTP end ) end # DEPRECATED - will be removed when old commands are gone def using_proxy? default_proxy.instance_variable_get(:@is_proxy_class) || false end # DEPRECATED - will be removed when old commands are gone def proxy_vars Hash[[:address,:user,:pass,:port].map do |x| [x, default_proxy.instance_variable_get("@proxy_#{x}")] end] end def servers @servers end def sync_additional_config @additional_config = servers_config end private # Allow mocking of the home dir def self.home_dir File.expand_path('~') end def load_config_files @global_config = RHC::Vendor::ParseConfig.new(global_config_path) if File.exists?(global_config_path) @local_config = RHC::Vendor::ParseConfig.new(File.expand_path(local_config_path)) if File.exists?(local_config_path) rescue Errno::EACCES => e raise Errno::EACCES.new("Could not open config file: #{e.message}") end def load_servers @servers ||= RHC::Servers.new end def servers_config lazy_init libra_server_conf = (@local_config['libra_server'] rescue nil) || (@global_config['libra_server'] rescue nil) if libra_server_conf servers.find(libra_server_conf).to_config rescue nil else servers.list.first.to_config rescue nil end end def lazy_init unless @loaded load_config_files load_servers @loaded = true end end end end rhc-1.38.7/lib/rhc/autocomplete_templates/0000755000004100000410000000000012756270552020532 5ustar www-datawww-datarhc-1.38.7/lib/rhc/autocomplete_templates/bash.erb0000644000004100000410000000175112756270552022145 0ustar www-datawww-data# # This is the bash auto completion script for the rhc command # _rhc() { local cur opts prev COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" if [ $COMP_CWORD -eq 1 ]; then if [[ "$cur" == -* ]]; then opts=<%= "\"%s\"" % global_options.sort.join(' ') %> elif [ -z $cur ]; then opts=<%= "\"%s\"" % top_level_commands.sort.join(' ') %> else opts=<%= "\"%s\"" % commands.map{ |c| c.first }.delete_if{ |s| s.include? ' ' }.sort.join(' ') %> fi else prev="${COMP_WORDS[@]:0:COMP_CWORD}" SAVE_IFS=$IFS IFS=" " case "${prev[*]}" in <%- for name, data in commands %> <%= "\"rhc %s\")" % name %> if [[ "$cur" == -* ]]; then opts=<%= "\"%s\"" % data[:switches].sort.join(" ") %> else opts=<%= "\"%s\"" % data[:actions].sort.join(" ") %> fi ;; <%- end %> esac IFS=$SAVE_IFS fi COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 } complete -o default -F _rhc rhc rhc-1.38.7/lib/rhc/rest.rb0000644000004100000410000001201712756270551015255 0ustar www-datawww-datamodule RHC module Rest autoload :Base, 'rhc/rest/base' autoload :Attributes, 'rhc/rest/attributes' autoload :HTTPClient, 'rhc/rest/httpclient' autoload :Alias, 'rhc/rest/alias' autoload :Api, 'rhc/rest/api' autoload :Application, 'rhc/rest/application' autoload :Authorization, 'rhc/rest/authorization' autoload :Cartridge, 'rhc/rest/cartridge' autoload :Client, 'rhc/rest/client' autoload :Deployment, 'rhc/rest/deployment' autoload :Domain, 'rhc/rest/domain' autoload :EnvironmentVariable, 'rhc/rest/environment_variable' autoload :GearGroup, 'rhc/rest/gear_group' autoload :Key, 'rhc/rest/key' autoload :Membership, 'rhc/rest/membership' autoload :Region, 'rhc/rest/region' autoload :Team, 'rhc/rest/team' autoload :User, 'rhc/rest/user' class Exception < RuntimeError attr_reader :code def initialize(message=nil, code=1) super(message) @code = (Integer(code) rescue nil) end end #Exceptions thrown in case of an HTTP 5xx is received. class ServerErrorException < Exception; end #Exceptions thrown in case of an HTTP 503 is received. # #503 Service Unavailable # #The server is currently unable to handle the request due to a temporary #overloading or maintenance of the server. The implication is that this #is a temporary condition which will be alleviated after some delay. class ServiceUnavailableException < ServerErrorException; end #Exceptions thrown in case of an HTTP 4xx is received with the exception #of 401, 403, 403 and 422 where a more sepcific exception is thrown # #HTTP Error Codes 4xx # #The 4xx class of status code is intended for cases in which the client #seems to have errored. class ClientErrorException < Exception; end #Exceptions thrown in case of an HTTP 404 is received. # #404 Not Found # #The server has not found anything matching the Request-URI or the #requested resource does not exist class ResourceNotFoundException < ClientErrorException; end class ApiEndpointNotFound < ResourceNotFoundException; end # 404 errors for specific resource types class DomainNotFoundException < ResourceNotFoundException def initialize(msg) super(msg,127) end end class ApplicationNotFoundException < ResourceNotFoundException def initialize(msg) super(msg,101) end end #Exceptions thrown in case of an HTTP 422 is received. class ValidationException < ClientErrorException attr_reader :field def initialize(message, field=nil, error_code=1) super(message, error_code) @field = field end end #Exceptions thrown in case of an HTTP 403 is received. # #403 Forbidden # #The server understood the request, but is refusing to fulfill it. #Authorization will not help and the request SHOULD NOT be repeated. class RequestDeniedException < ClientErrorException; end #Exceptions thrown in case of an HTTP 401 is received. # #401 Unauthorized # #The request requires user authentication. If the request already #included Authorization credentials, then the 401 response indicates #that authorization has been refused for those credentials. class UnAuthorizedException < ClientErrorException; end class TokenExpiredOrInvalid < UnAuthorizedException; end # DEPRECATED Unreachable host, SSL Exception class ResourceAccessException < Exception; end #I/O Exceptions Connection timeouts, etc class ConnectionException < Exception; end class TimeoutException < ConnectionException def initialize(message, nested=nil) super(message) @nested = nested end def on_receive? @nested.is_a? HTTPClient::ReceiveTimeoutError end def on_connect? @nested.is_a? HTTPClient::ConnectTimeoutError end def on_send? @nested.is_a? HTTPClient::SendTimeoutError end end class SSLConnectionFailed < ConnectionException attr_reader :reason def initialize(reason, message) super message @reason = reason end end class CertificateVerificationFailed < SSLConnectionFailed; end class SelfSignedCertificate < CertificateVerificationFailed; end class SSLVersionRejected < SSLConnectionFailed; end class MultipleCartridgeCreationNotSupported < Exception; end class DownloadingCartridgesNotSupported < Exception; end class InitialGitUrlNotSupported < Exception; end class SslCertificatesNotSupported < Exception; end class AuthorizationsNotSupported < Exception def initialize(message="The server does not support setting, retrieving, or authenticating with authorization tokens.") super(message, 1) end end end end rhc-1.38.7/lib/rhc/coverage_helper.rb0000644000004100000410000000276212756270551017440 0ustar www-datawww-data# Must be the first module imported at entry points (executables that run # in seperate processes from the test harness) otherwise coverage will be # incomplete if RUBY_VERSION >= '1.9' and ENV['RHC_FEATURE_COVERAGE'] require 'simplecov' SimpleCov.start do coverage_dir 'coverage/features/' command_name 'Integration Tests' # Filters - these files will be ignored. add_filter 'lib/rhc/vendor/' # vendored files should be taken directly and only # namespaces changed add_filter 'features/' # Don't report on the files that run the cucumber tests add_filter 'lib/rhc-feature-coverage-helper.rb' add_filter 'spec/' # Don't report on the files that run the spec tests # Groups - general categories of test areas add_group('Commands') { |src_file| src_file.filename.include?(File.join(%w[lib rhc commands])) } add_group('RHC Lib') { |src_file| src_file.filename.include?(File.join(%w[lib rhc])) } add_group('REST') { |src_file| src_file.filename.include?(File.join(%w[lib rhc/rest])) } add_group('Test') { |src_file| src_file.filename.include?(File.join(%w[features])) or src_file.filename.include?(File.join(%w[spec])) } use_merging = true # Note, the #:nocov: coverage exclusion should only be used on external functions # that cannot be nondestructively tested in a developer environment. end # suppress output SimpleCov.at_exit do end end rhc-1.38.7/lib/rhc/git_helpers.rb0000644000004100000410000000770112756270551016611 0ustar www-datawww-datarequire 'open4' require 'fileutils' module RHC module GitHelpers def git_cmd "git" end def git_version(cmd=discover_git_executable) `"#{cmd}" --version 2>&1`.strip end def has_git? discover_git_executable.present? end # try my best to discover a git executable def discover_git_executable @git_executable ||= begin guessing_locations = [git_cmd] #:nocov: if RHC::Helpers.windows? guessing_locations << discover_windows_executables do |base| [ "git.exe", "#{base}\\Git\\bin\\git.exe", "#{base}\\git.exe", ] end end # make sure commands can be executed and finally pick the first one guessing_locations.flatten.uniq.select do |cmd| ((File.exist?(cmd) && File.executable?(cmd)) || exe?(cmd)) && (begin git_version(cmd) $?.success? rescue ; false ; end) end.collect{|cmd| cmd =~ / / ? '"' + cmd + '"' : cmd}.first #:nocov: end end def git_clone_deploy_hooks(repo_dir) debug "Deploy default hooks" Dir.chdir(repo_dir) do |dir| Dir.glob(".openshift/git_hooks/*") do |hook| FileUtils.cp(hook, ".git/hooks/") end end end def git_clone_application(app) repo_dir = options.repo || app.name debug "Pulling new repo down" dir = git_clone_repo(app.git_url, repo_dir) debug "Configuring git repo" Dir.chdir(repo_dir) do git_config_set "rhc.app-id", app.id git_config_set "rhc.app-name", app.name git_config_set "rhc.domain-name", app.domain_id git_remote_add("upstream", app.initial_git_url) if app.initial_git_url.present? end git_clone_deploy_hooks(repo_dir) dir end # :nocov: These all call external binaries so test them in cucumber def git_remote_add(remote_name, remote_url) cmd = "#{discover_git_executable} remote add upstream \"#{remote_url}\"" debug "Running #{cmd} 2>&1" output = %x[#{cmd} 2>&1] raise RHC::GitException, "Error while adding upstream remote - #{output}" unless output.empty? end def git_config_get(key) return nil unless has_git? config_get_cmd = "#{discover_git_executable} config --get #{key}" value = %x[#{config_get_cmd}].strip debug "Git config '#{config_get_cmd}' returned '#{value}'" value = nil if $?.exitstatus != 0 or value.empty? value end def git_config_set(key, value) unset_cmd = "#{discover_git_executable} config --unset-all #{key}" config_cmd = "#{discover_git_executable} config --add #{key} #{value}" debug "Adding #{key} = #{value} to git config" commands = [unset_cmd, config_cmd] commands.each do |cmd| debug "Running #{cmd} 2>&1" output = %x[#{cmd} 2>&1] raise RHC::GitException, "Error while adding config values to git - #{output}" unless output.empty? end end # :nocov: def git_clone_repo(git_url, repo_dir) # quote the repo to avoid input injection risk destination = (repo_dir ? " \"#{repo_dir}\"" : "") cmd = "#{discover_git_executable} clone #{git_url}#{destination}" debug "Running #{cmd}" status, stdout, stderr = run_with_tee(cmd) if status != 0 case stderr when /fatal: destination path '[^']*' already exists and is not an empty directory./ raise RHC::GitDirectoryExists, "The directory you are cloning into already exists." when /^Permission denied \(.*?publickey.*?\).$/ raise RHC::GitPermissionDenied, "You don't have permission to access this repository. Check that your SSH public keys are correct." else raise RHC::GitException, "Unable to clone your repository. Called Git with: #{cmd}" end end File.expand_path(repo_dir) end end end rhc-1.38.7/lib/rhc/commands.rb0000644000004100000410000002766712756270551016122 0ustar www-datawww-datarequire 'commander' require 'commander/command' ## monkey patch option parsing to also parse global options all at once # to avoid conflicts and side effects of similar short switches module Commander class Command attr_accessor :default_action, :root, :info def default_action? default_action.present? end def root? root.present? end alias_method :option_old, :option def option(*args, &block) opts = args.pop if Hash === args.last option_old(*args, &block).tap do |options| options.last.merge!(opts) if opts end end # # Force proxy_option_struct to default to nil for values, # backported for Commander 4.0.3 # def proxy_option_struct proxy_options.inject Options.new do |options, (option, value)| # options that are present will evaluate to true value = true if value.nil? # if multiple values were specified for this option, collect it as an # array. on 'fill_arguments' we will decide between stick with the array # (if :type => :list) or just take the last value from array. # not part of the backported method. if proxy_options.select{ |item| item[0] == option }.length > 1 if options[option] options[option] << value else options.__send__ :"#{option}=", [value] end else options.__send__ :"#{option}=", value end options end end def deprecated(as_alias=nil) return false unless info return info[:deprecated] if info[:deprecated] return false unless info[:aliases] info[:aliases].select{ |a| ['-',' '].map{ |s| Array(a[:action]).join(s) }.include?(as_alias) }.map{ |a| a[:deprecated] }.first if as_alias end def parse_options_and_call_procs *args runner = Commander::Runner.instance opts = OptionParser.new # add global options runner.options.each do |option| opts.on(*option[:args], &runner.global_option_proc(option[:switches], &option[:proc])) end # add command options @options.each do |option| opts.on(*option[:args], &option[:proc]) opts end # Separate option lists with '--' remaining = args.split('--').map{ |a| opts.parse!(a) }.inject([]) do |arr, h| arr << '--' arr.concat(h) end remaining.shift _, config_path = proxy_options.find{ |arg| arg[0] == :config } clean, _ = proxy_options.find{ |arg| arg[0] == :clean } begin @config = RHC::Config.new @config.use_config(config_path) if config_path @config.sync_additional_config $terminal.debug("Using config file #{@config.config_path}") unless clean local_command_options = (@options.collect{|o| Commander::Runner.switch_to_sym(o[:switches].last.split.first)} rescue []) @config.to_options.each_pair do |key, value| next if local_command_options.include?(key) next if proxy_options.detect{ |arr| arr[0] == key } if sw = opts.send(:search, :long, key.to_s.gsub(/_/, '-')) _, cb, val = sw.send(:conv_arg, nil, value) {|*exc| raise(*exc) } cb.call(val) if cb else proxy_options << [key, value] end end end rescue ArgumentError => e n = OptionParser::InvalidOption.new(e.message) n.reason = "The configuration file #{@config.path} contains an invalid setting" n.set_backtrace(e.backtrace) raise n rescue OptionParser::ParseError => e e.reason = "The configuration file #{@config.path} contains an invalid setting" raise end remaining end end end # # Allow Command::Options to lazily evaluate procs and lambdas # module Commander class Command remove_const(:Options) class Options def initialize(init=nil) @defaults = {} @table = {} default(init) if init end def respond_to?(meth) super || meth.to_s =~ /^\w+(=)?$/ end def method_missing meth, *args, &block if meth.to_s =~ /^\w+=$/ raise ArgumentError, "Options does not support #{meth} without a single argument" if args.length != 1 self[meth.to_s.chop] = args.first elsif meth.to_s =~ /^\w+$/ if !@table.has_key?(meth) && !@defaults.has_key?(meth) begin; return super; rescue NoMethodError; nil; end end raise ArgumentError, "Options does not support #{meth} with arguments" if args.length != 0 self[meth] else super end end def respond_to_missing?(meth, private_method = false) meth.to_s =~ /^\w+(=)?$/ end def []=(meth, value) @table[meth.to_sym] = value end def [](meth) k = meth.to_sym value = @table.has_key?(k) ? @table[k] : @defaults[k] value = value.call if value.is_a? Proc value end def __explicit__ @table end def ==(other) @table == other.instance_variable_get(:@table) end def default defaults = {} @defaults.merge!(__to_hash__(defaults)) end def __replace__(options) @table = __to_hash__(options) end def __hash__ @defaults.merge(@table) end def __to_hash__(obj) Options === obj ? obj.__hash__ : obj end end end end module RHC module Commands autoload :Base, 'rhc/commands/base' def self.load Dir[File.join(File.dirname(__FILE__), "commands", "*.rb")].each do |file| require file end self end def self.add(opts) commands[opts[:name]] = opts end def self.global_option(*args, &block) global_options << [args.freeze, block] end def self.deprecated! instance = Commander::Runner.instance command_name = instance.command_name_from_args command = instance.active_command if new_cmd = command.deprecated(command_name) new_cmd = "rhc #{command.name}" if new_cmd == true RHC::Helpers.deprecated_command new_cmd end end def self.needs_configuration!(cmd, options, config) if not (cmd.class.suppress_wizard? or options.noprompt or options.help or config.has_configs_from_files?) $stderr.puts RHC::Helpers.color("You have not yet configured the OpenShift client tools. Please run 'rhc setup'.", :yellow) end end def self.to_commander(instance=Commander::Runner.instance) global_options.each do |args, block| args = args.dup opts = (args.pop if Hash === args.last) || {} option = instance.global_option(*args, &block).last option.merge!(opts) end commands.each_pair do |name, opts| name = Array(name) names = [name.reverse.join('-'), name.join(' ')] if name.length > 1 name = name.join('-') instance.command name do |c| c.description = opts[:description] c.summary = opts[:summary] c.syntax = opts[:syntax] c.default_action = opts[:default] c.info = opts (options_metadata = Array(opts[:options])).each do |o| option_data = [o[:switches], o[:type], o[:description], o.slice(:optional, :default, :hide, :covered_by)].compact.flatten(1) c.option *option_data o[:arg] = Commander::Runner.switch_to_sym(Array(o[:switches]).last) end (args_metadata = Array(opts[:args])).each do |meta| switches = meta[:switches] unless switches.blank? switches = switches.dup switches << meta[:description] switches << meta.slice(:optional, :default, :hide, :covered_by, :allow_nil) c.option *switches end end Array(opts[:aliases]).each do |a| action = Array(a[:action]) [' ', '-'].each do |s| cmd = action.join(s) instance.alias_command cmd, name end end if names names.each{ |alt| instance.alias_command alt, name } else c.root = true end c.when_called do |args, options| deprecated! config = c.instance_variable_get(:@config) cmd = opts[:class].new cmd.options = options cmd.config = config args = fill_arguments(cmd, options, args_metadata, options_metadata, args) needs_configuration!(cmd, options, config) return execute(cmd, :help, args) unless opts[:method] execute(cmd, opts[:method], args) end end end self end protected def self.execute(cmd, method, args) cmd.send(method, *args) end def self.fill_arguments(cmd, options, args, opts, arguments) # process defaults defaults = {} covers = {} (opts + args).each do |option_meta| arg = option_meta[:option_symbol] || option_meta[:name] || option_meta[:arg] or next if arg && option_meta[:type] != :list && options[arg].is_a?(Array) options[arg] = options[arg].last end Array(option_meta[:covered_by]).each{ |sym| (covers[sym] ||= []) << arg } case v = option_meta[:default] when Symbol cmd.send(v, defaults, arg) when Proc v.call(defaults, arg) when nil else defaults[arg] = v end end options.default(defaults) # process required options opts.each do |option_meta| raise ArgumentError.new("Missing required option '#{option_meta[:arg]}'.") if option_meta[:required] && options[option_meta[:arg]].nil? end slots = Array.new(args.count) available = arguments.dup args.each_with_index do |arg, i| value = argument_to_slot(options, available, arg) if value.nil? if arg[:allow_nil] != true && !arg[:optional] raise ArgumentError, "Missing required argument '#{arg[:name]}'." end end slots[i] = value end raise ArgumentError, "Too many arguments passed in: #{available.reverse.join(" ")}" unless available.empty? # reset covered arguments options.__explicit__.keys.each do |k| if covered = covers[k] covered.each do |sym| raise ArgumentError, "The options '#{sym}' and '#{k}' cannot both be provided" unless options.__explicit__[sym].nil? options[sym] = nil end end end slots end def self.argument_to_slot(options, available, arg) if Array(arg[:covered_by]).any?{ |k| !options.__explicit__[k].nil? } return nil end option = arg[:option_symbol] value = options.__explicit__[option] if option if value.nil? value = if arg[:type] == :list take_leading_list(available) else v = available.shift if v == '--' v = nil else available.shift if available.first == '--' end v end end value = options[option] if option && (value.nil? || (value.is_a?(Array) && value.blank?)) if arg[:type] == :list value = Array(value) end options[option] = value if option && !value.nil? value end def self.take_leading_list(available) if i = available.index('--') left = available.shift(i) available.shift left else left = available.dup available.clear left end end def self.commands @commands ||= {} end def self.global_options @options ||= [] end end end rhc-1.38.7/lib/rhc/command_runner.rb0000644000004100000410000001323612756270551017313 0ustar www-datawww-datamodule RHC class CommandRunner < Commander::Runner # regex fix from git - match on word boundries def valid_command_names_from *args arg_string = args.delete_if { |value| value =~ /^-/ }.join ' ' commands.keys.find_all { |name| name if /^#{name}\b/.match arg_string } end if Commander::VERSION == '4.0.3' #:nocov: def program(*args) Array(super).first end #:nocov: end def delete_args(*switches) if @args.any?{|i| switches.include?(i)} switches.each{|switch| @args.delete switch} return true end false end def options_parse_trace delete_args("--trace") end def options_parse_debug delete_args("-d", "--debug") end def options_parse_version if @args.include? "--version" say version exit 0 end end HELP_OPTIONS = ['--help', '-h'] def options_parse_help if (@args & HELP_OPTIONS).present? args = (@args -= HELP_OPTIONS) args.shift if args.first == 'help' && !command_exists?(args.join(' ')) exit run_help(args) end end # override so we can do our own error handling def run! trace = false require_program :version, :description global_option('-h', '--help', 'Help on any command', :hide => true) global_option('--version', 'Display version information', :hide => true) # special case --debug so all commands can output relevant info on it $terminal.debug = options_parse_debug # special case --trace because we need to use it in the runner trace = options_parse_trace # special case --version so it is processed before an invalid command options_parse_version # help is a special branch prior to command execution options_parse_help unless trace begin run_active_command rescue InvalidCommandError => e run_help(provided_arguments) rescue \ OptionParser::InvalidOption => e RHC::Helpers.error e.message 1 rescue \ ArgumentError, OptionParser::ParseError => e help_bindings = CommandHelpBindings.new(active_command, commands, self) usage = RHC::HelpFormatter.new(self).render_command_syntax(help_bindings) message = case e when OptionParser::AmbiguousOption "The option #{e.args.join(' ')} is ambiguous. You will need to specify the entire option." else e.message end RHC::Helpers.error message say "#{usage}" 1 rescue RHC::Exception, RHC::Rest::Exception => e RHC::Helpers.error e.message e.code.nil? ? 128 : [1, (e.code || 1).to_i].max end else run_active_command end end def provided_arguments @args[0, @args.find_index { |arg| arg != '--' and arg.start_with?('-') } || @args.length] end def global_option(*args, &block) opts = args.pop if Hash === args.last super(*args, &block).tap do |options| options.last.merge!(opts) if opts end end def create_default_commands command 'help options' do |c| c.description = "Display all global options and information about configuration" c.when_called do |args, options| say help_formatter.render_options self 0 end end command :help do |c| c.syntax = '' c.description = 'Display global or help documentation.' c.when_called(&method(:run_help)) end end def run_help(args=[], options=nil) args.delete_if{ |a| a.start_with? '-' } unless args[0] == 'commands' variations = (1..args.length).reverse_each.map{ |n| args[0,n].join('-') } cmd = variations.find{ |cmd| command_exists?(cmd) } end if args.empty? say help_formatter.render 0 else if cmd.nil? matches = (variations || ['']).inject(nil) do |candidates, term| term = term.downcase keys = commands.keys.map(&:downcase) prefix, keys = keys.partition{ |n| n.start_with? term } inline, keys = keys.partition{ |n| n.include? term } break [term, prefix, inline] unless prefix.empty? && inline.empty? end unless matches RHC::Helpers.error "The command '#{program :name} #{provided_arguments.join(' ')}' is not recognized.\n" say "See '#{program :name} help' for a list of valid commands." return 1 end candidates = (matches[1] + matches[2]).map{ |n| commands[n] }.uniq.sort_by{ |c| c.name } if candidates.length == 1 cmd = candidates.first.name else RHC::Helpers.pager RHC::Helpers.say matches[0] != '' ? "Showing commands matching '#{matches[0]}'" : "Showing all commands" candidates.reverse.each do |command| RHC::Helpers.paragraph do aliases = (commands.map{ |(k,v)| k if command == v }.compact - [command.name]).map{ |s| "'#{s}'"} aliases[0] = "(also #{aliases[0]}" if aliases[0] aliases[-1] << ')' if aliases[0] RHC::Helpers.header [RHC::Helpers.color(command.name, :cyan), *aliases.join(', ')] say command.description || command.summary end end return 1 end end RHC::Helpers.pager command = command(cmd) help_bindings = CommandHelpBindings.new command, commands, self say help_formatter.render_command help_bindings 0 end end end end rhc-1.38.7/lib/rhc/scp_helpers.rb0000644000004100000410000000146412756270551016613 0ustar www-datawww-data### # ssh_key_helpers.rb - methods to help manipulate ssh keys # # Copyright 2012 Red Hat, Inc. and/or its affiliates. # # 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 'net/ssh' require 'net/scp' require 'rhc/ssh_helpers' require 'rhc/vendor/sshkey' module RHC module SCPHelpers include SSHHelpers end end rhc-1.38.7/lib/rhc/output_helpers.rb0000644000004100000410000002761512756270551017374 0ustar www-datawww-datamodule RHC module OutputHelpers def display_team(team, ids=false) paragraph do header ["Team #{team.name}", ("(owned by #{team.owner.name})" if team.owner.present?)] do section(:bottom => 1) do say format_table \ nil, get_properties( team, (:id if ids), (:global if team.global?), :compact_members ), :delete => true end end end end def display_domain(domain, applications=nil, ids=false) paragraph do header ["Domain #{domain.name}", ("(owned by #{domain.owner.name})" if domain.owner.present?)] do section(:bottom => 1) do say format_table \ nil, get_properties( domain, :creation_time, (:id if ids), (:allowed_gear_sizes unless domain.allowed_gear_sizes.nil?), (:suffix unless domain.suffix.nil? || openshift_online_server?), :compact_members ), :delete => true end applications.each do |a| display_app(a,a.cartridges) end if applications.present? end end end #--------------------------- # Application information #--------------------------- def display_app(app, cartridges=nil, properties=nil, verbose=false) paragraph do header [app.name, "@ #{app.app_url}", "(uuid: #{app.uuid})"] do section(:bottom => 1) do say format_table \ nil, get_properties(app, properties || [:domain, :creation_time, :gear_info, :git_url, :initial_git_url, :ssh_string, :auto_deploy, :aliases]), :delete => true end cartridges.each{ |c| section(:bottom => 1){ display_cart(c, verbose ? :verbose : []) } } if cartridges end end end def display_app_summary(applications) section do if !applications.nil? and !applications.empty? paragraph do indent do say table(applications.map do |app| [app.name, app.app_url] end) end end end end end def display_app_configurations(rest_app) display_app(rest_app, nil, [:auto_deploy, :keep_deployments, :deployment_type, :deployment_branch]) end def display_server(server) paragraph do header ["Server '#{server.nickname || to_host(server.hostname)}'", (server.persisted? ? ("(in use)" if server.default?) : "(not configured, run 'rhc setup')")], {:color => (server.persisted? ? (:green if server.default?) : :yellow)} do section(:bottom => 1) do say format_table \ nil, get_properties( server, :hostname, :login, :use_authorization_tokens, :insecure, :timeout, :ssl_version, :ssl_client_cert_file, :ssl_client_key_file, :ssl_ca_file ), { :delete => true, :color => (server.persisted? ? (:green if server.default?) : :yellow) } end end end end def display_region(region) paragraph do header ["Region '#{region.name}'", "(uuid: #{region.uuid})", ("(default)" if region.default?)], {:color => (:green if region.default?)} do section(:bottom => 1) do say format_table \ nil, get_properties( region, :description, :zones ), { :delete => true, :color => (:green if region.default?) } end end end end def format_cart_header(cart) [ cart.name, cart.name != cart.display_name ? "(#{cart.display_name})" : nil, ].compact end def format_scaling_info(scaling) "x%d (minimum: %s, maximum: %s) on %s gears" % [:current_scale, :scales_from, :scales_to, :gear_profile].map{ |key| format_value(key, scaling[key]) } if scaling end def format_cart_gears(cart) if cart.scalable? format_scaling_info(cart.scaling) elsif cart.shares_gears? "Located with #{cart.collocated_with.join(", ")}" elsif cart.external? && cart.current_scale == 0 "none (external service)" else "%d %s" % [format_value(:current_scale, cart.current_scale), format_value(:gear_profile, cart.gear_profile)] end end def format_gear_info(info) "%d (defaults to %s)" % [:gear_count, :gear_profile].map{ |key| format_value(key, info[key]) } if info end #--------------------------- # Cartridge information #--------------------------- def display_cart(cart, *properties) verbose = properties.delete(:verbose) say format_table \ format_cart_header(cart), get_properties(cart, *properties). concat(verbose && cart.custom? ? [[:description, cart.description.strip]] : []). concat([[:downloaded_cartridge_url, cart.url]]). concat(verbose && cart.custom? ? [[:version, cart.version]] : []). concat(verbose && cart.custom? && cart.license.strip.downcase != 'unknown' ? [[:license, cart.license]] : []). concat(cart.custom? ? [[:website, cart.website]] : []). concat([[cart.scalable? ? :scaling : :gears, format_cart_gears(cart)]]). concat(cart.properties.map{ |p| ["#{table_heading(p['name'])}:", p['value']] }.sort{ |a,b| a[0] <=> b[0] }). concat(cart.environment_variables.present? ? [[:environment_variables, cart.environment_variables.map{|item| "#{item[:name]}=#{item[:value]}" }.sort.join(', ')]] : []), :delete => true say format_usage_message(cart) if cart.usage_rate? end def display_key(key, *properties) properties = [:fingerprint, :principal, :visible_to_ssh?] if properties.empty? say format_table( properties.include?(:name) ? nil : format_key_header(key), get_properties(key, *properties), { :delete => true, :color => (:green if properties.include?(:visible_to_ssh?) && key.visible_to_ssh?), } ) end def display_authorization(auth, default=nil) say format_table( auth.note || "", get_properties(auth, :token, :scopes, :creation_time, :expires_in_seconds), { :delete => true, :color => (:green if auth.token == default), } ) end def format_key_header(key) [ key.name, "(type: #{key.type})", ].compact end def display_cart_storage_info(cart, title="Storage Info") say format_table \ title, get_properties(cart,:base_gear_storage,:additional_gear_storage) end def display_cart_storage_list(carts) carts.each{ |cart| paragraph{ display_cart_storage_info(cart, cart.display_name) } } end def format_usage_message(cart) cart.usage_rates.map do |rate, plans| plans = plans.map(&:capitalize) if plans if plans && plans.length > 1 "This cartridge costs an additional $#{rate} per gear after the first 3 gears on the #{plans[0...-1].join(', ')} and #{plans[-1]} plans." elsif plans && plans.length == 1 "This cartridge costs an additional $#{rate} per gear after the first 3 gears on the #{plans.first} plan." else "This cartridge costs an additional $#{rate} per gear after the first 3 gears." end end end def default_display_env_var(env_var_name, env_var_value=nil) info "#{env_var_name}#{env_var_value.nil? ? '' : '=' + env_var_value}" end def display_env_var_list(env_vars, opts={}) if env_vars.present? if opts[:table] say table(env_vars.collect{ |item| [item.name, opts[:quotes] ? "\"#{item.value}\"" : item.value] }, :header => ['Name', 'Value']) else env_vars.sort.each do |env_var| default_display_env_var(env_var.name, opts[:quotes] ? "\"#{env_var.value}\"" : env_var.value) end end end end def display_deployment(item, highlight_active=true) deployment = item[:deployment] active = item[:active] paragraph do say format_table( "Deployment ID #{deployment.id} #{active ? '(active)' : '(inactive)'}", get_properties(deployment, :ref, :sha1, :created_at, :artifact_url, :hot_deploy, :force_clean_build, :activations), { :delete => true, :color => (:green if active && highlight_active) } ) end end def display_deployment_list(deployment_activations, highlight_active=true) if deployment_activations.present? paragraph do deployment_activations.each do |item| activation = item[:activation] deployment = item[:deployment] rollback = item[:rollback] rollback_to = item[:rollback_to] rolled_back = item[:rolled_back] active = item[:active] say color( date(activation.created_at.to_s) + ', deployment ' + deployment.id + (rollback ? " (rollback to #{date(rollback_to.to_s)}#{rolled_back ? ', rolled back' : ''})" : rolled_back ? ' (rolled back)' : ''), active ? :green : rolled_back ? :yellow : nil) end end end end private def format_table(heading,values,opts = {}) values = values.to_a if values.is_a? Hash values.delete_if do |arr| arr[0] = "#{table_heading(arr.first)}:" if arr[0].is_a? Symbol opts[:delete] and arr.last.nil? || arr.last == "" end table(values, :heading => heading, :indent => heading ? ' ' : nil, :color => opts[:color]) end def format_no_info(type) ["This #{type} has no information to show"] end # This uses the array of properties to retrieve them from an object def get_properties(object,*properties) properties.flatten.map do |prop| # Either send the property to the object or yield it next if prop.nil? value = begin block_given? ? yield(prop) : object.send(prop) rescue ::Exception => e debug_error(e) "" end [prop, format_value(prop,value)] end.compact end # Format some special values def format_value(prop,value) case prop when :plan_id case value when 'free' then 'Free' when 'silver' then 'Silver' else value && value.capitalize || nil end when :visible_to_ssh? value || nil when :creation_time, :created_at date(value) when :scales_from,:scales_to (value == -1 ? "available" : value) when :gear_info format_gear_info(value) when :base_gear_storage,:additional_gear_storage ((value.nil? or value == 0) ? "None" : "#{value}GB") when :aliases value.kind_of?(Array) ? value.join(', ') : value when :expires_in_seconds distance_of_time_in_words(value) when :activations value.collect{|item| date(item.created_at.to_s)}.join("\n") when :auto_deploy value ? 'auto (on git push)' : "manual (use 'rhc deploy')" else case value when Array then value.empty? ? '' : value.join(', ') else value end end end end end rhc-1.38.7/lib/rhc/tar_gz.rb0000644000004100000410000000247512756270551015575 0ustar www-datawww-datarequire 'stringio' require 'archive/tar/minitar' include Archive::Tar TAR_BIN = File.executable?('/usr/bin/gnutar') ? '/usr/bin/gnutar' : 'tar' module RHC module TarGz def self.contains(filename, search, force_ruby=false) return false if ! (File.file? filename and File.basename(filename).downcase =~ /.\.t(ar\.)?gz$/i) regex = Regexp.new search if RHC::Helpers.windows? or RHC::Helpers.mac? or force_ruby begin zlib::GzipReader.open(filename) do |gz| Minitar::Reader.open gz do |tar| tar.each_entry do |entry| if entry.full_name =~ regex return true end end end end rescue zlib::GzipFile::Error, zlib::GzipFile::Error false end else # combining STDOUT and STDERR (i.e., 2>&1) does not suppress output # when the specs run via 'bundle exec rake spec' system "#{TAR_BIN} --wildcards -tf '#{filename}' '#{regex.source}' 2>/dev/null >/dev/null" end end private def self.zlib #:nocov: require 'zlib' rescue nil if defined? Zlib::GzipReader Zlib else require 'rhc/vendor/zliby' RHC::Vendor::Zlib end #:nocov: end end end rhc-1.38.7/lib/rhc/deployment_helpers.rb0000644000004100000410000001054212756270552020204 0ustar www-datawww-datarequire 'rhc/ssh_helpers' module RHC module DeploymentHelpers extend self include RHC::SSHHelpers protected def deploy_artifact(rest_app, artifact, hot_deploy, force_clean_build) is_file = File.file?(artifact) is_url = URI::ABS_URI.match(artifact).present? if rest_app.deployment_type == 'binary' if is_file deploy_local_file(rest_app, artifact, hot_deploy, force_clean_build) elsif is_url deploy_file_from_url(rest_app, artifact, hot_deploy, force_clean_build) else paragraph do warn "The application '#{rest_app.name}' is configured for binary deployments but the artifact "\ "provided ('#{artifact}') is not a binary file. Please provide the path to a deployable file on "\ "your local filesystem or a url, or configure your app to deploy from a git reference with 'rhc "\ "configure-app #{rest_app.name} --deployment-type git'." end raise IncompatibleDeploymentTypeException end elsif is_file || is_url paragraph do warn "The application '#{rest_app.name}' is configured for git "\ "reference deployments but the artifact provided ('#{artifact}') is #{is_file ? 'a file' : 'a url'}. Please "\ "provide a git reference to deploy (branch, tag or commit SHA1) or configure your app to deploy from binaries "\ "with 'rhc configure-app #{rest_app.name} --deployment-type binary'." end raise IncompatibleDeploymentTypeException else deploy_git_ref(rest_app, artifact, hot_deploy, force_clean_build) end end def deploy_git_ref(rest_app, ref, hot_deploy, force_clean_build) say "Deployment of git ref '#{ref}' in progress for application #{rest_app.name} ..." ssh_url = URI(rest_app.ssh_url) remote_cmd = "gear deploy #{ref}#{hot_deploy ? ' --hot-deploy' : ''}#{force_clean_build ? ' --force-clean-build' : ''}" begin ssh_ruby(ssh_url.host, ssh_url.user, remote_cmd) success "Success" rescue ssh_cmd = "ssh -t #{ssh_url.user}@#{ssh_url.host} '#{remote_cmd}'" warn "Error deploying git ref. You can try to deploy manually with:\n#{ssh_cmd}" raise end end def deploy_local_file(rest_app, filename, hot_deploy, force_clean_build) filename = File.expand_path(filename) say "Deployment of file '#{filename}' in progress for application #{rest_app.name} ..." ssh_url = URI(rest_app.ssh_url) remote_cmd = "oo-binary-deploy#{hot_deploy ? ' --hot-deploy' : ''}#{force_clean_build ? ' --force-clean-build' : ''}" begin ssh_send_file_ruby(ssh_url.host, ssh_url.user, remote_cmd, filename) success "Success" rescue ssh_cmd = "ssh -t #{ssh_url.user}@#{ssh_url.host} '#{remote_cmd}'" warn "Error deploying local file. You can try to deploy manually with:\n#{ssh_cmd}" raise end end def deploy_file_from_url(rest_app, file_url, hot_deploy, force_clean_build) say "Deployment of file '#{file_url}' in progress for application #{rest_app.name} ..." ssh_url = URI(rest_app.ssh_url) file_url = URI(file_url) remote_cmd = "oo-binary-deploy#{hot_deploy ? ' --hot-deploy' : ''}#{force_clean_build ? ' --force-clean-build' : ''}" begin ssh_send_url_ruby(ssh_url.host, ssh_url.user, remote_cmd, file_url) success "Success" rescue ssh_cmd = "ssh -t #{ssh_url.user}@#{ssh_url.host} '#{remote_cmd}'" warn "Error deploying file from url. You can try to deploy manually with:\n#{ssh_cmd}" raise end end def activate_deployment(rest_app, deployment_id) say "Activating deployment '#{deployment_id}' on application #{rest_app.name} ..." ssh_url = URI(rest_app.ssh_url) remote_cmd = "gear activate --all #{deployment_id}" begin ssh_ruby(ssh_url.host, ssh_url.user, remote_cmd) success "Success" rescue ssh_cmd = "ssh -t #{ssh_url.user}@#{ssh_url.host} '#{remote_cmd}'" warn "Error activating deployment. You can try to activate manually with:\n#{ssh_cmd}" raise end end end end rhc-1.38.7/lib/rhc/autocomplete.rb0000644000004100000410000000414212756270551017001 0ustar www-datawww-datamodule RHC class AutoComplete attr_reader :runner def initialize(runner=::Commander::Runner.instance, shell='bash') @runner, @shell = runner, shell end def to_s @s ||= template.result AutoCompleteBindings.new(self).get_binding end private def template @template ||= ERB.new(File.read(File.join(File.dirname(__FILE__), 'autocomplete_templates', "#{@shell}.erb")), nil, '-') end end class AutoCompleteBindings attr_reader :commands, :top_level_commands, :global_options def initialize(data) @commands = {} @top_level_commands = [] data.runner.commands.each_pair do |name, cmd| next if cmd.summary.nil? next if cmd.deprecated(name) if cmd.root? if cmd.name == name @top_level_commands << name end else @top_level_commands << name if name == cmd.name commands = name.split ' ' action = commands.pop id = commands.join(' ') v = @commands[id] || {:actions => [], :switches => []} v[:actions] << action unless id == '' && name != cmd.name @commands[id] = v end v = @commands[name.to_s] || {:actions => [], :switches => []} v[:switches].concat(cmd.options.map do |o| if o[:switches] s = o[:switches][-1].split(' ')[0] if m = /--\[no-\](.+)/.match(s) s = ["--#{m[1]}", "--no-#{m[1]}"] else s end end end.flatten.compact.sort) @commands[name.to_s] = v end # Inject autocomplete for a help command whose possible actions are all other commands in root or hyphenated form @top_level_commands << 'help' @commands['help'] = { :actions => @commands.keys.sort.select {|c| c != '' && c !~ / /}, :switches => [] } @commands.delete('') @commands = @commands.to_a.sort{ |a,b| a[0] <=> b[0] } @top_level_commands.sort! @global_options = data.runner.options.map{ |o| o[:switches][-1].split(' ')[0] }.sort end end end rhc-1.38.7/lib/rhc/usage_templates/0000755000004100000410000000000012756270552017135 5ustar www-datawww-datarhc-1.38.7/lib/rhc/usage_templates/help.erb0000644000004100000410000000302512756270552020557 0ustar www-datawww-dataUsage: rhc [--help] [--version] [--debug] [] <%= Array(program :description).first %> <% remaining = Hash[commands.dup.select{ |name, command| not command.summary.blank? }] basic = remaining.slice!('setup', 'create-app', 'apps', 'cartridges', 'add-cartridge', 'set-env', 'logout') remaining.delete_if{ |name, command| alias?(name) } begin -%> Getting started: <%= table(basic.map{ |arr| arr[1] = arr[1].summary || arr[1].description; arr }, table_args(' ', 18)).join("\n") %> <% end unless basic.empty? %> <% debugging = remaining.slice!('app restart', 'app show', 'tail', 'port-forward', 'threaddump', 'snapshot', 'git-clone') begin -%> Working with apps: <%= table(debugging.map{ |arr| arr[1] = arr[1].summary || arr[1].description; arr }, table_args(' ', 18)).join("\n") %> <% end unless debugging.empty? %> Management commands: <%= table(remaining.sort.select{ |arr| arr[1].root? and arr[1].summary.present? }.map do |arr| arr[1] = arr[1].summary || arr[1].description arr end, table_args(' ', 18)).join("\n") %> Some management commands have nested actions - run 'rhc help ' for more info. See '<%= Array(program :name).first %> help ' for more information on a specific command. You can see a list of all commands with 'rhc help commands'. See 'rhc help options' for a list of global command-line options and information about the config file. <% if program :help -%> <% for title, body in program(:help) %> <%= $terminal.color title.to_s.upcase, :bold %>: <%= body %> <% end -%> <% end -%>rhc-1.38.7/lib/rhc/usage_templates/options_help.erb0000644000004100000410000000117412756270552022335 0ustar www-datawww-dataThe following options can be passed to any command: <%= table(options.map{ |option| [option[:switches].join(', '), option[:description]] }, table_args(' ', 26)).join("\n") %> The following configuration options are stored in the <%= RHC::Helpers.system_path('.openshift/express.conf') %> file in your home directory. Passing any of these options to 'rhc setup' will save that value to the file: <%= table( RHC::Config::OPTIONS.select{ |key,value| not value.nil? }.map do |key,value| ["--#{key.to_s.gsub('_','-')}", value[0] || key] end, table_args(' ', 26).merge(:header => ['Option', 'In Config']) ).join("\n") -%> rhc-1.38.7/lib/rhc/usage_templates/missing_help.erb0000644000004100000410000000011712756270552022307 0ustar www-datawww-dataSee '<%= Array(program :name).first %> help' for a list of available commands. rhc-1.38.7/lib/rhc/usage_templates/command_help.erb0000644000004100000410000000333512756270552022261 0ustar www-datawww-data<% if @command.syntax.is_a?(Array) -%> Usage <%= table( @command.syntax.map do |s| s ? [Array(program :name).first, @command.name, s] : [' '] end, table_args(' ') ).join("\n") %> <% else -%> Usage: <%= Array(program :name).first %> <%= @command.name %> <%= @command.syntax %> <% end -%> <%= @command.description || @command.summary %> <% if @actions.blank? or @command.root? and @command.info.present? and @command.info[:method].present? -%> <% unless @command.info[:args].blank? or @command.options.all?{ |o| o[:hide] or o[:switches].present? } -%> Arguments <%= table( @command.info[:args].select do |opt| not opt[:hide] and not opt[:switches].present? end.map do |opt| [opt[:name], opt[:description]] end, table_args(' ', 25) ).join("\n") %> <% end -%> <% unless @command.options.blank? or @command.options.all?{ |o| o[:hide] } -%> Options <%= table( @command.options.select do |opt| not opt[:hide] end.map do |opt| [opt[:switches].join(', '), opt[:description]] end, table_args(' ', 25) ).join("\n") %> <% end -%> Global Options <%= table( @global_options.select do |opt| not (opt[:hide] || @command.options.any?{ |o| (o[:switches].map(&:split).map(&:first) & opt[:switches].map(&:split).map(&:first)).present? }) end.map do |opt| [opt[:switches].join(', '), opt[:description]] end, table_args(' ', 25) ).join("\n") %> See 'rhc help options' for a full list of global options. <% end -%> <% if @actions.present? -%> List of Actions <%= table(@actions.map{ |a| [a[:name], a[:summary]] }, table_args(' ', 13)).join("\n") %> <% if @command.default_action? -%> The default action for this resource is '<%= @command.default_action %>' <% end -%> <% end -%>rhc-1.38.7/lib/rhc/usage_templates/command_syntax_help.erb0000644000004100000410000000125512756270552023666 0ustar www-datawww-data<% if @command.syntax.is_a?(Array) -%> Usage <%= table( @command.syntax.map do |s| s ? [Array(program :name).first, @command.name, s] : [' '] end, table_args(' ') ).join("\n") %> <% else -%> Usage: <%= Array(program :name).first %> <%= @command.name %><%= " #{@command.syntax}" if @command.syntax %> <% end -%> <% unless @command.options.blank? or @command.options.all?{ |o| o[:hide] } -%> Pass '--help' to see the full list of options <% end -%> <% unless @actions.nil? or @actions.empty? -%> <%= @command.description || @command.summary %> List of Actions <%= table(@actions.map{ |a| [a[:name], a[:summary]] }, table_args(' ', 18)).join("\n") %> <% end -%> rhc-1.38.7/lib/rhc/wizard.rb0000644000004100000410000005241512756270551015606 0ustar www-datawww-datarequire 'rhc' require 'fileutils' require 'socket' require 'rhc/server_helpers' module RHC class Wizard include HighLine::SystemExtensions def self.has_configuration? File.exists? RHC::Config.local_config_path end DEFAULT_MAX_LENGTH = 16 SERVER_STAGES = [ :server_stage, ] CONFIG_STAGES = [ :login_stage, :create_config_stage, ] KEY_STAGES = [ :config_ssh_key_stage, :upload_ssh_key_stage, ] TEST_STAGES = [ :install_client_tools_stage, :setup_test_stage, ] NAMESPACE_STAGES = [ :config_namespace_stage, ] APP_STAGES = [ :show_app_info_stage, ] STAGES = [:greeting_stage] + SERVER_STAGES + CONFIG_STAGES + KEY_STAGES + TEST_STAGES + NAMESPACE_STAGES + APP_STAGES + [:finalize_stage] def stages STAGES end attr_reader :rest_client # # Running the setup wizard may change the contents of opts and config if # the create_config_stage completes successfully. # def initialize(config=RHC::Config.new, opts=Commander::Command::Options.new, servers=RHC::Servers.new) @config = config @options = opts @servers = servers @servers.sync_from_config(@config) end # Public: Runs the setup wizard to make sure ~/.openshift and ~/.ssh are correct # # Examples # # wizard.run() # # => true # # Returns nil on failure or true on success def run stages.each do |stage| debug "Running #{stage}" if self.send(stage).nil? return nil end end true end protected include RHC::Helpers include RHC::SSHHelpers include RHC::GitHelpers include RHC::CartridgeHelpers include RHC::ServerHelpers attr_reader :config, :options, :servers attr_accessor :auth, :user attr_writer :rest_client def hostname Socket.gethostname end def openshift_server options.server || config['libra_server'] || openshift_online_server end def new_client_for_options client_from_options({ :auth => auth, }) end def core_auth @core_auth ||= begin if options.ssl_client_cert_file && options.ssl_client_key_file RHC::Auth::X509.new(options) else RHC::Auth::Basic.new(options) end end end def token_auth RHC::Auth::Token.new(options, core_auth, token_store) end def auth(reset=false) @auth = nil if reset @auth ||= begin if options.token token_auth else core_auth end end end def token_store @token_store ||= RHC::Auth::TokenStore.new(config.home_conf_path) end def username options.rhlogin || (auth.username if auth.respond_to?(:username)) end def print_dot $terminal.instance_variable_get(:@output).print('.') end # cache SSH keys from the REST client def ssh_keys @ssh_keys ||= rest_client.sshkeys end # clear SSH key cache def clear_ssh_keys_cache @ssh_keys = nil end # return true if the account has the public key defined by # RHC::Config::ssh_pub_key_file_path def ssh_key_uploaded? ssh_keys.present? && ssh_keys.any? { |k| k.fingerprint.present? && k.fingerprint == fingerprint_for_default_key } end def non_ssh_key_uploaded? ssh_keys.present? && !ssh_keys.all?(&:is_ssh?) end def existing_keys_info return unless ssh_keys indent{ ssh_keys.each{ |key| paragraph{ display_key(key) } } } end def applications @applications ||= rest_client.domains.map(&:applications).flatten end def namespace_optional? true end # # Stages # def greeting_stage info "OpenShift Client Tools (RHC) Setup Wizard" paragraph do say "This wizard will help you upload your SSH keys, set your application namespace, and check that other programs like Git are properly installed." end true end def server_stage paragraph do unless options.__explicit__[:server] say "If you have your own OpenShift server, you can specify it now. Just hit enter to use#{openshift_online_server? ? ' the server for OpenShift Online' : ''}: #{openshift_server}." options.server = ask "Enter the server hostname: " do |q| q.default = openshift_server q.responses[:ask_on_error] = '' end paragraph{ say "You can add more servers later using 'rhc server'." } end end true end def login_stage if token_for_user options.token = token_for_user say "Using an existing token for #{options.rhlogin} to login to #{openshift_server}" elsif options.rhlogin say "Using #{options.rhlogin} to login to #{openshift_server}" end self.rest_client = new_client_for_options begin rest_client.api rescue RHC::Rest::CertificateVerificationFailed => e debug "Certificate validation failed: #{e.reason}" unless options.insecure if RHC::Rest::SelfSignedCertificate === e warn "The server's certificate is self-signed, which means that a secure connection can't be established to '#{openshift_server}'." else warn "The server's certificate could not be verified, which means that a secure connection can't be established to '#{openshift_server}'." end if openshift_online_server? paragraph{ warn "This may mean that a server between you and OpenShift is capable of accessing information sent from this client. If you wish to continue without checking the certificate, please pass the -k (or --insecure) option to this command." } return else paragraph{ warn "You may bypass this check, but any data you send to the server could be intercepted by others." } return unless agree "Connect without checking the certificate? (yes|no): " options.insecure = true self.rest_client = new_client_for_options retry end end end self.user = rest_client.user options.rhlogin = self.user.login unless username if options.create_token == false say "Skipping token generation..." elsif rest_client.supports_sessions? && !options.token paragraph do info "OpenShift can create and store a token on disk which allows to you to access the server without using your password. The key is stored in your home directory and should be kept secret. You can delete the key at any time by running 'rhc logout'." if options.create_token or agree "Generate a token now? (yes|no) " say "Generating an authorization token for this client ... " token = rest_client.new_session options.token = token.token self.auth(true).save(token.token) self.rest_client = new_client_for_options self.user = rest_client.user success "lasts #{distance_of_time_in_words(token.expires_in_seconds)}" end end end true end def create_config_stage paragraph do FileUtils.mkdir_p File.dirname(config.path) changed = Commander::Command::Options.new(options) changed.rhlogin = username changed.password = nil changed.use_authorization_tokens = options.create_token != false && !changed.token.nil? changed.insecure = options.insecure == true options.__replace__(changed) # Save servers.yml if: # 1. we've been explicitly told to (typically when running the "rhc server" command) # 2. if the servers.yml file exists # 3. if we're configuring a second server write_servers_yml = @save_servers || servers.present? || (servers.list.present? && !servers.hostname_exists?(options.server)) # Decide which fields to save to express.conf # 1. If we've already been told explicitly, use that # 2. If we're writing servers.yml, only save server to express.conf # 3. If we're not writing servers.yml, save everything to express.conf config_fields_to_save = @config_fields_to_save || (write_servers_yml ? [:server] : nil) # Save config unless we've been explicitly told not to save any fields to express.conf if config_fields_to_save != [] say "Saving configuration to #{system_path(config.path)} ... " config.backup FileUtils.rm(config.path, :force => true) config.save!(changed, config_fields_to_save) success "done" end if write_servers_yml say "Saving server configuration to #{system_path(servers.path)} ... " servers.backup servers.add_or_update(options.server, :login => options.rhlogin, :use_authorization_tokens => options.use_authorization_tokens, :insecure => options.insecure, :timeout => options.timeout, :ssl_version => options.ssl_version, :ssl_client_cert_file => options.ssl_client_cert_file, :ssl_client_key_file => options.ssl_client_key_file, :ssl_ca_file => options.ssl_ca_file) servers.save! success "done" end end true end def config_ssh_key_stage if config.should_run_ssh_wizard? paragraph{ say "No SSH keys were found. We will generate a pair of keys for you." } ssh_pub_key_file_path = generate_ssh_key_ruby paragraph{ say " Created: #{ssh_pub_key_file_path}" } end true end def upload_ssh_key_stage return true if ssh_key_uploaded? || non_ssh_key_uploaded? upload = paragraph do agree "Your public SSH key must be uploaded to the OpenShift server to access code. Upload now? (yes|no) " end if upload if ssh_keys.empty? paragraph do info "Since you do not have any keys associated with your OpenShift account, "\ "your new key will be uploaded as the 'default' key." upload_ssh_key('default') end else paragraph { existing_keys_info } key_fingerprint = fingerprint_for_default_key unless key_fingerprint paragraph do warn "Your public SSH key at #{system_path(RHC::Config.ssh_pub_key_file_path)} is invalid or unreadable. "\ "Setup can not continue until you manually remove or fix your "\ "public and private keys id_rsa keys." end return false end paragraph do say "You can enter a name for your key, or leave it blank to use the default name. " \ "Using the same name as an existing key will overwrite the old key." end ask_for_key_name end else paragraph do info "You can upload your public SSH key at a later time using the 'rhc sshkey' command" end end true end def ask_for_key_name(default_name=get_preferred_key_name) key_name = nil paragraph do begin key_name = ask "Provide a name for this key: " do |q| q.default = default_name q.responses[:ask_on_error] = '' end end while !upload_ssh_key(key_name) end end def get_preferred_key_name userkey = username ? username.gsub(/@.*/, '') : '' pubkey_base_name = "#{userkey}#{hostname.gsub(/\..*\z/,'')}".gsub(/[^A-Za-z0-9]/,'').slice(0,16) find_unique_key_name(pubkey_base_name) end # given the base name and the maximum length, # find a name that does not clash with what is in opts[:keys] def find_unique_key_name(base='default') max = DEFAULT_MAX_LENGTH key_name_suffix = 1 candidate = base while ssh_keys.detect { |k| k.name == candidate } candidate = base.slice(0, max - key_name_suffix.to_s.length) + key_name_suffix.to_s key_name_suffix += 1 end candidate end def upload_ssh_key(key_name) return false unless key_name.present? type, content, comment = ssh_key_triple_for_default_key if ssh_keys.present? && ssh_keys.any? { |k| k.name == key_name } clear_ssh_keys_cache paragraph do say "Key with the name '#{key_name}' already exists. Updating ... " key = rest_client.find_key(key_name) key.update(type, content) success "done" end else clear_ssh_keys_cache begin rest_client.add_key(key_name, content, type) paragraph{ say "Uploading key '#{key_name}' ... #{color('done', :green)}" } rescue RHC::Rest::ValidationException => e error e.message || "Unknown error during key upload." return false end end true end ## # Alert the user that they should manually install tools if they are not # currently available # # Unix Tools: # git # # Windows Tools: # msysgit (Git for Windows) # TortoiseGIT (Windows Explorer integration) # def install_client_tools_stage if windows? windows_install else generic_unix_install_check end true end def config_namespace_stage paragraph do say "Checking for a domain ... " domains = rest_client.domains if domains.length == 0 warn "none" paragraph do say [ "Applications are grouped into domains - each domain has a unique name (called a namespace) that becomes part of your public application URL.", ("You may create your first domain here or leave it blank and use 'rhc create-domain' later." if namespace_optional?), "You will not be able to create an application without completing this step.", ].compact.join(' ') end ask_for_namespace else success domains.map(&:name).join(', ') end end true end def show_app_info_stage section do say "Checking for applications ... " if !applications.nil? and !applications.empty? success "found #{applications.length}" paragraph do indent do say table(applications.map do |app| [app.name, app.app_url] end) end end else info "none" paragraph{ say "Run 'rhc create-app' to create your first application." } paragraph do say table(standalone_cartridges.sort {|a,b| a.display_name <=> b.display_name }.map do |cart| [' ', cart.display_name, "rhc create-app #{cart.name}"] end) end end paragraph do indent do say "You are using #{color(self.user.consumed_gears.to_s, :green)} of #{color(self.user.max_gears.to_s, :green)} total gears" if user.max_gears.is_a? Fixnum say "The following gear sizes are available to you: #{self.user.capabilities.gear_sizes.join(', ')}" if user.capabilities.gear_sizes.present? end end end true end # Perform basic tests to ensure that setup is sane # search for private methods starting with "test_" and execute them # in alphabetical order # NOTE: The order itself is not important--the tests should be independent. # However, the hash order is unpredictable in Ruby 1.8, and is preserved in # 1.9. There are two similar tests that can fail under the same conditions, # and this makes the spec results inconsistent between 1.8 and 1.9. # Thus, we force an order with #sort to ensure spec passage on both. def setup_test_stage say "Checking common problems " failed = false all_test_methods.sort.each do |test| begin send(test) print_dot unless failed true rescue => e say "\n" unless failed failed = true paragraph{ error e.message } false end end.tap do |pass| success(' done') if !failed end true end def all_test_methods (protected_methods + private_methods).select {|m| m.to_s.start_with? 'test_'} end ### # tests for specific user errors def test_private_key_mode pub_key_file = RHC::Config.ssh_pub_key_file_path private_key_file = RHC::Config.ssh_priv_key_file_path if File.exist?(private_key_file) unless File.stat(private_key_file).mode.to_s(8) =~ /[4-7]00$/ raise "Your private SSH key file should be set as readable only to yourself. Please run 'chmod 600 #{system_path(private_key_file)}'" end end true end # test connectivity an app def test_ssh_connectivity return true unless ssh_key_uploaded? || non_ssh_key_uploaded? applications.take(1).each do |app| begin host, user = RHC::Helpers.ssh_string_parts(app.ssh_url) ssh = Net::SSH.start(host, user, :timeout => 60) rescue Interrupt => e debug_error(e) raise "Connection attempt to #{app.host} was interrupted" rescue ::Exception => e debug_error(e) raise "An SSH connection could not be established to #{app.host}. Your SSH configuration may not be correct, or the application may not be responding. #{e.message} (#{e.class})" ensure ssh.close if ssh end end true end ### def finalize_stage paragraph do say "The OpenShift client tools have been configured on your computer. " \ "You can run this setup wizard at any time by using the command 'rhc setup' " \ "We will now execute your original " \ "command (rhc #{ARGV.join(" ")})" end true end def config_namespace(namespace) # skip if string is empty if namespace_optional? and (namespace.nil? or namespace.chomp.blank?) paragraph{ info "You may create a domain later through 'rhc create-domain'" } return true end begin domain = rest_client.add_domain(namespace) options.namespace = namespace success "Your domain '#{domain.name}' has been successfully created" rescue RHC::Rest::ValidationException => e error e.message || "Unknown error during domain creation." return false end true end def ask_for_namespace # Ask for a namespace at least once, configure the namespace if a valid, # non-blank string is provided. namespace = nil paragraph do begin namespace = ask "Please enter a namespace (letters and numbers only)#{namespace_optional? ? " ||" : ""}: " do |q| q.responses[:ask_on_error] = '' end end while !config_namespace(namespace) end end def generic_unix_install_check paragraph do say "Checking for git ... " if has_git? success("found #{git_version}") rescue success('found') else warn "needs to be installed" paragraph do say "Automated installation of client tools is not supported for " \ "your platform. You will need to manually install git for full " \ "OpenShift functionality." end end end end def windows_install unless discover_ssh_executable.present? && discover_git_executable.present? warn < 1 do success "Your client tools are now configured." end true end end class EmbeddedWizard < Wizard def stages super - APP_STAGES - KEY_STAGES - [:setup_test_stage] end def finalize_stage true end protected def namespace_optional? false end end class DomainWizard < Wizard def initialize(*args) client = args.length == 3 ? args.pop : nil super *args self.rest_client = client || new_client_for_options end def stages [:config_namespace_stage] end protected def namespace_optional? false end end class SSHWizard < Wizard def stages KEY_STAGES end def initialize(rest_client, config, options) self.rest_client = rest_client super config, options end end class ServerWizard < Wizard def initialize(config, options, server_configs, set_as_default=false) @save_servers = true @config_fields_to_save = set_as_default ? [:server] : [] super config, options, server_configs end def stages CONFIG_STAGES end end end rhc-1.38.7/lib/rhc/exceptions.rb0000644000004100000410000002071512756270551016465 0ustar www-datawww-datamodule RHC class Exception < StandardError attr_reader :code def initialize(message=nil, code=1) super(message) @code = code end end class ConfirmationError < Exception def initialize(message="This action requires the --confirm option (or entering 'yes' at a prompt) to run.", code=1) super(message, code) end end class CartridgeNotFoundException < Exception def initialize(message="Cartridge not found") super message, 154 end end class AliasNotFoundException < Exception def initialize(message="Alias not found") super message, 156 end end class MultipleCartridgesException < Exception def initialize(message="Multiple cartridge found") super message, 155 end end class EnvironmentVariableNotFoundException < Exception def initialize(message="Environment variable not found") super message, 157 end end class EnvironmentVariablesNotSupportedException < Exception def initialize(message="Server does not support environment variables") super message, 158 end end class EnvironmentVariableNotProvidedException < Exception def initialize(message="Environment variable not provided") super message, 159 end end class JenkinsNotInstalledOnServer < Exception def initialize(message="There is no installed cartridge that exposes Jenkins") super message, 160 end end class KeyNotFoundException < Exception def initialize(message="SSHKey not found") super message, 118 end end class GitException < Exception def initialize(message="Git returned an error") super message, 216 end end class TeamsNotSupportedException < Exception def initialize(message="Server does not support teams") super message, 161 end end class TeamNotFoundException < Exception def initialize(message="Team not found") super message, 162 end end class MemberNotFoundException < Exception def initialize(message="Member not found") super message, 163 end end class ServerNicknameExistsException < Exception def initialize(nickname) super "You already have a server configured with the nickname '#{nickname}'", 164 end end class ServerHostnameExistsException < Exception def initialize(hostname) super "You already have a server configured with the hostname '#{hostname}'", 165 end end class ServerNotConfiguredException < Exception def initialize(server) super "You don't have any server configured with the hostname or nickname '#{server}'", 166 end end class ServerInUseException < Exception def initialize(message="Server in use") super message, 167 end end class RegionsAndZonesNotSupportedException < Exception def initialize(message="Server does not support regions and zones") super message, 168 end end class NoRegionConfiguredException < Exception def initialize(message="Server doesn't have any regions or zones configured") super message, 169 end end class GitPermissionDenied < GitException; end class GitDirectoryExists < GitException; end class DeprecatedError < RuntimeError; end class KeyFileNotExistentException < Exception def initialize(message="SSH Key file not found") super message, 128 end end class KeyFileAccessDeniedException < Exception def initialize(message = "Insufficient acces to SSH Key file") super message, 128 end end class KeyDataInvalidException < Exception def initialize(message = "SSH Key file contains invalid data") super message, 128 end end class PermissionDeniedException < Exception def initialize(message="Permission denied") super message, 129 end end class NoPortsToForwardException < Exception def initialize(message="No available ports to forward") super message, 102 end end class PortForwardFailedException < Exception def initialize(message="Port forward failed") super message, 1 end end class SnapshotSaveException < Exception def initialize(message="Error trying to save snapshot") super message, 130 end end class SnapshotRestoreException < Exception def initialize(message="Error trying to restore snapshot") super message, 130 end end class DeploymentNotFoundException < Exception def initialize(message="Deployment not found") super message, 131 end end class DeploymentsNotSupportedException < Exception def initialize(message="The server does not support deployments") super message, 132 end end class IncompatibleDeploymentTypeException < Exception def initialize(message="The artifact provided is not compatible with the app deployment type.") super message, 133 end end class AppCloneNotSupportedException < Exception def initialize(message="The server does not support cloning apps") super message, 134 end end class MissingScalingValueException < Exception def initialize(message="Must provide either a min or max value for scaling") super message end end class CartridgeNotScalableException < Exception def initialize(message="Cartridge is not scalable") super message end end class ConnectionFailed < Exception end class SSHAuthenticationFailed < Exception def initialize(host, user) super "Authentication to server #{host} with user #{user} failed" end end class SSHConnectionRefused < ConnectionFailed def initialize(host, user) super "The server #{host} refused a connection with user #{user}. The application may be unavailable.", 1 end end class SSHCommandFailed < Exception def initialize(exit_status, message=nil) super message || "SSH command finished with exit status = #{exit_status}", 133 end end class AdditionalStorageArgumentsException < Exception def initialize(message="Only one storage action can be performed at a time.") super message, 1 end end class AdditionalStorageValueException < Exception def initialize(message="The amount format must be a number, optionally followed by 'GB' (ex.: 5GB)") super message, 1 end end class AdditionalStorageRemoveException < Exception def initialize(message="The amount of additional storage to be removed exceeds the total amount in use. Add the -f flag to override.") super message, 1 end end class ChangeMembersOnResourceNotSupported < Exception def initialize(message="You can only add or remove members on a domain or team.") super message, 1 end end class MembersNotSupported < Exception def initialize(message="The server does not support adding or removing members on this resource.") super message, 1 end end class UnsupportedError < Exception def initialize(message="This operation is not supported by the server.") super message, 1 end end class NoPerGearOperations < UnsupportedError def initialize super "The server does not support operations on individual gears." end end class ServerAPINotSupportedException < UnsupportedError def initialize(min_version, current_version) super "The server does not support this command (requires #{min_version}, found #{current_version})." end end class OperationNotSupportedException < UnsupportedError; end class InvalidURIException < Exception def initialize(uri) super "Invalid URI specified: #{uri}" end end class InvalidSSHExecutableException < Exception def initialize(message="Invalid or missing SSH executable") super message end end class FileOrPathNotFound < Exception def initialize(message="File, file path, or directory could not be found") super message end end class RemoteFileOrPathNotFound < FileOrPathNotFound def initialize(message="Remote File, file path, or directory could not be found") super message end end class ArgumentNotValid < Exception def initialize(message="Argument is not valid for this command") super message end end class NoDomainsForUser < Exception def initialize(message="In order to deploy applications, you must create a domain with 'rhc setup' or 'rhc create-domain'.") super message, 1 end end class HighAvailabilityNotSupportedException < Exception def initialize(message="The server does not support high availability") super message, 135 end end end rhc-1.38.7/lib/rhc/context_helper.rb0000644000004100000410000001374012756270551017327 0ustar www-datawww-datarequire 'rhc/git_helpers' require 'rhc/server_helpers' module RHC # # Methods in this module should not attempt to read from the options hash # in a recursive manner (server_context can't read options.server). # module ContextHelpers include RHC::GitHelpers include RHC::ServerHelpers def self.included(other) other.module_eval do def self.takes_team(opts={}) if opts[:argument] argument :team_name, "Name of a team", ["-t", "--team-name NAME"], :allow_nil => true, :covered_by => :team_id else #:nocov: option ["-t", "--team-name NAME"], "Name of a team", :covered_by => :team_id #:nocov: end option ["--team-id ID"], "ID of a team", :covered_by => :team_name end def self.takes_domain(opts={}) if opts[:argument] argument :namespace, "Name of a domain", ["-n", "--namespace NAME"], :allow_nil => true, :default => :from_local_git else #:nocov: option ["-n", "--namespace NAME"], "Name of a domain", :default => :from_local_git #:nocov: end end def self.takes_membership_container(opts={}) if opts && opts[:argument] if opts && opts[:writable] #:nocov: argument :namespace, "Name of a domain", ["-n", "--namespace NAME"], :allow_nil => true, :default => :from_local_git #:nocov: else argument :target, "The name of a domain, or an application name with domain (domain or domain/application)", ["--target NAME_OR_PATH"], :allow_nil => true, :covered_by => [:application_id, :namespace, :app] end end option ["-n", "--namespace NAME"], "Name of a domain" option ["-a", "--app NAME"], "Name of an application" unless opts && opts[:writable] option ["-t", "--team-name NAME"], "Name of a team" option ["--team-id ID"], "ID of a team" end def self.takes_application(opts={}) if opts[:argument] argument :app, "Name of an application", ["-a", "--app NAME"], :allow_nil => true, :default => :from_local_git, :covered_by => :application_id else option ["-a", "--app NAME"], "Name of an application", :default => :from_local_git, :covered_by => :application_id end option ["-n", "--namespace NAME"], "Name of a domain", :default => :from_local_git option ["--application-id ID"], "ID of an application", :hide => true, :default => :from_local_git, :covered_by => :app end end end def find_team(opts={}) if id = options.team_id.presence return rest_client.find_team_by_id(id, opts) end team_name = (opts && opts[:team_name]) || options.team_name if team_name.present? rest_client.find_team(team_name, opts) else raise ArgumentError, "You must specify a team name with -t, or a team id with --team-id." end end def find_domain(opts={}) domain = options.namespace || options.target || namespace_context if domain rest_client.find_domain(domain) else raise ArgumentError, "You must specify a domain with -n." end end def find_membership_container(opts={}) domain, app = discover_domain_and_app if options.team_id.present? rest_client.find_team_by_id(options.team_id) elsif options.team_name.present? rest_client.find_team(options.team_name) elsif app && domain rest_client.find_application(domain, app) elsif domain rest_client.find_domain(domain) elsif opts && opts[:writable] raise ArgumentError, "You must specify a domain with -n, or a team with -t." else raise ArgumentError, "You must specify a domain with -n, an application with -a, or a team with -t." end end def find_app(opts={}) if id = options.application_id.presence if opts.delete(:with_gear_groups) return rest_client.find_application_by_id_gear_groups(id, opts) else return rest_client.find_application_by_id(id, opts) end end option = (opts && opts[:app]) || options.app domain, app = if option if option =~ /\// option.split(/\//) else [options.namespace || namespace_context, option] end end if app.present? && domain.present? if opts.delete(:with_gear_groups) rest_client.find_application_gear_groups(domain, app, opts) else rest_client.find_application(domain, app, opts) end else raise ArgumentError, "You must specify an application with -a, or run this command from within Git directory cloned from OpenShift." end end def server_context(defaults=nil, arg=nil) value = libra_server_env || (!options.clean && config['libra_server']) || openshift_online_server defaults[arg] = value if defaults && arg value end def from_local_git(defaults, arg) @local_git_config ||= { :application_id => git_config_get('rhc.app-id').presence, :app => git_config_get('rhc.app-name').presence, :namespace => git_config_get('rhc.domain-name').presence, } defaults[arg] ||= @local_git_config[arg] unless @local_git_config[arg].nil? @local_git_config end def namespace_context # right now we don't have any logic since we only support one domain # TODO: add domain lookup based on uuid domain = rest_client.domains.first raise RHC::NoDomainsForUser if domain.nil? domain.name end def discover_domain_and_app if options.target.present? options.target.split(/\//) elsif options.namespace || options.app if options.app =~ /\// options.app.split(/\//) else [options.namespace || namespace_context, options.app] end end end end end rhc-1.38.7/COPYRIGHT0000644000004100000410000000006412756270552013724 0ustar www-datawww-dataCopyright 2012 Red Hat, Inc. and/or its affiliates. rhc-1.38.7/rhc.gemspec0000644000004100000410000002342412756270552014557 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = "rhc" s.version = "1.38.7" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Red Hat"] s.date = "2016-08-01" s.description = "The client tools for the OpenShift platform that allow for application management." s.email = "dev@lists.openshift.redhat.com" s.executables = ["rhc"] s.files = ["COPYRIGHT", "LICENSE", "README.md", "Rakefile", "autocomplete/rhc_bash", "bin/rhc", "conf/express.conf", "features/app_feature.rb", "features/assets/deploy.tar.gz", "features/core_feature.rb", "features/deployments_feature.rb", "features/domains_feature.rb", "features/keys_feature.rb", "features/members_feature.rb", "features/server_feature.rb", "lib/rhc.rb", "lib/rhc/auth.rb", "lib/rhc/auth/basic.rb", "lib/rhc/auth/token.rb", "lib/rhc/auth/token_store.rb", "lib/rhc/auth/x509.rb", "lib/rhc/autocomplete.rb", "lib/rhc/autocomplete_templates/bash.erb", "lib/rhc/cartridge_helpers.rb", "lib/rhc/cli.rb", "lib/rhc/command_runner.rb", "lib/rhc/commands.rb", "lib/rhc/commands/account.rb", "lib/rhc/commands/alias.rb", "lib/rhc/commands/app.rb", "lib/rhc/commands/apps.rb", "lib/rhc/commands/authorization.rb", "lib/rhc/commands/base.rb", "lib/rhc/commands/cartridge.rb", "lib/rhc/commands/deployment.rb", "lib/rhc/commands/domain.rb", "lib/rhc/commands/env.rb", "lib/rhc/commands/git_clone.rb", "lib/rhc/commands/logout.rb", "lib/rhc/commands/member.rb", "lib/rhc/commands/port_forward.rb", "lib/rhc/commands/region.rb", "lib/rhc/commands/scp.rb", "lib/rhc/commands/server.rb", "lib/rhc/commands/setup.rb", "lib/rhc/commands/snapshot.rb", "lib/rhc/commands/ssh.rb", "lib/rhc/commands/sshkey.rb", "lib/rhc/commands/tail.rb", "lib/rhc/commands/team.rb", "lib/rhc/commands/threaddump.rb", "lib/rhc/config.rb", "lib/rhc/context_helper.rb", "lib/rhc/core_ext.rb", "lib/rhc/coverage_helper.rb", "lib/rhc/deployment_helpers.rb", "lib/rhc/exceptions.rb", "lib/rhc/git_helpers.rb", "lib/rhc/help_formatter.rb", "lib/rhc/helpers.rb", "lib/rhc/highline_extensions.rb", "lib/rhc/json.rb", "lib/rhc/output_helpers.rb", "lib/rhc/rest.rb", "lib/rhc/rest/activation.rb", "lib/rhc/rest/alias.rb", "lib/rhc/rest/api.rb", "lib/rhc/rest/application.rb", "lib/rhc/rest/attributes.rb", "lib/rhc/rest/authorization.rb", "lib/rhc/rest/base.rb", "lib/rhc/rest/cartridge.rb", "lib/rhc/rest/client.rb", "lib/rhc/rest/deployment.rb", "lib/rhc/rest/domain.rb", "lib/rhc/rest/environment_variable.rb", "lib/rhc/rest/gear_group.rb", "lib/rhc/rest/httpclient.rb", "lib/rhc/rest/key.rb", "lib/rhc/rest/membership.rb", "lib/rhc/rest/mock.rb", "lib/rhc/rest/region.rb", "lib/rhc/rest/team.rb", "lib/rhc/rest/user.rb", "lib/rhc/scp_helpers.rb", "lib/rhc/server_helpers.rb", "lib/rhc/servers.rb", "lib/rhc/ssh_helpers.rb", "lib/rhc/tar_gz.rb", "lib/rhc/usage_templates/command_help.erb", "lib/rhc/usage_templates/command_syntax_help.erb", "lib/rhc/usage_templates/help.erb", "lib/rhc/usage_templates/missing_help.erb", "lib/rhc/usage_templates/options_help.erb", "lib/rhc/vendor/okjson.rb", "lib/rhc/vendor/parseconfig.rb", "lib/rhc/vendor/sshkey.rb", "lib/rhc/vendor/zliby.rb", "lib/rhc/version.rb", "lib/rhc/wizard.rb", "spec/coverage_helper.rb", "spec/direct_execution_helper.rb", "spec/keys/example.pem", "spec/keys/example_private.pem", "spec/keys/server.pem", "spec/rest_spec_helper.rb", "spec/rhc/assets/cert.crt", "spec/rhc/assets/cert_key_rsa", "spec/rhc/assets/empty.txt", "spec/rhc/assets/env_vars.txt", "spec/rhc/assets/env_vars_2.txt", "spec/rhc/assets/foo.txt", "spec/rhc/assets/targz_corrupted.tar.gz", "spec/rhc/assets/targz_sample.tar.gz", "spec/rhc/auth_spec.rb", "spec/rhc/cli_spec.rb", "spec/rhc/command_spec.rb", "spec/rhc/commands/account_spec.rb", "spec/rhc/commands/alias_spec.rb", "spec/rhc/commands/app_spec.rb", "spec/rhc/commands/apps_spec.rb", "spec/rhc/commands/authorization_spec.rb", "spec/rhc/commands/cartridge_spec.rb", "spec/rhc/commands/deployment_spec.rb", "spec/rhc/commands/domain_spec.rb", "spec/rhc/commands/env_spec.rb", "spec/rhc/commands/git_clone_spec.rb", "spec/rhc/commands/logout_spec.rb", "spec/rhc/commands/member_spec.rb", "spec/rhc/commands/port_forward_spec.rb", "spec/rhc/commands/region_spec.rb", "spec/rhc/commands/scp_spec.rb", "spec/rhc/commands/server_spec.rb", "spec/rhc/commands/setup_spec.rb", "spec/rhc/commands/snapshot_spec.rb", "spec/rhc/commands/ssh_spec.rb", "spec/rhc/commands/sshkey_spec.rb", "spec/rhc/commands/tail_spec.rb", "spec/rhc/commands/team_spec.rb", "spec/rhc/commands/threaddump_spec.rb", "spec/rhc/config_spec.rb", "spec/rhc/helpers_spec.rb", "spec/rhc/highline_extensions_spec.rb", "spec/rhc/json_spec.rb", "spec/rhc/rest_application_spec.rb", "spec/rhc/rest_client_spec.rb", "spec/rhc/rest_spec.rb", "spec/rhc/servers_spec.rb", "spec/rhc/targz_spec.rb", "spec/rhc/wizard_spec.rb", "spec/spec_helper.rb", "spec/wizard_spec_helper.rb"] s.homepage = "https://github.com/openshift/rhc" s.post_install_message = "===========================================================================\n\nIf this is your first time installing the RHC tools, please run 'rhc setup'\n\n===========================================================================" s.require_paths = ["lib"] s.rubygems_version = "1.8.23" s.summary = "OpenShift Client Tools" s.test_files = ["features/app_feature.rb", "features/assets/deploy.tar.gz", "features/core_feature.rb", "features/deployments_feature.rb", "features/domains_feature.rb", "features/keys_feature.rb", "features/members_feature.rb", "features/server_feature.rb", "spec/coverage_helper.rb", "spec/direct_execution_helper.rb", "spec/keys/example.pem", "spec/keys/example_private.pem", "spec/keys/server.pem", "spec/rest_spec_helper.rb", "spec/rhc/assets/cert.crt", "spec/rhc/assets/cert_key_rsa", "spec/rhc/assets/empty.txt", "spec/rhc/assets/env_vars.txt", "spec/rhc/assets/env_vars_2.txt", "spec/rhc/assets/foo.txt", "spec/rhc/assets/targz_corrupted.tar.gz", "spec/rhc/assets/targz_sample.tar.gz", "spec/rhc/auth_spec.rb", "spec/rhc/cli_spec.rb", "spec/rhc/command_spec.rb", "spec/rhc/commands/account_spec.rb", "spec/rhc/commands/alias_spec.rb", "spec/rhc/commands/app_spec.rb", "spec/rhc/commands/apps_spec.rb", "spec/rhc/commands/authorization_spec.rb", "spec/rhc/commands/cartridge_spec.rb", "spec/rhc/commands/deployment_spec.rb", "spec/rhc/commands/domain_spec.rb", "spec/rhc/commands/env_spec.rb", "spec/rhc/commands/git_clone_spec.rb", "spec/rhc/commands/logout_spec.rb", "spec/rhc/commands/member_spec.rb", "spec/rhc/commands/port_forward_spec.rb", "spec/rhc/commands/region_spec.rb", "spec/rhc/commands/scp_spec.rb", "spec/rhc/commands/server_spec.rb", "spec/rhc/commands/setup_spec.rb", "spec/rhc/commands/snapshot_spec.rb", "spec/rhc/commands/ssh_spec.rb", "spec/rhc/commands/sshkey_spec.rb", "spec/rhc/commands/tail_spec.rb", "spec/rhc/commands/team_spec.rb", "spec/rhc/commands/threaddump_spec.rb", "spec/rhc/config_spec.rb", "spec/rhc/helpers_spec.rb", "spec/rhc/highline_extensions_spec.rb", "spec/rhc/json_spec.rb", "spec/rhc/rest_application_spec.rb", "spec/rhc/rest_client_spec.rb", "spec/rhc/rest_spec.rb", "spec/rhc/servers_spec.rb", "spec/rhc/targz_spec.rb", "spec/rhc/wizard_spec.rb", "spec/spec_helper.rb", "spec/wizard_spec_helper.rb"] if s.respond_to? :specification_version then s.specification_version = 3 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q, ["~> 3.0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_runtime_dependency(%q, ["< 4.3.0", ">= 4.0"]) s.add_development_dependency(%q, ["<= 1.3.20"]) s.add_development_dependency(%q, ["< 0.6.0", ">= 0.4"]) s.add_runtime_dependency(%q, ["~> 1.6.11"]) s.add_runtime_dependency(%q, ["< 2.7.0", ">= 2.4.0"]) s.add_runtime_dependency(%q, [">= 1.1.2"]) s.add_runtime_dependency(%q, [">= 1.2.0"]) s.add_runtime_dependency(%q, [">= 0"]) s.add_development_dependency(%q, [">= 0.8.7"]) s.add_development_dependency(%q, ["< 2.99", ">= 2.8.0"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, ["< 1.12"]) else s.add_dependency(%q, ["~> 3.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["< 4.3.0", ">= 4.0"]) s.add_dependency(%q, ["<= 1.3.20"]) s.add_dependency(%q, ["< 0.6.0", ">= 0.4"]) s.add_dependency(%q, ["~> 1.6.11"]) s.add_dependency(%q, ["< 2.7.0", ">= 2.4.0"]) s.add_dependency(%q, [">= 1.1.2"]) s.add_dependency(%q, [">= 1.2.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0.8.7"]) s.add_dependency(%q, ["< 2.99", ">= 2.8.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["< 1.12"]) end else s.add_dependency(%q, ["~> 3.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["< 4.3.0", ">= 4.0"]) s.add_dependency(%q, ["<= 1.3.20"]) s.add_dependency(%q, ["< 0.6.0", ">= 0.4"]) s.add_dependency(%q, ["~> 1.6.11"]) s.add_dependency(%q, ["< 2.7.0", ">= 2.4.0"]) s.add_dependency(%q, [">= 1.1.2"]) s.add_dependency(%q, [">= 1.2.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, [">= 0.8.7"]) s.add_dependency(%q, ["< 2.99", ">= 2.8.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["< 1.12"]) end end rhc-1.38.7/LICENSE0000644000004100000410000000101312756270552013431 0ustar www-datawww-dataLicensed 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. rhc-1.38.7/README.md0000644000004100000410000000760312756270552013716 0ustar www-datawww-data# OpenShift Command Line Tools (RHC) [![Build Status](https://secure.travis-ci.org/openshift/rhc.png)](http://travis-ci.org/openshift/rhc) The OpenShift command line tools allow you to manage your OpenShift applications from the command line. The [Getting Started guide](https://openshift.redhat.com/app/getting_started) has additional info on installing the tool on each supported operating system. Please stop by #openshift on irc.freenode.net if you have any questions or comments. For more information about OpenShift, visit https://openshift.redhat.com or the OpenShift support page https://openshift.redhat.com/support. RHC from rubygems.org is built on Ruby 1.8.7. RHC does have conditional dependencies during build time, so it can be built on multiple versions of Ruby. If you are running into any dependency issues when using RHC, please try to build RHC locally using `gem build rhc.gemspec` to use the correct dependencies. ## Using RHC to create an application DEPENDENCIES: * git * openssh-clients * ruby (1.8.7 or later) * rubygems Step 1: Run the setup command to configure your system: $ rhc setup Follow the instructions in setup to set your SSH keys and create a domain. The name you choose for your domain will form part of your application's public URL. Step 2: Create an OpenShift application: $ rhc app create -a appname -r /path/to/new/git/repo -t Once that's complete, follow the directions printed at the end of running rhc app create. ## Making changes to your application Once your site is created, updating it is as simple as making changes to your git repo. Commit them, then push. For example: $ edit index.php $ git commit -a -m "what I did" $ git push Then just reload your web page to see the changes. ## OS X Notes: git: OS X 10.6 comes w/ ssh and ruby, but not with git, unless you have Xcode 4.0.x installed (as a developer you should have Xcode anyway). Xcode, however, is not free (unless you are a registered Apple Developer) and costs around $5 from the Apple App Store. If you do not have Xcode, you can obtain a pre-packaged version of git from: http://code.google.com/p/git-osx-installer/ Installing git from MacPorts/HomeBrew/Fink/etc requires Xcode. Now obtain the client code, either via 'git clone' as above or via the rhc gem. ## Developing / Contributing We expect code contributions to follow these standards: 1. Ensure code matches the [GitHub Ruby styleguide](https://github.com/styleguide/ruby), except where the file establishes a different standard. 2. We use RSpec for functional testing and Cucumber for our high level integration tests. Specs are in 'spec/' and can be run with bundle exec rake spec. Features are in 'features/' and can be run with bundle exec rake features (although these tests runs against the gem installed locally so you will need to gem install first). See [README.md](https://github.com/openshift/rhc/blob/master/features/README.md) in the features dir for more info. 3. We maintain 100% line coverage of all newly added code via spec testing. The build will fail if new code is added and it does not have full line coverage. Some old code is currently excluded until it can be refactored. Run bundle exec rake spec on Ruby 1.9+ to see your code coverage level. 4. When writting a new Command please follow [ADDING_COMMANDS.md](https://github.com/openshift/rhc/blob/master/lib/rhc/commands/ADDING_COMMANDS.md) Once you've made your changes: 1. [Fork](http://help.github.com/forking/) the code 2. Create a topic branch - `git checkout -b my_branch` 3. Push to your branch - `git push origin my_branch` 4. Create a [Pull Request](http://help.github.com/pull-requests/) from your branch 5. That's it! If you use vim, we've included a .vimrc in the root of this project. In order to use it, install https://github.com/MarcWeber/vim-addon-local-vimrc