17
17
package com.google.firebase.dataconnect.gradle.plugin
18
18
19
19
import com.google.cloud.storage.Blob
20
+ import com.google.cloud.storage.Bucket
21
+ import com.google.cloud.storage.Storage
20
22
import com.google.cloud.storage.Storage.BlobListOption
21
23
import com.google.cloud.storage.StorageException
22
24
import com.google.cloud.storage.StorageOptions
25
+ import com.google.firebase.dataconnect.gradle.plugin.DataConnectExecutableVersionsRegistry.VersionInfo
23
26
import com.google.firebase.dataconnect.gradle.plugin.DataConnectExecutableVersionsRegistry.serializedValue
24
27
import io.github.z4kn4fein.semver.Version
25
28
import io.github.z4kn4fein.semver.toVersion
@@ -67,21 +70,21 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
67
70
registry.versions.size,
68
71
jsonFile.absolutePath,
69
72
registry.defaultVersion,
70
- registry.versions.sortedWith(versionInfoComparator).joinToString {
71
- " ${it.version} -${it.os.serializedValue} "
72
- }
73
+ registry.versions.sortedWith(versionInfoComparator).toLogString()
73
74
)
74
75
75
- val cloudStorageVersions = downloadVersionInfoFromCloudStorage()
76
+ val cloudStorageVersions: Set <CloudStorageVersionInfo > =
77
+ StorageOptions .getDefaultInstance()
78
+ .service
79
+ .getDataConnectExecutablesBucket()
80
+ .list(BlobListOption .prefix(" emulator/" ))
81
+ .iterateAll()
82
+ .mapNotNull { it.toCloudStorageVersionInfoOrNull() }
83
+ .toSet()
76
84
77
- val unknownVersions =
78
- cloudStorageVersions
79
- .filterNotIn(registry)
80
- .sortedWith(
81
- compareBy<CloudStorageVersionInfo > { it.version }
82
- .thenByDescending { it.operatingSystem.serializedValue }
83
- )
84
- if (unknownVersions.isEmpty()) {
85
+ val cloudStorageVersionsMissingFromRegistry: List <CloudStorageVersionInfo > =
86
+ cloudStorageVersions.filterNotIn(registry).sortedWith(cloudStorageVersionInfoComparator)
87
+ if (cloudStorageVersionsMissingFromRegistry.isEmpty()) {
85
88
logger.lifecycle(
86
89
" Not updating {} since it already contains all versions." ,
87
90
jsonFile.absolutePath
@@ -91,17 +94,20 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
91
94
92
95
logger.lifecycle(
93
96
" Downloading details for {} versions missing from registry file: {}" ,
94
- unknownVersions .size,
95
- unknownVersions.joinToString { " ${it.version} - ${it.operatingSystem.serializedValue} " }
97
+ cloudStorageVersionsMissingFromRegistry .size,
98
+ cloudStorageVersionsMissingFromRegistry.toLogString()
96
99
)
97
- val unknownVersionInfos = unknownVersions.map { it.toRegistryVersionInfo(workDirectory) }
98
- val updatedRegistry = registry.updatedWith(unknownVersionInfos)
100
+
101
+ val updatedRegistry =
102
+ registry.updatedWith(
103
+ cloudStorageVersionsMissingFromRegistry.map { it.toRegistryVersionInfo(workDirectory) }
104
+ )
99
105
100
106
logger.lifecycle(
101
107
" Updating {} with {} versions: {}" ,
102
108
jsonFile.absolutePath,
103
- unknownVersions .size,
104
- unknownVersions.joinToString { " ${it.version} - ${it.operatingSystem.serializedValue} " }
109
+ cloudStorageVersionsMissingFromRegistry .size,
110
+ cloudStorageVersionsMissingFromRegistry.toLogString()
105
111
)
106
112
107
113
if (updatedRegistry.defaultVersion == registry.defaultVersion) {
@@ -122,103 +128,104 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
122
128
DataConnectExecutableVersionsRegistry .save(updatedRegistry, jsonFile)
123
129
}
124
130
125
- private fun downloadVersionInfoFromCloudStorage (): Set <CloudStorageVersionInfo > {
126
- val storage = StorageOptions .getDefaultInstance().service
131
+ private data class CloudStorageVersionInfo (
132
+ val version : Version ,
133
+ val operatingSystem : OperatingSystem ,
134
+ val blob : Blob ,
135
+ )
136
+
137
+ private fun Storage.getDataConnectExecutablesBucket (): Bucket {
127
138
val bucketName = " firemat-preview-drop"
128
139
logger.lifecycle(" Finding all Data Connect executable versions in GCS bucket: {}" , bucketName)
129
- val bucket =
130
- storage
131
- .runCatching { get(bucketName) }
132
- .onFailure { e ->
133
- if (
134
- e is StorageException &&
135
- e.cause.let {
136
- it is com.google.api.client.http.HttpResponseException &&
137
- (it.statusCode == 401 || it.statusCode == 403 )
138
- }
139
- ) {
140
- logger.error(
141
- " ERROR: 401/403 error returned from Google Cloud Storage; " +
142
- " try running \" gcloud auth application-default login\" and/or unsetting the " +
143
- " GOOGLE_APPLICATION_CREDENTIALS environment variable to fix"
144
- )
145
- }
140
+
141
+ return runCatching { get(bucketName) }
142
+ .onFailure { e ->
143
+ if (
144
+ e is StorageException &&
145
+ e.cause.let {
146
+ it is com.google.api.client.http.HttpResponseException &&
147
+ (it.statusCode == 401 || it.statusCode == 403 )
148
+ }
149
+ ) {
150
+ logger.error(
151
+ " ERROR: 401/403 error returned from Google Cloud Storage; " +
152
+ " try running \" gcloud auth application-default login\" and/or unsetting the " +
153
+ " GOOGLE_APPLICATION_CREDENTIALS environment variable to fix"
154
+ )
146
155
}
147
- .getOrThrow()
148
- ? : throw DataConnectGradleException (" bvkxzp2esg" , " GCS bucket not found: $bucketName " )
156
+ }
157
+ .getOrThrow()
158
+ ? : throw DataConnectGradleException (" bvkxzp2esg" , " GCS bucket not found: $bucketName " )
159
+ }
149
160
150
- val invalidVersions = setOf (" 1.15.0" .toVersion())
151
- val minVersion = " 1.3.4" .toVersion()
161
+ private fun Blob.toCloudStorageVersionInfoOrNull (): CloudStorageVersionInfo ? {
162
+ logger.debug(" [av7zhespw2] Found Data Connect executable file: {}" , name)
163
+ val match =
164
+ fileNameRegex.matchEntire(name)
165
+ ? : run {
166
+ logger.debug(
167
+ " [p4vjjcp2kq] Ignoring Data Connect executable file: {} " +
168
+ " (does not match regex: {})" ,
169
+ name,
170
+ fileNameRegex
171
+ )
172
+ return null
173
+ }
152
174
153
- val blobs = bucket.list(BlobListOption .prefix(" emulator/" ))
154
- val regex = " .*dataconnect-emulator-([^-]+)-v(.*)" .toRegex()
155
- val dataConnectExecutableBinaries =
156
- blobs
157
- .iterateAll()
158
- .mapNotNull {
159
- logger.debug(" [av7zhespw2] Found Data Connect executable file: {}" , it.name)
160
- val match =
161
- regex.matchEntire(it.name)
162
- ? : run {
163
- logger.debug(
164
- " [p4vjjcp2kq] Ignoring Data Connect executable file: {} " +
165
- " (does not match regex: {})" ,
166
- it.name,
167
- regex
168
- )
169
- return @mapNotNull null
170
- }
171
- CloudStorageVersionInfo (
172
- version =
173
- run {
174
- val versionString = match.groups[2 ]?.value
175
- versionString?.toVersionOrNull(strict = false )
176
- ? : run {
177
- logger.info(
178
- " WARNING: Ignoring Data Connect executable file: {} " +
179
- " (invalid version: {} (in match for regex {}))" ,
180
- it.name,
181
- versionString,
182
- regex
183
- )
184
- return @mapNotNull null
185
- }
186
- },
187
- operatingSystem =
188
- when (val operatingSystemString = match.groups[1 ]?.value) {
189
- " linux" -> OperatingSystem .Linux
190
- " macos" -> OperatingSystem .MacOS
191
- " windows" -> OperatingSystem .Windows
192
- else -> {
193
- logger.info(
194
- " WARNING: Ignoring Data Connect executable file: {} " +
195
- " (unknown operating system name: {} (in match for regex {}))" ,
196
- it.name,
197
- operatingSystemString,
198
- regex
199
- )
200
- return @mapNotNull null
201
- }
202
- },
203
- blob = it,
175
+ val versionString = match.groups[2 ]?.value
176
+ val version = versionString?.toVersionOrNull(strict = false )
177
+ if (version == = null ) {
178
+ logger.info(
179
+ " Ignoring Data Connect executable file: {} " +
180
+ " (invalid version: {} (in match for regex {}))" ,
181
+ name,
182
+ versionString,
183
+ fileNameRegex
184
+ )
185
+ return null
186
+ }
187
+
188
+ if (version < minVersion) {
189
+ logger.info(
190
+ " Ignoring Data Connect executable file: {} " +
191
+ " (version {} is less than the minimum version: {})" ,
192
+ name,
193
+ versionString,
194
+ minVersion
195
+ )
196
+ return null
197
+ }
198
+
199
+ if (version in invalidVersions) {
200
+ logger.info(
201
+ " Ignoring Data Connect executable file: {} " + " (version {} is a known invalid version)" ,
202
+ name,
203
+ versionString
204
+ )
205
+ return null
206
+ }
207
+
208
+ val operatingSystem =
209
+ when (val operatingSystemString = match.groups[1 ]?.value) {
210
+ " linux" -> OperatingSystem .Linux
211
+ " macos" -> OperatingSystem .MacOS
212
+ " windows" -> OperatingSystem .Windows
213
+ else -> {
214
+ logger.info(
215
+ " WARNING: Ignoring Data Connect executable file: {} " +
216
+ " (unknown operating system name: {} (in match for regex {}))" ,
217
+ name,
218
+ operatingSystemString,
219
+ fileNameRegex
204
220
)
221
+ return null
205
222
}
206
- .filter { it.version >= minVersion }
207
- .filterNot { invalidVersions.contains(it.version) }
208
- .toSet()
223
+ }
209
224
210
- return dataConnectExecutableBinaries
225
+ return CloudStorageVersionInfo (version, operatingSystem, blob = this )
211
226
}
212
227
213
- private data class CloudStorageVersionInfo (
214
- val version : Version ,
215
- val operatingSystem : OperatingSystem ,
216
- val blob : Blob ,
217
- )
218
-
219
- private fun CloudStorageVersionInfo.toRegistryVersionInfo (
220
- workDirectory : File
221
- ): DataConnectExecutableVersionsRegistry .VersionInfo {
228
+ private fun CloudStorageVersionInfo.toRegistryVersionInfo (workDirectory : File ): VersionInfo {
222
229
val dateFormatter = DateTimeFormatter .ofLocalizedDateTime(FormatStyle .LONG )
223
230
224
231
logger.lifecycle(
@@ -238,19 +245,31 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
238
245
" never happen; if it _does_ happen it _could_ indicate a compromised " +
239
246
" downloaded binary [y5967yd2cf]"
240
247
}
241
- return DataConnectExecutableVersionsRegistry .VersionInfo (
242
- version,
243
- operatingSystem,
244
- fileInfo.sizeInBytes,
245
- fileInfo.sha512DigestHex
246
- )
248
+ return VersionInfo (version, operatingSystem, fileInfo.sizeInBytes, fileInfo.sha512DigestHex)
247
249
}
248
250
249
251
private companion object {
250
252
251
253
val versionInfoComparator =
252
- compareBy<DataConnectExecutableVersionsRegistry .VersionInfo > { it.version }
253
- .thenByDescending { it.os.serializedValue }
254
+ compareBy<VersionInfo > { it.version }.thenByDescending { it.os.serializedValue }
255
+
256
+ val cloudStorageVersionInfoComparator =
257
+ compareBy<CloudStorageVersionInfo > { it.version }
258
+ .thenByDescending { it.operatingSystem.serializedValue }
259
+
260
+ @JvmName(" toLogStringCloudStorageVersionInfo" )
261
+ fun Iterable<CloudStorageVersionInfo>.toLogString (): String = joinToString {
262
+ " ${it.version} -${it.operatingSystem.serializedValue} "
263
+ }
264
+
265
+ @JvmName(" toLogStringVersionInfo" )
266
+ fun Iterable<VersionInfo>.toLogString (): String = joinToString {
267
+ " ${it.version} -${it.os.serializedValue} "
268
+ }
269
+
270
+ val invalidVersions = setOf (" 1.15.0" .toVersion())
271
+ val minVersion = " 1.3.4" .toVersion()
272
+ val fileNameRegex = " .*dataconnect-emulator-([^-]+)-v(.*)" .toRegex()
254
273
255
274
/* *
256
275
* Creates a returns a new list that contains all elements of the receiving [Iterable] that are
@@ -265,16 +284,14 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
265
284
}
266
285
267
286
private fun DataConnectExecutableVersionsRegistry.Root.updatedWith (
268
- updatedVersions : Iterable <DataConnectExecutableVersionsRegistry . VersionInfo >
287
+ updatedVersions : Iterable <VersionInfo >
269
288
): DataConnectExecutableVersionsRegistry .Root {
270
- val mergedVersions = buildList {
289
+ val allVersions = buildList {
271
290
addAll(versions)
272
- for (version in updatedVersions) {
273
- val index = indexOfLast { versionInfoComparator.compare(it, version) < 0 }
274
- add(index + 1 , version)
275
- }
291
+ addAll(updatedVersions)
292
+ sortWith(versionInfoComparator)
276
293
}
277
- return copy(defaultVersion = mergedVersions .maxOf { it.version }, versions = mergedVersions )
294
+ return copy(defaultVersion = allVersions .maxOf { it.version }, versions = allVersions )
278
295
}
279
296
}
280
297
}
0 commit comments