deploygate 0.0.6 → 0.1.0

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.
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