053c2b3cf6
This addresses an issue found in #8696 which was determined to be due to the Go module proxy having a cached copy of a tag that doesn't match the official version (due a build prep error weeks ago). All of the repos got new patch versions, but the content is identical. |
||
---|---|---|
.. | ||
authentication | ||
client | ||
errors | ||
storage | ||
.gitignore | ||
.travis.yml | ||
CHANGELOG.md | ||
go.mod | ||
go.sum | ||
Gopkg.lock | ||
Gopkg.toml | ||
LICENSE | ||
Makefile | ||
README.md | ||
triton.go | ||
version.go |
triton-go
triton-go
is a client SDK for Go applications using Joyent's Triton Compute
and Object Storage (Manta) APIs.
The Triton Go SDK is used in the following open source projects.
- Consul
- Packer
- Vault
- Terraform
- Terraform Triton Provider
- Docker Machine
- Triton Kubernetes
- HashiCorp go-discover
Usage
Triton uses HTTP Signature to sign the Date header in each HTTP request
made to the Triton API. Currently, requests can be signed using either a private
key file loaded from disk (using an authentication.PrivateKeySigner
), or
using a key stored with the local SSH Agent (using an SSHAgentSigner
.
To construct a Signer, use the New*
range of methods in the authentication
package. In the case of authentication.NewSSHAgentSigner
, the parameters are
the fingerprint of the key with which to sign, and the account name (normally
stored in the TRITON_ACCOUNT
environment variable). There is also support for
passing in a username, this will allow you to use an account other than the main
Triton account. For example:
input := authentication.SSHAgentSignerInput{
KeyID: "a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11",
AccountName: "AccountName",
Username: "Username",
}
sshKeySigner, err := authentication.NewSSHAgentSigner(input)
if err != nil {
log.Fatalf("NewSSHAgentSigner: %s", err)
}
An appropriate key fingerprint can be generated using ssh-keygen
.
ssh-keygen -Emd5 -lf ~/.ssh/id_rsa.pub | cut -d " " -f 2 | sed 's/MD5://'
Each top level package, account
, compute
, identity
, network
, all have
their own separate client. In order to initialize a package client, simply pass
the global triton.ClientConfig
struct into the client's constructor function.
config := &triton.ClientConfig{
TritonURL: os.Getenv("TRITON_URL"),
MantaURL: os.Getenv("MANTA_URL"),
AccountName: accountName,
Username: os.Getenv("TRITON_USER"),
Signers: []authentication.Signer{sshKeySigner},
}
c, err := compute.NewClient(config)
if err != nil {
log.Fatalf("compute.NewClient: %s", err)
}
Constructing compute.Client
returns an interface which exposes compute
API
resources. The same goes for all other packages. Reference their unique
documentation for more information.
The same triton.ClientConfig
will initialize the Manta storage
client as
well...
c, err := storage.NewClient(config)
if err != nil {
log.Fatalf("storage.NewClient: %s", err)
}
Error Handling
If an error is returned by the HTTP API, the error
returned from the function
will contain an instance of errors.APIError
in the chain. Error wrapping
is performed using the pkg/errors library.
Acceptance Tests
NOTE: The tests are not currently well-structured, and depend on many hard-coded specifics of the Joyent Public Cloud (JPC) which is being shut down in November 2019. It is likely not possible to run the test suite against a local Triton installation at this time, though that is definitely the intended test suite target environment for future development.
Acceptance Tests run directly against the Triton API, so you will need either a local installation of Triton or an account with Joyent's Public Cloud offering in order to run them. The tests create real resources (and thus cost real money)!
In order to run acceptance tests, the following environment variables must be set:
TRITON_TEST
- must be set to any value in order to indicate desire to create resourcesTRITON_URL
- the base endpoint for the Triton APITRITON_ACCOUNT
- the account name for the Triton APITRITON_KEY_ID
- the fingerprint of the SSH key identifying the key
Additionally, you may set TRITON_KEY_MATERIAL
to the contents of an unencrypted
private key. If this is set, the PrivateKeySigner (see above) will be used - if
not the SSHAgentSigner will be used. You can also set TRITON_USER
to run the tests
against an account other than the main Triton account.
Example Run
The verbose output has been removed for brevity here.
$ HTTP_PROXY=http://localhost:8888 \
TRITON_TEST=1 \
TRITON_URL=https://us-sw-1.api.joyent.com \
TRITON_ACCOUNT=AccountName \
TRITON_KEY_ID=a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11 \
go test -v -run "TestAccKey"
=== RUN TestAccKey_Create
--- PASS: TestAccKey_Create (12.46s)
=== RUN TestAccKey_Get
--- PASS: TestAccKey_Get (4.30s)
=== RUN TestAccKey_Delete
--- PASS: TestAccKey_Delete (15.08s)
PASS
ok github.com/joyent/triton-go 31.861s
Example API
There's an examples/
directory available with sample code setup for many of
the APIs within this library. Most of these can be run using go run
and
referencing your SSH key file use by your active triton
CLI profile.
$ eval "$(triton env us-sw-1)"
$ TRITON_KEY_FILE=~/.ssh/triton-id_rsa go run examples/compute/instances.go
The following is a complete example of how to initialize the compute
package
client and list all instances under an account. More detailed usage of this
library follows.
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"time"
triton "github.com/joyent/triton-go"
"github.com/joyent/triton-go/authentication"
"github.com/joyent/triton-go/compute"
)
func main() {
keyID := os.Getenv("TRITON_KEY_ID")
accountName := os.Getenv("TRITON_ACCOUNT")
keyMaterial := os.Getenv("TRITON_KEY_MATERIAL")
userName := os.Getenv("TRITON_USER")
var signer authentication.Signer
var err error
if keyMaterial == "" {
input := authentication.SSHAgentSignerInput{
KeyID: keyID,
AccountName: accountName,
Username: userName,
}
signer, err = authentication.NewSSHAgentSigner(input)
if err != nil {
log.Fatalf("Error Creating SSH Agent Signer: {{err}}", err)
}
} else {
var keyBytes []byte
if _, err = os.Stat(keyMaterial); err == nil {
keyBytes, err = ioutil.ReadFile(keyMaterial)
if err != nil {
log.Fatalf("Error reading key material from %s: %s",
keyMaterial, err)
}
block, _ := pem.Decode(keyBytes)
if block == nil {
log.Fatalf(
"Failed to read key material '%s': no key found", keyMaterial)
}
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
log.Fatalf(
"Failed to read key '%s': password protected keys are\n"+
"not currently supported. Please decrypt the key prior to use.", keyMaterial)
}
} else {
keyBytes = []byte(keyMaterial)
}
input := authentication.PrivateKeySignerInput{
KeyID: keyID,
PrivateKeyMaterial: keyBytes,
AccountName: accountName,
Username: userName,
}
signer, err = authentication.NewPrivateKeySigner(input)
if err != nil {
log.Fatalf("Error Creating SSH Private Key Signer: {{err}}", err)
}
}
config := &triton.ClientConfig{
TritonURL: os.Getenv("TRITON_URL"),
AccountName: accountName,
Username: userName,
Signers: []authentication.Signer{signer},
}
c, err := compute.NewClient(config)
if err != nil {
log.Fatalf("compute.NewClient: %s", err)
}
listInput := &compute.ListInstancesInput{}
instances, err := c.Instances().List(context.Background(), listInput)
if err != nil {
log.Fatalf("compute.Instances.List: %v", err)
}
numInstances := 0
for _, instance := range instances {
numInstances++
fmt.Println(fmt.Sprintf("-- Instance: %v", instance.Name))
}
}