README.md
1# triton-go
2
3`triton-go` is an idiomatic library exposing a client SDK for Go applications
4using Joyent's Triton Compute and Storage (Manta) APIs.
5
6[![Build Status](https://travis-ci.org/joyent/triton-go.svg?branch=master)](https://travis-ci.org/joyent/triton-go) [![Go Report Card](https://goreportcard.com/badge/github.com/joyent/triton-go)](https://goreportcard.com/report/github.com/joyent/triton-go)
7
8The Triton Go SDK is used in the following open source projects.
9
10- [Consul](https://www.consul.io/docs/agent/cloud-auto-join.html#joyent-triton)
11- [Packer](http://github.com/hashicorp/packer)
12- [Vault](http://github.com/hashicorp/vault)
13- [Terraform](http://github.com/hashicorp/terraform)
14- [Terraform Triton Provider](https://github.com/terraform-providers/terraform-provider-triton)
15- [Docker Machine](https://github.com/joyent/docker-machine-driver-triton)
16- [Triton Kubernetes](https://github.com/joyent/triton-kubernetes)
17- [HashiCorp go-discover](https://github.com/hashicorp/go-discover)
18
19## Usage
20
21Triton uses [HTTP Signature][4] to sign the Date header in each HTTP request
22made to the Triton API. Currently, requests can be signed using either a private
23key file loaded from disk (using an [`authentication.PrivateKeySigner`][5]), or
24using a key stored with the local SSH Agent (using an [`SSHAgentSigner`][6].
25
26To construct a Signer, use the `New*` range of methods in the `authentication`
27package. In the case of `authentication.NewSSHAgentSigner`, the parameters are
28the fingerprint of the key with which to sign, and the account name (normally
29stored in the `TRITON_ACCOUNT` environment variable). There is also support for
30passing in a username, this will allow you to use an account other than the main
31Triton account. For example:
32
33```go
34input := authentication.SSHAgentSignerInput{
35 KeyID: "a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11",
36 AccountName: "AccountName",
37 Username: "Username",
38}
39sshKeySigner, err := authentication.NewSSHAgentSigner(input)
40if err != nil {
41 log.Fatalf("NewSSHAgentSigner: %s", err)
42}
43```
44
45An appropriate key fingerprint can be generated using `ssh-keygen`.
46
47```
48ssh-keygen -Emd5 -lf ~/.ssh/id_rsa.pub | cut -d " " -f 2 | sed 's/MD5://'
49```
50
51Each top level package, `account`, `compute`, `identity`, `network`, all have
52their own separate client. In order to initialize a package client, simply pass
53the global `triton.ClientConfig` struct into the client's constructor function.
54
55```go
56config := &triton.ClientConfig{
57 TritonURL: os.Getenv("TRITON_URL"),
58 MantaURL: os.Getenv("MANTA_URL"),
59 AccountName: accountName,
60 Username: os.Getenv("TRITON_USER"),
61 Signers: []authentication.Signer{sshKeySigner},
62}
63
64c, err := compute.NewClient(config)
65if err != nil {
66 log.Fatalf("compute.NewClient: %s", err)
67}
68```
69
70Constructing `compute.Client` returns an interface which exposes `compute` API
71resources. The same goes for all other packages. Reference their unique
72documentation for more information.
73
74The same `triton.ClientConfig` will initialize the Manta `storage` client as
75well...
76
77```go
78c, err := storage.NewClient(config)
79if err != nil {
80 log.Fatalf("storage.NewClient: %s", err)
81}
82```
83
84## Error Handling
85
86If an error is returned by the HTTP API, the `error` returned from the function
87will contain an instance of `errors.APIError` in the chain. Error wrapping
88is performed using the [pkg/errors][7] library.
89
90## Acceptance Tests
91
92Acceptance Tests run directly against the Triton API, so you will need either a
93local installation of Triton or an account with Joyent's Public Cloud offering
94in order to run them. The tests create real resources (and thus cost real
95money)!
96
97In order to run acceptance tests, the following environment variables must be
98set:
99
100- `TRITON_TEST` - must be set to any value in order to indicate desire to create
101 resources
102- `TRITON_URL` - the base endpoint for the Triton API
103- `TRITON_ACCOUNT` - the account name for the Triton API
104- `TRITON_KEY_ID` - the fingerprint of the SSH key identifying the key
105
106Additionally, you may set `TRITON_KEY_MATERIAL` to the contents of an unencrypted
107private key. If this is set, the PrivateKeySigner (see above) will be used - if
108not the SSHAgentSigner will be used. You can also set `TRITON_USER` to run the tests
109against an account other than the main Triton account
110
111### Example Run
112
113The verbose output has been removed for brevity here.
114
115```
116$ HTTP_PROXY=http://localhost:8888 \
117 TRITON_TEST=1 \
118 TRITON_URL=https://us-sw-1.api.joyent.com \
119 TRITON_ACCOUNT=AccountName \
120 TRITON_KEY_ID=a4:c6:f3:75:80:27:e0:03:a9:98:79:ef:c5:0a:06:11 \
121 go test -v -run "TestAccKey"
122=== RUN TestAccKey_Create
123--- PASS: TestAccKey_Create (12.46s)
124=== RUN TestAccKey_Get
125--- PASS: TestAccKey_Get (4.30s)
126=== RUN TestAccKey_Delete
127--- PASS: TestAccKey_Delete (15.08s)
128PASS
129ok github.com/joyent/triton-go 31.861s
130```
131
132## Example API
133
134There's an `examples/` directory available with sample code setup for many of
135the APIs within this library. Most of these can be run using `go run` and
136referencing your SSH key file use by your active `triton` CLI profile.
137
138```sh
139$ eval "$(triton env us-sw-1)"
140$ TRITON_KEY_FILE=~/.ssh/triton-id_rsa go run examples/compute/instances.go
141```
142
143The following is a complete example of how to initialize the `compute` package
144client and list all instances under an account. More detailed usage of this
145library follows.
146
147```go
148
149
150package main
151
152import (
153 "context"
154 "fmt"
155 "io/ioutil"
156 "log"
157 "os"
158 "time"
159
160 triton "github.com/joyent/triton-go"
161 "github.com/joyent/triton-go/authentication"
162 "github.com/joyent/triton-go/compute"
163)
164
165func main() {
166 keyID := os.Getenv("TRITON_KEY_ID")
167 accountName := os.Getenv("TRITON_ACCOUNT")
168 keyMaterial := os.Getenv("TRITON_KEY_MATERIAL")
169 userName := os.Getenv("TRITON_USER")
170
171 var signer authentication.Signer
172 var err error
173
174 if keyMaterial == "" {
175 input := authentication.SSHAgentSignerInput{
176 KeyID: keyID,
177 AccountName: accountName,
178 Username: userName,
179 }
180 signer, err = authentication.NewSSHAgentSigner(input)
181 if err != nil {
182 log.Fatalf("Error Creating SSH Agent Signer: {{err}}", err)
183 }
184 } else {
185 var keyBytes []byte
186 if _, err = os.Stat(keyMaterial); err == nil {
187 keyBytes, err = ioutil.ReadFile(keyMaterial)
188 if err != nil {
189 log.Fatalf("Error reading key material from %s: %s",
190 keyMaterial, err)
191 }
192 block, _ := pem.Decode(keyBytes)
193 if block == nil {
194 log.Fatalf(
195 "Failed to read key material '%s': no key found", keyMaterial)
196 }
197
198 if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
199 log.Fatalf(
200 "Failed to read key '%s': password protected keys are\n"+
201 "not currently supported. Please decrypt the key prior to use.", keyMaterial)
202 }
203
204 } else {
205 keyBytes = []byte(keyMaterial)
206 }
207
208 input := authentication.PrivateKeySignerInput{
209 KeyID: keyID,
210 PrivateKeyMaterial: keyBytes,
211 AccountName: accountName,
212 Username: userName,
213 }
214 signer, err = authentication.NewPrivateKeySigner(input)
215 if err != nil {
216 log.Fatalf("Error Creating SSH Private Key Signer: {{err}}", err)
217 }
218 }
219
220 config := &triton.ClientConfig{
221 TritonURL: os.Getenv("TRITON_URL"),
222 AccountName: accountName,
223 Username: userName,
224 Signers: []authentication.Signer{signer},
225 }
226
227 c, err := compute.NewClient(config)
228 if err != nil {
229 log.Fatalf("compute.NewClient: %s", err)
230 }
231
232 listInput := &compute.ListInstancesInput{}
233 instances, err := c.Instances().List(context.Background(), listInput)
234 if err != nil {
235 log.Fatalf("compute.Instances.List: %v", err)
236 }
237 numInstances := 0
238 for _, instance := range instances {
239 numInstances++
240 fmt.Println(fmt.Sprintf("-- Instance: %v", instance.Name))
241 }
242}
243
244```
245
246[4]: https://github.com/joyent/node-http-signature/blob/master/http_signing.md
247[5]: https://godoc.org/github.com/joyent/triton-go/authentication
248[6]: https://godoc.org/github.com/joyent/triton-go/authentication
249[7]: https://github.com/pkg/errors
250