Skip to content

Commit

Permalink
🔀 Fix pre-upload hash check (#28)
Browse files Browse the repository at this point in the history
* ♻️ Convert `UploadItem` to struct

* ✨ Add combinedHash to dependency model

* ✨ Prepare new pre-upload hash comparison

* ✨ Prepare carthage hash metadata upload
  • Loading branch information
olejnjak authored Mar 21, 2022
1 parent 14713d9 commit eb3f052
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Sources/GCP_Remote/Model/GoogleClaims.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import JWTKit
struct GoogleClaims: JWTPayload {
enum Scope: String, Codable {
case readOnly = "https://www.googleapis.com/auth/devstorage.read_only"
case readWrite = "https://www.googleapis.com/auth/devstorage.read_write"
case readWrite = "https://www.googleapis.com/auth/devstorage.full_control"
}

/// Service account email
Expand Down
8 changes: 5 additions & 3 deletions Sources/GCP_Remote/Model/Metadata.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Foundation

public struct Metadata: Codable {
public let crc32c: String
public let etag: String
public let md5Hash: String
public struct CustomMetadata: Codable {
public let carthageHash: String?
}

public let metadata: CustomMetadata?
}
20 changes: 20 additions & 0 deletions Sources/GCP_Remote/Model/UploadItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Foundation
import TSCBasic

public struct UploadItem {
public let localFile: AbsolutePath
public let remotePath: String
public let hash: String

// MARK: - Initializers

public init(
localFile: AbsolutePath,
remotePath: String,
hash: String
) {
self.localFile = localFile
self.remotePath = remotePath
self.hash = hash
}
}
22 changes: 22 additions & 0 deletions Sources/GCP_Remote/Services/GCPAPIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ public protocol GCPAPIServicing {
bucket: String,
token: AccessToken
) async throws -> Metadata

func updateMetadata(
_ metadata: Metadata,
object: String,
bucket: String,
token: AccessToken
) async throws
}

public final class GCPAPIService: GCPAPIServicing {
Expand Down Expand Up @@ -101,6 +108,21 @@ public final class GCPAPIService: GCPAPIServicing {
)
}

public func updateMetadata(
_ metadata: Metadata,
object: String,
bucket: String,
token: AccessToken
) async throws {
var request = URLRequest(url: .init(string: "https://storage.googleapis.com/storage/v1/b/\(bucket)/o/\(object.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)")!)
token.addToRequest(&request)
request.httpMethod = "PATCH"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(metadata)

try await session.data(request: request)
}

// MARK: - Private helpers

private func url(
Expand Down
43 changes: 20 additions & 23 deletions Sources/GCP_Remote/Services/GCPUploader.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import Foundation
import TSCBasic
import Logger
import CryptoKit

public typealias UploadItem = (localFile: AbsolutePath, remotePath: String)

public protocol GCPUploading {
func upload(items: [UploadItem]) async throws
}
Expand Down Expand Up @@ -44,42 +41,34 @@ public struct GCPUploader: GCPUploading {
readOnly: false
)

try await items.asyncForEach { localPath, remotePath in
try await items.asyncForEach {
let localPath = $0.localFile
let remotePath = $0.remotePath
let localHash = $0.hash
let name = localPath.basenameWithoutExt

logger.info("Uploading dependency", name)

do {
let existingMetadata: Metadata?
let remoteHash: String?

do {
let metadata = try await gcpAPI.metadata(
remoteHash = try await gcpAPI.metadata(
object: remotePath,
bucket: config.bucket,
token: token
)
existingMetadata = metadata
logger.debug(
"Existing MD5 for dependency",
name,
"is",
metadata.md5Hash
)
).metadata?.carthageHash
} catch {
existingMetadata = nil
remoteHash = nil
logger.debug("Unable to fetch existing metadata")
logger.debug(error)
}

if let currentMD5 = existingMetadata?.md5Hash,
let data = try? Data(contentsOf: localPath.asURL) {
let md5 = Data(Insecure.MD5.hash(data: data))
.base64EncodedString()

logger.debug("Comparing MD5 hash for dependendency", name)
logger.debug("Local:", md5, "Remote:", currentMD5)
if let currentHash = remoteHash {
logger.debug("Comparing hash for dependendency", name)
logger.debug("Local:", localHash, "Remote:", remoteHash ?? "(nil)")

if currentMD5 == md5 {
if currentHash == localHash {
logger.info("Dependency " + name + " has not changed, skipping upload")
return
}
Expand All @@ -91,6 +80,14 @@ public struct GCPUploader: GCPUploading {
bucket: config.bucket,
token: token
)

try await gcpAPI.updateMetadata(
.init(metadata: .init(carthageHash: localHash)),
object: remotePath,
bucket: config.bucket,
token: token
)

logger.info("Successfully uploaded dependency", name)
} catch {
logger.info("Unable to upload dependency", name)
Expand Down
1 change: 1 addition & 0 deletions Sources/TorinoCore/Model/Dependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ public struct Dependency {
public let version: String
public let frameworks: [Container]
public let versionFile: AbsolutePath
public let hash: String
}
21 changes: 18 additions & 3 deletions Sources/TorinoCore/Model/VersionFile.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import CryptoKit
import Foundation
import TSCBasic

public struct VersionFile: Decodable {
public struct Framework: Decodable {
public let name: String
public let container: String?
public let hash: String
}

public let commitish: String
Expand All @@ -14,9 +16,22 @@ public struct VersionFile: Decodable {
public let watchOS: [Framework]?

public var allContainers: [String] {
[iOS, macOS, tvOS, watchOS].compactMap { $0 }
.joined()
.compactMap(\.container)
allFrameworks.compactMap(\.container)
}

public var combinedHash: String {
let allHashes = allFrameworks.reduce("") { partialResult, container in
partialResult + String(container.hash.count) + container.hash
}

return Data(Insecure.MD5.hash(data: allHashes.data(using: .utf8)!))
.base64EncodedString()
}

private var allFrameworks: [Framework] {
[iOS, macOS, tvOS, watchOS]
.compactMap { $0 }
.flatMap { $0 }
}
}

Expand Down
3 changes: 2 additions & 1 deletion Sources/TorinoCore/Services/DependenciesLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public struct CarthageDependenciesLoader: DependenciesLoading {
frameworks: $0.versionFile.allContainers.map {
.init(name: $0, path: buildDir.appending(component: $0))
},
versionFile: $0.path
versionFile: $0.path,
hash: $0.versionFile.combinedHash
)
}
}
Expand Down
12 changes: 8 additions & 4 deletions Sources/TorinoCore/Services/DependenciesUploader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ struct LocalDependenciesUploader: DependenciesUploading {
destination: cachePath
)

uploadItems.append((cachePath, pathProvider.remoteCachePath(
dependency: dependency.name,
version: dependency.version
)))
uploadItems.append(.init(
localFile: cachePath,
remotePath: pathProvider.remoteCachePath(
dependency: dependency.name,
version: dependency.version
),
hash: dependency.hash
))
}

if let gcpUploader = gcpUploader {
Expand Down

0 comments on commit eb3f052

Please sign in to comment.