fastlane-plugin-dynatrace 1.0.0 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99b5f6070d7eb68fccc4b912a89c4ae9a15814f041baa97c683a2f8c1b9e8590
|
4
|
+
data.tar.gz: c547493757afd54cc2f256debbba3487dd73cfab0aa23c961b1a641220c18ab9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b06bfc63cf1707d8e9d33d2c0f36046a848f43adfa1babac635e8dfb8a7d42dfc85cd19f25d49453de686c5577ab1910fe6f2a3fe77af885a97b926210d350d
|
7
|
+
data.tar.gz: 594bd21d1c29fc5b59adda5307ba4a7b064fb345d4d984446ecc143732265757a1810db839c9dceb069f72d49c9cb2a19b7dbbc19d6fe54e1b3a9547c3e87e32
|
data/README.md
CHANGED
@@ -10,30 +10,45 @@ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To
|
|
10
10
|
fastlane add_plugin dynatrace
|
11
11
|
```
|
12
12
|
|
13
|
-
|
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.
|
13
|
+
⚠️ The way Apple introduced the two-factor authentication interferes with a fully automated workflow. The workaround requires manual interaction. For more information, see **App Store Connect Two-Factor-Authentication** section below for details.
|
15
14
|
|
16
15
|
## About the Dynatrace fastlane plugin
|
17
|
-
|
16
|
+
The Dynatrace fastlane plugin manages uploading symbol files (iOS, tvOS) or obfuscation mapping files (Android) to the Dynatrace cluster. Symbol and mapping files are used to make reported stack traces human-readable. The plugin also allows to download the latest dSYM files from App Store Connect, which enables full automation of the mobile app deployment, and the pre-processing of dSYM files, a step that is necessary for the Dynatrace cluster to be able to symbolicate.
|
18
17
|
|
19
|
-
|
18
|
+
The plugin provides a single action `dynatrace_process_symbols`. The configuration depends on whether the app is (A) iOS and Bitcode-enabled or (B) iOS/tvOS and not Bitcode-enabled or an Android app.
|
20
19
|
|
21
|
-
|
22
|
-
|---------------------|--------------|
|
20
|
+
For Bitcode-enabled iOS apps we recommend to let the plugin handle the download of the dSYM files from App Store Connect and upload to Dynatrace.
|
23
21
|
|
24
|
-
## Is your app Bitcode enabled?
|
25
|
-
> Only applies for apps distributed via the AppStore or TestFlight.
|
26
22
|
|
27
|
-
|
23
|
+
## Usage
|
24
|
+
To get started, ask your Dynatrace administrator for an [API token ](https://www.dynatrace.com/support/help/shortlink/api-authentication) with **Mobile symbolication file management** permission . To generate the API token, go to **Integration** > **Dynatrace API**. The token is used by the authenticate the plugin into Dynatrace and upload the symbol and mapping files.
|
28
25
|
|
29
|
-
|
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`).
|
26
|
+
Add the action `dynatrace_process_symbols` to your Fastfile. You'll find all the configuration options and a default configuration later in the readme.
|
31
27
|
|
32
|
-
|
28
|
+
Now, when you run fastlane, the Dynatrace plugin will manage the symbol files of your app as configured.
|
29
|
+
|
30
|
+
### Dynatrace Managed (1.195 and earlier)
|
31
|
+
For cluster versions 1.195 and earlier, the Dynatrace application 'Symbolication Client' has to be downloaded manually and explicitly specified (`dtxDssClientPath`). For all cluster versions above 1.195 it is 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.
|
32
|
+
|
33
|
+
|
34
|
+
## A) Bitcode-enabled iOS apps
|
35
|
+
This is the right approach if your app is distributed via Apple's App Store or TestFlight and Bitcode-enabled. For all other cases, follow the approach B below.
|
36
|
+
|
37
|
+
Background: If your app is Bitcode-enabled, then the dSYMs that are generated during the Xcode build are **not** the dSYMs that need to be uploaded to Dynatrace. 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*, processed and uploaded to Dynatrace.
|
38
|
+
|
39
|
+
### Automatically downloading dSYMs
|
40
|
+
|
41
|
+
To fully automate the following five-step workflow, add the snippets below to the respective files and fill in the placeholders. Uploading the app the App Store Connect is a necessary prerequisite and either handled manually or by fastlane directly:
|
42
|
+
|
43
|
+
1. Wait until the build is processed
|
44
|
+
2. Download the resulting dSYM files
|
45
|
+
3. Process dSYM files into the format that Dynatrace requires
|
46
|
+
4. Upload processed dSYM files to Dynatrace
|
33
47
|
|
34
|
-
### Automatically downloading dSYMs and using AppFile for authentication
|
35
48
|
|
36
49
|
#### AppFile
|
50
|
+
Make sure the following information is present in `AppFile` to authenticate with App Store Connect.
|
51
|
+
|
37
52
|
```ruby
|
38
53
|
app_identifier("com.yourcompany.yourappID") # bundle identifier of your app
|
39
54
|
apple_id("user@email.com")
|
@@ -45,7 +60,7 @@ dynatrace_process_symbols(
|
|
45
60
|
appId: "<Dynatrace application ID>",
|
46
61
|
apitoken: "<Dynatrace API token>",
|
47
62
|
os: "ios",
|
48
|
-
bundleId: "<CFBundlebundleId
|
63
|
+
bundleId: "<CFBundlebundleId>",
|
49
64
|
versionStr: "<Build Number (CFBundleVersion)>",
|
50
65
|
version: "<App Version (CFBundleShortVersionString)>",
|
51
66
|
server: "<Dynatrace Environment URL>",
|
@@ -53,18 +68,30 @@ dynatrace_process_symbols(
|
|
53
68
|
)
|
54
69
|
```
|
55
70
|
|
71
|
+
#### Waiting time between app upload and dSYM file download
|
72
|
+
After a completed upload to App Store Connect, there is some waiting time before the dSYM files are ready to be downloaded. The Dynatrace fastlane plugin waits and downloads the symbol files if setting the `waitForDsymProcessing` is true and a waiting period is provided via `waitForDsymProcessingTimeout`. We recommend 1800 seconds (30 mins) as the default waiting time. In our experience, this is sufficiently long for the processing to happen. If this duration is not long enough, you need to increase it.
|
73
|
+
|
74
|
+
> Note: this timeout is the **maximum** waiting time. If the symbol files are ready sooner, the plugin will continue to the download and will not wait for the whole duration of the timeout.
|
56
75
|
|
57
|
-
## If you are NOT using Bitcode, or if you have already downloaded your new symbols from App Store Connect manually.
|
58
76
|
|
59
|
-
|
77
|
+
## B) Not Bitcode-enabled iOS/tvOS apps or Android apps
|
78
|
+
If at least one of the following conditions is true, you should follow this approach:
|
79
|
+
|
80
|
+
* **not** using Bitcode for your iOS/tvOS app
|
81
|
+
* already downloaded the new symbol files from App Store Connect manually
|
82
|
+
* deploy an Android app
|
83
|
+
|
84
|
+
#### Fastfile
|
85
|
+
Use the parameter `symbolsfile` to provide a relative path to the symbols file.
|
86
|
+
|
60
87
|
```ruby
|
61
88
|
dynatrace_process_symbols(
|
62
89
|
appId: "<Dynatrace application ID>",
|
63
90
|
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)>",
|
91
|
+
os: "<ios>, <tvos> or <android>",
|
92
|
+
bundleId: "<CFBundlebundleId (iOS, tvOS) / package (Android)>",
|
93
|
+
versionStr: "<CFBundleShortVersionString (iOS, tvOS) / versionName (Android)>",
|
94
|
+
version: "<CFBundleVersion (iOS, tvOS) / versionCode (Android)>",
|
68
95
|
server: "<Dynatrace Environment URL>",
|
69
96
|
symbolsfile: "<Symbols File Path>"
|
70
97
|
)
|
@@ -73,26 +100,43 @@ dynatrace_process_symbols(
|
|
73
100
|
## List of all Parameters
|
74
101
|
| Key | Description | default value |
|
75
102
|
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
|
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`. | |
|
103
|
+
| action | *(iOS/tvOS only)* Action to be performed by DTXDssClient (`upload` or `decode`). | `upload` |
|
104
|
+
| downloadDsyms | *(iOS/tvOS only)* Download the dSYMs from App Store Connect. | `false` |
|
105
|
+
| waitForDsymProcessing | *(iOS/tvOS only)* Wait for dSYM processing to be finished. | `true` |
|
106
|
+
| waitForDsymProcessingTimeout | *(iOS/tvOS only)* Timeout in seconds to wait for the dSYMs be downloadable. | `1800` |
|
107
|
+
| username | *(iOS/tvOS only)* The username/AppleID to use to download the dSYMs. Alternatively you can specify this in your AppFile as `apple_id`. | |
|
108
|
+
| os | The type of the symbol files, either `ios`, `tvOS` or `android`. | |
|
82
109
|
| apitoken | Dynatrace API token with mobile symbolication permissions. | |
|
83
110
|
| dtxDssClientPath | **(DEPRECATED)** The path to your DTXDssClient. The DTXDssClient is downloaded and updated automatically, unless this key is set. | |
|
84
111
|
| 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. | |
|
112
|
+
| bundleId | The CFBundlebundleId (iOS, tvOS) / package (Android) of the application. Alternatively you can specify this in your AppFile as `app_identifier`. | |
|
113
|
+
| versionStr | The CFBundleShortVersionString (iOS, tvOS) / versionName (Android) | |
|
114
|
+
| version | The CFBundleVersion (iOS, tvOS) / versionCode (Android). Is also used for the dSYM download. | |
|
88
115
|
| symbolsfile | Path to the dSYM file to be processed. If downloadDsyms is set, this is only a fallback. | |
|
89
116
|
| server | The API endpoint for the Dynatrace environment (e.g. `https://environmentID.live.dynatrace.com` or `https://dynatrace-managed.com/e/environmentID`). | |
|
90
117
|
| debugMode | Enable debug logging. | false |
|
91
118
|
|
119
|
+
## App Store Connect Two-Factor-Authentication
|
120
|
+
When the plugin is used to download symbols from *App Store Connect* automatically (`downloadDsyms: true`), valid App Store Connect credentials with access to 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.
|
121
|
+
|
122
|
+
Apple started enforcing 2-Factor-Authentication for the *App Store Connect* API in February 2021. This [limits the ability to automate the symbol processing](https://github.com/fastlane/fastlane/discussions/17655), 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.
|
123
|
+
|
124
|
+
### Fastlane Session
|
125
|
+
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
|
126
|
+
) under **spaceauth**.
|
127
|
+
|
128
|
+
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).
|
129
|
+
|
130
|
+
> Note:
|
131
|
+
> - Session is only valid for the "region" you created it in. If your CI is in a different geographical location, the authentication might fail
|
132
|
+
> - Generated sessions are valid for up to one month. Apple's API doesn't specify details about that, so it is only noticable by a failing build
|
92
133
|
|
93
134
|
## Example
|
94
135
|
Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
|
95
136
|
|
137
|
+
## Tests
|
138
|
+
This plugin includes a set of RSpec unit tests, which can be executed by running ` bundle exec rspec spec`.
|
139
|
+
|
96
140
|
## Issues and Feedback
|
97
141
|
For any other issues and feedback about this plugin, please submit it to this repository or contact [Dynatrace Support](https://support.dynatrace.com).
|
98
142
|
|
@@ -103,4 +147,4 @@ If you have trouble using plugins, check out the [Plugins Troubleshooting](https
|
|
103
147
|
For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
|
104
148
|
|
105
149
|
## About _fastlane_
|
106
|
-
_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).
|
150
|
+
_fastlane_ is the easiest way to automate beta deployments and releases for your iOS, tvOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
|
@@ -2,7 +2,9 @@ require 'fastlane/action'
|
|
2
2
|
require 'net/http'
|
3
3
|
require 'open-uri'
|
4
4
|
require 'zip'
|
5
|
-
require
|
5
|
+
require 'fileutils'
|
6
|
+
require 'os'
|
7
|
+
require 'json'
|
6
8
|
require_relative '../helper/dynatrace_helper'
|
7
9
|
|
8
10
|
module Fastlane
|
@@ -26,64 +28,90 @@ module Fastlane
|
|
26
28
|
UI.message "BundleID: #{bundleId}"
|
27
29
|
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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 = nil
|
44
|
+
unless response.body.nil?
|
45
|
+
message = JSON.parse(response.body)["error"]["message"]
|
46
|
+
end
|
47
|
+
if message.nil?
|
48
|
+
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/)."
|
49
|
+
else
|
50
|
+
UI.user_error! "Symbol upload error (Response Code: #{response.code}). #{message}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
return
|
54
|
+
elsif params[:os] != "ios" && params[:os] != "tvos"
|
55
|
+
UI.user_error! "Unsopported value os=#{params[:os]}"
|
36
56
|
end
|
37
57
|
|
58
|
+
# iOS/tvOS workflow
|
38
59
|
dtxDssClientPath = Helper::DynatraceHelper.get_dss_client(params)
|
39
60
|
|
40
61
|
dsym_paths = []
|
41
62
|
symbolFilesKey = "symbolsfile" # default to iOS
|
42
63
|
|
43
|
-
if
|
44
|
-
|
45
|
-
|
46
|
-
startTime = Time.now
|
47
|
-
|
48
|
-
# it takes a couple of minutes until the new build is available through the API
|
49
|
-
# -> retry until available
|
50
|
-
while params[:waitForDsymProcessing] and # wait is active
|
51
|
-
!lane_context[SharedValues::DSYM_PATHS] and # has dsym path
|
52
|
-
(Time.now - startTime) < params[:waitForDsymProcessingTimeout] # is in time
|
53
|
-
|
54
|
-
Actions::DownloadDsymsAction.run(wait_for_dsym_processing: params[:waitForDsymProcessing],
|
55
|
-
wait_timeout: (params[:waitForDsymProcessingTimeout] - (Time.now - startTime)).round(0), # remaining timeout
|
56
|
-
app_identifier: bundleId,
|
57
|
-
username: username,
|
58
|
-
version: params[:version],
|
59
|
-
build_number: params[:versionStr],
|
60
|
-
platform: :ios) # should be optional (Hint: it's not)
|
61
|
-
|
62
|
-
if !lane_context[SharedValues::DSYM_PATHS] and (Time.now - startTime) < params[:waitForDsymProcessingTimeout]
|
63
|
-
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)."
|
64
|
-
sleep(60)
|
65
|
-
end
|
66
|
-
end
|
64
|
+
if !OS.mac?
|
65
|
+
UI.user_error! "A macOS machine is required to process iOS symbols."
|
66
|
+
end
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
if params[:downloadDsyms] == true
|
69
|
+
UI.message "Downloading dSYMs from App Store Connect"
|
70
|
+
startTime = Time.now
|
71
71
|
|
72
|
-
|
72
|
+
UI.message "Checking AppFile for possible username/AppleID"
|
73
|
+
username = CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
|
74
|
+
if username
|
75
|
+
UI.message "Using #{username} from your AppFile"
|
76
|
+
else
|
77
|
+
username = params[:username]
|
78
|
+
UI.message "Didn't find a username in AppFile, using passed username parameter: #{params[:username]}"
|
79
|
+
end
|
73
80
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
81
|
+
# it takes a couple of minutes until the new build is available through the API
|
82
|
+
# -> retry until available
|
83
|
+
while params[:waitForDsymProcessing] and # wait is active
|
84
|
+
!lane_context[SharedValues::DSYM_PATHS] and # has dsym path
|
85
|
+
(Time.now - startTime) < params[:waitForDsymProcessingTimeout] # is in time
|
86
|
+
|
87
|
+
Actions::DownloadDsymsAction.run(wait_for_dsym_processing: params[:waitForDsymProcessing],
|
88
|
+
wait_timeout: (params[:waitForDsymProcessingTimeout] - (Time.now - startTime)).round(0), # remaining timeout
|
89
|
+
app_identifier: bundleId,
|
90
|
+
username: username,
|
91
|
+
version: params[:version],
|
92
|
+
build_number: params[:versionStr],
|
93
|
+
platform: params[:os] == "ios" ? :ios : :appletvos)
|
94
|
+
|
95
|
+
if !lane_context[SharedValues::DSYM_PATHS] and (Time.now - startTime) < params[:waitForDsymProcessingTimeout]
|
96
|
+
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)."
|
97
|
+
sleep(60)
|
78
98
|
end
|
79
|
-
else
|
80
|
-
UI.error "dSYM download disabled, using local path (#{params[:symbolsfile]})"
|
81
|
-
dsym_paths << params[:symbolsfile] if params[:symbolsfile]
|
82
99
|
end
|
83
100
|
|
84
|
-
|
85
|
-
|
86
|
-
|
101
|
+
if (Time.now - startTime) > params[:waitForDsymProcessingTimeout]
|
102
|
+
UI.user_error!("Timeout during dSYM download. Try increasing :waitForDsymProcessingTimeout.")
|
103
|
+
end
|
104
|
+
|
105
|
+
dsym_paths += Actions.lane_context[SharedValues::DSYM_PATHS] if Actions.lane_context[SharedValues::DSYM_PATHS]
|
106
|
+
|
107
|
+
if dsym_paths.count > 0
|
108
|
+
UI.message "Downloaded the dSYMs from App Store Connect. Paths: #{dsym_paths}"
|
109
|
+
else
|
110
|
+
raise 'No dSYM paths found!'
|
111
|
+
end
|
112
|
+
else
|
113
|
+
UI.important "dSYM download disabled, using local path (#{params[:symbolsfile]})"
|
114
|
+
dsym_paths << params[:symbolsfile] if params[:symbolsfile]
|
87
115
|
end
|
88
116
|
|
89
117
|
# check if we have dSYMs to proceed with
|
@@ -106,7 +134,7 @@ module Fastlane
|
|
106
134
|
command << "versionStr=\"#{params[:versionStr]}\""
|
107
135
|
command << "version=\"#{params[:version]}\""
|
108
136
|
command << symbolFilesCommandSnippet
|
109
|
-
command << "server=\"#{Helper::DynatraceHelper.
|
137
|
+
command << "server=\"#{Helper::DynatraceHelper.without_trailing_slash(params[:server])}\""
|
110
138
|
command << "DTXLogLevel=ALL -verbose" if params[:debugMode] == true
|
111
139
|
command << "forced=1" # if the file already exists
|
112
140
|
|
@@ -177,11 +205,11 @@ module Fastlane
|
|
177
205
|
|
178
206
|
FastlaneCore::ConfigItem.new(key: :os,
|
179
207
|
env_name: "FL_UPLOAD_TO_DYNATRACE_OS",
|
180
|
-
description: "The type of the symbol files, either \"ios\" or \"android\"",
|
208
|
+
description: "The type of the symbol files, either \"ios\", \"tvos\" or \"android\"",
|
181
209
|
sensitive: false,
|
182
210
|
optional: false,
|
183
211
|
verify_block: proc do |value|
|
184
|
-
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"))
|
212
|
+
UI.user_error!("Please specify the type of the symbol files. Possible values are \"ios\", \"tvos\" or \"android\".") unless (value and not value.empty? and (value == "ios" || value == "tvos" || value =="android"))
|
185
213
|
end),
|
186
214
|
|
187
215
|
FastlaneCore::ConfigItem.new(key: :apitoken,
|
@@ -1,4 +1,8 @@
|
|
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")
|
@@ -7,68 +11,178 @@ module Fastlane
|
|
7
11
|
class DynatraceHelper
|
8
12
|
def self.get_dss_client(params)
|
9
13
|
dynatraceDir = "dynatrace"
|
10
|
-
versionFile = "version"
|
11
14
|
dtxDssClientBin = "DTXDssClient"
|
15
|
+
versionFilePath = "#{dynatraceDir}/version"
|
12
16
|
dtxDssClientPath = "#{dynatraceDir}/#{dtxDssClientBin}"
|
13
17
|
|
14
|
-
if
|
15
|
-
UI.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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.without_trailing_slash(params[:server])}/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
|
32
59
|
|
33
|
-
|
34
|
-
|
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
|
35
66
|
|
36
|
-
#
|
37
|
-
|
38
|
-
|
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
|
39
73
|
end
|
40
74
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
IO.copy_stream(entry.get_input_stream, "#{dynatraceDir}/#{dtxDssClientBin}")
|
57
|
-
FileUtils.chmod("+x", "#{dynatraceDir}/#{dtxDssClientBin}")
|
58
|
-
end
|
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
|
59
90
|
end
|
60
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
|
61
111
|
end
|
62
112
|
end
|
63
113
|
return dtxDssClientPath
|
64
114
|
end
|
65
115
|
|
66
|
-
def self.
|
67
|
-
if
|
68
|
-
return
|
116
|
+
def self.without_trailing_slash(server)
|
117
|
+
if server[-1] == '/'
|
118
|
+
return server[0..-2]
|
69
119
|
else
|
70
|
-
return
|
120
|
+
return server
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.get_host_name(params)
|
125
|
+
uri = URI.split(params[:server])
|
126
|
+
|
127
|
+
unless uri[2].nil?
|
128
|
+
return uri[2]
|
71
129
|
end
|
130
|
+
|
131
|
+
# no procotol prefix -> host name is with path
|
132
|
+
if uri[5][-1] == '/'
|
133
|
+
return uri[5][0..-2] # remove trailing /
|
134
|
+
else
|
135
|
+
return uri[5]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.put_android_symbols(params, bundleId)
|
140
|
+
path = "/api/config/v1/symfiles/#{params[:appId]}/#{bundleId}/ANDROID/#{params[:version]}/#{params[:versionStr]}"
|
141
|
+
|
142
|
+
# if path points to dynatrace managed, we need to prepend the path component from the server (/e/{your-environment-id})
|
143
|
+
if params[:server].include? '/e/'
|
144
|
+
uri = URI.split(params[:server])
|
145
|
+
if uri[5].nil?
|
146
|
+
UI.user_error! "The path component of your managed Dynatrace URL is empty. Does the server URL follow the correct pattern (https://{your-domain}/e/{your-environment-id})?"
|
147
|
+
end
|
148
|
+
path = self.without_trailing_slash(uri[5]) + path
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
req = Net::HTTP::Put.new(path, initheader = { 'Content-Type' => 'text/plain',
|
153
|
+
'Authorization' => "Api-Token #{params[:apitoken]}"} )
|
154
|
+
|
155
|
+
req.body = IO.read(params[:symbolsfile])
|
156
|
+
http = Net::HTTP.new(self.get_host_name(params), 443)
|
157
|
+
http.use_ssl = true
|
158
|
+
http.request(req)
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
def self.check_fallback_or_raise(fallback_client, error)
|
163
|
+
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/."
|
164
|
+
UI.important error
|
165
|
+
if File.exists?(fallback_client) and File.size(fallback_client) > 0
|
166
|
+
UI.important "Using cached client: #{fallback_client}"
|
167
|
+
else
|
168
|
+
UI.important "No cached fallback found."
|
169
|
+
raise error
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# assumes the token parameter is appended last (there is only one parameter anyway)
|
174
|
+
def self.to_redacted_api_token_string(url)
|
175
|
+
urlStr = url.to_s
|
176
|
+
str = "Api-Token="
|
177
|
+
idx = urlStr.index(str)
|
178
|
+
token_len = urlStr.length - idx - str.length
|
179
|
+
urlStr[idx + str.length..idx + str.length + token_len] = "-" * token_len
|
180
|
+
return urlStr
|
181
|
+
end
|
182
|
+
|
183
|
+
# for test mocking
|
184
|
+
def self.save_to_tempfile(url)
|
185
|
+
open(url)
|
72
186
|
end
|
73
187
|
end
|
74
188
|
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: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dynatrace LLC
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|