Go client library for the Suborbital Extension Engine (SE2)
In a Go project, run this to fetch the module:
go get github.com/suborbital/se2-go@latest
Every operation with SE2 is done with a se2.Client
.
package main
import (
"context"
"log"
"time"
"github.com/suborbital/se2-go"
)
func main() {
token, exists := os.LookupEnv("SE2_ENV_TOKEN")
if !exists {
log.Fatal("could not find token")
}
client, err := se2.NewClient(se2.ModeProduction, token)
if err != nil {
log.Fatal(err)
}
ctx, cxl := context.WithTimeout(context.Background(), 10*time.Second)
defer cxl()
// get a list of plugins
plugins, err := client.GetPlugins(ctx, "tenantName")
if err != nil {
log.Fatal(err)
}
for _, r := range plugins {
log.Println(r.FQMN)
}
}
Some of the methods require an API key that you get by creating a new access key in the admin area of https://suborbital.network. That API key is restricted to a specific environment. Every request is going to be against the environment that the key belongs to.
Others are session, or plugin specific. For those you need to first create a session, and then reuse the token with all the endpoints.
Each of these methods can be seen in the "everything" annotated example.
func example() {
tenant, err := client.GetTenantByName(ctx, "tenantName")
if err != nil {
// handle error
}
}
GetTenantByName
will fetch the tenant by the given name. It requires a configured client with an API key that is valid. If the request was not successful, or the response was anything other than a 200 OK, it will return an error.
The returned tenant's structure:
type TenantResponse struct {
AuthorizedParty string `json:"authorized_party"`
ID string `json:"id"`
Environment string `json:"environment"`
Name string `json:"name"`
Description string `json:"description"`
}
func example() {
tenant, err := client.CreateTenant(ctx, "tenantName", "tenant description")
if err != nil {
// handle error
}
}
CreateTenant
will create tenant with a given name and description. A successful response looks like TenantResponse
. Requires a configured client and a correct API key. If the tenant wasn't created, this method returns a non nil error.
func example() {
tenants, err := client.ListTenants(ctx)
if err != nil {
// handle error
}
}
ListTenants
will list all the tenants within the environment that the access key belongs to. The response looks like this:
type ListTenantResponse struct {
Tenants []TenantResponse
}
UpdateTenantByName
will update a tenant by the name you specified with a new description. No other parts of a tenant can be updated. It will return a non nil error if the update failed. This usually happens because the tenant was not found, or there was an authentication issue.
This method requires a configured client with a valid API key.
func example() {
tenant, err := client.UpdateTenantByName(ctx, "tenantName", "new description")
}
DeleteTenantByName
will delete the tenant by the name if it finds it. Requires a configured client with a valid API key. If there was an issue deleting the tenant, it will return a non nil error. This is usually because a tenant by that name does not exist in the environment the API key specifies, or there was another authentication issue.
func example() {
err := client.DeleteTenantByName(ctx, "tenantName")
if err != nil {
// handle error
}
}
ListTemplates
will list all the available templates for the environment specified by the API key. Requires a configured client and a valid API key.
func example() {
templates, err := client.ListTemplates(ctx)
if err != nil {
// handle error
}
}
Returned payload looks like this:
type ListTemplatesResponse struct {
Templates []Template `json:"templates"`
}
type Template struct {
Name string `json:"name"`
Lang string `json:"lang"`
Main string `json:"main,omitempty"`
Version string `json:"api_version"`
}
GetTemplate
will return a single template specified by its name in the environment the API key is for. It requires a configured client and a valid API key.
func example() {
template, err := client.GetTemplate(ctx, "templateName")
if err != nil {
// handle error
}
}
ImportTemplatesFromGitHub
will import templates found in a GitHub repository. It takes a repository name in the form of organization/repository
, a reference, which can be a branch, tag, or commit sha, and a path, which is where the templates are within the repository.
To help with figuring out what either of those should be, the arguments will be substituted into this download link:
https://github.com/{repository}/archive/{ref}.tar.gz
Any two values that would result in a 200 OK and a download response from GitHub will be valid arguments to the method.
This requires a configured client, a valid API key, and that the repository is public and not archived.
func example() {
err := client.ImportTemplatesFromGitHub(ctx, "suborbital/sdk", "main", "templates")
if err != nil {
// handle error
}
}
GetPlugins
will list the plugins for a given tenant, which is scoped to the environment the API key specifies. Requires a configured client and a valid API key.
func example() {
plugins, err := client.GetPlugins(ctx, "tenantName")
if err != nil {
// handle error
}
}
The response shape is a PluginResponse
, which looks like this:
type Plugin struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Lang string `json:"lang"`
Ref string `json:"ref"`
APIVersion string `json:"apiVersion"`
FQMN string `json:"fqmn"`
URI string `json:"uri"`
}
type PluginResponse struct {
Plugins []Plugin `json:"plugins"`
}
There's only a single endpoint here. Use this to create a session to use with the builder methods.
CreateSession
creates a session for a given tenant, namespace, and plugin name. Once you have the token, every request to the builder with the same session will relate to the same tenant-namespace-plugin.
This requires a configured client with a valid API key.
func example() {
session, err := client.CreateSession(ctx, "tenantName", "namespace", "pluginName")
if err != nil {
// handle error
}
}
GetBuilderFeatures
returns a list of things that the builder can do for you. This endpoint requires only a configured client and a valid API key. It does NOT need a session token.
func example() {
features, err := client.GetBuilderFeatures(ctx)
if err != nil {
// handle error
}
}
The shape of features
is this:
type BuilderFeaturesResponse struct {
Features []string `json:"features"`
Languages []Languages
}
type Languages struct {
ID string `json:"identifier"`
ShortName string `json:"short"`
PrettyName string `json:"pretty"`
}
CreatePluginDraft
creates a plugin draft based on a template. You can query the list of available templates with the ListTemplates
call, or add new ones using the ImportTemplatesFromGitHub
method.
This endpoint requires a session token to work. Pass in the variable as is you get from the CreateSession
method.
func example() {
session, err := client.CreateSession(ctx, "tenantName", "namespace", "pluginName")
if err != nil {
// handle error
}
draft, err := client.CreatePluginDraft(ctx, "javascript", session)
if err != nil {
// handle error
}
}
The returned draft
variable will be of this structure, where Lang
is the programming language, and Contents
is the actual code of the starter state of the plugin.
type DraftResponse struct {
Lang string `json:"lang"`
Contents string `json:"contents"`
}
GetPluginDraft
returns the current state of the draft plugin specified by the session token. The response is the same as you get from CreatePluginDraft
. It requires a session token.
func example() {
draft, err := client.GetPluginDraft(ctx, session)
if err != nil {
// handle error
}
}
BuildPlugin
takes in a new plugin code body as a byte slice and a session token, and builds the plugin. It returns a non nil error if something went wrong. This is most likely going to be a syntax error in the supplied code, or a form of authentication error. It requires a non empty plugin code body, and a session token.
func example() {
output, err := client.BuildPlugin(ctx, pluginBody, session)
if err != nil {
// handle error
}
}
The returned output
has the following structure:
type BuildPluginResponse struct {
Succeeded bool `json:"succeeded"`
OutputLog string `json:"outputLog"`
}
The OutputLog
is what the compiler printed to the terminal on the server. You can use it to debug what happened if the build did not succeed.
If succeeded
is true, a new call to GetPluginDraft
with the same session token will be the same code that you passed into the BuildPlugin
method.
TestPluginDraft
takes in a byte slice to use as input to the current state of the plugin in the session, and will return a test response which has a string result for output, and an error if something went wrong while executing the plugin with the input.
func example() {
result, err := client.TestPluginDraft(ctx, []byte(`hello`), session)
if err != nil {
// handle error
}
}
The structure of the returned result
is this:
type TestPluginDraftResponse struct {
Result string `json:"result"`
Error runError `json:"error"`
}
type runError struct {
Code int `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
PromotePluginDraft
will push the current draft version to the live edge servers. It will take a short time while code propagates, but after this executing the plugin on the edge will use the new code. This requires a session token.
func example() {
response, err := client.PromotePluginDraft(ctx, session)
if err != nil {
// handle error
}
}
If all went well, the structure of the returned response
looks like this:
type PromotePluginDraftResponse struct {
Ref string `json:"ref"`
}
Contains a single method to execute published, or promoted, plugins.
Exec
takes a byte slice payload that will be used as input for the plugin, and a trio of ident, namespace, and plugin name to specify which plugin to execute. The trio of inputs is the same as you used in the CreateSession
call. If execution failed, err
is going to be non nil.
This endpoint requires a configured client, and a valid API key, not a session token!
func example() {
responseBytes, err := client.Exec(ctx, []byte(`hello`), "tenantName", "namespace", "pluginName")
if err != nil {
// handle error
}
}
responseBytes
is a []byte
type. This is a bytes in, bytes out operation.