-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(authentication): support for exec kube config #29
Conversation
3443611
to
98e9aae
Compare
I think you mean issue #5. Nice patch, I've also worked around this. I will pull up my implementation and share some feedback. I think the Process api of foundation has many problems and you really need to be careful not to run into potential dead locks or hangs. |
oh yes, thanks 🤦
Happy to get feedback on that. |
So, if I remember correctly, I also tried many different ways implementing But it is quite fragile. // this is an async method, the task will run in the background
try task.run()
// this will return the data that is currently available in the read buffer
// of the `FileHandle`. It might be all the data you need, but it might also be less.
return pipe.fileHandleForReading.availableData It is better to use // this is an async method, the task will run in the background
try task.run()
// Block until the process exits
task.waitUntilExit()
// this will return the data that is currently available in the read buffer
// of the `FileHandle`. It might be all the data you need, but it might also be less.
return pipe.fileHandleForReading.availableData BUT: Some processes produce lots of output and only exit once somebody actually reads the output. If the output does not fit into the internal buffers, So, the safest way to make sure that the process can run and you collect all the output, is to read the output from a separate thread. This is the solution I landed on and which has been working reliably in production (for a different use-case than here). With all other approaches I had occasional hangs of the program which required restarting the program altogether. import Foundation
enum Process {
enum ShellError: Error {
case failure(terminationStatus: Int, errorMessage: String?, arguments: [String])
case missingExecutable(name: String)
}
private static func getEnvSearchPaths(
pathString: String?,
currentWorkingDirectory: URL?
) -> [URL] {
// Compute search paths from PATH variable.
let pathSeparator: Character = ":"
return (pathString ?? "").split(separator: pathSeparator).map(String.init).compactMap({ pathString in
if let cwd = currentWorkingDirectory {
return URL(fileURLWithPath: pathString, relativeTo: cwd)
}
return URL(fileURLWithPath: pathString)
})
}
private static func findExecutable(_ name: String) -> URL? {
if FileManager.default.isExecutableFile(atPath: name) {
return URL(fileURLWithPath: name)
}
let paths = getEnvSearchPaths(pathString: ProcessInfo.processInfo.environment["PATH"], currentWorkingDirectory: nil)
return paths.lazy
.map { URL(fileURLWithPath: name, relativeTo: $0) }
.first {
FileManager.default.isExecutableFile(atPath: $0.path)
}
}
public static func execute(_ arguments: [String], env: [String: String] = ProcessInfo.processInfo.environment) throws -> Data? {
guard arguments.count > 0 else {
fatalError("arguments must not be empty")
}
guard let executable = findExecutable(arguments[0]) else {
throw ShellError.missingExecutable(name: arguments[0])
}
var outputData : Data?
var errorData : Data?
let group = DispatchGroup()
let task = Foundation.Process()
task.executableURL = executable
task.arguments = Array(arguments.dropFirst())
let outputPipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = outputPipe
task.standardError = errorPipe
try task.run()
group.enter()
Thread { // read full output in a separate thread
outputData = try? outputPipe.fileHandleForReading.readToEnd()
group.leave()
}.start()
group.enter()
Thread { // read full error output in a separate thread
errorData = try? errorPipe.fileHandleForReading.readToEnd()
group.leave()
}.start()
task.waitUntilExit()
// wait until the reader threads complete
if .timedOut == group.wait(timeout: .now() + .seconds(10)) {
fatalError("Task exited, but timed out waiting for output.")
}
guard task.terminationStatus == 0 else {
let message = errorData.flatMap { String(data: $0, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) } ?? ""
throw ShellError.failure(terminationStatus: Int(task.terminationStatus), errorMessage: message, arguments: arguments)
}
return outputData
}
} |
Don't worry about it, and you don't have to apologise. We all have the same problem with free time 😉 Speaking of free time, I'll try to take a look at this PR in the next couple of days 😅 And big thanks to you and to @t089 for all the work you've done! |
Have you tried to play around with Instead of starting two threads this should create a dispatch source, that listens to the incoming data at the file handle, i.e.: let task = Process()
task.executableURL = URL(fileURLWithPath: command)
task.arguments = arguments
let pipe = Pipe()
let fileHandle = FileHandle(fileDescriptor: STDOUT_FILENO)
fileHandle.readabilityHandler = { handle in
let data = handle.availableData
if data.count > 0 {
pipe.fileHandleForWriting.write(data)
}
}
task.standardOutput = pipe
try task.run()
task.waitUntilExit() |
Yes, I have been playing around with this, but found that this does not work as expected on linux. It can work reliably on macOS but I had all kinds of unexpected behaviour on linux that's why I changed to the "brute force" method of spawning two threads and read until EOF. This has been working reliably. Relatedly, Swift Tool Support Core funnily completely skips |
98e9aae
to
a442a51
Compare
Hi @iabudiab Sorry for the delay again 😞 No i hadn't. If this is to apply @t089 solution, close this PR and open on your behalf with that approach, fine by me, let's just go ahead with this, may be another person in the world needing this. Cheers 🍻 |
Hello,
I saw this issue #5 and i remembered that i had locally a branch with this, so i decide to clean it up a little bit and give it some makeup 💄 and open a PR with it.
I have been using this privately, mostly because i'm not totally proud of it, as long as it works and there isn't much more to do, but please shout your ideas, suggestions, ...
Ideally there are a few validations that need to be done, as which version of the client.authentication.k8s.io resource should be used to decode.
Although the only resources versions that are available for now, are equal, which are client.authentication.k8s.io/v1 and client.authentication.k8s.io/v1beta1, so it works for now.
Not sure what is your preference, but probably this ydataai@c2ded62#diff-ff7d732f3cc6e1355f72ed08c4c685417df831d6e7cfedb605418b494a87711bR361 should be moved into the models package.
I only use this for development, to connect remotely to AWS clusters when i need, in-cluster they use the environment inject into the Pod, so this is not necessary.
I'm more then welcome to change whatever is necessary 😊
Thanks 🍻
PS: @iabudiab and all the involved in it great work on the CRD support and so on 👏
I know i was suppose to help and discuss and suddenly disappeared (sorry 😞), but unfortunately and working in a startup i didn't had time to dedicate to this, i haven't using that much Swift on the core, unfortunately 😢 , neither use your recent work, but i pretend to use this CRD support some day soon. 🙏