Skip to content

Commit

Permalink
Cilicon 2.0 (#22)
Browse files Browse the repository at this point in the history
* Add support for Tart Images

* Add SSH support

* Add pre and post run and ssh credentials

* Added ANSIParser

* SSHLogger using ANSIParser

* fix ANSI parser

* broken: add oci fetching test

* push progress

* close filehandle

* Cilicon Progress

* rm Copier, adjust installer, add upgrader

* Add auto-config

* cleanup dependencies

* clean restart

* add basic gitlab support

* Cilicon 2.0 readme (#21)

* Update README.md

* Reduce minimum config

* Remove autotransfer

---------

Co-authored-by: Alex Moisei <dimentar@gmail.com>
  • Loading branch information
Marcocanc and Dimentar committed Jun 14, 2023
1 parent bfb1b4e commit faaa9ea
Show file tree
Hide file tree
Showing 51 changed files with 1,644 additions and 616 deletions.
130 changes: 104 additions & 26 deletions Cilicon.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "bigint",
"kind" : "remoteSourceControl",
"location" : "https://github.com/attaswift/BigInt.git",
"state" : {
"revision" : "0ed110f7555c34ff468e72e1686e59721f2b0da6",
"version" : "5.3.0"
}
},
{
"identity" : "bluecryptor",
"kind" : "remoteSourceControl",
Expand Down Expand Up @@ -27,6 +36,14 @@
"version" : "1.0.201"
}
},
{
"identity" : "citadel",
"kind" : "remoteSourceControl",
"location" : "https://github.com/orlandos-nl/Citadel",
"state" : {
"revision" : "3e920e6f4b364b272a7e40cf675d5145de520f76"
}
},
{
"identity" : "kituracontracts",
"kind" : "remoteSourceControl",
Expand All @@ -45,31 +62,76 @@
"version" : "2.0.0"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10",
"version" : "1.1.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2",
"version" : "1.0.4"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "067254c79435de759aeef4a6a03e43d087d61312",
"version" : "2.0.5"
}
},
{
"identity" : "swift-jwt",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Kitura/Swift-JWT",
"state" : {
"revision" : "08e02ff214c41df49bdd189ff837d68ba11c437b",
"version" : "4.0.0"
"revision" : "f68ec28fbd90a651597e9e825ea7f315f8d52a1f",
"version" : "4.0.1"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "6fe203dc33195667ce1759bf0182975e4653ba1c",
"version" : "1.4.4"
"revision" : "32e8d724467f8fe623624570367e3d50c5638e46",
"version" : "1.5.2"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "2d8e6ca36fe3e8ed74b0883f593757a45463c34d",
"version" : "2.53.0"
}
},
{
"identity" : "swift-nio-ssh",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Joannis/swift-nio-ssh.git",
"state" : {
"revision" : "70506c0345480a9070ef71b58cff2b3f3e6a7662",
"version" : "0.3.1"
}
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/Yams",
"state" : {
"revision" : "01835dc202670b5bb90d07f3eae41867e9ed29f6",
"version" : "5.0.1"
"revision" : "f47ba4838c30dbd59998a4e4c87ab620ff959e8a",
"version" : "5.0.5"
}
}
],
Expand Down
139 changes: 139 additions & 0 deletions Cilicon/ANSIParser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import AppKit

struct ANSIParser {
typealias Color = NSColor
typealias Font = NSFont
static let fontName = "Andale Mono"

static let defaultFont = Font.monospacedSystemFont(ofSize: Font.systemFontSize, weight: .regular)
static let defaultAttributes: [NSAttributedString.Key: Any] = [
.font: defaultFont as Any
]

static func parse(_ log: String) -> AttributedString {
guard let regex = try? Regex(#"\[[0-9;]+m"#) else { return .init(log) }
var result = AttributedString()
let ranges = log.ranges(of: regex)
/// Create copy of ranges offset by 1, playing a role of next
var nextRanges = ranges.dropFirst()
nextRanges.append(log.endIndex ..< log.endIndex)

for (range, next) in zip(ranges, nextRanges) {
result.append(
AttributedString(
/// String to format, is placed between the `range` and `next` ranged
String(log[range.upperBound ..< next.lowerBound]),
/// ANSI Code to parse
attributes: .init(attributesFor(ansiCode: String(log[range])))
)
)
}

/// Fallback in case failed to parse
if result.characters.isEmpty {
result.append(AttributedString(log.replacing(regex, with: { _ in "" }), attributes: .init(Self.defaultAttributes)))
}

return result
}


private static func attributesFor(ansiCode: String) -> [NSAttributedString.Key: Any] {

var attributes: [NSAttributedString.Key: Any] = Self.defaultAttributes
attributes[.font] = defaultFont
let codes = ansiCode
.split(separator: ";")
.map { $0.trimmingCharacters(in: .decimalDigits.inverted) }
.map { Int($0) ?? 0 }

/// In case of `38` and `48` -> `break codesLoop` as final part of attribute format
codesLoop: for (index, code) in codes.enumerated() {
switch code {
case 0:
attributes = Self.defaultAttributes
case 1:
let newDescriptor = defaultFont.fontDescriptor.withSymbolicTraits(.bold)
attributes[.font] = Font(descriptor: newDescriptor, size: Font.systemFontSize)
case 3:
let newDescriptor = defaultFont.fontDescriptor.withSymbolicTraits(.italic)
attributes[.font] = Font(descriptor: newDescriptor, size: Font.systemFontSize)
case 4: attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue
case 9: attributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
case 30 ... 37: attributes[.foregroundColor] = colorFromAnsiCode(code - 30)
case 40 ... 47: attributes[.backgroundColor] = colorFromAnsiCode(code - 40)
case 38, 48:
let destinationKey: NSAttributedString.Key = codes[index] == 38 ? .foregroundColor : .backgroundColor

/// format: `...38;5;x` or `...48;5;x`
if codes.count >= 3, codes[index + 1] == 5 {
attributes[destinationKey] = colorFromAnsiCode(codes[index + 2])
break codesLoop
}
/// format: `...38;2;r;g;b` or `...48;2;r;g;b`
if codes.count >= 5, codes[index + 1] == 2 {
attributes[destinationKey] = Color(
red: CGFloat(codes[index + 2]) / 255,
green: CGFloat(codes[index + 3]) / 255,
blue: CGFloat(codes[index + 4]) / 255,
alpha: 1
)
break codesLoop
}
default: break
}
}

return attributes
}

/// Converting the `8, 16, 256 bits` code into `Color`
private static func colorFromAnsiCode(_ code: Int) -> Color {
if code < 16 {
return standardAnsiColors[safe: code] ?? .black
} else if code < 232 {
let r = Double((code / 36) % 6) * 51.0 / 255.0
let g = Double((code / 6) % 6) * 51.0 / 255.0
let b = Double(code % 6) * 51.0 / 255.0
return Color(red: r, green: g, blue: b, alpha: 1)
} else {
let gray = Double(8 + (code - 232) * 10) / 255.0
return Color(red: gray, green: gray, blue: gray, alpha: 1)
}
}

/// Standart ANSI `8, 16 bits` Colors
private static let standardAnsiColors: [Color] = [
.black,
.red,
.green,
.yellow,
.blue,
.magenta,
.cyan,
.white,
.init(red: 0.5, green: 0.5, blue: 0.5, alpha: 1), // bright black
.init(red: 1.0, green: 0.5, blue: 0.5, alpha: 1), // bright red
.init(red: 0.5, green: 1.0, blue: 0.5, alpha: 1), // bright green
.init(red: 1.0, green: 1.0, blue: 0.5, alpha: 1), // bright yellow
.init(red: 0.5, green: 0.5, blue: 1.0, alpha: 1), // bright blue
.init(red: 1.0, green: 0.5, blue: 1.0, alpha: 1), // bright magenta
.init(red: 0.5, green: 1.0, blue: 1.0, alpha: 1), // bright cyan
.white, // bright white
]
}

extension NSFont {
static func italicSystemFont(ofSize fontSize: CGFloat) -> Self? {
let font = NSFont(name: ANSIParser.fontName, size: systemFontSize) ?? .systemFont(ofSize: systemFontSize)
let italicDescriptor = font.fontDescriptor.withSymbolicTraits(.italic)
return Self(descriptor: italicDescriptor, size: fontSize)
}
}

extension Array {
subscript(safe index: Int) -> Element? {
guard index < count else { return nil }
return self[index]
}
}
61 changes: 51 additions & 10 deletions Cilicon/CiliconApp.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,61 @@
import SwiftUI

import Yams
@main
struct CiliconApp: App {
let config = Result {
try ConfigManager().config
}
@State private var vmSource: String = ""

var body: some Scene {
Window("Cilicon", id: "cihost") {
switch config {
case .success(let config):
let contentView = ContentView(config: config)
AnyView(contentView)
case .failure(let error):
AnyView(Text(error.localizedDescription))

if ConfigManager.fileExists {
switch Result(catching: { try ConfigManager().config }) {
case .success(let config):
let contentView = ContentView(config: config)
AnyView(contentView)
case .failure(let error):
Text(String(describing: error))
}

} else {
Text("No Config found.\n\nTo create one, enter the path or an OCI image starting with oci:// below and press return")
.multilineTextAlignment(.center)
TextField(
"VM Source. Enter the VM path or an OCI image starting with oci://",
text: $vmSource
)
.frame(width: 500)
.onSubmit {
guard let source = VMSource(string: vmSource) else {
return
}
let scriptConfig = ScriptProvisionerConfig(run: "echo Hello World && sleep 10 && echo Shutting down")
let config = Config(provisioner: .script(scriptConfig),
hardware: .init(ramGigabytes: 8,
display: .default,
connectsToAudioDevice: false),
directoryMounts: [],
source: source,
vmClonePath: URL(filePath: NSHomeDirectory()).appending(component: "vmclone").path,
editorMode: false,
retryDelay: 3,
sshCredentials: .init(username: "admin", password: "admin"))

try? YAMLEncoder().encode(config).write(toFile: ConfigManager.path, atomically: true, encoding: .utf8)
restart()
}
}
}
}
}

extension App {
func restart() {
let url = URL(fileURLWithPath: Bundle.main.resourcePath!)
let path = url.deletingLastPathComponent().deletingLastPathComponent().absoluteString
let task = Process()
task.launchPath = "/usr/bin/open"
task.arguments = [path]
task.launch()
exit(0)
}
}
16 changes: 16 additions & 0 deletions Cilicon/Config/BuildkiteAgentProvisionerConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Foundation
struct BuildkiteAgentProvisionerConfig: Decodable {
let agentToken: String
let tags: [String]

enum CodingKeys: CodingKey {
case agentToken
case tags
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.agentToken = try container.decode(String.self, forKey: .agentToken)
self.tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
}
}

0 comments on commit faaa9ea

Please sign in to comment.