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