diff --git a/README.md b/README.md index 637aafa..85661e5 100755 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ an Auth token in your Segment workspace. Reach out to your Customer Support Engineer (CSE) or Customer Success Manager (CSM) to have them add this feature to your account. Once that is completed, you may continue. -### Uploading Your Analytics Live Plugins to Your Workspace +### Uploading Your Analytics Live Plugins to Your Source In order to upload your Analytics Live Plugins you'll need the following command: diff --git a/Sources/segmentcli/Commands/LivePlugins.swift b/Sources/segmentcli/Commands/LivePlugins.swift index 0e56bcd..e6b8342 100755 --- a/Sources/segmentcli/Commands/LivePlugins.swift +++ b/Sources/segmentcli/Commands/LivePlugins.swift @@ -14,36 +14,36 @@ import Segment class EdgeFnGroup: CommandGroup { let name = "liveplugins" let shortDescription = "Work with and develop analytics live plugins" - let children: [Routable] = [EdgeFnLatestCommand(), EdgeFnUpload(), EdgeFnDisable()] + let children: [Routable] = [EdgeFnLatestCommand(), EdgeFnUpload(), EdgeFnDeleteCode()] init() {} } -class EdgeFnDisable: Command { - let name = "disable" - let shortDescription = "Disable Live Plugins for a given source ID" +class EdgeFnDeleteCode: Command { + let name = "delete" + let shortDescription = "Deletes Live Plugin code for a given source ID" @Param var sourceId: String func execute() throws { guard let workspace = currentWorkspace else { exitWithError(code: .commandFailed, message: "No authentication tokens found."); return } executeAndWait { semaphore in - let spinner = Spinner(.dots, "Uploading live plugin ...") + let spinner = Spinner(.dots, "Deleting live plugin code ...") spinner.start() - PAPI.shared.edgeFunctions.disable(token: workspace.token, sourceId: sourceId) { data, response, error in + PAPI.shared.edgeFunctions.deleteCode(token: workspace.token, sourceId: sourceId) { data, response, error in spinner.stop() if let error = error { exitWithError(error) } - + let statusCode = PAPI.shared.statusCode(response: response) - + switch statusCode { case .ok: // success! - print("Live plugins disabled for \(self.sourceId.italic.bold).") - + print("Live plugin code deleted for \(self.sourceId.italic.bold).") + case .unauthorized: fallthrough case .unauthorized2: @@ -70,84 +70,26 @@ class EdgeFnUpload: Command { func execute() throws { guard let workspace = currentWorkspace else { exitWithError(code: .commandFailed, message: "No authentication tokens found."); return } - var uploadURL: URL? = nil - let fileURL = URL(fileURLWithPath: filePath.expandingTildeInPath) - // generate upload URL - executeAndWait { semaphore in - let spinner = Spinner(.dots, "Generating upload URL ...") - spinner.start() - - PAPI.shared.edgeFunctions.generateUploadURL(token: workspace.token, sourceId: sourceId) { data, response, error in - spinner.stop() - - if let error = error { - exitWithError(error) - } - - let statusCode = PAPI.shared.statusCode(response: response) - - switch statusCode { - case .ok: - // success! - if let jsonData = data, let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] { - if let uploadString = json[keyPath: "data.uploadURL"] as? String { - uploadURL = URL(string: uploadString) - } - } - - case .unauthorized: - fallthrough - case .unauthorized2: - exitWithError(code: .commandFailed, message: "Supplied token is not authorized.") - case .notFound: - exitWithError(code: .commandFailed, message: "No live plugins were found.") - default: - exitWithError("An unknown error occurred.") - } - semaphore.signal() - } + // Read file contents + guard let fileContents = try? Data(contentsOf: fileURL) else { + exitWithError(code: .commandFailed, message: "Unable to read file contents from \(filePath)") + return } - // upload it to the URL we were given. - executeAndWait { semaphore in - let spinner = Spinner(.dots, "Uploading \(fileURL.lastPathComponent) ...") - spinner.start() - - PAPI.shared.edgeFunctions.uploadToGeneratedURL(token: workspace.token, url: uploadURL, fileURL: fileURL) { data, response, error in - spinner.stop() - - if let error = error { - exitWithError(error) - } - - let statusCode = PAPI.shared.statusCode(response: response) - - switch statusCode { - case .ok: - // success! - break - - case .unauthorized: - fallthrough - case .unauthorized2: - exitWithError(code: .commandFailed, message: "Supplied token is not authorized.") - case .notFound: - exitWithError(code: .commandFailed, message: "No live plugins were found.") - default: - exitWithError("An unknown error occurred.") - } - semaphore.signal() - } + // Convert Data to String + guard let code = String(data: fileContents, encoding: .utf8) else { + exitWithError(code: .commandFailed, message: "Unable to convert file contents to string. File may not be valid UTF-8.") + return } // call create to make a new connection to the version we just posted. executeAndWait { semaphore in let spinner = Spinner(.dots, "Creating new live plugin version ...") spinner.start() - - PAPI.shared.edgeFunctions.createNewVersion(token: workspace.token, sourceId: sourceId, uploadURL: uploadURL) { data, response, error in + + PAPI.shared.edgeFunctions.createNewVersion(token: workspace.token, sourceId: sourceId, code: code) { data, response, error in spinner.stop() if let error = error { diff --git a/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift b/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift index 6fe58b2..d833479 100755 --- a/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift +++ b/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift @@ -9,7 +9,7 @@ import Foundation extension PAPI { class EdgeFunctions: PAPISection { - static let pathEntry = "edge-functions" + static let pathEntry = "live-plugins" func latest(token: String, sourceId: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { guard var url = URL(string: PAPIEndpoint) else { completion(nil, nil, "Unable to create URL."); return } @@ -27,69 +27,55 @@ extension PAPI { task.resume() } - func disable(token: String, sourceId: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { + func deleteCode(token: String, sourceId: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { guard var url = URL(string: PAPIEndpoint) else { completion(nil, nil, "Unable to create URL."); return } - - url.appendPathComponent(PAPI.Sources.pathEntry) - url.appendPathComponent(sourceId) - url.appendPathComponent(PAPI.EdgeFunctions.pathEntry) - url.appendPathComponent("disable") - - var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) - request.httpMethod = "PATCH" - request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = "{ \"sourceId\": \"\(sourceId)\" }".data(using: .utf8) - let task = URLSession.shared.dataTask(with: request, completionHandler: completion) - task.resume() - } - - func generateUploadURL(token: String, sourceId: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { - guard var url = URL(string: PAPIEndpoint) else { completion(nil, nil, "Unable to create URL."); return } - url.appendPathComponent(PAPI.Sources.pathEntry) url.appendPathComponent(sourceId) url.appendPathComponent(PAPI.EdgeFunctions.pathEntry) - url.appendPathComponent("upload-url") - + url.appendPathComponent("delete-code") + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) - request.httpMethod = "POST" + request.httpMethod = "DELETE" request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = "{ \"sourceId\": \"\(sourceId)\" }".data(using: .utf8) - + let task = URLSession.shared.dataTask(with: request, completionHandler: completion) task.resume() } - + // http://blah.com/whatever/create?sourceId=1 - - func createNewVersion(token: String, sourceId: String, uploadURL: URL?, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { + + func createNewVersion(token: String, sourceId: String, code: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { guard var url = URL(string: PAPIEndpoint) else { completion(nil, nil, "Unable to create URL."); return } - guard let uploadURL = uploadURL else { completion(nil, nil, "Upload URL is invalid."); return } + guard !code.isEmpty else { completion(nil, nil, "Code cannot be empty."); return } url.appendPathComponent(PAPI.Sources.pathEntry) url.appendPathComponent(sourceId) url.appendPathComponent(PAPI.EdgeFunctions.pathEntry) - + url.appendPathComponent("create") + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) request.httpMethod = "POST" request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = "{ \"uploadURL\": \"\(uploadURL.absoluteString)\", \"sourceId\": \"\(sourceId)\" }".data(using: .utf8) + + let payload: [String: Any] = [ + "code": code, + "sourceId": sourceId + ] + + do { + request.httpBody = try JSONSerialization.data(withJSONObject: payload, options: []) + if let bodyString = String(data: request.httpBody!, encoding: .utf8) { + print("Request payload: \(bodyString)") + } + } catch { + completion(nil, nil, "Failed to create JSON payload: \(error)") + return + } let task = URLSession.shared.dataTask(with: request, completionHandler: completion) task.resume() } - - func uploadToGeneratedURL(token: String, url: URL?, fileURL: URL?, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { - guard let url = url else { completion(nil, nil, "URL is nil."); return } - guard let fileURL = fileURL else { completion(nil, nil, "File URL is nil."); return } - var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) - request.httpMethod = "PUT" - let task = URLSession.shared.uploadTask(with: request, fromFile: fileURL, completionHandler: completion) - task.resume() - } } }