1
+ import * as tl from 'azure-pipelines-task-lib/task' ;
2
+ import msRestAzure = require( 'azure-pipelines-tasks-azure-arm-rest/azure-arm-common' ) ;
3
+ import Model = require( 'azure-pipelines-tasks-azure-arm-rest/azureModels' ) ;
4
+ import armStorage = require( 'azure-pipelines-tasks-azure-arm-rest/azure-arm-storage' ) ;
5
+ import BlobService = require( 'azp-tasks-az-blobstorage-provider/blobservice' ) ;
6
+ import { AzureEndpoint } from 'azure-pipelines-tasks-azure-arm-rest/azureModels' ;
7
+ import { AzureRMEndpoint } from 'azure-pipelines-tasks-azure-arm-rest/azure-arm-endpoint' ;
8
+
9
+ /**
10
+ * Class responsible for downloading artifacts from Azure Blob Storage.
11
+ *
12
+ * @class AzureStorageArtifactDownloader
13
+ *
14
+ * @property {string } connectedService - The connected service name.
15
+ * @property {string } azureStorageAccountName - The name of the Azure Storage account.
16
+ * @property {string } [azureResourceGroupName] - The name of the Azure Resource Group (optional).
17
+ * @property {string } containerName - The name of the container in Azure Blob Storage.
18
+ * @property {string } commonVirtualPath - The common virtual path within the container.
19
+ *
20
+ * @constructor
21
+ * @param {string } connectedService - The connected service name.
22
+ * @param {string } azureStorageAccountName - The name of the Azure Storage account.
23
+ * @param {string } containerName - The name of the container in Azure Blob Storage.
24
+ * @param {string } commonVirtualPath - The common virtual path within the container.
25
+ * @param {string } [azureResourceGroupName] - The name of the Azure Resource Group (optional).
26
+ *
27
+ * @method downloadArtifacts
28
+ * @async
29
+ * @param {string } downloadToPath - The local path where the artifacts will be downloaded.
30
+ * @param {string } fileType - The type of files to download. If not specified, defaults to all files ("**").
31
+ * @returns {Promise<void> } A promise that resolves when the download is complete.
32
+ * @throws Will throw an error if the requested URL is too long (status code 414) or any other error occurs during the download process.
33
+ *
34
+ * @method _getStorageAccountDetails
35
+ * @async
36
+ * @private
37
+ * @returns {Promise<StorageAccountInfo> } A promise that resolves to the storage account details.
38
+ *
39
+ * @method _legacyGetStorageAccount
40
+ * @async
41
+ * @private
42
+ * @param {armStorage.StorageManagementClient } storageArmClient - The Azure Storage Management client.
43
+ * @returns {Promise<Model.StorageAccount> } A promise that resolves to the legacy storage account details.
44
+ *
45
+ * @method _getStorageAccount
46
+ * @async
47
+ * @private
48
+ * @param {armStorage.StorageManagementClient } storageArmClient - The Azure Storage Management client.
49
+ * @returns {Promise<Model.StorageAccount> } A promise that resolves to the storage account details.
50
+ *
51
+ * @method _getStorageAccountWithResourceGroup
52
+ * @async
53
+ * @private
54
+ * @param {armStorage.StorageManagementClient } storageArmClient - The Azure Storage Management client.
55
+ * @param {string } resourceGroupName - The name of the Azure Resource Group.
56
+ * @param {string } storageAccountName - The name of the Azure Storage account.
57
+ * @returns {Promise<Model.StorageAccount | undefined> } A promise that resolves to the storage account details or undefined if not found.
58
+ *
59
+ * @method _getARMCredentials
60
+ * @async
61
+ * @private
62
+ * @returns {Promise<msRestAzure.ApplicationTokenCredentials> } A promise that resolves to the Azure Resource Manager credentials.
63
+ */
64
+ export class AzureStorageArtifactDownloader {
65
+ public connectedService : string ;
66
+ public azureStorageAccountName : string ;
67
+ public azureResourceGroupName ?: string ;
68
+ public containerName : string ;
69
+ public commonVirtualPath : string ;
70
+
71
+ constructor ( connectedService : string , azureStorageAccountName : string , containerName : string , commonVirtualPath : string , azureResourceGroupName ?: string ) {
72
+ this . connectedService = connectedService ;
73
+ this . azureStorageAccountName = azureStorageAccountName ;
74
+ this . azureResourceGroupName = azureResourceGroupName ;
75
+ this . containerName = containerName ;
76
+ this . commonVirtualPath = commonVirtualPath ;
77
+ }
78
+
79
+ /**
80
+ * Downloads artifacts from Azure Blob Storage to the specified local path.
81
+ *
82
+ * @param downloadToPath - The local path where the artifacts will be downloaded.
83
+ * @param fileType - The type of files to download. If not specified, defaults to all files ("**").
84
+ * @returns A promise that resolves when the download is complete.
85
+ * @throws Will throw an error if the requested URL is too long (status code 414) or any other error occurs during the download process.
86
+ */
87
+ public async downloadArtifacts ( downloadToPath : string , fileType : string ) : Promise < void > {
88
+ try {
89
+ console . log ( tl . loc ( 'DownloadFromAzureBlobStorage' , this . containerName ) ) ;
90
+
91
+ const endpointObject = await this . _getAzureRMEndpoint ( ) ;
92
+ const storageAccount : StorageAccountInfo = await this . _getStorageAccountDetails ( ) ;
93
+ const blobService = new BlobService . BlobService ( storageAccount . name , "" , "" , true , endpointObject ) ;
94
+ await blobService . downloadBlobs ( downloadToPath , this . containerName , this . commonVirtualPath , fileType || "**" ) ;
95
+
96
+ } catch ( e ) {
97
+ if ( e . statusCode === 414 ) throw new Error ( tl . loc ( 'RequestedUrlTooLong' ) ) ;
98
+ throw e ;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Retrieves the storage account details for the specified Azure Storage Account.
104
+ *
105
+ * This method first attempts to get the storage account details using the provided resource group name
106
+ * for a faster query. If the resource group name is not provided or the fast query fails, it falls back
107
+ * to a legacy query method.
108
+ *
109
+ * @returns {Promise<StorageAccountInfo> } A promise that resolves to the storage account information.
110
+ *
111
+ * @throws Will throw an error if the storage account details cannot be retrieved.
112
+ *
113
+ * @private
114
+ */
115
+ private async _getStorageAccountDetails ( ) : Promise < StorageAccountInfo > {
116
+ tl . debug ( "Getting storage account details for " + this . azureStorageAccountName ) ;
117
+
118
+ const subscriptionId : string = tl . getEndpointDataParameter ( this . connectedService , "subscriptionId" , false ) ;
119
+ const credentials = await this . _getARMCredentials ( ) ;
120
+ const storageArmClient = new armStorage . StorageManagementClient ( credentials , subscriptionId ) ;
121
+
122
+ const isUseOldStorageAccountQuery = process . env . AZP_TASK_FF_JAVATOOLINSTALLER_USE_OLD_SA_QUERY
123
+ ? ! ! process . env . AZP_TASK_FF_JAVATOOLINSTALLER_USE_OLD_SA_QUERY
124
+ : false ;
125
+
126
+ let storageAccount = null ;
127
+ if ( this . azureResourceGroupName ) {
128
+ tl . debug ( "Group name is provided. Using fast query to get storage account details." ) ;
129
+ storageAccount = await this . _getStorageAccountWithResourceGroup ( storageArmClient , this . azureResourceGroupName , this . azureStorageAccountName ) ;
130
+ }
131
+
132
+ if ( ! storageAccount ) {
133
+ tl . debug ( "Group name is not provided or fast query failed. Using legacy query to get storage account details." ) ;
134
+ storageAccount = isUseOldStorageAccountQuery
135
+ ? await this . _legacyGetStorageAccount ( storageArmClient )
136
+ : await this . _getStorageAccount ( storageArmClient ) ;
137
+
138
+ }
139
+
140
+ const storageAccountResourceGroupName = armStorage . StorageAccounts . getResourceGroupNameFromUri ( storageAccount . id ) ;
141
+ tl . debug ( "Fetched Storage Account Resource Group name: " + storageAccountResourceGroupName ) ;
142
+
143
+ return < StorageAccountInfo > {
144
+ name : this . azureStorageAccountName ,
145
+ resourceGroupName : storageAccountResourceGroupName
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Retrieves a storage account from the list of classic and RM accounts.
151
+ *
152
+ * @param storageArmClient - The storage management client used to list storage accounts.
153
+ * @returns A promise that resolves to the storage account matching the specified name.
154
+ * @throws An error if the storage account does not exist.
155
+ */
156
+ private async _legacyGetStorageAccount ( storageArmClient : armStorage . StorageManagementClient ) : Promise < Model . StorageAccount > {
157
+ const storageAccounts = await storageArmClient . storageAccounts . listClassicAndRMAccounts ( null ) ;
158
+ const index = storageAccounts . findIndex ( account => account . name . toLowerCase ( ) == this . azureStorageAccountName . toLowerCase ( ) ) ;
159
+ if ( index < 0 ) {
160
+ throw new Error ( tl . loc ( "StorageAccountDoesNotExist" , this . azureStorageAccountName ) ) ;
161
+ }
162
+
163
+ return storageAccounts [ index ] ;
164
+ }
165
+
166
+ /**
167
+ * Retrieves the storage account details using the provided StorageManagementClient.
168
+ *
169
+ * @param storageArmClient - The client used to manage storage accounts.
170
+ * @returns A promise that resolves to the storage account details.
171
+ * @throws Will throw an error if the storage account does not exist.
172
+ */
173
+ private async _getStorageAccount ( storageArmClient : armStorage . StorageManagementClient ) : Promise < Model . StorageAccount > {
174
+ const storageAccount = await storageArmClient . storageAccounts . getClassicOrArmAccountByName ( this . azureStorageAccountName , null ) ;
175
+
176
+ if ( ! storageAccount ) {
177
+ throw new Error ( tl . loc ( 'StorageAccountDoesNotExist' , this . azureStorageAccountName ) ) ;
178
+ }
179
+
180
+ return storageAccount ;
181
+ }
182
+
183
+ /**
184
+ * Retrieves the storage account details for a given resource group and storage account name.
185
+ *
186
+ * @param storageArmClient - The Storage Management Client used to interact with Azure Storage resources.
187
+ * @param resourceGroupName - The name of the resource group containing the storage account.
188
+ * @param storageAccountName - The name of the storage account to retrieve details for.
189
+ * @returns A promise that resolves to the storage account details if found, or undefined if not found.
190
+ * @throws Will log a warning if the storage account details cannot be retrieved using a fast query.
191
+ */
192
+ private async _getStorageAccountWithResourceGroup ( storageArmClient : armStorage . StorageManagementClient , resourceGroupName : string , storageAccountName : string ) : Promise < Model . StorageAccount | undefined > {
193
+ let storageAccount = undefined ;
194
+
195
+ try {
196
+ storageAccount = await storageArmClient . storageAccounts . getStorageAccountProperties ( resourceGroupName , storageAccountName ) ;
197
+ } catch ( e ) {
198
+ tl . warning ( "Failed to get storage account details using fast query." ) ;
199
+ }
200
+
201
+ if ( storageAccount ) {
202
+ tl . debug ( "Found storage account details using fast query." ) ;
203
+ }
204
+
205
+ return storageAccount ;
206
+ }
207
+
208
+ /**
209
+ * Retrieves Azure Resource Manager (ARM) credentials.
210
+ *
211
+ * @returns {Promise<msRestAzure.ApplicationTokenCredentials> } A promise that resolves to the ARM credentials.
212
+ * @private
213
+ */
214
+ private async _getARMCredentials ( ) : Promise < msRestAzure . ApplicationTokenCredentials > {
215
+ const endpoint : AzureEndpoint = await new AzureRMEndpoint ( this . connectedService ) . getEndpoint ( ) ;
216
+ return endpoint . applicationTokenCredentials ;
217
+ }
218
+
219
+ /**
220
+ * Retrieves the full Azure Resource Manager (ARM) endpoint details.
221
+ *
222
+ * @returns {Promise<AzureEndpoint> } A promise that resolves to the AzureEndpoint.
223
+ * @private
224
+ */
225
+ private async _getAzureRMEndpoint ( ) : Promise < AzureEndpoint > {
226
+ const endpoint : AzureEndpoint = await new AzureRMEndpoint ( this . connectedService ) . getEndpoint ( ) ;
227
+ return endpoint ;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Represents the information of an Azure Storage Account.
233
+ *
234
+ * @interface StorageAccountInfo
235
+ * @property {string } name - The name of the storage account.
236
+ * @property {string } resourceGroupName - The name of the resource group that the storage account belongs to.
237
+ */
238
+ interface StorageAccountInfo {
239
+ name : string ;
240
+ resourceGroupName : string ;
241
+ }
0 commit comments