shenzhen_fir 0.14.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,119 @@
1
+ require 'json'
2
+ require 'openssl'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ module Shenzhen::Plugins
7
+ module HockeyApp
8
+ class Client
9
+ HOSTNAME = 'upload.hockeyapp.net'
10
+
11
+ def initialize(api_token)
12
+ @api_token = api_token
13
+ @connection = Faraday.new(:url => "https://#{HOSTNAME}") do |builder|
14
+ builder.request :multipart
15
+ builder.request :url_encoded
16
+ builder.response :json, :content_type => /\bjson$/
17
+ builder.use FaradayMiddleware::FollowRedirects
18
+ builder.adapter :net_http
19
+ end
20
+ end
21
+
22
+ def upload_build(ipa, options)
23
+ options[:ipa] = Faraday::UploadIO.new(ipa, 'application/octet-stream') if ipa and File.exist?(ipa)
24
+
25
+ if dsym_filename = options.delete(:dsym_filename)
26
+ options[:dsym] = Faraday::UploadIO.new(dsym_filename, 'application/octet-stream')
27
+ end
28
+
29
+ @connection.post do |req|
30
+ if options[:public_identifier].nil?
31
+ req.url("/api/2/apps/upload")
32
+ else
33
+ req.url("/api/2/apps/#{options.delete(:public_identifier)}/app_versions/upload")
34
+ end
35
+ req.headers['X-HockeyAppToken'] = @api_token
36
+ req.body = options
37
+ end.on_complete do |env|
38
+ yield env[:status], env[:body] if block_given?
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ command :'distribute:hockeyapp' do |c|
46
+ c.syntax = "ipa distribute:hockeyapp [options]"
47
+ c.summary = "Distribute an .ipa file over HockeyApp"
48
+ c.description = ""
49
+ c.option '-f', '--file FILE', ".ipa file for the build"
50
+ c.option '-d', '--dsym FILE', "zipped .dsym package for the build"
51
+ c.option '-a', '--token TOKEN', "API Token. Available at https://rink.hockeyapp.net/manage/auth_tokens"
52
+ c.option '-i', '--identifier PUBLIC_IDENTIFIER', "Public identifier of the app you are targeting, if not specified HockeyApp will use the bundle identifier to choose the right"
53
+ c.option '-m', '--notes NOTES', "Release notes for the build (Default: Textile)"
54
+ c.option '-r', '--release RELEASE', [:beta, :store, :alpha, :enterprise], "Release type: 0 - Beta, 1 - Store, 2 - Alpha , 3 - Enterprise"
55
+ c.option '--markdown', 'Notes are written with Markdown'
56
+ c.option '--tags TAGS', "Comma separated list of tags which will receive access to the build"
57
+ c.option '--teams TEAMS', "Comma separated list of team ID numbers to which this build will be restricted"
58
+ c.option '--users USERS', "Comma separated list of user ID numbers to which this build will be restricted"
59
+ c.option '--notify', "Notify permitted teammates to install the build"
60
+ c.option '--downloadOff', "Upload but don't allow download of this version just yet"
61
+ c.option '--mandatory', "Make this update mandatory"
62
+ c.option '--commit-sha SHA', "The Git commit SHA for this build"
63
+ c.option '--build-server-url URL', "The URL of the build job on your build server"
64
+ c.option '--repository-url URL', "The URL of your source repository"
65
+
66
+ c.action do |args, options|
67
+ determine_file! unless @file = options.file
68
+ say_warning "Missing or unspecified .ipa file" unless @file and File.exist?(@file)
69
+
70
+ determine_dsym! unless @dsym = options.dsym
71
+ say_warning "Specified dSYM.zip file doesn't exist" if @dsym and !File.exist?(@dsym)
72
+
73
+ determine_hockeyapp_api_token! unless @api_token = options.token || ENV['HOCKEYAPP_API_TOKEN']
74
+ say_error "Missing API Token" and abort unless @api_token
75
+
76
+ determine_notes! unless @notes = options.notes
77
+ say_error "Missing release notes" and abort unless @notes
78
+
79
+ parameters = {}
80
+ parameters[:public_identifier] = options.identifier if options.identifier
81
+ parameters[:notes] = @notes
82
+ parameters[:notes_type] = options.markdown ? "1" : "0"
83
+ parameters[:notify] = "1" if options.notify && !options.downloadOff
84
+ parameters[:status] = options.downloadOff ? "1" : "2"
85
+ parameters[:tags] = options.tags if options.tags
86
+ parameters[:teams] = options.teams if options.teams
87
+ parameters[:users] = options.users if options.users
88
+ parameters[:dsym_filename] = @dsym if @dsym
89
+ parameters[:mandatory] = "1" if options.mandatory
90
+ parameters[:release_type] = case options.release
91
+ when :beta
92
+ "0"
93
+ when :store
94
+ "1"
95
+ when :alpha
96
+ "2"
97
+ when :enterprise
98
+ "3"
99
+ end
100
+ parameters[:commit_sha] = options.commit_sha if options.commit_sha
101
+ parameters[:build_server_url] = options.build_server_url if options.build_server_url
102
+ parameters[:repository_url] = options.repository_url if options.repository_url
103
+
104
+ client = Shenzhen::Plugins::HockeyApp::Client.new(@api_token)
105
+ response = client.upload_build(@file, parameters)
106
+ case response.status
107
+ when 200...300
108
+ say_ok "Build successfully uploaded to HockeyApp"
109
+ else
110
+ say_error "Error uploading to HockeyApp: #{response.body}"
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def determine_hockeyapp_api_token!
117
+ @api_token ||= ask "API Token:"
118
+ end
119
+ end
@@ -0,0 +1,142 @@
1
+ require 'security'
2
+ require 'fileutils'
3
+ require 'digest/md5'
4
+ require 'shellwords'
5
+
6
+ module Shenzhen::Plugins
7
+ module ITunesConnect
8
+ ITUNES_CONNECT_SERVER = 'Xcode:itunesconnect.apple.com'
9
+
10
+ class Client
11
+ attr_reader :ipa, :sdk, :params
12
+
13
+ def initialize(ipa, apple_id, sdk, account, password, params = [])
14
+ @ipa = ipa
15
+ @apple_id = apple_id
16
+ @sdk = sdk
17
+ @account = account
18
+ @password = password
19
+ @params = params
20
+ @filename = File.basename(@ipa).tr(" ", "_")
21
+ end
22
+
23
+ def upload_build!
24
+ size = File.size(@ipa)
25
+ checksum = Digest::MD5.file(@ipa)
26
+
27
+ begin
28
+ FileUtils.mkdir_p("Package.itmsp")
29
+ FileUtils.copy_entry(@ipa, "Package.itmsp/#{@filename}")
30
+
31
+ File.write("Package.itmsp/metadata.xml", metadata(@apple_id, checksum, size))
32
+
33
+ raise if /(error)|(fail)/i === transport
34
+ rescue
35
+ say_error "An error occurred when trying to upload the build to iTunesConnect.\nRun with --verbose for more info." and abort
36
+ ensure
37
+ FileUtils.rm_rf("Package.itmsp", :secure => true)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def transport
44
+ xcode = `xcode-select --print-path`.strip
45
+ tool = File.join(File.dirname(xcode), "Applications/Application Loader.app/Contents/MacOS/itms/bin/iTMSTransporter").gsub(/\s/, '\ ')
46
+ tool = File.join(File.dirname(xcode), "Applications/Application Loader.app/Contents/itms/bin/iTMSTransporter").gsub(/\s/, '\ ') if !File.exist?(tool)
47
+
48
+ escaped_password = Shellwords.escape(@password)
49
+ args = [tool, "-m upload", "-f Package.itmsp", "-u #{Shellwords.escape(@account)}", "-p #{escaped_password}"]
50
+ command = args.join(' ')
51
+
52
+ puts command.sub("-p #{escaped_password}", "-p ******") if $verbose
53
+
54
+ output = `#{command} 2> /dev/null`
55
+ puts output.chomp if $verbose
56
+
57
+ raise "Failed to upload package to iTunes Connect" unless $?.exitstatus == 0
58
+
59
+ output
60
+ end
61
+
62
+ def metadata(apple_id, checksum, size)
63
+ %{<?xml version="1.0" encoding="UTF-8"?>
64
+ <package version="software4.7" xmlns="http://apple.com/itunes/importer">
65
+ <software_assets apple_id="#{apple_id}">
66
+ <asset type="bundle">
67
+ <data_file>
68
+ <file_name>#{@filename}</file_name>
69
+ <checksum type="md5">#{checksum}</checksum>
70
+ <size>#{size}</size>
71
+ </data_file>
72
+ </asset>
73
+ </software_assets>
74
+ </package>
75
+ }
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ command :'distribute:itunesconnect' do |c|
82
+ c.syntax = "ipa distribute:itunesconnect [options]"
83
+ c.summary = "Upload an .ipa file to iTunes Connect"
84
+ c.description = "Upload an .ipa file directly to iTunes Connect for review. Requires that the app is in the 'Waiting for upload' state and the --upload flag to be set."
85
+ c.option '-f', '--file FILE', ".ipa file for the build"
86
+ c.option '-a', '--account ACCOUNT', "Apple ID used to log into https://itunesconnect.apple.com"
87
+ c.option '-p', '--password PASSWORD', "Password for the account unless already stored in the keychain"
88
+ c.option '-u', '--upload', "Actually attempt to upload the build to iTunes Connect"
89
+ c.option '-w', '--warnings', "Check for warnings when validating the ipa"
90
+ c.option '-e', '--errors', "Check for errors when validating the ipa"
91
+ c.option '-i', '--apple-id STRING', "Apple ID from iTunes Connect"
92
+ c.option '--sdk SDK', "SDK to use when validating the ipa. Defaults to 'iphoneos'"
93
+ c.option '--save-keychain', "Save the provided account in the keychain for future use"
94
+
95
+ c.action do |args, options|
96
+ options.default :upload => false, :sdk => 'iphoneos', :save_keychain => true
97
+
98
+ determine_file! unless @file = options.file
99
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
100
+
101
+ determine_itunes_connect_account! unless @account = options.account || ENV['ITUNES_CONNECT_ACCOUNT']
102
+ say_error "Missing iTunes Connect account" and abort unless @account
103
+
104
+ apple_id = options.apple_id
105
+ say_error "Missing Apple ID" and abort unless apple_id
106
+
107
+ @password = options.password || ENV['ITUNES_CONNECT_PASSWORD']
108
+ if @password.nil? && @password = Security::GenericPassword.find(:s => Shenzhen::Plugins::ITunesConnect::ITUNES_CONNECT_SERVER, :a => @account)
109
+ @password = @password.password
110
+ say_ok "Found password in keychain for account: #{@account}" if options.verbose
111
+ else
112
+ determine_itunes_connect_password! unless @password
113
+ say_error "Missing iTunes Connect password" and abort unless @password
114
+
115
+ Security::GenericPassword.add(Shenzhen::Plugins::ITunesConnect::ITUNES_CONNECT_SERVER, @account, @password, {:U => nil}) if options.save_keychain
116
+ end
117
+
118
+ unless /^[0-9a-zA-Z]*$/ === @password
119
+ say_warning "Password contains special characters, which may not be handled properly by iTMSTransporter. If you experience problems uploading to iTunes Connect, please consider changing your password to something with only alphanumeric characters."
120
+ end
121
+
122
+ parameters = []
123
+ parameters << :warnings if options.warnings
124
+ parameters << :errors if options.errors
125
+
126
+ client = Shenzhen::Plugins::ITunesConnect::Client.new(@file, apple_id, options.sdk, @account, @password, parameters)
127
+
128
+ client.upload_build!
129
+ say_ok "Upload complete."
130
+ say_warning "You may want to double check iTunes Connect to make sure it was received correctly."
131
+ end
132
+
133
+ private
134
+
135
+ def determine_itunes_connect_account!
136
+ @account ||= ask "iTunes Connect account:"
137
+ end
138
+
139
+ def determine_itunes_connect_password!
140
+ @password ||= password "iTunes Connect password:"
141
+ end
142
+ end
@@ -0,0 +1,135 @@
1
+ require 'json'
2
+ require 'openssl'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ module Shenzhen::Plugins
7
+ module Pgyer
8
+ class Client
9
+ HOSTNAME = 'www.pgyer.com'
10
+
11
+ def initialize(user_key, api_key)
12
+ @user_key, @api_key = user_key, api_key
13
+ @connection = Faraday.new(:url => "http://#{HOSTNAME}", :request => { :timeout => 120 }) do |builder|
14
+ builder.request :multipart
15
+ builder.request :json
16
+ builder.response :json, :content_type => /\bjson$/
17
+ builder.use FaradayMiddleware::FollowRedirects
18
+ builder.adapter :net_http
19
+ end
20
+ end
21
+
22
+ def upload_build(ipa, options)
23
+ options.update({
24
+ :uKey => @user_key,
25
+ :_api_key => @api_key,
26
+ :file => Faraday::UploadIO.new(ipa, 'application/octet-stream')
27
+ })
28
+
29
+ @connection.post("/apiv1/app/upload", options).on_complete do |env|
30
+ yield env[:status], env[:body] if block_given?
31
+ end
32
+
33
+ rescue Faraday::Error::TimeoutError
34
+ say_error "Timed out while uploading build. Check http://www.pgyer.com/my to see if the upload was completed." and abort
35
+ end
36
+
37
+ def update_app_info(options)
38
+ connection = Faraday.new(:url => "http://#{HOSTNAME}", :request => { :timeout => 120 }) do |builder|
39
+ builder.request :url_encoded
40
+ builder.request :json
41
+ builder.response :logger
42
+ builder.response :json, :content_type => /\bjson$/
43
+ builder.use FaradayMiddleware::FollowRedirects
44
+ builder.adapter :net_http
45
+ end
46
+
47
+ options.update({
48
+ :uKey => @user_key,
49
+ :_api_key => @api_key,
50
+ })
51
+
52
+ connection.post("/apiv1/app/update", options) do |env|
53
+ yield env[:status], env[:body] if block_given?
54
+ end
55
+
56
+ rescue Faraday::Error::TimeoutError
57
+ say_error "Timed out while uploading build. Check http://www.pgyer.com/my to see if the upload was completed." and abort
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ command :'distribute:pgyer' do |c|
64
+ c.syntax = "ipa distribute:pgyer [options]"
65
+ c.summary = "Distribute an .ipa file over Pgyer"
66
+ c.description = ""
67
+ c.option '-f', '--file FILE', ".ipa file for the build"
68
+ c.option '-a', '--api_key KEY', "API KEY. Available at http://www.pgyer.com/doc/api#uploadApp"
69
+ c.option '-u', '--user_key KEY', "USER KEY. Available at http://www.pgyer.com/doc/api#uploadApp/"
70
+ c.option '--range RANGE', "Publish range. e.g. 1 (default), 2, 3"
71
+ c.option '--[no-]public', "Allow build app on public to download. it is not public default."
72
+ c.option '--password PASSWORD', "Set password to allow visit app web page."
73
+
74
+ c.action do |args, options|
75
+ determine_file! unless @file = options.file
76
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
77
+
78
+ determine_pgyer_user_key! unless @user_key = options.user_key || ENV['PGYER_USER_KEY']
79
+ say_error "Missing User Key" and abort unless @user_key
80
+
81
+ determine_pgyer_api_key! unless @api_key = options.api_key || ENV['PGYER_API_KEY']
82
+ say_error "Missing API Key" and abort unless @api_key
83
+
84
+ determine_publish_range! unless @publish_range = options.range
85
+ say_error "Missing Publish Range" and abort unless @publish_range
86
+
87
+ determine_is_public! unless @is_public = !!options.public
88
+ @is_public = @is_public ? 1 : 2
89
+
90
+ parameters = {}
91
+ parameters[:publishRange] = @publish_range
92
+ parameters[:isPublishToPublic] = @is_public
93
+ parameters[:password] = options.password.chomp if options.password
94
+
95
+ client = Shenzhen::Plugins::Pgyer::Client.new(@user_key, @api_key)
96
+ response = client.upload_build(@file, parameters)
97
+ case response.status
98
+ when 200...300
99
+ app_id = response.body['appKey']
100
+ app_short_uri = response.body['appShortcutUrl']
101
+
102
+ app_response = client.update_app_info({
103
+ :aKey => app_id,
104
+ :appUpdateDescription => @notes
105
+ })
106
+
107
+ if app_response.status == 200
108
+ say_ok "Build successfully uploaded to Pgyer, visit url: http://www.pgyer.com/#{app_short_uri}"
109
+ else
110
+ say_error "Error update build information: #{response.body}" and abort
111
+ end
112
+ else
113
+ say_error "Error uploading to Pgyer: #{response.body}" and abort
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def determine_pgyer_api_key!
120
+ @api_key ||= ask "API Key:"
121
+ end
122
+
123
+ def determine_pgyer_user_key!
124
+ @user_key ||= ask "User Key:"
125
+ end
126
+
127
+ def determine_publish_range!
128
+ @publish_range ||= "1"
129
+ end
130
+
131
+ def determine_is_public!
132
+ @is_public ||= false
133
+ end
134
+
135
+ end
@@ -0,0 +1,81 @@
1
+ require 'json'
2
+ require 'openssl'
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ module Shenzhen::Plugins
7
+ module RivieraBuild
8
+ class Client
9
+ HOSTNAME = 'apps.rivierabuild.com'
10
+
11
+ def initialize(api_token)
12
+ @api_token = api_token
13
+ @connection = Faraday.new(:url => "https://#{HOSTNAME}", :request => { :timeout => 120 }) do |builder|
14
+ builder.request :multipart
15
+ builder.request :url_encoded
16
+ builder.response :json, :content_type => /\bjson$/
17
+ builder.use FaradayMiddleware::FollowRedirects
18
+ builder.adapter :net_http
19
+ end
20
+ end
21
+
22
+ def upload_build(ipa, options)
23
+ options[:file] = Faraday::UploadIO.new(ipa, 'application/octet-stream') if ipa and File.exist?(ipa)
24
+
25
+ @connection.post do |req|
26
+ req.url("/api/upload")
27
+ req.body = options
28
+ end.on_complete do |env|
29
+ yield env[:status], env[:body] if block_given?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ command :'distribute:rivierabuild' do |c|
37
+ c.syntax = "ipa distribute:rivierabuild [options]"
38
+ c.summary = "Distribute an .ipa file over RivieraBuild"
39
+ c.description = ""
40
+ c.option '-f', '--file FILE', ".ipa file for the build"
41
+ c.option '-k', '--key KEY', "API KEY. Available at https://apps.rivierabuild.com/settings"
42
+ c.option '-a', '--availability AVAILABILITY', "For how long the build will be available? More info: http://api.rivierabuild.com"
43
+ c.option '-p', '--passcode PASSCODE', "Optional passcode required to install the build on a device"
44
+ c.option '-n', '--note NOTE', "Release notes for the build, Markdown"
45
+ c.option '--commit-sha SHA', "The Git commit SHA for this build"
46
+ c.option '--app-id', "Riviera Build Application ID"
47
+
48
+ c.action do |args, options|
49
+ determine_file! unless @file = options.file
50
+ say_warning "Missing or unspecified .ipa file" unless @file and File.exist?(@file)
51
+
52
+ determine_rivierabuild_api_token! unless @api_token = options.key || ENV['RIVIERA_API_KEY']
53
+ say_error "Missing API Token" and abort unless @api_token
54
+
55
+ determine_availability! unless @availability = options.availability
56
+ say_error "Missing availability" and abort unless @availability
57
+
58
+ parameters = {}
59
+ parameters[:api_key] = @api_token
60
+ parameters[:availability] = @availability
61
+ parameters[:passcode] = options.passcode if options.passcode
62
+ parameters[:app_id] = options.app_id if options.app_id
63
+ parameters[:note] = options.note if options.note
64
+ parameters[:commit_sha] = options.commit_sha if options.commit_sha
65
+
66
+ client = Shenzhen::Plugins::RivieraBuild::Client.new(@api_token)
67
+ response = client.upload_build(@file, parameters)
68
+ case response.status
69
+ when 200...300
70
+ say_ok "Build successfully uploaded to RivieraBuild: #{response.body['file_url']}"
71
+ else
72
+ say_error "Error uploading to RivieraBuild: #{response.body}"
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def determine_rivierabuild_api_token!
79
+ @api_token ||= ask "API Key:"
80
+ end
81
+ end
@@ -0,0 +1,139 @@
1
+ require 'aws-sdk'
2
+
3
+ module Shenzhen::Plugins
4
+ module S3
5
+ class Client
6
+ def initialize(access_key_id, secret_access_key, region)
7
+ @s3 = AWS::S3.new(:access_key_id => access_key_id,
8
+ :secret_access_key => secret_access_key,
9
+ :region => region)
10
+ end
11
+
12
+ def upload_build(ipa, options)
13
+ path = expand_path_with_substitutions_from_ipa_plist(ipa, options[:path]) if options[:path]
14
+
15
+ @s3.buckets.create(options[:bucket]) if options[:create]
16
+
17
+ bucket = @s3.buckets[options[:bucket]]
18
+
19
+ uploaded_urls = []
20
+
21
+ files = []
22
+ files << ipa
23
+ files << options[:dsym] if options[:dsym]
24
+ files.each do |file|
25
+ basename = File.basename(file)
26
+ key = path ? File.join(path, basename) : basename
27
+ File.open(file) do |descriptor|
28
+ obj = bucket.objects.create(key, descriptor, :acl => options[:acl])
29
+ uploaded_urls << obj.public_url.to_s
30
+ end
31
+ end
32
+
33
+ uploaded_urls
34
+ end
35
+
36
+ private
37
+
38
+ def expand_path_with_substitutions_from_ipa_plist(ipa, path)
39
+ substitutions = path.scan(/\{CFBundle[^}]+\}/)
40
+ return path if substitutions.empty?
41
+
42
+ Dir.mktmpdir do |dir|
43
+ system "unzip -q #{ipa} -d #{dir} 2> /dev/null"
44
+
45
+ plist = Dir["#{dir}/**/*.app/Info.plist"].last
46
+
47
+ substitutions.uniq.each do |substitution|
48
+ key = substitution[1...-1]
49
+ value = Shenzhen::PlistBuddy.print(plist, key)
50
+
51
+ path.gsub!(Regexp.new(substitution), value) if value
52
+ end
53
+ end
54
+
55
+ return path
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ command :'distribute:s3' do |c|
62
+ c.syntax = "ipa distribute:s3 [options]"
63
+ c.summary = "Distribute an .ipa file over Amazon S3"
64
+ c.description = ""
65
+
66
+ c.example '', '$ ipa distribute:s3 -f ./file.ipa -a accesskeyid --bucket bucket-name'
67
+
68
+ c.option '-f', '--file FILE', ".ipa file for the build"
69
+ c.option '-d', '--dsym FILE', "zipped .dsym package for the build"
70
+ c.option '-a', '--access-key-id ACCESS_KEY_ID', "AWS Access Key ID"
71
+ c.option '-s', '--secret-access-key SECRET_ACCESS_KEY', "AWS Secret Access Key"
72
+ c.option '-b', '--bucket BUCKET', "S3 bucket"
73
+ c.option '--[no-]create', "Create bucket if it doesn't already exist"
74
+ c.option '-r', '--region REGION', "Optional AWS region (for bucket creation)"
75
+ c.option '--acl ACL', "Uploaded object permissions e.g public_read (default), private, public_read_write, authenticated_read"
76
+ c.option '--source-dir SOURCE', "Optional source directory e.g. ./build"
77
+ c.option '-P', '--path PATH', "S3 'path'. Values from Info.plist will be substituded for keys wrapped in {} \n\t\t eg. \"/path/to/folder/{CFBundleVersion}/\" could be evaluated as \"/path/to/folder/1.0.0/\""
78
+
79
+ c.action do |args, options|
80
+ Dir.chdir(options.source_dir) if options.source_dir
81
+
82
+ determine_file! unless @file = options.file
83
+ say_error "Missing or unspecified .ipa file" and abort unless @file and File.exist?(@file)
84
+
85
+ determine_dsym! unless @dsym = options.dsym
86
+ say_error "Specified dSYM.zip file doesn't exist" if @dsym and !File.exist?(@dsym)
87
+
88
+ determine_access_key_id! unless @access_key_id = options.access_key_id
89
+ say_error "Missing AWS Access Key ID" and abort unless @access_key_id
90
+
91
+ determine_secret_access_key! unless @secret_access_key = options.secret_access_key
92
+ say_error "Missing AWS Secret Access Key" and abort unless @secret_access_key
93
+
94
+ determine_bucket! unless @bucket = options.bucket
95
+ say_error "Missing bucket" and abort unless @bucket
96
+
97
+ determine_region! unless @region = options.region
98
+
99
+ determine_acl! unless @acl = options.acl
100
+ say_error "Missing ACL" and abort unless @acl
101
+
102
+ @path = options.path
103
+
104
+ client = Shenzhen::Plugins::S3::Client.new(@access_key_id, @secret_access_key, @region)
105
+
106
+ begin
107
+ urls = client.upload_build @file, {:bucket => @bucket, :create => !!options.create, :acl => @acl, :dsym => @dsym, :path => @path}
108
+ urls.each { |url| say_ok url}
109
+ say_ok "Build successfully uploaded to S3"
110
+ rescue => exception
111
+ say_error "Error while uploading to S3: #{exception}"
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def determine_access_key_id!
118
+ @access_key_id ||= ENV['AWS_ACCESS_KEY_ID']
119
+ @access_key_id ||= ask "Access Key ID:"
120
+ end
121
+
122
+ def determine_secret_access_key!
123
+ @secret_access_key ||= ENV['AWS_SECRET_ACCESS_KEY']
124
+ @secret_access_key ||= ask "Secret Access Key:"
125
+ end
126
+
127
+ def determine_bucket!
128
+ @bucket ||= ENV['S3_BUCKET']
129
+ @bucket ||= ask "S3 Bucket:"
130
+ end
131
+
132
+ def determine_region!
133
+ @region ||= ENV['AWS_REGION']
134
+ end
135
+
136
+ def determine_acl!
137
+ @acl ||= "public_read"
138
+ end
139
+ end