├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── account ├── accesskeys.go ├── accesskeys_test.go ├── account.go ├── account_test.go ├── client.go ├── config.go ├── config_test.go ├── keys.go └── keys_test.go ├── authentication ├── agent_key_identifier.go ├── ecdsa_signature.go ├── private_key_signer.go ├── rsa_signature.go ├── signature.go ├── signer.go ├── ssh_agent_signer.go ├── test_signer.go └── util.go ├── client ├── client.go └── client_test.go ├── cmd ├── agent │ ├── account │ │ └── public.go │ ├── compute │ │ ├── public.go │ │ ├── sortImages.go │ │ └── sortInstances.go │ ├── identity │ │ └── public.go │ ├── network │ │ └── public.go │ └── storage │ │ └── public.go ├── config │ └── config.go ├── internal │ ├── command │ │ └── public.go │ ├── config │ │ └── public.go │ └── logger │ │ ├── format.go │ │ ├── level.go │ │ ├── pgx.go │ │ └── public.go ├── manta │ ├── cmd │ │ ├── docs │ │ │ ├── man │ │ │ │ └── public.go │ │ │ ├── md │ │ │ │ └── public.go │ │ │ └── public.go │ │ ├── list │ │ │ └── public.go │ │ ├── root.go │ │ ├── shell │ │ │ ├── autocomplete │ │ │ │ ├── bash │ │ │ │ │ └── public.go │ │ │ │ └── public.go │ │ │ └── public.go │ │ └── version │ │ │ └── public.go │ └── main.go └── triton │ ├── cmd │ ├── accesskeys │ │ ├── create │ │ │ └── public.go │ │ ├── delete │ │ │ └── public.go │ │ ├── get │ │ │ └── public.go │ │ ├── list │ │ │ └── public.go │ │ └── public.go │ ├── account │ │ ├── get │ │ │ └── public.go │ │ ├── public.go │ │ └── update │ │ │ └── public.go │ ├── datacenters │ │ └── public.go │ ├── docs │ │ ├── man │ │ │ └── public.go │ │ ├── md │ │ │ └── public.go │ │ └── public.go │ ├── instances │ │ ├── count │ │ │ └── public.go │ │ ├── create │ │ │ └── public.go │ │ ├── delete │ │ │ └── public.go │ │ ├── get │ │ │ └── public.go │ │ ├── ip │ │ │ └── public.go │ │ ├── list │ │ │ └── public.go │ │ ├── public.go │ │ ├── reboot │ │ │ └── public.go │ │ ├── start │ │ │ └── public.go │ │ └── stop │ │ │ └── public.go │ ├── keys │ │ ├── create │ │ │ └── public.go │ │ ├── delete │ │ │ └── public.go │ │ ├── get │ │ │ └── public.go │ │ ├── list │ │ │ └── public.go │ │ └── public.go │ ├── packages │ │ ├── get │ │ │ └── public.go │ │ ├── list │ │ │ └── public.go │ │ └── public.go │ ├── root.go │ ├── root_test.go │ ├── services │ │ └── public.go │ ├── shell │ │ ├── autocomplete │ │ │ ├── bash │ │ │ │ └── public.go │ │ │ └── public.go │ │ └── public.go │ └── version │ │ └── public.go │ └── main.go ├── compute ├── client.go ├── client_test.go ├── datacenters.go ├── datacenters_test.go ├── images.go ├── images_test.go ├── instances.go ├── instances_test.go ├── packages.go ├── packages_test.go ├── ping.go ├── ping_test.go ├── services.go ├── services_test.go ├── snapshots.go ├── snapshots_test.go ├── volumes.go └── volumes_test.go ├── errors ├── errors.go └── errors_test.go ├── examples ├── account │ ├── account_info │ │ └── main.go │ ├── config │ │ └── main.go │ └── keys │ │ └── main.go ├── compute │ ├── create_instance │ │ └── main.go │ ├── create_instance_with_volumes │ │ └── main.go │ ├── instances │ │ └── main.go │ └── volumes │ │ ├── crud │ │ └── main.go │ │ └── list_volumes │ │ └── main.go ├── network │ └── create_fabric.go ├── services │ ├── groups │ │ └── list_groups.go │ └── templates │ │ └── list_templates.go └── storage │ ├── create_job │ └── main.go │ ├── create_mpu │ └── main.go │ ├── force_delete │ └── main.go │ ├── force_put │ └── main.go │ ├── get_object │ └── main.go │ ├── list_directory │ └── main.go │ ├── mls │ └── main.go │ ├── object_put │ └── main.go │ └── sign_url │ └── main.go ├── go.mod ├── go.sum ├── identity ├── client.go ├── policies.go ├── policies_test.go ├── roles.go ├── roles_test.go ├── users.go └── users_test.go ├── network ├── client.go ├── fabrics.go ├── fabrics_test.go ├── firewall.go ├── firewall_test.go ├── network.go └── network_test.go ├── scripts ├── go-test-with-coverage.sh └── gofmt-check.sh ├── services ├── client.go ├── groups.go ├── groups_test.go ├── templates.go └── templates_test.go ├── storage ├── client.go ├── directory.go ├── directory_test.go ├── job.go ├── objects.go ├── signing.go ├── snaplink.go └── snaplink_test.go ├── testutils ├── basic_runner.go ├── mock_http.go ├── random.go ├── runner.go ├── statebag.go ├── step.go └── steps.go ├── triton.go ├── triton_test.go ├── utils └── http_debugging.go └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exe 3 | .DS_Store 4 | bin/ 5 | pkg/ 6 | .vagrant/ 7 | *.backup 8 | *.log 9 | *.bak 10 | *~ 11 | *# 12 | .*.swp 13 | .idea/ 14 | *.test 15 | *.iml 16 | Notes.md 17 | coverage.txt 18 | cmd/triton/triton 19 | cmd/triton/docs/ 20 | vendor/ 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | language: go 4 | go: 5 | - "1.10" 6 | 7 | install: 8 | - go get github.com/golang/dep/cmd/dep 9 | 10 | script: 11 | - make tools default 12 | 13 | branches: 14 | only: 15 | - master 16 | matrix: 17 | fast_finish: true 18 | allow_failures: 19 | - go: tip -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright 2020 Joyent, Inc. 9 | # 10 | 11 | TEST?=$$(go list ./... |grep -Ev 'vendor|examples|testutils') 12 | GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor) 13 | 14 | .PHONY: all 15 | all: 16 | 17 | .PHONY: tools 18 | tools: ## Download and install all dev/code tools 19 | @echo "==> Installing dev tools" 20 | go get -u github.com/golang/dep/cmd/dep 21 | 22 | .PHONY: build 23 | build: 24 | @govvv build 25 | 26 | .PHONY: install 27 | install: 28 | @govvv install 29 | 30 | .PHONY: test 31 | test: ## Run unit tests 32 | @echo "==> Running unit test with coverage" 33 | @./scripts/go-test-with-coverage.sh 34 | 35 | .PHONY: testacc 36 | testacc: ## Run acceptance tests 37 | @echo "==> Running acceptance tests" 38 | TRITON_TEST=1 go test $(TEST) -v -run TestAcc -timeout 60m 39 | 40 | .PHONY: check 41 | check: 42 | scripts/gofmt-check.sh 43 | 44 | .PHONY: help 45 | help: ## Display this help message 46 | @echo "GNU make(1) targets:" 47 | @grep -E '^[a-zA-Z_.-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' 48 | -------------------------------------------------------------------------------- /account/account.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package account 10 | 11 | import ( 12 | "context" 13 | "encoding/json" 14 | "net/http" 15 | "path" 16 | "time" 17 | 18 | "github.com/joyent/triton-go/v2/client" 19 | "github.com/pkg/errors" 20 | ) 21 | 22 | type Account struct { 23 | ID string `json:"id"` 24 | Login string `json:"login"` 25 | Email string `json:"email"` 26 | CompanyName string `json:"companyName"` 27 | FirstName string `json:"firstName"` 28 | LastName string `json:"lastName"` 29 | Address string `json:"address"` 30 | PostalCode string `json:"postalCode"` 31 | City string `json:"city"` 32 | State string `json:"state"` 33 | Country string `json:"country"` 34 | Phone string `json:"phone"` 35 | Created time.Time `json:"created"` 36 | Updated time.Time `json:"updated"` 37 | TritonCNSEnabled bool `json:"triton_cns_enabled"` 38 | } 39 | 40 | type GetInput struct{} 41 | 42 | func (c AccountClient) Get(ctx context.Context, input *GetInput) (*Account, error) { 43 | fullPath := path.Join("/", c.Client.AccountName) 44 | reqInputs := client.RequestInput{ 45 | Method: http.MethodGet, 46 | Path: fullPath, 47 | } 48 | respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) 49 | if respReader != nil { 50 | defer respReader.Close() 51 | } 52 | if err != nil { 53 | return nil, errors.Wrap(err, "unable to get account details") 54 | } 55 | 56 | var result *Account 57 | decoder := json.NewDecoder(respReader) 58 | if err = decoder.Decode(&result); err != nil { 59 | return nil, errors.Wrap(err, "unable to decode get account details") 60 | } 61 | 62 | return result, nil 63 | } 64 | 65 | type UpdateInput struct { 66 | Email string `json:"email,omitempty"` 67 | CompanyName string `json:"companyName,omitempty"` 68 | FirstName string `json:"firstName,omitempty"` 69 | LastName string `json:"lastName,omitempty"` 70 | Address string `json:"address,omitempty"` 71 | PostalCode string `json:"postalCode,omitempty"` 72 | City string `json:"city,omitempty"` 73 | State string `json:"state,omitempty"` 74 | Country string `json:"country,omitempty"` 75 | Phone string `json:"phone,omitempty"` 76 | TritonCNSEnabled bool `json:"triton_cns_enabled,omitempty"` 77 | } 78 | 79 | // UpdateAccount updates your account details with the given parameters. 80 | // TODO(jen20) Work out a safe way to test this 81 | func (c AccountClient) Update(ctx context.Context, input *UpdateInput) (*Account, error) { 82 | fullPath := path.Join("/", c.Client.AccountName) 83 | reqInputs := client.RequestInput{ 84 | Method: http.MethodPost, 85 | Path: fullPath, 86 | Body: input, 87 | } 88 | respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) 89 | if err != nil { 90 | return nil, errors.Wrap(err, "unable to update account") 91 | } 92 | if respReader != nil { 93 | defer respReader.Close() 94 | } 95 | 96 | var result *Account 97 | decoder := json.NewDecoder(respReader) 98 | if err = decoder.Decode(&result); err != nil { 99 | return nil, errors.Wrap(err, "unable to decode update account response") 100 | } 101 | 102 | return result, nil 103 | } 104 | -------------------------------------------------------------------------------- /account/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package account 10 | 11 | import ( 12 | "net/http" 13 | 14 | triton "github.com/joyent/triton-go/v2" 15 | "github.com/joyent/triton-go/v2/client" 16 | ) 17 | 18 | type AccountClient struct { 19 | Client *client.Client 20 | } 21 | 22 | func newAccountClient(client *client.Client) *AccountClient { 23 | return &AccountClient{ 24 | Client: client, 25 | } 26 | } 27 | 28 | // NewClient returns a new client for working with Account endpoints and 29 | // resources within CloudAPI 30 | func NewClient(config *triton.ClientConfig) (*AccountClient, error) { 31 | // TODO: Utilize config interface within the function itself 32 | client, err := client.New( 33 | config.TritonURL, 34 | config.MantaURL, 35 | config.AccountName, 36 | config.Signers..., 37 | ) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return newAccountClient(client), nil 42 | } 43 | 44 | // SetHeader allows a consumer of the current client to set custom headers for 45 | // the next backend HTTP request sent to CloudAPI 46 | func (c *AccountClient) SetHeader(header *http.Header) { 47 | c.Client.RequestHeader = header 48 | } 49 | 50 | // Config returns a c used for accessing functions pertaining 51 | // to Config functionality in the Triton API. 52 | func (c *AccountClient) Config() *ConfigClient { 53 | return &ConfigClient{c.Client} 54 | } 55 | 56 | // Keys returns a Compute client used for accessing functions pertaining to SSH 57 | // key functionality in the Triton API. 58 | func (c *AccountClient) Keys() *KeysClient { 59 | return &KeysClient{c.Client} 60 | } 61 | 62 | // AccessKeys returns a Compute Client used for accessing functions related to 63 | // Access Keys functionality 64 | func (c *AccountClient) AccessKeys() *AccessKeysClient { 65 | return &AccessKeysClient{c.Client} 66 | } 67 | -------------------------------------------------------------------------------- /account/config.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package account 10 | 11 | import ( 12 | "context" 13 | "encoding/json" 14 | "net/http" 15 | "path" 16 | 17 | "github.com/joyent/triton-go/v2/client" 18 | "github.com/pkg/errors" 19 | ) 20 | 21 | type ConfigClient struct { 22 | client *client.Client 23 | } 24 | 25 | // Config represents configuration for your account. 26 | type Config struct { 27 | // DefaultNetwork is the network that docker containers are provisioned on. 28 | DefaultNetwork string `json:"default_network"` 29 | } 30 | 31 | type GetConfigInput struct{} 32 | 33 | // GetConfig outputs configuration for your account. 34 | func (c *ConfigClient) Get(ctx context.Context, input *GetConfigInput) (*Config, error) { 35 | fullPath := path.Join("/", c.client.AccountName, "config") 36 | reqInputs := client.RequestInput{ 37 | Method: http.MethodGet, 38 | Path: fullPath, 39 | } 40 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 41 | if respReader != nil { 42 | defer respReader.Close() 43 | } 44 | if err != nil { 45 | return nil, errors.Wrap(err, "unable to get account config") 46 | } 47 | 48 | var result *Config 49 | decoder := json.NewDecoder(respReader) 50 | if err = decoder.Decode(&result); err != nil { 51 | return nil, errors.Wrap(err, "unable to decode get account config response") 52 | } 53 | 54 | return result, nil 55 | } 56 | 57 | type UpdateConfigInput struct { 58 | // DefaultNetwork is the network that docker containers are provisioned on. 59 | DefaultNetwork string `json:"default_network"` 60 | } 61 | 62 | // UpdateConfig updates configuration values for your account. 63 | func (c *ConfigClient) Update(ctx context.Context, input *UpdateConfigInput) (*Config, error) { 64 | fullPath := path.Join("/", c.client.AccountName, "config") 65 | reqInputs := client.RequestInput{ 66 | Method: http.MethodPost, 67 | Path: fullPath, 68 | Body: input, 69 | } 70 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 71 | if respReader != nil { 72 | defer respReader.Close() 73 | } 74 | if err != nil { 75 | return nil, errors.Wrap(err, "unable to update account config") 76 | } 77 | 78 | var result *Config 79 | decoder := json.NewDecoder(respReader) 80 | if err = decoder.Decode(&result); err != nil { 81 | return nil, errors.Wrap(err, "unable to decode update account config response") 82 | } 83 | 84 | return result, nil 85 | } 86 | -------------------------------------------------------------------------------- /account/keys.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package account 10 | 11 | import ( 12 | "context" 13 | "encoding/json" 14 | "net/http" 15 | "path" 16 | 17 | "github.com/joyent/triton-go/v2/client" 18 | "github.com/pkg/errors" 19 | ) 20 | 21 | type KeysClient struct { 22 | client *client.Client 23 | } 24 | 25 | // Key represents a public key 26 | type Key struct { 27 | // Name of the key 28 | Name string `json:"name"` 29 | 30 | // Key fingerprint 31 | Fingerprint string `json:"fingerprint"` 32 | 33 | // OpenSSH-formatted public key 34 | Key string `json:"key"` 35 | } 36 | 37 | type ListKeysInput struct{} 38 | 39 | // ListKeys lists all public keys we have on record for the specified 40 | // account. 41 | func (c *KeysClient) List(ctx context.Context, _ *ListKeysInput) ([]*Key, error) { 42 | fullPath := path.Join("/", c.client.AccountName, "keys") 43 | reqInputs := client.RequestInput{ 44 | Method: http.MethodGet, 45 | Path: fullPath, 46 | } 47 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 48 | if respReader != nil { 49 | defer respReader.Close() 50 | } 51 | if err != nil { 52 | return nil, errors.Wrap(err, "unable to list keys") 53 | } 54 | 55 | var result []*Key 56 | decoder := json.NewDecoder(respReader) 57 | if err = decoder.Decode(&result); err != nil { 58 | return nil, errors.Wrap(err, "unable to decode list keys response") 59 | } 60 | 61 | return result, nil 62 | } 63 | 64 | type GetKeyInput struct { 65 | KeyName string 66 | } 67 | 68 | func (c *KeysClient) Get(ctx context.Context, input *GetKeyInput) (*Key, error) { 69 | fullPath := path.Join("/", c.client.AccountName, "keys", input.KeyName) 70 | reqInputs := client.RequestInput{ 71 | Method: http.MethodGet, 72 | Path: fullPath, 73 | } 74 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 75 | if respReader != nil { 76 | defer respReader.Close() 77 | } 78 | if err != nil { 79 | return nil, errors.Wrap(err, "unable to get key") 80 | } 81 | 82 | var result *Key 83 | decoder := json.NewDecoder(respReader) 84 | if err = decoder.Decode(&result); err != nil { 85 | return nil, errors.Wrap(err, "unable to decode get key response") 86 | } 87 | 88 | return result, nil 89 | } 90 | 91 | type DeleteKeyInput struct { 92 | KeyName string 93 | } 94 | 95 | func (c *KeysClient) Delete(ctx context.Context, input *DeleteKeyInput) error { 96 | fullPath := path.Join("/", c.client.AccountName, "keys", input.KeyName) 97 | reqInputs := client.RequestInput{ 98 | Method: http.MethodDelete, 99 | Path: fullPath, 100 | } 101 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 102 | if respReader != nil { 103 | defer respReader.Close() 104 | } 105 | if err != nil { 106 | return errors.Wrap(err, "unable to delete key") 107 | } 108 | 109 | return nil 110 | } 111 | 112 | // CreateKeyInput represents the option that can be specified 113 | // when creating a new key. 114 | type CreateKeyInput struct { 115 | // Name of the key. Optional. 116 | Name string `json:"name,omitempty"` 117 | 118 | // OpenSSH-formatted public key. 119 | Key string `json:"key"` 120 | } 121 | 122 | // CreateKey uploads a new OpenSSH key to Triton for use in HTTP signing and SSH. 123 | func (c *KeysClient) Create(ctx context.Context, input *CreateKeyInput) (*Key, error) { 124 | fullPath := path.Join("/", c.client.AccountName, "keys") 125 | reqInputs := client.RequestInput{ 126 | Method: http.MethodPost, 127 | Path: fullPath, 128 | Body: input, 129 | } 130 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 131 | if respReader != nil { 132 | defer respReader.Close() 133 | } 134 | if err != nil { 135 | return nil, errors.Wrap(err, "unable to create key") 136 | } 137 | 138 | var result *Key 139 | decoder := json.NewDecoder(respReader) 140 | if err = decoder.Decode(&result); err != nil { 141 | return nil, errors.Wrap(err, "unable to decode create key response") 142 | } 143 | 144 | return result, nil 145 | } 146 | -------------------------------------------------------------------------------- /authentication/agent_key_identifier.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | import "path" 4 | 5 | type KeyID struct { 6 | UserName string 7 | AccountName string 8 | Fingerprint string 9 | IsManta bool 10 | } 11 | 12 | func (input *KeyID) generate() string { 13 | var keyID string 14 | if input.UserName != "" { 15 | if input.IsManta { 16 | keyID = path.Join("/", input.AccountName, input.UserName, "keys", input.Fingerprint) 17 | } else { 18 | keyID = path.Join("/", input.AccountName, "users", input.UserName, "keys", input.Fingerprint) 19 | } 20 | } else { 21 | keyID = path.Join("/", input.AccountName, "keys", input.Fingerprint) 22 | } 23 | 24 | return keyID 25 | } 26 | -------------------------------------------------------------------------------- /authentication/ecdsa_signature.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package authentication 10 | 11 | import ( 12 | "encoding/asn1" 13 | "encoding/base64" 14 | "fmt" 15 | "math/big" 16 | 17 | "github.com/pkg/errors" 18 | "golang.org/x/crypto/ssh" 19 | ) 20 | 21 | type ecdsaSignature struct { 22 | hashAlgorithm string 23 | R *big.Int 24 | S *big.Int 25 | } 26 | 27 | func (s *ecdsaSignature) SignatureType() string { 28 | return fmt.Sprintf("ecdsa-%s", s.hashAlgorithm) 29 | } 30 | 31 | func (s *ecdsaSignature) String() string { 32 | toEncode := struct { 33 | R *big.Int 34 | S *big.Int 35 | }{ 36 | R: s.R, 37 | S: s.S, 38 | } 39 | 40 | signatureBytes, err := asn1.Marshal(toEncode) 41 | if err != nil { 42 | panic(fmt.Sprintf("Error marshaling signature: %s", err)) 43 | } 44 | 45 | return base64.StdEncoding.EncodeToString(signatureBytes) 46 | } 47 | 48 | func newECDSASignature(signatureBlob []byte) (*ecdsaSignature, error) { 49 | var ecSig struct { 50 | R *big.Int 51 | S *big.Int 52 | } 53 | 54 | if err := ssh.Unmarshal(signatureBlob, &ecSig); err != nil { 55 | return nil, errors.Wrap(err, "unable to unmarshall signature") 56 | } 57 | 58 | rValue := ecSig.R.Bytes() 59 | var hashAlgorithm string 60 | switch len(rValue) { 61 | case 31, 32: 62 | hashAlgorithm = "sha256" 63 | case 65, 66: 64 | hashAlgorithm = "sha512" 65 | default: 66 | return nil, fmt.Errorf("Unsupported key length: %d", len(rValue)) 67 | } 68 | 69 | return &ecdsaSignature{ 70 | hashAlgorithm: hashAlgorithm, 71 | R: ecSig.R, 72 | S: ecSig.S, 73 | }, nil 74 | } 75 | -------------------------------------------------------------------------------- /authentication/rsa_signature.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package authentication 10 | 11 | import ( 12 | "encoding/base64" 13 | ) 14 | 15 | type rsaSignature struct { 16 | hashAlgorithm string 17 | signature []byte 18 | } 19 | 20 | func (s *rsaSignature) SignatureType() string { 21 | return s.hashAlgorithm 22 | } 23 | 24 | func (s *rsaSignature) String() string { 25 | return base64.StdEncoding.EncodeToString(s.signature) 26 | } 27 | 28 | func newRSASignature(signatureBlob []byte) (*rsaSignature, error) { 29 | return &rsaSignature{ 30 | hashAlgorithm: "rsa-sha1", 31 | signature: signatureBlob, 32 | }, nil 33 | } 34 | -------------------------------------------------------------------------------- /authentication/signature.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package authentication 10 | 11 | import ( 12 | "fmt" 13 | "regexp" 14 | ) 15 | 16 | type httpAuthSignature interface { 17 | SignatureType() string 18 | String() string 19 | } 20 | 21 | func keyFormatToKeyType(keyFormat string) (string, error) { 22 | if keyFormat == "ssh-rsa" { 23 | return "rsa", nil 24 | } 25 | 26 | if keyFormat == "ssh-ed25519" { 27 | return "ed25519", nil 28 | } 29 | 30 | if regexp.MustCompile("^ecdsa-sha2-*").Match([]byte(keyFormat)) { 31 | return "ecdsa", nil 32 | } 33 | 34 | return "", fmt.Errorf("Unknown key format: %s", keyFormat) 35 | } 36 | -------------------------------------------------------------------------------- /authentication/signer.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package authentication 10 | 11 | const authorizationHeaderFormat = `Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"` 12 | 13 | type Signer interface { 14 | DefaultAlgorithm() string 15 | KeyFingerprint() string 16 | Sign(dateHeader string, isManta bool) (string, error) 17 | SignRaw(toSign string) (string, string, error) 18 | } 19 | -------------------------------------------------------------------------------- /authentication/test_signer.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package authentication 10 | 11 | // TestSigner represents an authentication key signer which we can use for 12 | // testing purposes only. This will largely be a stub to send through client 13 | // unit tests. 14 | type TestSigner struct{} 15 | 16 | // NewTestSigner constructs a new instance of test signer 17 | func NewTestSigner() (Signer, error) { 18 | return &TestSigner{}, nil 19 | } 20 | 21 | func (s *TestSigner) DefaultAlgorithm() string { 22 | return "" 23 | } 24 | 25 | func (s *TestSigner) KeyFingerprint() string { 26 | return "" 27 | } 28 | 29 | func (s *TestSigner) Sign(dateHeader string, isManta bool) (string, error) { 30 | return "", nil 31 | } 32 | 33 | func (s *TestSigner) SignRaw(toSign string) (string, string, error) { 34 | return "", "", nil 35 | } 36 | -------------------------------------------------------------------------------- /authentication/util.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package authentication 10 | 11 | import ( 12 | "crypto/ecdsa" 13 | "crypto/md5" 14 | "crypto/rsa" 15 | "fmt" 16 | "strings" 17 | 18 | "github.com/pkg/errors" 19 | "golang.org/x/crypto/ssh" 20 | ) 21 | 22 | // formatPublicKeyFingerprint produces the MD5 fingerprint of the given SSH 23 | // public key. If display is true, the fingerprint is formatted with colons 24 | // between each byte, as per the output of OpenSSL. 25 | func formatPublicKeyFingerprint(privateKey interface{}, display bool) (string, error) { 26 | var key ssh.PublicKey 27 | switch privateKey.(type) { 28 | case *rsa.PrivateKey: 29 | p, err := ssh.NewPublicKey(privateKey.(*rsa.PrivateKey).Public()) 30 | if err != nil { 31 | return "", errors.Wrap(err, "unable to parse SSH key from private key") 32 | } 33 | key = p 34 | case *ecdsa.PrivateKey: 35 | p, err := ssh.NewPublicKey(privateKey.(*ecdsa.PrivateKey).Public()) 36 | if err != nil { 37 | return "", errors.Wrap(err, "unable to parse SSH key from private key") 38 | } 39 | key = p 40 | default: 41 | return "", fmt.Errorf("unable to parse SSH key from private key") 42 | 43 | } 44 | publicKeyFingerprint := md5.New() 45 | publicKeyFingerprint.Write(key.Marshal()) 46 | publicKeyFingerprintString := fmt.Sprintf("%x", publicKeyFingerprint.Sum(nil)) 47 | 48 | if !display { 49 | return publicKeyFingerprintString, nil 50 | } 51 | 52 | formatted := "" 53 | for i := 0; i < len(publicKeyFingerprintString); i = i + 2 { 54 | formatted = fmt.Sprintf("%s%s:", formatted, publicKeyFingerprintString[i:i+2]) 55 | } 56 | 57 | return strings.TrimSuffix(formatted, ":"), nil 58 | } 59 | -------------------------------------------------------------------------------- /cmd/agent/compute/sortImages.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package compute 10 | 11 | import ( 12 | "sort" 13 | 14 | "github.com/joyent/triton-go/v2/compute" 15 | ) 16 | 17 | type imageSort []*compute.Image 18 | 19 | func sortImages(images []*compute.Image) []*compute.Image { 20 | sortedImages := images 21 | sort.Sort(imageSort(sortedImages)) 22 | return sortedImages 23 | } 24 | 25 | func (a imageSort) Len() int { 26 | return len(a) 27 | } 28 | 29 | func (a imageSort) Swap(i, j int) { 30 | a[i], a[j] = a[j], a[i] 31 | } 32 | 33 | func (a imageSort) Less(i, j int) bool { 34 | itime := a[i].PublishedAt 35 | jtime := a[j].PublishedAt 36 | return itime.Unix() < jtime.Unix() 37 | } 38 | -------------------------------------------------------------------------------- /cmd/agent/compute/sortInstances.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package compute 10 | 11 | import ( 12 | "sort" 13 | 14 | "github.com/joyent/triton-go/v2/compute" 15 | ) 16 | 17 | type instanceSort []*compute.Instance 18 | 19 | func sortInstances(instances []*compute.Instance) []*compute.Instance { 20 | sortInstances := instances 21 | sort.Sort(instanceSort(sortInstances)) 22 | return sortInstances 23 | } 24 | 25 | func (a instanceSort) Len() int { 26 | return len(a) 27 | } 28 | 29 | func (a instanceSort) Swap(i, j int) { 30 | a[i], a[j] = a[j], a[i] 31 | } 32 | 33 | func (a instanceSort) Less(i, j int) bool { 34 | itime := a[i].Created 35 | jtime := a[j].Created 36 | return itime.Unix() < jtime.Unix() 37 | } 38 | -------------------------------------------------------------------------------- /cmd/agent/identity/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package identity 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/config" 13 | "github.com/joyent/triton-go/v2/identity" 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | func NewGetIdentityClient(cfg *config.TritonClientConfig) (*identity.IdentityClient, error) { 18 | identityClient, err := identity.NewClient(cfg.Config) 19 | if err != nil { 20 | return nil, errors.Wrap(err, "Error Creating Triton Identity Client") 21 | } 22 | return identityClient, nil 23 | } 24 | -------------------------------------------------------------------------------- /cmd/agent/network/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package network 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/config" 13 | "github.com/joyent/triton-go/v2/network" 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | func NewGetNetworkClient(cfg *config.TritonClientConfig) (*network.NetworkClient, error) { 18 | networkClient, err := network.NewClient(cfg.Config) 19 | if err != nil { 20 | return nil, errors.Wrap(err, "Error Creating Triton Netowkr Client") 21 | } 22 | return networkClient, nil 23 | } 24 | -------------------------------------------------------------------------------- /cmd/agent/storage/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package storage 10 | 11 | import ( 12 | "context" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/config" 15 | tsc "github.com/joyent/triton-go/v2/storage" 16 | "github.com/pkg/errors" 17 | ) 18 | 19 | type AgentStorageClient struct { 20 | client *tsc.StorageClient 21 | } 22 | 23 | func NewStorageClient(cfg *config.TritonClientConfig) (*AgentStorageClient, error) { 24 | storageClient, err := tsc.NewClient(cfg.Config) 25 | if err != nil { 26 | return nil, errors.Wrap(err, "Error Creating Triton Storage Client") 27 | } 28 | return &AgentStorageClient{ 29 | client: storageClient, 30 | }, nil 31 | } 32 | 33 | func (c *AgentStorageClient) GetDirectoryListing(args []string) (*tsc.ListDirectoryOutput, error) { 34 | 35 | input := &tsc.ListDirectoryInput{} 36 | if len(args) > 0 { 37 | input.DirectoryName = args[0] 38 | } 39 | 40 | directoryOutput, err := c.client.Dir().List(context.Background(), input) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | return directoryOutput, nil 46 | } 47 | -------------------------------------------------------------------------------- /cmd/internal/command/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package command 10 | 11 | import "github.com/spf13/cobra" 12 | 13 | type SetupFunc func(parent *Command) error 14 | 15 | type Command struct { 16 | Cobra *cobra.Command 17 | Setup SetupFunc 18 | } 19 | -------------------------------------------------------------------------------- /cmd/internal/config/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package config 10 | 11 | const ( 12 | KeyTritonAccount = "general.triton.account" 13 | KeyTritonURL = "general.triton.url" 14 | KeyTritonSSHKeyMaterial = "general.triton.key-material" 15 | KeyTritonSSHKeyID = "general.triton.key-id" 16 | 17 | KeyMantaAccount = "general.manta.account" 18 | KeyMantaURL = "general.manta.url" 19 | KeyMantaSSHKeyMaterial = "general.manta.key-material" 20 | KeyMantaSSHKeyID = "general.manta.key-id" 21 | 22 | DefaultManDir = "./docs/man" 23 | ManSect = 8 24 | 25 | DefaultMarkdownDir = "./docs/md" 26 | DefaultMarkdownURLPrefix = "/command" 27 | 28 | KeyDocManDir = "doc.mandir" 29 | KeyDocMarkdownDir = "doc.markdown-dir" 30 | KeyDocMarkdownURLPrefix = "doc.markdown-url-prefix" 31 | 32 | KeyBashAutoCompletionTarget = "shell.autocomplete.bash.target" 33 | 34 | KeyUsePager = "general.use-pager" 35 | KeyUseUTC = "general.utc" 36 | 37 | KeyLogFormat = "log.format" 38 | KeyLogLevel = "log.level" 39 | KeyLogStats = "log.stats" 40 | KeyLogTermColor = "log.use-color" 41 | 42 | KeyInstanceName = "compute.instance.name" 43 | KeyInstanceID = "compute.instance.id" 44 | KeyInstanceWait = "compute.instance.wait" 45 | KeyInstanceFirewall = "compute.instance.firewall" 46 | KeyInstanceState = "compute.instance.state" 47 | KeyInstanceBrand = "compute.instance.brand" 48 | KeyInstanceNetwork = "compute.instance.networks" 49 | KeyInstanceTag = "compute.instance.tag" 50 | KeyInstanceSearchTag = "compute.instance.search-tags" 51 | KeyInstanceMetadata = "compute.instance.metadata" 52 | KeyInstanceAffinityRule = "compute.instance.affinity" 53 | KeyInstanceUserdata = "compute.instance.userdata" 54 | KeyInstanceNamePrefix = "compute.instance.name-prefix" 55 | 56 | KeyPackageName = "compute.package.name" 57 | KeyPackageID = "compute.package.id" 58 | KeyPackageMemory = "compute.package.memory" 59 | KeyPackageDisk = "compute.package.disk" 60 | KeyPackageSwap = "compute.package.swap" 61 | KeyPackageVPCUs = "compute.package.vcpu" 62 | 63 | KeyImageName = "compute.image.name" 64 | KeyImageId = "compute.image.id" 65 | 66 | KeySSHKeyFingerprint = "keys.fingerprint" 67 | KeySSHKeyName = "keys.name" 68 | KeySSHKey = "keys.publickey" 69 | 70 | KeyAccessKeyID = "accesskeys.accesskeyid" 71 | 72 | KeyAccountEmail = "account.email" 73 | KeyAccountCompanyName = "account.companyname" 74 | KeyAccountFirstName = "account.firstname" 75 | KeyAccountLastName = "account.lastname" 76 | KeyAccountAddress = "account.address" 77 | KeyAccountPostcode = "account.postcode" 78 | KeyAccountCity = "account.city" 79 | KeyAccountState = "account.state" 80 | KeyAccountCountry = "account.country" 81 | KeyAccountPhone = "account.phone" 82 | KeyAccountTritonCNSEnabled = "account.triton_cns_enabled" 83 | ) 84 | -------------------------------------------------------------------------------- /cmd/internal/logger/format.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package logger 10 | 11 | import ( 12 | "fmt" 13 | "strings" 14 | 15 | "github.com/joyent/triton-go/v2/cmd/internal/config" 16 | "github.com/spf13/viper" 17 | ) 18 | 19 | type Format uint 20 | 21 | const ( 22 | FormatAuto Format = iota 23 | FormatZerolog 24 | FormatHuman 25 | ) 26 | 27 | func (f Format) String() string { 28 | switch f { 29 | case FormatAuto: 30 | return "auto" 31 | case FormatZerolog: 32 | return "zerolog" 33 | case FormatHuman: 34 | return "human" 35 | default: 36 | panic(fmt.Sprintf("unknown log format: %d", f)) 37 | } 38 | } 39 | 40 | func getLogFormat() (Format, error) { 41 | switch logFormat := strings.ToLower(viper.GetString(config.KeyLogFormat)); logFormat { 42 | case "auto": 43 | return FormatAuto, nil 44 | case "json", "zerolog": 45 | return FormatZerolog, nil 46 | case "human": 47 | return FormatHuman, nil 48 | default: 49 | return FormatAuto, fmt.Errorf("unsupported log format: %q", logFormat) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cmd/internal/logger/level.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package logger 10 | 11 | import ( 12 | "fmt" 13 | "strings" 14 | 15 | "github.com/joyent/triton-go/v2/cmd/internal/config" 16 | "github.com/rs/zerolog" 17 | "github.com/spf13/viper" 18 | ) 19 | 20 | type Level int 21 | 22 | const ( 23 | LevelBegin Level = iota - 2 24 | LevelDebug 25 | LevelInfo // Default, zero-initialized value 26 | LevelWarn 27 | LevelError 28 | LevelFatal 29 | 30 | LevelEnd 31 | ) 32 | 33 | func (f Level) String() string { 34 | switch f { 35 | case LevelDebug: 36 | return "debug" 37 | case LevelInfo: 38 | return "info" 39 | case LevelWarn: 40 | return "warn" 41 | case LevelError: 42 | return "error" 43 | case LevelFatal: 44 | return "fatal" 45 | default: 46 | panic(fmt.Sprintf("unknown log level: %d", f)) 47 | } 48 | } 49 | 50 | func logLevels() []Level { 51 | levels := make([]Level, 0, LevelEnd-LevelBegin) 52 | for i := LevelBegin + 1; i < LevelEnd; i++ { 53 | levels = append(levels, i) 54 | } 55 | 56 | return levels 57 | } 58 | 59 | func logLevelsStr() []string { 60 | intLevels := logLevels() 61 | levels := make([]string, 0, len(intLevels)) 62 | for _, lvl := range intLevels { 63 | levels = append(levels, lvl.String()) 64 | } 65 | return levels 66 | } 67 | 68 | func setLogLevel() (logLevel Level, err error) { 69 | switch strLevel := strings.ToLower(viper.GetString(config.KeyLogLevel)); strLevel { 70 | case "debug": 71 | zerolog.SetGlobalLevel(zerolog.DebugLevel) 72 | logLevel = LevelDebug 73 | case "info": 74 | zerolog.SetGlobalLevel(zerolog.InfoLevel) 75 | logLevel = LevelInfo 76 | case "warn": 77 | zerolog.SetGlobalLevel(zerolog.WarnLevel) 78 | logLevel = LevelWarn 79 | case "error": 80 | zerolog.SetGlobalLevel(zerolog.ErrorLevel) 81 | logLevel = LevelError 82 | case "fatal": 83 | zerolog.SetGlobalLevel(zerolog.FatalLevel) 84 | logLevel = LevelFatal 85 | default: 86 | return LevelDebug, fmt.Errorf("unsupported error level: %q (supported levels: %s)", logLevel, 87 | strings.Join(logLevelsStr(), " ")) 88 | } 89 | 90 | return logLevel, nil 91 | } 92 | -------------------------------------------------------------------------------- /cmd/internal/logger/pgx.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package logger 10 | 11 | import ( 12 | "github.com/jackc/pgx" 13 | "github.com/rs/zerolog" 14 | ) 15 | 16 | type PGX struct { 17 | l zerolog.Logger 18 | } 19 | 20 | func NewPGX(l zerolog.Logger) pgx.Logger { 21 | return &PGX{l: l} 22 | } 23 | 24 | func (l PGX) Log(level pgx.LogLevel, msg string, data map[string]interface{}) { 25 | switch level { 26 | case pgx.LogLevelDebug: 27 | l.l.Debug().Fields(data).Msg(msg) 28 | case pgx.LogLevelInfo: 29 | l.l.Info().Fields(data).Msg(msg) 30 | case pgx.LogLevelWarn: 31 | l.l.Warn().Fields(data).Msg(msg) 32 | case pgx.LogLevelError: 33 | l.l.Error().Fields(data).Msg(msg) 34 | default: 35 | l.l.Debug().Fields(data).Str("level", level.String()).Msg(msg) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/internal/logger/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package logger 10 | 11 | import ( 12 | "fmt" 13 | "io" 14 | "io/ioutil" 15 | stdlog "log" 16 | "os" 17 | "time" 18 | 19 | "github.com/joyent/triton-go/v2/cmd/internal/config" 20 | "github.com/mattn/go-isatty" 21 | "github.com/pkg/errors" 22 | "github.com/rs/zerolog" 23 | "github.com/rs/zerolog/log" 24 | "github.com/sean-/conswriter" 25 | "github.com/spf13/viper" 26 | ) 27 | 28 | const ( 29 | // Use a log format that resembles time.RFC3339Nano but includes all trailing 30 | // zeros so that we get fixed-width logging. 31 | logTimeFormat = "2006-01-02T15:04:05.000000000Z07:00" 32 | ) 33 | 34 | var stdLogger *stdlog.Logger 35 | 36 | func init() { 37 | // Initialize zerolog with a set set of defaults. Re-initialization of 38 | // logging with user-supplied configuration parameters happens in Setup(). 39 | 40 | // os.Stderr isn't guaranteed to be thread-safe, wrap in a sync writer. Files 41 | // are guaranteed to be safe, terminals are not. 42 | w := zerolog.ConsoleWriter{ 43 | Out: os.Stderr, 44 | NoColor: true, 45 | } 46 | zlog := zerolog.New(zerolog.SyncWriter(w)).With().Timestamp().Logger() 47 | 48 | zerolog.DurationFieldUnit = time.Microsecond 49 | zerolog.DurationFieldInteger = true 50 | zerolog.TimeFieldFormat = logTimeFormat 51 | zerolog.SetGlobalLevel(zerolog.InfoLevel) 52 | 53 | log.Logger = zlog 54 | 55 | stdlog.SetFlags(0) 56 | stdlog.SetOutput(zlog) 57 | } 58 | 59 | func Setup() error { 60 | logLevel, err := setLogLevel() 61 | if err != nil { 62 | return errors.Wrap(err, "unable to set log level") 63 | } 64 | 65 | var logWriter io.Writer 66 | if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) { 67 | logWriter = conswriter.GetTerminal() 68 | } else { 69 | logWriter = os.Stderr 70 | } 71 | 72 | logFmt, err := getLogFormat() 73 | if err != nil { 74 | return errors.Wrap(err, "unable to parse log format") 75 | } 76 | 77 | if logFmt == FormatAuto { 78 | if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) { 79 | logFmt = FormatHuman 80 | } else { 81 | logFmt = FormatZerolog 82 | } 83 | } 84 | 85 | var zlog zerolog.Logger 86 | switch logFmt { 87 | case FormatZerolog: 88 | zlog = zerolog.New(logWriter).With().Timestamp().Logger() 89 | case FormatHuman: 90 | useColor := viper.GetBool(config.KeyLogTermColor) 91 | w := zerolog.ConsoleWriter{ 92 | Out: logWriter, 93 | NoColor: !useColor, 94 | } 95 | zlog = zerolog.New(w).With().Timestamp().Logger() 96 | default: 97 | return fmt.Errorf("unsupported log format: %q", logFmt) 98 | } 99 | 100 | log.Logger = zlog 101 | 102 | stdlog.SetFlags(0) 103 | stdlog.SetOutput(zlog) 104 | stdLogger = &stdlog.Logger{} 105 | 106 | // In order to prevent random libraries from hooking the standard logger and 107 | // filling the logger with garbage, discard all log entries. At debug level, 108 | // however, let it all through. 109 | if logLevel != LevelDebug { 110 | stdLogger.SetOutput(ioutil.Discard) 111 | } else { 112 | stdLogger.SetOutput(zlog) 113 | } 114 | 115 | return nil 116 | } 117 | -------------------------------------------------------------------------------- /cmd/manta/cmd/docs/man/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package man 10 | 11 | import ( 12 | "fmt" 13 | "os" 14 | "path" 15 | "strconv" 16 | "strings" 17 | 18 | "github.com/joyent/triton-go/v2" 19 | "github.com/joyent/triton-go/v2/cmd/internal/command" 20 | "github.com/joyent/triton-go/v2/cmd/internal/config" 21 | "github.com/pkg/errors" 22 | "github.com/rs/zerolog/log" 23 | "github.com/spf13/cobra" 24 | "github.com/spf13/cobra/doc" 25 | "github.com/spf13/viper" 26 | ) 27 | 28 | var Cmd = &command.Command{ 29 | Cobra: &cobra.Command{ 30 | Use: "man", 31 | Short: "Generates and installs Joyent Manta cli man pages", 32 | Long: `This command automatically generates up-to-date man pages of Manta CLI 33 | command-line interface. By default, it creates the man page files 34 | in the "docs/man" directory under the current directory.`, 35 | 36 | PreRunE: func(cmd *cobra.Command, args []string) error { 37 | return nil 38 | }, 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | header := &doc.GenManHeader{ 41 | Manual: "Manta", 42 | Section: strconv.Itoa(config.ManSect), 43 | Source: strings.Join([]string{"Manta", triton.Version}, " "), 44 | } 45 | 46 | manDir := viper.GetString(config.KeyDocManDir) 47 | 48 | manSectDir := path.Join(manDir, fmt.Sprintf("man%d", config.ManSect)) 49 | if _, err := os.Stat(manSectDir); os.IsNotExist(err) { 50 | if err := os.MkdirAll(manSectDir, 0777); err != nil { 51 | return errors.Wrapf(err, "unable to make mandir %q", manSectDir) 52 | } 53 | } 54 | 55 | cmd.Root().DisableAutoGenTag = true 56 | log.Info().Str("MANDIR", manDir).Int("section", config.ManSect).Msg("Installing man(1) pages") 57 | 58 | err := doc.GenManTree(cmd.Root(), header, manSectDir) 59 | if err != nil { 60 | return errors.Wrap(err, "unable to generate man(1) pages") 61 | } 62 | 63 | log.Info().Msg("Installation completed successfully.") 64 | 65 | return nil 66 | }, 67 | }, 68 | Setup: func(parent *command.Command) error { 69 | 70 | { 71 | const ( 72 | key = config.KeyDocManDir 73 | longName = "man-dir" 74 | shortName = "m" 75 | description = "Specify the MANDIR to use" 76 | defaultValue = config.DefaultManDir 77 | ) 78 | 79 | flags := parent.Cobra.PersistentFlags() 80 | flags.StringP(longName, shortName, defaultValue, description) 81 | viper.BindPFlag(key, flags.Lookup(longName)) 82 | viper.BindEnv(key, "MANDIR") 83 | viper.SetDefault(key, defaultValue) 84 | } 85 | 86 | return nil 87 | }, 88 | } 89 | -------------------------------------------------------------------------------- /cmd/manta/cmd/docs/md/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package md 10 | 11 | import ( 12 | "fmt" 13 | "os" 14 | "path" 15 | "path/filepath" 16 | "strings" 17 | "time" 18 | 19 | "github.com/joyent/triton-go/v2/cmd/internal/command" 20 | "github.com/joyent/triton-go/v2/cmd/internal/config" 21 | "github.com/pkg/errors" 22 | "github.com/rs/zerolog/log" 23 | "github.com/spf13/cobra" 24 | "github.com/spf13/cobra/doc" 25 | "github.com/spf13/viper" 26 | ) 27 | 28 | const template = `--- 29 | date: %s 30 | title: "%s" 31 | slug: %s 32 | url: %s 33 | --- 34 | ` 35 | 36 | var Cmd = &command.Command{ 37 | Cobra: &cobra.Command{ 38 | Use: "doc", 39 | Short: "Generates and installs Joyent Manta cli documentation in markdown", 40 | Long: `Generate Markdown documentation for the Manta CLI. 41 | 42 | It creates one Markdown file per command `, 43 | 44 | PreRunE: func(cmd *cobra.Command, args []string) error { 45 | return nil 46 | }, 47 | RunE: func(cmd *cobra.Command, args []string) error { 48 | mdDir := viper.GetString(config.KeyDocMarkdownDir) 49 | 50 | if _, err := os.Stat(mdDir); os.IsNotExist(err) { 51 | if err := os.MkdirAll(mdDir, 0777); err != nil { 52 | return errors.Wrapf(err, "unable to make mddir %q", mdDir) 53 | } 54 | } 55 | 56 | log.Info().Str(config.KeyDocMarkdownDir, mdDir).Msg("Installing markdown documentation") 57 | 58 | now := time.Now().UTC().Format(time.RFC3339) 59 | prefix := viper.GetString(config.KeyDocMarkdownURLPrefix) 60 | prepender := func(filename string) string { 61 | name := filepath.Base(filename) 62 | base := strings.TrimSuffix(name, path.Ext(name)) 63 | url := prefix + path.Join("/", strings.ToLower(base), "/") 64 | return fmt.Sprintf(template, now, strings.Replace(base, "_", " ", -1), base, url) 65 | } 66 | 67 | linkHandler := func(name string) string { 68 | base := strings.TrimSuffix(name, path.Ext(name)) 69 | return prefix + path.Join("/", strings.ToLower(base), "/") 70 | } 71 | 72 | doc.GenMarkdownTreeCustom(cmd.Root(), mdDir, prepender, linkHandler) 73 | 74 | log.Info().Msg("Installation completed successfully.") 75 | 76 | return nil 77 | }, 78 | }, 79 | Setup: func(parent *command.Command) error { 80 | 81 | { 82 | const ( 83 | key = config.KeyDocMarkdownDir 84 | longName = "dir" 85 | shortName = "d" 86 | description = "Specify the directory for generated Markdown files" 87 | defaultValue = config.DefaultMarkdownDir 88 | ) 89 | 90 | flags := parent.Cobra.Flags() 91 | flags.StringP(longName, shortName, defaultValue, description) 92 | viper.BindPFlag(key, flags.Lookup(longName)) 93 | viper.SetDefault(key, defaultValue) 94 | } 95 | 96 | { 97 | const ( 98 | key = config.KeyDocMarkdownURLPrefix 99 | longName = "url-prefix" 100 | shortName = "p" 101 | description = "Specify the prefix for links generated by Markdown" 102 | defaultValue = config.DefaultMarkdownURLPrefix 103 | ) 104 | 105 | flags := parent.Cobra.Flags() 106 | flags.String(longName, defaultValue, description) 107 | viper.BindPFlag(key, flags.Lookup(longName)) 108 | viper.SetDefault(key, defaultValue) 109 | } 110 | 111 | return nil 112 | }, 113 | } 114 | -------------------------------------------------------------------------------- /cmd/manta/cmd/docs/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package docs 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/manta/cmd/docs/man" 14 | "github.com/joyent/triton-go/v2/cmd/manta/cmd/docs/md" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var Cmd = &command.Command{ 19 | Cobra: &cobra.Command{ 20 | Use: "doc", 21 | Aliases: []string{"docs", "documentation"}, 22 | Short: "Documentation for Joyent Manta cli", 23 | }, 24 | 25 | Setup: func(parent *command.Command) error { 26 | cmds := []*command.Command{ 27 | man.Cmd, 28 | md.Cmd, 29 | } 30 | 31 | for _, cmd := range cmds { 32 | cmd.Setup(cmd) 33 | parent.Cobra.AddCommand(cmd.Cobra) 34 | } 35 | 36 | return nil 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /cmd/manta/cmd/list/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package list 10 | 11 | import ( 12 | "fmt" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/agent/storage" 15 | cfg "github.com/joyent/triton-go/v2/cmd/config" 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | "github.com/sean-/conswriter" 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | var Cmd = &command.Command{ 22 | Cobra: &cobra.Command{ 23 | Args: cobra.MaximumNArgs(3), 24 | Use: "ls", 25 | Short: "list directory contents", 26 | SilenceUsage: true, 27 | Example: ` 28 | $ manta ls 29 | $ manta ls /stor 30 | `, 31 | PreRunE: func(cmd *cobra.Command, args []string) error { 32 | return nil 33 | }, 34 | RunE: func(cmd *cobra.Command, args []string) error { 35 | cons := conswriter.GetTerminal() 36 | 37 | c, err := cfg.NewMantaConfig() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | s, err := storage.NewStorageClient(c) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | directoryOutput, err := s.GetDirectoryListing(args) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | cons.Write([]byte(fmt.Sprintf("Found %d directory entries", directoryOutput.ResultSetSize))) 53 | 54 | for _, entry := range directoryOutput.Entries { 55 | cons.Write([]byte(fmt.Sprintf("\n%s/", entry.Name))) 56 | } 57 | 58 | return nil 59 | }, 60 | }, 61 | Setup: func(parent *command.Command) error { 62 | return nil 63 | }, 64 | } 65 | -------------------------------------------------------------------------------- /cmd/manta/cmd/shell/autocomplete/bash/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package bash 10 | 11 | import ( 12 | "fmt" 13 | "os" 14 | 15 | "github.com/joyent/triton-go/v2/cmd/internal/command" 16 | "github.com/joyent/triton-go/v2/cmd/internal/config" 17 | "github.com/pkg/errors" 18 | "github.com/rs/zerolog/log" 19 | "github.com/spf13/cobra" 20 | "github.com/spf13/viper" 21 | ) 22 | 23 | var Cmd = &command.Command{ 24 | Cobra: &cobra.Command{ 25 | Use: "bash", 26 | Short: "Generates shell autocompletion file for Joyent Manta CLI", 27 | Long: `Generates a shell autocompletion script for Joyent Manta CLI. 28 | 29 | By default, the file is written directly to /etc/bash_completion.d 30 | for convenience, and the command may need superuser rights, e.g.: 31 | 32 | $ sudo manta shell autocomplete bash 33 | 34 | Add ` + "`--bash-autocomplete-dir=/path/to/file`" + ` flag to set alternative 35 | folder location. 36 | 37 | Logout and in again to reload the completion scripts, 38 | or just source them in directly: 39 | 40 | $ . /etc/bash_completion`, 41 | 42 | PreRunE: func(cmd *cobra.Command, args []string) error { 43 | return nil 44 | }, 45 | RunE: func(cmd *cobra.Command, args []string) error { 46 | target := viper.GetString(config.KeyBashAutoCompletionTarget) 47 | if _, err := os.Stat(target); os.IsNotExist(err) { 48 | if err := os.MkdirAll(target, 0777); err != nil { 49 | return errors.Wrapf(err, "unable to make bash-autocomplete-target %q", target) 50 | } 51 | } 52 | bashFile := fmt.Sprintf("%s/triton.sh", target) 53 | err := cmd.Root().GenBashCompletionFile(bashFile) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | log.Info().Msg("Installation completed successfully.") 59 | 60 | return nil 61 | }, 62 | }, 63 | Setup: func(parent *command.Command) error { 64 | 65 | { 66 | const ( 67 | key = config.KeyBashAutoCompletionTarget 68 | longName = "bash-autocomplete-dir" 69 | defaultValue = "/etc/bash_completion.d" 70 | description = "autocompletion directory" 71 | ) 72 | 73 | flags := parent.Cobra.PersistentFlags() 74 | flags.String(longName, defaultValue, description) 75 | viper.BindPFlag(key, flags.Lookup(longName)) 76 | } 77 | 78 | return nil 79 | }, 80 | } 81 | -------------------------------------------------------------------------------- /cmd/manta/cmd/shell/autocomplete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package autocomplete 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/manta/cmd/shell/autocomplete/bash" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | var Cmd = &command.Command{ 18 | Cobra: &cobra.Command{ 19 | Use: "autocomplete", 20 | Short: "Autocompletion generation", 21 | }, 22 | 23 | Setup: func(parent *command.Command) error { 24 | cmds := []*command.Command{ 25 | bash.Cmd, 26 | } 27 | 28 | for _, cmd := range cmds { 29 | cmd.Setup(cmd) 30 | parent.Cobra.AddCommand(cmd.Cobra) 31 | } 32 | 33 | return nil 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /cmd/manta/cmd/shell/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package shell 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/manta/cmd/shell/autocomplete" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | var Cmd = &command.Command{ 18 | Cobra: &cobra.Command{ 19 | Use: "shell", 20 | Short: "shell commands", 21 | }, 22 | 23 | Setup: func(parent *command.Command) error { 24 | cmds := []*command.Command{ 25 | autocomplete.Cmd, 26 | } 27 | 28 | for _, cmd := range cmds { 29 | cmd.Setup(cmd) 30 | parent.Cobra.AddCommand(cmd.Cobra) 31 | } 32 | 33 | return nil 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /cmd/manta/cmd/version/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package version 10 | 11 | import ( 12 | "fmt" 13 | 14 | triton "github.com/joyent/triton-go/v2" 15 | "github.com/joyent/triton-go/v2/cmd/internal/command" 16 | "github.com/sean-/conswriter" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var Cmd = &command.Command{ 21 | Cobra: &cobra.Command{ 22 | Use: "version", 23 | Short: "print Joyent manta cli version", 24 | SilenceUsage: true, 25 | 26 | RunE: func(cmd *cobra.Command, args []string) error { 27 | cons := conswriter.GetTerminal() 28 | cons.Write([]byte(fmt.Sprintf("Version: %s\n", triton.UserAgent()))) 29 | return nil 30 | }, 31 | }, 32 | Setup: func(parent *command.Command) error { 33 | return nil 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /cmd/manta/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "os" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/manta/cmd" 15 | "github.com/rs/zerolog/log" 16 | "github.com/sean-/conswriter" 17 | ) 18 | 19 | func main() { 20 | defer func() { 21 | p := conswriter.GetTerminal() 22 | p.Wait() 23 | }() 24 | 25 | if err := cmd.Execute(); err != nil { 26 | log.Error().Err(err).Msg("unable to run") 27 | os.Exit(1) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/create/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package create 10 | 11 | import ( 12 | "fmt" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/agent/account" 15 | cfg "github.com/joyent/triton-go/v2/cmd/config" 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | "github.com/joyent/triton-go/v2/cmd/internal/config" 18 | "github.com/sean-/conswriter" 19 | "github.com/spf13/cobra" 20 | "github.com/spf13/viper" 21 | ) 22 | 23 | var Cmd = &command.Command{ 24 | Cobra: &cobra.Command{ 25 | Args: cobra.NoArgs, 26 | Use: "create", 27 | Aliases: []string{"add"}, 28 | Short: "create Triton Access Key", 29 | SilenceUsage: true, 30 | PreRunE: func(cmd *cobra.Command, args []string) error { 31 | return nil 32 | }, 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | cons := conswriter.GetTerminal() 35 | 36 | c, err := cfg.NewTritonConfig() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | a, err := account.NewAccountClient(c) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | accesskey, err := a.CreateAccessKey() 47 | if err != nil { 48 | return err 49 | } 50 | 51 | cons.Write([]byte(fmt.Sprintf("Created access key %q", accesskey.AccessKeyID))) 52 | 53 | return nil 54 | }, 55 | }, 56 | Setup: func(parent *command.Command) error { 57 | 58 | { 59 | const ( 60 | key = config.KeyAccessKeyID 61 | longName = "accesskeyid" 62 | defaultValue = "" 63 | description = "Access Key Identifier" 64 | ) 65 | 66 | flags := parent.Cobra.Flags() 67 | flags.String(longName, defaultValue, description) 68 | viper.BindPFlag(key, flags.Lookup(longName)) 69 | } 70 | 71 | return nil 72 | }, 73 | } 74 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/delete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package accesskeydelete 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | 15 | "github.com/joyent/triton-go/v2/cmd/agent/account" 16 | cfg "github.com/joyent/triton-go/v2/cmd/config" 17 | "github.com/joyent/triton-go/v2/cmd/internal/command" 18 | "github.com/sean-/conswriter" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | var Cmd = &command.Command{ 23 | Cobra: &cobra.Command{ 24 | Args: cobra.NoArgs, 25 | Use: "delete", 26 | Short: "delete Triton Access Key", 27 | SilenceUsage: true, 28 | PreRunE: func(cmd *cobra.Command, args []string) error { 29 | if cfg.GetAccessKeyID() == "" { 30 | return errors.New("`accesskeyid` must be specified") 31 | } 32 | 33 | return nil 34 | }, 35 | RunE: func(cmd *cobra.Command, args []string) error { 36 | cons := conswriter.GetTerminal() 37 | 38 | c, err := cfg.NewTritonConfig() 39 | if err != nil { 40 | return err 41 | } 42 | 43 | a, err := account.NewAccountClient(c) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | accesskey, err := a.DeleteAccessKey() 49 | if err != nil { 50 | return err 51 | } 52 | 53 | cons.Write([]byte(fmt.Sprintf("Deleted access key %q", accesskey.AccessKeyID))) 54 | 55 | return nil 56 | }, 57 | }, 58 | Setup: func(parent *command.Command) error { 59 | return nil 60 | }, 61 | } 62 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package get 10 | 11 | import ( 12 | "errors" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/agent/account" 15 | cfg "github.com/joyent/triton-go/v2/cmd/config" 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | "github.com/sean-/conswriter" 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | var Cmd = &command.Command{ 22 | Cobra: &cobra.Command{ 23 | Args: cobra.NoArgs, 24 | Use: "get", 25 | Short: "get Triton Access Key", 26 | SilenceUsage: true, 27 | PreRunE: func(cmd *cobra.Command, args []string) error { 28 | if cfg.GetAccessKeyID() == "" { 29 | return errors.New("`accesskeyid` must be specified") 30 | } 31 | 32 | return nil 33 | }, 34 | RunE: func(cmd *cobra.Command, args []string) error { 35 | cons := conswriter.GetTerminal() 36 | 37 | c, err := cfg.NewTritonConfig() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | a, err := account.NewAccountClient(c) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | accesskey, err := a.GetAccessKey() 48 | if err != nil { 49 | return err 50 | } 51 | cons.Write([]byte(accesskey.SecretAccessKey)) 52 | 53 | return nil 54 | }, 55 | }, 56 | Setup: func(parent *command.Command) error { 57 | return nil 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/list/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package list 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/agent/account" 13 | cfg "github.com/joyent/triton-go/v2/cmd/config" 14 | "github.com/joyent/triton-go/v2/cmd/internal/command" 15 | "github.com/olekukonko/tablewriter" 16 | "github.com/sean-/conswriter" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var Cmd = &command.Command{ 21 | Cobra: &cobra.Command{ 22 | Args: cobra.NoArgs, 23 | Use: "list", 24 | Short: "list Triton Access Keys", 25 | Aliases: []string{"ls"}, 26 | SilenceUsage: true, 27 | PreRunE: func(cmd *cobra.Command, args []string) error { 28 | return nil 29 | }, 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | cons := conswriter.GetTerminal() 32 | 33 | c, err := cfg.NewTritonConfig() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | a, err := account.NewAccountClient(c) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | accesskeys, err := a.ListAccessKeys() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | table := tablewriter.NewWriter(cons) 49 | table.SetHeaderAlignment(tablewriter.ALIGN_CENTER) 50 | table.SetHeaderLine(false) 51 | table.SetAutoFormatHeaders(true) 52 | 53 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) 54 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 55 | table.SetCenterSeparator("") 56 | table.SetColumnSeparator("") 57 | table.SetRowSeparator("") 58 | 59 | table.SetHeader([]string{"ID", "SECRET"}) 60 | 61 | for _, accesskey := range accesskeys { 62 | table.Append([]string{accesskey.AccessKeyID, accesskey.SecretAccessKey}) 63 | } 64 | 65 | table.Render() 66 | 67 | return nil 68 | }, 69 | }, 70 | Setup: func(parent *command.Command) error { 71 | return nil 72 | }, 73 | } 74 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package accesskeys 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/internal/config" 14 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/accesskeys/create" 15 | accessKeyDelete "github.com/joyent/triton-go/v2/cmd/triton/cmd/accesskeys/delete" 16 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/accesskeys/get" 17 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/accesskeys/list" 18 | "github.com/spf13/cobra" 19 | "github.com/spf13/viper" 20 | ) 21 | 22 | var Cmd = &command.Command{ 23 | Cobra: &cobra.Command{ 24 | Use: "accesskeys", 25 | Aliases: []string{"accesskey"}, 26 | Short: "List and manage Triton Access Keys.", 27 | }, 28 | 29 | Setup: func(parent *command.Command) error { 30 | 31 | cmds := []*command.Command{ 32 | list.Cmd, 33 | get.Cmd, 34 | accessKeyDelete.Cmd, 35 | create.Cmd, 36 | } 37 | 38 | for _, cmd := range cmds { 39 | cmd.Setup(cmd) 40 | parent.Cobra.AddCommand(cmd.Cobra) 41 | } 42 | 43 | { 44 | const ( 45 | key = config.KeyAccessKeyID 46 | longName = "accesskeyid" 47 | defaultValue = "" 48 | description = "Access Key Identifier" 49 | ) 50 | 51 | flags := parent.Cobra.PersistentFlags() 52 | flags.String(longName, defaultValue, description) 53 | viper.BindPFlag(key, flags.Lookup(longName)) 54 | } 55 | 56 | return nil 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /cmd/triton/cmd/account/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package get 10 | 11 | import ( 12 | "fmt" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/agent/account" 15 | cfg "github.com/joyent/triton-go/v2/cmd/config" 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | "github.com/sean-/conswriter" 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | var Cmd = &command.Command{ 22 | Cobra: &cobra.Command{ 23 | Args: cobra.NoArgs, 24 | Use: "get", 25 | Short: "Show account information", 26 | SilenceUsage: true, 27 | PreRunE: func(cmd *cobra.Command, args []string) error { 28 | return nil 29 | }, 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | cons := conswriter.GetTerminal() 32 | 33 | c, err := cfg.NewTritonConfig() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | a, err := account.NewAccountClient(c) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | accDetails, err := a.Get() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | cons.Write([]byte(fmt.Sprintf("id: %s", accDetails.ID))) 49 | cons.Write([]byte(fmt.Sprintf("\nlogin: %s", accDetails.Login))) 50 | cons.Write([]byte(fmt.Sprintf("\nemail: %s", accDetails.Email))) 51 | cons.Write([]byte(fmt.Sprintf("\ncompanyName: %s", accDetails.CompanyName))) 52 | cons.Write([]byte(fmt.Sprintf("\nfirstName: %s", accDetails.FirstName))) 53 | cons.Write([]byte(fmt.Sprintf("\nlastName: %s", accDetails.LastName))) 54 | cons.Write([]byte(fmt.Sprintf("\npostalCode: %s", accDetails.PostalCode))) 55 | cons.Write([]byte(fmt.Sprintf("\ntriton_cns_enabled: %t", accDetails.TritonCNSEnabled))) 56 | cons.Write([]byte(fmt.Sprintf("\naddress: %s", accDetails.Address))) 57 | cons.Write([]byte(fmt.Sprintf("\ncity: %s", accDetails.City))) 58 | cons.Write([]byte(fmt.Sprintf("\nstate: %s", accDetails.State))) 59 | cons.Write([]byte(fmt.Sprintf("\ncountry: %s", accDetails.Country))) 60 | cons.Write([]byte(fmt.Sprintf("\nphone: %s", accDetails.Phone))) 61 | cons.Write([]byte(fmt.Sprintf("\nupdated: %s (%s)", accDetails.Updated.String(), cfg.FormatTime(accDetails.Updated)))) 62 | cons.Write([]byte(fmt.Sprintf("\ncreated: %s (%s)", accDetails.Created.String(), cfg.FormatTime(accDetails.Created)))) 63 | 64 | return nil 65 | }, 66 | }, 67 | 68 | Setup: func(parent *command.Command) error { 69 | return nil 70 | }, 71 | } 72 | -------------------------------------------------------------------------------- /cmd/triton/cmd/account/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package account 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/account/get" 14 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/account/update" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var Cmd = &command.Command{ 19 | Cobra: &cobra.Command{ 20 | Use: "account", 21 | Short: "Get and update your Triton account", 22 | }, 23 | 24 | Setup: func(parent *command.Command) error { 25 | cmds := []*command.Command{ 26 | get.Cmd, 27 | update.Cmd, 28 | } 29 | 30 | for _, cmd := range cmds { 31 | cmd.Setup(cmd) 32 | parent.Cobra.AddCommand(cmd.Cobra) 33 | } 34 | 35 | return nil 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /cmd/triton/cmd/datacenters/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package datacenters 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 13 | cfg "github.com/joyent/triton-go/v2/cmd/config" 14 | "github.com/joyent/triton-go/v2/cmd/internal/command" 15 | "github.com/olekukonko/tablewriter" 16 | "github.com/sean-/conswriter" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var Cmd = &command.Command{ 21 | Cobra: &cobra.Command{ 22 | Args: cobra.NoArgs, 23 | Use: "datacenters", 24 | Short: "Show datacenters in this cloud", 25 | SilenceUsage: true, 26 | PreRunE: func(cmd *cobra.Command, args []string) error { 27 | return nil 28 | }, 29 | RunE: func(cmd *cobra.Command, args []string) error { 30 | cons := conswriter.GetTerminal() 31 | 32 | c, err := cfg.NewTritonConfig() 33 | if err != nil { 34 | return err 35 | } 36 | 37 | a, err := compute.NewComputeClient(c) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | dcs, err := a.GetDataCenterList() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | table := tablewriter.NewWriter(cons) 48 | table.SetHeaderAlignment(tablewriter.ALIGN_CENTER) 49 | table.SetHeaderLine(false) 50 | table.SetAutoFormatHeaders(true) 51 | 52 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) 53 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 54 | table.SetCenterSeparator("") 55 | table.SetColumnSeparator("") 56 | table.SetRowSeparator("") 57 | 58 | table.SetHeader([]string{"NAME", "URL"}) 59 | 60 | for _, dc := range dcs { 61 | table.Append([]string{dc.Name, dc.URL}) 62 | } 63 | 64 | table.Render() 65 | 66 | return nil 67 | }, 68 | }, 69 | Setup: func(parent *command.Command) error { 70 | return nil 71 | }, 72 | } 73 | -------------------------------------------------------------------------------- /cmd/triton/cmd/docs/man/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package man 10 | 11 | import ( 12 | "fmt" 13 | "os" 14 | "strconv" 15 | 16 | "path" 17 | "strings" 18 | 19 | "github.com/joyent/triton-go/v2" 20 | "github.com/joyent/triton-go/v2/cmd/internal/command" 21 | "github.com/joyent/triton-go/v2/cmd/internal/config" 22 | "github.com/pkg/errors" 23 | "github.com/rs/zerolog/log" 24 | "github.com/spf13/cobra" 25 | "github.com/spf13/cobra/doc" 26 | "github.com/spf13/viper" 27 | ) 28 | 29 | var Cmd = &command.Command{ 30 | Cobra: &cobra.Command{ 31 | Use: "man", 32 | Short: "Generates and installs triton cli man pages", 33 | Long: `This command automatically generates up-to-date man pages of Triton CLI 34 | command-line interface. By default, it creates the man page files 35 | in the "docs/man" directory under the current directory.`, 36 | 37 | PreRunE: func(cmd *cobra.Command, args []string) error { 38 | return nil 39 | }, 40 | RunE: func(cmd *cobra.Command, args []string) error { 41 | header := &doc.GenManHeader{ 42 | Manual: "Triton", 43 | Section: strconv.Itoa(config.ManSect), 44 | Source: strings.Join([]string{"Triton", triton.Version}, " "), 45 | } 46 | 47 | manDir := viper.GetString(config.KeyDocManDir) 48 | 49 | manSectDir := path.Join(manDir, fmt.Sprintf("man%d", config.ManSect)) 50 | if _, err := os.Stat(manSectDir); os.IsNotExist(err) { 51 | if err := os.MkdirAll(manSectDir, 0777); err != nil { 52 | return errors.Wrapf(err, "unable to make mandir %q", manSectDir) 53 | } 54 | } 55 | 56 | cmd.Root().DisableAutoGenTag = true 57 | log.Info().Str("MANDIR", manDir).Int("section", config.ManSect).Msg("Installing man(1) pages") 58 | 59 | err := doc.GenManTree(cmd.Root(), header, manSectDir) 60 | if err != nil { 61 | return errors.Wrap(err, "unable to generate man(1) pages") 62 | } 63 | 64 | log.Info().Msg("Installation completed successfully.") 65 | 66 | return nil 67 | }, 68 | }, 69 | Setup: func(parent *command.Command) error { 70 | 71 | { 72 | const ( 73 | key = config.KeyDocManDir 74 | longName = "man-dir" 75 | shortName = "m" 76 | description = "Specify the MANDIR to use" 77 | defaultValue = config.DefaultManDir 78 | ) 79 | 80 | flags := parent.Cobra.PersistentFlags() 81 | flags.StringP(longName, shortName, defaultValue, description) 82 | viper.BindPFlag(key, flags.Lookup(longName)) 83 | viper.BindEnv(key, "MANDIR") 84 | viper.SetDefault(key, defaultValue) 85 | } 86 | 87 | return nil 88 | }, 89 | } 90 | -------------------------------------------------------------------------------- /cmd/triton/cmd/docs/md/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package md 10 | 11 | import ( 12 | "fmt" 13 | "os" 14 | "path" 15 | "path/filepath" 16 | "strings" 17 | "time" 18 | 19 | "github.com/joyent/triton-go/v2/cmd/internal/command" 20 | "github.com/joyent/triton-go/v2/cmd/internal/config" 21 | "github.com/pkg/errors" 22 | "github.com/rs/zerolog/log" 23 | "github.com/spf13/cobra" 24 | "github.com/spf13/cobra/doc" 25 | "github.com/spf13/viper" 26 | ) 27 | 28 | const template = `--- 29 | date: %s 30 | title: "%s" 31 | slug: %s 32 | url: %s 33 | --- 34 | ` 35 | 36 | var Cmd = &command.Command{ 37 | Cobra: &cobra.Command{ 38 | Use: "doc", 39 | Short: "Generates and installs triton cli documentation in markdown", 40 | Long: `Generate Markdown documentation for the Triton CLI. 41 | 42 | It creates one Markdown file per command `, 43 | 44 | PreRunE: func(cmd *cobra.Command, args []string) error { 45 | return nil 46 | }, 47 | RunE: func(cmd *cobra.Command, args []string) error { 48 | mdDir := viper.GetString(config.KeyDocMarkdownDir) 49 | 50 | if _, err := os.Stat(mdDir); os.IsNotExist(err) { 51 | if err := os.MkdirAll(mdDir, 0777); err != nil { 52 | return errors.Wrapf(err, "unable to make mddir %q", mdDir) 53 | } 54 | } 55 | 56 | log.Info().Str(config.KeyDocMarkdownDir, mdDir).Msg("Installing markdown documentation") 57 | 58 | now := time.Now().UTC().Format(time.RFC3339) 59 | prefix := viper.GetString(config.KeyDocMarkdownURLPrefix) 60 | prepender := func(filename string) string { 61 | name := filepath.Base(filename) 62 | base := strings.TrimSuffix(name, path.Ext(name)) 63 | url := prefix + path.Join("/", strings.ToLower(base), "/") 64 | return fmt.Sprintf(template, now, strings.Replace(base, "_", " ", -1), base, url) 65 | } 66 | 67 | linkHandler := func(name string) string { 68 | base := strings.TrimSuffix(name, path.Ext(name)) 69 | return prefix + path.Join("/", strings.ToLower(base), "/") 70 | } 71 | 72 | doc.GenMarkdownTreeCustom(cmd.Root(), mdDir, prepender, linkHandler) 73 | 74 | log.Info().Msg("Installation completed successfully.") 75 | 76 | return nil 77 | }, 78 | }, 79 | Setup: func(parent *command.Command) error { 80 | 81 | { 82 | const ( 83 | key = config.KeyDocMarkdownDir 84 | longName = "dir" 85 | shortName = "d" 86 | description = "Specify the directory for generated Markdown files" 87 | defaultValue = config.DefaultMarkdownDir 88 | ) 89 | 90 | flags := parent.Cobra.Flags() 91 | flags.StringP(longName, shortName, defaultValue, description) 92 | viper.BindPFlag(key, flags.Lookup(longName)) 93 | viper.SetDefault(key, defaultValue) 94 | } 95 | 96 | { 97 | const ( 98 | key = config.KeyDocMarkdownURLPrefix 99 | longName = "url-prefix" 100 | shortName = "p" 101 | description = "Specify the prefix for links generated by Markdown" 102 | defaultValue = config.DefaultMarkdownURLPrefix 103 | ) 104 | 105 | flags := parent.Cobra.Flags() 106 | flags.String(longName, defaultValue, description) 107 | viper.BindPFlag(key, flags.Lookup(longName)) 108 | viper.SetDefault(key, defaultValue) 109 | } 110 | 111 | return nil 112 | }, 113 | } 114 | -------------------------------------------------------------------------------- /cmd/triton/cmd/docs/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package docs 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/docs/man" 14 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/docs/md" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var Cmd = &command.Command{ 19 | Cobra: &cobra.Command{ 20 | Use: "doc", 21 | Aliases: []string{"docs", "documentation"}, 22 | Short: "Documentation for Triton cli", 23 | }, 24 | 25 | Setup: func(parent *command.Command) error { 26 | cmds := []*command.Command{ 27 | man.Cmd, 28 | md.Cmd, 29 | } 30 | 31 | for _, cmd := range cmds { 32 | cmd.Setup(cmd) 33 | parent.Cobra.AddCommand(cmd.Cobra) 34 | } 35 | 36 | return nil 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/count/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package count 10 | 11 | import ( 12 | "fmt" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 15 | cfg "github.com/joyent/triton-go/v2/cmd/config" 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | "github.com/sean-/conswriter" 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | var Cmd = &command.Command{ 22 | Cobra: &cobra.Command{ 23 | Args: cobra.NoArgs, 24 | Use: "count", 25 | Short: "count triton instances", 26 | SilenceUsage: true, 27 | PreRunE: func(cmd *cobra.Command, args []string) error { 28 | return nil 29 | }, 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | cons := conswriter.GetTerminal() 32 | 33 | c, err := cfg.NewTritonConfig() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | a, err := compute.NewComputeClient(c) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | instances, err := a.CountInstanceList() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | cons.Write([]byte(fmt.Sprintf("Found %d instances", instances))) 49 | 50 | return nil 51 | }, 52 | }, 53 | Setup: func(parent *command.Command) error { 54 | return nil 55 | }, 56 | } 57 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/delete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package delete 10 | 11 | import ( 12 | "errors" 13 | 14 | "fmt" 15 | 16 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 17 | cfg "github.com/joyent/triton-go/v2/cmd/config" 18 | "github.com/joyent/triton-go/v2/cmd/internal/command" 19 | "github.com/sean-/conswriter" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | var Cmd = &command.Command{ 24 | Cobra: &cobra.Command{ 25 | Args: cobra.NoArgs, 26 | Use: "delete", 27 | Short: "delete instance", 28 | SilenceUsage: true, 29 | PreRunE: func(cmd *cobra.Command, args []string) error { 30 | if cfg.GetMachineID() == "" && cfg.GetMachineName() == "" { 31 | return errors.New("Either `id` or `name` must be specified") 32 | } 33 | 34 | if cfg.GetMachineID() != "" && cfg.GetMachineName() != "" { 35 | return errors.New("Only 1 of `id` or `name` must be specified") 36 | } 37 | 38 | return nil 39 | }, 40 | RunE: func(cmd *cobra.Command, args []string) error { 41 | cons := conswriter.GetTerminal() 42 | 43 | c, err := cfg.NewTritonConfig() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | a, err := compute.NewComputeClient(c) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | instance, err := a.DeleteInstance() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | cons.Write([]byte(fmt.Sprintf("Deleted instance (async) %q", instance.Name))) 59 | 60 | return nil 61 | }, 62 | }, 63 | Setup: func(parent *command.Command) error { 64 | return nil 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package get 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | 15 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 16 | cfg "github.com/joyent/triton-go/v2/cmd/config" 17 | "github.com/joyent/triton-go/v2/cmd/internal/command" 18 | "github.com/olekukonko/tablewriter" 19 | "github.com/sean-/conswriter" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | var Cmd = &command.Command{ 24 | Cobra: &cobra.Command{ 25 | Args: cobra.NoArgs, 26 | Use: "get", 27 | Short: "get a triton instance", 28 | SilenceUsage: true, 29 | PreRunE: func(cmd *cobra.Command, args []string) error { 30 | if cfg.GetMachineID() == "" && cfg.GetMachineName() == "" { 31 | return errors.New("Either `id` or `name` must be specified") 32 | } 33 | 34 | if cfg.GetMachineID() != "" && cfg.GetMachineName() != "" { 35 | return errors.New("Only 1 of `id` or `name` must be specified") 36 | } 37 | 38 | return nil 39 | }, 40 | RunE: func(cmd *cobra.Command, args []string) error { 41 | cons := conswriter.GetTerminal() 42 | 43 | c, err := cfg.NewTritonConfig() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | a, err := compute.NewComputeClient(c) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | instance, err := a.GetInstance() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | table := tablewriter.NewWriter(cons) 59 | table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 60 | table.SetHeaderLine(false) 61 | table.SetAutoFormatHeaders(true) 62 | 63 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) 64 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 65 | table.SetCenterSeparator("") 66 | table.SetColumnSeparator("") 67 | table.SetRowSeparator("") 68 | 69 | table.SetHeader([]string{"------", "------"}) 70 | 71 | table.Append([]string{"id", instance.ID}) 72 | table.Append([]string{"name", instance.Name}) 73 | table.Append([]string{"package", instance.Package}) 74 | table.Append([]string{"image", instance.Image}) 75 | table.Append([]string{"brand", instance.Brand}) 76 | table.Append([]string{"firewall enabled", fmt.Sprintf("%t", instance.FirewallEnabled)}) 77 | 78 | table.Render() 79 | 80 | return nil 81 | }, 82 | }, 83 | 84 | Setup: func(parent *command.Command) error { 85 | return nil 86 | }, 87 | } 88 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/ip/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package ip 10 | 11 | import ( 12 | "errors" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 15 | cfg "github.com/joyent/triton-go/v2/cmd/config" 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | "github.com/sean-/conswriter" 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | var Cmd = &command.Command{ 22 | Cobra: &cobra.Command{ 23 | Args: cobra.NoArgs, 24 | Use: "ip", 25 | Short: "get the ip a triton instance", 26 | SilenceUsage: true, 27 | PreRunE: func(cmd *cobra.Command, args []string) error { 28 | if cfg.GetMachineID() == "" && cfg.GetMachineName() == "" { 29 | return errors.New("Either `id` or `name` must be specified") 30 | } 31 | 32 | if cfg.GetMachineID() != "" && cfg.GetMachineName() != "" { 33 | return errors.New("Only 1 of `id` or `name` must be specified") 34 | } 35 | 36 | return nil 37 | }, 38 | RunE: func(cmd *cobra.Command, args []string) error { 39 | cons := conswriter.GetTerminal() 40 | 41 | c, err := cfg.NewTritonConfig() 42 | if err != nil { 43 | return err 44 | } 45 | 46 | a, err := compute.NewComputeClient(c) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | instance, err := a.GetInstance() 52 | if err != nil { 53 | return err 54 | } 55 | 56 | cons.Write([]byte(instance.PrimaryIP)) 57 | 58 | return nil 59 | }, 60 | }, 61 | 62 | Setup: func(parent *command.Command) error { 63 | return nil 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/list/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package list 10 | 11 | import ( 12 | "strings" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 15 | cfg "github.com/joyent/triton-go/v2/cmd/config" 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | tc "github.com/joyent/triton-go/v2/compute" 18 | "github.com/olekukonko/tablewriter" 19 | "github.com/sean-/conswriter" 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | var Cmd = &command.Command{ 24 | Cobra: &cobra.Command{ 25 | Args: cobra.NoArgs, 26 | Use: "list", 27 | Short: "list triton instances", 28 | Aliases: []string{"ls"}, 29 | SilenceUsage: true, 30 | PreRunE: func(cmd *cobra.Command, args []string) error { 31 | return nil 32 | }, 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | cons := conswriter.GetTerminal() 35 | 36 | c, err := cfg.NewTritonConfig() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | a, err := compute.NewComputeClient(c) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | instances, err := a.GetInstanceList() 47 | if err != nil { 48 | return err 49 | } 50 | 51 | images, err := a.GetImagesList() 52 | if err != nil { 53 | return err 54 | } 55 | 56 | table := tablewriter.NewWriter(cons) 57 | table.SetHeaderAlignment(tablewriter.ALIGN_RIGHT) 58 | table.SetHeaderLine(false) 59 | table.SetAutoFormatHeaders(true) 60 | 61 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT}) 62 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 63 | table.SetCenterSeparator("") 64 | table.SetColumnSeparator("") 65 | table.SetRowSeparator("") 66 | 67 | table.SetHeader([]string{"SHORTID", "NAME", "IMG", "STATE", "FLAGS", "AGE"}) 68 | 69 | var numInstances uint 70 | for _, instance := range instances { 71 | table.Append([]string{string(instance.ID[:8]), instance.Name, a.FormatImageName(images, instance.Image), instance.State, formatInstanceFlags(instance), cfg.FormatTime(instance.Created)}) 72 | numInstances++ 73 | } 74 | 75 | table.Render() 76 | 77 | return nil 78 | }, 79 | }, 80 | Setup: func(parent *command.Command) error { 81 | return nil 82 | }, 83 | } 84 | 85 | func formatInstanceFlags(instance *tc.Instance) string { 86 | flags := []string{} 87 | 88 | if instance.Docker { 89 | flags = append(flags, "D") 90 | } 91 | if strings.ToLower(instance.Brand) == "kvm" { 92 | flags = append(flags, "K") 93 | } 94 | if instance.FirewallEnabled { 95 | flags = append(flags, "F") 96 | } 97 | 98 | return strings.Join(flags, "") 99 | 100 | } 101 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package instances 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/internal/config" 14 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/instances/count" 15 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/instances/create" 16 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/instances/delete" 17 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/instances/get" 18 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/instances/ip" 19 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/instances/list" 20 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/instances/reboot" 21 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/instances/start" 22 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/instances/stop" 23 | "github.com/spf13/cobra" 24 | "github.com/spf13/pflag" 25 | "github.com/spf13/viper" 26 | ) 27 | 28 | var Cmd = &command.Command{ 29 | Cobra: &cobra.Command{ 30 | Use: "instances", 31 | Aliases: []string{"instance", "vms", "machines"}, 32 | Short: "Instances (aka VMs/Machines/Containers)", 33 | }, 34 | 35 | Setup: func(parent *command.Command) error { 36 | 37 | cmds := []*command.Command{ 38 | list.Cmd, 39 | create.Cmd, 40 | delete.Cmd, 41 | get.Cmd, 42 | count.Cmd, 43 | reboot.Cmd, 44 | start.Cmd, 45 | stop.Cmd, 46 | ip.Cmd, 47 | } 48 | 49 | for _, cmd := range cmds { 50 | cmd.Setup(cmd) 51 | parent.Cobra.AddCommand(cmd.Cobra) 52 | } 53 | 54 | { 55 | const ( 56 | key = config.KeyInstanceID 57 | longName = "id" 58 | defaultValue = "" 59 | description = "Instance ID" 60 | ) 61 | 62 | flags := parent.Cobra.PersistentFlags() 63 | flags.String(longName, defaultValue, description) 64 | viper.BindPFlag(key, flags.Lookup(longName)) 65 | } 66 | 67 | { 68 | const ( 69 | key = config.KeyInstanceName 70 | longName = "name" 71 | shortName = "n" 72 | defaultValue = "" 73 | description = "Instance Name" 74 | ) 75 | 76 | flags := parent.Cobra.PersistentFlags() 77 | flags.StringP(longName, shortName, defaultValue, description) 78 | viper.BindPFlag(key, flags.Lookup(longName)) 79 | } 80 | 81 | { 82 | const ( 83 | key = config.KeyInstanceTag 84 | longName = "tag" 85 | shortName = "t" 86 | description = "Instance Tags. This flag can be used multiple times" 87 | ) 88 | 89 | flags := parent.Cobra.PersistentFlags() 90 | flags.StringSliceP(longName, shortName, nil, description) 91 | viper.BindPFlag(key, flags.Lookup(longName)) 92 | } 93 | 94 | { 95 | flags := parent.Cobra.PersistentFlags() 96 | flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName { 97 | switch name { 98 | case "tag": 99 | name = "tags" 100 | break 101 | } 102 | 103 | return pflag.NormalizedName(name) 104 | }) 105 | } 106 | 107 | { 108 | const ( 109 | key = config.KeyInstanceState 110 | longName = "state" 111 | defaultValue = "" 112 | description = "Instance state (e.g. running)" 113 | ) 114 | 115 | flags := parent.Cobra.PersistentFlags() 116 | flags.String(longName, defaultValue, description) 117 | viper.BindPFlag(key, flags.Lookup(longName)) 118 | } 119 | 120 | { 121 | const ( 122 | key = config.KeyInstanceBrand 123 | longName = "brand" 124 | defaultValue = "" 125 | description = "Instance brand (e.g. lx, kvm)" 126 | ) 127 | 128 | flags := parent.Cobra.PersistentFlags() 129 | flags.String(longName, defaultValue, description) 130 | viper.BindPFlag(key, flags.Lookup(longName)) 131 | } 132 | 133 | return nil 134 | }, 135 | } 136 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/reboot/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package reboot 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | 15 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 16 | cfg "github.com/joyent/triton-go/v2/cmd/config" 17 | "github.com/joyent/triton-go/v2/cmd/internal/command" 18 | "github.com/sean-/conswriter" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | var Cmd = &command.Command{ 23 | Cobra: &cobra.Command{ 24 | Args: cobra.NoArgs, 25 | Use: "reboot", 26 | Short: "reboot instance", 27 | SilenceUsage: true, 28 | PreRunE: func(cmd *cobra.Command, args []string) error { 29 | if cfg.GetMachineID() == "" && cfg.GetMachineName() == "" { 30 | return errors.New("Either `id` or `name` must be specified") 31 | } 32 | 33 | if cfg.GetMachineID() != "" && cfg.GetMachineName() != "" { 34 | return errors.New("Only 1 of `id` or `name` must be specified") 35 | } 36 | 37 | return nil 38 | }, 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | cons := conswriter.GetTerminal() 41 | 42 | c, err := cfg.NewTritonConfig() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | a, err := compute.NewComputeClient(c) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | instance, err := a.RebootInstance() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | cons.Write([]byte(fmt.Sprintf("Rebooted instance %q", instance.Name))) 58 | 59 | return nil 60 | }, 61 | }, 62 | Setup: func(parent *command.Command) error { 63 | return nil 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/start/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package start 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | 15 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 16 | cfg "github.com/joyent/triton-go/v2/cmd/config" 17 | "github.com/joyent/triton-go/v2/cmd/internal/command" 18 | "github.com/sean-/conswriter" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | var Cmd = &command.Command{ 23 | Cobra: &cobra.Command{ 24 | Args: cobra.NoArgs, 25 | Use: "start", 26 | Short: "start instance", 27 | SilenceUsage: true, 28 | PreRunE: func(cmd *cobra.Command, args []string) error { 29 | if cfg.GetMachineID() == "" && cfg.GetMachineName() == "" { 30 | return errors.New("Either `id` or `name` must be specified") 31 | } 32 | 33 | if cfg.GetMachineID() != "" && cfg.GetMachineName() != "" { 34 | return errors.New("Only 1 of `id` or `name` must be specified") 35 | } 36 | 37 | return nil 38 | }, 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | cons := conswriter.GetTerminal() 41 | 42 | c, err := cfg.NewTritonConfig() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | a, err := compute.NewComputeClient(c) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | instance, err := a.StartInstance() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | cons.Write([]byte(fmt.Sprintf("Started instance %q", instance.Name))) 58 | 59 | return nil 60 | }, 61 | }, 62 | Setup: func(parent *command.Command) error { 63 | return nil 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/stop/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package stop 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | 15 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 16 | cfg "github.com/joyent/triton-go/v2/cmd/config" 17 | "github.com/joyent/triton-go/v2/cmd/internal/command" 18 | "github.com/sean-/conswriter" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | var Cmd = &command.Command{ 23 | Cobra: &cobra.Command{ 24 | Args: cobra.NoArgs, 25 | Use: "stop", 26 | Short: "stop instance", 27 | SilenceUsage: true, 28 | PreRunE: func(cmd *cobra.Command, args []string) error { 29 | if cfg.GetMachineID() == "" && cfg.GetMachineName() == "" { 30 | return errors.New("Either `id` or `name` must be specified") 31 | } 32 | 33 | if cfg.GetMachineID() != "" && cfg.GetMachineName() != "" { 34 | return errors.New("Only 1 of `id` or `name` must be specified") 35 | } 36 | 37 | return nil 38 | }, 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | cons := conswriter.GetTerminal() 41 | 42 | c, err := cfg.NewTritonConfig() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | a, err := compute.NewComputeClient(c) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | instance, err := a.StopInstance() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | cons.Write([]byte(fmt.Sprintf("Stopped instance %q", instance.Name))) 58 | 59 | return nil 60 | }, 61 | }, 62 | Setup: func(parent *command.Command) error { 63 | return nil 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /cmd/triton/cmd/keys/create/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package create 10 | 11 | import ( 12 | "fmt" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/agent/account" 15 | cfg "github.com/joyent/triton-go/v2/cmd/config" 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | "github.com/joyent/triton-go/v2/cmd/internal/config" 18 | "github.com/sean-/conswriter" 19 | "github.com/spf13/cobra" 20 | "github.com/spf13/viper" 21 | ) 22 | 23 | var Cmd = &command.Command{ 24 | Cobra: &cobra.Command{ 25 | Args: cobra.NoArgs, 26 | Use: "create", 27 | Aliases: []string{"add"}, 28 | Short: "create Triton SSH Key", 29 | SilenceUsage: true, 30 | PreRunE: func(cmd *cobra.Command, args []string) error { 31 | return nil 32 | }, 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | cons := conswriter.GetTerminal() 35 | 36 | c, err := cfg.NewTritonConfig() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | a, err := account.NewAccountClient(c) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | key, err := a.CreateKey() 47 | if err != nil { 48 | return err 49 | } 50 | 51 | cons.Write([]byte(fmt.Sprintf("Created key %q", key.Name))) 52 | 53 | return nil 54 | }, 55 | }, 56 | Setup: func(parent *command.Command) error { 57 | 58 | { 59 | const ( 60 | key = config.KeySSHKey 61 | longName = "publickey" 62 | defaultValue = "" 63 | description = "SSH Key PublicKey" 64 | ) 65 | 66 | flags := parent.Cobra.Flags() 67 | flags.String(longName, defaultValue, description) 68 | viper.BindPFlag(key, flags.Lookup(longName)) 69 | } 70 | 71 | return nil 72 | }, 73 | } 74 | -------------------------------------------------------------------------------- /cmd/triton/cmd/keys/delete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package keyDelete 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | 15 | "github.com/joyent/triton-go/v2/cmd/agent/account" 16 | cfg "github.com/joyent/triton-go/v2/cmd/config" 17 | "github.com/joyent/triton-go/v2/cmd/internal/command" 18 | "github.com/sean-/conswriter" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | var Cmd = &command.Command{ 23 | Cobra: &cobra.Command{ 24 | Args: cobra.NoArgs, 25 | Use: "delete", 26 | Short: "delete Triton SSH Key", 27 | SilenceUsage: true, 28 | PreRunE: func(cmd *cobra.Command, args []string) error { 29 | if cfg.GetSSHKeyFingerprint() == "" && cfg.GetSSHKeyName() == "" { 30 | return errors.New("Either `fingerprint` or `keyname` must be specified") 31 | } 32 | 33 | if cfg.GetSSHKeyFingerprint() != "" && cfg.GetSSHKeyName() != "" { 34 | return errors.New("Only 1 of `fingerprint` or `keyname` must be specified") 35 | } 36 | 37 | return nil 38 | }, 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | cons := conswriter.GetTerminal() 41 | 42 | c, err := cfg.NewTritonConfig() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | a, err := account.NewAccountClient(c) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | key, err := a.DeleteKey() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | cons.Write([]byte(fmt.Sprintf("Deleted key %q", key.Name))) 58 | 59 | return nil 60 | }, 61 | }, 62 | Setup: func(parent *command.Command) error { 63 | return nil 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /cmd/triton/cmd/keys/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package get 10 | 11 | import ( 12 | "errors" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/agent/account" 15 | cfg "github.com/joyent/triton-go/v2/cmd/config" 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | "github.com/sean-/conswriter" 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | var Cmd = &command.Command{ 22 | Cobra: &cobra.Command{ 23 | Args: cobra.NoArgs, 24 | Use: "get", 25 | Short: "get Triton SSH Key", 26 | SilenceUsage: true, 27 | PreRunE: func(cmd *cobra.Command, args []string) error { 28 | if cfg.GetSSHKeyFingerprint() == "" && cfg.GetSSHKeyName() == "" { 29 | return errors.New("Either `fingerprint` or `keyname` must be specified") 30 | } 31 | 32 | if cfg.GetSSHKeyFingerprint() != "" && cfg.GetSSHKeyName() != "" { 33 | return errors.New("Only 1 of `fingerprint` or `keyname` must be specified") 34 | } 35 | return nil 36 | }, 37 | RunE: func(cmd *cobra.Command, args []string) error { 38 | cons := conswriter.GetTerminal() 39 | 40 | c, err := cfg.NewTritonConfig() 41 | if err != nil { 42 | return err 43 | } 44 | 45 | a, err := account.NewAccountClient(c) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | key, err := a.GetKey() 51 | if err != nil { 52 | return err 53 | } 54 | 55 | cons.Write([]byte(key.Key)) 56 | 57 | return nil 58 | }, 59 | }, 60 | Setup: func(parent *command.Command) error { 61 | return nil 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /cmd/triton/cmd/keys/list/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package list 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/agent/account" 13 | cfg "github.com/joyent/triton-go/v2/cmd/config" 14 | "github.com/joyent/triton-go/v2/cmd/internal/command" 15 | "github.com/olekukonko/tablewriter" 16 | "github.com/sean-/conswriter" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var Cmd = &command.Command{ 21 | Cobra: &cobra.Command{ 22 | Args: cobra.NoArgs, 23 | Use: "list", 24 | Short: "list Triton SSH Keys", 25 | Aliases: []string{"ls"}, 26 | SilenceUsage: true, 27 | PreRunE: func(cmd *cobra.Command, args []string) error { 28 | return nil 29 | }, 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | cons := conswriter.GetTerminal() 32 | 33 | c, err := cfg.NewTritonConfig() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | a, err := account.NewAccountClient(c) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | keys, err := a.ListKeys() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | table := tablewriter.NewWriter(cons) 49 | table.SetHeaderAlignment(tablewriter.ALIGN_CENTER) 50 | table.SetHeaderLine(false) 51 | table.SetAutoFormatHeaders(true) 52 | 53 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) 54 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 55 | table.SetCenterSeparator("") 56 | table.SetColumnSeparator("") 57 | table.SetRowSeparator("") 58 | 59 | table.SetHeader([]string{"FINGERPRINT", "NAME"}) 60 | 61 | for _, key := range keys { 62 | table.Append([]string{key.Fingerprint, key.Name}) 63 | } 64 | 65 | table.Render() 66 | 67 | return nil 68 | }, 69 | }, 70 | Setup: func(parent *command.Command) error { 71 | return nil 72 | }, 73 | } 74 | -------------------------------------------------------------------------------- /cmd/triton/cmd/keys/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package keys 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/internal/config" 14 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/keys/create" 15 | keyDelete "github.com/joyent/triton-go/v2/cmd/triton/cmd/keys/delete" 16 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/keys/get" 17 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/keys/list" 18 | "github.com/spf13/cobra" 19 | "github.com/spf13/viper" 20 | ) 21 | 22 | var Cmd = &command.Command{ 23 | Cobra: &cobra.Command{ 24 | Use: "keys", 25 | Aliases: []string{"key"}, 26 | Short: "List and manage Triton SSH Keys.", 27 | }, 28 | 29 | Setup: func(parent *command.Command) error { 30 | 31 | cmds := []*command.Command{ 32 | list.Cmd, 33 | get.Cmd, 34 | keyDelete.Cmd, 35 | create.Cmd, 36 | } 37 | 38 | for _, cmd := range cmds { 39 | cmd.Setup(cmd) 40 | parent.Cobra.AddCommand(cmd.Cobra) 41 | } 42 | 43 | { 44 | const ( 45 | key = config.KeySSHKeyFingerprint 46 | longName = "fingerprint" 47 | defaultValue = "" 48 | description = "SSH Key Fingerprint" 49 | ) 50 | 51 | flags := parent.Cobra.PersistentFlags() 52 | flags.String(longName, defaultValue, description) 53 | viper.BindPFlag(key, flags.Lookup(longName)) 54 | } 55 | 56 | { 57 | const ( 58 | key = config.KeySSHKeyName 59 | longName = "keyname" 60 | defaultValue = "" 61 | description = "SSH Key Name" 62 | ) 63 | 64 | flags := parent.Cobra.PersistentFlags() 65 | flags.String(longName, defaultValue, description) 66 | viper.BindPFlag(key, flags.Lookup(longName)) 67 | } 68 | 69 | return nil 70 | }, 71 | } 72 | -------------------------------------------------------------------------------- /cmd/triton/cmd/packages/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package get 10 | 11 | import ( 12 | "encoding/json" 13 | "errors" 14 | 15 | "bytes" 16 | 17 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 18 | cfg "github.com/joyent/triton-go/v2/cmd/config" 19 | "github.com/joyent/triton-go/v2/cmd/internal/command" 20 | "github.com/sean-/conswriter" 21 | "github.com/spf13/cobra" 22 | ) 23 | 24 | var Cmd = &command.Command{ 25 | Cobra: &cobra.Command{ 26 | Args: cobra.NoArgs, 27 | Use: "get", 28 | Short: "get triton package", 29 | SilenceUsage: true, 30 | PreRunE: func(cmd *cobra.Command, args []string) error { 31 | if cfg.GetPkgID() == "" && cfg.GetPkgName() == "" { 32 | return errors.New("Either `id` or `name` must be specified") 33 | } 34 | 35 | if cfg.GetPkgID() != "" && cfg.GetPkgName() != "" { 36 | return errors.New("Only 1 of `id` or `name` must be specified") 37 | } 38 | return nil 39 | }, 40 | RunE: func(cmd *cobra.Command, args []string) error { 41 | cons := conswriter.GetTerminal() 42 | 43 | c, err := cfg.NewTritonConfig() 44 | if err != nil { 45 | return err 46 | } 47 | 48 | a, err := compute.NewComputeClient(c) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | pkg, err := a.GetPackage() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | bytes, err := json.Marshal(pkg) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | output, _ := prettyPrintJSON(bytes) 64 | 65 | cons.Write(output) 66 | 67 | return nil 68 | }, 69 | }, 70 | Setup: func(parent *command.Command) error { 71 | return nil 72 | }, 73 | } 74 | 75 | func prettyPrintJSON(b []byte) ([]byte, error) { 76 | var out bytes.Buffer 77 | err := json.Indent(&out, b, "", " ") 78 | return out.Bytes(), err 79 | } 80 | -------------------------------------------------------------------------------- /cmd/triton/cmd/packages/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package packages 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/internal/config" 14 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/packages/get" 15 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/packages/list" 16 | "github.com/spf13/cobra" 17 | "github.com/spf13/viper" 18 | ) 19 | 20 | var Cmd = &command.Command{ 21 | Cobra: &cobra.Command{ 22 | Use: "packages", 23 | Aliases: []string{"package", "pkgs"}, 24 | Short: "List and get Triton packages.", 25 | Long: `A package is a collection of attributes -- for example disk quota, 26 | amount of RAM -- used when creating an instance. They have a name 27 | and ID for identification.`, 28 | }, 29 | 30 | Setup: func(parent *command.Command) error { 31 | 32 | cmds := []*command.Command{ 33 | list.Cmd, 34 | get.Cmd, 35 | } 36 | 37 | for _, cmd := range cmds { 38 | cmd.Setup(cmd) 39 | parent.Cobra.AddCommand(cmd.Cobra) 40 | } 41 | 42 | { 43 | const ( 44 | key = config.KeyPackageID 45 | longName = "id" 46 | defaultValue = "" 47 | description = "Package ID" 48 | ) 49 | 50 | flags := parent.Cobra.PersistentFlags() 51 | flags.String(longName, defaultValue, description) 52 | viper.BindPFlag(key, flags.Lookup(longName)) 53 | } 54 | 55 | { 56 | const ( 57 | key = config.KeyPackageName 58 | longName = "name" 59 | defaultValue = "" 60 | description = "Package Name" 61 | ) 62 | 63 | flags := parent.Cobra.PersistentFlags() 64 | flags.String(longName, defaultValue, description) 65 | viper.BindPFlag(key, flags.Lookup(longName)) 66 | } 67 | 68 | return nil 69 | }, 70 | } 71 | -------------------------------------------------------------------------------- /cmd/triton/cmd/root_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "regexp" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/joyent/triton-go/v2/testutils" 13 | ) 14 | 15 | func TestListDataCenters_Cmd(t *testing.T) { 16 | testutils.AccTest(t, testutils.TestCase{ 17 | Steps: []testutils.Step{ 18 | &testutils.StepAssertFunc{ 19 | AssertFunc: func(state testutils.TritonStateBag) error { 20 | cmd := exec.Command("../triton", "datacenters") 21 | out := bytes.NewBuffer([]byte{}) 22 | 23 | cmd.Stdout = out 24 | cmd.Stderr = os.Stderr 25 | err := cmd.Run() 26 | if err != nil { 27 | return fmt.Errorf("%v", err) 28 | } 29 | re, err := regexp.Compile(`(?i)url`) 30 | if err != nil { 31 | return fmt.Errorf("Error compiling Regexp: %v", err) 32 | } 33 | 34 | if !re.MatchString(out.String()) { 35 | return fmt.Errorf("Unexpected command stdout:\n%s", out.String()) 36 | } 37 | 38 | t.Logf("\n%s =>\n%s", strings.Join(cmd.Args[:], " "), out.String()) 39 | return nil 40 | }, 41 | }, 42 | }, 43 | }) 44 | } 45 | 46 | func TestListInstances_Cmd(t *testing.T) { 47 | testutils.AccTest(t, testutils.TestCase{ 48 | Steps: []testutils.Step{ 49 | &testutils.StepAssertFunc{ 50 | AssertFunc: func(state testutils.TritonStateBag) error { 51 | cmd := exec.Command("../triton", "instances", "list") 52 | out := bytes.NewBuffer([]byte{}) 53 | 54 | cmd.Stdout = out 55 | cmd.Stderr = os.Stderr 56 | err := cmd.Run() 57 | if err != nil { 58 | return fmt.Errorf("%v", err) 59 | } 60 | re, err := regexp.Compile(`(?i)shortid`) 61 | if err != nil { 62 | return fmt.Errorf("Error compiling Regexp: %v", err) 63 | } 64 | 65 | if !re.MatchString(out.String()) { 66 | return fmt.Errorf("Unexpected command stdout:\n%s", out.String()) 67 | } 68 | t.Logf("\n%s =>\n%s", strings.Join(cmd.Args[:], " "), out.String()) 69 | return nil 70 | }, 71 | }, 72 | }, 73 | }) 74 | } 75 | 76 | func TestListPackages_Cmd(t *testing.T) { 77 | testutils.AccTest(t, testutils.TestCase{ 78 | Steps: []testutils.Step{ 79 | &testutils.StepAssertFunc{ 80 | AssertFunc: func(state testutils.TritonStateBag) error { 81 | cmd := exec.Command("../triton", "package", "list", "--memory", "128") 82 | out := bytes.NewBuffer([]byte{}) 83 | 84 | cmd.Stdout = out 85 | cmd.Stderr = os.Stderr 86 | err := cmd.Run() 87 | if err != nil { 88 | return fmt.Errorf("%v", err) 89 | } 90 | re, err := regexp.Compile(`(?i)shortid`) 91 | if err != nil { 92 | return fmt.Errorf("Error compiling Regexp: %v", err) 93 | } 94 | 95 | if !re.MatchString(out.String()) { 96 | return fmt.Errorf("Unexpected command stdout:\n%s", out.String()) 97 | } 98 | t.Logf("\n%s =>\n%s", strings.Join(cmd.Args[:], " "), out.String()) 99 | return nil 100 | }, 101 | }, 102 | }, 103 | }) 104 | } 105 | 106 | func TestGetPackageByName_Cmd(t *testing.T) { 107 | testutils.AccTest(t, testutils.TestCase{ 108 | Steps: []testutils.Step{ 109 | &testutils.StepAssertFunc{ 110 | AssertFunc: func(state testutils.TritonStateBag) error { 111 | cmd := exec.Command("../triton", "package", "get", "--name", "sample-128M") 112 | out := bytes.NewBuffer([]byte{}) 113 | 114 | cmd.Stdout = out 115 | cmd.Stderr = os.Stderr 116 | err := cmd.Run() 117 | if err != nil { 118 | return fmt.Errorf("%v", err) 119 | } 120 | re, err := regexp.Compile(`(?i)shortid`) 121 | if err != nil { 122 | return fmt.Errorf("Error compiling Regexp: %v", err) 123 | } 124 | 125 | if !re.MatchString(out.String()) { 126 | return fmt.Errorf("Unexpected command stdout:\n%s", out.String()) 127 | } 128 | t.Logf("\n%s =>\n%s", strings.Join(cmd.Args[:], " "), out.String()) 129 | return nil 130 | }, 131 | }, 132 | }, 133 | }) 134 | } 135 | -------------------------------------------------------------------------------- /cmd/triton/cmd/services/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package services 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/agent/compute" 13 | cfg "github.com/joyent/triton-go/v2/cmd/config" 14 | "github.com/joyent/triton-go/v2/cmd/internal/command" 15 | "github.com/olekukonko/tablewriter" 16 | "github.com/sean-/conswriter" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var Cmd = &command.Command{ 21 | Cobra: &cobra.Command{ 22 | Args: cobra.NoArgs, 23 | Use: "services", 24 | Short: "Show services in this cloud", 25 | SilenceUsage: true, 26 | PreRunE: func(cmd *cobra.Command, args []string) error { 27 | return nil 28 | }, 29 | RunE: func(cmd *cobra.Command, args []string) error { 30 | cons := conswriter.GetTerminal() 31 | 32 | c, err := cfg.NewTritonConfig() 33 | if err != nil { 34 | return err 35 | } 36 | 37 | a, err := compute.NewComputeClient(c) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | services, err := a.GetServiceList() 43 | if err != nil { 44 | return err 45 | } 46 | 47 | table := tablewriter.NewWriter(cons) 48 | table.SetHeaderAlignment(tablewriter.ALIGN_CENTER) 49 | table.SetHeaderLine(false) 50 | table.SetAutoFormatHeaders(true) 51 | 52 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) 53 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 54 | table.SetCenterSeparator("") 55 | table.SetColumnSeparator("") 56 | table.SetRowSeparator("") 57 | 58 | table.SetHeader([]string{"NAME", "ENDPOINT"}) 59 | 60 | for _, service := range services { 61 | table.Append([]string{service.Name, service.Endpoint}) 62 | } 63 | 64 | table.Render() 65 | 66 | return nil 67 | }, 68 | }, 69 | Setup: func(parent *command.Command) error { 70 | return nil 71 | }, 72 | } 73 | -------------------------------------------------------------------------------- /cmd/triton/cmd/shell/autocomplete/bash/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package bash 10 | 11 | import ( 12 | "fmt" 13 | 14 | "os" 15 | 16 | "github.com/joyent/triton-go/v2/cmd/internal/command" 17 | "github.com/joyent/triton-go/v2/cmd/internal/config" 18 | "github.com/pkg/errors" 19 | "github.com/rs/zerolog/log" 20 | "github.com/spf13/cobra" 21 | "github.com/spf13/viper" 22 | ) 23 | 24 | var Cmd = &command.Command{ 25 | Cobra: &cobra.Command{ 26 | Use: "bash", 27 | Short: "Generates shell autocompletion file for Triton", 28 | Long: `Generates a shell autocompletion script for Triton. 29 | 30 | By default, the file is written directly to /etc/bash_completion.d 31 | for convenience, and the command may need superuser rights, e.g.: 32 | 33 | $ sudo triton shell autocomplete bash 34 | 35 | Add ` + "`--bash-autocomplete-dir=/path/to/file`" + ` flag to set alternative 36 | folder location. 37 | 38 | Logout and in again to reload the completion scripts, 39 | or just source them in directly: 40 | 41 | $ . /etc/bash_completion`, 42 | 43 | PreRunE: func(cmd *cobra.Command, args []string) error { 44 | return nil 45 | }, 46 | RunE: func(cmd *cobra.Command, args []string) error { 47 | target := viper.GetString(config.KeyBashAutoCompletionTarget) 48 | if _, err := os.Stat(target); os.IsNotExist(err) { 49 | if err := os.MkdirAll(target, 0777); err != nil { 50 | return errors.Wrapf(err, "unable to make bash-autocomplete-target %q", target) 51 | } 52 | } 53 | bashFile := fmt.Sprintf("%s/triton.sh", target) 54 | err := cmd.Root().GenBashCompletionFile(bashFile) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | log.Info().Msg("Installation completed successfully.") 60 | 61 | return nil 62 | }, 63 | }, 64 | Setup: func(parent *command.Command) error { 65 | 66 | { 67 | const ( 68 | key = config.KeyBashAutoCompletionTarget 69 | longName = "bash-autocomplete-dir" 70 | defaultValue = "/etc/bash_completion.d" 71 | description = "autocompletion directory" 72 | ) 73 | 74 | flags := parent.Cobra.PersistentFlags() 75 | flags.String(longName, defaultValue, description) 76 | viper.BindPFlag(key, flags.Lookup(longName)) 77 | } 78 | 79 | return nil 80 | }, 81 | } 82 | -------------------------------------------------------------------------------- /cmd/triton/cmd/shell/autocomplete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package autocomplete 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/shell/autocomplete/bash" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | var Cmd = &command.Command{ 18 | Cobra: &cobra.Command{ 19 | Use: "autocomplete", 20 | Short: "Autocompletion generation", 21 | }, 22 | 23 | Setup: func(parent *command.Command) error { 24 | cmds := []*command.Command{ 25 | bash.Cmd, 26 | } 27 | 28 | for _, cmd := range cmds { 29 | cmd.Setup(cmd) 30 | parent.Cobra.AddCommand(cmd.Cobra) 31 | } 32 | 33 | return nil 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /cmd/triton/cmd/shell/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package shell 10 | 11 | import ( 12 | "github.com/joyent/triton-go/v2/cmd/internal/command" 13 | "github.com/joyent/triton-go/v2/cmd/triton/cmd/shell/autocomplete" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | var Cmd = &command.Command{ 18 | Cobra: &cobra.Command{ 19 | Use: "shell", 20 | Short: "shell commands", 21 | }, 22 | 23 | Setup: func(parent *command.Command) error { 24 | cmds := []*command.Command{ 25 | autocomplete.Cmd, 26 | } 27 | 28 | for _, cmd := range cmds { 29 | cmd.Setup(cmd) 30 | parent.Cobra.AddCommand(cmd.Cobra) 31 | } 32 | 33 | return nil 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /cmd/triton/cmd/version/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package version 10 | 11 | import ( 12 | "fmt" 13 | 14 | triton "github.com/joyent/triton-go/v2" 15 | "github.com/joyent/triton-go/v2/cmd/internal/command" 16 | "github.com/sean-/conswriter" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var Cmd = &command.Command{ 21 | Cobra: &cobra.Command{ 22 | Use: "version", 23 | Short: "print triton cli version", 24 | SilenceUsage: true, 25 | 26 | RunE: func(cmd *cobra.Command, args []string) error { 27 | cons := conswriter.GetTerminal() 28 | cons.Write([]byte(fmt.Sprintf("Version: %s\n", triton.UserAgent()))) 29 | return nil 30 | }, 31 | }, 32 | Setup: func(parent *command.Command) error { 33 | return nil 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /cmd/triton/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "os" 13 | 14 | "github.com/joyent/triton-go/v2/cmd/triton/cmd" 15 | "github.com/rs/zerolog/log" 16 | "github.com/sean-/conswriter" 17 | ) 18 | 19 | func main() { 20 | defer func() { 21 | p := conswriter.GetTerminal() 22 | p.Wait() 23 | }() 24 | 25 | if err := cmd.Execute(); err != nil { 26 | log.Error().Err(err).Msg("unable to run") 27 | os.Exit(1) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /compute/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package compute 10 | 11 | import ( 12 | "net/http" 13 | 14 | triton "github.com/joyent/triton-go/v2" 15 | "github.com/joyent/triton-go/v2/client" 16 | ) 17 | 18 | type ComputeClient struct { 19 | Client *client.Client 20 | } 21 | 22 | func newComputeClient(client *client.Client) *ComputeClient { 23 | return &ComputeClient{ 24 | Client: client, 25 | } 26 | } 27 | 28 | // NewClient returns a new client for working with Compute endpoints and 29 | // resources within CloudAPI 30 | func NewClient(config *triton.ClientConfig) (*ComputeClient, error) { 31 | // TODO: Utilize config interface within the function itself 32 | client, err := client.New( 33 | config.TritonURL, 34 | config.MantaURL, 35 | config.AccountName, 36 | config.Signers..., 37 | ) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return newComputeClient(client), nil 42 | } 43 | 44 | // SetHeaders allows a consumer of the current client to set custom headers for 45 | // the next backend HTTP request sent to CloudAPI 46 | func (c *ComputeClient) SetHeader(header *http.Header) { 47 | c.Client.RequestHeader = header 48 | } 49 | 50 | // Datacenters returns a Compute client used for accessing functions pertaining 51 | // to DataCenter functionality in the Triton API. 52 | func (c *ComputeClient) Datacenters() *DataCentersClient { 53 | return &DataCentersClient{c.Client} 54 | } 55 | 56 | // Images returns a Compute client used for accessing functions pertaining to 57 | // Images functionality in the Triton API. 58 | func (c *ComputeClient) Images() *ImagesClient { 59 | return &ImagesClient{c.Client} 60 | } 61 | 62 | // Machine returns a Compute client used for accessing functions pertaining to 63 | // machine functionality in the Triton API. 64 | func (c *ComputeClient) Instances() *InstancesClient { 65 | return &InstancesClient{c.Client} 66 | } 67 | 68 | // Packages returns a Compute client used for accessing functions pertaining to 69 | // Packages functionality in the Triton API. 70 | func (c *ComputeClient) Packages() *PackagesClient { 71 | return &PackagesClient{c.Client} 72 | } 73 | 74 | // Services returns a Compute client used for accessing functions pertaining to 75 | // Services functionality in the Triton API. 76 | func (c *ComputeClient) Services() *ServicesClient { 77 | return &ServicesClient{c.Client} 78 | } 79 | 80 | // Snapshots returns a Compute client used for accessing functions pertaining to 81 | // Snapshots functionality in the Triton API. 82 | func (c *ComputeClient) Snapshots() *SnapshotsClient { 83 | return &SnapshotsClient{c.Client} 84 | } 85 | 86 | // Snapshots returns a Compute client used for accessing functions pertaining to 87 | // Snapshots functionality in the Triton API. 88 | func (c *ComputeClient) Volumes() *VolumesClient { 89 | return &VolumesClient{c.Client} 90 | } 91 | -------------------------------------------------------------------------------- /compute/client_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package compute_test 10 | 11 | import ( 12 | "context" 13 | "io/ioutil" 14 | "net/http" 15 | "path" 16 | "strings" 17 | "testing" 18 | 19 | "github.com/joyent/triton-go/v2/compute" 20 | "github.com/joyent/triton-go/v2/testutils" 21 | ) 22 | 23 | // MockComputeClient is used to mock out compute.ComputeClient for all tests 24 | // under the triton-go/compute package 25 | func MockComputeClient() *compute.ComputeClient { 26 | return &compute.ComputeClient{ 27 | Client: testutils.NewMockClient(testutils.MockClientInput{ 28 | AccountName: accountURL, 29 | }), 30 | } 31 | } 32 | 33 | const ( 34 | testHeaderName = "X-Test-Header" 35 | testHeaderVal1 = "number one" 36 | testHeaderVal2 = "number two" 37 | ) 38 | 39 | func TestSetHeader(t *testing.T) { 40 | computeClient := MockComputeClient() 41 | 42 | do := func(ctx context.Context, cc *compute.ComputeClient) error { 43 | defer testutils.DeactivateClient() 44 | 45 | header := &http.Header{} 46 | header.Add(testHeaderName, testHeaderVal1) 47 | header.Add(testHeaderName, testHeaderVal2) 48 | cc.SetHeader(header) 49 | 50 | _, err := cc.Datacenters().List(ctx, &compute.ListDataCentersInput{}) 51 | 52 | return err 53 | } 54 | 55 | t.Run("override header", func(t *testing.T) { 56 | testutils.RegisterResponder("GET", path.Join("/", accountURL, "datacenters"), overrideHeaderTest(t)) 57 | 58 | err := do(context.Background(), computeClient) 59 | if err != nil { 60 | t.Error(err) 61 | } 62 | }) 63 | } 64 | 65 | func overrideHeaderTest(t *testing.T) func(req *http.Request) (*http.Response, error) { 66 | return func(req *http.Request) (*http.Response, error) { 67 | // test existence of custom headers at all 68 | if req.Header.Get(testHeaderName) == "" { 69 | t.Errorf("request header should contain '%s'", testHeaderName) 70 | } 71 | testHeader := strings.Join(req.Header[testHeaderName], ",") 72 | // test override of initial header 73 | if !strings.Contains(testHeader, testHeaderVal1) { 74 | t.Errorf("request header should not contain %q: got %q", testHeaderVal1, testHeader) 75 | } 76 | if strings.Contains(testHeader, testHeaderVal2) { 77 | t.Errorf("request header should contain '%s': got '%s'", testHeaderVal2, testHeader) 78 | } 79 | 80 | header := http.Header{} 81 | header.Add("Content-Type", "application/json") 82 | 83 | body := strings.NewReader(`{ 84 | "us-east-1": "https://us-east-1.api.joyentcloud.com" 85 | } 86 | `) 87 | 88 | return &http.Response{ 89 | StatusCode: http.StatusOK, 90 | Header: header, 91 | Body: ioutil.NopCloser(body), 92 | }, nil 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /compute/datacenters.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package compute 10 | 11 | import ( 12 | "context" 13 | "encoding/json" 14 | "fmt" 15 | "net/http" 16 | "path" 17 | "sort" 18 | 19 | "github.com/joyent/triton-go/v2/client" 20 | "github.com/joyent/triton-go/v2/errors" 21 | pkgerrors "github.com/pkg/errors" 22 | ) 23 | 24 | type DataCentersClient struct { 25 | client *client.Client 26 | } 27 | 28 | type DataCenter struct { 29 | Name string `json:"name"` 30 | URL string `json:"url"` 31 | } 32 | 33 | type ListDataCentersInput struct{} 34 | 35 | func (c *DataCentersClient) List(ctx context.Context, _ *ListDataCentersInput) ([]*DataCenter, error) { 36 | fullPath := path.Join("/", c.client.AccountName, "datacenters") 37 | 38 | reqInputs := client.RequestInput{ 39 | Method: http.MethodGet, 40 | Path: fullPath, 41 | } 42 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 43 | if respReader != nil { 44 | defer respReader.Close() 45 | } 46 | if err != nil { 47 | return nil, pkgerrors.Wrap(err, "unable to list data centers") 48 | } 49 | 50 | var intermediate map[string]string 51 | decoder := json.NewDecoder(respReader) 52 | if err = decoder.Decode(&intermediate); err != nil { 53 | return nil, pkgerrors.Wrap(err, "unable to decode list data centers response") 54 | } 55 | 56 | keys := make([]string, len(intermediate)) 57 | i := 0 58 | for k := range intermediate { 59 | keys[i] = k 60 | i++ 61 | } 62 | sort.Strings(keys) 63 | 64 | result := make([]*DataCenter, len(intermediate)) 65 | i = 0 66 | for _, key := range keys { 67 | result[i] = &DataCenter{ 68 | Name: key, 69 | URL: intermediate[key], 70 | } 71 | i++ 72 | } 73 | 74 | return result, nil 75 | } 76 | 77 | type GetDataCenterInput struct { 78 | Name string 79 | } 80 | 81 | func (c *DataCentersClient) Get(ctx context.Context, input *GetDataCenterInput) (*DataCenter, error) { 82 | dcs, err := c.List(ctx, &ListDataCentersInput{}) 83 | if err != nil { 84 | return nil, pkgerrors.Wrap(err, "unable to get data center") 85 | } 86 | 87 | for _, dc := range dcs { 88 | if dc.Name == input.Name { 89 | return &DataCenter{ 90 | Name: input.Name, 91 | URL: dc.URL, 92 | }, nil 93 | } 94 | } 95 | 96 | return nil, &errors.APIError{ 97 | StatusCode: http.StatusNotFound, 98 | Code: "ResourceNotFound", 99 | Message: fmt.Sprintf("data center %q not found", input.Name), 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /compute/ping.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package compute 10 | 11 | import ( 12 | "context" 13 | "encoding/json" 14 | "net/http" 15 | 16 | "github.com/joyent/triton-go/v2/client" 17 | pkgerrors "github.com/pkg/errors" 18 | ) 19 | 20 | const pingEndpoint = "/--ping" 21 | 22 | type CloudAPI struct { 23 | Versions []string `json:"versions"` 24 | } 25 | 26 | type PingOutput struct { 27 | Ping string `json:"ping"` 28 | CloudAPI CloudAPI `json:"cloudapi"` 29 | } 30 | 31 | // Ping sends a request to the '/--ping' endpoint and returns a `pong` as well 32 | // as a list of API version numbers your instance of CloudAPI is presenting. 33 | func (c *ComputeClient) Ping(ctx context.Context) (*PingOutput, error) { 34 | reqInputs := client.RequestInput{ 35 | Method: http.MethodGet, 36 | Path: pingEndpoint, 37 | } 38 | response, err := c.Client.ExecuteRequestRaw(ctx, reqInputs) 39 | if err != nil { 40 | return nil, pkgerrors.Wrap(err, "unable to ping") 41 | } 42 | if response == nil { 43 | return nil, pkgerrors.Wrap(err, "unable to ping") 44 | } 45 | if response.Body != nil { 46 | defer response.Body.Close() 47 | } 48 | 49 | var result *PingOutput 50 | decoder := json.NewDecoder(response.Body) 51 | if err = decoder.Decode(&result); err != nil { 52 | return nil, pkgerrors.Wrap(err, "unable to decode ping response") 53 | } 54 | 55 | return result, nil 56 | } 57 | -------------------------------------------------------------------------------- /compute/ping_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package compute_test 10 | 11 | import ( 12 | "context" 13 | "errors" 14 | "io/ioutil" 15 | "net/http" 16 | "reflect" 17 | "strings" 18 | "testing" 19 | 20 | "github.com/joyent/triton-go/v2/compute" 21 | "github.com/joyent/triton-go/v2/testutils" 22 | ) 23 | 24 | var ( 25 | mockVersions = []string{"7.0.0", "7.1.0", "7.2.0", "7.3.0", "8.0.0"} 26 | testError = errors.New("unable to ping") 27 | ) 28 | 29 | func TestPing(t *testing.T) { 30 | computeClient := MockComputeClient() 31 | 32 | do := func(ctx context.Context, pc *compute.ComputeClient) (*compute.PingOutput, error) { 33 | defer testutils.DeactivateClient() 34 | 35 | ping, err := pc.Ping(ctx) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return ping, nil 40 | } 41 | 42 | t.Run("successful", func(t *testing.T) { 43 | testutils.RegisterResponder("GET", "/--ping", pingSuccessFunc) 44 | 45 | resp, err := do(context.Background(), computeClient) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | if resp.Ping != "pong" { 51 | t.Errorf("ping was not pong: expected %s", resp.Ping) 52 | } 53 | 54 | if !reflect.DeepEqual(resp.CloudAPI.Versions, mockVersions) { 55 | t.Errorf("ping did not contain CloudAPI versions: expected %s", mockVersions) 56 | } 57 | }) 58 | 59 | t.Run("EOF decode", func(t *testing.T) { 60 | testutils.RegisterResponder("GET", "/--ping", pingEmptyFunc) 61 | 62 | _, err := do(context.Background(), computeClient) 63 | if err == nil { 64 | t.Fatal(err) 65 | } 66 | 67 | if !strings.Contains(err.Error(), "EOF") { 68 | t.Errorf("expected error to contain EOF: found %s", err) 69 | } 70 | }) 71 | 72 | t.Run("error", func(t *testing.T) { 73 | testutils.RegisterResponder("GET", "/--ping", pingErrorFunc) 74 | 75 | out, err := do(context.Background(), computeClient) 76 | if err == nil { 77 | t.Fatal(err) 78 | } 79 | if out != nil { 80 | t.Error("expected pingOut to be nil") 81 | } 82 | 83 | if !strings.Contains(err.Error(), "unable to ping") { 84 | t.Errorf("expected error to equal testError: found %s", err) 85 | } 86 | }) 87 | 88 | t.Run("bad decode", func(t *testing.T) { 89 | testutils.RegisterResponder("GET", "/--ping", pingDecodeFunc) 90 | 91 | _, err := do(context.Background(), computeClient) 92 | if err == nil { 93 | t.Fatal(err) 94 | } 95 | 96 | if !strings.Contains(err.Error(), "invalid character") { 97 | t.Errorf("expected decode to fail: found %s", err) 98 | } 99 | }) 100 | } 101 | 102 | func pingSuccessFunc(req *http.Request) (*http.Response, error) { 103 | header := http.Header{} 104 | header.Add("Content-Type", "application/json") 105 | 106 | body := strings.NewReader(`{ 107 | "ping": "pong", 108 | "cloudapi": { 109 | "versions": ["7.0.0", "7.1.0", "7.2.0", "7.3.0", "8.0.0"] 110 | } 111 | }`) 112 | 113 | return &http.Response{ 114 | StatusCode: 200, 115 | Header: header, 116 | Body: ioutil.NopCloser(body), 117 | }, nil 118 | } 119 | 120 | func pingEmptyFunc(req *http.Request) (*http.Response, error) { 121 | header := http.Header{} 122 | header.Add("Content-Type", "application/json") 123 | 124 | return &http.Response{ 125 | StatusCode: 200, 126 | Header: header, 127 | Body: ioutil.NopCloser(strings.NewReader("")), 128 | }, nil 129 | } 130 | 131 | func pingErrorFunc(req *http.Request) (*http.Response, error) { 132 | return nil, testError 133 | } 134 | 135 | func pingDecodeFunc(req *http.Request) (*http.Response, error) { 136 | header := http.Header{} 137 | header.Add("Content-Type", "application/json") 138 | 139 | body := strings.NewReader(`{ 140 | (ham!(// 141 | }`) 142 | 143 | return &http.Response{ 144 | StatusCode: 200, 145 | Header: header, 146 | Body: ioutil.NopCloser(body), 147 | }, nil 148 | } 149 | -------------------------------------------------------------------------------- /compute/services.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package compute 10 | 11 | import ( 12 | "context" 13 | "encoding/json" 14 | "net/http" 15 | "path" 16 | "sort" 17 | 18 | "github.com/joyent/triton-go/v2/client" 19 | "github.com/pkg/errors" 20 | ) 21 | 22 | type ServicesClient struct { 23 | client *client.Client 24 | } 25 | 26 | type Service struct { 27 | Name string 28 | Endpoint string 29 | } 30 | 31 | type ListServicesInput struct{} 32 | 33 | func (c *ServicesClient) List(ctx context.Context, _ *ListServicesInput) ([]*Service, error) { 34 | fullPath := path.Join("/", c.client.AccountName, "services") 35 | reqInputs := client.RequestInput{ 36 | Method: http.MethodGet, 37 | Path: fullPath, 38 | } 39 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 40 | if respReader != nil { 41 | defer respReader.Close() 42 | } 43 | if err != nil { 44 | return nil, errors.Wrap(err, "unable to list services") 45 | } 46 | 47 | var intermediate map[string]string 48 | decoder := json.NewDecoder(respReader) 49 | if err = decoder.Decode(&intermediate); err != nil { 50 | return nil, errors.Wrap(err, "unable to decode list services response") 51 | } 52 | 53 | keys := make([]string, len(intermediate)) 54 | i := 0 55 | for k := range intermediate { 56 | keys[i] = k 57 | i++ 58 | } 59 | sort.Strings(keys) 60 | 61 | result := make([]*Service, len(intermediate)) 62 | i = 0 63 | for _, key := range keys { 64 | result[i] = &Service{ 65 | Name: key, 66 | Endpoint: intermediate[key], 67 | } 68 | i++ 69 | } 70 | 71 | return result, nil 72 | } 73 | -------------------------------------------------------------------------------- /errors/errors_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package errors 10 | 11 | import ( 12 | "net/http" 13 | "testing" 14 | 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | func TestCheckIsSpecificError(t *testing.T) { 19 | t.Run("API error", func(t *testing.T) { 20 | err := &APIError{ 21 | StatusCode: http.StatusNotFound, 22 | Code: "ResourceNotFound", 23 | Message: "Resource Not Found", // note dosesn't matter 24 | } 25 | 26 | if !IsSpecificError(err, "ResourceNotFound") { 27 | t.Fatalf("Expected `ResourceNotFound`, got %v", err.Code) 28 | } 29 | 30 | if IsSpecificError(err, "IncorrectCode") { 31 | t.Fatalf("Expected `IncorrectCode`, got %v", err.Code) 32 | } 33 | }) 34 | 35 | t.Run("Non Specific Error Type", func(t *testing.T) { 36 | err := errors.New("This is a new error") 37 | 38 | if IsSpecificError(err, "ResourceNotFound") { 39 | t.Fatalf("Specific Error Type Found") 40 | } 41 | }) 42 | } 43 | 44 | func TestCheckIsSpecificStatusCode(t *testing.T) { 45 | t.Run("API error", func(t *testing.T) { 46 | err := &APIError{ 47 | StatusCode: http.StatusNotFound, 48 | Code: "ResourceNotFound", 49 | Message: "Resource Not Found", // note dosesn't matter 50 | } 51 | 52 | if !IsSpecificStatusCode(err, http.StatusNotFound) { 53 | t.Fatalf("Expected `404`, got %v", err.StatusCode) 54 | } 55 | 56 | if IsSpecificStatusCode(err, http.StatusNoContent) { 57 | t.Fatalf("Expected `404`, got %v", err.Code) 58 | } 59 | }) 60 | 61 | t.Run("Non Specific Error Type", func(t *testing.T) { 62 | err := errors.New("This is a new error") 63 | 64 | if IsSpecificStatusCode(err, http.StatusNotFound) { 65 | t.Fatalf("Specific Error Type Found") 66 | } 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /examples/account/account_info/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "context" 13 | "fmt" 14 | "io/ioutil" 15 | "log" 16 | "os" 17 | 18 | "encoding/pem" 19 | 20 | triton "github.com/joyent/triton-go/v2" 21 | "github.com/joyent/triton-go/v2/account" 22 | "github.com/joyent/triton-go/v2/authentication" 23 | ) 24 | 25 | func printAccount(acct *account.Account) { 26 | fmt.Println("Account ID:", acct.ID) 27 | fmt.Println("Account Email:", acct.Email) 28 | fmt.Println("Account Login:", acct.Login) 29 | } 30 | 31 | func main() { 32 | keyID := os.Getenv("TRITON_KEY_ID") 33 | accountName := os.Getenv("TRITON_ACCOUNT") 34 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 35 | userName := os.Getenv("TRITON_USER") 36 | 37 | var signer authentication.Signer 38 | var err error 39 | 40 | if keyMaterial == "" { 41 | input := authentication.SSHAgentSignerInput{ 42 | KeyID: keyID, 43 | AccountName: accountName, 44 | Username: userName, 45 | } 46 | signer, err = authentication.NewSSHAgentSigner(input) 47 | if err != nil { 48 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 49 | } 50 | } else { 51 | var keyBytes []byte 52 | if _, err = os.Stat(keyMaterial); err == nil { 53 | keyBytes, err = ioutil.ReadFile(keyMaterial) 54 | if err != nil { 55 | log.Fatalf("Error reading key material from %s: %s", 56 | keyMaterial, err) 57 | } 58 | block, _ := pem.Decode(keyBytes) 59 | if block == nil { 60 | log.Fatalf( 61 | "Failed to read key material '%s': no key found", keyMaterial) 62 | } 63 | 64 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 65 | log.Fatalf( 66 | "Failed to read key '%s': password protected keys are\n"+ 67 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 68 | } 69 | 70 | } else { 71 | keyBytes = []byte(keyMaterial) 72 | } 73 | 74 | input := authentication.PrivateKeySignerInput{ 75 | KeyID: keyID, 76 | PrivateKeyMaterial: keyBytes, 77 | AccountName: accountName, 78 | Username: userName, 79 | } 80 | signer, err = authentication.NewPrivateKeySigner(input) 81 | if err != nil { 82 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 83 | } 84 | } 85 | 86 | config := &triton.ClientConfig{ 87 | TritonURL: os.Getenv("TRITON_URL"), 88 | AccountName: accountName, 89 | Username: userName, 90 | Signers: []authentication.Signer{signer}, 91 | } 92 | 93 | a, err := account.NewClient(config) 94 | if err != nil { 95 | log.Fatalf("compute.NewClient: %v", err) 96 | } 97 | 98 | acct, err := a.Get(context.Background(), &account.GetInput{}) 99 | if err != nil { 100 | log.Fatalf("account.Get: %v", err) 101 | } 102 | 103 | fmt.Println("New ----") 104 | printAccount(acct) 105 | 106 | input := &account.UpdateInput{ 107 | CompanyName: fmt.Sprintf("%s-old", acct.CompanyName), 108 | } 109 | 110 | updatedAcct, err := a.Update(context.Background(), input) 111 | if err != nil { 112 | log.Fatalf("account.Update: %v", err) 113 | } 114 | 115 | fmt.Println("New ----") 116 | printAccount(updatedAcct) 117 | } 118 | -------------------------------------------------------------------------------- /examples/account/config/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "context" 13 | "fmt" 14 | "io/ioutil" 15 | "log" 16 | "os" 17 | 18 | "encoding/pem" 19 | 20 | triton "github.com/joyent/triton-go/v2" 21 | "github.com/joyent/triton-go/v2/account" 22 | "github.com/joyent/triton-go/v2/authentication" 23 | "github.com/joyent/triton-go/v2/network" 24 | ) 25 | 26 | func main() { 27 | keyID := os.Getenv("TRITON_KEY_ID") 28 | accountName := os.Getenv("TRITON_ACCOUNT") 29 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 30 | userName := os.Getenv("TRITON_USER") 31 | 32 | var signer authentication.Signer 33 | var err error 34 | 35 | if keyMaterial == "" { 36 | input := authentication.SSHAgentSignerInput{ 37 | KeyID: keyID, 38 | AccountName: accountName, 39 | Username: userName, 40 | } 41 | signer, err = authentication.NewSSHAgentSigner(input) 42 | if err != nil { 43 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 44 | } 45 | } else { 46 | var keyBytes []byte 47 | if _, err = os.Stat(keyMaterial); err == nil { 48 | keyBytes, err = ioutil.ReadFile(keyMaterial) 49 | if err != nil { 50 | log.Fatalf("Error reading key material from %s: %s", 51 | keyMaterial, err) 52 | } 53 | block, _ := pem.Decode(keyBytes) 54 | if block == nil { 55 | log.Fatalf( 56 | "Failed to read key material '%s': no key found", keyMaterial) 57 | } 58 | 59 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 60 | log.Fatalf( 61 | "Failed to read key '%s': password protected keys are\n"+ 62 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 63 | } 64 | 65 | } else { 66 | keyBytes = []byte(keyMaterial) 67 | } 68 | 69 | input := authentication.PrivateKeySignerInput{ 70 | KeyID: keyID, 71 | PrivateKeyMaterial: keyBytes, 72 | AccountName: accountName, 73 | Username: userName, 74 | } 75 | signer, err = authentication.NewPrivateKeySigner(input) 76 | if err != nil { 77 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 78 | } 79 | } 80 | 81 | config := &triton.ClientConfig{ 82 | TritonURL: os.Getenv("TRITON_URL"), 83 | AccountName: accountName, 84 | Username: userName, 85 | Signers: []authentication.Signer{signer}, 86 | } 87 | 88 | nc, err := network.NewClient(config) 89 | if err != nil { 90 | log.Fatalf("network.NewClient: %v", err) 91 | } 92 | 93 | ac, err := account.NewClient(config) 94 | if err != nil { 95 | log.Fatalf("account.NewClient: %v", err) 96 | } 97 | 98 | cfg, err := ac.Config().Get(context.Background(), &account.GetConfigInput{}) 99 | if err != nil { 100 | log.Fatalf("account.Config.Get: %v", err) 101 | } 102 | currentNet := cfg.DefaultNetwork 103 | fmt.Println("Current Network:", currentNet) 104 | 105 | var defaultNet string 106 | networks, err := nc.List(context.Background(), &network.ListInput{}) 107 | if err != nil { 108 | log.Fatalf("network.List: %v", err) 109 | } 110 | for _, iterNet := range networks { 111 | if iterNet.Id != currentNet { 112 | defaultNet = iterNet.Id 113 | } 114 | } 115 | fmt.Println("Chosen Network:", defaultNet) 116 | 117 | input := &account.UpdateConfigInput{ 118 | DefaultNetwork: defaultNet, 119 | } 120 | _, err = ac.Config().Update(context.Background(), input) 121 | if err != nil { 122 | log.Fatalf("account.Config.Update: %v", err) 123 | } 124 | 125 | cfg, err = ac.Config().Get(context.Background(), &account.GetConfigInput{}) 126 | if err != nil { 127 | log.Fatalf("account.Config.Get: %v", err) 128 | } 129 | fmt.Println("Default Network:", cfg.DefaultNetwork) 130 | } 131 | -------------------------------------------------------------------------------- /examples/account/keys/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "context" 13 | "fmt" 14 | "log" 15 | "os" 16 | 17 | "encoding/pem" 18 | "io/ioutil" 19 | 20 | triton "github.com/joyent/triton-go/v2" 21 | "github.com/joyent/triton-go/v2/account" 22 | "github.com/joyent/triton-go/v2/authentication" 23 | ) 24 | 25 | func main() { 26 | keyID := os.Getenv("TRITON_KEY_ID") 27 | accountName := os.Getenv("TRITON_ACCOUNT") 28 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 29 | userName := os.Getenv("TRITON_USER") 30 | 31 | var signer authentication.Signer 32 | var err error 33 | 34 | if keyMaterial == "" { 35 | input := authentication.SSHAgentSignerInput{ 36 | KeyID: keyID, 37 | AccountName: accountName, 38 | Username: userName, 39 | } 40 | signer, err = authentication.NewSSHAgentSigner(input) 41 | if err != nil { 42 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 43 | } 44 | } else { 45 | var keyBytes []byte 46 | if _, err = os.Stat(keyMaterial); err == nil { 47 | keyBytes, err = ioutil.ReadFile(keyMaterial) 48 | if err != nil { 49 | log.Fatalf("Error reading key material from %s: %s", 50 | keyMaterial, err) 51 | } 52 | block, _ := pem.Decode(keyBytes) 53 | if block == nil { 54 | log.Fatalf( 55 | "Failed to read key material '%s': no key found", keyMaterial) 56 | } 57 | 58 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 59 | log.Fatalf( 60 | "Failed to read key '%s': password protected keys are\n"+ 61 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 62 | } 63 | 64 | } else { 65 | keyBytes = []byte(keyMaterial) 66 | } 67 | 68 | input := authentication.PrivateKeySignerInput{ 69 | KeyID: keyID, 70 | PrivateKeyMaterial: keyBytes, 71 | AccountName: accountName, 72 | Username: userName, 73 | } 74 | signer, err = authentication.NewPrivateKeySigner(input) 75 | if err != nil { 76 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 77 | } 78 | } 79 | 80 | config := &triton.ClientConfig{ 81 | TritonURL: os.Getenv("TRITON_URL"), 82 | AccountName: accountName, 83 | Username: userName, 84 | Signers: []authentication.Signer{signer}, 85 | } 86 | 87 | a, err := account.NewClient(config) 88 | if err != nil { 89 | log.Fatalf("failed to init a new account client: %v", err) 90 | } 91 | 92 | keys, err := a.Keys().List(context.Background(), &account.ListKeysInput{}) 93 | if err != nil { 94 | log.Fatalf("failed to list keys: %v", err) 95 | } 96 | 97 | for _, key := range keys { 98 | fmt.Println("Key Name:", key.Name) 99 | } 100 | 101 | if key := keys[0]; key != nil { 102 | input := &account.GetKeyInput{ 103 | KeyName: key.Name, 104 | } 105 | 106 | key, err := a.Keys().Get(context.Background(), input) 107 | if err != nil { 108 | log.Fatalf("failed to get key: %v", err) 109 | } 110 | 111 | fmt.Println("First Key:", key.Key) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /examples/compute/instances/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "context" 13 | "encoding/pem" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | 19 | triton "github.com/joyent/triton-go/v2" 20 | "github.com/joyent/triton-go/v2/authentication" 21 | "github.com/joyent/triton-go/v2/compute" 22 | ) 23 | 24 | func main() { 25 | keyID := os.Getenv("TRITON_KEY_ID") 26 | accountName := os.Getenv("TRITON_ACCOUNT") 27 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 28 | userName := os.Getenv("TRITON_USER") 29 | 30 | var signer authentication.Signer 31 | var err error 32 | 33 | if keyMaterial == "" { 34 | input := authentication.SSHAgentSignerInput{ 35 | KeyID: keyID, 36 | AccountName: accountName, 37 | Username: userName, 38 | } 39 | signer, err = authentication.NewSSHAgentSigner(input) 40 | if err != nil { 41 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 42 | } 43 | } else { 44 | var keyBytes []byte 45 | if _, err = os.Stat(keyMaterial); err == nil { 46 | keyBytes, err = ioutil.ReadFile(keyMaterial) 47 | if err != nil { 48 | log.Fatalf("Error reading key material from %s: %s", 49 | keyMaterial, err) 50 | } 51 | block, _ := pem.Decode(keyBytes) 52 | if block == nil { 53 | log.Fatalf( 54 | "Failed to read key material '%s': no key found", keyMaterial) 55 | } 56 | 57 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 58 | log.Fatalf( 59 | "Failed to read key '%s': password protected keys are\n"+ 60 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 61 | } 62 | 63 | } else { 64 | keyBytes = []byte(keyMaterial) 65 | } 66 | 67 | input := authentication.PrivateKeySignerInput{ 68 | KeyID: keyID, 69 | PrivateKeyMaterial: keyBytes, 70 | AccountName: accountName, 71 | Username: userName, 72 | } 73 | signer, err = authentication.NewPrivateKeySigner(input) 74 | if err != nil { 75 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 76 | } 77 | } 78 | 79 | config := &triton.ClientConfig{ 80 | TritonURL: os.Getenv("TRITON_URL"), 81 | AccountName: accountName, 82 | Username: userName, 83 | Signers: []authentication.Signer{signer}, 84 | } 85 | 86 | c, err := compute.NewClient(config) 87 | if err != nil { 88 | log.Fatalf("compute.NewClient: %v", err) 89 | } 90 | 91 | listInput := &compute.ListInstancesInput{} 92 | instances, err := c.Instances().List(context.Background(), listInput) 93 | if err != nil { 94 | log.Fatalf("compute.Instances.List: %v", err) 95 | } 96 | numInstances := 0 97 | for _, instance := range instances { 98 | numInstances++ 99 | fmt.Println(fmt.Sprintf("-- Instance: %v", instance.Name)) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /examples/compute/volumes/list_volumes/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "context" 13 | "encoding/pem" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | 19 | "github.com/joyent/triton-go/v2" 20 | "github.com/joyent/triton-go/v2/authentication" 21 | "github.com/joyent/triton-go/v2/compute" 22 | ) 23 | 24 | func main() { 25 | keyID := os.Getenv("TRITON_KEY_ID") 26 | accountName := os.Getenv("TRITON_ACCOUNT") 27 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 28 | userName := os.Getenv("TRITON_USER") 29 | 30 | var signer authentication.Signer 31 | var err error 32 | 33 | if keyMaterial == "" { 34 | input := authentication.SSHAgentSignerInput{ 35 | KeyID: keyID, 36 | AccountName: accountName, 37 | Username: userName, 38 | } 39 | signer, err = authentication.NewSSHAgentSigner(input) 40 | if err != nil { 41 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 42 | } 43 | } else { 44 | var keyBytes []byte 45 | if _, err = os.Stat(keyMaterial); err == nil { 46 | keyBytes, err = ioutil.ReadFile(keyMaterial) 47 | if err != nil { 48 | log.Fatalf("Error reading key material from %s: %v", 49 | keyMaterial, err) 50 | } 51 | block, _ := pem.Decode(keyBytes) 52 | if block == nil { 53 | log.Fatalf( 54 | "Failed to read key material %q: no key found", keyMaterial) 55 | } 56 | 57 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 58 | log.Fatalf( 59 | "Failed to read key %q: password protected keys are\n"+ 60 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 61 | } 62 | 63 | } else { 64 | keyBytes = []byte(keyMaterial) 65 | } 66 | 67 | input := authentication.PrivateKeySignerInput{ 68 | KeyID: keyID, 69 | PrivateKeyMaterial: keyBytes, 70 | AccountName: accountName, 71 | Username: userName, 72 | } 73 | signer, err = authentication.NewPrivateKeySigner(input) 74 | if err != nil { 75 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 76 | } 77 | } 78 | 79 | config := &triton.ClientConfig{ 80 | TritonURL: os.Getenv("TRITON_URL"), 81 | AccountName: accountName, 82 | Username: userName, 83 | Signers: []authentication.Signer{signer}, 84 | } 85 | 86 | c, err := compute.NewClient(config) 87 | if err != nil { 88 | log.Fatalf("compute.NewClient: %v", err) 89 | } 90 | 91 | listInput := &compute.ListVolumesInput{} 92 | volumes, err := c.Volumes().List(context.Background(), listInput) 93 | if err != nil { 94 | log.Fatalf("compute.Volumes.List: %v", err) 95 | } 96 | numInstances := 0 97 | for _, volume := range volumes { 98 | numInstances++ 99 | fmt.Println(fmt.Sprintf("-- Volume: %s", volume.Name)) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /examples/network/create_fabric.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "context" 13 | "encoding/pem" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | "time" 19 | 20 | triton "github.com/joyent/triton-go/v2" 21 | "github.com/joyent/triton-go/v2/authentication" 22 | "github.com/joyent/triton-go/v2/network" 23 | ) 24 | 25 | func main() { 26 | keyID := os.Getenv("TRITON_KEY_ID") 27 | accountName := os.Getenv("TRITON_ACCOUNT") 28 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 29 | userName := os.Getenv("TRITON_USER") 30 | 31 | var signer authentication.Signer 32 | var err error 33 | 34 | if keyMaterial == "" { 35 | input := authentication.SSHAgentSignerInput{ 36 | KeyID: keyID, 37 | AccountName: accountName, 38 | Username: userName, 39 | } 40 | signer, err = authentication.NewSSHAgentSigner(input) 41 | if err != nil { 42 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 43 | } 44 | } else { 45 | var keyBytes []byte 46 | if _, err = os.Stat(keyMaterial); err == nil { 47 | keyBytes, err = ioutil.ReadFile(keyMaterial) 48 | if err != nil { 49 | log.Fatalf("Error reading key material from %s: %s", 50 | keyMaterial, err) 51 | } 52 | block, _ := pem.Decode(keyBytes) 53 | if block == nil { 54 | log.Fatalf( 55 | "Failed to read key material '%s': no key found", keyMaterial) 56 | } 57 | 58 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 59 | log.Fatalf( 60 | "Failed to read key '%s': password protected keys are\n"+ 61 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 62 | } 63 | 64 | } else { 65 | keyBytes = []byte(keyMaterial) 66 | } 67 | 68 | input := authentication.PrivateKeySignerInput{ 69 | KeyID: keyID, 70 | PrivateKeyMaterial: keyBytes, 71 | AccountName: accountName, 72 | Username: userName, 73 | } 74 | signer, err = authentication.NewPrivateKeySigner(input) 75 | if err != nil { 76 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 77 | } 78 | } 79 | 80 | config := &triton.ClientConfig{ 81 | TritonURL: os.Getenv("TRITON_URL"), 82 | AccountName: accountName, 83 | Username: userName, 84 | Signers: []authentication.Signer{signer}, 85 | } 86 | 87 | n, err := network.NewClient(config) 88 | if err != nil { 89 | log.Fatalf("Network NewClient(): %v", err) 90 | } 91 | 92 | fabric, err := n.Fabrics().Create(context.Background(), &network.CreateFabricInput{ 93 | FabricVLANID: 2, 94 | Name: "testnet", 95 | Description: "This is a test network", 96 | Subnet: "10.50.1.0/24", 97 | ProvisionStartIP: "10.50.1.10", 98 | ProvisionEndIP: "10.50.1.240", 99 | }) 100 | if err != nil { 101 | panic(err) 102 | } 103 | 104 | fmt.Println("Fabric was successfully created!") 105 | fmt.Println("Name:", fabric.Name) 106 | time.Sleep(5 * time.Second) 107 | 108 | err = n.Fabrics().Delete(context.Background(), &network.DeleteFabricInput{ 109 | FabricVLANID: 2, 110 | NetworkID: fabric.Id, 111 | }) 112 | if err != nil { 113 | panic(err) 114 | } 115 | 116 | fmt.Println("Fabric was successfully deleted!") 117 | time.Sleep(5 * time.Second) 118 | 119 | fwrule, err := n.Firewall().CreateRule(context.Background(), &network.CreateRuleInput{ 120 | Enabled: false, 121 | Rule: "FROM any TO tag \"bone-thug\" = \"basket-ball\" ALLOW udp PORT 8600", 122 | }) 123 | if err != nil { 124 | log.Fatalf("Failed to create Firewall Rule: %v", err) 125 | } 126 | 127 | fmt.Println("Firewall Rule was successfully added!") 128 | time.Sleep(5 * time.Second) 129 | 130 | err = n.Firewall().DeleteRule(context.Background(), &network.DeleteRuleInput{ 131 | ID: fwrule.ID, 132 | }) 133 | if err != nil { 134 | log.Fatalf("Failed to delete Firewall Rule: %v", err) 135 | } 136 | 137 | fmt.Println("Firewall Rule was successfully deleted!") 138 | time.Sleep(5 * time.Second) 139 | 140 | } 141 | -------------------------------------------------------------------------------- /examples/storage/force_delete/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "encoding/pem" 13 | "io/ioutil" 14 | "log" 15 | "os" 16 | 17 | "context" 18 | 19 | "fmt" 20 | 21 | triton "github.com/joyent/triton-go/v2" 22 | "github.com/joyent/triton-go/v2/authentication" 23 | "github.com/joyent/triton-go/v2/storage" 24 | ) 25 | 26 | func main() { 27 | keyID := os.Getenv("TRITON_KEY_ID") 28 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 29 | mantaUser := os.Getenv("MANTA_USER") 30 | mantaFolder := os.Getenv("MANTA_FOLDER") 31 | userName := os.Getenv("TRITON_USER") 32 | 33 | var signer authentication.Signer 34 | var err error 35 | 36 | if keyMaterial == "" { 37 | input := authentication.SSHAgentSignerInput{ 38 | KeyID: keyID, 39 | AccountName: mantaUser, 40 | Username: userName, 41 | } 42 | signer, err = authentication.NewSSHAgentSigner(input) 43 | if err != nil { 44 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 45 | } 46 | } else { 47 | var keyBytes []byte 48 | if _, err = os.Stat(keyMaterial); err == nil { 49 | keyBytes, err = ioutil.ReadFile(keyMaterial) 50 | if err != nil { 51 | log.Fatalf("Error reading key material from %s: %s", 52 | keyMaterial, err) 53 | } 54 | block, _ := pem.Decode(keyBytes) 55 | if block == nil { 56 | log.Fatalf( 57 | "Failed to read key material '%s': no key found", keyMaterial) 58 | } 59 | 60 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 61 | log.Fatalf( 62 | "Failed to read key '%s': password protected keys are\n"+ 63 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 64 | } 65 | 66 | } else { 67 | keyBytes = []byte(keyMaterial) 68 | } 69 | 70 | input := authentication.PrivateKeySignerInput{ 71 | KeyID: keyID, 72 | PrivateKeyMaterial: keyBytes, 73 | AccountName: mantaUser, 74 | Username: userName, 75 | } 76 | signer, err = authentication.NewPrivateKeySigner(input) 77 | if err != nil { 78 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 79 | } 80 | } 81 | 82 | config := &triton.ClientConfig{ 83 | MantaURL: os.Getenv("MANTA_URL"), 84 | AccountName: mantaUser, 85 | Username: userName, 86 | Signers: []authentication.Signer{signer}, 87 | } 88 | 89 | client, err := storage.NewClient(config) 90 | if err != nil { 91 | log.Fatalf("NewClient: %v", err) 92 | } 93 | 94 | err = client.Dir().Delete(context.Background(), &storage.DeleteDirectoryInput{ 95 | DirectoryName: mantaFolder, 96 | ForceDelete: true, 97 | }) 98 | 99 | if err != nil { 100 | log.Fatalf("Error deleting nested folder structure: %v", err) 101 | } 102 | fmt.Println("Successfully deleted all nested objects") 103 | } 104 | -------------------------------------------------------------------------------- /examples/storage/force_put/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "encoding/pem" 13 | "io/ioutil" 14 | "log" 15 | "os" 16 | 17 | "context" 18 | 19 | "fmt" 20 | 21 | triton "github.com/joyent/triton-go/v2" 22 | "github.com/joyent/triton-go/v2/authentication" 23 | "github.com/joyent/triton-go/v2/storage" 24 | ) 25 | 26 | func main() { 27 | keyID := os.Getenv("TRITON_KEY_ID") 28 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 29 | mantaUser := os.Getenv("MANTA_USER") 30 | userName := os.Getenv("TRITON_USER") 31 | 32 | var signer authentication.Signer 33 | var err error 34 | 35 | if keyMaterial == "" { 36 | input := authentication.SSHAgentSignerInput{ 37 | KeyID: keyID, 38 | AccountName: mantaUser, 39 | Username: userName, 40 | } 41 | signer, err = authentication.NewSSHAgentSigner(input) 42 | if err != nil { 43 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 44 | } 45 | } else { 46 | var keyBytes []byte 47 | if _, err = os.Stat(keyMaterial); err == nil { 48 | keyBytes, err = ioutil.ReadFile(keyMaterial) 49 | if err != nil { 50 | log.Fatalf("Error reading key material from %s: %s", 51 | keyMaterial, err) 52 | } 53 | block, _ := pem.Decode(keyBytes) 54 | if block == nil { 55 | log.Fatalf( 56 | "Failed to read key material '%s': no key found", keyMaterial) 57 | } 58 | 59 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 60 | log.Fatalf( 61 | "Failed to read key '%s': password protected keys are\n"+ 62 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 63 | } 64 | 65 | } else { 66 | keyBytes = []byte(keyMaterial) 67 | } 68 | 69 | input := authentication.PrivateKeySignerInput{ 70 | KeyID: keyID, 71 | PrivateKeyMaterial: keyBytes, 72 | AccountName: mantaUser, 73 | Username: userName, 74 | } 75 | signer, err = authentication.NewPrivateKeySigner(input) 76 | if err != nil { 77 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 78 | } 79 | } 80 | 81 | config := &triton.ClientConfig{ 82 | MantaURL: os.Getenv("MANTA_URL"), 83 | AccountName: mantaUser, 84 | Username: userName, 85 | Signers: []authentication.Signer{signer}, 86 | } 87 | 88 | client, err := storage.NewClient(config) 89 | if err != nil { 90 | log.Fatalf("NewClient: %v", err) 91 | } 92 | 93 | reader, err := os.Open("/tmp/foo.txt") 94 | if err != nil { 95 | log.Fatalf("os.Open: %v", err) 96 | } 97 | defer reader.Close() 98 | 99 | err = client.Objects().Put(context.Background(), &storage.PutObjectInput{ 100 | ObjectPath: "/stor/folder1/folder2/folder3/folder4/foo.txt", 101 | ObjectReader: reader, 102 | ForceInsert: true, 103 | }) 104 | 105 | if err != nil { 106 | log.Fatalf("Error creating nested folder structure: %v", err) 107 | } 108 | fmt.Println("Successfully uploaded /tmp/foo.txt to /stor/folder1/folder2/folder3/folder4/foo.txt") 109 | } 110 | -------------------------------------------------------------------------------- /examples/storage/list_directory/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "context" 13 | "encoding/pem" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | 19 | triton "github.com/joyent/triton-go/v2" 20 | "github.com/joyent/triton-go/v2/authentication" 21 | "github.com/joyent/triton-go/v2/storage" 22 | ) 23 | 24 | const path = "/stor" 25 | 26 | func main() { 27 | var ( 28 | signer authentication.Signer 29 | err error 30 | 31 | keyID = os.Getenv("MANTA_KEY_ID") 32 | accountName = os.Getenv("MANTA_USER") 33 | keyMaterial = os.Getenv("MANTA_KEY_MATERIAL") 34 | userName = os.Getenv("TRITON_USER") 35 | ) 36 | 37 | if keyMaterial == "" { 38 | input := authentication.SSHAgentSignerInput{ 39 | KeyID: keyID, 40 | AccountName: accountName, 41 | Username: userName, 42 | } 43 | signer, err = authentication.NewSSHAgentSigner(input) 44 | if err != nil { 45 | log.Fatalf("error creating SSH agent signer: %v", err) 46 | } 47 | } else { 48 | var keyBytes []byte 49 | if _, err = os.Stat(keyMaterial); err == nil { 50 | keyBytes, err = ioutil.ReadFile(keyMaterial) 51 | if err != nil { 52 | log.Fatalf("error reading key material from %q: %v", 53 | keyMaterial, err) 54 | } 55 | block, _ := pem.Decode(keyBytes) 56 | if block == nil { 57 | log.Fatalf( 58 | "failed to read key material %q: no key found", keyMaterial) 59 | } 60 | 61 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 62 | log.Fatalf("failed to read key %q: password protected keys are\n"+ 63 | "not currently supported, decrypt key prior to use", 64 | keyMaterial) 65 | } 66 | 67 | } else { 68 | keyBytes = []byte(keyMaterial) 69 | } 70 | 71 | input := authentication.PrivateKeySignerInput{ 72 | KeyID: keyID, 73 | PrivateKeyMaterial: keyBytes, 74 | AccountName: accountName, 75 | Username: userName, 76 | } 77 | signer, err = authentication.NewPrivateKeySigner(input) 78 | if err != nil { 79 | log.Fatalf("error creating SSH private key signer: %v", err) 80 | } 81 | } 82 | 83 | config := &triton.ClientConfig{ 84 | MantaURL: os.Getenv("MANTA_URL"), 85 | AccountName: accountName, 86 | Username: userName, 87 | Signers: []authentication.Signer{signer}, 88 | } 89 | 90 | client, err := storage.NewClient(config) 91 | if err != nil { 92 | log.Fatalf("failed to init storage client: %v", err) 93 | } 94 | 95 | ctx := context.Background() 96 | output, err := client.Dir().List(ctx, &storage.ListDirectoryInput{ 97 | DirectoryName: path, 98 | }) 99 | if err != nil { 100 | fmt.Printf("could not find %q\n", path) 101 | return 102 | } 103 | 104 | for _, item := range output.Entries { 105 | fmt.Print("******* ITEM *******\n") 106 | fmt.Printf("Name: %s\n", item.Name) 107 | fmt.Printf("Type: %s\n", item.Type) 108 | fmt.Print("\n") 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /examples/storage/mls/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "context" 13 | "fmt" 14 | "io/ioutil" 15 | "log" 16 | "os" 17 | 18 | "encoding/pem" 19 | 20 | triton "github.com/joyent/triton-go/v2" 21 | "github.com/joyent/triton-go/v2/authentication" 22 | "github.com/joyent/triton-go/v2/storage" 23 | ) 24 | 25 | func main() { 26 | keyID := os.Getenv("TRITON_KEY_ID") 27 | accountName := os.Getenv("TRITON_ACCOUNT") 28 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 29 | userName := os.Getenv("TRITON_USER") 30 | 31 | var signer authentication.Signer 32 | var err error 33 | 34 | if keyMaterial == "" { 35 | input := authentication.SSHAgentSignerInput{ 36 | KeyID: keyID, 37 | AccountName: accountName, 38 | Username: userName, 39 | } 40 | signer, err = authentication.NewSSHAgentSigner(input) 41 | if err != nil { 42 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 43 | } 44 | } else { 45 | var keyBytes []byte 46 | if _, err = os.Stat(keyMaterial); err == nil { 47 | keyBytes, err = ioutil.ReadFile(keyMaterial) 48 | if err != nil { 49 | log.Fatalf("Error reading key material from %s: %s", 50 | keyMaterial, err) 51 | } 52 | block, _ := pem.Decode(keyBytes) 53 | if block == nil { 54 | log.Fatalf( 55 | "Failed to read key material '%s': no key found", keyMaterial) 56 | } 57 | 58 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 59 | log.Fatalf( 60 | "Failed to read key '%s': password protected keys are\n"+ 61 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 62 | } 63 | 64 | } else { 65 | keyBytes = []byte(keyMaterial) 66 | } 67 | 68 | input := authentication.PrivateKeySignerInput{ 69 | KeyID: keyID, 70 | PrivateKeyMaterial: keyBytes, 71 | AccountName: accountName, 72 | Username: userName, 73 | } 74 | signer, err = authentication.NewPrivateKeySigner(input) 75 | if err != nil { 76 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 77 | } 78 | } 79 | 80 | config := &triton.ClientConfig{ 81 | MantaURL: os.Getenv("TRITON_URL"), 82 | AccountName: accountName, 83 | Username: userName, 84 | Signers: []authentication.Signer{signer}, 85 | } 86 | 87 | client, err := storage.NewClient(config) 88 | if err != nil { 89 | log.Fatalf("NewClient: %v", err) 90 | } 91 | 92 | ctx := context.Background() 93 | input := &storage.ListDirectoryInput{} 94 | output, err := client.Dir().List(ctx, input) 95 | if err != nil { 96 | log.Fatalf("storage.Dir.List: %v", err) 97 | } 98 | 99 | for _, dir := range output.Entries { 100 | fmt.Println(dir.Name) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/storage/object_put/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "context" 13 | "fmt" 14 | "io/ioutil" 15 | "log" 16 | "os" 17 | 18 | "encoding/pem" 19 | 20 | triton "github.com/joyent/triton-go/v2" 21 | "github.com/joyent/triton-go/v2/authentication" 22 | "github.com/joyent/triton-go/v2/storage" 23 | ) 24 | 25 | func main() { 26 | keyID := os.Getenv("TRITON_KEY_ID") 27 | accountName := os.Getenv("TRITON_ACCOUNT") 28 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 29 | userName := os.Getenv("TRITON_USER") 30 | 31 | var signer authentication.Signer 32 | var err error 33 | 34 | if keyMaterial == "" { 35 | input := authentication.SSHAgentSignerInput{ 36 | KeyID: keyID, 37 | AccountName: accountName, 38 | Username: userName, 39 | } 40 | signer, err = authentication.NewSSHAgentSigner(input) 41 | if err != nil { 42 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 43 | } 44 | } else { 45 | var keyBytes []byte 46 | if _, err = os.Stat(keyMaterial); err == nil { 47 | keyBytes, err = ioutil.ReadFile(keyMaterial) 48 | if err != nil { 49 | log.Fatalf("Error reading key material from %s: %s", 50 | keyMaterial, err) 51 | } 52 | block, _ := pem.Decode(keyBytes) 53 | if block == nil { 54 | log.Fatalf( 55 | "Failed to read key material '%s': no key found", keyMaterial) 56 | } 57 | 58 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 59 | log.Fatalf( 60 | "Failed to read key '%s': password protected keys are\n"+ 61 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 62 | } 63 | 64 | } else { 65 | keyBytes = []byte(keyMaterial) 66 | } 67 | 68 | input := authentication.PrivateKeySignerInput{ 69 | KeyID: keyID, 70 | PrivateKeyMaterial: keyBytes, 71 | AccountName: accountName, 72 | Username: userName, 73 | } 74 | signer, err = authentication.NewPrivateKeySigner(input) 75 | if err != nil { 76 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 77 | } 78 | } 79 | 80 | config := &triton.ClientConfig{ 81 | MantaURL: os.Getenv("TRITON_URL"), 82 | AccountName: accountName, 83 | Username: userName, 84 | Signers: []authentication.Signer{signer}, 85 | } 86 | 87 | client, err := storage.NewClient(config) 88 | if err != nil { 89 | log.Fatalf("NewClient: %v", err) 90 | } 91 | 92 | reader, err := os.Open("/tmp/foo.txt") 93 | if err != nil { 94 | log.Fatalf("os.Open: %v", err) 95 | } 96 | defer reader.Close() 97 | 98 | err = client.Objects().Put(context.Background(), &storage.PutObjectInput{ 99 | ObjectPath: "/stor/foo.txt", 100 | ObjectReader: reader, 101 | }) 102 | if err != nil { 103 | log.Fatalf("storage.Objects.Put: %v", err) 104 | } 105 | fmt.Println("Successfully uploaded /tmp/foo.txt!") 106 | } 107 | -------------------------------------------------------------------------------- /examples/storage/sign_url/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package main 10 | 11 | import ( 12 | "encoding/pem" 13 | "io/ioutil" 14 | "log" 15 | "os" 16 | 17 | "net/http" 18 | "time" 19 | 20 | triton "github.com/joyent/triton-go/v2" 21 | "github.com/joyent/triton-go/v2/authentication" 22 | "github.com/joyent/triton-go/v2/storage" 23 | ) 24 | 25 | func main() { 26 | keyID := os.Getenv("TRITON_KEY_ID") 27 | accountName := os.Getenv("TRITON_ACCOUNT") 28 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 29 | userName := os.Getenv("TRITON_USER") 30 | 31 | var signer authentication.Signer 32 | var err error 33 | 34 | if keyMaterial == "" { 35 | input := authentication.SSHAgentSignerInput{ 36 | KeyID: keyID, 37 | AccountName: accountName, 38 | Username: userName, 39 | } 40 | signer, err = authentication.NewSSHAgentSigner(input) 41 | if err != nil { 42 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 43 | } 44 | } else { 45 | var keyBytes []byte 46 | if _, err = os.Stat(keyMaterial); err == nil { 47 | keyBytes, err = ioutil.ReadFile(keyMaterial) 48 | if err != nil { 49 | log.Fatalf("Error reading key material from %s: %s", 50 | keyMaterial, err) 51 | } 52 | block, _ := pem.Decode(keyBytes) 53 | if block == nil { 54 | log.Fatalf( 55 | "Failed to read key material '%s': no key found", keyMaterial) 56 | } 57 | 58 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 59 | log.Fatalf( 60 | "Failed to read key '%s': password protected keys are\n"+ 61 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 62 | } 63 | 64 | } else { 65 | keyBytes = []byte(keyMaterial) 66 | } 67 | 68 | input := authentication.PrivateKeySignerInput{ 69 | KeyID: keyID, 70 | PrivateKeyMaterial: keyBytes, 71 | AccountName: accountName, 72 | Username: userName, 73 | } 74 | signer, err = authentication.NewPrivateKeySigner(input) 75 | if err != nil { 76 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 77 | } 78 | } 79 | 80 | config := &triton.ClientConfig{ 81 | MantaURL: os.Getenv("TRITON_URL"), 82 | AccountName: accountName, 83 | Username: userName, 84 | Signers: []authentication.Signer{signer}, 85 | } 86 | 87 | client, err := storage.NewClient(config) 88 | if err != nil { 89 | log.Fatalf("NewClient: %v", err) 90 | } 91 | 92 | input := &storage.SignURLInput{ 93 | ObjectPath: "/stor/books/treasure_island.txt", 94 | Method: http.MethodGet, 95 | ValidityPeriod: 5 * time.Minute, 96 | } 97 | signed, err := client.SignURL(input) 98 | if err != nil { 99 | log.Fatalf("SignURL: %v", err) 100 | } 101 | 102 | log.Printf("Signed URL: %s", signed.SignedURL("http")) 103 | } 104 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/joyent/triton-go/v2 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af 7 | github.com/cockroachdb/apd v1.1.0 // indirect 8 | github.com/dustin/go-humanize v1.0.0 9 | github.com/imdario/mergo v0.3.6 10 | github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect 11 | github.com/jackc/pgx v3.3.0+incompatible 12 | github.com/lib/pq v1.1.1 // indirect 13 | github.com/mattn/go-isatty v0.0.8 14 | github.com/mattn/go-runewidth v0.0.3 // indirect 15 | github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4 16 | github.com/pkg/errors v0.9.1 17 | github.com/rs/zerolog v1.4.0 18 | github.com/satori/go.uuid v1.2.0 // indirect 19 | github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627 20 | github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5 // indirect 21 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 22 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect 23 | github.com/spf13/afero v1.2.1 // indirect 24 | github.com/spf13/cobra v0.0.5 25 | github.com/spf13/pflag v1.0.3 26 | github.com/spf13/viper v1.4.0 27 | github.com/stretchr/testify v1.3.0 // indirect 28 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect 29 | golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect 30 | golang.org/x/text v0.3.2 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /identity/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package identity 10 | 11 | import ( 12 | "net/http" 13 | 14 | triton "github.com/joyent/triton-go/v2" 15 | "github.com/joyent/triton-go/v2/client" 16 | ) 17 | 18 | type IdentityClient struct { 19 | Client *client.Client 20 | } 21 | 22 | func newIdentityClient(client *client.Client) *IdentityClient { 23 | return &IdentityClient{ 24 | Client: client, 25 | } 26 | } 27 | 28 | // NewClient returns a new client for working with Identity endpoints and 29 | // resources within CloudAPI 30 | func NewClient(config *triton.ClientConfig) (*IdentityClient, error) { 31 | // TODO: Utilize config interface within the function itself 32 | client, err := client.New( 33 | config.TritonURL, 34 | config.MantaURL, 35 | config.AccountName, 36 | config.Signers..., 37 | ) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return newIdentityClient(client), nil 42 | } 43 | 44 | // SetHeaders allows a consumer of the current client to set custom headers for 45 | // the next backend HTTP request sent to CloudAPI 46 | func (c *IdentityClient) SetHeader(header *http.Header) { 47 | c.Client.RequestHeader = header 48 | } 49 | 50 | // Roles returns a Roles client used for accessing functions pertaining to 51 | // Role functionality in the Triton API. 52 | func (c *IdentityClient) Roles() *RolesClient { 53 | return &RolesClient{c.Client} 54 | } 55 | 56 | // Users returns a Users client used for accessing functions pertaining to 57 | // User functionality in the Triton API. 58 | func (c *IdentityClient) Users() *UsersClient { 59 | return &UsersClient{c.Client} 60 | } 61 | 62 | // Policies returns a Policies client used for accessing functions pertaining to 63 | // Policy functionality in the Triton API. 64 | func (c *IdentityClient) Policies() *PoliciesClient { 65 | return &PoliciesClient{c.Client} 66 | } 67 | -------------------------------------------------------------------------------- /network/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package network 10 | 11 | import ( 12 | "net/http" 13 | 14 | triton "github.com/joyent/triton-go/v2" 15 | "github.com/joyent/triton-go/v2/client" 16 | ) 17 | 18 | type NetworkClient struct { 19 | Client *client.Client 20 | } 21 | 22 | func newNetworkClient(client *client.Client) *NetworkClient { 23 | return &NetworkClient{ 24 | Client: client, 25 | } 26 | } 27 | 28 | // NewClient returns a new client for working with Network endpoints and 29 | // resources within CloudAPI 30 | func NewClient(config *triton.ClientConfig) (*NetworkClient, error) { 31 | // TODO: Utilize config interface within the function itself 32 | client, err := client.New( 33 | config.TritonURL, 34 | config.MantaURL, 35 | config.AccountName, 36 | config.Signers..., 37 | ) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return newNetworkClient(client), nil 42 | } 43 | 44 | // SetHeaders allows a consumer of the current client to set custom headers for 45 | // the next backend HTTP request sent to CloudAPI 46 | func (c *NetworkClient) SetHeader(header *http.Header) { 47 | c.Client.RequestHeader = header 48 | } 49 | 50 | // Fabrics returns a FabricsClient used for accessing functions pertaining to 51 | // Fabric functionality in the Triton API. 52 | func (c *NetworkClient) Fabrics() *FabricsClient { 53 | return &FabricsClient{c.Client} 54 | } 55 | 56 | // Firewall returns a FirewallClient client used for accessing functions 57 | // pertaining to firewall functionality in the Triton API. 58 | func (c *NetworkClient) Firewall() *FirewallClient { 59 | return &FirewallClient{c.Client} 60 | } 61 | -------------------------------------------------------------------------------- /network/network.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package network 10 | 11 | import ( 12 | "context" 13 | "encoding/json" 14 | "net/http" 15 | "path" 16 | 17 | "github.com/joyent/triton-go/v2/client" 18 | "github.com/pkg/errors" 19 | ) 20 | 21 | type Network struct { 22 | Id string `json:"id"` 23 | Name string `json:"name"` 24 | Public bool `json:"public"` 25 | Fabric bool `json:"fabric"` 26 | Description string `json:"description"` 27 | Subnet string `json:"subnet"` 28 | ProvisioningStartIP string `json:"provision_start_ip"` 29 | ProvisioningEndIP string `json:"provision_end_ip"` 30 | Gateway string `json:"gateway"` 31 | Resolvers []string `json:"resolvers"` 32 | Routes map[string]string `json:"routes"` 33 | InternetNAT bool `json:"internet_nat"` 34 | } 35 | 36 | type ListInput struct{} 37 | 38 | func (c *NetworkClient) List(ctx context.Context, _ *ListInput) ([]*Network, error) { 39 | fullPath := path.Join("/", c.Client.AccountName, "networks") 40 | reqInputs := client.RequestInput{ 41 | Method: http.MethodGet, 42 | Path: fullPath, 43 | } 44 | respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) 45 | if respReader != nil { 46 | defer respReader.Close() 47 | } 48 | if err != nil { 49 | return nil, errors.Wrap(err, "unable to list networks") 50 | } 51 | 52 | var result []*Network 53 | decoder := json.NewDecoder(respReader) 54 | if err = decoder.Decode(&result); err != nil { 55 | return nil, errors.Wrap(err, "unable to decode list networks response") 56 | } 57 | 58 | return result, nil 59 | } 60 | 61 | type GetInput struct { 62 | ID string 63 | } 64 | 65 | func (c *NetworkClient) Get(ctx context.Context, input *GetInput) (*Network, error) { 66 | fullPath := path.Join("/", c.Client.AccountName, "networks", input.ID) 67 | reqInputs := client.RequestInput{ 68 | Method: http.MethodGet, 69 | Path: fullPath, 70 | } 71 | respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) 72 | if respReader != nil { 73 | defer respReader.Close() 74 | } 75 | if err != nil { 76 | return nil, errors.Wrap(err, "unable to get network") 77 | } 78 | 79 | var result *Network 80 | decoder := json.NewDecoder(respReader) 81 | if err = decoder.Decode(&result); err != nil { 82 | return nil, errors.Wrap(err, "unable to decode get network response") 83 | } 84 | 85 | return result, nil 86 | } 87 | -------------------------------------------------------------------------------- /scripts/go-test-with-coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.txt 5 | 6 | for d in $(go list ./... | grep -v vendor | grep -v examples | grep -v testutils); do 7 | go test -coverprofile=profile.out -covermode=atomic $d 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt 10 | rm profile.out 11 | fi 12 | done -------------------------------------------------------------------------------- /scripts/gofmt-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright 2019 Joyent, Inc. 10 | # 11 | 12 | # 13 | # Determine the list of Go source files to check for gofmt problems. Note that 14 | # we do not check the formatting of files in the vendor/ tree. 15 | # 16 | if ! files=$(git ls-files '*.go' ':!:vendor/*') || [[ -z $files ]]; then 17 | printf 'ERROR: could not find go file list\n' >&2 18 | exit 1 19 | fi 20 | 21 | if ! diff=$(gofmt -d $files); then 22 | printf 'ERROR: could not run "gofmt -d"\n' >&2 23 | exit 1 24 | fi 25 | 26 | if [[ -z $diff ]]; then 27 | printf 'gofmt check ok\n' 28 | exit 0 29 | fi 30 | 31 | # 32 | # Report the detected formatting issues and exit non-zero so that "make check" 33 | # will fail. 34 | # 35 | printf -- '---- GOFMT ERRORS -------\n' 36 | printf -- '%s\n' "$diff" 37 | exit 2 38 | -------------------------------------------------------------------------------- /services/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package services 10 | 11 | import ( 12 | "net/http" 13 | 14 | triton "github.com/joyent/triton-go/v2" 15 | "github.com/joyent/triton-go/v2/client" 16 | ) 17 | 18 | type ServiceGroupClient struct { 19 | Client *client.Client 20 | } 21 | 22 | func newServiceGroupClient(client *client.Client) *ServiceGroupClient { 23 | return &ServiceGroupClient{ 24 | Client: client, 25 | } 26 | } 27 | 28 | // NewClient returns a new client for working with Service Groups endpoints and 29 | // resources within TSG 30 | func NewClient(config *triton.ClientConfig) (*ServiceGroupClient, error) { 31 | // TODO: Utilize config interface within the function itself 32 | client, err := client.New( 33 | config.TritonURL, 34 | config.MantaURL, 35 | config.AccountName, 36 | config.Signers..., 37 | ) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return newServiceGroupClient(client), nil 42 | } 43 | 44 | // SetHeaders allows a consumer of the current client to set custom headers for 45 | // the next backend HTTP request sent to CloudAPI 46 | func (c *ServiceGroupClient) SetHeader(header *http.Header) { 47 | c.Client.RequestHeader = header 48 | } 49 | 50 | // Templates returns a TemplatesClient used for accessing functions pertaining 51 | // to Instance Templates functionality in the TSG API. 52 | func (c *ServiceGroupClient) Templates() *TemplatesClient { 53 | return &TemplatesClient{c.Client} 54 | } 55 | 56 | // Groups returns a GroupsClient used for accessing functions pertaining 57 | // to Service Groups functionality in the TSG API. 58 | func (c *ServiceGroupClient) Groups() *GroupsClient { 59 | return &GroupsClient{c.Client} 60 | } 61 | -------------------------------------------------------------------------------- /storage/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package storage 10 | 11 | import ( 12 | "net/http" 13 | 14 | triton "github.com/joyent/triton-go/v2" 15 | "github.com/joyent/triton-go/v2/client" 16 | ) 17 | 18 | type StorageClient struct { 19 | Client *client.Client 20 | } 21 | 22 | func newStorageClient(client *client.Client) *StorageClient { 23 | return &StorageClient{ 24 | Client: client, 25 | } 26 | } 27 | 28 | // NewClient returns a new client for working with Storage endpoints and 29 | // resources within CloudAPI 30 | func NewClient(config *triton.ClientConfig) (*StorageClient, error) { 31 | // TODO: Utilize config interface within the function itself 32 | client, err := client.New( 33 | config.TritonURL, 34 | config.MantaURL, 35 | config.AccountName, 36 | config.Signers..., 37 | ) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return newStorageClient(client), nil 42 | } 43 | 44 | // SetHeader allows a consumer of the current client to set a custom header for 45 | // the next backend HTTP request sent to CloudAPI. 46 | func (c *StorageClient) SetHeader(header *http.Header) { 47 | c.Client.RequestHeader = header 48 | } 49 | 50 | // Dir returns a DirectoryClient used for accessing functions pertaining to 51 | // Directories functionality of the Manta API. 52 | func (c *StorageClient) Dir() *DirectoryClient { 53 | return &DirectoryClient{c.Client} 54 | } 55 | 56 | // Jobs returns a JobClient used for accessing functions pertaining to Jobs 57 | // functionality of the Triton Object Storage API. 58 | func (c *StorageClient) Jobs() *JobClient { 59 | return &JobClient{c.Client} 60 | } 61 | 62 | // Objects returns an ObjectsClient used for accessing functions pertaining to 63 | // Objects functionality of the Triton Object Storage API. 64 | func (c *StorageClient) Objects() *ObjectsClient { 65 | return &ObjectsClient{c.Client} 66 | } 67 | 68 | // SnapLinks returns an SnapLinksClient used for accessing functions pertaining to 69 | // SnapLinks functionality of the Triton Object Storage API. 70 | func (c *StorageClient) SnapLinks() *SnapLinksClient { 71 | return &SnapLinksClient{c.Client} 72 | } 73 | -------------------------------------------------------------------------------- /storage/signing.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package storage 10 | 11 | import ( 12 | "bytes" 13 | "fmt" 14 | "net/url" 15 | "path" 16 | "strconv" 17 | "strings" 18 | "time" 19 | 20 | "github.com/pkg/errors" 21 | ) 22 | 23 | // SignURLInput represents parameters to a SignURL operation. 24 | type SignURLInput struct { 25 | ValidityPeriod time.Duration 26 | Method string 27 | ObjectPath string 28 | } 29 | 30 | // SignURLOutput contains the outputs of a SignURL operation. To simply 31 | // access the signed URL, use the SignedURL method. 32 | type SignURLOutput struct { 33 | host string 34 | objectPath string 35 | Method string 36 | Algorithm string 37 | Signature string 38 | Expires string 39 | KeyID string 40 | } 41 | 42 | // SignedURL returns a signed URL for the given scheme. Valid schemes are 43 | // `http` and `https`. 44 | func (output *SignURLOutput) SignedURL(scheme string) string { 45 | query := &url.Values{} 46 | query.Set("algorithm", output.Algorithm) 47 | query.Set("expires", output.Expires) 48 | query.Set("keyId", output.KeyID) 49 | query.Set("signature", output.Signature) 50 | 51 | sUrl := url.URL{} 52 | sUrl.Scheme = scheme 53 | sUrl.Host = output.host 54 | sUrl.Path = output.objectPath 55 | sUrl.RawQuery = query.Encode() 56 | 57 | return sUrl.String() 58 | } 59 | 60 | // SignURL creates a time-expiring URL that can be shared with others. 61 | // This is useful to generate HTML links, for example. 62 | func (s *StorageClient) SignURL(input *SignURLInput) (*SignURLOutput, error) { 63 | output := &SignURLOutput{ 64 | host: s.Client.MantaURL.Host, 65 | objectPath: fmt.Sprintf("/%s%s", s.Client.AccountName, input.ObjectPath), 66 | Method: input.Method, 67 | Algorithm: strings.ToUpper(s.Client.Authorizers[0].DefaultAlgorithm()), 68 | Expires: strconv.FormatInt(time.Now().Add(input.ValidityPeriod).Unix(), 10), 69 | KeyID: path.Join("/", s.Client.AccountName, "keys", s.Client.Authorizers[0].KeyFingerprint()), 70 | } 71 | 72 | toSign := bytes.Buffer{} 73 | toSign.WriteString(input.Method + "\n") 74 | toSign.WriteString(s.Client.MantaURL.Host + "\n") 75 | toSign.WriteString(fmt.Sprintf("/%s%s\n", s.Client.AccountName, input.ObjectPath)) 76 | 77 | query := &url.Values{} 78 | query.Set("algorithm", output.Algorithm) 79 | query.Set("expires", output.Expires) 80 | query.Set("keyId", output.KeyID) 81 | toSign.WriteString(query.Encode()) 82 | 83 | signature, _, err := s.Client.Authorizers[0].SignRaw(toSign.String()) 84 | if err != nil { 85 | return nil, errors.Wrapf(err, "error signing string") 86 | } 87 | 88 | output.Signature = signature 89 | return output, nil 90 | } 91 | -------------------------------------------------------------------------------- /storage/snaplink.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package storage 10 | 11 | import ( 12 | "context" 13 | "fmt" 14 | "net/http" 15 | 16 | "github.com/joyent/triton-go/v2/client" 17 | "github.com/pkg/errors" 18 | ) 19 | 20 | type SnapLinksClient struct { 21 | client *client.Client 22 | } 23 | 24 | // PutSnapLinkInput represents parameters to a PutSnapLink operation. 25 | type PutSnapLinkInput struct { 26 | LinkPath string 27 | SourcePath string 28 | } 29 | 30 | // PutSnapLink creates a SnapLink to an object. 31 | func (s *SnapLinksClient) Put(ctx context.Context, input *PutSnapLinkInput) error { 32 | linkPath := fmt.Sprintf("/%s%s", s.client.AccountName, input.LinkPath) 33 | sourcePath := fmt.Sprintf("/%s%s", s.client.AccountName, input.SourcePath) 34 | headers := &http.Header{} 35 | headers.Set("Content-Type", "application/json; type=link") 36 | headers.Set("location", sourcePath) 37 | headers.Set("Accept", "~1.0") 38 | headers.Set("Accept-Version", "application/json, */*") 39 | 40 | reqInput := client.RequestInput{ 41 | Method: http.MethodPut, 42 | Path: linkPath, 43 | Headers: headers, 44 | } 45 | respBody, _, err := s.client.ExecuteRequestStorage(ctx, reqInput) 46 | if respBody != nil { 47 | defer respBody.Close() 48 | } 49 | if err != nil { 50 | return errors.Wrapf(err, "unable to put snaplink") 51 | } 52 | 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /storage/snaplink_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package storage_test 10 | 11 | import ( 12 | "context" 13 | "net/http" 14 | "path" 15 | "strings" 16 | "testing" 17 | 18 | "github.com/joyent/triton-go/v2/storage" 19 | "github.com/joyent/triton-go/v2/testutils" 20 | "github.com/pkg/errors" 21 | ) 22 | 23 | const accountURL = "testing" 24 | 25 | var ( 26 | putSnapLinkErrorType = errors.New("unable to put snaplink") 27 | linkPath = "/stor/foobar.json" 28 | brokenLinkPath = "/missingfolder/foo.json" 29 | sourcePath = "/stor/foo.json" 30 | ) 31 | 32 | func MockStorageClient() *storage.StorageClient { 33 | return &storage.StorageClient{ 34 | Client: testutils.NewMockClient(testutils.MockClientInput{ 35 | AccountName: accountURL, 36 | }), 37 | } 38 | } 39 | 40 | func TestPutSnaplink(t *testing.T) { 41 | storageClient := MockStorageClient() 42 | 43 | do := func(ctx context.Context, sc *storage.StorageClient) error { 44 | defer testutils.DeactivateClient() 45 | 46 | return sc.SnapLinks().Put(ctx, &storage.PutSnapLinkInput{ 47 | LinkPath: linkPath, 48 | SourcePath: sourcePath, 49 | }) 50 | } 51 | 52 | t.Run("successful", func(t *testing.T) { 53 | testutils.RegisterResponder("PUT", path.Join("/", accountURL, linkPath), putSnapLinkSuccess) 54 | 55 | err := do(context.Background(), storageClient) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | }) 60 | 61 | t.Run("error", func(t *testing.T) { 62 | testutils.RegisterResponder("PUT", path.Join("/", accountURL, brokenLinkPath), putSnapLinkError) 63 | 64 | err := do(context.Background(), storageClient) 65 | if err == nil { 66 | t.Fatal(err) 67 | } 68 | 69 | if !strings.Contains(err.Error(), "unable to put snaplink") { 70 | t.Errorf("expected error to equal testError: found %v", err) 71 | } 72 | }) 73 | } 74 | 75 | func putSnapLinkSuccess(req *http.Request) (*http.Response, error) { 76 | header := http.Header{} 77 | header.Add("Content-Type", "application/json") 78 | 79 | return &http.Response{ 80 | StatusCode: http.StatusNoContent, 81 | Header: header, 82 | }, nil 83 | } 84 | 85 | func putSnapLinkError(req *http.Request) (*http.Response, error) { 86 | return nil, putSnapLinkErrorType 87 | } 88 | -------------------------------------------------------------------------------- /testutils/basic_runner.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package testutils 10 | 11 | import ( 12 | "log" 13 | "sync" 14 | "sync/atomic" 15 | ) 16 | 17 | type runState int32 18 | 19 | const ( 20 | stateIdle runState = iota 21 | stateRunning 22 | stateCancelling 23 | ) 24 | 25 | type basicRunner struct { 26 | Steps []Step 27 | 28 | cancelCh chan struct{} 29 | doneCh chan struct{} 30 | state runState 31 | l sync.Mutex 32 | } 33 | 34 | func (b *basicRunner) Run(state TritonStateBag) { 35 | b.l.Lock() 36 | if b.state != stateIdle { 37 | panic("already running") 38 | } 39 | 40 | cancelCh := make(chan struct{}) 41 | doneCh := make(chan struct{}) 42 | b.cancelCh = cancelCh 43 | b.doneCh = doneCh 44 | b.state = stateRunning 45 | b.l.Unlock() 46 | 47 | defer func() { 48 | b.l.Lock() 49 | b.cancelCh = nil 50 | b.doneCh = nil 51 | b.state = stateIdle 52 | close(doneCh) 53 | b.l.Unlock() 54 | }() 55 | 56 | // This goroutine listens for cancels and puts the StateCancelled key 57 | // as quickly as possible into the state bag to mark it. 58 | go func() { 59 | select { 60 | case <-cancelCh: 61 | // Flag cancel and wait for finish 62 | state.Put(StateCancelled, true) 63 | <-doneCh 64 | case <-doneCh: 65 | } 66 | }() 67 | 68 | for _, step := range b.Steps { 69 | // We also check for cancellation here since we can't be sure 70 | // the goroutine that is running to set it actually ran. 71 | if runState(atomic.LoadInt32((*int32)(&b.state))) == stateCancelling { 72 | state.Put(StateCancelled, true) 73 | break 74 | } 75 | 76 | action := step.Run(state) 77 | defer step.Cleanup(state) 78 | 79 | if _, ok := state.GetOk(StateCancelled); ok { 80 | break 81 | } 82 | 83 | if action == Halt { 84 | log.Println("[INFO] Halt requested by current step") 85 | state.Put(StateHalted, true) 86 | break 87 | } 88 | } 89 | } 90 | 91 | func (b *basicRunner) Cancel() { 92 | b.l.Lock() 93 | switch b.state { 94 | case stateIdle: 95 | // Not running, so Cancel is... done. 96 | b.l.Unlock() 97 | return 98 | case stateRunning: 99 | // Running, so mark that we cancelled and set the state 100 | close(b.cancelCh) 101 | b.state = stateCancelling 102 | fallthrough 103 | case stateCancelling: 104 | // Already cancelling, so just wait until we're done 105 | ch := b.doneCh 106 | b.l.Unlock() 107 | <-ch 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /testutils/random.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package testutils 10 | 11 | import ( 12 | "fmt" 13 | "math/rand" 14 | 15 | "github.com/sean-/seed" 16 | ) 17 | 18 | func init() { 19 | seed.Init() 20 | } 21 | 22 | // RandString generates a random alphanumeric string of the length specified 23 | func RandString(strlen int) string { 24 | return RandStringFromCharSet(strlen, CharSetAlphaNum) 25 | } 26 | 27 | // RandPrefixString generates a random alphanumeric string of the length specified 28 | // with the given prefix 29 | func RandPrefixString(prefix string, strlen int) string { 30 | requiredLength := strlen - len(prefix) 31 | return fmt.Sprintf("%s%s", prefix, RandString(requiredLength)) 32 | } 33 | 34 | // RandStringFromCharSet generates a random string by selecting characters from 35 | // the charset provided 36 | func RandStringFromCharSet(strlen int, charSet string) string { 37 | result := make([]byte, strlen) 38 | for i := 0; i < strlen; i++ { 39 | result[i] = charSet[rand.Intn(len(charSet))] 40 | } 41 | return string(result) 42 | } 43 | 44 | const ( 45 | // CharSetAlphaNum is the alphanumeric character set for use with 46 | // RandStringFromCharSet 47 | CharSetAlphaNum = "abcdefghijklmnopqrstuvwxyz012346789" 48 | 49 | // CharSetAlpha is the alphabetical character set for use with 50 | // RandStringFromCharSet 51 | CharSetAlpha = "abcdefghijklmnopqrstuvwxyz" 52 | ) 53 | -------------------------------------------------------------------------------- /testutils/runner.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package testutils 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | "io/ioutil" 15 | "log" 16 | "os" 17 | "testing" 18 | 19 | triton "github.com/joyent/triton-go/v2" 20 | "github.com/joyent/triton-go/v2/authentication" 21 | ) 22 | 23 | const TestEnvVar = "TRITON_TEST" 24 | 25 | type TestCase struct { 26 | Steps []Step 27 | State TritonStateBag 28 | } 29 | 30 | func AccTest(t *testing.T, c TestCase) { 31 | // We only run acceptance tests if an env var is set because they're 32 | // slow and generally require some outside configuration. 33 | if os.Getenv(TestEnvVar) == "" { 34 | t.Skip(fmt.Sprintf( 35 | "Acceptance tests skipped unless env '%s' set", 36 | TestEnvVar)) 37 | return 38 | } 39 | 40 | // Disable extra logging output unless TRITON_VERBOSE_TESTS is set. 41 | if triton.GetEnv("VERBOSE_TESTS") == "" { 42 | log.SetOutput(ioutil.Discard) 43 | } 44 | 45 | tritonURL := triton.GetEnv("URL") 46 | tritonAccount := triton.GetEnv("ACCOUNT") 47 | tritonKeyID := triton.GetEnv("KEY_ID") 48 | tritonKeyMaterial := triton.GetEnv("KEY_MATERIAL") 49 | userName := triton.GetEnv("USER") 50 | mantaURL := triton.GetEnv("MANTA_URL") 51 | 52 | var prerollErrors []error 53 | if tritonURL == "" { 54 | prerollErrors = append(prerollErrors, 55 | errors.New("The TRITON_URL environment variable must be set to run acceptance tests")) 56 | } 57 | if tritonAccount == "" { 58 | prerollErrors = append(prerollErrors, 59 | errors.New("The TRITON_ACCOUNT environment variable must be set to run acceptance tests")) 60 | } 61 | if tritonKeyID == "" { 62 | prerollErrors = append(prerollErrors, 63 | errors.New("The TRITON_KEY_ID environment variable must be set to run acceptance tests")) 64 | } 65 | if len(prerollErrors) > 0 { 66 | for _, err := range prerollErrors { 67 | t.Error(err) 68 | } 69 | t.FailNow() 70 | } 71 | 72 | var signer authentication.Signer 73 | var err error 74 | if tritonKeyMaterial != "" { 75 | log.Println("[INFO] Creating Triton Client with Private Key Signer...") 76 | input := authentication.PrivateKeySignerInput{ 77 | KeyID: tritonKeyID, 78 | PrivateKeyMaterial: []byte(tritonKeyMaterial), 79 | AccountName: tritonAccount, 80 | Username: userName, 81 | } 82 | signer, err = authentication.NewPrivateKeySigner(input) 83 | if err != nil { 84 | t.Fatalf("Error creating private key signer: %s", err) 85 | } 86 | } else { 87 | log.Println("[INFO] Creating Triton Client with SSH Key Signer...") 88 | input := authentication.SSHAgentSignerInput{ 89 | KeyID: tritonKeyID, 90 | AccountName: tritonAccount, 91 | Username: userName, 92 | } 93 | signer, err = authentication.NewSSHAgentSigner(input) 94 | if err != nil { 95 | t.Fatalf("Error creating SSH Agent signer: %s", err) 96 | } 97 | } 98 | 99 | // Old world... we spun up a universal client. This is pushed deeper into 100 | // the process within `testutils.StepClient`. 101 | // 102 | // client, err := NewClient(tritonURL, tritonAccount, signer) 103 | // if err != nil { 104 | // t.Fatalf("Error creating Triton Client: %s", err) 105 | // } 106 | 107 | config := &triton.ClientConfig{ 108 | TritonURL: tritonURL, 109 | MantaURL: mantaURL, 110 | AccountName: tritonAccount, 111 | Username: userName, 112 | Signers: []authentication.Signer{signer}, 113 | } 114 | 115 | state := &basicTritonStateBag{ 116 | TritonConfig: config, 117 | } 118 | 119 | runner := &basicRunner{ 120 | Steps: c.Steps, 121 | } 122 | 123 | runner.Run(state) 124 | 125 | if errs := state.ErrorsOrNil(); errs != nil { 126 | for _, err := range errs { 127 | t.Error(err) 128 | } 129 | t.Fatal("\n\nThere may be dangling resources in your Triton account!") 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /testutils/statebag.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package testutils 10 | 11 | import ( 12 | "sync" 13 | 14 | triton "github.com/joyent/triton-go/v2" 15 | ) 16 | 17 | type TritonStateBag interface { 18 | Get(string) interface{} 19 | GetOk(string) (interface{}, bool) 20 | Put(string, interface{}) 21 | Remove(string) 22 | 23 | Config() *triton.ClientConfig 24 | 25 | Client() interface{} 26 | PutClient(interface{}) 27 | 28 | AppendError(error) 29 | ErrorsOrNil() []error 30 | } 31 | 32 | // TritonStateBag implements StateBag by using a normal map underneath 33 | // protected by a RWMutex. 34 | type basicTritonStateBag struct { 35 | TritonConfig *triton.ClientConfig 36 | TritonClient interface{} 37 | 38 | errors []error 39 | data map[string]interface{} 40 | 41 | l sync.RWMutex 42 | once sync.Once 43 | } 44 | 45 | func (b *basicTritonStateBag) Config() *triton.ClientConfig { 46 | b.l.RLock() 47 | defer b.l.RUnlock() 48 | 49 | return b.TritonConfig 50 | } 51 | 52 | func (b *basicTritonStateBag) Client() interface{} { 53 | b.l.RLock() 54 | defer b.l.RUnlock() 55 | 56 | return b.TritonClient 57 | } 58 | 59 | func (b *basicTritonStateBag) PutClient(client interface{}) { 60 | b.l.Lock() 61 | defer b.l.Unlock() 62 | 63 | b.TritonClient = client 64 | } 65 | 66 | func (b *basicTritonStateBag) AppendError(err error) { 67 | b.l.Lock() 68 | defer b.l.Unlock() 69 | 70 | if b.errors == nil { 71 | b.errors = make([]error, 0, 1) 72 | } 73 | 74 | b.errors = append(b.errors, err) 75 | } 76 | 77 | func (b *basicTritonStateBag) ErrorsOrNil() []error { 78 | b.l.RLock() 79 | defer b.l.RUnlock() 80 | 81 | return b.errors 82 | } 83 | 84 | func (b *basicTritonStateBag) Get(k string) interface{} { 85 | result, _ := b.GetOk(k) 86 | return result 87 | } 88 | 89 | func (b *basicTritonStateBag) GetOk(k string) (interface{}, bool) { 90 | b.l.RLock() 91 | defer b.l.RUnlock() 92 | 93 | result, ok := b.data[k] 94 | return result, ok 95 | } 96 | 97 | func (b *basicTritonStateBag) Put(k string, v interface{}) { 98 | b.l.Lock() 99 | defer b.l.Unlock() 100 | 101 | // Make sure the map is initialized one time, on write 102 | b.once.Do(func() { 103 | b.data = make(map[string]interface{}) 104 | }) 105 | 106 | // Write the data 107 | b.data[k] = v 108 | } 109 | 110 | func (b *basicTritonStateBag) Remove(k string) { 111 | b.l.Lock() 112 | defer b.l.Unlock() 113 | 114 | delete(b.data, k) 115 | } 116 | -------------------------------------------------------------------------------- /testutils/step.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2018, Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package testutils 10 | 11 | type StepAction uint 12 | 13 | const ( 14 | Continue StepAction = iota 15 | Halt 16 | ) 17 | 18 | const ( 19 | StateCancelled = "cancelled" 20 | StateHalted = "halted" 21 | ) 22 | 23 | type Step interface { 24 | Run(TritonStateBag) StepAction 25 | 26 | Cleanup(TritonStateBag) 27 | } 28 | -------------------------------------------------------------------------------- /triton.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package triton 10 | 11 | import ( 12 | "os" 13 | 14 | "github.com/joyent/triton-go/v2/authentication" 15 | ) 16 | 17 | // Universal package used for defining configuration used across all client 18 | // constructors. 19 | 20 | // ClientConfig is a placeholder/input struct around the behavior of configuring 21 | // a client constructor through the implementation's runtime environment 22 | // (SDC/MANTA env vars). 23 | type ClientConfig struct { 24 | TritonURL string 25 | MantaURL string 26 | AccountName string 27 | Username string 28 | Signers []authentication.Signer 29 | } 30 | 31 | var envPrefixes = []string{"TRITON", "SDC"} 32 | 33 | // GetEnv looks up environment variables using the preferred "TRITON" prefix, 34 | // but falls back to the retired "SDC" prefix. For example, looking up "USER" 35 | // will search for "TRITON_USER" followed by "SDC_USER". If the environment 36 | // variable is not set, an empty string is returned. GetEnv() is used to aid in 37 | // the transition and deprecation of the "SDC_*" environment variables. 38 | func GetEnv(name string) string { 39 | for _, prefix := range envPrefixes { 40 | if val, found := os.LookupEnv(prefix + "_" + name); found { 41 | return val 42 | } 43 | } 44 | 45 | return "" 46 | } 47 | -------------------------------------------------------------------------------- /triton_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package triton_test 10 | 11 | import ( 12 | "os" 13 | "testing" 14 | 15 | triton "github.com/joyent/triton-go/v2" 16 | ) 17 | 18 | func TestGetEnv(t *testing.T) { 19 | tests := []struct { 20 | name string 21 | varname string 22 | input string 23 | value string 24 | }{ 25 | {"Triton", "TRITON_NAME", "NAME", "good"}, 26 | {"SDC", "SDC_NAME", "NAME", "good"}, 27 | {"unrelated", "BAD_NAME", "NAME", ""}, 28 | {"missing", "", "NAME", ""}, 29 | } 30 | for _, test := range tests { 31 | t.Run(test.name, func(t *testing.T) { 32 | os.Setenv(test.varname, test.value) 33 | defer os.Unsetenv(test.varname) 34 | 35 | if val := triton.GetEnv(test.input); val != test.value { 36 | t.Errorf("expected %s env var to be '%s': got '%s'", 37 | test.varname, test.value, val) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /utils/http_debugging.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package utils 10 | 11 | import ( 12 | "fmt" 13 | "io" 14 | "net/http" 15 | "net/http/httputil" 16 | "os" 17 | ) 18 | 19 | // This file is used for tracing HTTP requests (trace). 20 | // 21 | // All HTTP requests and responses through this transport will be printed to 22 | // stderr. 23 | 24 | // TraceRoundTripper to wrap a HTTP Transport. 25 | func TraceRoundTripper(in http.RoundTripper) http.RoundTripper { 26 | return &traceRoundTripper{inner: in, logger: os.Stderr} 27 | } 28 | 29 | type traceRoundTripper struct { 30 | inner http.RoundTripper 31 | logger io.Writer 32 | } 33 | 34 | func (d *traceRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 35 | d.dumpRequest(req) 36 | res, err := d.inner.RoundTrip(req) 37 | if err != nil { 38 | fmt.Fprintf(d.logger, "\n\tERROR for request: %v\n", err) 39 | } 40 | if res != nil { 41 | d.dumpResponse(res) 42 | } 43 | return res, err 44 | } 45 | 46 | func (d *traceRoundTripper) dumpRequest(r *http.Request) { 47 | dump, err := httputil.DumpRequestOut(r, true) 48 | if err != nil { 49 | fmt.Fprintf(d.logger, "\n\tERROR dumping: %v\n", err) 50 | return 51 | } 52 | d.dump("REQUEST", dump) 53 | } 54 | 55 | func (d *traceRoundTripper) dumpResponse(r *http.Response) { 56 | dump, err := httputil.DumpResponse(r, true) 57 | if err != nil { 58 | fmt.Fprintf(d.logger, "\n\tERROR dumping: %v\n", err) 59 | return 60 | } 61 | d.dump("RESPONSE", dump) 62 | } 63 | 64 | func (d *traceRoundTripper) dump(label string, dump []byte) { 65 | fmt.Fprintf(d.logger, "\n%s:\n--\n%s\n", label, string(dump)) 66 | if label == "RESPONSE" { 67 | fmt.Fprintf(d.logger, "\n") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | // 8 | 9 | package triton 10 | 11 | import ( 12 | "fmt" 13 | "runtime" 14 | ) 15 | 16 | // Version represents main version number of the current release 17 | // of the Triton-go SDK. 18 | const Version = "2.0.0" 19 | 20 | // Prerelease adds a pre-release marker to the version. 21 | // 22 | // If this is "" (empty string) then it means that it is a final release. 23 | // Otherwise, this is a pre-release such as "dev" (in development), "beta", 24 | // "rc1", etc. 25 | var Prerelease = "pre3" 26 | 27 | // UserAgent returns a Triton-go characteristic string that allows the 28 | // network protocol peers to identify the version, release and runtime 29 | // of the Triton-go client from which the requests originate. 30 | func UserAgent() string { 31 | if Prerelease != "" { 32 | return fmt.Sprintf("triton-go/%s-%s (%s-%s; %s)", Version, Prerelease, 33 | runtime.GOARCH, runtime.GOOS, runtime.Version()) 34 | } 35 | 36 | return fmt.Sprintf("triton-go/%s (%s-%s; %s)", Version, runtime.GOARCH, 37 | runtime.GOOS, runtime.Version()) 38 | } 39 | 40 | // CloudAPIMajorVersion specifies the CloudAPI version compatibility 41 | // for current release of the Triton-go SDK. 42 | const CloudAPIMajorVersion = "8" 43 | --------------------------------------------------------------------------------