fastlane-plugin-dynatrace 1.0.1 → 1.0.5

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: 0d33abfce5cb589461d864dbb9dd94561b35d4c70079456144c2b15a71b23493
4
- data.tar.gz: 41e48c8dc20f1258efd8ec0e0fa24768eba1b2ab344c103ec85abc4192c4f151
3
+ metadata.gz: 2a4bb1f32d9beb111e957c5247455a16fac1fdc31ab91efd0f3821e3bffdd707
4
+ data.tar.gz: 62b51b71bce9470c3fdae48c6c1d83d59d2f92430729a3b289e51465460b5e3e
5
5
  SHA512:
6
- metadata.gz: 335db4d66fedc928e82ed2aa3e1394ef0c6cea0010cd8d601417bbf4b55980e6ba654c9392edfd67aca67d67eceb98d09cc6092212af8ecf79e6039f904f0a0c
7
- data.tar.gz: c4f8d63463b4edf3c5c50724784372f403d2d2ea35d7da7f3af4278631d0010a14a904bec8a0ceefcbdd4ddf41e1bf709557ca902a3f1e3cb4fa03070610719a
6
+ metadata.gz: 3ff43ef6e41476bcaa630ac0fedb477349be4abe547ca301b5950de67528270795b4d1f6eefa5fee5d5fc22a2405739c969aead5dc35289ce299d0ee802b20e6
7
+ data.tar.gz: 2b52fe099e14027b341f6ec05056303772e9359e4707e96b610388a979fe9c1e24963938151ad76135afac467a4a95eea6ffa3ca86e997c1550a8528b9e6c2f4
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
- ### 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.
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
- 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.
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
- ## Action: `dynatrace_process_symbols`
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
- | Supported Platforms | ios, android |
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
- 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.
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
- ### 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`).
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
- > 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.
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 (iOS) / package (Android)>",
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.
56
73
 
57
- ## If you are NOT using Bitcode, or if you have already downloaded your new symbols from App Store Connect manually.
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.
75
+
76
+
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.
58
86
 
59
- ### Supply all parameters locally
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,27 @@ 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`). | |
117
+ | cleanBuildArtifacts | Clean build artifacts after processing. | `true` |
90
118
  | debugMode | Enable debug logging. | false |
91
119
 
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.
120
+ ## App Store Connect Two-Factor-Authentication
121
+ 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.
94
122
 
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.
123
+ 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.
96
124
 
97
125
  ### Fastlane Session
98
126
  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
@@ -100,14 +128,16 @@ The full documentation for this can be found on the [fastlane docs](https://docs
100
128
 
101
129
  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
130
 
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.
131
+ > Note:
132
+ > - Session is only valid for the "region" you created it in. If your CI is in a different geographical location, the authentication might fail
133
+ > - 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
107
134
 
108
135
  ## Example
109
136
  Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
110
137
 
138
+ ## Tests
139
+ This plugin includes a set of RSpec unit tests, which can be executed by running `bundle exec rspec spec`.
140
+
111
141
  ## Issues and Feedback
112
142
  For any other issues and feedback about this plugin, please submit it to this repository or contact [Dynatrace Support](https://support.dynatrace.com).
113
143
 
@@ -118,4 +148,4 @@ If you have trouble using plugins, check out the [Plugins Troubleshooting](https
118
148
  For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/).
119
149
 
120
150
  ## About _fastlane_
121
- _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).
151
+ _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 "fileutils"
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
 
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]}"
56
+ end
57
+
58
+ # iOS/tvOS workflow
29
59
  dtxDssClientPath = Helper::DynatraceHelper.get_dss_client(params)
30
60
 
31
61
  dsym_paths = []
32
62
  symbolFilesKey = "symbolsfile" # default to iOS
33
63
 
34
- if params[:os] == "ios"
35
- if params[:downloadDsyms] == true
36
- UI.message "Downloading dSYMs from App Store Connect"
37
- startTime = Time.now
64
+ if !OS.mac?
65
+ UI.user_error! "A macOS machine is required to process iOS symbols."
66
+ end
38
67
 
39
- UI.message "Checking AppFile for possible username/AppleID"
40
- username = CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
41
- if username
42
- UI.message "Using #{username} from your AppFile"
43
- else
44
- username = params[:username]
45
- UI.message "Didn't find a username in AppFile, using passed username parameter: #{params[:username]}"
46
- end
68
+ if params[:downloadDsyms] == true
69
+ UI.message "Downloading dSYMs from App Store Connect"
70
+ startTime = Time.now
47
71
 
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
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
67
80
 
68
- if (Time.now - startTime) > params[:waitForDsymProcessingTimeout]
69
- UI.user_error!("Timeout during dSYM download. Try increasing :waitForDsymProcessingTimeout.")
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)
70
98
  end
99
+ end
100
+
101
+ if (Time.now - startTime) > params[:waitForDsymProcessingTimeout]
102
+ UI.user_error!("Timeout during dSYM download. Try increasing :waitForDsymProcessingTimeout.")
103
+ end
71
104
 
72
- dsym_paths += Actions.lane_context[SharedValues::DSYM_PATHS] if Actions.lane_context[SharedValues::DSYM_PATHS]
105
+ dsym_paths += Actions.lane_context[SharedValues::DSYM_PATHS] if Actions.lane_context[SharedValues::DSYM_PATHS]
73
106
 
74
- if dsym_paths.count > 0
75
- UI.message "Downloaded the dSYMs from App Store Connect. Paths: #{dsym_paths}"
76
- else
77
- raise 'No dSYM paths found!'
78
- end
107
+ if dsym_paths.count > 0
108
+ UI.message "Downloaded the dSYMs from App Store Connect. Paths: #{dsym_paths}"
79
109
  else
80
- UI.error "dSYM download disabled, using local path (#{params[:symbolsfile]})"
81
- dsym_paths << params[:symbolsfile] if params[:symbolsfile]
110
+ raise 'No dSYM paths found!'
82
111
  end
83
-
84
- else # android
85
- dsym_paths << params[:symbolsfile] if params[:symbolsfile]
86
- symbolFilesKey = "file"
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.get_server_base_url(params)}\""
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
 
@@ -130,8 +158,10 @@ module Fastlane
130
158
  end
131
159
  })
132
160
 
133
- UI.message "Cleaning build artifacts"
134
- Fastlane::Actions::CleanBuildArtifactsAction.run(exclude_pattern: nil)
161
+ if params[:cleanBuildArtifacts]
162
+ UI.message "Cleaning build artifacts"
163
+ Fastlane::Actions::CleanBuildArtifactsAction.run(exclude_pattern: nil)
164
+ end
135
165
  end
136
166
 
137
167
  def self.description
@@ -177,11 +207,11 @@ module Fastlane
177
207
 
178
208
  FastlaneCore::ConfigItem.new(key: :os,
179
209
  env_name: "FL_UPLOAD_TO_DYNATRACE_OS",
180
- description: "The type of the symbol files, either \"ios\" or \"android\"",
210
+ description: "The type of the symbol files, either \"ios\", \"tvos\" or \"android\"",
181
211
  sensitive: false,
182
212
  optional: false,
183
213
  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"))
214
+ 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
215
  end),
186
216
 
187
217
  FastlaneCore::ConfigItem.new(key: :apitoken,
@@ -238,6 +268,12 @@ module Fastlane
238
268
  UI.user_error!("Please provide your environment API endpoint. Pass using `server: 'server'`") unless (value and not value.empty?)
239
269
  end),
240
270
 
271
+ FastlaneCore::ConfigItem.new(key: :cleanBuildArtifacts,
272
+ env_name: "FL_UPLOAD_TO_DYNATRACE_CLEAN_BUILD_ARTIFACTS",
273
+ default_value: true,
274
+ is_string: false,
275
+ description: "Clean build artifacts after processing"),
276
+
241
277
  FastlaneCore::ConfigItem.new(key: :debugMode,
242
278
  env_name: "FL_UPLOAD_TO_DYNATRACE_DEBUG_MODE",
243
279
  description: "Enable debug logging",
@@ -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 (params.all_keys.include? :dtxDssClientPath and not params[:dtxDssClientPath].nil?)
15
- UI.message "DEPRECATION WARNING: DTXDssClientPath doesn't need to be specified anymore, the DTXDssClient is downloaded and updated automatically."
16
- dtxDssClientPath = params[:dtxDssClientPath]
17
- else
18
- # get latest version info
19
- clientUri = URI("#{self.get_server_base_url(params)}/api/config/v1/symfiles/dtxdss-download?Api-Token=#{params[:apitoken]}")
20
- response = Net::HTTP.get_response(clientUri)
21
-
22
- if not response.kind_of? Net::HTTPSuccess
23
- base_error = "Couldn't update DTXDssClient (invalid response: #{response.message} (#{response.code})) for URL: #{clientUri})"
24
- if File.exists?("#{dynatraceDir}/#{dtxDssClientBin}")
25
- UI.important base_error
26
- UI.important "Using cached DTXDssClient: #{dynatraceDir}/#{dtxDssClientBin}"
27
- return dtxDssClientPath
28
- else
29
- UI.user_error! base_error
30
- end
31
- end
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
- remoteClientUrl = JSON.parse(response.body)["dssClientUrl"]
34
- UI.message "Remote DSS client: #{remoteClientUrl}"
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
- # check local state
37
- if (!File.directory?(dynatraceDir))
38
- Dir.mkdir(dynatraceDir)
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
- if (!File.exists?("#{dynatraceDir}/#{versionFile}") or
42
- !File.exists?("#{dynatraceDir}/#{dtxDssClientBin}") or
43
- File.read("#{dynatraceDir}/#{versionFile}") != remoteClientUrl)
44
- # update local state
45
- UI.message "Found a different remote DTXDssClient client. Updating local version."
46
- File.delete("#{dynatraceDir}/#{versionFile}") if File.exist?("#{dynatraceDir}/#{versionFile}")
47
- File.delete("#{dynatraceDir}/#{dtxDssClientBin}") if File.exist?("#{dynatraceDir}/#{dtxDssClientBin}")
48
-
49
- File.write("#{dynatraceDir}/#{versionFile}", remoteClientUrl)
50
-
51
- # get client from served archive
52
- open(remoteClientUrl) do |zipped|
53
- Zip::InputStream.open(zipped) do |unzipped|
54
- entry = unzipped.get_next_entry
55
- if (entry.name == dtxDssClientBin)
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.get_server_base_url(params)
67
- if params[:server][-1] == '/'
68
- return params[:server][0..-2]
116
+ def self.without_trailing_slash(server)
117
+ if server[-1] == '/'
118
+ return server[0..-2]
69
119
  else
70
- return params[:server]
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
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Dynatrace
3
- VERSION = "1.0.1"
3
+ VERSION = "1.0.5"
4
4
  end
5
5
  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.1
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dynatrace LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-03 00:00:00.000000000 Z
11
+ date: 2021-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -167,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
167
  - !ruby/object:Gem::Version
168
168
  version: '0'
169
169
  requirements: []
170
- rubygems_version: 3.2.3
170
+ rubygems_version: 3.1.4
171
171
  signing_key:
172
172
  specification_version: 4
173
173
  summary: This action processes and uploads your symbol files to Dynatrace