gRPC API Guide
In this guide you'll see how you can use the TSB gRPC API to perform common operations on the platform. The examples in this guide use the Go bindings, as those are the ones we provide on request by default, but gRPC clients generated for other languages will work as well.
Using the public SDKs
The protobuf files for the TSB APIs are published in the Tetrate Buf Schema Registry. There you will find the documentation for the protobuf files as well as language SDKs that are ready to use without having to download the protobuf files and generate the language bindings locally.
In order to integrate with our APIs:
- Go to the language SDK page of your choice.
- Follow the instructions to download the
protocolbuffers
module. - Follow the instructions to download the
grpc
module.
Once that is done, you should be all set to start using the SDKs.
Manually downloading the protobuf files
The recommended approach is to use the public SDKs, but if you need, instead, to build the language bindings on your own based on the protobuf files, you can download them using the Buf CLI as follows:
$ buf export buf.build/tetrate/api:<version> --output=<download dir>
This will download the Tetrate API protobuf files and all the dependencies needed to generate the code for your preferred language.
Initializing the gRPC client
Transport configuration
The first thing to configure when creating the gRPC client is the connection to TSB. Depending how TSB is exposed, you can configure the connection as a plain-text connection or as a TLS one.
Configuring a plain-text connection
To configure a plain text connection, use the following gRPC dial options:
opts := []grpc.DialOption{
grpc.WithBlock(), // Block when calling Dial until the connection is really established
grpc.WithInsecure(),
}
tsbAddress := "<tsb host>:<tsb port>"
cc, err := grpc.DialContext(ctx, tsbAddress, opts...)
The first option will instruct the gRPC client to block on the Dial call until the connection has been established, the second option configures the plain-text transport credentials.
Configuring a TLS connection
If TSB is exposed via TLS, you should enable TLS connections as follows:
tlsConfig := &tls.Config{} // Default TLS configuration to use the host CA
creds := credentials.NewTLS(tlsConfig)
opts := []grpc.DialOption{
grpc.WithBlock(), // Block when calling Dial until the connection is really established
grpc.WithTransportCredentials(creds),
}
tsbAddress := "<tsb host>:<tsb port>"
cc, err := grpc.DialContext(ctx, tsbAddress, opts...)
The default TLS configuration will use the host CA to verify the certificate presented by the TSB server. If the certificate cannot be validated by that CA because it's self-signed, or because its root CA is not a public one, configure the TLS connection with a custom CA as follows:
// Read the custom CA bundle from a file
ca, err := ioutil.ReadFile("custom-ca.crt")
if err != nil {
return fmt.Errorf("failed to load CA file: %w", err)
}
// Load the CA bundle into an x509 certificate pool
certs := x509.NewCertPool()
if ok := certs.AppendCertsFromPEM(ca); !ok {
return errors.New("error loading CA")
}
// Configure the TLS options to use the loaded root CA
tlsConfig := &tls.Config{
RootCAs: certs,
}
Finally, if you want to establish a TLS connection, but don't want to validate the server certificate, you can skip the validation by configuring the TLS options as follows:
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
This will instruct the gRPC client to establish a TLS connection without validating the certificate presented by TSB.
Authentication
Once the transport options have been configured, you must set authentication as
well. Authentication is configured as a credentials.PerRPCCredentials
object.
TSB has two main authentication mechanisms: basic authentication and JWT token authentication.
Basic Auth
In order to configure basic authentication, you can use a helper object that implements the gRPC interfaces you need. The following code shows an example of how to configure it:
// BasicAuth is an HTTP Basic credentials provider for gRPC
type BasicAuth struct {
Username string
Password string
}
// GetRequestMetadata implements credentials.PerRPCCredentials
func (b BasicAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
auth := b.Username + ":" + b.Password
enc := base64.StdEncoding.EncodeToString([]byte(auth))
return map[string]string{"authorization": "Basic " + enc}, nil
}
// RequireTransportSecurity implements credentials.PerRPCCredentials
func (BasicAuth) RequireTransportSecurity() bool {
return false
}
With this object in place, configure the gRPC client to use Basic Authentication as follows:
auth := BasicAuth{
Username: "username",
Password: "password",
}
opts := []grpc.DialOption{
grpc.WithBlock(), // Block when calling Dial until the connection is really established
grpc.WithPerRPCCredentials(auth),
}
tsbAddress := "<tsb host>:<tsb port>"
cc, err := grpc.DialContext(ctx, tsbAddress, opts...)
JWT Token Auth
For JWT Token-based authentication, use a supporting object that will facilitate configuring the gRPC options:
// TokenAuth is a JWT based credentials provider for gRPC
type TokenAuth string
// GetRequestMetadata implements credentials.PerRPCCredentials
func (t TokenAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
return map[string]string{"x-tetrate-token": string(t)}, nil
}
// RequireTransportSecurity implements credentials.PerRPCCredentials
func (TokenAuth) RequireTransportSecurity() bool {
return false
}
Then, configure the gRPC client with the following DialOptions
:
auth := TokenAuth("jwt-token")
opts := []grpc.DialOption{
grpc.WithBlock(), // Block when calling Dial until the connection is really established
grpc.WithPerRPCCredentials(auth),
}
tsbAddress := "<tsb host>:<tsb port>"
cc, err := grpc.DialContext(ctx, tsbAddress, opts...)
Example: creating organizations, tenants and workspaces
The following example demonstrates how you can instantiate the different TSB gRPC clients using the configured connection to access TSB's different APIs.
var (
tsbAddress = "<tsb host>:<tsb port>"
username = "username"
password = "password"
tlsConfig = &tls.Config{
// Add any custom TLS options
}
)
// ----------------------------------
// Connection configuration
// ----------------------------------
opts := []grpc.DialOption{
grpc.WithBlock(),
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
grpc.WithPerRPCCredentials(BasicAuth{
Username: username,
Password: password,
}),
}
cc, err := grpc.DialContext(context.Background(), tsbAddress, opts...)
if err != nil {
panic(err)
}
// ------------------------------------------------
// gRPC client creation and API usage
// ------------------------------------------------
var (
orgsClient = v2.NewOrganizationsClient(cc)
tenantsClient = v2.NewTenantsClient(cc)
workspacesClient = v2.NewWorkspacesClient(cc)
)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
org, err := orgsClient.CreateOrganization(ctx, &v2.CreateOrganizationRequest{
Name: "myorg",
Organization: &v2.Organization{
DisplayName: "My Organization",
Description: "Organization created using the TSB gRPC API",
},
})
if err != nil {
panic(err)
}
fmt.Printf("Created organization %q (%s)\n", org.DisplayName, org.Fqn)
tenant, err := tenantsClient.CreateTenant(ctx, &v2.CreateTenantRequest{
Parent: org.Fqn, // Create the tenant in the organization we just created
Name: "mytenant",
Tenant: &v2.Tenant{
DisplayName: "My Tenant",
Description: "Tenant created using the TSB gRPC API",
},
})
if err != nil {
panic(err)
}
fmt.Printf("Created tenant %q (%s)\n", tenant.DisplayName, tenant.Fqn)
ws, err := workspacesClient.CreateWorkspace(ctx, &v2.CreateWorkspaceRequest{
Parent: tenant.Fqn, // Create the workspace in the tenant we just created
Name: "myworkspace",
Workspace: &v2.Workspace{
DisplayName: "My Workspace",
Description: "Workspace created using the TSB gRPC API",
NamespaceSelector: &typesv2.NamespaceSelector{
Names: []string{"cluster1/*"},
},
},
})
if err != nil {
panic(err)
}
fmt.Printf("Created workspace %q (%s)\n", ws.DisplayName, ws.Fqn)
Using dry run mode.
Dry run mode is a feature that allows you to simulate the execution of a process without making any actual changes. The following example is based on the previous example. It will create a workspace in dry run mode, which will pass all the validations but will not actually create the workspace:
// Add metadata to the context to enable dry run mode
ctx = metadata.AppendToOutgoingContext(ctx, "x-tetrate-dry-run", "server-side")
ws, err := workspacesClient.CreateWorkspace(ctx, &v2.CreateWorkspaceRequest{
Parent: tenant.Fqn, // Create the workspace in the tenant we just created
Name: "myworkspace-dry-run",
Workspace: &v2.Workspace{
DisplayName: "My dry-run Workspace",
Description: "Workspace created using the TSB gRPC API",
NamespaceSelector: &typesv2.NamespaceSelector{
Names: []string{"cluster2/*"},
},
},
})
if err != nil {
panic(err)
}
// Even though the request succeeded, and a workspace was returned, the workspace was not created.
fmt.Printf("Created workspace %q (%s)\n", ws.DisplayName, ws.Fqn)
Appendix
Obtaining JWT tokens from TSB
The first time you connect to TSB, you may not have a token. If you want to use token-based authentication, you can exchange the basic authentication credentials for a session token to connect to TSB.
The following example shows a helper function that can be used to get an access token that you can configure in the JWT Token-based authentication options:
func GetToken(ctx context.Context, address string, username string, password string) (string, error) {
tlsConfig := &tls.Config{
// Add any custom TLS options
}
creds := credentials.NewTLS(tlsConfig)
opts := []grpc.DialOption{
grpc.WithBlock(),
grpc.WithTransportCredentials(creds),
// Note we are not configuring per RPC credentials here. This will create
// a connection to the TSB IAM API and credentials will be provided in
// in the request payload
}
cc, err := grpc.DialContext(ctx, address, opts...)
if err != nil {
return "", err
}
defer func() { _ = cc.Close() }()
iamClient := iam.NewAuthenticationClient(cc)
tokens, err := iamClient.Authenticate(ctx, &iam.Credentials{
Auth: &iam.Credentials_Basic{
Basic: &iam.Credentials_BasicAuth{
Username: username,
Password: password,
},
},
})
if err != nil {
return "", err
}
return tokens.BearerToken, nil
}
Using the Tetrate API objects in your protobuf files
If you want to import the Tetrate API protobuf files in your protobuf files, you will need to have the protobuf files available in your project. The recommended and most convenient way of doing it is by building the project using Buf.
Adding the Tetrate APIs to your buf
project, just requires adding the dependency to your buf.yaml
file:
# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml
version: v2
name: buf.build/tetrateio/test-protos
lint:
use:
- STANDARD
breaking:
use:
- FILE
deps:
- buf.build/tetrateio/api:1.12.5
Once added, run the following command to fetch all transitive dependencies:
$ buf dep update
To generate the code for your preferred language you will need to add the following configuration in your buf.gen.yaml
.
This will ensure that all packages for the generated code are properly generated. If you are using another language other
than Go you can refer to the Buf documentation to get the exact
language-specific option.
version: v2
managed:
enabled: true
override:
- file_option: go_package_prefix
module: buf.build/tetrate/api
value: buf.build/gen/go/tetrate/api/protocolbuffers/go
Once the dependency has been added to the project, you can start importing the Tetrate APIs in your project. The following example shows how to import and use the TSB Workspaces:
syntax = "proto3";
package example;
import "tsb/v2/workspace.proto";
message Example {
string name = 1;
tetrateio.api.tsb.v2.Workspace workspace = 2;
}
You can now run the buf generate
command to generate the language bindings for your protobuf files using the Tetrate APIs.