Compare commits

..
5 Commits
Author SHA1 Message Date
Fedor KorotkovandNikolay Edigaryev 2f5a6790d8 Refactored registry token handling (#57)
* Refactored registry token handling

* Update Sources/tart/OCI/Registry.swift

Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com>

* Target our dedicated M1 Mini

Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com>
2022-05-09 16:13:22 +03:00
Fedor Korotkov cc697b4f6e More images in docs (#56)
* More images in docs

* More images in docs

* Updated GIF
2022-05-09 12:59:51 +03:00
Nikolay Edigaryev f6acbe8fe5 Registry: invalidate expired tokens (#55) 2022-05-08 19:46:11 -04:00
Fedor Korotkov 341fd168b3 Fixed release build entitlements (#54) 2022-05-08 13:05:10 +03:00
Fedor Korotkov e8a6efa60a Return title to docs (#53)
* Return title to docs

* smaller
2022-05-07 11:24:25 +03:00
8 changed files with 54 additions and 15 deletions
+1 -2
View File
@@ -1,7 +1,6 @@
persistent_worker: persistent_worker:
labels: labels:
os: darwin name: Mac-Mini-M1
arch: arm64
task: task:
name: Test name: Test
+1
View File
@@ -1 +1,2 @@
*.png filter=lfs diff=lfs merge=lfs -text *.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
-3
View File
@@ -12,9 +12,6 @@ builds:
before: before:
hooks: hooks:
- swift build -c release --product tart - swift build -c release --product tart
after:
hooks:
- codesign --sign - --entitlements Resources/tart.entitlements --force .build/arm64-apple-macosx/release/tart - codesign --sign - --entitlements Resources/tart.entitlements --force .build/arm64-apple-macosx/release/tart
archives: archives:
+10 -2
View File
@@ -1,4 +1,4 @@
![tart VM view app](Resources/TartScreenshot.png) ![Tart – open source virtualization for your automation needs](Resources/TartSocial.png)
*Tart* is a virtualization toolset to build, run and manage virtual machines on Apple Silicon. *Tart* is a virtualization toolset to build, run and manage virtual machines on Apple Silicon.
Built by CI engineers for your automation needs. Here are some highlights of Tart: Built by CI engineers for your automation needs. Here are some highlights of Tart:
@@ -16,6 +16,8 @@ tart clone ghcr.io/cirruslabs/macos-monterey-base:latest monterey-base
tart run monterey-base tart run monterey-base
``` ```
![tart VM view app](Resources/TartScreenshot.png)
## CI Integration ## CI Integration
[Cirrus CLI](https://github.com/cirruslabs/cirrus-cli) is an open-sourced CI-agnostic tool that can run workloads [Cirrus CLI](https://github.com/cirruslabs/cirrus-cli) is an open-sourced CI-agnostic tool that can run workloads
@@ -28,7 +30,11 @@ task:
macos_instance: macos_instance:
# can be a remote or a local virtual machine # can be a remote or a local virtual machine
image: ghcr.io/cirruslabs/macos-monterey-base:latest image: ghcr.io/cirruslabs/macos-monterey-base:latest
script: echo "Hello from within a Tart VM!" hello_script:
- echo "Hello from within a Tart VM!"
- echo "Here is my CPU info:"
- sysctl -n machdep.cpu.brand_string
- sleep 15
``` ```
Run it locally or in CI with the following command: Run it locally or in CI with the following command:
@@ -38,6 +44,8 @@ brew install cirruslabs/cli/cirrus
cirrus run cirrus run
``` ```
![Cirrus CLI Run](Resources/TartCirrusCLI.gif)
[Cirrus CI](https://cirrus-ci.org/) already leverages Tart to power its macOS cloud infrastructure. The `.cirrus.yml` [Cirrus CI](https://cirrus-ci.org/) already leverages Tart to power its macOS cloud infrastructure. The `.cirrus.yml`
config from above will just work in Cirrus CI and your tasks will be executed inside Tart VMs in our cloud. config from above will just work in Cirrus CI and your tasks will be executed inside Tart VMs in our cloud.
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8a3a324193c4bd7797102765ab16f44adf58e49ca615bac3963cefd0d3a10594
size 339678
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:46046004d63f9b4b7de3d5d3f9ca14367a9e4b0a124b4b0b65225f67a0d0fb45 oid sha256:3a43f541b1ab0b57ae2060d371cba5dbb1f5c80b89c76434b7154d8144f66e61
size 1266887 size 205325
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:769dcd5411f44071cb46f63459118fef728c866a581cf94a28811f407ca9827d
size 606936
+34 -6
View File
@@ -8,14 +8,37 @@ enum RegistryError: Error {
} }
struct TokenResponse: Decodable { struct TokenResponse: Decodable {
let creationTime = Date()
var token: String var token: String
var expires_in: Int?
var tokenExpiresAt: Date {
get {
// Tokens can expire and expire_in field is used to determine when:
//
// >The duration in seconds since the token was issued that it will remain valid.
// >When omitted, this defaults to 60 seconds. For compatibility with older clients,
// >a token should never be returned with less than 60 seconds to live.
//
// [1]: https://docs.docker.com/registry/spec/auth/token/#requesting-a-token
creationTime + TimeInterval(expires_in ?? 60)
}
}
var isValid: Bool {
get {
Date() < tokenExpiresAt
}
}
} }
class Registry { class Registry {
var baseURL: URL var baseURL: URL
var namespace: String var namespace: String
var token: String? = nil var currentAuthToken: TokenResponse? = nil
init(host: String, namespace: String) throws { init(host: String, namespace: String) throws {
var baseURLComponents = URLComponents() var baseURLComponents = URLComponents()
@@ -147,6 +170,11 @@ class Registry {
} }
request.httpBody = body request.httpBody = body
// Invalidate token if it has expired
if currentAuthToken?.isValid == false {
currentAuthToken = nil
}
var (data, response) = try await authAwareRequest(request: request) var (data, response) = try await authAwareRequest(request: request)
if response.statusCode == 401 { if response.statusCode == 401 {
@@ -199,20 +227,20 @@ class Registry {
headers["Authorization"] = "Basic \(encodedCredentials!)" headers["Authorization"] = "Basic \(encodedCredentials!)"
} }
let (responseData, response) = try await rawRequest("GET", authenticateURL, headers: headers) let (tokenResponseRaw, response) = try await rawRequest("GET", authenticateURL, headers: headers)
if response.statusCode != 200 { if response.statusCode != 200 {
throw RegistryError.AuthFailed(why: "received unexpected HTTP status code \(response.statusCode) " throw RegistryError.AuthFailed(why: "received unexpected HTTP status code \(response.statusCode) "
+ "while retrieving an authentication token", details: String(decoding: responseData, as: UTF8.self)) + "while retrieving an authentication token", details: String(decoding: tokenResponseRaw, as: UTF8.self))
} }
token = try JSONDecoder().decode(TokenResponse.self, from: responseData).token currentAuthToken = try JSONDecoder().decode(TokenResponse.self, from: tokenResponseRaw)
} }
private func authAwareRequest(request: URLRequest) async throws -> (Data, HTTPURLResponse) { private func authAwareRequest(request: URLRequest) async throws -> (Data, HTTPURLResponse) {
var request = request var request = request
if let token = self.token { if let token = currentAuthToken {
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.addValue("Bearer \(token.token)", forHTTPHeaderField: "Authorization")
} }
let (responseData, response) = try await URLSession.shared.data(for: request) let (responseData, response) = try await URLSession.shared.data(for: request)