1717package com.google.firebase.dataconnect.gradle.plugin
1818
1919import com.google.cloud.storage.Blob
20+ import com.google.cloud.storage.Bucket
21+ import com.google.cloud.storage.Storage
2022import com.google.cloud.storage.Storage.BlobListOption
2123import com.google.cloud.storage.StorageException
2224import com.google.cloud.storage.StorageOptions
25+ import com.google.firebase.dataconnect.gradle.plugin.DataConnectExecutableVersionsRegistry.VersionInfo
2326import com.google.firebase.dataconnect.gradle.plugin.DataConnectExecutableVersionsRegistry.serializedValue
2427import io.github.z4kn4fein.semver.Version
2528import io.github.z4kn4fein.semver.toVersion
@@ -67,21 +70,21 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
6770 registry.versions.size,
6871 jsonFile.absolutePath,
6972 registry.defaultVersion,
70- registry.versions.sortedWith(versionInfoComparator).joinToString {
71- " ${it.version} -${it.os.serializedValue} "
72- }
73+ registry.versions.sortedWith(versionInfoComparator).toLogString()
7374 )
7475
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()
7684
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()) {
8588 logger.lifecycle(
8689 " Not updating {} since it already contains all versions." ,
8790 jsonFile.absolutePath
@@ -91,17 +94,20 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
9194
9295 logger.lifecycle(
9396 " 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()
9699 )
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+ )
99105
100106 logger.lifecycle(
101107 " Updating {} with {} versions: {}" ,
102108 jsonFile.absolutePath,
103- unknownVersions .size,
104- unknownVersions.joinToString { " ${it.version} - ${it.operatingSystem.serializedValue} " }
109+ cloudStorageVersionsMissingFromRegistry .size,
110+ cloudStorageVersionsMissingFromRegistry.toLogString()
105111 )
106112
107113 if (updatedRegistry.defaultVersion == registry.defaultVersion) {
@@ -122,103 +128,104 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
122128 DataConnectExecutableVersionsRegistry .save(updatedRegistry, jsonFile)
123129 }
124130
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 {
127138 val bucketName = " firemat-preview-drop"
128139 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+ )
146155 }
147- .getOrThrow()
148- ? : throw DataConnectGradleException (" bvkxzp2esg" , " GCS bucket not found: $bucketName " )
156+ }
157+ .getOrThrow()
158+ ? : throw DataConnectGradleException (" bvkxzp2esg" , " GCS bucket not found: $bucketName " )
159+ }
149160
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+ }
152174
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
204220 )
221+ return null
205222 }
206- .filter { it.version >= minVersion }
207- .filterNot { invalidVersions.contains(it.version) }
208- .toSet()
223+ }
209224
210- return dataConnectExecutableBinaries
225+ return CloudStorageVersionInfo (version, operatingSystem, blob = this )
211226 }
212227
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 {
222229 val dateFormatter = DateTimeFormatter .ofLocalizedDateTime(FormatStyle .LONG )
223230
224231 logger.lifecycle(
@@ -238,19 +245,31 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
238245 " never happen; if it _does_ happen it _could_ indicate a compromised " +
239246 " downloaded binary [y5967yd2cf]"
240247 }
241- return DataConnectExecutableVersionsRegistry .VersionInfo (
242- version,
243- operatingSystem,
244- fileInfo.sizeInBytes,
245- fileInfo.sha512DigestHex
246- )
248+ return VersionInfo (version, operatingSystem, fileInfo.sizeInBytes, fileInfo.sha512DigestHex)
247249 }
248250
249251 private companion object {
250252
251253 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()
254273
255274 /* *
256275 * Creates a returns a new list that contains all elements of the receiving [Iterable] that are
@@ -265,16 +284,14 @@ abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
265284 }
266285
267286 private fun DataConnectExecutableVersionsRegistry.Root.updatedWith (
268- updatedVersions : Iterable <DataConnectExecutableVersionsRegistry . VersionInfo >
287+ updatedVersions : Iterable <VersionInfo >
269288 ): DataConnectExecutableVersionsRegistry .Root {
270- val mergedVersions = buildList {
289+ val allVersions = buildList {
271290 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)
276293 }
277- return copy(defaultVersion = mergedVersions .maxOf { it.version }, versions = mergedVersions )
294+ return copy(defaultVersion = allVersions .maxOf { it.version }, versions = allVersions )
278295 }
279296 }
280297}
0 commit comments