deploygate 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +13 -5
  2. data/.gitignore +4 -0
  3. data/.travis.yml +4 -4
  4. data/README.md +3 -3
  5. data/lib/deploygate/android/gradle_deploy.rb +95 -0
  6. data/lib/deploygate/android/gradle_plugin_installer.rb +101 -0
  7. data/lib/deploygate/android/gradle_project.rb +14 -0
  8. data/lib/deploygate/commands/deploy/build.rb +15 -154
  9. data/lib/deploygate/{build.rb → project.rb} +3 -3
  10. data/lib/deploygate/version.rb +1 -1
  11. data/lib/deploygate/xcode/analyze.rb +116 -0
  12. data/lib/deploygate/xcode/export.rb +281 -0
  13. data/lib/deploygate/{builds → xcode}/ios.rb +13 -5
  14. data/lib/deploygate/xcode/member_center.rb +52 -0
  15. data/lib/deploygate/xcode/member_centers/app.rb +35 -0
  16. data/lib/deploygate/xcode/member_centers/provisioning_profile.rb +99 -0
  17. data/lib/deploygate.rb +14 -5
  18. data/spec/deploygate/project_spec.rb +39 -0
  19. data/spec/deploygate/xcode/analyze_spec.rb +3 -0
  20. data/spec/deploygate/{builds/ios → xcode}/export_spec.rb +21 -21
  21. data/spec/deploygate/{builds → xcode}/ios_spec.rb +12 -12
  22. data/spec/deploygate/xcode/member_center_spec.rb +21 -0
  23. data/spec/deploygate/xcode/member_centers/app_spec.rb +60 -0
  24. data/spec/deploygate/xcode/member_centers/provisioning_profile_spec.rb +3 -0
  25. metadata +70 -66
  26. data/lib/deploygate/builds/ios/analyze.rb +0 -111
  27. data/lib/deploygate/builds/ios/export.rb +0 -188
  28. data/lib/deploygate/builds/ios/set_profile.rb +0 -128
  29. data/spec/deploygate/build_spec.rb +0 -37
  30. data/spec/deploygate/builds/ios/analyze_spec.rb +0 -3
  31. data/spec/deploygate/builds/ios/set_profile_spec.rb +0 -3
@@ -0,0 +1,281 @@
1
+ module DeployGate
2
+ module Xcode
3
+ class Export
4
+ AD_HOC = 'ad-hoc'
5
+ ENTERPRISE = 'enterprise'
6
+ SUPPORT_EXPORT_METHOD = [AD_HOC, ENTERPRISE]
7
+ PROFILE_EXTNAME = '.mobileprovision'
8
+
9
+ class << self
10
+
11
+ # @param [String] bundle_identifier
12
+ # @param [String] uuid
13
+ # @return [String]
14
+ def provisioning_profile(bundle_identifier, uuid = nil)
15
+ data = DeployGate::Xcode::Export.find_local_data(bundle_identifier, uuid)
16
+ profiles = data[:profiles]
17
+ teams = data[:teams]
18
+
19
+ target_provisioning_profile = nil
20
+ if teams.empty?
21
+ target_provisioning_profile = create_provisioning(bundle_identifier, uuid)
22
+ elsif teams.count == 1
23
+ target_provisioning_profile = select_profile(profiles[teams.keys.first])
24
+ elsif teams.count >= 2
25
+ target_provisioning_profile = select_teams(teams, profiles)
26
+ end
27
+
28
+ target_provisioning_profile
29
+ end
30
+
31
+ # @param [String] bundle_identifier
32
+ # @param [String] uuid
33
+ # @return [Hash]
34
+ def find_local_data(bundle_identifier, uuid = nil)
35
+ result_profiles = {}
36
+ teams = {}
37
+ profile_paths = load_profile_paths
38
+ profiles = profile_paths.map{|p| profile_to_plist(p)}
39
+ profiles.reject! {|profile| profile['UUID'] != uuid} unless uuid.nil?
40
+
41
+ profiles.each do |profile|
42
+ entities = profile['Entitlements']
43
+ unless entities['get-task-allow']
44
+ team = entities['com.apple.developer.team-identifier']
45
+ application_id = entities['application-identifier']
46
+ application_id.slice!(/^#{team}\./)
47
+ application_id = '.' + application_id if application_id == '*'
48
+ if bundle_identifier.match(application_id) &&
49
+ DateTime.now < profile['ExpirationDate'] &&
50
+ installed_certificate?(profile['Path'])
51
+
52
+ teams[team] = profile['TeamName'] if teams[team].nil?
53
+ result_profiles[team] = [] if result_profiles[team].nil?
54
+ result_profiles[team].push(profile['Path'])
55
+ end
56
+ end
57
+ end
58
+
59
+ {
60
+ :teams => teams,
61
+ :profiles => result_profiles
62
+ }
63
+ end
64
+
65
+ # @param [String] profile_path
66
+ # @return [Boolean]
67
+ def installed_certificate?(profile_path)
68
+ profile = profile_to_plist(profile_path)
69
+ certs = profile['DeveloperCertificates'].map do |cert|
70
+ certificate_str = cert.read
71
+ certificate = OpenSSL::X509::Certificate.new certificate_str
72
+ id = OpenSSL::Digest::SHA1.new(certificate.to_der).to_s.upcase!
73
+ installed_distribution_certificate_ids.include?(id)
74
+ end
75
+ certs.include?(true)
76
+ end
77
+
78
+ # @return [Array]
79
+ def installed_distribution_certificate_ids
80
+ certificates = installed_certificates()
81
+ ids = []
82
+ certificates.each do |current|
83
+ next unless current.match(/iPhone Distribution:/)
84
+ begin
85
+ (ids << current.match(/.*\) (.*) \".*/)[1])
86
+ rescue
87
+ # the last line does not match
88
+ end
89
+ end
90
+
91
+ ids
92
+ end
93
+
94
+ # @return [Array]
95
+ def installed_distribution_conflicting_certificates
96
+ certificates = installed_certificates()
97
+ names = []
98
+ certificates.each do |current|
99
+ begin
100
+ names << current.match(/(iPhone Distribution:.*)/)[1]
101
+ rescue
102
+ end
103
+ end
104
+
105
+ conflicting_names = names.select{|e| names.index(e) != names.rindex(e)}.uniq
106
+ conflicting_certificates = []
107
+ certificates.each do |current|
108
+ begin
109
+ name = current.match(/(iPhone Distribution:.*)/)[1]
110
+ next unless conflicting_names.include?(name)
111
+ conflicting_certificates << current
112
+ rescue
113
+ end
114
+ end
115
+
116
+ conflicting_certificates
117
+ end
118
+
119
+ # @return [Array]
120
+ def installed_certificates
121
+ available = `security find-identity -v -p codesigning`
122
+ certificates = []
123
+ available.split("\n").each do |current|
124
+ next if current.include? "REVOKED"
125
+ certificates << current
126
+ end
127
+
128
+ certificates
129
+ end
130
+
131
+ # @param [Array] profile_paths
132
+ # @return [String]
133
+ def select_profile(profile_paths)
134
+ select = nil
135
+
136
+ profile_paths.each do |path|
137
+ select = path if adhoc?(path) && select.nil?
138
+ select = path if inhouse?(path)
139
+ end
140
+ select
141
+ end
142
+
143
+ # @param [String] profile_path
144
+ # @return [String]
145
+ def codesigning_identity(profile_path)
146
+ profile = profile_to_plist(profile_path)
147
+ identity = nil
148
+
149
+ profile['DeveloperCertificates'].each do |cert|
150
+ certificate_str = cert.read
151
+ certificate = OpenSSL::X509::Certificate.new certificate_str
152
+ id = OpenSSL::Digest::SHA1.new(certificate.to_der).to_s.upcase!
153
+
154
+ available = `security find-identity -v -p codesigning`
155
+ available.split("\n").each do |current|
156
+ next if current.include? "REVOKED"
157
+ begin
158
+ search = current.match(/.*\) (.*) \"(.*)\"/)
159
+ identity = search[2] if id == search[1]
160
+ rescue
161
+ end
162
+ end
163
+ end
164
+
165
+ identity
166
+ end
167
+
168
+ # @param [String] profile_path
169
+ # @return [String]
170
+ def method(profile_path)
171
+ adhoc?(profile_path) ? AD_HOC : ENTERPRISE
172
+ end
173
+
174
+ # @param [String] profile_path
175
+ # @return [Boolean]
176
+ def adhoc?(profile_path)
177
+ profile = profile_to_plist(profile_path)
178
+ !profile['Entitlements']['get-task-allow'] && profile['ProvisionsAllDevices'].nil?
179
+ end
180
+
181
+ # @param [String] profile_path
182
+ # @return [Boolean]
183
+ def inhouse?(profile_path)
184
+ profile = profile_to_plist(profile_path)
185
+ !profile['Entitlements']['get-task-allow'] && !profile['ProvisionsAllDevices'].nil?
186
+ end
187
+
188
+ def load_profile_paths
189
+ profiles_path = File.expand_path("~") + "/Library/MobileDevice/Provisioning Profiles/*.mobileprovision"
190
+ Dir[profiles_path]
191
+ end
192
+
193
+ # @param [String] profile_path
194
+ # @return [Hash]
195
+ def profile_to_plist(profile_path)
196
+ File.open(profile_path) do |profile|
197
+ asn1 = OpenSSL::ASN1.decode(profile.read)
198
+ plist_str = asn1.value[1].value[0].value[2].value[1].value[0].value
199
+ plist = Plist.parse_xml plist_str.force_encoding('UTF-8')
200
+ plist['Path'] = profile_path
201
+ return plist
202
+ end
203
+ end
204
+
205
+ def create_provisioning(identifier, uuid)
206
+ app = MemberCenters::App.new(identifier)
207
+ provisioning_prifile = MemberCenters::ProvisioningProfile.new(identifier)
208
+
209
+ begin
210
+ unless app.created?
211
+ app.create!
212
+ puts "App ID #{identifier} was created"
213
+ end
214
+ rescue => e
215
+ DeployGate::Message::Error.print("Error: Failed to create App ID")
216
+ raise e
217
+ end
218
+
219
+ begin
220
+ provisioning_profiles = provisioning_prifile.create!(uuid)
221
+ rescue => e
222
+ DeployGate::Message::Error.print("Error: Failed to create provisioning profile")
223
+ raise e
224
+ end
225
+
226
+ select_profile(provisioning_profiles)
227
+ end
228
+
229
+ # @param [Hash] teams
230
+ # @param [Hash] profiles
231
+ # @return [String]
232
+ def select_teams(teams, profiles)
233
+ result = nil
234
+ cli = HighLine.new
235
+ cli.choose do |menu|
236
+ menu.prompt = 'Please select team'
237
+ teams.each_with_index do |team, index|
238
+ menu.choice("#{team[1]} #{team[0]}") {
239
+ result = DeployGate::Xcode::Export.select_profile(profiles[team])
240
+ }
241
+ end
242
+ end
243
+
244
+ result
245
+ end
246
+
247
+ def check_local_certificates
248
+ if installed_distribution_certificate_ids.count == 0
249
+ # not local install certificate
250
+ DeployGate::Message::Error.print("Error: Not local install distribution certificate")
251
+ puts <<EOF
252
+
253
+ Not local install iPhone Distribution certificates.
254
+ Please install certificate.
255
+
256
+ Docs: https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingCertificates/MaintainingCertificates.html
257
+
258
+ EOF
259
+ exit
260
+ end
261
+
262
+ conflicting_certificates = installed_distribution_conflicting_certificates
263
+ if conflicting_certificates.count > 0
264
+ DeployGate::Message::Error.print("Error: Conflicting local install certificates")
265
+ puts <<EOF
266
+
267
+ Conflicting local install certificates.
268
+ Please uninstall certificates.
269
+ EOF
270
+ conflicting_certificates.each do |certificate|
271
+ puts certificate
272
+ end
273
+ puts ""
274
+
275
+ exit
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
@@ -1,5 +1,5 @@
1
1
  module DeployGate
2
- module Builds
2
+ module Xcode
3
3
  module Ios
4
4
  WORK_DIR_EXTNAME = '.xcworkspace'
5
5
  PROJECT_DIR_EXTNAME = '.xcodeproj'
@@ -13,18 +13,26 @@ module DeployGate
13
13
  # @param [String] codesigning_identity
14
14
  # @param [String] export_method
15
15
  # @return [String]
16
- def build(ios_analyze, target_scheme, codesigning_identity, export_method = Export::AD_HOC)
17
- raise NotSupportExportMethodError, 'Not support export' unless Export::SUPPORT_EXPORT_METHOD.include?(export_method)
16
+ def build(ios_analyze, target_scheme, codesigning_identity, export_method = DeployGate::Xcode::Export::AD_HOC)
17
+ raise NotSupportExportMethodError, 'Not support export' unless DeployGate::Xcode::Export::SUPPORT_EXPORT_METHOD.include?(export_method)
18
18
 
19
19
  values = {
20
20
  :export_method => export_method,
21
21
  :workspace => ios_analyze.build_workspace,
22
- :configuration => Analyze::BUILD_CONFIGRATION,
22
+ :configuration => DeployGate::Xcode::Analyze::BUILD_CONFIGRATION,
23
23
  :scheme => target_scheme,
24
24
  :codesigning_identity => codesigning_identity
25
25
  }
26
26
  v = FastlaneCore::Configuration.create(Gym::Options.available_options, values)
27
- absolute_ipa_path = File.expand_path(Gym::Manager.new.work(v))
27
+
28
+ begin
29
+ absolute_ipa_path = File.expand_path(Gym::Manager.new.work(v))
30
+ rescue => e
31
+ # TODO: build error handling
32
+ use_xcode_path = `xcode-select -p`
33
+ DeployGate::Message::Error.print("Current Xcode used to build: #{use_xcode_path} (via xcode-select)")
34
+ raise e
35
+ end
28
36
  absolute_dsym_path = absolute_ipa_path.gsub(".ipa", ".app.dSYM.zip") # TODO: upload to deploygate
29
37
 
30
38
  absolute_ipa_path
@@ -0,0 +1,52 @@
1
+ require 'singleton'
2
+
3
+ module DeployGate
4
+ module Xcode
5
+ class MemberCenter
6
+ include Singleton
7
+ attr_reader :email, :method
8
+
9
+ def initialize
10
+ @email = input_email
11
+ Spaceship.login @email
12
+ Spaceship.select_team
13
+
14
+ if Spaceship.client.in_house?
15
+ @method = Export::ENTERPRISE
16
+ else
17
+ @method = Export::AD_HOC
18
+ end
19
+ end
20
+
21
+ # @return [Boolean]
22
+ def adhoc?
23
+ @method == Export::AD_HOC
24
+ end
25
+
26
+ # @return [Boolean]
27
+ def in_house?
28
+ @method == Export::ENTERPRISE
29
+ end
30
+
31
+ private
32
+
33
+ # @return [String]
34
+ def input_email
35
+ puts <<EOF
36
+
37
+ No suitable provisioning profile found to export the app.
38
+
39
+ Please enter your email and password for Apple Developer Center
40
+ to set up/download provisioning profile automatically so you can
41
+ export the app without any extra steps.
42
+
43
+ Note: Your password will be stored to Keychain and never be sent to DeployGate.
44
+
45
+ EOF
46
+ print 'Email: '
47
+ STDIN.gets.chop
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,35 @@
1
+ module DeployGate
2
+ module Xcode
3
+ module MemberCenters
4
+ class App
5
+ attr_reader :uuid, :member_center
6
+
7
+ # @param [String] uuid
8
+ # @return [DeployGate::Xcode::MemberCenters::App]
9
+ def initialize(uuid)
10
+ @member_center = DeployGate::Xcode::MemberCenter.instance
11
+ @uuid = uuid
12
+ end
13
+
14
+ # @return [Boolean]
15
+ def created?
16
+ Spaceship.app.all.collect do |app|
17
+ return true if app.bundle_id == @uuid
18
+ end
19
+
20
+ false
21
+ end
22
+
23
+ # @return [void]
24
+ def create!
25
+ Spaceship.app.create!(bundle_id: @uuid, name: name())
26
+ end
27
+
28
+ # @return [String]
29
+ def name
30
+ @uuid.split('.').join(' ')
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,99 @@
1
+ module DeployGate
2
+ module Xcode
3
+ module MemberCenters
4
+ class ProvisioningProfile
5
+ attr_reader :member_center, :app_identifier
6
+
7
+ OUTPUT_PATH = '/tmp/dg/provisioning_profile/'
8
+ CERTIFICATE_OUTPUT_PATH = '/tmp/dg/certificate/'
9
+
10
+ def initialize(app_identifier)
11
+ @member_center = DeployGate::Xcode::MemberCenter.instance
12
+ @app_identifier = app_identifier
13
+
14
+ FileUtils.mkdir_p(OUTPUT_PATH)
15
+ end
16
+
17
+ # @param [String] uuid
18
+ # @return [Array]
19
+ def create!(uuid = nil)
20
+ profiles = if uuid.nil?
21
+ all_create()
22
+ else
23
+ [download(uuid)]
24
+ end
25
+
26
+ profiles
27
+ end
28
+
29
+ private
30
+
31
+ # @return [Array]
32
+ def all_create
33
+ if @member_center.adhoc?
34
+ prod_certs = Spaceship.certificate.production.all
35
+ else
36
+ prod_certs = Spaceship.certificate.all.reject{|cert| cert.class != Spaceship::Portal::Certificate::InHouse}
37
+ end
38
+
39
+ # check local install certificate
40
+ FileUtils.mkdir_p(CERTIFICATE_OUTPUT_PATH)
41
+ distribution_cert_ids = []
42
+ prod_certs.each do |cert|
43
+ path = File.join(CERTIFICATE_OUTPUT_PATH, "#{cert.id}.cer")
44
+ raw_data = cert.download_raw
45
+ File.write(path, raw_data)
46
+ distribution_cert_ids.push(cert.id) if FastlaneCore::CertChecker.installed?(path)
47
+ end
48
+ raise 'Not local install certificate' if distribution_cert_ids.empty?
49
+
50
+ provisionings = []
51
+ distribution_cert_ids.each do |cert_id|
52
+ values = sigh_config_values(cert_id: cert_id)
53
+ download_profile_path = download_profile(values)
54
+ provisionings.push(download_profile_path)
55
+ end
56
+
57
+ provisionings
58
+ end
59
+
60
+ # @param [String] uuid
61
+ # @return [String]
62
+ def download(uuid)
63
+ profiles = Spaceship.provisioning_profile.all.reject!{|p| p.uuid != uuid}
64
+
65
+ raise 'Not Xcode selected Provisioning Profile' if profiles.empty?
66
+ select_profile = profiles.first
67
+ method = select_profile.kind_of?(Spaceship::Portal::ProvisioningProfile::AdHoc)
68
+
69
+ values = sigh_config_values(adhoc: method, provisioning_name: select_profile.name)
70
+ download_profile(values)
71
+ end
72
+
73
+ # @param [Hash] values
74
+ # @return [String]
75
+ def download_profile(values)
76
+ config = FastlaneCore::Configuration.create(Sigh::Options.available_options, values)
77
+ Sigh.config = config
78
+
79
+ Sigh::Manager.start
80
+ end
81
+
82
+ # @return [Hash]
83
+ def sigh_config_values(adhoc: @member_center.adhoc?, provisioning_name: nil, cert_id: nil)
84
+ values = {
85
+ adhoc: adhoc,
86
+ app_identifier: @app_identifier,
87
+ username: @member_center.email,
88
+ output_path: OUTPUT_PATH,
89
+ team_id: Spaceship.client.team_id
90
+ }
91
+ values.merge!({provisioning_name: provisioning_name}) unless provisioning_name.nil?
92
+ values.merge!({cert_id: cert_id}) unless cert_id.nil?
93
+
94
+ values
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
data/lib/deploygate.rb CHANGED
@@ -13,6 +13,10 @@ require "uuid"
13
13
  require "gem_update_checker"
14
14
  require "active_support/core_ext/time"
15
15
  require "locale"
16
+ require "tempfile"
17
+ require "open3"
18
+ require "open-uri"
19
+ require "rexml/document"
16
20
 
17
21
  # ios build
18
22
  require "gym"
@@ -39,13 +43,18 @@ require "deploygate/config/credential"
39
43
  require "deploygate/config/cache_version"
40
44
  require "deploygate/session"
41
45
  require "deploygate/deploy"
42
- require "deploygate/build"
46
+ require "deploygate/project"
43
47
  require "deploygate/user"
44
- require "deploygate/builds/ios"
45
- require "deploygate/builds/ios/export"
46
- require "deploygate/builds/ios/analyze"
47
- require "deploygate/builds/ios/set_profile"
48
48
  require "deploygate/message/error"
49
49
  require "deploygate/message/success"
50
50
  require "deploygate/message/warning"
51
+ require "deploygate/xcode/member_center"
52
+ require "deploygate/xcode/member_centers/app"
53
+ require "deploygate/xcode/member_centers/provisioning_profile"
54
+ require "deploygate/xcode/export"
55
+ require "deploygate/xcode/analyze"
56
+ require "deploygate/xcode/ios"
57
+ require "deploygate/android/gradle_deploy"
58
+ require "deploygate/android/gradle_plugin_installer"
59
+ require "deploygate/android/gradle_project"
51
60
  require "deploygate/version"
@@ -0,0 +1,39 @@
1
+ describe DeployGate::Project do
2
+ describe "#ios?" do
3
+ it "when select workspace" do
4
+ allow(DeployGate::Xcode::Ios).to receive(:ios_root?).and_return(false)
5
+ allow(DeployGate::Xcode::Ios).to receive(:workspace?).and_return(true)
6
+ allow(DeployGate::Xcode::Ios).to receive(:project?).and_return(false)
7
+
8
+ result = DeployGate::Project.ios?('path')
9
+ expect(result).to be_truthy
10
+ end
11
+
12
+ it "when workspaces" do
13
+ allow(DeployGate::Xcode::Ios).to receive(:ios_root?).and_return(false)
14
+ allow(DeployGate::Xcode::Ios).to receive(:workspace?).and_return(false)
15
+ allow(DeployGate::Xcode::Ios).to receive(:project?).and_return(true)
16
+
17
+ result = DeployGate::Project.ios?('path')
18
+ expect(result).to be_truthy
19
+ end
20
+
21
+ it "not ios" do
22
+ allow(DeployGate::Xcode::Ios).to receive(:ios_root?).and_return(false)
23
+ allow(DeployGate::Xcode::Ios).to receive(:workspace?).and_return(false)
24
+ allow(DeployGate::Xcode::Ios).to receive(:project?).and_return(false)
25
+
26
+ result = DeployGate::Project.ios?('path')
27
+ expect(result).to be_falsey
28
+ end
29
+ end
30
+
31
+ describe "#android?" do
32
+ it "android project" do
33
+ allow(DeployGate::Android::GradleProject).to receive(:root_dir?).and_return(true)
34
+
35
+ result = DeployGate::Project.android?('path')
36
+ expect(result).to be_truthy
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ describe DeployGate::Xcode::Analyze do
2
+ # TODO: add test
3
+ end