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