Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
90 changes: 17 additions & 73 deletions Sources/segmentcli/Commands/LivePlugins.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@ 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 {
Expand All @@ -42,7 +42,7 @@ class EdgeFnDisable: Command {
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
Expand Down Expand Up @@ -74,80 +74,24 @@ class EdgeFnUpload: Command {

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 {
Expand Down
55 changes: 5 additions & 50 deletions Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -27,47 +27,11 @@ extension PAPI {
task.resume()
}

func disable(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")

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 = "{ \"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)
Expand All @@ -77,19 +41,10 @@ extension PAPI {
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)
request.httpBody = "{ \"code\": \"\(code)\", \"sourceId\": \"\(sourceId)\" }".data(using: .utf8)

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()
}
}
}