From 663c5ff1110631d18458495cc98303aaf3a5c4e4 Mon Sep 17 00:00:00 2001 From: Guillaume Rose Date: Thu, 4 Mar 2021 09:17:14 +0100 Subject: [PATCH 1/6] Use a single function to call the daemon --- .../DaemonCommander/DaemonCommander.swift | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/CodeReady Containers/DaemonCommander/DaemonCommander.swift b/CodeReady Containers/DaemonCommander/DaemonCommander.swift index 85a34a0..34bb4e7 100644 --- a/CodeReady Containers/DaemonCommander/DaemonCommander.swift +++ b/CodeReady Containers/DaemonCommander/DaemonCommander.swift @@ -22,36 +22,32 @@ class DaemonCommander { self.daemonSocket?.close() } // connect sets up the socket and connects to the daemon sokcet - public func connectToDaemon() { + public func sendCommand(command: Data) -> Data { do { // Create an Unix socket... try self.daemonSocket = Socket.create(family: .unix, type: .stream, proto: .unix) guard let socket = self.daemonSocket else { print("Unable to unwrap socket...") - return + return "Failed".data(using: .utf8)! } self.daemonSocket = socket try socket.connect(to: self.socketPath) } catch let error { guard error is Socket.Error else { print(error.localizedDescription) - return + return "Failed".data(using: .utf8)! } } - } - - public func sendCommand(command: Data) { + do { try self.daemonSocket?.write(from: command) } catch let error { guard error is Socket.Error else { print(error.localizedDescription) - return + return "Failed".data(using: .utf8)! } } - } - - public func readResponse() -> Data { + do { var readData = Data(capacity: DaemonCommander.bufferSize) let bytesRead = try self.daemonSocket?.read(into: &readData) @@ -77,9 +73,7 @@ func SendCommandToDaemon(command: Request) -> Data? { print(String(data: req, encoding: .utf8)!) let daemonConnection = DaemonCommander(sockPath: socketPath.path) print(socketPath.path) - daemonConnection.connectToDaemon() - daemonConnection.sendCommand(command: req) - let reply = daemonConnection.readResponse() + let reply = daemonConnection.sendCommand(command: req) return reply } catch let error { print(error.localizedDescription) @@ -137,9 +131,7 @@ func SendCommandToDaemon(command: ConfigGetRequest) -> Data? { do { let req = try JSONEncoder().encode(command) let daemonConnection = DaemonCommander(sockPath: socketPath.path) - daemonConnection.connectToDaemon() - daemonConnection.sendCommand(command: req) - return daemonConnection.readResponse() + return daemonConnection.sendCommand(command: req) } catch let error { print(error.localizedDescription) } @@ -149,9 +141,7 @@ func SendCommandToDaemon(command: ConfigGetRequest) -> Data? { func sendToDaemonAndReadResponse(payload: Data) -> Data? { let daemonConnection = DaemonCommander(sockPath: socketPath.path) print(socketPath.path) - daemonConnection.connectToDaemon() - daemonConnection.sendCommand(command: payload) - let reply = daemonConnection.readResponse() + let reply = daemonConnection.sendCommand(command: payload) if reply.count > 0 { return reply } From a43868949bf8768dd4bbcc2027d6a2d537aeff75 Mon Sep 17 00:00:00 2001 From: Guillaume Rose Date: Thu, 4 Mar 2021 09:40:15 +0100 Subject: [PATCH 2/6] Throws exception in DaemonCommander instead of returning failed string --- .../DaemonCommander/DaemonCommander.swift | 64 ++++++------------- .../DaemonCommander/Handlers.swift | 1 + 2 files changed, 20 insertions(+), 45 deletions(-) diff --git a/CodeReady Containers/DaemonCommander/DaemonCommander.swift b/CodeReady Containers/DaemonCommander/DaemonCommander.swift index 34bb4e7..a54f193 100644 --- a/CodeReady Containers/DaemonCommander/DaemonCommander.swift +++ b/CodeReady Containers/DaemonCommander/DaemonCommander.swift @@ -10,57 +10,34 @@ import Socket import Foundation class DaemonCommander { - var daemonSocket: Socket? = nil - var socketPath: String - static var bufferSize = 1024 + let socketPath: String + static let bufferSize = 1024 init(sockPath: String) { self.socketPath = sockPath } - deinit { - self.daemonSocket?.close() - } - // connect sets up the socket and connects to the daemon sokcet - public func sendCommand(command: Data) -> Data { - do { - // Create an Unix socket... - try self.daemonSocket = Socket.create(family: .unix, type: .stream, proto: .unix) - guard let socket = self.daemonSocket else { - print("Unable to unwrap socket...") - return "Failed".data(using: .utf8)! - } - self.daemonSocket = socket - try socket.connect(to: self.socketPath) - } catch let error { - guard error is Socket.Error else { - print(error.localizedDescription) - return "Failed".data(using: .utf8)! - } - } - + public func sendCommand(command: Data) throws -> Data { do { - try self.daemonSocket?.write(from: command) - } catch let error { - guard error is Socket.Error else { - print(error.localizedDescription) - return "Failed".data(using: .utf8)! + let daemonSocket = try Socket.create(family: .unix, type: .stream, proto: .unix) + defer { + daemonSocket.close() } - } - - do { + try daemonSocket.connect(to: self.socketPath) + try daemonSocket.write(from: command) var readData = Data(capacity: DaemonCommander.bufferSize) - let bytesRead = try self.daemonSocket?.read(into: &readData) - if bytesRead! > 1 { + let bytesRead = try daemonSocket.read(into: &readData) + if bytesRead > 1 { return readData } + throw DaemonError.badResponse } catch let error { guard error is Socket.Error else { print(error.localizedDescription) - return "Failed".data(using: .utf8)! + throw DaemonError.io } + throw error } - return "Failed".data(using: .utf8)! } } @@ -72,9 +49,7 @@ func SendCommandToDaemon(command: Request) -> Data? { let req = try JSONEncoder().encode(command) print(String(data: req, encoding: .utf8)!) let daemonConnection = DaemonCommander(sockPath: socketPath.path) - print(socketPath.path) - let reply = daemonConnection.sendCommand(command: req) - return reply + return try daemonConnection.sendCommand(command: req) } catch let error { print(error.localizedDescription) } @@ -102,7 +77,7 @@ struct configunset: Encodable { func SendCommandToDaemon(command: ConfigsetRequest) -> Data? { do { let req = try JSONEncoder().encode(command) - let res = sendToDaemonAndReadResponse(payload: req) + let res = try sendToDaemonAndReadResponse(payload: req) if res?.count ?? -1 > 0 { return res } @@ -116,7 +91,7 @@ func SendCommandToDaemon(command: ConfigsetRequest) -> Data? { func SendCommandToDaemon(command: ConfigunsetRequest) -> Data? { do { let req = try JSONEncoder().encode(command) - let res = sendToDaemonAndReadResponse(payload: req) + let res = try sendToDaemonAndReadResponse(payload: req) if res?.count ?? -1 > 0 { return res } @@ -131,17 +106,16 @@ func SendCommandToDaemon(command: ConfigGetRequest) -> Data? { do { let req = try JSONEncoder().encode(command) let daemonConnection = DaemonCommander(sockPath: socketPath.path) - return daemonConnection.sendCommand(command: req) + return try daemonConnection.sendCommand(command: req) } catch let error { print(error.localizedDescription) } return "Failed".data(using: .utf8) } -func sendToDaemonAndReadResponse(payload: Data) -> Data? { +func sendToDaemonAndReadResponse(payload: Data) throws -> Data? { let daemonConnection = DaemonCommander(sockPath: socketPath.path) - print(socketPath.path) - let reply = daemonConnection.sendCommand(command: payload) + let reply = try daemonConnection.sendCommand(command: payload) if reply.count > 0 { return reply } diff --git a/CodeReady Containers/DaemonCommander/Handlers.swift b/CodeReady Containers/DaemonCommander/Handlers.swift index 4049f18..3334bee 100644 --- a/CodeReady Containers/DaemonCommander/Handlers.swift +++ b/CodeReady Containers/DaemonCommander/Handlers.swift @@ -137,6 +137,7 @@ struct CrcConfigs: Codable { } enum DaemonError: Error { + case io // input/output error case noResponse case badResponse case undefined From 031bfa07eb66db44343b963cdd279f09cb455674 Mon Sep 17 00:00:00 2001 From: Guillaume Rose Date: Thu, 4 Mar 2021 09:57:56 +0100 Subject: [PATCH 3/6] Propagate socket error to the user with exception when changing settings --- .../DaemonCommander/DaemonCommander.swift | 34 +++++-------------- .../config/ConfigViewController.swift | 24 ++++++------- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/CodeReady Containers/DaemonCommander/DaemonCommander.swift b/CodeReady Containers/DaemonCommander/DaemonCommander.swift index a54f193..5c3b37b 100644 --- a/CodeReady Containers/DaemonCommander/DaemonCommander.swift +++ b/CodeReady Containers/DaemonCommander/DaemonCommander.swift @@ -74,32 +74,14 @@ struct configunset: Encodable { var properties: [String] } -func SendCommandToDaemon(command: ConfigsetRequest) -> Data? { - do { - let req = try JSONEncoder().encode(command) - let res = try sendToDaemonAndReadResponse(payload: req) - if res?.count ?? -1 > 0 { - return res - } - } - catch let jsonErr { - print(jsonErr) - } - return "Failed".data(using: .utf8) +func SendCommandToDaemon(command: ConfigsetRequest) throws -> Data { + let req = try JSONEncoder().encode(command) + return try sendToDaemonAndReadResponse(payload: req) } -func SendCommandToDaemon(command: ConfigunsetRequest) -> Data? { - do { - let req = try JSONEncoder().encode(command) - let res = try sendToDaemonAndReadResponse(payload: req) - if res?.count ?? -1 > 0 { - return res - } - } - catch let jsonErr { - print(jsonErr) - } - return "Failed".data(using: .utf8) +func SendCommandToDaemon(command: ConfigunsetRequest) throws -> Data { + let req = try JSONEncoder().encode(command) + return try sendToDaemonAndReadResponse(payload: req) } func SendCommandToDaemon(command: ConfigGetRequest) -> Data? { @@ -113,11 +95,11 @@ func SendCommandToDaemon(command: ConfigGetRequest) -> Data? { return "Failed".data(using: .utf8) } -func sendToDaemonAndReadResponse(payload: Data) throws -> Data? { +func sendToDaemonAndReadResponse(payload: Data) throws -> Data { let daemonConnection = DaemonCommander(sockPath: socketPath.path) let reply = try daemonConnection.sendCommand(command: payload) if reply.count > 0 { return reply } - return "Failed".data(using: .utf8) + throw DaemonError.badResponse } diff --git a/CodeReady Containers/config/ConfigViewController.swift b/CodeReady Containers/config/ConfigViewController.swift index 055b42c..e4e4e02 100644 --- a/CodeReady Containers/config/ConfigViewController.swift +++ b/CodeReady Containers/config/ConfigViewController.swift @@ -241,9 +241,9 @@ class ConfigViewController: NSViewController { alert.beginSheetModal(for: self.view.window!) { (response) in if response == .alertFirstButtonReturn { // encode the json for configset and send it to the daemon - let configsJson = configset(properties: self.changedConfigs ?? CrcConfigs()) - guard let res = SendCommandToDaemon(command: ConfigsetRequest(command: "setconfig", args: configsJson)) else { return } do { + let configsJson = configset(properties: self.changedConfigs ?? CrcConfigs()) + let res = try SendCommandToDaemon(command: ConfigsetRequest(command: "setconfig", args: configsJson)) let result = try JSONDecoder().decode(configResult.self, from: res) if !result.Error.isEmpty { let alert = NSAlert() @@ -252,17 +252,17 @@ class ConfigViewController: NSViewController { alert.alertStyle = .warning alert.runModal() } - } catch let jsonErr { - print(jsonErr) - } - if self.configsNeedingUnset.count > 0 { - print(self.configsNeedingUnset) - guard let res = SendCommandToDaemon(command: ConfigunsetRequest(command: "unsetconfig", args: configunset(properties: self.configsNeedingUnset))) else { return } - print(String(data: res, encoding: .utf8) ?? "Nothing") - self.configsNeedingUnset = [] + if self.configsNeedingUnset.count > 0 { + print(self.configsNeedingUnset) + let res = try SendCommandToDaemon(command: ConfigunsetRequest(command: "unsetconfig", args: configunset(properties: self.configsNeedingUnset))) + print(String(data: res, encoding: .utf8) ?? "Nothing") + self.configsNeedingUnset = [] + } + self.LoadConfigs() + self.clearChangeTrackers() + } catch let error { + showAlertFailedAndCheckLogs(message: "Failed to apply configuration", informativeMsg: error.localizedDescription) } - self.LoadConfigs() - self.clearChangeTrackers() } } } From f62eccdad16753f8d3f4ae37d8b39b20e810d961 Mon Sep 17 00:00:00 2001 From: Guillaume Rose Date: Thu, 4 Mar 2021 10:04:07 +0100 Subject: [PATCH 4/6] Push up the error dialog in AppDelegate when starting the VM --- CodeReady Containers/AppDelegate.swift | 9 +++++++- .../DaemonCommander/DaemonCommander.swift | 13 ++++------- .../DaemonCommander/Handlers.swift | 23 ++++--------------- 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/CodeReady Containers/AppDelegate.swift b/CodeReady Containers/AppDelegate.swift index 28ff746..a672e7c 100644 --- a/CodeReady Containers/AppDelegate.swift +++ b/CodeReady Containers/AppDelegate.swift @@ -82,7 +82,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { // check if pull-secret-file is configured // if yes call HadleStart("") // otherwise invoke the pullSecretPicker view - let response = GetConfigFromDaemon(properties: ["pull-secret-file"]) + var response: Dictionary + do { + response = try GetConfigFromDaemon(properties: ["pull-secret-file"]) + } catch let error { + showAlertFailedAndCheckLogs(message: "Failed to Check if Pull Secret is configured", + informativeMsg: "Ensure the CRC daemon is running, for more information please check the logs.\nError: \(error)") + return + } if self.status == "Stopped" { DispatchQueue.global(qos: .userInteractive).async { HandleStart(pullSecretPath: "") diff --git a/CodeReady Containers/DaemonCommander/DaemonCommander.swift b/CodeReady Containers/DaemonCommander/DaemonCommander.swift index 5c3b37b..ba57dfb 100644 --- a/CodeReady Containers/DaemonCommander/DaemonCommander.swift +++ b/CodeReady Containers/DaemonCommander/DaemonCommander.swift @@ -84,15 +84,10 @@ func SendCommandToDaemon(command: ConfigunsetRequest) throws -> Data { return try sendToDaemonAndReadResponse(payload: req) } -func SendCommandToDaemon(command: ConfigGetRequest) -> Data? { - do { - let req = try JSONEncoder().encode(command) - let daemonConnection = DaemonCommander(sockPath: socketPath.path) - return try daemonConnection.sendCommand(command: req) - } catch let error { - print(error.localizedDescription) - } - return "Failed".data(using: .utf8) +func SendCommandToDaemon(command: ConfigGetRequest) throws -> Data { + let req = try JSONEncoder().encode(command) + let daemonConnection = DaemonCommander(sockPath: socketPath.path) + return try daemonConnection.sendCommand(command: req) } func sendToDaemonAndReadResponse(payload: Data) throws -> Data { diff --git a/CodeReady Containers/DaemonCommander/Handlers.swift b/CodeReady Containers/DaemonCommander/Handlers.swift index 3334bee..8142c59 100644 --- a/CodeReady Containers/DaemonCommander/Handlers.swift +++ b/CodeReady Containers/DaemonCommander/Handlers.swift @@ -427,25 +427,10 @@ func FetchVersionInfoFromDaemon() -> (String, String) { return ("","") } -func GetConfigFromDaemon(properties: [String]) -> Dictionary { - let r = SendCommandToDaemon(command: ConfigGetRequest(command: "getconfig", args: PropertiesArray(properties: properties))) - guard let data = r else { return ["":""] } - if String(bytes: data, encoding: .utf8) == "Failed" { - // Alert show error - DispatchQueue.main.async { - showAlertFailedAndCheckLogs(message: "Failed to Check if Pull Secret is configured", informativeMsg: "Ensure the CRC daemon is running, for more information please check the logs") - } - return ["":""] - } - do { - let configGetResult = try JSONDecoder().decode(ConfigGetResult.self, from: data) - if !configGetResult.Configs.isEmpty { - return configGetResult.Configs - } - } catch let jsonErr { - print(jsonErr) - } - return ["":""] +func GetConfigFromDaemon(properties: [String]) throws -> Dictionary { + let data = try SendCommandToDaemon(command: ConfigGetRequest(command: "getconfig", args: PropertiesArray(properties: properties))) + let configGetResult = try JSONDecoder().decode(ConfigGetResult.self, from: data) + return configGetResult.Configs } func GetAllConfigFromDaemon() throws -> (CrcConfigs?) { From 9c350b30a4951d49c433d19445b037dd50c4d180 Mon Sep 17 00:00:00 2001 From: Guillaume Rose Date: Thu, 4 Mar 2021 13:42:53 +0100 Subject: [PATCH 5/6] =?UTF-8?q?Don=E2=80=99t=20block=20UI=20thread=20when?= =?UTF-8?q?=20refreshing=20the=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If status takes too long to answer, it might block the UI a little bit. --- CodeReady Containers/AppDelegate.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CodeReady Containers/AppDelegate.swift b/CodeReady Containers/AppDelegate.swift index a672e7c..47ef716 100644 --- a/CodeReady Containers/AppDelegate.swift +++ b/CodeReady Containers/AppDelegate.swift @@ -221,10 +221,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { } @objc func refreshStatusAndMenu() { - let status = clusterStatus() - self.status = status - DispatchQueue.main.async { - self.initializeMenus(status: status) + DispatchQueue.global(qos: .background).async { + let status = clusterStatus() + self.status = status + DispatchQueue.main.async { + self.initializeMenus(status: status) + } } } } From 5c2ad1ed2787591f9115b607a7349d13d886948a Mon Sep 17 00:00:00 2001 From: Guillaume Rose Date: Thu, 4 Mar 2021 13:44:48 +0100 Subject: [PATCH 6/6] Propagate exceptions when calling the daemon, use it to show errors to users Also reshape status handling: each exception type has a different status. --- CodeReady Containers/AppDelegate.swift | 37 +-- .../DaemonCommander/DaemonCommander.swift | 15 +- .../DaemonCommander/Handlers.swift | 212 +++++------------- .../DetailedStatusViewController.swift | 18 +- CodeReady Containers/Helpers/Helpers.swift | 55 +++-- .../config/ConfigViewController.swift | 48 ++-- 6 files changed, 121 insertions(+), 264 deletions(-) diff --git a/CodeReady Containers/AppDelegate.swift b/CodeReady Containers/AppDelegate.swift index 47ef716..702b8b4 100644 --- a/CodeReady Containers/AppDelegate.swift +++ b/CodeReady Containers/AppDelegate.swift @@ -153,41 +153,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { NSApplication.shared.terminate(self) } - func updateStatusMenuItem(status: String) { - if status == "Stopped" { - self.statusMenuItem.title = "Cluster is Stopped" - self.statusMenuItem.image = NSImage(named:NSImage.statusUnavailableName) - } - if status == "Running" { - self.statusMenuItem.title = "Cluster is Running" - self.statusMenuItem.image = NSImage(named:NSImage.statusAvailableName) - } - } - - func showClusterStartingMessageOnStatusMenuItem() { - self.statusMenuItem.title = "Cluster is starting..." - self.statusMenuItem.image = nil - } - - func showClusterStatusUnknownOnStatusMenuItem() { - self.statusMenuItem.title = "Status Unknown" - self.statusMenuItem.image = NSImage(named: NSImage.statusNoneName) - } - func updateMenuStates(state: MenuStates) { - self.startMenuItem.isEnabled = state.startMenuEnabled - self.stopMenuItem.isEnabled = state.stopMenuEnabled - self.deleteMenuItem.isEnabled = state.deleteMenuEnabled - self.webConsoleMenuItem.isEnabled = state.webconsoleMenuEnabled - self.ocLoginForDeveloper.isEnabled = state.ocLoginForDeveloperEnabled - self.ocLoginForKubeadmin.isEnabled = state.ocLoginForAdminEnabled - self.copyOcLoginCommand.isEnabled = state.copyOcLoginCommand - } - func initializeMenus(status: String) { - self.statusMenuItem.title = "Status Unknown" - self.statusMenuItem.image = NSImage(named:NSImage.statusNoneName) - updateStatusMenuItem(status: status) + self.statusMenuItem.title = status if status == "Running" { + self.statusMenuItem.image = NSImage(named:NSImage.statusAvailableName) self.startMenuItem.isEnabled = false self.stopMenuItem.isEnabled = true self.deleteMenuItem.isEnabled = true @@ -197,6 +166,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { self.ocLoginForKubeadmin.isEnabled = true self.statusItem.button?.appearsDisabled = false } else if status == "Stopped" { + self.statusMenuItem.image = NSImage(named:NSImage.statusUnavailableName) self.startMenuItem.isEnabled = true self.stopMenuItem.isEnabled = false self.deleteMenuItem.isEnabled = true @@ -206,6 +176,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { self.ocLoginForKubeadmin.isEnabled = false self.statusItem.button?.appearsDisabled = true } else { + self.statusMenuItem.image = NSImage(named:NSImage.statusNoneName) self.startMenuItem.isEnabled = true self.stopMenuItem.isEnabled = false self.deleteMenuItem.isEnabled = false diff --git a/CodeReady Containers/DaemonCommander/DaemonCommander.swift b/CodeReady Containers/DaemonCommander/DaemonCommander.swift index ba57dfb..d658e16 100644 --- a/CodeReady Containers/DaemonCommander/DaemonCommander.swift +++ b/CodeReady Containers/DaemonCommander/DaemonCommander.swift @@ -44,16 +44,11 @@ class DaemonCommander { let userHomePath: URL = FileManager.default.homeDirectoryForCurrentUser let socketPath: URL = userHomePath.appendingPathComponent(".crc").appendingPathComponent("crc.sock") -func SendCommandToDaemon(command: Request) -> Data? { - do { - let req = try JSONEncoder().encode(command) - print(String(data: req, encoding: .utf8)!) - let daemonConnection = DaemonCommander(sockPath: socketPath.path) - return try daemonConnection.sendCommand(command: req) - } catch let error { - print(error.localizedDescription) - } - return "Failed".data(using: .utf8) +func SendCommandToDaemon(command: Request) throws -> Data { + let req = try JSONEncoder().encode(command) + print(String(data: req, encoding: .utf8)!) + let daemonConnection = DaemonCommander(sockPath: socketPath.path) + return try daemonConnection.sendCommand(command: req) } struct ConfigsetRequest: Encodable { diff --git a/CodeReady Containers/DaemonCommander/Handlers.swift b/CodeReady Containers/DaemonCommander/Handlers.swift index 8142c59..2493b5d 100644 --- a/CodeReady Containers/DaemonCommander/Handlers.swift +++ b/CodeReady Containers/DaemonCommander/Handlers.swift @@ -144,39 +144,23 @@ enum DaemonError: Error { } func HandleStop() { - let r = SendCommandToDaemon(command: Request(command: "stop", args: nil)) - guard let data = r else { return } - if String(bytes: data, encoding: .utf8) == "Failed" { + var data: Data + do { + data = try SendCommandToDaemon(command: Request(command: "stop", args: nil)) + } catch let error { DispatchQueue.main.async { let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.updateMenuStates(state: MenuStates(startMenuEnabled: true, - stopMenuEnabled: false, - deleteMenuEnabled: true, - webconsoleMenuEnabled: true, - ocLoginForDeveloperEnabled: true, - ocLoginForAdminEnabled: true, - copyOcLoginCommand: true) - ) - - showAlertFailedAndCheckLogs(message: "Failed deleting the CRC cluster", informativeMsg: "Make sure the CRC daemon is running, or check the logs to get more information") + appDelegate?.refreshStatusAndMenu() + showAlertFailedAndCheckLogs(message: "Failed deleting the CRC cluster", informativeMsg: "Make sure the CRC daemon is running, or check the logs to get more information. Error: \(error)") } return } do { - let stopResult = try JSONDecoder().decode(StopResult.self, from: r!) + let stopResult = try JSONDecoder().decode(StopResult.self, from: data) if stopResult.Success { DispatchQueue.main.async { let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.updateMenuStates(state: MenuStates(startMenuEnabled: true, - stopMenuEnabled: false, - deleteMenuEnabled: true, - webconsoleMenuEnabled: false, - ocLoginForDeveloperEnabled: false, - ocLoginForAdminEnabled: false, - copyOcLoginCommand: false) - ) - appDelegate?.updateStatusMenuItem(status: "Stopped") - + appDelegate?.refreshStatusAndMenu() displayNotification(title: "Successfully Stopped Cluster", body: "The CRC Cluster was successfully stopped") } } @@ -184,15 +168,7 @@ func HandleStop() { DispatchQueue.main.async { showAlertFailedAndCheckLogs(message: "Failed to stop OpenShift cluster", informativeMsg: "\(stopResult.Error)") let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.showClusterStatusUnknownOnStatusMenuItem() - appDelegate?.updateMenuStates(state: MenuStates(startMenuEnabled: false, - stopMenuEnabled: true, - deleteMenuEnabled: true, - webconsoleMenuEnabled: true, - ocLoginForDeveloperEnabled: true, - ocLoginForAdminEnabled: true, - copyOcLoginCommand: true) - ) + appDelegate?.refreshStatusAndMenu() } } } catch let jsonErr { @@ -204,57 +180,33 @@ func HandleStop() { func HandleStart(pullSecretPath: String) { DispatchQueue.main.async { let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.showClusterStartingMessageOnStatusMenuItem() - } - let response: Data? - if pullSecretPath == "" { - response = SendCommandToDaemon(command: Request(command: "start", args: nil)) - } else { - response = SendCommandToDaemon(command: Request(command: "start", args: ["pullSecretFile":pullSecretPath])) + appDelegate?.initializeMenus(status: "Starting") } - guard let data = response else { return } - if String(bytes: data, encoding: .utf8) == "Failed" { + var response: Data + do { + if pullSecretPath == "" { + response = try SendCommandToDaemon(command: Request(command: "start", args: nil)) + } else { + response = try SendCommandToDaemon(command: Request(command: "start", args: ["pullSecretFile":pullSecretPath])) + } + displayNotification(title: "CodeReady Containers", body: "Starting OpenShift Cluster, this could take a few minutes..") + } catch let error { // show notification about the failure // Adjust the menus DispatchQueue.main.async { let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.updateMenuStates(state: MenuStates(startMenuEnabled: true, - stopMenuEnabled: false, - deleteMenuEnabled: true, - webconsoleMenuEnabled: false, - ocLoginForDeveloperEnabled: false, - ocLoginForAdminEnabled: false, - copyOcLoginCommand: false)) - - showAlertFailedAndCheckLogs(message: "Failed to start OpenShift cluster", informativeMsg: "CodeReady Containers failed to start the OpenShift cluster, ensure the CRC daemon is running or check the logs to find more information") - appDelegate?.showClusterStatusUnknownOnStatusMenuItem() + appDelegate?.refreshStatusAndMenu() + showAlertFailedAndCheckLogs(message: "Failed to start OpenShift cluster", informativeMsg: "CodeReady Containers failed to start the OpenShift cluster, ensure the CRC daemon is running or check the logs to find more information. Error: \(error)") } - } else { - displayNotification(title: "CodeReady Containers", body: "Starting OpenShift Cluster, this could take a few minutes..") + return } do { - let startResult = try JSONDecoder().decode(StartResult.self, from: data) + let startResult = try JSONDecoder().decode(StartResult.self, from: response) if startResult.Status == "Running" { DispatchQueue.main.async { let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.showClusterStartingMessageOnStatusMenuItem() - appDelegate?.updateMenuStates(state: MenuStates(startMenuEnabled: false, stopMenuEnabled: true, deleteMenuEnabled: true, webconsoleMenuEnabled: false, ocLoginForDeveloperEnabled: false, ocLoginForAdminEnabled: false, copyOcLoginCommand: false)) - } - // if vm is running but kubelet not yet started - if !startResult.KubeletStarted { - DispatchQueue.main.async { - let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.showClusterStatusUnknownOnStatusMenuItem() - displayNotification(title: "CodeReady Containers", body: "CodeReady Containers OpenShift Cluster is taking longer to start") - } - } else { - DispatchQueue.main.async { - let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.updateStatusMenuItem(status: startResult.Status) - appDelegate?.updateMenuStates(state: MenuStates(startMenuEnabled: false, stopMenuEnabled: true, deleteMenuEnabled: true, webconsoleMenuEnabled: true, ocLoginForDeveloperEnabled: true, ocLoginForAdminEnabled: true, copyOcLoginCommand: true)) - - displayNotification(title: "CodeReady Containers", body: "OpenShift Cluster is running") - } + appDelegate?.refreshStatusAndMenu() + displayNotification(title: "CodeReady Containers", body: "OpenShift Cluster is running") } } if startResult.Error != "" { @@ -262,15 +214,7 @@ func HandleStart(pullSecretPath: String) { let errMsg = startResult.Error.split(separator: "\n") showAlertFailedAndCheckLogs(message: "Failed to start OpenShift cluster", informativeMsg: "\(errMsg[0])") let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.showClusterStatusUnknownOnStatusMenuItem() - appDelegate?.updateMenuStates(state: MenuStates(startMenuEnabled: true, - stopMenuEnabled: false, - deleteMenuEnabled: true, - webconsoleMenuEnabled: false, - ocLoginForDeveloperEnabled: false, - ocLoginForAdminEnabled: false, - copyOcLoginCommand: false) - ) + appDelegate?.refreshStatusAndMenu() } } } catch let jsonErr { @@ -287,28 +231,16 @@ func HandleDelete() { if !yes { return } - let r = SendCommandToDaemon(command: Request(command: "delete", args: nil)) - guard let data = r else { return } - if String(bytes: data, encoding: .utf8) == "Failed" { - // handle failure to delete - // send alert that delete failed - // rearrange menu states - DispatchQueue.main.async { - let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.updateMenuStates(state: MenuStates(startMenuEnabled: true, stopMenuEnabled: false, deleteMenuEnabled: true, webconsoleMenuEnabled: false, ocLoginForDeveloperEnabled: false, ocLoginForAdminEnabled: false, copyOcLoginCommand: false)) - - showAlertFailedAndCheckLogs(message: "Failed to delete cluster", informativeMsg: "CRC failed to delete the OCP cluster, make sure the CRC daemom is running or check the logs to find more information") - } - } + do { + let data = try SendCommandToDaemon(command: Request(command: "delete", args: nil)) let deleteResult = try JSONDecoder().decode(DeleteResult.self, from: data) if deleteResult.Success { // send notification that delete succeeded // rearrage menus DispatchQueue.main.async { let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.updateMenuStates(state: MenuStates(startMenuEnabled: true, stopMenuEnabled: false, deleteMenuEnabled: false, webconsoleMenuEnabled: false, ocLoginForDeveloperEnabled: false, ocLoginForAdminEnabled: false, copyOcLoginCommand: false)) - appDelegate?.showClusterStatusUnknownOnStatusMenuItem() + appDelegate?.refreshStatusAndMenu() displayNotification(title: "Cluster Deleted", body: "The CRC Cluster is successfully deleted") } } @@ -316,44 +248,35 @@ func HandleDelete() { DispatchQueue.main.async { showAlertFailedAndCheckLogs(message: "Failed to delete OpenShift cluster", informativeMsg: "\(deleteResult.Error)") let appDelegate = NSApplication.shared.delegate as? AppDelegate - appDelegate?.showClusterStatusUnknownOnStatusMenuItem() + appDelegate?.refreshStatusAndMenu() } } - } catch let jsonErr { - print(jsonErr.localizedDescription) + } catch let error { + DispatchQueue.main.async { + let appDelegate = NSApplication.shared.delegate as? AppDelegate + appDelegate?.refreshStatusAndMenu() + showAlertFailedAndCheckLogs(message: "Failed to delete cluster", informativeMsg: "CRC failed to delete the OCP cluster, make sure the CRC daemom is running or check the logs to find more information. Error: \(error)") + } } } func HandleWebConsoleURL() { - let r = SendCommandToDaemon(command: Request(command: "webconsoleurl", args: nil)) - guard let data = r else { return } - if String(bytes: data, encoding: .utf8) == "Failed" { - // Alert show error - DispatchQueue.main.async { - showAlertFailedAndCheckLogs(message: "Failed to launch web console", informativeMsg: "Ensure the CRC daemon is running and a CRC cluster is running, for more information please check the logs") - } - } do { + let data = try SendCommandToDaemon(command: Request(command: "webconsoleurl", args: nil)) let webConsoleResult = try JSONDecoder().decode(WebConsoleResult.self, from: data) if webConsoleResult.Success { // open the webconsoleURL NSWorkspace.shared.open(URL(string: webConsoleResult.ClusterConfig.WebConsoleURL)!) } - } catch let jsonErr { - print(jsonErr) + } catch let error { + showAlertFailedAndCheckLogs(message: "Failed to launch web console", + informativeMsg: "Ensure the CRC daemon is running and a CRC cluster is running, for more information please check the logs. Error: \(error)") } } func HandleLoginCommandForKubeadmin() { - let r = SendCommandToDaemon(command: Request(command: "webconsoleurl", args: nil)) - guard let data = r else { return } - if String(bytes: data, encoding: .utf8) == "Failed" { - // Alert show error - DispatchQueue.main.async { - showAlertFailedAndCheckLogs(message: "Failed to get login command", informativeMsg: "Ensure the CRC daemon is running and a CRC cluster is running, for more information please check the logs") - } - } do { + let data = try SendCommandToDaemon(command: Request(command: "webconsoleurl", args: nil)) let webConsoleResult = try JSONDecoder().decode(WebConsoleResult.self, from: data) if webConsoleResult.Success { // form the login command, put in clipboard and show notification @@ -370,21 +293,14 @@ func HandleLoginCommandForKubeadmin() { displayNotification(title: "OC Login with kubeadmin", body: "OC Login command copied to clipboard, go ahead and login to your cluster") } } - } catch let jsonErr { - print(jsonErr.localizedDescription) + } catch let error { + showAlertFailedAndCheckLogs(message: "Failed to get login command", informativeMsg: "Ensure the CRC daemon is running and a CRC cluster is running, for more information please check the logs. Error: \(error)") } } func HandleLoginCommandForDeveloper() { - let r = SendCommandToDaemon(command: Request(command: "webconsoleurl", args: nil)) - guard let data = r else { return } - if String(bytes: data, encoding: .utf8) == "Failed" { - // Alert show error - DispatchQueue.main.async { - showAlertFailedAndCheckLogs(message: "Failed to get login command", informativeMsg: "Ensure the CRC daemon is running and a CRC cluster is running, for more information please check the logs") - } - } do { + let data = try SendCommandToDaemon(command: Request(command: "webconsoleurl", args: nil)) let webConsoleResult = try JSONDecoder().decode(WebConsoleResult.self, from: data) if webConsoleResult.Success { // form the login command, put in clipboard and show notification @@ -400,29 +316,22 @@ func HandleLoginCommandForDeveloper() { displayNotification(title: "OC Login with developer", body: "OC Login command copied to clipboard, go ahead and login to your cluster") } } - } catch let jsonErr { - print(jsonErr.localizedDescription) + } catch let error { + showAlertFailedAndCheckLogs(message: "Failed to get login command", informativeMsg: "Ensure the CRC daemon is running and a CRC cluster is running, for more information please check the logs. Error: \(error)") } } func FetchVersionInfoFromDaemon() -> (String, String) { - let r = SendCommandToDaemon(command: Request(command: "version", args: nil)) - guard let data = r else { return ("", "") } - if String(bytes: data, encoding: .utf8) == "Failed" { - // Alert show error - DispatchQueue.main.async { - showAlertFailedAndCheckLogs(message: "Failed to fetch version", informativeMsg: "Ensure the CRC daemon is running, for more information please check the logs") - } - return ("","") - } do { + let data = try SendCommandToDaemon(command: Request(command: "version", args: nil)) let versionResult = try JSONDecoder().decode(VersionResult.self, from: data) if versionResult.Success { let crcVersion = "\(versionResult.CrcVersion)+\(versionResult.CommitSha)" return (crcVersion, versionResult.OpenshiftVersion) } - } catch let jsonErr { - print(jsonErr) + } catch let error { + showAlertFailedAndCheckLogs(message: "Failed to fetch version", informativeMsg: "Ensure the CRC daemon is running, for more information please check the logs. Error: \(error)") + return ("","") } return ("","") } @@ -433,21 +342,12 @@ func GetConfigFromDaemon(properties: [String]) throws -> Dictionary (CrcConfigs?) { - let crcConfig: CrcConfigs? = nil - let r = SendCommandToDaemon(command: Request(command: "getconfig", args: nil)) - guard let data = r else { print("Unable to read response from daemon"); throw DaemonError.badResponse } - if String(bytes: data, encoding: .utf8) == "Failed" { - throw DaemonError.badResponse - } +func GetAllConfigFromDaemon() throws -> CrcConfigs { + let data = try SendCommandToDaemon(command: Request(command: "getconfig", args: nil)) let decoder = JSONDecoder() - do { - let configResult = try decoder.decode(GetconfigResult.self, from: data) - if configResult.Error == "" { - return configResult.Configs - } - } catch let jsonErr { - print(jsonErr) + let configResult = try decoder.decode(GetconfigResult.self, from: data) + if configResult.Error == "" { + return configResult.Configs } - return crcConfig + throw DaemonError.badResponse } diff --git a/CodeReady Containers/DetailedStatusViewController.swift b/CodeReady Containers/DetailedStatusViewController.swift index 4095c46..9a47566 100644 --- a/CodeReady Containers/DetailedStatusViewController.swift +++ b/CodeReady Containers/DetailedStatusViewController.swift @@ -36,22 +36,22 @@ class DetailedStatusViewController: NSViewController { func updateViewWithClusterStatus() { DispatchQueue.global(qos: .background).async { - let r = SendCommandToDaemon(command: Request(command: "status", args: nil)) - - DispatchQueue.main.async { - guard let data = r else { return } - do { - let status = try JSONDecoder().decode(ClusterStatus.self, from: data) - if status.Success { + do { + let data = try SendCommandToDaemon(command: Request(command: "status", args: nil)) + let status = try JSONDecoder().decode(ClusterStatus.self, from: data) + if status.Success { + DispatchQueue.main.async { self.vmStatus.stringValue = status.CrcStatus self.ocpStatus.stringValue = status.OpenshiftStatus self.diskUsage.stringValue = "\(Units(bytes: status.DiskUse).getReadableUnit()) of \(Units(bytes: status.DiskSize).getReadableUnit()) (Inside the VM)" self.cacheSize.stringValue = Units(bytes: folderSize(folderPath: self.cacheDirPath)).getReadableUnit() self.cacheDirectory.stringValue = self.cacheDirPath.path } - } catch let jsonErr { - print(jsonErr.localizedDescription) + } else { + showAlertFailedAndCheckLogs(message: "Failed to get status", informativeMsg: status.Error) } + } catch { + showAlertFailedAndCheckLogs(message: "Failed to get status", informativeMsg: error.localizedDescription) } } } diff --git a/CodeReady Containers/Helpers/Helpers.swift b/CodeReady Containers/Helpers/Helpers.swift index 7aebdbc..8596cdb 100644 --- a/CodeReady Containers/Helpers/Helpers.swift +++ b/CodeReady Containers/Helpers/Helpers.swift @@ -11,21 +11,23 @@ import Cocoa // Displays an alert and option to check the logs func showAlertFailedAndCheckLogs(message: String, informativeMsg: String) { - NSApplication.shared.activate(ignoringOtherApps: true) - - let alert: NSAlert = NSAlert() - alert.messageText = message - alert.informativeText = informativeMsg - alert.alertStyle = NSAlert.Style.warning - alert.addButton(withTitle: "Check Logs") - alert.addButton(withTitle: "Not now") - if alert.runModal() == .alertFirstButtonReturn { - // Open logs file - print("Check Logs button clicked") - let logFilePath: URL = userHomePath.appendingPathComponent(".crc").appendingPathComponent("crcd").appendingPathExtension("log") - NSWorkspace.shared.open(logFilePath) - } else { - print("Not now button clicked") + DispatchQueue.main.async { + NSApplication.shared.activate(ignoringOtherApps: true) + + let alert: NSAlert = NSAlert() + alert.messageText = message + alert.informativeText = informativeMsg + alert.alertStyle = NSAlert.Style.warning + alert.addButton(withTitle: "Check Logs") + alert.addButton(withTitle: "Not now") + if alert.runModal() == .alertFirstButtonReturn { + // Open logs file + print("Check Logs button clicked") + let logFilePath: URL = userHomePath.appendingPathComponent(".crc").appendingPathComponent("crcd").appendingPathExtension("log") + NSWorkspace.shared.open(logFilePath) + } else { + print("Not now button clicked") + } } } @@ -79,25 +81,18 @@ struct ClusterStatus: Decodable { // Get the status of the cluster from the daemon func clusterStatus() -> String { - let status = SendCommandToDaemon(command: Request(command: "status", args: nil)) - guard let data = status else { return "Unknown" } - print(String(bytes: data, encoding: .utf8)!) - if String(bytes: data, encoding: .utf8) == "Failed" { - print("In failed") - return "Unknown" - } do { + let data = try SendCommandToDaemon(command: Request(command: "status", args: nil)) let st = try JSONDecoder().decode(ClusterStatus.self, from: data) - if st.OpenshiftStatus.contains("Running") { - return "Running" - } - if st.OpenshiftStatus == "Stopped" { - return "Stopped" + if st.Error != "" { + print(st.Error) + return "Backend error" } - } catch let jsonERR { - print(jsonERR.localizedDescription) + return st.OpenshiftStatus + } catch let error { + print(error) + return "Broken daemon?" } - return "Unknown" } func folderSize(folderPath:URL) -> Int64 { diff --git a/CodeReady Containers/config/ConfigViewController.swift b/CodeReady Containers/config/ConfigViewController.swift index e4e4e02..3afad46 100644 --- a/CodeReady Containers/config/ConfigViewController.swift +++ b/CodeReady Containers/config/ConfigViewController.swift @@ -89,47 +89,43 @@ class ConfigViewController: NSViewController { } func LoadConfigs() { - var configs: CrcConfigs? = nil DispatchQueue.global(qos: .background).async { - do{ - if (try GetAllConfigFromDaemon()) != nil { - configs = try GetAllConfigFromDaemon()! - } + var configs: CrcConfigs + do { + configs = try GetAllConfigFromDaemon() } catch DaemonError.noResponse { - DispatchQueue.main.async { - showAlertFailedAndCheckLogs(message: "Did not receive any response from the daemon", informativeMsg: "Ensure the CRC daemon is running, for more information please check the logs") - } + showAlertFailedAndCheckLogs(message: "Did not receive any response from the daemon", informativeMsg: "Ensure the CRC daemon is running, for more information please check the logs") + return } catch { - DispatchQueue.main.async { - showAlertFailedAndCheckLogs(message: "Bad response", informativeMsg: "Undefined error") - } + showAlertFailedAndCheckLogs(message: "Bad response", informativeMsg: "Undefined error") + return } DispatchQueue.main.async { // Load config property values - self.cpusLabel?.intValue = Int32((configs?.cpus ?? 0)) - self.cpuSlider?.intValue = Int32((configs?.cpus ?? 0)) + self.cpusLabel?.intValue = Int32((configs.cpus ?? 0)) + self.cpuSlider?.intValue = Int32((configs.cpus ?? 0)) - self.httpProxy?.stringValue = configs?.httpProxy ?? "Unset" - self.httpsProxy?.stringValue = configs?.httpsProxy ?? "Unset" - self.proxyCaFile?.stringValue = configs?.proxyCaFile ?? "Unset" - self.noProxy?.stringValue = configs?.noProxy ?? "Unset" + self.httpProxy?.stringValue = configs.httpProxy ?? "Unset" + self.httpsProxy?.stringValue = configs.httpsProxy ?? "Unset" + self.proxyCaFile?.stringValue = configs.proxyCaFile ?? "Unset" + self.noProxy?.stringValue = configs.noProxy ?? "Unset" - self.memorySlider?.doubleValue = Float64(configs?.memory ?? 0) - self.memoryLabel?.doubleValue = Float64(configs?.memory ?? 0) - self.nameservers?.stringValue = configs?.nameserver ?? "Unset" - self.diskSizeTextField?.doubleValue = Float64(configs?.diskSize ?? 0) - self.diskSizeStepper?.intValue = Int32(configs?.diskSize ?? 0) - self.pullSecretFilePathTextField?.stringValue = configs?.pullSecretFile ?? "Unset" + self.memorySlider?.doubleValue = Float64(configs.memory ?? 0) + self.memoryLabel?.doubleValue = Float64(configs.memory ?? 0) + self.nameservers?.stringValue = configs.nameserver ?? "Unset" + self.diskSizeTextField?.doubleValue = Float64(configs.diskSize ?? 0) + self.diskSizeStepper?.intValue = Int32(configs.diskSize ?? 0) + self.pullSecretFilePathTextField?.stringValue = configs.pullSecretFile ?? "Unset" - if configs?.consentTelemetry == "" { + if configs.consentTelemetry == "" { self.enableTelemetrySwitch?.state = .off } else { - self.enableTelemetrySwitch?.state = (configs?.consentTelemetry?.lowercased()) == "yes" ? .on : .off + self.enableTelemetrySwitch?.state = (configs.consentTelemetry?.lowercased()) == "yes" ? .on : .off } - guard let autoStartValue = configs?.autostartTray else { return } + guard let autoStartValue = configs.autostartTray else { return } self.autostartAtLoginButton?.state = (autoStartValue) ? .on : .off self.view.display()