fastlane-plugin-shuttle 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +72 -0
- data/lib/fastlane/plugin/shuttle.rb +16 -0
- data/lib/fastlane/plugin/shuttle/actions/shuttle_action.rb +138 -0
- data/lib/fastlane/plugin/shuttle/helper/app_environment_selector.rb +118 -0
- data/lib/fastlane/plugin/shuttle/helper/shuttle_helper.rb +300 -0
- data/lib/fastlane/plugin/shuttle/version.rb +5 -0
- metadata +190 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 602fcbb57374635eb54413662526bb1cbb5eb92e
|
4
|
+
data.tar.gz: 1337002082164db30a5e3dfe6a1edbd6d32497b7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac11b3760899ffb20832691a6e1b61967b93e74ad07d950a219298fadfd1542134eaf0bc15629630c059544c53c94f7f301ded0488ce5f8cf75d5721ff8fdfc9
|
7
|
+
data.tar.gz: cce1ed5253fbae930f1b660403f6e6b6325eaec4b71ab2846641d09dde957eade498f239c62f124ab7552069f54f1a43a70ff8a3b1ae025c91a709f421f89680
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Frédéric Ruaudel <fred@h2g.io>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Shuttle `fastlane` plugin
|
2
|
+
|
3
|
+
[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-shuttle) [![Gem Version](https://badge.fury.io/rb/fastlane-plugin-shuttle.svg)](https://badge.fury.io/rb/fastlane-plugin-shuttle)
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-shuttle`, add it to your project by running:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
fastlane add_plugin shuttle
|
11
|
+
```
|
12
|
+
|
13
|
+
## About Shuttle
|
14
|
+
|
15
|
+
Publish your builds on your [Shuttle.tools](https://shuttle.tools) instance
|
16
|
+
|
17
|
+
This plugin provides a `shuttle` action which allows you to upload and distribute your apps to your testers via your Shuttle instance interface.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
To get started, first, [obtain an API access token](https://docs.shuttle.tools/api-access-token/) in your Shuttle instance admin section. The API Access Token is used to authenticate with the Shuttle API in each call.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
url = shuttle(
|
25
|
+
access_token: <shuttle access token>,
|
26
|
+
package_path: <path to your IPA or APK binary file>,
|
27
|
+
release_name: <release name displayed in shuttle>,
|
28
|
+
release_notes: <release notes>,
|
29
|
+
base_url: "https://<your instance name>.shuttle.tools/")
|
30
|
+
```
|
31
|
+
|
32
|
+
The action parameters `access_token` can be omitted when its value is [set as environment variables](https://docs.fastlane.tools/advanced/#environment-variables). Below a list of all available environment variables:
|
33
|
+
|
34
|
+
- `SHUTTLE_ACCESS_TOKEN` - API Access Token for Shuttle API
|
35
|
+
- `SHUTTLE_BASE_URL` - Shuttle instance URL (eg. https://<your instance name>.shuttle.tools/)
|
36
|
+
- `SHUTTLE_RELEASE_NAME` - The name of the release (eg. MyApp v3)
|
37
|
+
- `SHUTTLE_PACKAGE_PATH` - Build release path for android or ios build (if not provided, it'll check in shared values `GRADLE_APK_OUTPUT_PATH` or `IPA_OUTPUT_PATH`)
|
38
|
+
- `SHUTTLE_ENV_ID` - The uniq ID of the app's environment you want to publish the build to (if not provided, it will try to guess it or ask to select/create it interactively then display the value so you can set it definitively)
|
39
|
+
- `SHUTTLE_RELEASE_NOTES` - Release notes
|
40
|
+
|
41
|
+
## Example
|
42
|
+
|
43
|
+
Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
|
44
|
+
|
45
|
+
## Run tests for this plugin
|
46
|
+
|
47
|
+
To run both the tests, and code style validation, run
|
48
|
+
|
49
|
+
```
|
50
|
+
rake
|
51
|
+
```
|
52
|
+
|
53
|
+
To automatically fix many of the styling issues, use
|
54
|
+
```
|
55
|
+
rubocop -a
|
56
|
+
```
|
57
|
+
|
58
|
+
## Issues and Feedback
|
59
|
+
|
60
|
+
For any other issues and feedback about this plugin, please submit it to this repository.
|
61
|
+
|
62
|
+
## Troubleshooting
|
63
|
+
|
64
|
+
If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
|
65
|
+
|
66
|
+
## Using _fastlane_ Plugins
|
67
|
+
|
68
|
+
For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
|
69
|
+
|
70
|
+
## About _fastlane_
|
71
|
+
|
72
|
+
_fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'fastlane/plugin/shuttle/version'
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
module Shuttle
|
5
|
+
# Return all .rb files inside the "actions" and "helper" directory
|
6
|
+
def self.all_classes
|
7
|
+
Dir[File.expand_path('**/{actions,helper}/*.rb', File.dirname(__FILE__))]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# By default we want to import all available actions and helpers
|
13
|
+
# A plugin can contain any number of actions and plugins
|
14
|
+
Fastlane::Shuttle.all_classes.each do |current|
|
15
|
+
require current
|
16
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'fastlane/action'
|
2
|
+
require_relative '../helper/shuttle_helper'
|
3
|
+
require_relative '../helper/app_environment_selector'
|
4
|
+
require 'faraday'
|
5
|
+
require 'json'
|
6
|
+
require 'app-info'
|
7
|
+
require 'terminal-table'
|
8
|
+
|
9
|
+
ShuttleInstance = Struct.new(:base_url, :access_token)
|
10
|
+
ShuttleApp = Struct.new(:id, :name, :platform_id, :path)
|
11
|
+
ShuttleEnvironment = Struct.new(:id, :name, :package_id, :app_id, :versioning_id, :path)
|
12
|
+
ShuttleBuild = Struct.new(:id)
|
13
|
+
AppEnvironment = Struct.new(:shuttle_app, :shuttle_environment)
|
14
|
+
PackageInfo = Struct.new(:id, :name, :path, :platform_id, :release_version, :build_version)
|
15
|
+
ReleaseInfo = Struct.new(:name, :notes, :build, :environment, :commit_id)
|
16
|
+
|
17
|
+
module Fastlane
|
18
|
+
module Actions
|
19
|
+
module SharedValues
|
20
|
+
SHUTTLE_DOWNLOAD_LINK = :SHUTTLE_DOWNLOAD_LINK
|
21
|
+
end
|
22
|
+
|
23
|
+
class ShuttleAction < Action
|
24
|
+
def self.run(params)
|
25
|
+
helper = Helper::ShuttleHelper
|
26
|
+
selector = Helper::AppEnvironmentSelector
|
27
|
+
shuttle_instance = helper.get_shuttle_instance(params)
|
28
|
+
package_info = helper.get_app_info(params)
|
29
|
+
|
30
|
+
UI.message("Uploading #{package_info.platform_id} package #{package_info.path} with ID #{package_info.id}…")
|
31
|
+
|
32
|
+
app_environment = selector.get_app_environment(shuttle_instance, package_info, params)
|
33
|
+
|
34
|
+
release = helper.get_release_info(params, app_environment, package_info)
|
35
|
+
|
36
|
+
helper.print_summary_table(shuttle_instance, app_environment, package_info, release)
|
37
|
+
|
38
|
+
release.build = helper.upload_build(shuttle_instance, package_info, app_environment.shuttle_app.id)
|
39
|
+
|
40
|
+
helper.create_release(shuttle_instance, release)
|
41
|
+
|
42
|
+
download_url = helper.download_url(shuttle_instance, app_environment, package_info)
|
43
|
+
Actions.lane_context[SharedValues::SHUTTLE_DOWNLOAD_LINK] = download_url
|
44
|
+
|
45
|
+
return download_url
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.description
|
49
|
+
"Publish your builds on [Shuttle.tools](https://www.shuttle.tools)"
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.authors
|
53
|
+
["Frédéric Ruaudel <fred@h2g.io>"]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.return_value
|
57
|
+
# If your method provides a return value, you can describe here what it does
|
58
|
+
"Shuttle download link"
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.details
|
62
|
+
# Optional:
|
63
|
+
[
|
64
|
+
"If you don't know which `env_id` to set, just run the action interactively without `env_id` parameter to force the plugin to fetch available info from your instance or give you the opportunity to create any needed app and environment that would be missing.",
|
65
|
+
"Once done, you will get the associated `env_id` in the _Shuttle upload info summary_ table at the end of the script execution. Just add it in your action parameter to make it works reliably next time including in your CI non-interactive environment"
|
66
|
+
].join("\n")
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.output
|
70
|
+
[
|
71
|
+
['SHUTTLE_DOWNLOAD_LINK', 'The newly generated download link for this build']
|
72
|
+
]
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.available_options
|
76
|
+
[
|
77
|
+
FastlaneCore::ConfigItem.new(key: :package_path,
|
78
|
+
env_name: "SHUTTLE_PACKAGE_PATH",
|
79
|
+
description: "The path to the new app you want to upload to Shuttle ( if not provided, it'll check in shared values GRADLE_APK_OUTPUT_PATH or IPA_OUTPUT_PATH)",
|
80
|
+
optional: true,
|
81
|
+
type: String),
|
82
|
+
FastlaneCore::ConfigItem.new(key: :base_url,
|
83
|
+
env_name: "SHUTTLE_BASE_URL",
|
84
|
+
description: "The base url of your Shuttle instance",
|
85
|
+
optional: false,
|
86
|
+
type: String),
|
87
|
+
FastlaneCore::ConfigItem.new(key: :access_token,
|
88
|
+
env_name: "SHUTTLE_ACCESS_TOKEN",
|
89
|
+
description: "The access token of your account on Shuttle",
|
90
|
+
optional: false,
|
91
|
+
type: String),
|
92
|
+
FastlaneCore::ConfigItem.new(key: :release_name,
|
93
|
+
env_name: "SHUTTLE_RELEASE_NAME",
|
94
|
+
description: "The name of the release (eg. MyApp v3)",
|
95
|
+
optional: true,
|
96
|
+
type: String),
|
97
|
+
FastlaneCore::ConfigItem.new(key: :release_notes,
|
98
|
+
env_name: "SHUTTLE_RELEASE_NOTES",
|
99
|
+
description: "The release notes of the release (eg. Bug fixes)",
|
100
|
+
optional: true,
|
101
|
+
default_value: "Bug fixes and improvements",
|
102
|
+
type: String),
|
103
|
+
FastlaneCore::ConfigItem.new(key: :env_id,
|
104
|
+
env_name: "SHUTTLE_ENV_ID",
|
105
|
+
description: "The uniq ID of the app's environment you want to publish the build to (if not provided, it will try to guess it or ask to select/create it interactively then display the value so you can set it definitively)",
|
106
|
+
optional: true,
|
107
|
+
type: String),
|
108
|
+
]
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.example_code
|
112
|
+
[
|
113
|
+
'download_url = shuttle(
|
114
|
+
access_token: "...",
|
115
|
+
base_url: "https://myInstance.shuttle.tools",
|
116
|
+
package_path: "./app.ipa"
|
117
|
+
)',
|
118
|
+
'shuttle(
|
119
|
+
access_token: "...",
|
120
|
+
base_url: "https://myInstance.shuttle.tools",
|
121
|
+
package_path: "./app.ipa",
|
122
|
+
env_id: "UD6VCR-2X7TME-XSMZW6-MNXIR7",
|
123
|
+
release_name: "My App v5.0-1",
|
124
|
+
release_notes: "Changelog"
|
125
|
+
)'
|
126
|
+
]
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.is_supported?(platform)
|
130
|
+
# Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
|
131
|
+
# See: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform
|
132
|
+
#
|
133
|
+
# [:ios, :mac, :android].include?(platform)
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'fastlane_core/ui/ui'
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
5
|
+
|
6
|
+
module Helper
|
7
|
+
class AppEnvironmentSelector
|
8
|
+
# class methods that you define here become available in your action
|
9
|
+
# as `Helper::ShuttleHelper.your_method`
|
10
|
+
#
|
11
|
+
def self.get_app_environment(shuttle_instance, package_info, params)
|
12
|
+
helper = Helper::ShuttleHelper
|
13
|
+
|
14
|
+
app_environment = self.get_app_env_from_params(shuttle_instance, package_info, params, helper)
|
15
|
+
return app_environment unless app_environment.nil?
|
16
|
+
|
17
|
+
all_environments = helper.get_environments(shuttle_instance)
|
18
|
+
|
19
|
+
app_environment = nil
|
20
|
+
environments = all_environments.select do |env|
|
21
|
+
env.package_id == package_info.id
|
22
|
+
end
|
23
|
+
|
24
|
+
app_environments = helper.get_app_environments(shuttle_instance, environments).select do |app_env|
|
25
|
+
app_env.shuttle_app.platform_id == package_info.platform_id
|
26
|
+
end
|
27
|
+
|
28
|
+
if app_environments.empty?
|
29
|
+
app = self.get_app_interactive(shuttle_instance, package_info, helper)
|
30
|
+
env = self.get_env_interactive(shuttle_instance, app, package_info, helper)
|
31
|
+
app_environment = AppEnvironment.new(app, env)
|
32
|
+
else
|
33
|
+
if app_environments.count == 1
|
34
|
+
app_environment = app_environments[0]
|
35
|
+
else
|
36
|
+
app_environment = self.desambiguate_app_environment(app_environments, package_info, helper)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
return app_environment
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.get_app_env_from_params(shuttle_instance, package_info, params, helper)
|
44
|
+
env_id = params[:env_id]
|
45
|
+
return nil if env_id.to_s.empty?
|
46
|
+
environment = helper.get_environment(shuttle_instance, env_id)
|
47
|
+
app = helper.get_app(shuttle_instance, environment.app_id)
|
48
|
+
|
49
|
+
return AppEnvironment.new(app, environment) if environment.package_id == package_info.id && app.platform_id == package_info.platform_id
|
50
|
+
|
51
|
+
UI.important("App #{app.name} doesn't match the given build platform #{package_info.platform_id}, please check that #{env_id} is the correct environment ID.") unless app.platform_id == package_info.platform_id
|
52
|
+
UI.important("Environement #{environment.name} with ID #{env_id} doesn't match the build's package ID #{package_info.id}, please check that you set the correct environment ID") unless environment.package_id == package_info.id
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.desambiguate_app_environment(app_environments, package_info, helper)
|
57
|
+
options = app_environments.map do |app_env|
|
58
|
+
"#{app_env.shuttle_app.name} (#{app_env.shuttle_environment.name})"
|
59
|
+
end
|
60
|
+
choice_index = helper.prompt_choices(
|
61
|
+
"Can't guess which app and environment to use, please choose the correct one:",
|
62
|
+
options,
|
63
|
+
"Too many environments with package id #{package_info.id} for #{package_info.platform_id}"
|
64
|
+
)
|
65
|
+
app_environment = app_environments[choice_index]
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.get_app_interactive(shuttle_instance, package_info, helper)
|
69
|
+
apps = helper.get_apps(shuttle_instance).select do |app|
|
70
|
+
app.platform_id == package_info.platform_id
|
71
|
+
end
|
72
|
+
options = apps.map do |app|
|
73
|
+
"#{app.name}"
|
74
|
+
end
|
75
|
+
create_new_option = "Create a new one…"
|
76
|
+
choice_index = helper.prompt_choices(
|
77
|
+
"Can't guess which app to use, please choose the correct one:",
|
78
|
+
options << create_new_option,
|
79
|
+
"No environments configured for package id #{package_info.id}"
|
80
|
+
)
|
81
|
+
return self.create_app_interactive(shuttle_instance, package_info, helper) if options[choice_index] == create_new_option
|
82
|
+
return apps[choice_index]
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.create_app_interactive(shuttle_instance, package_info, helper)
|
86
|
+
app_name = UI.input("app name (default: #{package_info.name}): ")
|
87
|
+
app_name = package_info.name if app_name.to_s.empty?
|
88
|
+
helper.create_app(shuttle_instance, app_name, package_info.platform_id)
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.get_env_interactive(shuttle_instance, app, package_info, helper)
|
92
|
+
environments = helper.get_environments_for_app(shuttle_instance, app).select do |env|
|
93
|
+
env.package_id == package_info.id
|
94
|
+
end
|
95
|
+
options = environments.map do |env|
|
96
|
+
"#{env.name}"
|
97
|
+
end
|
98
|
+
create_new_option = "Create a new one…"
|
99
|
+
choice_index = helper.prompt_choices(
|
100
|
+
"Can't guess which #{app.name}'s environment to use, please choose the correct one:",
|
101
|
+
options << create_new_option,
|
102
|
+
"No environments configured for package id #{package_info.id}"
|
103
|
+
)
|
104
|
+
return self.create_environment_interactive(shuttle_instance, app, package_info, helper) if options[choice_index] == create_new_option
|
105
|
+
return environments[choice_index]
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.create_environment_interactive(shuttle_instance, app, package_info, helper)
|
109
|
+
env_name = UI.input("environment name: ")
|
110
|
+
versioning_id_choices = ["version_only", "version_and_build"]
|
111
|
+
choice_index = helper.prompt_choices("environment version scheme:", versioning_id_choices, "interactive mode needed")
|
112
|
+
app_name = package_info.name if app_name.to_s.empty?
|
113
|
+
helper.create_environment(shuttle_instance, env_name, versioning_id_choices[choice_index], app.id, package_info.id)
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
require 'fastlane_core/ui/ui'
|
2
|
+
require 'fastlane/action'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Fastlane
|
6
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
7
|
+
|
8
|
+
module Helper
|
9
|
+
class ShuttleHelper
|
10
|
+
# class methods that you define here become available in your action
|
11
|
+
# as `Helper::ShuttleHelper.your_method`
|
12
|
+
#
|
13
|
+
def self.get_shuttle_instance(params)
|
14
|
+
shuttle_base_url = params[:base_url]
|
15
|
+
shuttle_access_token = params[:access_token]
|
16
|
+
ShuttleInstance.new(shuttle_base_url, shuttle_access_token)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.get_app_info(params)
|
20
|
+
package_path = params[:package_path] unless params[:package_path].to_s.empty?
|
21
|
+
package_path = Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::IPA_OUTPUT_PATH] if package_path.to_s.empty?
|
22
|
+
package_path = Fastlane::Actions.lane_context[Fastlane::Actions::SharedValues::GRADLE_APK_OUTPUT_PATH] if package_path.to_s.empty?
|
23
|
+
UI.abort_with_message!("No Package file found") if package_path.to_s.empty?
|
24
|
+
UI.abort_with_message!("Package at path #{package_path} does not exist") unless File.exist?(package_path)
|
25
|
+
app_info = ::AppInfo.parse(package_path)
|
26
|
+
PackageInfo.new(app_info.identifier, app_info.name, package_path, app_info.os.downcase, app_info.release_version, app_info.build_version)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.get_release_name(params, app_environment, package_info)
|
30
|
+
return params[:release_name] unless params[:release_name].to_s.empty?
|
31
|
+
release_name = "#{app_environment.shuttle_app.name} v#{package_info.release_version}"
|
32
|
+
if app_environment.shuttle_environment.versioning_id == "version_and_build"
|
33
|
+
return "#{release_name}-#{package_info.build_version}"
|
34
|
+
end
|
35
|
+
return release_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.get_release_info(params, app_environment, package_info)
|
39
|
+
release_name = self.get_release_name(params, app_environment, package_info)
|
40
|
+
commit_id = Helper.backticks("git show --format='%H' --quiet").chomp
|
41
|
+
ReleaseInfo.new(release_name, params[:release_notes], nil, app_environment.shuttle_environment, commit_id)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.connection(shuttle_instance, endpoint, is_multipart = false)
|
45
|
+
return Faraday.new(url: "#{shuttle_instance.base_url}/api#{endpoint}") do |builder|
|
46
|
+
# builder.response :logger, Logger.new(STDOUT), bodies: true
|
47
|
+
builder.headers["Authorization"] = "Bearer #{shuttle_instance.access_token}"
|
48
|
+
builder.headers["Accept"] = "application/vnd.api+json"
|
49
|
+
if is_multipart
|
50
|
+
builder.request :multipart
|
51
|
+
else
|
52
|
+
builder.headers["Content-Type"] = "application/vnd.api+json"
|
53
|
+
end
|
54
|
+
builder.adapter :net_http
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.get(shuttle_instance, endpoint, debug=false)
|
59
|
+
connection = self.connection(shuttle_instance, endpoint)
|
60
|
+
response = connection.get()
|
61
|
+
case response.status
|
62
|
+
when 200...300
|
63
|
+
data = JSON.parse(response.body)
|
64
|
+
UI.message("Debug: #{JSON.pretty_generate(data["data"])}\n") if debug == true
|
65
|
+
data["data"]
|
66
|
+
else
|
67
|
+
UI.abort_with_message!("Error #{response.status.to_s} occured while calling endpoint #{endpoint}")
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.environment_from_json(json_env)
|
73
|
+
attrb = json_env["attributes"]
|
74
|
+
ShuttleEnvironment.new(
|
75
|
+
json_env["id"],
|
76
|
+
attrb["name"],
|
77
|
+
attrb["package_id"],
|
78
|
+
json_env["relationships"]["app"]["data"]["id"],
|
79
|
+
attrb["versioning_id"],
|
80
|
+
attrb["path"]
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.get_environments(shuttle_instance)
|
85
|
+
self.get(shuttle_instance, '/environments').map do |json_env|
|
86
|
+
self.environment_from_json(json_env)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.get_environment(shuttle_instance, env_id)
|
91
|
+
json_env = self.get(shuttle_instance, "/environments/#{env_id}")
|
92
|
+
self.environment_from_json(json_env)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.get_environments_for_app(shuttle_instance, app)
|
96
|
+
self.get(shuttle_instance, "/apps/#{app.id}/environments").map do |json_env|
|
97
|
+
self.environment_from_json(json_env)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.create_environment(shuttle_instance, name, versioning_id, app_id, package_id)
|
102
|
+
body = JSON.generate({
|
103
|
+
data: {
|
104
|
+
type: "environments",
|
105
|
+
attributes: {
|
106
|
+
name: name,
|
107
|
+
path: name.downcase,
|
108
|
+
package_id: package_id,
|
109
|
+
versioning_id: versioning_id
|
110
|
+
},
|
111
|
+
relationships: {
|
112
|
+
app: {
|
113
|
+
data: {
|
114
|
+
id: app_id,
|
115
|
+
type: "apps"
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
})
|
121
|
+
json_env = self.post(shuttle_instance, "/environments", body)
|
122
|
+
self.environment_from_json(json_env)
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.app_from_json(json_app)
|
126
|
+
json_app_attrb = json_app["attributes"]
|
127
|
+
ShuttleApp.new(
|
128
|
+
json_app["id"],
|
129
|
+
json_app_attrb["name"],
|
130
|
+
json_app_attrb["platform_id"],
|
131
|
+
json_app_attrb["path"]
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.get_apps(shuttle_instance)
|
136
|
+
self.get(shuttle_instance, "/apps/").map do |json_app|
|
137
|
+
self.app_from_json(json_app)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.get_app(shuttle_instance, app_id)
|
142
|
+
json_app = self.get(shuttle_instance, "/apps/#{app_id}")
|
143
|
+
self.app_from_json(json_app)
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.create_app(shuttle_instance, app_name, app_platform)
|
147
|
+
app_path = "#{app_name.downcase}-#{app_platform}"
|
148
|
+
body = JSON.generate({
|
149
|
+
data: {
|
150
|
+
type: "apps",
|
151
|
+
attributes: {
|
152
|
+
name: app_name,
|
153
|
+
path: app_path,
|
154
|
+
platform_id: app_platform
|
155
|
+
}
|
156
|
+
}
|
157
|
+
})
|
158
|
+
json_app = self.post(shuttle_instance, "/apps", body)
|
159
|
+
self.app_from_json(json_app)
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.get_app_environments(shuttle_instance, environments)
|
163
|
+
apps = environments.map do |env|
|
164
|
+
self.get_app(shuttle_instance, env.app_id)
|
165
|
+
end
|
166
|
+
|
167
|
+
apps.zip(environments).map do |app_env|
|
168
|
+
AppEnvironment.new(
|
169
|
+
app_env[0],
|
170
|
+
app_env[1]
|
171
|
+
)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.post(shuttle_instance, endpoint, body, is_multipart: false, debug: false)
|
176
|
+
connection = self.connection(shuttle_instance, endpoint, is_multipart)
|
177
|
+
response = connection.post do |req|
|
178
|
+
req.body = body
|
179
|
+
end
|
180
|
+
case response.status
|
181
|
+
when 200...300
|
182
|
+
data = JSON.parse response.body
|
183
|
+
UI.message(JSON.pretty_generate(data)) if debug == true
|
184
|
+
data["data"]
|
185
|
+
else
|
186
|
+
self.abort(endpoint, body, response)
|
187
|
+
nil
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.abort(endpoint, body, response)
|
192
|
+
reqBody = JSON.parse body
|
193
|
+
errorBody = JSON.parse response.body
|
194
|
+
case endpoint
|
195
|
+
when "/releases"
|
196
|
+
UI.abort_with_message!("💥 Can't create release for #{reqBody["data"]["attributes"]["title"]}: #{errorBody["errors"][0]["detail"]}")
|
197
|
+
else
|
198
|
+
UI.abort_with_message!("Error #{response.status.to_s} occured while calling endpoint #{endpoint} with body #{body} => #{errorBody["errors"][0]["detail"]}")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.upload_build(shuttle_instance, package_info, app_id)
|
203
|
+
body = {
|
204
|
+
"build[app_id]": app_id,
|
205
|
+
"build[package]": Faraday::UploadIO.new(package_info.path, 'application/octet-stream')
|
206
|
+
}
|
207
|
+
json_build = self.post(shuttle_instance, '/builds', body, is_multipart: true)
|
208
|
+
ShuttleBuild.new(json_build["id"])
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.create_release(shuttle_instance, release)
|
212
|
+
body = JSON.generate({
|
213
|
+
data: {
|
214
|
+
type: "releases",
|
215
|
+
attributes: {
|
216
|
+
title: release.name,
|
217
|
+
notes: release.notes,
|
218
|
+
commit_id: release.commit_id
|
219
|
+
},
|
220
|
+
relationships: {
|
221
|
+
build: {
|
222
|
+
data: {
|
223
|
+
id: release.build.id,
|
224
|
+
type: "builds"
|
225
|
+
}
|
226
|
+
},
|
227
|
+
environment: {
|
228
|
+
data: {
|
229
|
+
id: release.environment.id,
|
230
|
+
type: "environments"
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
}
|
235
|
+
})
|
236
|
+
json_release = self.post(shuttle_instance, "/releases", body)
|
237
|
+
end
|
238
|
+
|
239
|
+
def self.prompt_choices(question, options, nonInteractiveErrorMessage)
|
240
|
+
UI.abort_with_message!(nonInteractiveErrorMessage) unless UI.interactive?
|
241
|
+
abort_option = "None match, abort"
|
242
|
+
user_choice = UI.select question, options << abort_option
|
243
|
+
case user_choice
|
244
|
+
when abort_option
|
245
|
+
UI.user_error!("Aborting…")
|
246
|
+
else
|
247
|
+
choice_index = options.find_index(user_choice)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def self.download_url(shuttle_instance, app_environment, package_info)
|
252
|
+
app = app_environment.shuttle_app
|
253
|
+
env = app_environment.shuttle_environment
|
254
|
+
url_path = File.join(
|
255
|
+
app.path,
|
256
|
+
env.path,
|
257
|
+
package_info.release_version)
|
258
|
+
url_path = File.join(url_path, package_info.build_version) if env.versioning_id == "version_and_build"
|
259
|
+
return URI.join(
|
260
|
+
shuttle_instance.base_url, url_path).to_s
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.print_summary_table(shuttle_instance, app_environment, package_info, release)
|
264
|
+
rows = [
|
265
|
+
'Shuttle Base URL',
|
266
|
+
'Shuttle app name',
|
267
|
+
'Shuttle env name',
|
268
|
+
'Shuttle env ID',
|
269
|
+
'Package path',
|
270
|
+
'Platform',
|
271
|
+
'Package Id',
|
272
|
+
'Release name',
|
273
|
+
'Release version',
|
274
|
+
'Build version',
|
275
|
+
'Release notes',
|
276
|
+
'Commit hash',
|
277
|
+
'Shuttle release URL'
|
278
|
+
].zip([
|
279
|
+
shuttle_instance.base_url,
|
280
|
+
app_environment.shuttle_app.name,
|
281
|
+
app_environment.shuttle_environment.name,
|
282
|
+
app_environment.shuttle_environment.id,
|
283
|
+
package_info.path,
|
284
|
+
package_info.platform_id,
|
285
|
+
package_info.id,
|
286
|
+
release.name,
|
287
|
+
package_info.release_version,
|
288
|
+
package_info.build_version,
|
289
|
+
release.notes,
|
290
|
+
release.commit_id,
|
291
|
+
self.download_url(shuttle_instance, app_environment, package_info)
|
292
|
+
])
|
293
|
+
table = Terminal::Table.new :rows => rows, :title => "Shuttle upload info summary".green
|
294
|
+
puts
|
295
|
+
puts table
|
296
|
+
puts
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
metadata
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fastlane-plugin-shuttle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shuttle Project
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-10-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: app-info
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.0.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec_junit_formatter
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.49.1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.49.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop-require_tools
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: fastlane
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 2.97.0
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 2.97.0
|
153
|
+
description:
|
154
|
+
email: dev@shuttle.tools
|
155
|
+
executables: []
|
156
|
+
extensions: []
|
157
|
+
extra_rdoc_files: []
|
158
|
+
files:
|
159
|
+
- LICENSE
|
160
|
+
- README.md
|
161
|
+
- lib/fastlane/plugin/shuttle.rb
|
162
|
+
- lib/fastlane/plugin/shuttle/actions/shuttle_action.rb
|
163
|
+
- lib/fastlane/plugin/shuttle/helper/app_environment_selector.rb
|
164
|
+
- lib/fastlane/plugin/shuttle/helper/shuttle_helper.rb
|
165
|
+
- lib/fastlane/plugin/shuttle/version.rb
|
166
|
+
homepage: https://github.com/ShuttleProject/fastlane-plugin-shuttle
|
167
|
+
licenses:
|
168
|
+
- MIT
|
169
|
+
metadata: {}
|
170
|
+
post_install_message:
|
171
|
+
rdoc_options: []
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
requirements: []
|
185
|
+
rubyforge_project:
|
186
|
+
rubygems_version: 2.6.13
|
187
|
+
signing_key:
|
188
|
+
specification_version: 4
|
189
|
+
summary: Publish your builds on Shuttle.tools
|
190
|
+
test_files: []
|