Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

import export and clone #17

Merged
merged 6 commits into from Sep 21, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
82 changes: 81 additions & 1 deletion lib/cli/commands/apps.rb
Expand Up @@ -208,6 +208,14 @@ def files(appname, path='/')
rescue VMC::Client::NotFound, VMC::Client::TargetError
err 'No such file or directory'
end

def download(appname, path=nil)
path = File.expand_path(path || "#{appname}.zip" )
banner = "Downloading last pushed source code to #{path}: "
display banner, false
client.app_download(appname, path)
display 'OK'.green
end

def pull(appname, path=nil)
path = File.expand_path(path || appname)
Expand All @@ -217,6 +225,78 @@ def pull(appname, path=nil)
display 'OK'.green
end

def clone(src_appname, dest_appname, dest_infra=nil)

# FIXME need to ask for dest_appname if nil

err "Application '#{dest_appname}' already exists" if app_exists?(dest_appname)

app = client.app_info(src_appname)

if client.infra_supported?
dest_infra = @options[:infra] || client.infra_name_for_description(
ask("Select Infrastructure",:indexed => true, :choices => client.infra_descriptions))
client.infra = dest_infra
end

url_template = "#{dest_appname}.${target-base}"
url_resolved = url_template.dup
resolve_lexically(url_resolved)

url = @options[:url] || ask("Application Deployed URL", :default => url_resolved)

Dir.mktmpdir do |dir|
zip_path = File.join(dir,src_appname)
pull(src_appname,zip_path)

display "Cloning '#{src_appname}' to '#{dest_appname}': "

manifest = {
:name => "#{dest_appname}",
:staging => app[:staging],
:uris => [ url ],
:instances => app[:instances],
:resources => app[:resources]
}
manifest[:staging][:command] = app[:staging][:command] if app[:staging][:command]
manifest[:infra] = { :provider => dest_infra } if dest_infra

client.create_app(dest_appname, manifest)

# Stage and upload the app bits.
upload_app_bits(dest_appname, zip_path, dest_infra)

# Clone services
client.services.select { |s| app[:services].include?(s[:name])}.each do |service|
display "Exporting data from #{service[:name]}: ", false
export_info = client.export_service(service[:name])
if export_info
display 'OK'.green
else
err "Export data from '#{service}': failed"
end
cloned_service_name = generate_cloned_service_name(src_appname,dest_appname,service[:name],dest_infra)
display "Creating service #{cloned_service_name}: ", false
client.create_service(dest_infra, service[:vendor], cloned_service_name)
display 'OK'.green
display "Binding service #{cloned_service_name}: ", false
client.bind_service(cloned_service_name, dest_appname)
display 'OK'.green
display "Importing data to #{cloned_service_name}: ", false
import_info = client.import_service(cloned_service_name,export_info[:uri])
if import_info
display 'OK'.green
else
err "Import data into '#{service}' failed"
end
end

no_start = @options[:nostart]
start(dest_appname, true) unless no_start

end
end

def logs(appname)
# Check if we have an app before progressing further
client.app_info(appname)
Expand Down Expand Up @@ -946,7 +1026,7 @@ def do_push(appname=nil)
runtime = info(:runtime)
infra = info(:infra)

if infra
if client.infra_supported? && infra
err "Infra '#{infra}' is not valid" unless client.infra_valid?(infra)
end

Expand Down
30 changes: 25 additions & 5 deletions lib/cli/commands/services.rb
Expand Up @@ -93,7 +93,28 @@ def clone_services(src_app, dest_app)
services.each { |service| bind_service_banner(service, dest_app, false) }
check_app_for_restart(dest_app)
end


def export_service(service)
display "Exporting data from '#{service}': ", false
export_info = client.export_service(service)
if export_info
display 'OK'.green
puts export_info[:uri]
else
err "Export data from '#{service}': failed"
end
end

def import_service(service,url)
display "Importing data into '#{service}': ", false
import_info = client.import_service(service,url)
if import_info
display 'OK'.green
else
err "Import data into '#{service}' failed"
end
end

def tunnel(service=nil, client_name=nil)
unless defined? Caldecott
display "To use `af tunnel', you must first install Caldecott:"
Expand Down Expand Up @@ -128,12 +149,11 @@ def tunnel(service=nil, client_name=nil)

raise VMC::Client::AuthError unless client.logged_in?

infra_name = info[:infra] ? info[:infra][:name] : default_infra

if infra_name
infra_name = nil
if client.infra_supported?
infra_name = info[:infra] ? info[:infra][:name] : default_infra
err "Infra '#{infra_name}' is not valid" unless client.infra_valid?(infra_name)
end


if not tunnel_pushed?(infra_name)
display "Deploying tunnel application '#{tunnel_appname(infra_name)}'."
Expand Down
21 changes: 21 additions & 0 deletions lib/cli/runner.rb
Expand Up @@ -286,6 +286,14 @@ def parse_command!
set_cmd(:apps, :files, 2)
end

when 'download'
usage('af download <appname> [path]')
if @args.size == 1
set_cmd(:apps, :download, 1)
else
set_cmd(:apps, :download, 2)
end

when 'pull'
usage('af pull <appname> [path]')
if @args.size == 1
Expand Down Expand Up @@ -374,6 +382,19 @@ def parse_command!
usage('af clone-services <src-app> <dest-app>')
set_cmd(:services, :clone_services, 2)

when 'export-service'
usage('af export-service <service-name>')
set_cmd(:services, :export_service, 1)

when 'import-service'
usage('af import-service <service-name> <url>')
set_cmd(:services, :import_service, 2)

when 'clone'
usage('af clone <src-app> <dest-app> [<infra>]')
set_cmd(:apps, :clone, 2) if @args.size == 2
set_cmd(:apps, :clone, 3) if @args.size == 3

when 'aliases'
usage('af aliases')
set_cmd(:misc, :aliases)
Expand Down
13 changes: 13 additions & 0 deletions lib/cli/services_helper.rb
Expand Up @@ -80,6 +80,19 @@ def random_service_name(service)
r = "%04x" % [rand(0x0100000)]
"#{service.to_s}-#{r}"
end

def generate_cloned_service_name(src_appname,dest_appname,src_servicename,dest_infra)
r = "%04x" % [rand(0x0100000)]
dest_servicename = src_servicename.sub(src_appname,dest_appname).sub(/-\h{4,5}/,"-#{r}")
if src_servicename == dest_servicename
if dest_infra
dest_servicename = "#{dest_servicename}-#{dest_infra}"
else
dest_servicename = "#{dest_servicename}-#{r}"
end
end
dest_servicename
end

def check_app_for_restart(appname)
app = client.app_info(appname)
Expand Down
5 changes: 4 additions & 1 deletion lib/cli/tunnel_helper.rb
Expand Up @@ -101,7 +101,10 @@ def tunnel_connection_info(type, service, token, infra)
begin
response = RestClient.get(tunnel_url(infra) + "/" + VMC::Client.path("services", service), "Auth-Token" => token)
break
rescue RestClient::Exception
rescue RestClient::Exception => e
puts "Error infra: #{infra}, url: #{tunnel_url(infra)}"
display tunnel_url(infra)
puts e.message.red
sleep 1
end

Expand Down
6 changes: 5 additions & 1 deletion lib/cli/usage.rb
Expand Up @@ -48,6 +48,7 @@ def command_usage
stop <appname> Stop the application
restart <appname> [--debug [MODE]] Restart the application
delete <appname> Delete the application
clone <src-app> <dest-app> [infra] Clone the application and services

Application Updates
update <appname> [--path,--debug [MODE]] Update the application bits
Expand All @@ -66,6 +67,7 @@ def command_usage

Application Download
pull <appname> [path] Downloads last pushed source to <appname> or [path]
download <appname> [path] Downloads last pushed source to zipfile

Application Environment
env <appname> List application environment variables
Expand All @@ -75,13 +77,15 @@ def command_usage
Services
services Lists of services available and provisioned
create-service <service> [--name,--bind] Create a provisioned service
create-service <service> --infra Create a provisioned service on a specified infrastructure
create-service <service> --infra Create a provisioned service on a specified infrastructure
create-service <service> <name> Create a provisioned service and assign it <name>
create-service <service> <name> <app> Create a provisioned service and assign it <name>, and bind to <app>
delete-service [servicename] Delete a provisioned service
bind-service <servicename> <appname> Bind a service to an application
unbind-service <servicename> <appname> Unbind service from the application
clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>
export-service <service> Export the data from a service
import-service <service> <url> Import data into a service
tunnel <servicename> [--port] Create a local tunnel to a service
tunnel <servicename> <clientcmd> Create a local tunnel to a service and start a local client

Expand Down
2 changes: 1 addition & 1 deletion lib/cli/version.rb
Expand Up @@ -2,6 +2,6 @@ module VMC
module Cli
# This version number is used as the RubyGem release version.
# The internal VMC version number is VMC::VERSION.
VERSION = '0.3.18.4'
VERSION = '0.3.18.5'
end
end
25 changes: 24 additions & 1 deletion lib/vmc/client.rb
Expand Up @@ -159,11 +159,21 @@ def app_files(name, path, instance='0')
body
end

def app_download(name,path)
check_login_status
url = path(VMC::APPS_PATH, name, "application")
status, body, headers = http_get(url,'application/octet-stream')
file = File.new(path,"wb")
file.write(body)
file.close
end

def app_pull(name, dir)
check_login_status
url = path(VMC::APPS_PATH, name, "application")
status, body, headers = http_get(url,'application/octet-stream')
file = Tempfile.new(name)
file.binmode
file.write(body)
file.close
::VMC::Cli::ZipUtil.unpack(file.path, dir)
Expand Down Expand Up @@ -237,6 +247,14 @@ def unbind_service(service, appname)
app[:services] = services
update_app(appname, app)
end

def export_service(service)
json_get(path(VMC::SERVICE_EXPORT_PATH, service))
end

def import_service(service,uri)
json_post(path(VMC::SERVICE_IMPORT_PATH, service),{uri:uri})
end

######################################################
# Resources
Expand Down Expand Up @@ -360,9 +378,14 @@ def infra_supported?

def base_for_infra(name)
info = infras.detect { |i| i[:infra] == name }
info ? info[:base] : "aws.af.cm"
info ? info[:base] : default_base
end

def default_base
return "vcap.me" if @target =~ /https?:\/\/api.vcap.me/
"aws.af.cm"
end

def infra_valid?(name)
infras.detect { |i| i[:infra] == name }
end
Expand Down
4 changes: 4 additions & 0 deletions lib/vmc/const.rb
Expand Up @@ -19,5 +19,9 @@ module VMC
APPS_PATH = 'apps'
SERVICES_PATH = 'services'
USERS_PATH = 'users'

# Service paths
SERVICE_EXPORT_PATH = ['services','export']
SERVICE_IMPORT_PATH = ['services','import']

end
9 changes: 9 additions & 0 deletions spec/assets/app_not_found.txt
@@ -0,0 +1,9 @@
HTTP/1.1 404 Not Found
Server: nginx/0.7.65
Date: Fri, 04 Mar 2011 02:56:21 GMT
Content-Type: application/json
Connection: keep-alive
Keep-Alive: timeout=20
Content-Length: 9

Not Found
44 changes: 44 additions & 0 deletions spec/unit/command_apps_spec.rb
Expand Up @@ -244,4 +244,48 @@

end

it 'should clone an app' do

@client = VMC::Client.new(@local_target, @auth_token)

login_path = "#{@local_target}/users/#{@user}/tokens"
stub_request(:post, login_path).to_return(File.new(spec_asset('login_success.txt')))
info_path = "#{@local_target}/#{VMC::INFO_PATH}"
stub_request(:get, info_path).to_return(File.new(spec_asset('info_authenticated.txt')))

options = {
:url => 'bar.vcap.me',
:nostart => true
}
command = VMC::Cli::Command::Apps.new(options)
command.client(@client)

services_path = "#{@local_target}/#{VMC::SERVICES_PATH}"
stub_request(:get,services_path).to_return(:body=>"[]")

apps_path = "#{@local_target}/#{VMC::APPS_PATH}"
stub_request(:post, apps_path)

src_app_path = "#{@local_target}/#{VMC::APPS_PATH}/foo"
stub_request(:get, src_app_path).to_return(File.new(spec_asset('standalone_app_info.txt')))
app_download_path = "#{@local_target}/#{VMC::APPS_PATH}/foo/application"
stub_request(:get, app_download_path).to_return(:status => 200,
:body => File.new(spec_asset('tests/standalone/java_app/target/zip/standalone-java-app-1.0.0.BUILD-SNAPSHOT-jar.zip')).binmode)

dest_app_path = "#{@local_target}/#{VMC::APPS_PATH}/bar"
stub_request(:get, dest_app_path).to_return(File.new(spec_asset('app_not_found.txt')))

app_upload_path = "#{@local_target}/#{VMC::APPS_PATH}/bar/application"
stub_request(:post, app_upload_path)

command.clone('foo','bar','rs')
a_request(:post, apps_path).should have_been_made.once # to create the clone
a_request(:post, app_upload_path).should have_been_made.once # to upload the code

end

it 'should clone services for an app' do
pending
end

end