fastlane-plugin-mango 1.0.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 +127 -0
- data/lib/fastlane/plugin/mango.rb +16 -0
- data/lib/fastlane/plugin/mango/actions/run_dockerized_task_action.rb +171 -0
- data/lib/fastlane/plugin/mango/helper/mango_helper.rb +310 -0
- data/lib/fastlane/plugin/mango/version.rb +5 -0
- metadata +175 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 75221491bc8b2b1f16e769b0208203efbd55ac56a5577a632e522b1caa5348ba
|
4
|
+
data.tar.gz: facf07278586a34970216bf5bef7984bdd0e2cebb9854ae2f75e84d62693f310
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: be523d5eaaf2475812c9aac88e1a82a8135b06649228d0c6684d3201064d4fceeaf28551dfcef541a5b820c8bb27d11bee76bfdb99e78a05600240585cf2c818
|
7
|
+
data.tar.gz: f095125f165f15695d06fb6d11589c57a61be876fb54bf1f9201919289ac93532c1d1f0eafd74af29577f062ab6a210539ec9a2b60fd3b2e22662a57faa1a9b4
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 XING SE
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
|
2
|
+
# Mango - Fastlane plugin
|
3
|
+
|
4
|
+
[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-mango)
|
5
|
+
|
6
|
+
A fastlane plugin that runs Android tasks on a specified [Docker](https://www.docker.com/) image
|
7
|
+
|
8
|
+
<img src="assets/mango_logo.png" alt="Mango Logo" width="256px" height="256px"/>
|
9
|
+
|
10
|
+
Running Android tests, especially [Espresso](https://developer.android.com/training/testing/espresso/) on a continuous integration environment like [Jenkins](https://jenkins.io/) can be a hassle. You need to boot, manage and destroy an [Android Virtual Device (AVD)](https://developer.android.com/studio/run/managing-avds) during the test run. This is why we, the mobile releases team at [XING](https://www.xing/com), built this plugin. It spins up a specified [Docker](https://www.docker.com/) image and runs a given task on it.
|
11
|
+
|
12
|
+
Another requirement we had was to run on a clean environment, which is why Mango also helps us to run our unit test and more. For an example check out the [example `Fastfile`](sample-android/fastlane/Fastfile)
|
13
|
+
|
14
|
+
## Prerequisites
|
15
|
+
|
16
|
+
In order to use this plugin you will need to to have [Docker](https://www.docker.com/) installed on the machine you are using.
|
17
|
+
Documentation on how to set it up properly on a Linux (ubuntu) machine can be found [here](docs/docker-linux.md).
|
18
|
+
|
19
|
+
If you need an Android Virtual Device (AVD) to run your tests (for example Espresso, Calabash or Appium), it's necessary to check that your CPU supports kvm virtualisation. We already experienced, that it doesn't fully work on macOS and are using Linux for that.
|
20
|
+
|
21
|
+
## Getting Started
|
22
|
+
|
23
|
+
This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-mango`, add it to your project by running:
|
24
|
+
|
25
|
+
```bash
|
26
|
+
fastlane add_plugin mango
|
27
|
+
```
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
After installing this plugin you have access to one additional action (`mango`) in your `Fastfile`.
|
32
|
+
|
33
|
+
So a lane in your `Fastfile` could look similar to this:
|
34
|
+
```ruby
|
35
|
+
desc "Run espresso tests on docker images"
|
36
|
+
lane :Espresso_Tests do |options|
|
37
|
+
mango(
|
38
|
+
container_name: "espresso_container",
|
39
|
+
docker_image: "thyrlian/android-sdk:latest",
|
40
|
+
container_timeout: 120,
|
41
|
+
android_task: "./gradlew connectedAndroidTest",
|
42
|
+
post_actions: "adb logcat -d > logcat.txt"
|
43
|
+
)
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
Now you can call this new lane by calling `bundle exec fastlane Espresso_Tests`.
|
48
|
+
|
49
|
+
The Plugin will start up the given `docker_image`, execute the given `android_task` and afterwards execute the `post_actions`.
|
50
|
+
|
51
|
+
Of
|
52
|
+
|
53
|
+
## Configuration options
|
54
|
+
The `mango` action has plenty of options to configure it.
|
55
|
+
|
56
|
+
| Option | Description | Default value | Optional | Type |
|
57
|
+
| - |:-|-:| :-:| -:|
|
58
|
+
| `container_name`| Name of the docker container. Will be generated randomly if not defined. | - | ✅ | `String` |
|
59
|
+
| `no_vnc_port` | Port to redirect noVNC. To observe your docker container from your browser while it's running. | 6080 | ❌ | `Integer` |
|
60
|
+
| `device_name` | Name of the Android device. | Nexus 5X | ❌ | `String` |
|
61
|
+
| `emulator_name`| Name of the Android emulator. | emulator-5554 | ❌ | `String` |
|
62
|
+
| `docker_image` | Name of the Docker image, that should be started and used to run your tasks. | butomo1989/docker-android-x86-5.1.1 | ❌ | `String` |
|
63
|
+
| `container_timeout` | Timeout (in seconds) to get a healthy docker container. Depending on your `docker_image` it may take some time until it's started up and ready to use. | 450 (this equals 7.5 minutes) | ❌ | `Integer` |
|
64
|
+
| `android_task` | A generic Android task you want to execute. | - | ❌ | `String` |
|
65
|
+
| `sdk_path` | The path to your Android sdk directory. | `ANDROID_HOME` environment variable | ✅ | `String` |
|
66
|
+
| `port_factor` | Base for calculating a unique port for noVNC. We recommend to use the `EXECUTOR_NUMBER` from your Jenkins environment. | - | ✅ | `String` |
|
67
|
+
| `workspace_dir` | Path to the workspace to execute commands. If you want to execute your `android_task` from a different directory you have to specify `workspace_dir`. | `/root/tests/` | ✅ | `String` |
|
68
|
+
| `maximal_run_time` | Defines the maximal time of your test run in minutes. This can be helpful if you want to kill hanging processes automatically after a certain time. | 60 | ✅ | `Integer` |
|
69
|
+
| `bundle_install` | Defines if the Android task must execute `bundle install` before running a build. This is useful if you want to execute non-gradle tasks. Like [Calabash](https://github.com/calabash/calabash-android), where you need to update/install your [Ruby](https://www.ruby-lang.org) dependencies. | `true` | ✅ | `Boolean` |
|
70
|
+
| `is_running_on_emulator` | Define if container boots up an emulator instance inside of it. This will trigger configuration of noVNC, assign necessary ports etc. | `true` | ✅ | `Boolean` |
|
71
|
+
| `post_actions` | Actions that will be executed after the main command has been executed. | - | ✅ | `String` |
|
72
|
+
| `pre_action` | Actions that will be executed before the docker image gets pulled | - | ✅ | `String` |
|
73
|
+
| `docker_registry_login` | Command to authenticate yourself in a custom Docker registry | - | ✅ | `String` |
|
74
|
+
| `pull_latest_image` | Defines if you want to pull the `:latest` tag of the given `docker_image` | `false` | ✅ | `Boolean` |
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
🎁 Bug reports and pull requests for new features are most welcome!
|
79
|
+
|
80
|
+
👷🏼 We are looking forward to your pull request, we'd love to help!
|
81
|
+
|
82
|
+
You can help by doing any of the following:
|
83
|
+
|
84
|
+
- Reviewing pull requests
|
85
|
+
- Bringing ideas for new features
|
86
|
+
- Answering questions on issues
|
87
|
+
- Improving documentation
|
88
|
+
|
89
|
+
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant code](http://contributor-covenant.org/) of conduct.
|
90
|
+
|
91
|
+
## Issues and Feedback
|
92
|
+
|
93
|
+
For any other issues and feedback about this plugin, please submit it to this repository.
|
94
|
+
|
95
|
+
## Troubleshooting
|
96
|
+
|
97
|
+
If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
|
98
|
+
|
99
|
+
## License
|
100
|
+
|
101
|
+
The fastlane plugin is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
102
|
+
|
103
|
+
## Example
|
104
|
+
|
105
|
+
Check out the [example `Fastfile`](sample-android/fastlane/Fastfile) to see how to use this plugin.
|
106
|
+
Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
|
107
|
+
|
108
|
+
## Run tests for this plugin
|
109
|
+
|
110
|
+
To run both the tests and code style validation, run
|
111
|
+
|
112
|
+
```
|
113
|
+
rake
|
114
|
+
```
|
115
|
+
|
116
|
+
To automatically fix many of the styling issues, use
|
117
|
+
```
|
118
|
+
rubocop -a
|
119
|
+
```
|
120
|
+
|
121
|
+
## Using _fastlane_ Plugins
|
122
|
+
|
123
|
+
For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
|
124
|
+
|
125
|
+
## About _fastlane_
|
126
|
+
|
127
|
+
_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/mango/version'
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
module Mango
|
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::Mango.all_classes.each do |current|
|
15
|
+
require current
|
16
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
class RunDockerizedTaskAction < Action
|
4
|
+
def self.run(params)
|
5
|
+
UI.important("The mango plugin is working!")
|
6
|
+
emulator_name = params[:emulator_name]
|
7
|
+
docker_emulator = Fastlane::Helper::MangoHelper.new(params)
|
8
|
+
docker_emulator.setup_container
|
9
|
+
|
10
|
+
failure_buffer_timeout = 5
|
11
|
+
timeout_command = "timeout #{params[:maximal_run_time] - failure_buffer_timeout}m"
|
12
|
+
workspace_dir = params[:workspace_dir]
|
13
|
+
|
14
|
+
android_task = params[:android_task]
|
15
|
+
if android_task
|
16
|
+
UI.success("Starting Android Task.")
|
17
|
+
bundle_install = params[:bundle_install] ? '&& bundle install ' : ''
|
18
|
+
|
19
|
+
docker_emulator.docker_exec("cd #{workspace_dir} #{bundle_install}&& #{timeout_command} #{android_task} || exit 1")
|
20
|
+
end
|
21
|
+
|
22
|
+
ensure
|
23
|
+
post_actions = params[:post_actions]
|
24
|
+
if post_actions
|
25
|
+
docker_emulator&.docker_exec("cd #{workspace_dir} && #{post_actions}")
|
26
|
+
end
|
27
|
+
|
28
|
+
UI.important("Cleaning up #{emulator_name} container")
|
29
|
+
docker_emulator.clean_container if docker_emulator.instance_variable_get('@container')
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.description
|
33
|
+
"Action that runs Android tasks on a specified Docker image"
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.authors
|
37
|
+
["Serghei Moret", "Daniel Hartwich"]
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.return_value
|
41
|
+
# If your method provides a return value, you can describe here what it does
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.is_supported?(platform)
|
45
|
+
platform == :android
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.details
|
49
|
+
# Optional:
|
50
|
+
""
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.available_options
|
54
|
+
[
|
55
|
+
FastlaneCore::ConfigItem.new(key: :container_name,
|
56
|
+
env_name: "CONTAINER_NAME",
|
57
|
+
description: "Name of the docker container. Will be generated randomly if not defined",
|
58
|
+
optional: true,
|
59
|
+
type: String),
|
60
|
+
|
61
|
+
FastlaneCore::ConfigItem.new(key: :no_vnc_port,
|
62
|
+
env_name: "NO_VNC_PORT",
|
63
|
+
description: "Port to redirect noVNC. 6080 by default",
|
64
|
+
default_value: 6080,
|
65
|
+
optional: false,
|
66
|
+
type: Integer),
|
67
|
+
|
68
|
+
FastlaneCore::ConfigItem.new(key: :device_name,
|
69
|
+
env_name: "DEVICE_NAME",
|
70
|
+
description: "Name of the Android device. Nexus 5X by default",
|
71
|
+
default_value: 'Nexus 5X',
|
72
|
+
optional: false,
|
73
|
+
type: String),
|
74
|
+
|
75
|
+
FastlaneCore::ConfigItem.new(key: :emulator_name,
|
76
|
+
env_name: "EMULATOR_NAME",
|
77
|
+
description: "Name of the Android emulator. emulator-5554 by default",
|
78
|
+
default_value: 'emulator-5554',
|
79
|
+
optional: false,
|
80
|
+
type: String),
|
81
|
+
|
82
|
+
FastlaneCore::ConfigItem.new(key: :docker_image,
|
83
|
+
env_name: "DOCKER_IMAGE",
|
84
|
+
description: "Name of the Docker image. butomo1989/docker-android-x86-5.1.1 by default",
|
85
|
+
default_value: 'butomo1989/docker-android-x86-5.1.1',
|
86
|
+
optional: false,
|
87
|
+
type: String),
|
88
|
+
|
89
|
+
FastlaneCore::ConfigItem.new(key: :container_timeout,
|
90
|
+
env_name: "CONTAINER_TIMEOUT",
|
91
|
+
description: "Timeout (in seconds) to get the healthy docker container. 450 (7.5 minutes) by default",
|
92
|
+
default_value: 450,
|
93
|
+
optional: false,
|
94
|
+
type: Integer),
|
95
|
+
|
96
|
+
FastlaneCore::ConfigItem.new(key: :android_task,
|
97
|
+
env_name: "ANDROID TASK",
|
98
|
+
description: "A generic Android task you want to execute",
|
99
|
+
is_string: true,
|
100
|
+
optional: false),
|
101
|
+
|
102
|
+
FastlaneCore::ConfigItem.new(key: :sdk_path,
|
103
|
+
env_name: "SDK_PATH",
|
104
|
+
description: "The path to your Android sdk directory (root). ANDROID_HOME by default",
|
105
|
+
default_value: ENV['ANDROID_HOME'],
|
106
|
+
is_string: true,
|
107
|
+
optional: true),
|
108
|
+
|
109
|
+
FastlaneCore::ConfigItem.new(key: :port_factor,
|
110
|
+
env_name: "PORT_FACTOR",
|
111
|
+
description: "Base for calculating a unique port for noVNC. You can pass EXECUTOR_NUMBER from Jenkins for example, this will be unique and not clash in case of several instances running on the same machine",
|
112
|
+
optional: true,
|
113
|
+
type: String),
|
114
|
+
|
115
|
+
FastlaneCore::ConfigItem.new(key: :workspace_dir,
|
116
|
+
env_name: "WORKSPACE_DIR",
|
117
|
+
default_value: '/root/tests/',
|
118
|
+
description: "Path to the workspace to execute commands",
|
119
|
+
optional: true,
|
120
|
+
type: String),
|
121
|
+
|
122
|
+
FastlaneCore::ConfigItem.new(key: :maximal_run_time,
|
123
|
+
env_name: "MAXIMAL_RUN_TIME",
|
124
|
+
default_value: 60,
|
125
|
+
description: "Defines the maximal time of your test run. Defaults to 60 minutes",
|
126
|
+
optional: true,
|
127
|
+
type: Integer),
|
128
|
+
|
129
|
+
FastlaneCore::ConfigItem.new(key: :bundle_install,
|
130
|
+
env_name: "BUNDLE_INSTALL",
|
131
|
+
default_value: false,
|
132
|
+
description: "Defines if the Android task must execute bundle install before running a build",
|
133
|
+
optional: true,
|
134
|
+
type: Boolean),
|
135
|
+
|
136
|
+
FastlaneCore::ConfigItem.new(key: :is_running_on_emulator,
|
137
|
+
env_name: "IS_RUNNING_ON_EMULATOR",
|
138
|
+
default_value: true,
|
139
|
+
description: "Define if we want to run the emulator in the container",
|
140
|
+
optional: true,
|
141
|
+
type: Boolean),
|
142
|
+
|
143
|
+
FastlaneCore::ConfigItem.new(key: :post_actions,
|
144
|
+
env_name: "POST_ACTIONS",
|
145
|
+
description: "Actions that will be executed after the main command has been executed",
|
146
|
+
is_string: true,
|
147
|
+
optional: true),
|
148
|
+
|
149
|
+
FastlaneCore::ConfigItem.new(key: :pre_action,
|
150
|
+
env_name: "PRE_ACTION",
|
151
|
+
description: "Actions that will be executed before the docker image gets pulled",
|
152
|
+
is_string: true,
|
153
|
+
optional: true),
|
154
|
+
|
155
|
+
FastlaneCore::ConfigItem.new(key: :docker_registry_login,
|
156
|
+
env_name: "DOCKER_REGISTRY_LOGIN",
|
157
|
+
description: "Authenticating yourself to a custom Docker registry",
|
158
|
+
type: String,
|
159
|
+
optional: true),
|
160
|
+
|
161
|
+
FastlaneCore::ConfigItem.new(key: :pull_latest_image,
|
162
|
+
env_name: "PULL_LATEST_IMAGE",
|
163
|
+
description: "Define if you want to pull the latest image",
|
164
|
+
type: Boolean,
|
165
|
+
default_value: false,
|
166
|
+
optional: true)
|
167
|
+
]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
require 'docker'
|
2
|
+
require 'socket'
|
3
|
+
require 'timeout'
|
4
|
+
require 'os'
|
5
|
+
|
6
|
+
module Fastlane
|
7
|
+
module Helper
|
8
|
+
class MangoHelper
|
9
|
+
attr_reader :container_name, :no_vnc_port, :device_name, :docker_image, :timeout, :port_factor, :maximal_run_time, :sleep_interval, :is_running_on_emulator
|
10
|
+
|
11
|
+
def initialize(params)
|
12
|
+
@container_name = params[:container_name]
|
13
|
+
@no_vnc_port = params[:no_vnc_port]
|
14
|
+
@device_name = params[:device_name]
|
15
|
+
@docker_image = params[:docker_image]
|
16
|
+
@timeout = params[:container_timeout]
|
17
|
+
@sdk_path = params[:sdk_path]
|
18
|
+
@port_factor = params[:port_factor].to_i
|
19
|
+
@maximal_run_time = params[:maximal_run_time]
|
20
|
+
@sleep_interval = 5
|
21
|
+
@container = nil
|
22
|
+
@adb_path = adb_path
|
23
|
+
@is_running_on_emulator = params[:is_running_on_emulator]
|
24
|
+
@pre_action = params[:pre_action]
|
25
|
+
@docker_registry_login = params[:docker_registry_login]
|
26
|
+
@pull_latest_image = params[:pull_latest_image]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Setting up the container:
|
30
|
+
# 1. Checks if ports are already allocated and kill the ones that do
|
31
|
+
# 2. Checks if Container we want to create already exist. If it does, restart it and check that the ports are correct
|
32
|
+
# 3. Creates container if there wasn't one already created or the created one has incorrect ports
|
33
|
+
# 4. Finally, waits until container is up and running (Healthy) using timeout specified in params
|
34
|
+
def setup_container
|
35
|
+
assign_unique_vnc_port if port_factor && is_running_on_emulator
|
36
|
+
|
37
|
+
if container_available?
|
38
|
+
@container.stop
|
39
|
+
@container.delete(force: true)
|
40
|
+
end
|
41
|
+
|
42
|
+
handle_ports_allocation if is_running_on_emulator
|
43
|
+
|
44
|
+
execute_pre_action if @pre_action
|
45
|
+
|
46
|
+
pull_from_registry if @pull_latest_image
|
47
|
+
|
48
|
+
# Make sure that network bridge for the current container is not already used
|
49
|
+
disconnect_network_bridge if container_name
|
50
|
+
|
51
|
+
create_container
|
52
|
+
|
53
|
+
begin
|
54
|
+
wait_for_healthy_container false
|
55
|
+
check_emulator_connection if is_running_on_emulator
|
56
|
+
rescue StandardError
|
57
|
+
UI.important("Will retry checking for a healthy docker container after #{sleep_interval} seconds")
|
58
|
+
@container.stop
|
59
|
+
@container.delete(force: true)
|
60
|
+
sleep @sleep_interval
|
61
|
+
create_container
|
62
|
+
wait_for_healthy_container
|
63
|
+
check_emulator_connection if is_running_on_emulator
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Stops and remove container
|
68
|
+
def clean_container
|
69
|
+
@container.stop
|
70
|
+
@container.delete(force: true)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Executes commands inside docker container
|
74
|
+
def docker_exec(command)
|
75
|
+
Actions.sh("docker exec -i #{container_name} bash -l -c \"#{command}\"")
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# Sets path to adb
|
81
|
+
def adb_path
|
82
|
+
"#{@sdk_path}/platform-tools/adb"
|
83
|
+
end
|
84
|
+
|
85
|
+
# assigns vnc port
|
86
|
+
def assign_unique_vnc_port
|
87
|
+
@no_vnc_port = 6080 + port_factor
|
88
|
+
@host_ip_address = `hostname -I | head -n1 | awk '{print $1;}'`.delete!("\n")
|
89
|
+
UI.success("Port: #{@no_vnc_port} was chosen for VNC")
|
90
|
+
UI.success("Link to VNC: http://#{@host_ip_address}:#{@no_vnc_port}")
|
91
|
+
end
|
92
|
+
|
93
|
+
# Restarts adb on the separate port and checks if created emulator is connected
|
94
|
+
def check_emulator_connection
|
95
|
+
UI.success('Checking if emulator is connected to ADB.')
|
96
|
+
|
97
|
+
if emulator_is_healthy?
|
98
|
+
UI.success('Emulator connected successfully')
|
99
|
+
else
|
100
|
+
raise "Something went wrong. Newly created device couldn't connect to the adb"
|
101
|
+
end
|
102
|
+
|
103
|
+
disable_animations
|
104
|
+
increase_logcat_storage
|
105
|
+
end
|
106
|
+
|
107
|
+
def emulator_is_healthy?
|
108
|
+
list_devices = docker_exec('adb devices')
|
109
|
+
list_devices.include? "\tdevice"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Creates new container using params
|
113
|
+
def create_container
|
114
|
+
UI.important("Creating container: #{container_name}")
|
115
|
+
print_cpu_load
|
116
|
+
begin
|
117
|
+
container = create_container_call
|
118
|
+
@container_name = container unless container_name
|
119
|
+
rescue StandardError
|
120
|
+
UI.important("Something went wrong while creating: #{container_name}, will retry in #{@sleep_interval} seconds")
|
121
|
+
print_cpu_load
|
122
|
+
`docker stop #{container_name}` if container_name
|
123
|
+
`docker rm #{container_name}` if container_name
|
124
|
+
sleep @sleep_interval
|
125
|
+
container = create_container_call
|
126
|
+
@container_name = container unless container_name
|
127
|
+
end
|
128
|
+
get_container_instance(container)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Gets container instance by container ID
|
132
|
+
def get_container_instance(container)
|
133
|
+
Docker::Container.all(all: true).each do |cont|
|
134
|
+
if cont.id == container
|
135
|
+
@container = cont
|
136
|
+
break
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Call to create a container. We don't use Docker API here, as it doesn't support --privileged.
|
142
|
+
def create_container_call
|
143
|
+
# When CPU is under load we cannot create a healthy container
|
144
|
+
wait_cpu_to_idle
|
145
|
+
|
146
|
+
docker_name = if container_name
|
147
|
+
"--name #{container_name}"
|
148
|
+
else
|
149
|
+
''
|
150
|
+
end
|
151
|
+
|
152
|
+
emulator_args = is_running_on_emulator ? "-p #{no_vnc_port}:6080 -e DEVICE='#{device_name}'" : ''
|
153
|
+
|
154
|
+
# Action.sh returns all output that the command produced but we are only
|
155
|
+
# interested in the last line, since it contains the id of the created container.
|
156
|
+
UI.important("Attaching #{ENV['PWD']} to the docker container")
|
157
|
+
output = Actions.sh("docker run -v $PWD:/root/tests --privileged -t -d #{emulator_args} #{docker_name} #{docker_image}").chomp
|
158
|
+
output.split("\n").last
|
159
|
+
end
|
160
|
+
|
161
|
+
def execute_pre_action
|
162
|
+
Actions.sh(@pre_action)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Pull the docker images before creating a container
|
166
|
+
def pull_from_registry
|
167
|
+
docker_image_name = docker_image.gsub(':latest', '')
|
168
|
+
Actions.sh(@docker_registry_login) if @docker_registry_login
|
169
|
+
Actions.sh("docker pull #{docker_image_name}")
|
170
|
+
end
|
171
|
+
|
172
|
+
# Checks that chosen ports are not already allocated. If they are, it will stop the allocated container
|
173
|
+
def handle_ports_allocation
|
174
|
+
vnc_allocated_container = container_of_allocated_port(no_vnc_port)
|
175
|
+
if vnc_allocated_container
|
176
|
+
UI.important("Port: #{no_vnc_port} was already allocated. Stopping Container.")
|
177
|
+
vnc_allocated_container.stop
|
178
|
+
end
|
179
|
+
|
180
|
+
if ports_open?('0.0.0.0', [@no_vnc_port])
|
181
|
+
UI.important('Something went wrong. One of the required ports is still busy')
|
182
|
+
sleep @sleep_interval
|
183
|
+
`docker stop #{container_name}` if container_name
|
184
|
+
`docker rm #{container_name}` if container_name
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Gets container instance of allocated port
|
189
|
+
def container_of_allocated_port(port)
|
190
|
+
Docker::Container.all.each do |container|
|
191
|
+
public_ports = container.info['Ports'].map { |public_port| public_port['PublicPort'] }
|
192
|
+
return container if public_ports.include? port
|
193
|
+
end
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
|
197
|
+
def print_cpu_load(load = cpu_load)
|
198
|
+
UI.important("CPU load is: #{load}")
|
199
|
+
end
|
200
|
+
|
201
|
+
# Checks if container is already available
|
202
|
+
def container_available?
|
203
|
+
return false unless container_name
|
204
|
+
all_containers = Docker::Container.all(all: true)
|
205
|
+
|
206
|
+
all_containers.each do |container|
|
207
|
+
if container.info['Names'].first[1..-1] == container_name
|
208
|
+
@container = container
|
209
|
+
return true
|
210
|
+
end
|
211
|
+
end
|
212
|
+
false
|
213
|
+
end
|
214
|
+
|
215
|
+
# Checks if container status is 'healthy'
|
216
|
+
def container_is_healthy?
|
217
|
+
if @container.json['State']['Health']
|
218
|
+
@container.json['State']['Health']['Status'] == 'healthy'
|
219
|
+
else
|
220
|
+
@container.json['State']['Status'] == 'running'
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Waits until container is healthy using specified timeout
|
225
|
+
def wait_for_healthy_container(will_exit = true)
|
226
|
+
UI.important('Waiting for Container to be in the Healthy state.')
|
227
|
+
|
228
|
+
number_of_tries = timeout / sleep_interval
|
229
|
+
number_of_tries.times do
|
230
|
+
sleep sleep_interval / 2
|
231
|
+
if container_is_healthy?
|
232
|
+
UI.success('Your container is ready to work')
|
233
|
+
return true
|
234
|
+
end
|
235
|
+
UI.important("Container status: #{@container.json['State']['Status']}")
|
236
|
+
sleep sleep_interval
|
237
|
+
end
|
238
|
+
UI.important("The Container failed to load after '#{timeout}' seconds timeout. Reason: '#{@container.json['State']['Status']}'")
|
239
|
+
# We use code "2" as we need something than just standard error code 1, so we can differentiate the next step in CI
|
240
|
+
exit 2 if will_exit
|
241
|
+
raise 'Fail'
|
242
|
+
end
|
243
|
+
|
244
|
+
# Checks if port is already open
|
245
|
+
def ports_open?(ip, ports)
|
246
|
+
raise "'ports' should be an array" unless ports.is_a? Array
|
247
|
+
ports.each do |port|
|
248
|
+
begin
|
249
|
+
Timeout.timeout(1) do
|
250
|
+
begin
|
251
|
+
s = TCPSocket.new(ip, port)
|
252
|
+
s.close
|
253
|
+
return true
|
254
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
255
|
+
end
|
256
|
+
end
|
257
|
+
rescue Timeout::Error
|
258
|
+
end
|
259
|
+
end
|
260
|
+
false
|
261
|
+
end
|
262
|
+
|
263
|
+
# Gets load average of Linux machine
|
264
|
+
def cpu_load
|
265
|
+
load = `cat /proc/loadavg`
|
266
|
+
load.split(' ').first.to_f
|
267
|
+
end
|
268
|
+
|
269
|
+
# Gets amount of the CPU cores
|
270
|
+
def cpu_core_amount
|
271
|
+
`cat /proc/cpuinfo | awk '/^processor/{print $3}' | tail -1`
|
272
|
+
end
|
273
|
+
|
274
|
+
# For half an hour waiting until CPU load average is less than number of cores*2 (which considers that CPU is ready)
|
275
|
+
# Raises when 30 minutes timeout exceeds
|
276
|
+
def wait_cpu_to_idle
|
277
|
+
if OS.linux?
|
278
|
+
30.times do
|
279
|
+
load = cpu_load
|
280
|
+
return true if load < cpu_core_amount.to_i * 1.5
|
281
|
+
print_cpu_load(load)
|
282
|
+
UI.important('Waiting for available resources..')
|
283
|
+
sleep 60
|
284
|
+
end
|
285
|
+
else
|
286
|
+
return true
|
287
|
+
end
|
288
|
+
raise "CPU was overloaded. Couldn't start emulator"
|
289
|
+
end
|
290
|
+
|
291
|
+
# Disables animation for faster and stable testing
|
292
|
+
def disable_animations
|
293
|
+
docker_exec('adb shell settings put global window_animation_scale 0.0')
|
294
|
+
docker_exec('adb shell settings put global transition_animation_scale 0.0')
|
295
|
+
docker_exec('adb shell settings put global animator_duration_scale 0.0')
|
296
|
+
end
|
297
|
+
|
298
|
+
# Increases logcat storage
|
299
|
+
def increase_logcat_storage
|
300
|
+
docker_exec('adb logcat -G 16m')
|
301
|
+
end
|
302
|
+
|
303
|
+
def disconnect_network_bridge
|
304
|
+
`docker network disconnect -f bridge #{container_name}`
|
305
|
+
rescue StandardError
|
306
|
+
# Do nothing if the network bridge is already gone
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fastlane-plugin-mango
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Serghei Moret, Daniel Hartwich
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-08-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: docker-api
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
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: fastlane
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.66.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.66.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
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: rake
|
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: rspec
|
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: rspec_junit_formatter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.58.2
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.58.2
|
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
|
+
description:
|
140
|
+
email: serghei.moret@xing.com, hartwich.daniel@gmail.com
|
141
|
+
executables: []
|
142
|
+
extensions: []
|
143
|
+
extra_rdoc_files: []
|
144
|
+
files:
|
145
|
+
- LICENSE
|
146
|
+
- README.md
|
147
|
+
- lib/fastlane/plugin/mango.rb
|
148
|
+
- lib/fastlane/plugin/mango/actions/run_dockerized_task_action.rb
|
149
|
+
- lib/fastlane/plugin/mango/helper/mango_helper.rb
|
150
|
+
- lib/fastlane/plugin/mango/version.rb
|
151
|
+
homepage: https://github.com/xing/mango
|
152
|
+
licenses:
|
153
|
+
- MIT
|
154
|
+
metadata: {}
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 2.7.7
|
172
|
+
signing_key:
|
173
|
+
specification_version: 4
|
174
|
+
summary: This plugin Android tasks on docker images
|
175
|
+
test_files: []
|