asca 0.1.2 → 1.0.0

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
+ #https://github.com/httprb/http/wiki
34
35
  spec.add_dependency "http"
35
36
  spec.add_dependency 'jwt'
36
37
  end
data/exe/asca CHANGED
@@ -10,31 +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', 'version, 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
20
21
  end
21
- opts.on('-v', '--version', 'Get current version') do |value|
22
- options[:action] = 'version'
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'
23
41
  end
24
42
  end.parse!
25
43
 
26
- case options[:action]
27
- when 'version'
28
- Asca::Log.info(Asca::VERSION)
29
- when 'profile-download'
30
- Asca::Profiles.download_profile options[:profile_name]
31
- when 'profile-install'
32
- Asca::Profiles.install_profile options[:profile_name]
33
- else
34
- if options[:action]
35
- puts "Unsupported action: " + options[:action]
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
36
57
  else
37
- puts Asca::Log.error('Wrong parameters')
58
+ Asca::Tools::Log.error("Unsupported tool name: #{tool_name}")
38
59
  end
39
-
40
- 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)
78
+ else
79
+ puts Asca::Tools::Log.error('Wrong parameters')
80
+ end
@@ -1,9 +1,15 @@
1
1
  require "asca/version"
2
- require "asca/log.rb"
3
2
  require "asca/consts.rb"
4
- require "asca/configuration.rb"
5
- require "asca/token"
6
- 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"
7
13
 
8
14
  module Asca
9
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,160 @@
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
+ `open #{profile_file_path}`
40
+ end
41
+
42
+ # notion: bundle_id is not bundle identifier and device id is udid。They are the corresponding api id.
43
+ def create_new_profile(options = {})
44
+ if !options[:name]
45
+ Asca::Tools::Asca::Tools::Log.error('No profile name specified')
46
+ return false
47
+ end
48
+ if !options[:type]
49
+ Asca::Tools::Asca::Tools::Log.error('No type specified')
50
+ return false
51
+ end
52
+ if !options[:bundle_id]
53
+ Asca::Tools::Asca::Tools::Log.error('No bundle id specfied')
54
+ return false
55
+ end
56
+ if !options[:certificate_ids] || options[:certificate_ids].length == 0
57
+ Asca::Tools::Asca::Tools::Log.error('No certificate id specified')
58
+ return false
59
+ end
60
+
61
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).post(URI_PROFILES, :json => { "data" => {
62
+ "type" => "profiles",
63
+ "attributes" => {
64
+ "name" => options[:name],
65
+ "profileType" => options[:type],
66
+ },
67
+ "relationships" => {
68
+ "bundleId" => {
69
+ "data" => {
70
+ "type" => "bundleIds",
71
+ "id" => options[:bundle_id],
72
+ }
73
+ },
74
+ "certificates" => {
75
+ "data" => options[:certificate_ids].map { |certificate_id| { "type" => "certificates", "id" => certificate_id }}
76
+ },
77
+ "devices" => {
78
+ "data" => options[:device_ids] ? options[:device_ids].map { |device_id| { "type" => "devices", "id" => device_id } } : nil
79
+ },
80
+ }
81
+ }})
82
+ if response.status.success?
83
+ return true
84
+ else
85
+ puts response.body
86
+ return false
87
+ end
88
+ end
89
+
90
+ def delete_profile(options = {})
91
+ # query profile id
92
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(URI_PROFILES, :params => { 'filter[name]' => options[:name] })
93
+ if response.status.success?
94
+ responseObj = JSON.parse(response.body)
95
+ queried_profile_list = responseObj["data"]
96
+ if queried_profile_list.length() > 0
97
+ profile_id = queried_profile_list[0]["id"]
98
+ end
99
+ else
100
+ Asca::Tools::Log.error(response.body)
101
+ return
102
+ end
103
+ if !profile_id
104
+ puts "No profile named #{options[:name]} found!"
105
+ return
106
+ end
107
+
108
+ # delete profile
109
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).delete(URI_PROFILES + "/#{profile_id}")
110
+ if response.status.success?
111
+ Asca::Tools::Log.info("Profile named #{options[:name]} deleted successfully!")
112
+ else
113
+ puts response.body
114
+ end
115
+ return response.status.success?
116
+ end
117
+
118
+ # 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.
119
+ def update_profile(options = {})
120
+ # query profile info
121
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(URI_PROFILES, :params => { 'filter[name]' => options[:name] })
122
+ if response.status.success?
123
+ responseObj = JSON.parse(response.body)
124
+ queried_profile_list = responseObj["data"]
125
+ if queried_profile_list.length() > 0
126
+ profile = queried_profile_list[0]
127
+ end
128
+ else
129
+ Asca::Tools::Log.error(response.body)
130
+ return
131
+ end
132
+
133
+ if !profile
134
+ Asca::Tools::Log.error("No profile named #{options[:name]} found")
135
+ return
136
+ end
137
+ # create new profile
138
+ profile_type = profile["attributes"]["profileType"]
139
+
140
+ # get bundle id
141
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(profile["relationships"]["bundleId"]["links"]["self"])
142
+ bundle_id = JSON.parse(response.body)["data"]["id"]
143
+ response = HTTP.auth('Bearer ' + Asca::Tools::Token.new_token).get(profile["relationships"]["certificates"]["links"]["self"])
144
+ certificate_ids = JSON.parse(response.body)["data"].map { |cer| cer["id"] }
145
+
146
+ device_ids = Asca::REST::Provisioning::Devices.list_devices.map { |device|
147
+ device["id"]
148
+ }
149
+
150
+ # delete old prifile
151
+ delete_profile :name => options[:name]
152
+
153
+ create_new_profile :name => options[:name], :type => profile_type, :bundle_id => bundle_id, :device_ids => device_ids, :certificate_ids => certificate_ids
154
+ return true
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
160
+ 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