Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Sources/App/Commands/Ingestion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ enum Ingestion {

let s3Readme: S3Readme?
do throws(S3Readme.Error) {
s3Readme = try await storeS3Readme(client: client, repository: repo, metadata: metadata, readme: readme)
s3Readme = try await storeS3Readme(repository: repo, metadata: metadata, readme: readme)
} catch {
// We don't want to fail ingestion in case storing the readme fails - warn and continue.
Current.logger().warning("storeS3Readme failed: \(error)")
Expand Down Expand Up @@ -239,15 +239,16 @@ enum Ingestion {
}


static func storeS3Readme(client: Client, repository: Repository, metadata: Github.Metadata, readme: Github.Readme?) async throws(S3Readme.Error) -> S3Readme? {
static func storeS3Readme(repository: Repository, metadata: Github.Metadata, readme: Github.Readme?) async throws(S3Readme.Error) -> S3Readme? {
@Dependency(\.s3) var s3
if let upstreamEtag = readme?.etag,
repository.s3Readme?.needsUpdate(upstreamEtag: upstreamEtag) ?? true,
let owner = metadata.repositoryOwner,
let repository = metadata.repositoryName,
let html = readme?.html {
let objectUrl = try await Current.storeS3Readme(owner, repository, html)
let objectUrl = try await s3.storeReadme(owner, repository, html)
if let imagesToCache = readme?.imagesToCache, imagesToCache.isEmpty == false {
try await Current.storeS3ReadmeImages(client, imagesToCache)
try await s3.storeReadmeImages(imagesToCache)
}
return .cached(s3ObjectUrl: objectUrl, githubEtag: upstreamEtag)
} else {
Expand Down
3 changes: 2 additions & 1 deletion Sources/App/Controllers/PackageController+routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ enum PackageController {
}

do {
let readme = try await Current.fetchS3Readme(req.client, owner, repository)
@Dependency(\.s3) var s3
let readme = try await s3.fetchReadme(owner, repository)
guard let branch = pkg.repository?.defaultBranch else {
return PackageReadme.View(model: .cacheLookupFailed(url: readmeHtmlUrl)).document()
}
Expand Down
13 changes: 0 additions & 13 deletions Sources/App/Core/AppEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import FoundationNetworking


struct AppEnvironment: Sendable {
var fetchS3Readme: @Sendable (_ client: Client, _ owner: String, _ repository: String) async throws -> String
var fileManager: FileManager
var getStatusCount: @Sendable (_ client: Client, _ status: Gitlab.Builder.Status) async throws -> Int
var git: Git
Expand All @@ -33,11 +32,6 @@ struct AppEnvironment: Sendable {
var logger: @Sendable () -> Logger
var setLogger: @Sendable (Logger) -> Void
var shell: Shell
var storeS3Readme: @Sendable (_ owner: String,
_ repository: String,
_ readme: String) async throws(S3Readme.Error) -> String
var storeS3ReadmeImages: @Sendable (_ client: Client,
_ imagesToCache: [Github.Readme.ImageToCache]) async throws(S3Readme.Error) -> Void
var triggerBuild: @Sendable (_ client: Client,
_ buildId: Build.Id,
_ cloneURL: String,
Expand All @@ -53,7 +47,6 @@ extension AppEnvironment {
nonisolated(unsafe) static var logger: Logger!

static let live = AppEnvironment(
fetchS3Readme: { client, owner, repo in try await S3Readme.fetchReadme(client:client, owner: owner, repository: repo) },
fileManager: .live,
getStatusCount: { client, status in
try await Gitlab.Builder.getStatusCount(client: client,
Expand All @@ -72,12 +65,6 @@ extension AppEnvironment {
logger: { logger },
setLogger: { logger in Self.logger = logger },
shell: .live,
storeS3Readme: { owner, repo, readme throws(S3Readme.Error) in
try await S3Readme.storeReadme(owner: owner, repository: repo, readme: readme)
},
storeS3ReadmeImages: { client, images throws(S3Readme.Error) in
try await S3Readme.storeReadmeImages(client: client, imagesToCache: images)
},
triggerBuild: { client, buildId, cloneURL, isDocBuild, platform, ref, swiftVersion, versionID in
try await Gitlab.Builder.triggerBuild(client: client,
buildId: buildId,
Expand Down
2 changes: 2 additions & 0 deletions Sources/App/Core/Dependencies/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ extension HTTPClient: DependencyKey {
}
)
}

func get(url: String) async throws -> Response { try await get(url: url, headers: .init()) }
}


Expand Down
66 changes: 66 additions & 0 deletions Sources/App/Core/Dependencies/S3Client.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Dependencies
import IssueReporting


// We currently cannot use @DependencyClient here due to
// https://github.com/pointfreeco/swift-dependencies/discussions/324
//@DependencyClient
struct S3Client {
var fetchReadme: @Sendable (_ owner: String, _ repository: String) async throws -> String
var storeReadme: @Sendable (_ owner: String, _ repository: String, _ readme: String) async throws(S3Readme.Error) -> String = { _, _, _ in
reportIssue("storeS3Readme"); return ""
}
var storeReadmeImages: @Sendable (_ imagesToCache: [Github.Readme.ImageToCache]) async throws(S3Readme.Error) -> Void
}


extension S3Client: DependencyKey {
static var liveValue: Self {
.init(
fetchReadme: { owner, repo in
try await S3Readme.fetchReadme(owner: owner, repository: repo)
},
storeReadme: { owner, repo, readme throws(S3Readme.Error) in
try await S3Readme.storeReadme(owner: owner, repository: repo, readme: readme)
},
storeReadmeImages: { images throws(S3Readme.Error) in
try await S3Readme.storeReadmeImages(imagesToCache: images)
}
)
}
}


extension S3Client: TestDependencyKey {
static var testValue: Self {
.init(
fetchReadme: { _, _ in unimplemented(); return "" },
storeReadme: { _, _, _ in unimplemented("storeS3Readme"); return "" },
storeReadmeImages: { _ throws(S3Readme.Error) in unimplemented("storeS3ReadmeImages") }
)
}
}


extension DependencyValues {
var s3: S3Client {
get { self[S3Client.self] }
set { self[S3Client.self] = newValue }
}
}


12 changes: 7 additions & 5 deletions Sources/App/Core/Extensions/S3Readme+ext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ extension S3Readme {
case storeImagesFailed
}

static func fetchReadme(client: Client, owner: String, repository: String) async throws(S3Readme.Error) -> String {
static func fetchReadme(owner: String, repository: String) async throws(S3Readme.Error) -> String {
let key = try S3Store.Key.readme(owner: owner, repository: repository)
let response: ClientResponse
@Dependency(\.httpClient) var httpClient
let response: HTTPClient.Response
do {
response = try await client.get(URI(string: key.objectUrl))
response = try await httpClient.get(url: key.objectUrl)
} catch {
throw .requestFailed(key: key, error: error)
}
Expand All @@ -56,16 +57,17 @@ extension S3Readme {
return key.objectUrl
}

static func storeReadmeImages(client: Client, imagesToCache: [Github.Readme.ImageToCache]) async throws(S3Readme.Error) {
static func storeReadmeImages(imagesToCache: [Github.Readme.ImageToCache]) async throws(S3Readme.Error) {
@Dependency(\.environment) var environment
@Dependency(\.httpClient) var httpClient
guard let accessKeyId = environment.awsAccessKeyId() else { throw .envVariableNotSet("AWS_ACCESS_KEY_ID") }
guard let secretAccessKey = environment.awsSecretAccessKey() else { throw .envVariableNotSet("AWS_SECRET_ACCESS_KEY")}

let store = S3Store(credentials: .init(keyId: accessKeyId, secret: secretAccessKey))
for imageToCache in imagesToCache {
Current.logger().debug("Copying readme image to \(imageToCache.s3Key.s3Uri) ...")
do {
let response = try await client.get(URI(stringLiteral: imageToCache.originalUrl))
let response = try await httpClient.get(url: imageToCache.originalUrl)
if var body = response.body, let imageData = body.readData(length: body.readableBytes) {
try await store.save(payload: imageData, to: imageToCache.s3Key)
}
Expand Down
33 changes: 16 additions & 17 deletions Tests/AppTests/IngestionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ class IngestionTests: AppTestCase {

func test_ingest_storeS3Readme() async throws {
let fetchCalls = QueueIsolated(0)
let storeCalls = QueueIsolated(0)
try await withDependencies {
$0.date.now = .now
$0.github.fetchLicense = { @Sendable _, _ in nil }
Expand All @@ -484,13 +485,7 @@ class IngestionTests: AppTestCase {
imagesToCache: [])
}
}
} operation: {
// setup
let app = self.app!
let pkg = Package(url: "https://github.com/foo/bar".url, processingStage: .reconciliation)
try await pkg.save(on: app.db)
let storeCalls = QueueIsolated(0)
Current.storeS3Readme = { owner, repo, html in
$0.s3.storeReadme = { owner, repo, html in
storeCalls.increment()
XCTAssertEqual(owner, "foo")
XCTAssertEqual(repo, "bar")
Expand All @@ -501,6 +496,11 @@ class IngestionTests: AppTestCase {
}
return "objectUrl"
}
} operation: {
// setup
let app = self.app!
let pkg = Package(url: "https://github.com/foo/bar".url, processingStage: .reconciliation)
try await pkg.save(on: app.db)

do { // first ingestion, no readme has been saved
// MUT
Expand Down Expand Up @@ -553,13 +553,7 @@ class IngestionTests: AppTestCase {
let pkg = Package(url: "https://github.com/foo/bar".url,
processingStage: .reconciliation)
try await pkg.save(on: app.db)
Current.storeS3Readme = { _, _, _ in "objectUrl" }
let storeS3ReadmeImagesCalls = QueueIsolated(0)
Current.storeS3ReadmeImages = { _, imagesToCache in
storeS3ReadmeImagesCalls.increment()

XCTAssertEqual(imagesToCache.count, 2)
}

try await withDependencies {
$0.date.now = .now
Expand All @@ -586,6 +580,11 @@ class IngestionTests: AppTestCase {
path: "/foo/bar/with-jwt-2.jpg"))
])
}
$0.s3.storeReadme = { _, _, _ in "objectUrl" }
$0.s3.storeReadmeImages = { imagesToCache in
storeS3ReadmeImagesCalls.increment()
XCTAssertEqual(imagesToCache.count, 2)
}
} operation: {
// MUT
try await Ingestion.ingest(client: app.client, database: app.db, mode: .limit(1))
Expand All @@ -601,10 +600,6 @@ class IngestionTests: AppTestCase {
let pkg = Package(url: "https://github.com/foo/bar".url, processingStage: .reconciliation)
try await pkg.save(on: app.db)
let storeCalls = QueueIsolated(0)
Current.storeS3Readme = { owner, repo, html throws(S3Readme.Error) in
storeCalls.increment()
throw .storeReadmeFailed
}

do { // first ingestion, no readme has been saved
try await withDependencies {
Expand All @@ -617,6 +612,10 @@ class IngestionTests: AppTestCase {
htmlUrl: "readme url",
imagesToCache: [])
}
$0.s3.storeReadme = { owner, repo, html throws(S3Readme.Error) in
storeCalls.increment()
throw .storeReadmeFailed
}
} operation: {
// MUT
let app = self.app!
Expand Down
3 changes: 0 additions & 3 deletions Tests/AppTests/Mocks/AppEnvironment+mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import Vapor
extension AppEnvironment {
static func mock(eventLoop: EventLoop) -> Self {
.init(
fetchS3Readme: { _, _, _ in "" },
fileManager: .mock,
getStatusCount: { _, _ in 100 },
git: .mock,
Expand All @@ -32,8 +31,6 @@ extension AppEnvironment {
logger: { logger },
setLogger: { logger in Self.logger = logger },
shell: .mock,
storeS3Readme: { _, _, _ in "s3ObjectUrl" },
storeS3ReadmeImages: { _, _ in },
triggerBuild: { _, _, _, _, _, _, _, _ in .init(status: .ok, webUrl: "http://web_url") }
)
}
Expand Down
Loading
Loading