asca 0.1.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "rake", "~> 10.0"
32
32
  spec.add_development_dependency "minitest", "~> 5.0"
33
33
  spec.add_dependency "json"
34
- spec.add_dependency "curb"
34
+ #https://github.com/httprb/http/wiki
35
+ spec.add_dependency "http"
35
36
  spec.add_dependency 'jwt'
36
37
  end
data/exe/asca CHANGED
@@ -10,21 +10,71 @@ require 'optparse'
10
10
 
11
11
  options = {}
12
12
  option_parser = OptionParser.new do |opts|
13
- opts.banner = 'This is an apple connect api wrapper cli.'
13
+ opts.banner = 'This is an apple connect api wrapper cli. It wraps most of the apple store connect REST api and also supplement some useful tools based on these apis. For how to use it, refer to https://github.com/xueminghao/appstoreconnectapi plase.'
14
14
 
15
- opts.on('-a action-name', '--action action-name', 'profile-install, profile-download') do |value|
16
- options[:action] = value
15
+ opts.on('--tools tool-name', 'Call a specific tool by name. such as register-device') do |value|
16
+ options[:tool_name] = value
17
17
  end
18
- opts.on('-n name', '--name name', 'Profile name') do |value|
19
- options[:profile_name] = value
18
+
19
+ opts.on('-a api-name', '--api api-name', 'api name') do |value|
20
+ options[:api_name] = value
21
+ end
22
+
23
+ opts.on('-m method', '--method method', 'api request method') do |value|
24
+ options[:method] = value
25
+ end
26
+
27
+ opts.on('-n name', '--name name', 'tool or api parameter: name') do |value|
28
+ options[:name] = value
29
+ end
30
+ opts.on('--udid udid', 'tool or api parameter: device udid') do |value|
31
+ options[:udid] = value
32
+ end
33
+ opts.on('--profile-names profile-name-array', 'tool or api parameter: profile name list') do |value|
34
+ options[:profile_names] = value.split(',')
35
+ end
36
+ opts.on('--auto-install', 'tool or api parameter: auto install the downloaded profiles') do
37
+ options[:auto_install] = true
38
+ end
39
+ opts.on('-v', '--version', 'current version') do |value|
40
+ options[:version] = 'version'
20
41
  end
21
42
  end.parse!
22
43
 
23
- case options[:action]
24
- when 'profile-download'
25
- Asca::Profiles.download_profile options[:profile_name]
26
- when 'profile-install'
27
- Asca::Profiles.install_profile options[:profile_name]
44
+ tool_name = options[:tool_name]
45
+ api_name = options[:api_name]
46
+
47
+ if tool_name
48
+ case tool_name
49
+ when 'register-device'
50
+ Asca::Tools.register_device :device_info => { :udid => options[:udid], :name => options[:name] }, :profile_names => options[:profile_names]
51
+ when 'download-profile'
52
+ if options[:auto_install]
53
+ Asca::REST::Provisioning::Profiles.install_profile :name => options[:name]
54
+ else
55
+ Asca::REST::Provisioning::Profiles.download_profile :name => options[:name]
56
+ end
57
+ else
58
+ Asca::Tools::Log.error("Unsupported tool name: #{tool_name}")
59
+ end
60
+ elsif api_name
61
+ method = options[:method]
62
+ method = method ? method : 'get'
63
+ case api_name
64
+ when 'device'
65
+ case method
66
+ when 'get'
67
+ Asca::REST::Provisioning::Devices.list_devices
68
+ when 'post'
69
+ Asca::REST::Provisioning::Devices.register_new_device options[:udid], options[:name]
70
+ else
71
+ Asca::Tools::Log.error("Unsupported request method #{method} for api #{api_name}, coming soon!!")
72
+ end
73
+ else
74
+ Asca::Tools::Log.error("Unsupported api request #{api_name}, coming soon!!")
75
+ end
76
+ elsif options[:version]
77
+ Asca::Tools::Log.info(Asca::VERSION)
28
78
  else
29
- puts "Unsupported action: " + options[:action]
30
- end
79
+ puts Asca::Tools::Log.error('Wrong parameters')
80
+ end
@@ -1,8 +1,15 @@
1
1
  require "asca/version"
2
2
  require "asca/consts.rb"
3
- require "asca/configuration.rb"
4
- require "asca/token"
5
- require "asca/profiles.rb"
3
+
4
+ require "asca/rest/provisioning/bundleids.rb"
5
+ require "asca/rest/provisioning/devices.rb"
6
+ require "asca/rest/provisioning/certificates.rb"
7
+ require "asca/rest/provisioning/profiles.rb"
8
+
9
+ require "asca/tools/log.rb"
10
+ require "asca/tools/configuration.rb"
11
+ require "asca/tools/token"
12
+ require "asca/tools/tools.rb"
6
13
 
7
14
  module Asca
8
15
  class Error < StandardError; end
@@ -7,4 +7,7 @@ module Asca
7
7
  end
8
8
 
9
9
  URI_PROFILES = url_for_path('v1/profiles')
10
+ URI_DEVICES = url_for_path('v1/devices')
11
+ URI_CERTIFICATES = url_for_path('v1/certificates')
12
+ URI_BUNDLEIDS = url_for_path('v1/bundleIds')
10
13
  end
@@ -0,0 +1,22 @@
1
+ require 'http'
2
+ require 'json'
3
+
4
+ module Asca
5
+ module REST
6
+ module Provisioning
7
+ class BundleIDs
8
+ class << self
9
+ def list_bundle_ids
10
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(URI_BUNDLEIDS)
11
+ if response.status.success?
12
+ bundleids = JSON.parse(response.body)
13
+ return bundleids
14
+ else
15
+ puts response.body
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ require 'http'
2
+
3
+ module Asca
4
+ module REST
5
+ module Provisioning
6
+ class Certificates
7
+ class << self
8
+ def list_certificates(options = {})
9
+ # type enum : [ "DEVELOPMENT", "DISTRIBUTION" ]
10
+ type = options[:type]
11
+ types = !type ? "DEVELOPMENT, DISTRIBUTION" : type
12
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(URI_CERTIFICATES, :params => {
13
+ "filter[certificateType]" => types
14
+ })
15
+ if response.status.success?
16
+ certificates = JSON.parse(response.body)["data"]
17
+ for certificate in certificates do
18
+ id = certificate["id"]
19
+ name = certificate["attributes"]["name"]
20
+ type = certificate["attributes"]["certificateType"]
21
+ puts "Certificate id: #{id} name: #{name} type: #{type}"
22
+ end
23
+ else
24
+ puts response.body
25
+ end
26
+ return response.status.success?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ require 'http'
2
+ require "json"
3
+
4
+ module Asca
5
+ module REST
6
+ module Provisioning
7
+ class Devices
8
+ class << self
9
+ def list_devices
10
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(URI_DEVICES, :params => { "limit": 200 })
11
+ if response.status.success?
12
+ devices = JSON.parse(response.body)
13
+ puts "device count #{devices["data"].length()}"
14
+ return devices["data"]
15
+ end
16
+ return nil
17
+ end
18
+
19
+ def register_new_device(options = {})
20
+ udid = options[:udid]
21
+ name = options[:name]
22
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).post(URI_DEVICES, :json => { "data" => {
23
+ "type" => "devices",
24
+ "attributes" => {
25
+ "name" => name,
26
+ "platform" => "IOS",
27
+ "udid" => udid
28
+ }
29
+ }})
30
+ if response.status.success?
31
+ return true
32
+ else
33
+ return false
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,167 @@
1
+ require 'http'
2
+ require 'json'
3
+ require "base64"
4
+
5
+ module Asca
6
+ module REST
7
+ module Provisioning
8
+ class Profiles
9
+ class << self
10
+ def download_profile(options = {})
11
+ profile_name = options[:name]
12
+ out_put_dir = options[:out_put_dir]
13
+ if !out_put_dir
14
+ out_put_dir = Asca::Tools::Configuration.get_config('out_put_dir')
15
+ if !out_put_dir
16
+ puts "Please enter your out put dir:"
17
+ out_put_dir = File.expand_path(gets.chomp)
18
+ Asca::Tools::Configuration.update_config('out_put_dir', out_put_dir)
19
+ end
20
+ end
21
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(URI_PROFILES, :params => { 'filter[name]' => profile_name })
22
+ if response.status.success?
23
+ profile_obj = JSON.parse(response.body)
24
+ profile_content = profile_obj["data"][0]["attributes"]['profileContent']
25
+ File.open(File.expand_path(profile_name + ".mobileprovision", out_put_dir), 'w') do |file|
26
+ file.write(Base64.decode64(profile_content))
27
+ end
28
+ else
29
+ Asca::Tools::Log.error(response.body)
30
+ end
31
+ end
32
+
33
+ def install_profile(options = {})
34
+ profile_name = options[:name]
35
+ download_profile :name => profile_name, :out_put_dir => Asca::Tools::Configuration::CACHE_DIR
36
+ profile_file_path = File.expand_path(profile_name + ".mobileprovision", Asca::Tools::Configuration::CACHE_DIR)
37
+
38
+ # install profile
39
+ FileUtils.cp(profile_file_path, File.expand_path('~/Library/MobileDevice/Provisioning Profiles'))
40
+ Asca::Tools::Log.info("#{profile_name} installed successfully!")
41
+ end
42
+
43
+ # notion: bundle_id is not bundle identifier and device id is udid。They are the corresponding api id.
44
+ def create_new_profile(options = {})
45
+ if !options[:name]
46
+ Asca::Tools::Asca::Tools::Log.error('No profile name specified')
47
+ return false
48
+ end
49
+ if !options[:type]
50
+ Asca::Tools::Asca::Tools::Log.error('No type specified')
51
+ return false
52
+ end
53
+ if !options[:bundle_id]
54
+ Asca::Tools::Asca::Tools::Log.error('No bundle id specfied')
55
+ return false
56
+ end
57
+ if !options[:certificate_ids] || options[:certificate_ids].length == 0
58
+ Asca::Tools::Asca::Tools::Log.error('No certificate id specified')
59
+ return false
60
+ end
61
+
62
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).post(URI_PROFILES, :json => { "data" => {
63
+ "type" => "profiles",
64
+ "attributes" => {
65
+ "name" => options[:name],
66
+ "profileType" => options[:type],
67
+ },
68
+ "relationships" => {
69
+ "bundleId" => {
70
+ "data" => {
71
+ "type" => "bundleIds",
72
+ "id" => options[:bundle_id],
73
+ }
74
+ },
75
+ "certificates" => {
76
+ "data" => options[:certificate_ids].map { |certificate_id| { "type" => "certificates", "id" => certificate_id }}
77
+ },
78
+ "devices" => {
79
+ "data" => options[:device_ids] ? options[:device_ids].map { |device_id| { "type" => "devices", "id" => device_id } } : nil
80
+ },
81
+ }
82
+ }})
83
+ if response.status.success?
84
+ return true
85
+ else
86
+ puts response.body
87
+ return false
88
+ end
89
+ end
90
+
91
+ def delete_profile(options = {})
92
+ # query profile id
93
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(URI_PROFILES, :params => { 'filter[name]' => options[:name] })
94
+ if response.status.success?
95
+ responseObj = JSON.parse(response.body)
96
+ queried_profile_list = responseObj["data"]
97
+ if queried_profile_list.length() > 0
98
+ profile_id = queried_profile_list[0]["id"]
99
+ end
100
+ else
101
+ Asca::Tools::Log.error(response.body)
102
+ return
103
+ end
104
+ if !profile_id
105
+ puts "No profile named #{options[:name]} found!"
106
+ return
107
+ end
108
+
109
+ # delete profile
110
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).delete(URI_PROFILES + "/#{profile_id}")
111
+ if response.status.success?
112
+ Asca::Tools::Log.info("Profile named #{options[:name]} deleted successfully!")
113
+ else
114
+ puts response.body
115
+ end
116
+ return response.status.success?
117
+ end
118
+
119
+ # update profile. The new profile is almost the same as the old one, such as the same name, bundle id, certificate ids, with only exception that the new one always try to include all the currently reigstered devices.
120
+ def update_profile(options = {})
121
+ # query profile info
122
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(URI_PROFILES, :params => { 'filter[name]' => options[:name] })
123
+ if response.status.success?
124
+ responseObj = JSON.parse(response.body)
125
+ queried_profile_list = responseObj["data"]
126
+ if queried_profile_list.length() > 0
127
+ profile = queried_profile_list[0]
128
+ end
129
+ else
130
+ Asca::Tools::Log.error(response.body)
131
+ return
132
+ end
133
+
134
+ if !profile
135
+ Asca::Tools::Log.error("No profile named #{options[:name]} found")
136
+ return
137
+ end
138
+ # create new profile
139
+ profile_type = profile["attributes"]["profileType"]
140
+
141
+ # get bundle id
142
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(profile["relationships"]["bundleId"]["links"]["self"])
143
+ bundle_id = JSON.parse(response.body)["data"]["id"]
144
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(profile["relationships"]["certificates"]["links"]["self"])
145
+ certificate_ids = JSON.parse(response.body)["data"].map { |cer| cer["id"] }
146
+
147
+ # get all device ids
148
+ device_ids = Asca::REST::Provisioning::Devices.list_devices.map { |device|
149
+ device["id"]
150
+ }
151
+
152
+ # delete old prifile
153
+ delete_profile :name => options[:name]
154
+
155
+ if profile_type.include? 'APP_STORE'
156
+ create_new_profile :name => options[:name], :type => profile_type, :bundle_id => bundle_id, :certificate_ids => certificate_ids
157
+ else
158
+ create_new_profile :name => options[:name], :type => profile_type, :bundle_id => bundle_id, :device_ids => device_ids, :certificate_ids => certificate_ids
159
+ end
160
+
161
+ return true
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,57 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+
4
+ module Asca
5
+ module Tools
6
+ class Configuration
7
+ ROOTDIR = File.expand_path '~/.com.hurryup.asca'
8
+ JSONFILE = File.expand_path 'config.json', ROOTDIR
9
+ CACHE_DIR = File.expand_path 'cache', ROOTDIR
10
+ class << self
11
+ # reset config file
12
+ def reset_config
13
+ # remove all
14
+ FileUtils.rm_rf(ROOTDIR)
15
+
16
+ # create root dir
17
+ Dir.mkdir ROOTDIR
18
+
19
+ # create cache dir
20
+ Dir.mkdir CACHE_DIR
21
+
22
+ # init config file
23
+ File.open(JSONFILE, 'w') { |file|
24
+ file.write("{}")
25
+ }
26
+ end
27
+
28
+ # update config
29
+ def update_config(key, value)
30
+ if !File.exist?(JSONFILE)
31
+ reset_config
32
+ end
33
+ file_content = File.read(JSONFILE)
34
+ configuration = JSON.parse(file_content)
35
+ if value
36
+ configuration[key] = value
37
+ else
38
+ configuration.delete(key)
39
+ end
40
+ File.open(JSONFILE, 'w') { |file|
41
+ file.write(JSON.pretty_generate(configuration))
42
+ }
43
+ return 0
44
+ end
45
+
46
+ def get_config(key)
47
+ if !File.exist?(JSONFILE)
48
+ reset_config
49
+ end
50
+ file_content = File.read(JSONFILE)
51
+ configuration = JSON.parse(file_content)
52
+ return configuration[key]
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ module Asca
2
+ module Tools
3
+ class Log
4
+ class Color
5
+ class << self
6
+ def time
7
+ "\e[37m"
8
+ end
9
+
10
+ def warn
11
+ "\e[33;1m"
12
+ end
13
+
14
+ def info
15
+ "\e[32m"
16
+ end
17
+
18
+ def error
19
+ "\e[31;1m"
20
+ end
21
+
22
+ def reset
23
+ "\e[0m"
24
+ end
25
+
26
+ def command
27
+ "\e[34m"
28
+ end
29
+ end
30
+ end
31
+
32
+ class << self
33
+ def warn message
34
+ puts "#{Color.warn}WARN: #{message}#{Color.reset}"
35
+ end
36
+
37
+ def info message
38
+ puts "#{Color.info}INFO: #{message}#{Color.reset}"
39
+ end
40
+
41
+ def error message
42
+ puts "#{Color.error}ERROR: #{message}#{Color.reset}", STDERR
43
+ end
44
+
45
+ def command message
46
+ puts "#{Color.command}RUN: \e[4m#{message}#{Color.reset}"
47
+ end
48
+
49
+ def puts message, io = STDOUT
50
+ io.puts "#{Color.time}[#{Time.new}]#{Color.reset} #{message}"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end