fastlane-plugin-dynatrace 0.1.4 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +74 -88
- data/lib/fastlane/plugin/dynatrace/actions/dynatrace_action.rb +182 -148
- data/lib/fastlane/plugin/dynatrace/helper/dynatrace_helper.rb +165 -5
- data/lib/fastlane/plugin/dynatrace/version.rb +1 -1
- metadata +11 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 920b0c5553ebb02953f85441f6ab8a4bba712003435f354e3f11458c0c23f7f9
|
4
|
+
data.tar.gz: 64d293694d00bd694df462fbd5ee5e16ed12359c93e18817e9bf9fe8a92f9c5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9be84816a06cf7df02d04d625c7b982e317a7b23a62f839de9cb1600c18ea08779c7f5d23d71de5cf802d507240c7826c61dd4daa07dd939f6e41cad9ad656ac
|
7
|
+
data.tar.gz: d452461f103ff34d7b290cd5a1bd3f4e988c01956779707cc5166572665101c817a3f880865d5ead209a547864b3a2e894875aa93e03cccdb4c3010748e560f4
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# Dynatrace Fastlane plugin
|
2
2
|
|
3
3
|
[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-dynatrace)
|
4
4
|
|
@@ -10,129 +10,115 @@ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To
|
|
10
10
|
fastlane add_plugin dynatrace
|
11
11
|
```
|
12
12
|
|
13
|
-
|
13
|
+
### Dynatrace Managed
|
14
|
+
If the installation is on version 1.195 or earlier the Symbolication Client has to be manually download and specified (`dtxDssClientPath`), else it's fetched and updated automatically. A matching version can be downloaded manually with this link [https://api.mobileagent.downloads.dynatrace.com/sprint-latest-dss-client/xyz](https://api.mobileagent.downloads.dynatrace.com/sprint-latest-dss-client/xyz) by replacing `xyz` with the 3-digit sprint version of your Dynatrace Managed installation.
|
14
15
|
|
15
|
-
## About
|
16
|
-
|
17
|
-
This plugin allows you to decode and upload symbolication files to Dynatrace. You can also use it to first download your latest dSym files from AppStore Connect if you use Bitcode.
|
16
|
+
## About the Dynatrace fastlane plugin
|
17
|
+
This plugin allows you to decode and upload symbol files (iOS) or just upload obfuscation mapping files (Android) to Dynatrace. You can also use it to first download your latest dSYM files from App Store Connect if you use Bitcode.
|
18
18
|
|
19
19
|
## Action: `dynatrace_process_symbols`
|
20
20
|
|
21
21
|
| Supported Platforms | ios, android |
|
22
22
|
|---------------------|--------------|
|
23
|
-
| Author | @MANassar |
|
24
|
-
|
25
|
-
|
26
|
-
## Parameters
|
27
|
-
|
28
|
-
| Key | Description | default value |
|
29
|
-
|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
|
30
|
-
| downloadDsyms | Boolean variable that enables downloading the Dsyms from AppStore Connect (iOS only) | false |
|
31
|
-
| username | The username or the AppleID to use to download the Dsyms. You can also store this in your AppFile as "apple_id and it will be automatically retrieved." | |
|
32
|
-
| dtxDssClientPath | The full path to your DTXDssClient. For example, it could be `./ios/agent/DTXDssClient` | `./DTXDssClient` |
|
33
|
-
| action | The action to perform. upload/decode | `upload` |
|
34
|
-
| appID | The app ID you get from your Dynatrace WebUI | |
|
35
|
-
| os | The OperatingSystem of the symbol files. Either "ios" or "android" | |
|
36
|
-
| apitoken | The Dynatrace API token. It should have the correct permissions. | |
|
37
|
-
| bundleId | The CFBundlebundleId (iOS) / package (Android) of the Application. Usually in reverse com notation. Ex. com.your_company.your_app. This can also be stored in the AppFile as "app_identifier" and it will be automatically retrieved. | |
|
38
|
-
| bundleName | The CFBundleName of the Application (iOS only) | |
|
39
|
-
| versionStr | The CFBundleShortVersionString (iOS) / versionName (Android | |
|
40
|
-
| version | The CFBundleVersion (iOS) / versionCode (Android). This will also be used for dsym download. | |
|
41
|
-
| symbolsfile | The path to a local symbol files to be processed and uploaded. You do not need to specify that if you use downloadDsyms. | |
|
42
|
-
| server | The API endpoint for the Dynatrace environment. For example https://environmentID.live.dynatrace.com or https://dynatrace-managed.com/e/environmentID | |
|
43
|
-
| debugMode | Debug logging enabled | false |
|
44
23
|
|
45
|
-
##
|
24
|
+
## Is your app Bitcode enabled?
|
25
|
+
> Only applies for apps distributed via the AppStore or TestFlight.
|
46
26
|
|
47
|
-
|
27
|
+
If your app is bitcode enabled, then the dSYMs that are generated during the Xcode build are **_not_** the dSYMs you want to upload to Dynatrace. This is because Apple recompiles the application on their servers, generating new dSYM files in the process. These newly generated dSYM files need to be downloaded from *App Store Connect*, then processed and uploaded to Dynatrace.
|
48
28
|
|
49
|
-
|
29
|
+
### Important
|
30
|
+
There is a time gap between the application being uploaded to App Store Connect and the dSYM files to be ready. So **_we have to introduce some "wait" time in the CI to accomodate for this_**. You can do this by setting the `waitForDsymProcessing` to true. Unfortunately, Apple does not specify how long this time is. We recommend 1800 seconds (30 mins) as this is usually enough for the symbols are ready for download. You can increase this timeout if needed (`waitForDsymProcessingTimeout`).
|
50
31
|
|
51
|
-
|
32
|
+
> Notice that this timeout is only the **maximum** waiting time. If the symbol files are ready sooner, it will continue processing and will not wait for the whole duration of the timeout.
|
52
33
|
|
53
|
-
###
|
54
|
-
|
55
|
-
```ruby
|
56
|
-
dynatrace_process_symbols(
|
57
|
-
dtxDssClientPath:"<path>/DTXDssClient",
|
58
|
-
appId: "your DT appID",
|
59
|
-
apitoken: "your DT API token",
|
60
|
-
os: "<ios> or <android>",
|
61
|
-
bundleId: "com.yourcompany.yourApp",
|
62
|
-
bundleName: "MyApp",
|
63
|
-
versionStr: "1.0",
|
64
|
-
version: "1",
|
65
|
-
symbolsfile: "<path to my app>.app.dSYM",
|
66
|
-
server: "https://<environmentID.live.dynatrace.com",
|
67
|
-
debugMode: true)
|
68
|
-
|
69
|
-
```
|
70
|
-
|
71
|
-
### Downloading dsyms and using AppFile
|
34
|
+
### Automatically downloading dSYMs and using AppFile for authentication
|
72
35
|
|
73
36
|
#### AppFile
|
74
|
-
|
75
37
|
```ruby
|
76
|
-
app_identifier("com.yourcompany.yourappID") #
|
77
|
-
apple_id("user@email.com")
|
38
|
+
app_identifier("com.yourcompany.yourappID") # bundle identifier of your app
|
39
|
+
apple_id("user@email.com")
|
78
40
|
```
|
79
41
|
|
80
42
|
#### Fastfile
|
81
|
-
|
82
43
|
```ruby
|
83
44
|
dynatrace_process_symbols(
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
debugMode: true)
|
94
|
-
|
45
|
+
appId: "<Dynatrace application ID>",
|
46
|
+
apitoken: "<Dynatrace API token>",
|
47
|
+
os: "ios",
|
48
|
+
bundleId: "<CFBundlebundleId (iOS) / package (Android)>",
|
49
|
+
versionStr: "<Build Number (CFBundleVersion)>",
|
50
|
+
version: "<App Version (CFBundleShortVersionString)>",
|
51
|
+
server: "<Dynatrace Environment URL>",
|
52
|
+
downloadDsyms: true
|
53
|
+
)
|
95
54
|
```
|
96
55
|
|
97
|
-
### Downloading dsyms, using AppFile - Only decoding them
|
98
56
|
|
99
|
-
|
57
|
+
## If you are NOT using Bitcode, or if you have already downloaded your new symbols from App Store Connect manually.
|
100
58
|
|
59
|
+
### Supply all parameters locally
|
101
60
|
```ruby
|
102
|
-
|
103
|
-
|
61
|
+
dynatrace_process_symbols(
|
62
|
+
appId: "<Dynatrace application ID>",
|
63
|
+
apitoken: "<Dynatrace API Token>",
|
64
|
+
os: "<ios> or <android>",
|
65
|
+
bundleId: "<CFBundlebundleId (iOS) / package (Android)>",
|
66
|
+
versionStr: "<CFBundleShortVersionString (iOS) / versionName (Android)>",
|
67
|
+
version: "<CFBundleVersion (iOS) / versionCode (Android)>",
|
68
|
+
server: "<Dynatrace Environment URL>",
|
69
|
+
symbolsfile: "<Symbols File Path>"
|
70
|
+
)
|
104
71
|
```
|
105
72
|
|
106
|
-
|
73
|
+
## List of all Parameters
|
74
|
+
| Key | Description | default value |
|
75
|
+
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
|
76
|
+
| action | *(iOS only)* Action to be performed by DTXDssClient (`upload` or `decode`). | `upload` |
|
77
|
+
| downloadDsyms | *(iOS only)* Download the dSYMs from App Store Connect. | `false` |
|
78
|
+
| waitForDsymProcessing | *(iOS only)* Wait for dSYM processing to be finished. | `true` |
|
79
|
+
| waitForDsymProcessingTimeout | *(iOS only)* Timeout in seconds to wait for the dSYMs be downloadable. | `1800` |
|
80
|
+
| username | *(iOS only)* The username/AppleID to use to download the dSYMs. Alternatively you can specify this in your AppFile as `apple_id`. | |
|
81
|
+
| os | The type of the symbol files, either `ios` or `android`. | |
|
82
|
+
| apitoken | Dynatrace API token with mobile symbolication permissions. | |
|
83
|
+
| dtxDssClientPath | **(DEPRECATED)** The path to your DTXDssClient. The DTXDssClient is downloaded and updated automatically, unless this key is set. | |
|
84
|
+
| appID | The application ID you get from your Dynatrace environment. | |
|
85
|
+
| bundleId | The CFBundlebundleId (iOS) / package (Android) of the application. Alternatively you can specify this in your AppFile as `app_identifier`. | |
|
86
|
+
| versionStr | The CFBundleShortVersionString (iOS) / versionName (Android) | |
|
87
|
+
| version | The CFBundleVersion (iOS) / versionCode (Android). Is also used for the dSYM download. | |
|
88
|
+
| symbolsfile | Path to the dSYM file to be processed. If downloadDsyms is set, this is only a fallback. | |
|
89
|
+
| server | The API endpoint for the Dynatrace environment (e.g. `https://environmentID.live.dynatrace.com` or `https://dynatrace-managed.com/e/environmentID`). | |
|
90
|
+
| debugMode | Enable debug logging. | false |
|
91
|
+
|
92
|
+
## AppStore Connect Two-Factor-Authentication
|
93
|
+
When the plugin is used to download symbols from *AppStore Connect* automatically (`downloadDsyms: true`) valid login credentials to an account with permissions to access the dSYM files are required. The preferred method of doing so is by setting the `FASTLANE_USER` and `FASTLANE_PASSWORD` environment variables to their respective values.
|
94
|
+
|
95
|
+
[Apple announced](https://github.com/fastlane/fastlane/discussions/17655) that 2-Factor-Authentication for the *AppStore Connect* API will be enforced starting February 2021. This limits the ability to automate the symbol processing, because it will most likely involve manual interaction, which is not suitable for CI automation. The only workaround at this point in time is to pre-generate a session and cache it in CI.
|
96
|
+
|
97
|
+
### Fastlane Session
|
98
|
+
The full documentation for this can be found on the [fastlane docs](https://docs.fastlane.tools/best-practices/continuous-integration/#two-step-or-two-factor-auth
|
99
|
+
) under **spaceauth**.
|
100
|
+
|
101
|
+
You can generate a session by running `fastlane spaceauth -u user@email.com` on your machine and copy the output into an environment variable `FASTLANE_SESSION` on the target system (e.g. CI).
|
102
|
+
|
103
|
+
### NOTE
|
104
|
+
- Session is only valid in the "region" you create it. If you CI is in a different geographical location the authentication might fail.
|
105
|
+
|
106
|
+
- Generated sessions are valid up to one month. Apple's API doesn't specify details about that, so it will only be visible by a failing build.
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
action = "decode",
|
111
|
-
downloadDsyms: true,
|
112
|
-
dtxDssClientPath:"<path>/DTXDssClient",
|
113
|
-
appId: "your DT appID",
|
114
|
-
apitoken: "your DT API token",
|
115
|
-
os: "<ios> or <android>",
|
116
|
-
bundleName: "MyApp",
|
117
|
-
versionStr: "1.0",
|
118
|
-
version: "1",
|
119
|
-
server: "https://dynatrace-managed.com/e/environmentID",
|
120
|
-
debugMode: true)
|
108
|
+
## Example
|
109
|
+
Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
|
121
110
|
|
122
|
-
|
111
|
+
## Tests
|
112
|
+
This plugin includes a set of RSpec unit tests, which can be executed by running ` bundle exec rspec spec`.
|
123
113
|
|
124
114
|
## Issues and Feedback
|
125
|
-
|
126
|
-
For any other issues and feedback about this plugin, please submit it to this repository or contact Dynatrace Support.
|
115
|
+
For any other issues and feedback about this plugin, please submit it to this repository or contact [Dynatrace Support](https://support.dynatrace.com).
|
127
116
|
|
128
117
|
## Troubleshooting
|
129
|
-
|
130
118
|
If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
|
131
119
|
|
132
120
|
## Using _fastlane_ Plugins
|
133
|
-
|
134
121
|
For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
|
135
122
|
|
136
123
|
## About _fastlane_
|
137
|
-
|
138
124
|
_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).
|
@@ -1,4 +1,10 @@
|
|
1
1
|
require 'fastlane/action'
|
2
|
+
require 'net/http'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'zip'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'os'
|
7
|
+
require 'json'
|
2
8
|
require_relative '../helper/dynatrace_helper'
|
3
9
|
|
4
10
|
module Fastlane
|
@@ -6,7 +12,6 @@ module Fastlane
|
|
6
12
|
class DynatraceProcessSymbolsAction < Action
|
7
13
|
|
8
14
|
def self.run(params)
|
9
|
-
# fastlane will take care of reading in the parameter and fetching the environment variable:
|
10
15
|
UI.message "DTXDssClientPath: #{params[:dtxDssClientPath]}"
|
11
16
|
UI.message "Parameter API Token: #{params[:apitoken]}"
|
12
17
|
UI.message "OS: #{params[:os]}"
|
@@ -14,232 +19,261 @@ module Fastlane
|
|
14
19
|
UI.message "Version: #{params[:version]}"
|
15
20
|
UI.message "Server URL: #{params[:server]}"
|
16
21
|
|
17
|
-
UI.message
|
22
|
+
UI.message "Checking AppFile for possible AppID"
|
18
23
|
bundleId = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
|
19
|
-
|
24
|
+
if bundleId
|
25
|
+
UI.message "Using #{bundleId} from your AppFile"
|
26
|
+
else
|
27
|
+
bundleId = params[:bundleId]
|
28
|
+
UI.message "BundleID: #{bundleId}"
|
29
|
+
end
|
20
30
|
|
21
|
-
if
|
22
|
-
|
31
|
+
if params[:os] == "android"
|
32
|
+
response = Helper::DynatraceHelper.put_android_symbols(params, bundleId)
|
33
|
+
case response.code
|
34
|
+
when '204'
|
35
|
+
UI.success "Successfully uploaded the mapping file (#{params[:symbolsfile]}) to Dynatrace."
|
36
|
+
when '400'
|
37
|
+
UI.user_error! "Failed to upload. The input is invalid."
|
38
|
+
when '401'
|
39
|
+
UI.user_error! "Invalid Dynatrace API token. See https://www.dynatrace.com/support/help/dynatrace-api/basics/dynatrace-api-authentication/#token-permissions and https://www.dynatrace.com/support/help/dynatrace-api/configuration-api/mobile-symbolication-api/"
|
40
|
+
when '413'
|
41
|
+
UI.user_error! "Failed to upload. The symbol file storage quota is exhausted. See https://www.dynatrace.com/support/help/shortlink/mobile-symbolication#manage-the-uploaded-symbol-files for more information."
|
42
|
+
else
|
43
|
+
message = JSON.parse(response.body)["error"]["message"]
|
44
|
+
if message.nil?
|
45
|
+
UI.user_error! "Symbol upload error (Response Code: #{response.code}). Please try again in a few minutes or contact the Dynatrace support (https://www.dynatrace.com/services-support/)."
|
46
|
+
else
|
47
|
+
UI.user_error! "Symbol upload error (Response Code: #{response.code}). #{message}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
return
|
51
|
+
elsif params[:os] != "ios"
|
52
|
+
UI.user_error! "Unsopported value os=#{params[:os]}"
|
23
53
|
end
|
24
54
|
|
55
|
+
# iOS workflow
|
56
|
+
dtxDssClientPath = Helper::DynatraceHelper.get_dss_client(params)
|
25
57
|
|
26
58
|
dsym_paths = []
|
27
|
-
symbolFilesKey = "symbolsfile" #default to iOS
|
59
|
+
symbolFilesKey = "symbolsfile" # default to iOS
|
28
60
|
|
29
|
-
if
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
61
|
+
if !OS.mac?
|
62
|
+
UI.user_error! "A macOS machine is required to process iOS symbols."
|
63
|
+
end
|
64
|
+
|
65
|
+
if params[:downloadDsyms] == true
|
66
|
+
UI.message "Downloading dSYMs from App Store Connect"
|
67
|
+
startTime = Time.now
|
68
|
+
|
69
|
+
UI.message "Checking AppFile for possible username/AppleID"
|
70
|
+
username = CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
|
71
|
+
if username
|
72
|
+
UI.message "Using #{username} from your AppFile"
|
73
|
+
else
|
74
|
+
username = params[:username]
|
75
|
+
UI.message "Didn't find a username in AppFile, using passed username parameter: #{params[:username]}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# it takes a couple of minutes until the new build is available through the API
|
79
|
+
# -> retry until available
|
80
|
+
while params[:waitForDsymProcessing] and # wait is active
|
81
|
+
!lane_context[SharedValues::DSYM_PATHS] and # has dsym path
|
82
|
+
(Time.now - startTime) < params[:waitForDsymProcessingTimeout] # is in time
|
83
|
+
|
84
|
+
Actions::DownloadDsymsAction.run(wait_for_dsym_processing: params[:waitForDsymProcessing],
|
85
|
+
wait_timeout: (params[:waitForDsymProcessingTimeout] - (Time.now - startTime)).round(0), # remaining timeout
|
86
|
+
app_identifier: bundleId,
|
87
|
+
username: username,
|
88
|
+
version: params[:version],
|
89
|
+
build_number: params[:versionStr],
|
90
|
+
platform: :ios) # should be optional (Hint: it's not)
|
91
|
+
|
92
|
+
if !lane_context[SharedValues::DSYM_PATHS] and (Time.now - startTime) < params[:waitForDsymProcessingTimeout]
|
93
|
+
UI.message "Version #{params[:version]} (Build #{params[:versionStr]}) isn't listed yet, retrying in 60 seconds (timeout in #{(params[:waitForDsymProcessingTimeout] - (Time.now - startTime)).round(0)} seconds)."
|
94
|
+
sleep(60)
|
35
95
|
end
|
96
|
+
end
|
36
97
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
UI.message("Using #{username} from your AppFile")
|
41
|
-
|
42
|
-
if !(username)
|
43
|
-
UI.message "Username: #{params[:username]}"
|
44
|
-
end
|
45
|
-
|
46
|
-
UI.message("Downloading Dsyms from AppStore Connect")
|
47
|
-
Fastlane::Actions::DownloadDsymsAction.run(app_identifier: bundleId,
|
48
|
-
username: username,
|
49
|
-
version: version)
|
50
|
-
dsym_paths += Actions.lane_context[SharedValues::DSYM_PATHS] if Actions.lane_context[SharedValues::DSYM_PATHS]
|
51
|
-
|
52
|
-
if dsym_paths.count > 0
|
53
|
-
UI.message("Downloaded the Dsyms from AppStore Connect. Paths #{dsym_paths}")
|
54
|
-
|
55
|
-
else
|
56
|
-
raise 'No dsyms found error'
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
rescue
|
61
|
-
UI.error("Couldn't download Dsyms. Checking if we have a local path")
|
62
|
-
dsym_paths << params[:symbolsfile] if params[:symbolsfile]
|
63
|
-
end #end the begin-rescue block
|
64
|
-
|
65
|
-
else #android
|
66
|
-
dsym_paths << params[:symbolsfile] if params[:symbolsfile]
|
67
|
-
symbolFilesKey = "file"
|
68
|
-
end
|
98
|
+
if (Time.now - startTime) > params[:waitForDsymProcessingTimeout]
|
99
|
+
UI.user_error!("Timeout during dSYM download. Try increasing :waitForDsymProcessingTimeout.")
|
100
|
+
end
|
69
101
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
102
|
+
dsym_paths += Actions.lane_context[SharedValues::DSYM_PATHS] if Actions.lane_context[SharedValues::DSYM_PATHS]
|
103
|
+
|
104
|
+
if dsym_paths.count > 0
|
105
|
+
UI.message "Downloaded the dSYMs from App Store Connect. Paths: #{dsym_paths}"
|
106
|
+
else
|
107
|
+
raise 'No dSYM paths found!'
|
108
|
+
end
|
109
|
+
else
|
110
|
+
UI.important "dSYM download disabled, using local path (#{params[:symbolsfile]})"
|
111
|
+
dsym_paths << params[:symbolsfile] if params[:symbolsfile]
|
112
|
+
end
|
78
113
|
|
79
|
-
#
|
114
|
+
# check if we have dSYMs to proceed with
|
115
|
+
if dsym_paths.count == 0
|
116
|
+
UI.message "Symbol file path: #{params[:symbolsfile]}"
|
117
|
+
dsym_paths = params[:symbolsfile]
|
118
|
+
symbolFilesCommandSnippet = "#{symbolFilesKey}=\"#{dsym_paths}\""
|
119
|
+
else
|
120
|
+
symbolFilesCommandSnippet = "#{symbolFilesKey}=\"#{dsym_paths[0]}\""
|
121
|
+
end
|
122
|
+
|
123
|
+
# start constructing the command that will trigger the DTXDssClient
|
80
124
|
command = []
|
81
|
-
command << "#{
|
125
|
+
command << "#{dtxDssClientPath}"
|
82
126
|
command << "-#{params[:action]}" #"-upload"
|
83
127
|
command << "appid=\"#{params[:appId]}\""
|
84
128
|
command << "apitoken=\"#{params[:apitoken]}\""
|
85
129
|
command << "os=#{params[:os]}"
|
86
130
|
command << "bundleId=\"#{bundleId}\""
|
87
|
-
|
88
|
-
command << "versionStr=\"#{version}\""
|
89
|
-
else
|
90
|
-
command << "versionStr=\"#{params[:versionStr]}\""
|
91
|
-
end
|
131
|
+
command << "versionStr=\"#{params[:versionStr]}\""
|
92
132
|
command << "version=\"#{params[:version]}\""
|
93
133
|
command << symbolFilesCommandSnippet
|
94
|
-
command << "server=\"#{params
|
134
|
+
command << "server=\"#{Helper::DynatraceHelper.get_base_url(params)}\""
|
95
135
|
command << "DTXLogLevel=ALL -verbose" if params[:debugMode] == true
|
96
|
-
command << "forced=1" #
|
136
|
+
command << "forced=1" # if the file already exists
|
97
137
|
|
98
138
|
# Create the full shell command to trigger the DTXDssClient
|
99
139
|
shell_command = command.join(' ')
|
100
140
|
|
141
|
+
UI.message "dSYM path: #{dsym_paths[0]}"
|
142
|
+
UI.message "#{shell_command}"
|
101
143
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
144
|
+
sh("#{shell_command}", error_callback: ->(result) {
|
145
|
+
# ShAction doesn't return any reference to the return value -> parse it from the output
|
146
|
+
result_groups = result.match /(?:ERROR: Execution failed, rc=)(-?\d*)(?:\sreason=)(.*)/
|
147
|
+
if result_groups and result_groups.length() >= 2
|
148
|
+
if result_groups[1] == "413"
|
149
|
+
UI.user_error!("DTXDssClient: #{result_groups[2]} See https://www.dynatrace.com/support/help/shortlink/mobile-symbolication#manage-the-uploaded-symbol-files for more information.")
|
150
|
+
else
|
151
|
+
UI.user_error!("DTXDssClient: #{result_groups[2]}")
|
152
|
+
end
|
153
|
+
else
|
154
|
+
UI.user_error!("DTXDssClient finished with errors.")
|
155
|
+
end
|
156
|
+
})
|
107
157
|
|
108
|
-
UI.message
|
158
|
+
UI.message "Cleaning build artifacts"
|
109
159
|
Fastlane::Actions::CleanBuildArtifactsAction.run(exclude_pattern: nil)
|
110
|
-
|
111
|
-
end #end the run functions
|
160
|
+
end
|
112
161
|
|
113
162
|
def self.description
|
114
|
-
"This action processes and uploads your symbol files to Dynatrace"
|
163
|
+
"This action processes and uploads your symbol files to Dynatrace."
|
115
164
|
end
|
116
165
|
|
117
166
|
def self.details
|
118
|
-
"This action allows you to process and upload symbol files to Dynatrace.
|
167
|
+
"This action allows you to process and upload symbol files to Dynatrace. If you use Bitcode you can also use it to download the latest dSYM files from App Store Connect."
|
119
168
|
end
|
120
169
|
|
121
170
|
def self.available_options
|
122
|
-
# Define all options your action supports.
|
123
171
|
[
|
124
172
|
FastlaneCore::ConfigItem.new(key: :action,
|
125
173
|
env_name: "FL_UPLOAD_TO_DYNATRACE_ACTION",
|
126
|
-
description: "
|
174
|
+
description: "(iOS only) Action to be performed by DTXDssClient (\"upload\" or \"decode\")",
|
127
175
|
default_value: "upload",
|
128
176
|
is_string: true,
|
129
177
|
verify_block: proc do |value|
|
130
|
-
UI.user_error!("Action needs to either be upload or decode") unless (value and value == "upload" or value == "decode")
|
131
|
-
# UI.user_error!("Couldn't find file at path '#{value}'") unless File.exist?(value)
|
178
|
+
UI.user_error!("Action needs to either be \"upload\" or \"decode\"") unless (value and value == "upload" or value == "decode")
|
132
179
|
end),
|
133
180
|
|
134
181
|
FastlaneCore::ConfigItem.new(key: :downloadDsyms,
|
135
|
-
env_name: "FL_UPLOAD_TO_DYNATRACE_DOWNLOAD_DSYMS",
|
182
|
+
env_name: "FL_UPLOAD_TO_DYNATRACE_DOWNLOAD_DSYMS",
|
136
183
|
default_value: false,
|
137
184
|
is_string: false,
|
138
|
-
description: "
|
139
|
-
|
185
|
+
description: "(iOS only) Download the dSYMs from App Store Connect"),
|
186
|
+
|
187
|
+
FastlaneCore::ConfigItem.new(key: :waitForDsymProcessing,
|
188
|
+
env_name: "FL_UPLOAD_TO_DYNATRACE_DOWNLOAD_DSYMS_WAIT_PROCESSING",
|
189
|
+
default_value: true,
|
190
|
+
is_string: false,
|
191
|
+
description: "(iOS only) Wait for dSYM processing to be finished"),
|
192
|
+
|
193
|
+
FastlaneCore::ConfigItem.new(key: :waitForDsymProcessingTimeout,
|
194
|
+
env_name: "FL_UPLOAD_TO_DYNATRACE_DOWNLOAD_DSYMS_WAIT_TIMEOUT",
|
195
|
+
default_value: 1800,
|
196
|
+
is_string: false,
|
197
|
+
description: "(iOS only) Timeout in seconds to wait for the dSYMs be downloadable"),
|
140
198
|
|
141
199
|
FastlaneCore::ConfigItem.new(key: :username,
|
142
|
-
env_name: "FL_UPLOAD_TO_DYNATRACE_DOWNLOAD_DSYMS_USERNAME",
|
143
|
-
description: "The username
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
end),
|
200
|
+
env_name: "FL_UPLOAD_TO_DYNATRACE_DOWNLOAD_DSYMS_USERNAME",
|
201
|
+
description: "(iOS only) The username/AppleID to use to download the dSYMs"),
|
202
|
+
|
203
|
+
FastlaneCore::ConfigItem.new(key: :os,
|
204
|
+
env_name: "FL_UPLOAD_TO_DYNATRACE_OS",
|
205
|
+
description: "The type of the symbol files, either \"ios\" or \"android\"",
|
206
|
+
sensitive: false,
|
207
|
+
optional: false,
|
208
|
+
verify_block: proc do |value|
|
209
|
+
UI.user_error!("Please specify the type of the symbol files. Possible values are \"ios\" or \"android\".") unless (value and not value.empty? and (value == "ios" || value =="android"))
|
210
|
+
end),
|
154
211
|
|
155
212
|
FastlaneCore::ConfigItem.new(key: :apitoken,
|
156
|
-
env_name: "FL_UPLOAD_TO_DYNATRACE_apitoken",
|
157
|
-
description: "
|
213
|
+
env_name: "FL_UPLOAD_TO_DYNATRACE_apitoken",
|
214
|
+
description: "Dynatrace API token with mobile symbolication permissions",
|
158
215
|
verify_block: proc do |value|
|
159
|
-
UI.user_error!("No API token for
|
160
|
-
# UI.user_error!("Couldn't find file at path '#{value}'") unless File.exist?(value)
|
216
|
+
UI.user_error!("No Dynatrade API token for specified, pass using `apitoken: 'token'`") unless (value and not value.empty?)
|
161
217
|
end),
|
162
218
|
|
163
219
|
FastlaneCore::ConfigItem.new(key: :dtxDssClientPath,
|
164
220
|
env_name: "FL_UPLOAD_TO_DYNATRACE_DTXDssClientPath",
|
165
|
-
description: "The path to your DTXDssClient",
|
166
|
-
|
221
|
+
description: "(DEPRECATED) The path to your DTXDssClient. The DTXDssClient is downloaded and updated automatically, unless this key is set",
|
222
|
+
optional: true),
|
223
|
+
|
224
|
+
FastlaneCore::ConfigItem.new(key: :appId,
|
225
|
+
env_name: "FL_UPLOAD_TO_DYNATRACE_APP_ID",
|
226
|
+
description: "The app ID you get from your Dynatrace environment",
|
167
227
|
verify_block: proc do |value|
|
168
|
-
UI.user_error!("
|
169
|
-
|
170
|
-
# default_value: false) # the default value if the user didn't provide one
|
171
|
-
end),
|
172
|
-
|
173
|
-
FastlaneCore::ConfigItem.new(key: :appId,
|
174
|
-
env_name: "FL_UPLOAD_TO_DYNATRACE_APP_ID",
|
175
|
-
description: "The app ID you get from your Dynatrace WebUI",
|
176
|
-
verify_block: proc do |value|
|
177
|
-
UI.user_error!("Please provide the appID for your app. Pass using `appId: 'appId'`") unless (value and not value.empty?)
|
178
|
-
# is_string: true # true: verifies the input is a string, false: every kind of value
|
179
|
-
# default_value: false) # the default value if the user didn't provide one
|
180
|
-
end),
|
228
|
+
UI.user_error!("Please provide the appID for your application. Pass using `appId: 'appId'`") unless (value and not value.empty?)
|
229
|
+
end),
|
181
230
|
|
182
231
|
FastlaneCore::ConfigItem.new(key: :bundleId,
|
183
232
|
env_name: "FL_UPLOAD_TO_DYNATRACE_BUNDLE_ID",
|
184
|
-
description: "The CFBundlebundleId (iOS) / package (Android) of the
|
233
|
+
description: "The CFBundlebundleId (iOS) / package (Android) of the application",
|
185
234
|
verify_block: proc do |value|
|
186
235
|
UI.user_error!("Please provide the BundleID for your app. Pass using `bundleId: 'bundleId'`") unless (value and not value.empty?)
|
187
|
-
|
188
|
-
# default_value: false) # the default value if the user didn't provide one
|
189
|
-
end),
|
236
|
+
end),
|
190
237
|
|
191
238
|
FastlaneCore::ConfigItem.new(key: :versionStr,
|
192
239
|
env_name: "FL_UPLOAD_TO_DYNATRACE_VERSION_STRING",
|
193
240
|
description: "The CFBundleShortVersionString (iOS) / versionName (Android)",
|
194
241
|
verify_block: proc do |value|
|
195
242
|
UI.user_error!("Please provide the CFBundleShortVersionString for your app. Pass using `versionStr: 'versionStr'`") unless (value and not value.empty?)
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
UI.user_error!("Please provide the version for your app. Pass using `version: 'version'`") unless (value and not value.empty?)
|
205
|
-
# is_string: true # true: verifies the input is a string, false: every kind of value
|
206
|
-
# default_value: false) # the default value if the user didn't provide one
|
207
|
-
end),
|
243
|
+
end),
|
244
|
+
|
245
|
+
FastlaneCore::ConfigItem.new(key: :version,
|
246
|
+
env_name: "FL_UPLOAD_TO_DYNATRACE_VERSION",
|
247
|
+
description: "The CFBundleVersion (iOS) / versionCode (Android). Is also used for the dSYM download",
|
248
|
+
verify_block: proc do |value|
|
249
|
+
UI.user_error!("Please provide the version for your app. Pass using `version: 'version'`") unless (value and not value.empty?)
|
250
|
+
end),
|
208
251
|
|
209
252
|
FastlaneCore::ConfigItem.new(key: :symbolsfile,
|
210
253
|
env_name: "FL_UPLOAD_TO_DYNATRACE_SYM_FILE_PATH",
|
211
|
-
description: "
|
254
|
+
description: "Path to the dSYM file to be processed. Is only used when downloadDsyms is not set",
|
255
|
+
verify_block: proc do |value|
|
256
|
+
UI.user_error!("Please provide a value for the symbol files. Pass using `symbolsfile: 'symbolsfile'`") unless (value and not value.empty?)
|
257
|
+
end),
|
258
|
+
|
259
|
+
FastlaneCore::ConfigItem.new(key: :server,
|
260
|
+
env_name: "FL_UPLOAD_TO_DYNATRACE_SERVER_URL",
|
261
|
+
description: "The API endpoint for the Dynatrace environment (e.g. https://environmentID.live.dynatrace.com or https://dynatrace-managed.com/e/environmentID)",
|
212
262
|
verify_block: proc do |value|
|
213
|
-
UI.user_error!("Please provide
|
214
|
-
|
215
|
-
# default_value: false) # the default value if the user didn't provide one
|
216
|
-
end),
|
217
|
-
|
218
|
-
FastlaneCore::ConfigItem.new(key: :server,
|
219
|
-
env_name: "FL_UPLOAD_TO_DYNATRACE_SERVER_URL",
|
220
|
-
description: "The API endpoint for the Dynatrace environment. For example https://<environmentID.live.dynatrace.com/api/config/v1",
|
221
|
-
verify_block: proc do |value|
|
222
|
-
UI.user_error!("Please provide your environment API endpoint. Pass using `server: 'server'`") unless (value and not value.empty?)
|
223
|
-
# is_string: true # true: verifies the input is a string, false: every kind of value
|
224
|
-
# default_value: false) # the default value if the user didn't provide one
|
225
|
-
end),
|
263
|
+
UI.user_error!("Please provide your environment API endpoint. Pass using `server: 'server'`") unless (value and not value.empty?)
|
264
|
+
end),
|
226
265
|
|
227
266
|
FastlaneCore::ConfigItem.new(key: :debugMode,
|
228
267
|
env_name: "FL_UPLOAD_TO_DYNATRACE_DEBUG_MODE",
|
229
|
-
description: "
|
268
|
+
description: "Enable debug logging",
|
269
|
+
default_value: false,
|
230
270
|
is_string: false,
|
231
|
-
optional: true
|
232
|
-
)
|
271
|
+
optional: true)
|
233
272
|
]
|
234
273
|
end
|
235
274
|
|
236
|
-
def self.return_value
|
237
|
-
# If your method provides a return value, you can describe here what it does
|
238
|
-
end
|
239
|
-
|
240
275
|
def self.authors
|
241
|
-
|
242
|
-
["MANassar/@MohamedANassar"]
|
276
|
+
["MANassar/@MohamedANassar", "cynicer"]
|
243
277
|
end
|
244
278
|
|
245
279
|
def self.is_supported?(platform)
|
@@ -1,15 +1,175 @@
|
|
1
1
|
require 'fastlane_core/ui/ui'
|
2
|
+
require 'digest'
|
3
|
+
require 'net/http'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'uri'
|
2
6
|
|
3
7
|
module Fastlane
|
4
8
|
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
5
9
|
|
6
10
|
module Helper
|
7
11
|
class DynatraceHelper
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
def self.get_dss_client(params)
|
13
|
+
dynatraceDir = "dynatrace"
|
14
|
+
dtxDssClientBin = "DTXDssClient"
|
15
|
+
versionFilePath = "#{dynatraceDir}/version"
|
16
|
+
dtxDssClientPath = "#{dynatraceDir}/#{dtxDssClientBin}"
|
17
|
+
|
18
|
+
if params.all_keys.include? :dtxDssClientPath and not params[:dtxDssClientPath].nil?
|
19
|
+
UI.important "DEPRECATION WARNING: dtxDssClientPath doesn't need to be specified anymore, the #{dtxDssClientBin} is downloaded and updated automatically."
|
20
|
+
return params[:dtxDssClientPath]
|
21
|
+
end
|
22
|
+
|
23
|
+
# get latest version info
|
24
|
+
clientUri = URI("#{self.get_base_url(params)}/api/config/v1/symfiles/dtxdss-download?Api-Token=#{params[:apitoken]}")
|
25
|
+
response = Net::HTTP.get_response(clientUri)
|
26
|
+
|
27
|
+
# filter any http errors
|
28
|
+
if not response.kind_of? Net::HTTPSuccess
|
29
|
+
error_msg = "Couldn't update #{dtxDssClientBin} (invalid response: #{response.message} (#{response.code})) for URL: #{self.to_redacted_api_token_string(clientUri)})"
|
30
|
+
self.check_fallback_or_raise(dtxDssClientPath, error_msg)
|
31
|
+
return dtxDssClientPath
|
32
|
+
end
|
33
|
+
|
34
|
+
# parse body
|
35
|
+
begin
|
36
|
+
responseJson = JSON.parse(response.body)
|
37
|
+
rescue JSON::GeneratorError,
|
38
|
+
JSON::JSONError,
|
39
|
+
JSON::NestingError,
|
40
|
+
JSON::ParserError
|
41
|
+
error_msg = "Error parsing response body: #{response.body} from URL (#{self.to_redacted_api_token_string(clientUri)}), failed with error #{$!}"
|
42
|
+
self.check_fallback_or_raise(dtxDssClientPath, error_msg)
|
43
|
+
return dtxDssClientPath
|
44
|
+
end
|
45
|
+
|
46
|
+
# parse url
|
47
|
+
remoteClientUrl = responseJson["dssClientUrl"]
|
48
|
+
if remoteClientUrl.nil? or remoteClientUrl.empty?
|
49
|
+
error_msg = "No value for dssClientUrl in response body (#{response.body})."
|
50
|
+
self.check_fallback_or_raise(dtxDssClientPath, error_msg)
|
51
|
+
return dtxDssClientPath
|
52
|
+
end
|
53
|
+
UI.message "Remote DSS client: #{remoteClientUrl}"
|
54
|
+
|
55
|
+
# check/update local state
|
56
|
+
if !File.directory?(dynatraceDir)
|
57
|
+
Dir.mkdir(dynatraceDir)
|
58
|
+
end
|
59
|
+
|
60
|
+
# only update if a file is missing or the local version is different
|
61
|
+
if !(File.exists?(versionFilePath) and
|
62
|
+
File.exists?(dtxDssClientPath) and
|
63
|
+
File.read(versionFilePath) == remoteClientUrl and
|
64
|
+
File.size(dtxDssClientPath) > 0)
|
65
|
+
updatedClient = false
|
66
|
+
|
67
|
+
# extract and save client
|
68
|
+
zipped_tmp = self.save_to_tempfile(remoteClientUrl)
|
69
|
+
if File.size(zipped_tmp) <= 0
|
70
|
+
error_msg = "Downloaded symbolication client archive is empty (0 bytes)."
|
71
|
+
self.check_fallback_or_raise(dtxDssClientPath, error_msg)
|
72
|
+
return dtxDssClientPath
|
73
|
+
end
|
74
|
+
|
75
|
+
begin
|
76
|
+
UI.message "Unzipping fetched file with MD5 hash: #{Digest::MD5.new << IO.read(zipped_tmp)}"
|
77
|
+
Zip::InputStream.open(zipped_tmp) do |unzipped|
|
78
|
+
entry = unzipped.get_next_entry
|
79
|
+
if (entry.name == dtxDssClientBin)
|
80
|
+
# remove old client
|
81
|
+
UI.message "Found a different remote #{dtxDssClientBin} client. Removing local version and updating."
|
82
|
+
File.delete(versionFilePath) if File.exist?(versionFilePath)
|
83
|
+
File.delete(dtxDssClientPath) if File.exist?(dtxDssClientPath)
|
84
|
+
|
85
|
+
# write new client
|
86
|
+
File.write(versionFilePath, remoteClientUrl)
|
87
|
+
IO.copy_stream(entry.get_input_stream, dtxDssClientPath)
|
88
|
+
FileUtils.chmod("+x", dtxDssClientPath)
|
89
|
+
updatedClient = true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
rescue Zip::DecompressionError,
|
93
|
+
Zip::DestinationFileExistsError,
|
94
|
+
Zip::EntryExistsError,
|
95
|
+
Zip::EntryNameError,
|
96
|
+
Zip::EntrySizeError,
|
97
|
+
Zip::GPFBit3Error,
|
98
|
+
Zip::InternalError,
|
99
|
+
Zip::CompressionMethodError
|
100
|
+
error_msg = "Could not update/extract #{dtxDssClientBin}, please try again."
|
101
|
+
self.check_fallback_or_raise(dtxDssClientPath, error_msg)
|
102
|
+
return dtxDssClientPath
|
103
|
+
end
|
104
|
+
|
105
|
+
if updatedClient
|
106
|
+
UI.success "Successfully updated DTXDssClient."
|
107
|
+
else
|
108
|
+
error_msg = "#{dtxDssClientBin} not found in served archive, please try again."
|
109
|
+
self.check_fallback_or_raise(dtxDssClientPath, error_msg)
|
110
|
+
return dtxDssClientPath
|
111
|
+
end
|
112
|
+
end
|
113
|
+
return dtxDssClientPath
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.get_base_url(params)
|
117
|
+
uri = URI.split(params[:server])
|
118
|
+
return uri[0] + '://' + uri[2]
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.get_host_name(params)
|
122
|
+
uri = URI.split(params[:server])
|
123
|
+
|
124
|
+
unless uri[2].nil?
|
125
|
+
return uri[2]
|
126
|
+
end
|
127
|
+
|
128
|
+
# no procotol prefix -> host name is with path
|
129
|
+
if uri[5][-1] == '/'
|
130
|
+
return uri[5][0..-2] # remove trailing /
|
131
|
+
else
|
132
|
+
return uri[5]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.put_android_symbols(params, bundleId)
|
137
|
+
path = "/api/config/v1/symfiles/#{params[:appId]}/#{bundleId}/ANDROID/#{params[:version]}/#{params[:versionStr]}"
|
138
|
+
|
139
|
+
req = Net::HTTP::Put.new(path, initheader = { 'Content-Type' => 'text/plain',
|
140
|
+
'Authorization' => "Api-Token #{params[:apitoken]}"} )
|
141
|
+
|
142
|
+
req.body = IO.read(params[:symbolsfile])
|
143
|
+
http = Net::HTTP.new(self.get_host_name(params), 443)
|
144
|
+
http.use_ssl = true
|
145
|
+
http.request(req)
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
def self.check_fallback_or_raise(fallback_client, error)
|
150
|
+
UI.important "If this error persists create an issue on our Github project (https://github.com/Dynatrace/fastlane-plugin-dynatrace/issues) or contact our support at https://www.dynatrace.com/support/contact-support/."
|
151
|
+
UI.important error
|
152
|
+
if File.exists?(fallback_client) and File.size(fallback_client) > 0
|
153
|
+
UI.important "Using cached client: #{fallback_client}"
|
154
|
+
else
|
155
|
+
UI.important "No cached fallback found."
|
156
|
+
raise error
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# assumes the token parameter is appended last (there is only one parameter anyway)
|
161
|
+
def self.to_redacted_api_token_string(url)
|
162
|
+
urlStr = url.to_s
|
163
|
+
str = "Api-Token="
|
164
|
+
idx = urlStr.index(str)
|
165
|
+
token_len = urlStr.length - idx - str.length
|
166
|
+
urlStr[idx + str.length..idx + str.length + token_len] = "-" * token_len
|
167
|
+
return urlStr
|
168
|
+
end
|
169
|
+
|
170
|
+
# for test mocking
|
171
|
+
def self.save_to_tempfile(url)
|
172
|
+
open(url)
|
13
173
|
end
|
14
174
|
end
|
15
175
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-dynatrace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- Dynatrace LLC
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -128,16 +128,16 @@ dependencies:
|
|
128
128
|
requirements:
|
129
129
|
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: 2.
|
131
|
+
version: 2.142.0
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: 2.
|
139
|
-
description:
|
140
|
-
email:
|
138
|
+
version: 2.142.0
|
139
|
+
description:
|
140
|
+
email: mobile.agent@dynatrace.com
|
141
141
|
executables: []
|
142
142
|
extensions: []
|
143
143
|
extra_rdoc_files: []
|
@@ -152,7 +152,7 @@ homepage: https://github.com/Dynatrace/fastlane-plugin-dynatrace
|
|
152
152
|
licenses:
|
153
153
|
- MIT
|
154
154
|
metadata: {}
|
155
|
-
post_install_message:
|
155
|
+
post_install_message:
|
156
156
|
rdoc_options: []
|
157
157
|
require_paths:
|
158
158
|
- lib
|
@@ -167,8 +167,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
167
167
|
- !ruby/object:Gem::Version
|
168
168
|
version: '0'
|
169
169
|
requirements: []
|
170
|
-
rubygems_version: 3.
|
171
|
-
signing_key:
|
170
|
+
rubygems_version: 3.1.4
|
171
|
+
signing_key:
|
172
172
|
specification_version: 4
|
173
173
|
summary: This action processes and uploads your symbol files to Dynatrace
|
174
174
|
test_files: []
|