├── .gitignore ├── .travis.yml ├── scripts ├── go-test-with-coverage.sh └── gofmt-check.sh ├── cmd ├── internal │ ├── command │ │ └── public.go │ ├── logger │ │ ├── pgx.go │ │ ├── format.go │ │ ├── level.go │ │ └── public.go │ └── config │ │ └── public.go ├── manta │ ├── main.go │ └── cmd │ │ ├── shell │ │ ├── public.go │ │ └── autocomplete │ │ │ ├── public.go │ │ │ └── bash │ │ │ └── public.go │ │ ├── version │ │ └── public.go │ │ ├── docs │ │ ├── public.go │ │ ├── man │ │ │ └── public.go │ │ └── md │ │ │ └── public.go │ │ └── list │ │ └── public.go ├── triton │ ├── main.go │ └── cmd │ │ ├── shell │ │ ├── public.go │ │ └── autocomplete │ │ │ ├── public.go │ │ │ └── bash │ │ │ └── public.go │ │ ├── account │ │ ├── public.go │ │ └── get │ │ │ └── public.go │ │ ├── version │ │ └── public.go │ │ ├── docs │ │ ├── public.go │ │ ├── man │ │ │ └── public.go │ │ └── md │ │ │ └── public.go │ │ ├── instances │ │ ├── count │ │ │ └── public.go │ │ ├── ip │ │ │ └── public.go │ │ ├── stop │ │ │ └── public.go │ │ ├── start │ │ │ └── public.go │ │ ├── reboot │ │ │ └── public.go │ │ ├── delete │ │ │ └── public.go │ │ ├── get │ │ │ └── public.go │ │ ├── list │ │ │ └── public.go │ │ └── public.go │ │ ├── accesskeys │ │ ├── get │ │ │ └── public.go │ │ ├── delete │ │ │ └── public.go │ │ ├── public.go │ │ ├── create │ │ │ └── public.go │ │ └── list │ │ │ └── public.go │ │ ├── keys │ │ ├── get │ │ │ └── public.go │ │ ├── delete │ │ │ └── public.go │ │ ├── create │ │ │ └── public.go │ │ ├── public.go │ │ └── list │ │ │ └── public.go │ │ ├── packages │ │ ├── public.go │ │ └── get │ │ │ └── public.go │ │ ├── datacenters │ │ └── public.go │ │ ├── services │ │ └── public.go │ │ └── root_test.go └── agent │ ├── network │ └── public.go │ ├── identity │ └── public.go │ ├── compute │ ├── sortImages.go │ └── sortInstances.go │ └── storage │ └── public.go ├── testutils ├── step.go ├── random.go ├── basic_runner.go ├── statebag.go └── runner.go ├── authentication ├── signer.go ├── agent_key_identifier.go ├── rsa_signature.go ├── signature.go ├── test_signer.go ├── ecdsa_signature.go └── util.go ├── triton_test.go ├── Makefile ├── go.mod ├── version.go ├── triton.go ├── storage ├── snaplink.go ├── client.go ├── snaplink_test.go └── signing.go ├── compute ├── ping.go ├── services.go ├── datacenters.go ├── client_test.go ├── client.go └── ping_test.go ├── utils └── http_debugging.go ├── network ├── client.go └── network.go ├── errors └── errors_test.go ├── services └── client.go ├── account ├── client.go ├── config.go ├── account.go └── keys.go ├── identity └── client.go └── examples ├── storage ├── mls │ └── main.go ├── force_delete │ └── main.go ├── sign_url │ └── main.go ├── object_put │ └── main.go ├── list_directory │ └── main.go └── force_put │ └── main.go ├── compute ├── volumes │ └── list_volumes │ │ └── main.go └── instances │ └── main.go └── account ├── keys └── main.go ├── account_info └── main.go └── config └── main.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 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /cmd/manta/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "os" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/manta/cmd" 16 | "github.com/rs/zerolog/log" 17 | "github.com/sean-/conswriter" 18 | ) 19 | 20 | func main() { 21 | defer func() { 22 | p := conswriter.GetTerminal() 23 | p.Wait() 24 | }() 25 | 26 | if err := cmd.Execute(); err != nil { 27 | log.Error().Err(err).Msg("unable to run") 28 | os.Exit(1) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cmd/triton/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "os" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd" 16 | "github.com/rs/zerolog/log" 17 | "github.com/sean-/conswriter" 18 | ) 19 | 20 | func main() { 21 | defer func() { 22 | p := conswriter.GetTerminal() 23 | p.Wait() 24 | }() 25 | 26 | if err := cmd.Execute(); err != nil { 27 | log.Error().Err(err).Msg("unable to run") 28 | os.Exit(1) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cmd/agent/network/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package network 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/config" 14 | "github.com/TritonDataCenter/triton-go/v2/network" 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | func NewGetNetworkClient(cfg *config.TritonClientConfig) (*network.NetworkClient, error) { 19 | networkClient, err := network.NewClient(cfg.Config) 20 | if err != nil { 21 | return nil, errors.Wrap(err, "Error Creating Triton Netowkr Client") 22 | } 23 | return networkClient, nil 24 | } 25 | -------------------------------------------------------------------------------- /cmd/agent/identity/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package identity 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/config" 14 | "github.com/TritonDataCenter/triton-go/v2/identity" 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | func NewGetIdentityClient(cfg *config.TritonClientConfig) (*identity.IdentityClient, error) { 19 | identityClient, err := identity.NewClient(cfg.Config) 20 | if err != nil { 21 | return nil, errors.Wrap(err, "Error Creating Triton Identity Client") 22 | } 23 | return identityClient, nil 24 | } 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/manta/cmd/shell/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package shell 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/manta/cmd/shell/autocomplete" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var Cmd = &command.Command{ 19 | Cobra: &cobra.Command{ 20 | Use: "shell", 21 | Short: "shell commands", 22 | }, 23 | 24 | Setup: func(parent *command.Command) error { 25 | cmds := []*command.Command{ 26 | autocomplete.Cmd, 27 | } 28 | 29 | for _, cmd := range cmds { 30 | cmd.Setup(cmd) 31 | parent.Cobra.AddCommand(cmd.Cobra) 32 | } 33 | 34 | return nil 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /cmd/triton/cmd/shell/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package shell 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/shell/autocomplete" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var Cmd = &command.Command{ 19 | Cobra: &cobra.Command{ 20 | Use: "shell", 21 | Short: "shell commands", 22 | }, 23 | 24 | Setup: func(parent *command.Command) error { 25 | cmds := []*command.Command{ 26 | autocomplete.Cmd, 27 | } 28 | 29 | for _, cmd := range cmds { 30 | cmd.Setup(cmd) 31 | parent.Cobra.AddCommand(cmd.Cobra) 32 | } 33 | 34 | return nil 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /cmd/agent/compute/sortImages.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package compute 11 | 12 | import ( 13 | "sort" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/compute" 16 | ) 17 | 18 | type imageSort []*compute.Image 19 | 20 | func sortImages(images []*compute.Image) []*compute.Image { 21 | sortedImages := images 22 | sort.Sort(imageSort(sortedImages)) 23 | return sortedImages 24 | } 25 | 26 | func (a imageSort) Len() int { 27 | return len(a) 28 | } 29 | 30 | func (a imageSort) Swap(i, j int) { 31 | a[i], a[j] = a[j], a[i] 32 | } 33 | 34 | func (a imageSort) Less(i, j int) bool { 35 | itime := a[i].PublishedAt 36 | jtime := a[j].PublishedAt 37 | return itime.Unix() < jtime.Unix() 38 | } 39 | -------------------------------------------------------------------------------- /cmd/manta/cmd/shell/autocomplete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package autocomplete 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/manta/cmd/shell/autocomplete/bash" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var Cmd = &command.Command{ 19 | Cobra: &cobra.Command{ 20 | Use: "autocomplete", 21 | Short: "Autocompletion generation", 22 | }, 23 | 24 | Setup: func(parent *command.Command) error { 25 | cmds := []*command.Command{ 26 | bash.Cmd, 27 | } 28 | 29 | for _, cmd := range cmds { 30 | cmd.Setup(cmd) 31 | parent.Cobra.AddCommand(cmd.Cobra) 32 | } 33 | 34 | return nil 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /cmd/triton/cmd/shell/autocomplete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package autocomplete 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/shell/autocomplete/bash" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var Cmd = &command.Command{ 19 | Cobra: &cobra.Command{ 20 | Use: "autocomplete", 21 | Short: "Autocompletion generation", 22 | }, 23 | 24 | Setup: func(parent *command.Command) error { 25 | cmds := []*command.Command{ 26 | bash.Cmd, 27 | } 28 | 29 | for _, cmd := range cmds { 30 | cmd.Setup(cmd) 31 | parent.Cobra.AddCommand(cmd.Cobra) 32 | } 33 | 34 | return nil 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /cmd/agent/compute/sortInstances.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package compute 11 | 12 | import ( 13 | "sort" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/compute" 16 | ) 17 | 18 | type instanceSort []*compute.Instance 19 | 20 | func sortInstances(instances []*compute.Instance) []*compute.Instance { 21 | sortInstances := instances 22 | sort.Sort(instanceSort(sortInstances)) 23 | return sortInstances 24 | } 25 | 26 | func (a instanceSort) Len() int { 27 | return len(a) 28 | } 29 | 30 | func (a instanceSort) Swap(i, j int) { 31 | a[i], a[j] = a[j], a[i] 32 | } 33 | 34 | func (a instanceSort) Less(i, j int) bool { 35 | itime := a[i].Created 36 | jtime := a[j].Created 37 | return itime.Unix() < jtime.Unix() 38 | } 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /scripts/gofmt-check.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 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 | # Copyright 2025 MNX Cloud, Inc. 11 | # 12 | 13 | # 14 | # Determine the list of Go source files to check for gofmt problems. Note that 15 | # we do not check the formatting of files in the vendor/ tree. 16 | # 17 | if ! files=$(git ls-files '*.go' ':!:vendor/*') || [[ -z $files ]]; then 18 | printf 'ERROR: could not find go file list\n' >&2 19 | exit 1 20 | fi 21 | 22 | if ! diff=$(gofmt -d $files); then 23 | printf 'ERROR: could not run "gofmt -d"\n' >&2 24 | exit 1 25 | fi 26 | 27 | if [[ -z $diff ]]; then 28 | printf 'gofmt check ok\n' 29 | exit 0 30 | fi 31 | 32 | # 33 | # Report the detected formatting issues and exit non-zero so that "make check" 34 | # will fail. 35 | # 36 | printf -- '---- GOFMT ERRORS -------\n' 37 | printf -- '%s\n' "$diff" 38 | exit 2 39 | -------------------------------------------------------------------------------- /cmd/manta/cmd/version/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package version 11 | 12 | import ( 13 | "fmt" 14 | 15 | triton "github.com/TritonDataCenter/triton-go/v2" 16 | "github.com/TritonDataCenter/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 | Use: "version", 24 | Short: "print manta cli version", 25 | SilenceUsage: true, 26 | 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | cons := conswriter.GetTerminal() 29 | cons.Write([]byte(fmt.Sprintf("Version: %s\n", triton.UserAgent()))) 30 | return nil 31 | }, 32 | }, 33 | Setup: func(parent *command.Command) error { 34 | return nil 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /cmd/triton/cmd/account/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package account 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/account/get" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/account/update" 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | var Cmd = &command.Command{ 20 | Cobra: &cobra.Command{ 21 | Use: "account", 22 | Short: "Get and update your Triton account", 23 | }, 24 | 25 | Setup: func(parent *command.Command) error { 26 | cmds := []*command.Command{ 27 | get.Cmd, 28 | update.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/version/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package version 11 | 12 | import ( 13 | "fmt" 14 | 15 | triton "github.com/TritonDataCenter/triton-go/v2" 16 | "github.com/TritonDataCenter/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 | Use: "version", 24 | Short: "print triton cli version", 25 | SilenceUsage: true, 26 | 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | cons := conswriter.GetTerminal() 29 | cons.Write([]byte(fmt.Sprintf("Version: %s\n", triton.UserAgent()))) 30 | return nil 31 | }, 32 | }, 33 | Setup: func(parent *command.Command) error { 34 | return nil 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/triton/cmd/docs/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package docs 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/docs/man" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/docs/md" 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | var Cmd = &command.Command{ 20 | Cobra: &cobra.Command{ 21 | Use: "doc", 22 | Aliases: []string{"docs", "documentation"}, 23 | Short: "Documentation for Triton cli", 24 | }, 25 | 26 | Setup: func(parent *command.Command) error { 27 | cmds := []*command.Command{ 28 | man.Cmd, 29 | md.Cmd, 30 | } 31 | 32 | for _, cmd := range cmds { 33 | cmd.Setup(cmd) 34 | parent.Cobra.AddCommand(cmd.Cobra) 35 | } 36 | 37 | return nil 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /cmd/manta/cmd/docs/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package docs 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/manta/cmd/docs/man" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/manta/cmd/docs/md" 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | var Cmd = &command.Command{ 20 | Cobra: &cobra.Command{ 21 | Use: "doc", 22 | Aliases: []string{"docs", "documentation"}, 23 | Short: "Documentation for Manta Object Storage cli", 24 | }, 25 | 26 | Setup: func(parent *command.Command) error { 27 | cmds := []*command.Command{ 28 | man.Cmd, 29 | md.Cmd, 30 | } 31 | 32 | for _, cmd := range cmds { 33 | cmd.Setup(cmd) 34 | parent.Cobra.AddCommand(cmd.Cobra) 35 | } 36 | 37 | return nil 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /triton_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package triton_test 11 | 12 | import ( 13 | "os" 14 | "testing" 15 | 16 | triton "github.com/TritonDataCenter/triton-go/v2" 17 | ) 18 | 19 | func TestGetEnv(t *testing.T) { 20 | tests := []struct { 21 | name string 22 | varname string 23 | input string 24 | value string 25 | }{ 26 | {"Triton", "TRITON_NAME", "NAME", "good"}, 27 | {"SDC", "SDC_NAME", "NAME", "good"}, 28 | {"unrelated", "BAD_NAME", "NAME", ""}, 29 | {"missing", "", "NAME", ""}, 30 | } 31 | for _, test := range tests { 32 | t.Run(test.name, func(t *testing.T) { 33 | os.Setenv(test.varname, test.value) 34 | defer os.Unsetenv(test.varname) 35 | 36 | if val := triton.GetEnv(test.input); val != test.value { 37 | t.Errorf("expected %s env var to be '%s': got '%s'", 38 | test.varname, test.value, val) 39 | } 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/internal/logger/format.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package logger 11 | 12 | import ( 13 | "fmt" 14 | "strings" 15 | 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 17 | "github.com/spf13/viper" 18 | ) 19 | 20 | type Format uint 21 | 22 | const ( 23 | FormatAuto Format = iota 24 | FormatZerolog 25 | FormatHuman 26 | ) 27 | 28 | func (f Format) String() string { 29 | switch f { 30 | case FormatAuto: 31 | return "auto" 32 | case FormatZerolog: 33 | return "zerolog" 34 | case FormatHuman: 35 | return "human" 36 | default: 37 | panic(fmt.Sprintf("unknown log format: %d", f)) 38 | } 39 | } 40 | 41 | func getLogFormat() (Format, error) { 42 | switch logFormat := strings.ToLower(viper.GetString(config.KeyLogFormat)); logFormat { 43 | case "auto": 44 | return FormatAuto, nil 45 | case "json", "zerolog": 46 | return FormatZerolog, nil 47 | case "human": 48 | return FormatHuman, nil 49 | default: 50 | return FormatAuto, fmt.Errorf("unsupported log format: %q", logFormat) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cmd/agent/storage/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package storage 11 | 12 | import ( 13 | "context" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/config" 16 | tsc "github.com/TritonDataCenter/triton-go/v2/storage" 17 | "github.com/pkg/errors" 18 | ) 19 | 20 | type AgentStorageClient struct { 21 | client *tsc.StorageClient 22 | } 23 | 24 | func NewStorageClient(cfg *config.TritonClientConfig) (*AgentStorageClient, error) { 25 | storageClient, err := tsc.NewClient(cfg.Config) 26 | if err != nil { 27 | return nil, errors.Wrap(err, "Error Creating Triton Storage Client") 28 | } 29 | return &AgentStorageClient{ 30 | client: storageClient, 31 | }, nil 32 | } 33 | 34 | func (c *AgentStorageClient) GetDirectoryListing(args []string) (*tsc.ListDirectoryOutput, error) { 35 | 36 | input := &tsc.ListDirectoryInput{} 37 | if len(args) > 0 { 38 | input.DirectoryName = args[0] 39 | } 40 | 41 | directoryOutput, err := c.client.Dir().List(context.Background(), input) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return directoryOutput, nil 47 | } 48 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/TritonDataCenter/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 | -------------------------------------------------------------------------------- /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 = "pre4" 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 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/count/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package count 11 | 12 | import ( 13 | "fmt" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 16 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 17 | "github.com/TritonDataCenter/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: "count", 26 | Short: "count triton instances", 27 | SilenceUsage: true, 28 | PreRunE: func(cmd *cobra.Command, args []string) error { 29 | return nil 30 | }, 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | cons := conswriter.GetTerminal() 33 | 34 | c, err := cfg.NewTritonConfig() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | a, err := compute.NewComputeClient(c) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | instances, err := a.CountInstanceList() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | cons.Write([]byte(fmt.Sprintf("Found %d instances", instances))) 50 | 51 | return nil 52 | }, 53 | }, 54 | Setup: func(parent *command.Command) error { 55 | return nil 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /triton.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package triton 11 | 12 | import ( 13 | "os" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/authentication" 16 | ) 17 | 18 | // Universal package used for defining configuration used across all client 19 | // constructors. 20 | 21 | // ClientConfig is a placeholder/input struct around the behavior of configuring 22 | // a client constructor through the implementation's runtime environment 23 | // (SDC/MANTA env vars). 24 | type ClientConfig struct { 25 | TritonURL string 26 | MantaURL string 27 | AccountName string 28 | Username string 29 | Signers []authentication.Signer 30 | } 31 | 32 | var envPrefixes = []string{"TRITON", "SDC"} 33 | 34 | // GetEnv looks up environment variables using the preferred "TRITON" prefix, 35 | // but falls back to the retired "SDC" prefix. For example, looking up "USER" 36 | // will search for "TRITON_USER" followed by "SDC_USER". If the environment 37 | // variable is not set, an empty string is returned. GetEnv() is used to aid in 38 | // the transition and deprecation of the "SDC_*" environment variables. 39 | func GetEnv(name string) string { 40 | for _, prefix := range envPrefixes { 41 | if val, found := os.LookupEnv(prefix + "_" + name); found { 42 | return val 43 | } 44 | } 45 | 46 | return "" 47 | } 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package get 11 | 12 | import ( 13 | "errors" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/account" 16 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 17 | "github.com/TritonDataCenter/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: "get", 26 | Short: "get 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.GetAccessKey() 49 | if err != nil { 50 | return err 51 | } 52 | cons.Write([]byte(accesskey.SecretAccessKey)) 53 | 54 | return nil 55 | }, 56 | }, 57 | Setup: func(parent *command.Command) error { 58 | return nil 59 | }, 60 | } 61 | -------------------------------------------------------------------------------- /storage/snaplink.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package storage 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "net/http" 16 | 17 | "github.com/TritonDataCenter/triton-go/v2/client" 18 | "github.com/pkg/errors" 19 | ) 20 | 21 | type SnapLinksClient struct { 22 | client *client.Client 23 | } 24 | 25 | // PutSnapLinkInput represents parameters to a PutSnapLink operation. 26 | type PutSnapLinkInput struct { 27 | LinkPath string 28 | SourcePath string 29 | } 30 | 31 | // PutSnapLink creates a SnapLink to an object. 32 | func (s *SnapLinksClient) Put(ctx context.Context, input *PutSnapLinkInput) error { 33 | linkPath := fmt.Sprintf("/%s%s", s.client.AccountName, input.LinkPath) 34 | sourcePath := fmt.Sprintf("/%s%s", s.client.AccountName, input.SourcePath) 35 | headers := &http.Header{} 36 | headers.Set("Content-Type", "application/json; type=link") 37 | headers.Set("location", sourcePath) 38 | headers.Set("Accept", "~1.0") 39 | headers.Set("Accept-Version", "application/json, */*") 40 | 41 | reqInput := client.RequestInput{ 42 | Method: http.MethodPut, 43 | Path: linkPath, 44 | Headers: headers, 45 | } 46 | respBody, _, err := s.client.ExecuteRequestStorage(ctx, reqInput) 47 | if respBody != nil { 48 | defer respBody.Close() 49 | } 50 | if err != nil { 51 | return errors.Wrapf(err, "unable to put snaplink") 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /compute/ping.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package compute 11 | 12 | import ( 13 | "context" 14 | "encoding/json" 15 | "net/http" 16 | 17 | "github.com/TritonDataCenter/triton-go/v2/client" 18 | pkgerrors "github.com/pkg/errors" 19 | ) 20 | 21 | const pingEndpoint = "/--ping" 22 | 23 | type CloudAPI struct { 24 | Versions []string `json:"versions"` 25 | } 26 | 27 | type PingOutput struct { 28 | Ping string `json:"ping"` 29 | CloudAPI CloudAPI `json:"cloudapi"` 30 | } 31 | 32 | // Ping sends a request to the '/--ping' endpoint and returns a `pong` as well 33 | // as a list of API version numbers your instance of CloudAPI is presenting. 34 | func (c *ComputeClient) Ping(ctx context.Context) (*PingOutput, error) { 35 | reqInputs := client.RequestInput{ 36 | Method: http.MethodGet, 37 | Path: pingEndpoint, 38 | } 39 | response, err := c.Client.ExecuteRequestRaw(ctx, reqInputs) 40 | if err != nil { 41 | return nil, pkgerrors.Wrap(err, "unable to ping") 42 | } 43 | if response == nil { 44 | return nil, pkgerrors.Wrap(err, "unable to ping") 45 | } 46 | if response.Body != nil { 47 | defer response.Body.Close() 48 | } 49 | 50 | var result *PingOutput 51 | decoder := json.NewDecoder(response.Body) 52 | if err = decoder.Decode(&result); err != nil { 53 | return nil, pkgerrors.Wrap(err, "unable to decode ping response") 54 | } 55 | 56 | return result, nil 57 | } 58 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/delete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package accesskeydelete 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/account" 17 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 18 | "github.com/TritonDataCenter/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 Triton Access Key", 28 | SilenceUsage: true, 29 | PreRunE: func(cmd *cobra.Command, args []string) error { 30 | if cfg.GetAccessKeyID() == "" { 31 | return errors.New("`accesskeyid` must be specified") 32 | } 33 | 34 | return nil 35 | }, 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | cons := conswriter.GetTerminal() 38 | 39 | c, err := cfg.NewTritonConfig() 40 | if err != nil { 41 | return err 42 | } 43 | 44 | a, err := account.NewAccountClient(c) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | accesskey, err := a.DeleteAccessKey() 50 | if err != nil { 51 | return err 52 | } 53 | 54 | cons.Write([]byte(fmt.Sprintf("Deleted access key %q", accesskey.AccessKeyID))) 55 | 56 | return nil 57 | }, 58 | }, 59 | Setup: func(parent *command.Command) error { 60 | return nil 61 | }, 62 | } 63 | -------------------------------------------------------------------------------- /cmd/manta/cmd/list/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package list 11 | 12 | import ( 13 | "fmt" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/storage" 16 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 17 | "github.com/TritonDataCenter/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.MaximumNArgs(3), 25 | Use: "ls", 26 | Short: "list directory contents", 27 | SilenceUsage: true, 28 | Example: ` 29 | $ manta ls 30 | $ manta ls /stor 31 | `, 32 | PreRunE: func(cmd *cobra.Command, args []string) error { 33 | return nil 34 | }, 35 | RunE: func(cmd *cobra.Command, args []string) error { 36 | cons := conswriter.GetTerminal() 37 | 38 | c, err := cfg.NewMantaConfig() 39 | if err != nil { 40 | return err 41 | } 42 | 43 | s, err := storage.NewStorageClient(c) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | directoryOutput, err := s.GetDirectoryListing(args) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | cons.Write([]byte(fmt.Sprintf("Found %d directory entries", directoryOutput.ResultSetSize))) 54 | 55 | for _, entry := range directoryOutput.Entries { 56 | cons.Write([]byte(fmt.Sprintf("\n%s/", entry.Name))) 57 | } 58 | 59 | return nil 60 | }, 61 | }, 62 | Setup: func(parent *command.Command) error { 63 | return nil 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package accesskeys 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/accesskeys/create" 16 | accessKeyDelete "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/accesskeys/delete" 17 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/accesskeys/get" 18 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/accesskeys/list" 19 | "github.com/spf13/cobra" 20 | "github.com/spf13/viper" 21 | ) 22 | 23 | var Cmd = &command.Command{ 24 | Cobra: &cobra.Command{ 25 | Use: "accesskeys", 26 | Aliases: []string{"accesskey"}, 27 | Short: "List and manage Triton Access Keys.", 28 | }, 29 | 30 | Setup: func(parent *command.Command) error { 31 | 32 | cmds := []*command.Command{ 33 | list.Cmd, 34 | get.Cmd, 35 | accessKeyDelete.Cmd, 36 | create.Cmd, 37 | } 38 | 39 | for _, cmd := range cmds { 40 | cmd.Setup(cmd) 41 | parent.Cobra.AddCommand(cmd.Cobra) 42 | } 43 | 44 | { 45 | const ( 46 | key = config.KeyAccessKeyID 47 | longName = "accesskeyid" 48 | defaultValue = "" 49 | description = "Access Key Identifier" 50 | ) 51 | 52 | flags := parent.Cobra.PersistentFlags() 53 | flags.String(longName, defaultValue, description) 54 | viper.BindPFlag(key, flags.Lookup(longName)) 55 | } 56 | 57 | return nil 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /cmd/triton/cmd/keys/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package get 11 | 12 | import ( 13 | "errors" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/account" 16 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 17 | "github.com/TritonDataCenter/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: "get", 26 | Short: "get 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 | 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 := account.NewAccountClient(c) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | key, err := a.GetKey() 52 | if err != nil { 53 | return err 54 | } 55 | 56 | cons.Write([]byte(key.Key)) 57 | 58 | return nil 59 | }, 60 | }, 61 | Setup: func(parent *command.Command) error { 62 | return nil 63 | }, 64 | } 65 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/ip/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package ip 11 | 12 | import ( 13 | "errors" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 16 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 17 | "github.com/TritonDataCenter/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: "ip", 26 | Short: "get the ip a triton 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.GetInstance() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | cons.Write([]byte(instance.PrimaryIP)) 58 | 59 | return nil 60 | }, 61 | }, 62 | 63 | Setup: func(parent *command.Command) error { 64 | return nil 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/stop/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package stop 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 17 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 18 | "github.com/TritonDataCenter/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: "stop", 27 | Short: "stop 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.StopInstance() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | cons.Write([]byte(fmt.Sprintf("Stopped instance %q", instance.Name))) 59 | 60 | return nil 61 | }, 62 | }, 63 | Setup: func(parent *command.Command) error { 64 | return nil 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /compute/services.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package compute 11 | 12 | import ( 13 | "context" 14 | "encoding/json" 15 | "net/http" 16 | "path" 17 | "sort" 18 | 19 | "github.com/TritonDataCenter/triton-go/v2/client" 20 | "github.com/pkg/errors" 21 | ) 22 | 23 | type ServicesClient struct { 24 | client *client.Client 25 | } 26 | 27 | type Service struct { 28 | Name string 29 | Endpoint string 30 | } 31 | 32 | type ListServicesInput struct{} 33 | 34 | func (c *ServicesClient) List(ctx context.Context, _ *ListServicesInput) ([]*Service, error) { 35 | fullPath := path.Join("/", c.client.AccountName, "services") 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 list services") 46 | } 47 | 48 | var intermediate map[string]string 49 | decoder := json.NewDecoder(respReader) 50 | if err = decoder.Decode(&intermediate); err != nil { 51 | return nil, errors.Wrap(err, "unable to decode list services response") 52 | } 53 | 54 | keys := make([]string, len(intermediate)) 55 | i := 0 56 | for k := range intermediate { 57 | keys[i] = k 58 | i++ 59 | } 60 | sort.Strings(keys) 61 | 62 | result := make([]*Service, len(intermediate)) 63 | i = 0 64 | for _, key := range keys { 65 | result[i] = &Service{ 66 | Name: key, 67 | Endpoint: intermediate[key], 68 | } 69 | i++ 70 | } 71 | 72 | return result, nil 73 | } 74 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/start/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package start 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 17 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 18 | "github.com/TritonDataCenter/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: "start", 27 | Short: "start 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.StartInstance() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | cons.Write([]byte(fmt.Sprintf("Started instance %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/reboot/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package reboot 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 17 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 18 | "github.com/TritonDataCenter/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: "reboot", 27 | Short: "reboot 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.RebootInstance() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | cons.Write([]byte(fmt.Sprintf("Rebooted instance %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/delete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package delete 11 | 12 | import ( 13 | "errors" 14 | 15 | "fmt" 16 | 17 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 18 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 19 | "github.com/TritonDataCenter/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: "delete", 28 | Short: "delete instance", 29 | SilenceUsage: true, 30 | PreRunE: func(cmd *cobra.Command, args []string) error { 31 | if cfg.GetMachineID() == "" && cfg.GetMachineName() == "" { 32 | return errors.New("Either `id` or `name` must be specified") 33 | } 34 | 35 | if cfg.GetMachineID() != "" && cfg.GetMachineName() != "" { 36 | return errors.New("Only 1 of `id` or `name` must be specified") 37 | } 38 | 39 | return nil 40 | }, 41 | RunE: func(cmd *cobra.Command, args []string) error { 42 | cons := conswriter.GetTerminal() 43 | 44 | c, err := cfg.NewTritonConfig() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | a, err := compute.NewComputeClient(c) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | instance, err := a.DeleteInstance() 55 | if err != nil { 56 | return err 57 | } 58 | 59 | cons.Write([]byte(fmt.Sprintf("Deleted instance (async) %q", instance.Name))) 60 | 61 | return nil 62 | }, 63 | }, 64 | Setup: func(parent *command.Command) error { 65 | return nil 66 | }, 67 | } 68 | -------------------------------------------------------------------------------- /cmd/triton/cmd/keys/delete/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package keyDelete 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/account" 17 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 18 | "github.com/TritonDataCenter/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 Triton SSH Key", 28 | SilenceUsage: true, 29 | PreRunE: func(cmd *cobra.Command, args []string) error { 30 | if cfg.GetSSHKeyFingerprint() == "" && cfg.GetSSHKeyName() == "" { 31 | return errors.New("Either `fingerprint` or `keyname` must be specified") 32 | } 33 | 34 | if cfg.GetSSHKeyFingerprint() != "" && cfg.GetSSHKeyName() != "" { 35 | return errors.New("Only 1 of `fingerprint` or `keyname` 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 := account.NewAccountClient(c) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | key, err := a.DeleteKey() 54 | if err != nil { 55 | return err 56 | } 57 | 58 | cons.Write([]byte(fmt.Sprintf("Deleted key %q", key.Name))) 59 | 60 | return nil 61 | }, 62 | }, 63 | Setup: func(parent *command.Command) error { 64 | return nil 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /network/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package network 11 | 12 | import ( 13 | "net/http" 14 | 15 | triton "github.com/TritonDataCenter/triton-go/v2" 16 | "github.com/TritonDataCenter/triton-go/v2/client" 17 | ) 18 | 19 | type NetworkClient struct { 20 | Client *client.Client 21 | } 22 | 23 | func newNetworkClient(client *client.Client) *NetworkClient { 24 | return &NetworkClient{ 25 | Client: client, 26 | } 27 | } 28 | 29 | // NewClient returns a new client for working with Network endpoints and 30 | // resources within CloudAPI 31 | func NewClient(config *triton.ClientConfig) (*NetworkClient, error) { 32 | // TODO: Utilize config interface within the function itself 33 | client, err := client.New( 34 | config.TritonURL, 35 | config.MantaURL, 36 | config.AccountName, 37 | config.Signers..., 38 | ) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return newNetworkClient(client), nil 43 | } 44 | 45 | // SetHeaders allows a consumer of the current client to set custom headers for 46 | // the next backend HTTP request sent to CloudAPI 47 | func (c *NetworkClient) SetHeader(header *http.Header) { 48 | c.Client.RequestHeader = header 49 | } 50 | 51 | // Fabrics returns a FabricsClient used for accessing functions pertaining to 52 | // Fabric functionality in the Triton API. 53 | func (c *NetworkClient) Fabrics() *FabricsClient { 54 | return &FabricsClient{c.Client} 55 | } 56 | 57 | // Firewall returns a FirewallClient client used for accessing functions 58 | // pertaining to firewall functionality in the Triton API. 59 | func (c *NetworkClient) Firewall() *FirewallClient { 60 | return &FirewallClient{c.Client} 61 | } 62 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /services/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package services 11 | 12 | import ( 13 | "net/http" 14 | 15 | triton "github.com/TritonDataCenter/triton-go/v2" 16 | "github.com/TritonDataCenter/triton-go/v2/client" 17 | ) 18 | 19 | type ServiceGroupClient struct { 20 | Client *client.Client 21 | } 22 | 23 | func newServiceGroupClient(client *client.Client) *ServiceGroupClient { 24 | return &ServiceGroupClient{ 25 | Client: client, 26 | } 27 | } 28 | 29 | // NewClient returns a new client for working with Service Groups endpoints and 30 | // resources within TSG 31 | func NewClient(config *triton.ClientConfig) (*ServiceGroupClient, error) { 32 | // TODO: Utilize config interface within the function itself 33 | client, err := client.New( 34 | config.TritonURL, 35 | config.MantaURL, 36 | config.AccountName, 37 | config.Signers..., 38 | ) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return newServiceGroupClient(client), nil 43 | } 44 | 45 | // SetHeaders allows a consumer of the current client to set custom headers for 46 | // the next backend HTTP request sent to CloudAPI 47 | func (c *ServiceGroupClient) SetHeader(header *http.Header) { 48 | c.Client.RequestHeader = header 49 | } 50 | 51 | // Templates returns a TemplatesClient used for accessing functions pertaining 52 | // to Instance Templates functionality in the TSG API. 53 | func (c *ServiceGroupClient) Templates() *TemplatesClient { 54 | return &TemplatesClient{c.Client} 55 | } 56 | 57 | // Groups returns a GroupsClient used for accessing functions pertaining 58 | // to Service Groups functionality in the TSG API. 59 | func (c *ServiceGroupClient) Groups() *GroupsClient { 60 | return &GroupsClient{c.Client} 61 | } 62 | -------------------------------------------------------------------------------- /cmd/triton/cmd/keys/create/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package create 11 | 12 | import ( 13 | "fmt" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/account" 16 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 17 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 18 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 19 | "github.com/sean-/conswriter" 20 | "github.com/spf13/cobra" 21 | "github.com/spf13/viper" 22 | ) 23 | 24 | var Cmd = &command.Command{ 25 | Cobra: &cobra.Command{ 26 | Args: cobra.NoArgs, 27 | Use: "create", 28 | Aliases: []string{"add"}, 29 | Short: "create Triton SSH Key", 30 | SilenceUsage: true, 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.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 | key, err := a.CreateKey() 48 | if err != nil { 49 | return err 50 | } 51 | 52 | cons.Write([]byte(fmt.Sprintf("Created key %q", key.Name))) 53 | 54 | return nil 55 | }, 56 | }, 57 | Setup: func(parent *command.Command) error { 58 | 59 | { 60 | const ( 61 | key = config.KeySSHKey 62 | longName = "publickey" 63 | defaultValue = "" 64 | description = "SSH Key PublicKey" 65 | ) 66 | 67 | flags := parent.Cobra.Flags() 68 | flags.String(longName, defaultValue, description) 69 | viper.BindPFlag(key, flags.Lookup(longName)) 70 | } 71 | 72 | return nil 73 | }, 74 | } 75 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/create/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package create 11 | 12 | import ( 13 | "fmt" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/account" 16 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 17 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 18 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 19 | "github.com/sean-/conswriter" 20 | "github.com/spf13/cobra" 21 | "github.com/spf13/viper" 22 | ) 23 | 24 | var Cmd = &command.Command{ 25 | Cobra: &cobra.Command{ 26 | Args: cobra.NoArgs, 27 | Use: "create", 28 | Aliases: []string{"add"}, 29 | Short: "create Triton Access Key", 30 | SilenceUsage: true, 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.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.CreateAccessKey() 48 | if err != nil { 49 | return err 50 | } 51 | 52 | cons.Write([]byte(fmt.Sprintf("Created access key %q", accesskey.AccessKeyID))) 53 | 54 | return nil 55 | }, 56 | }, 57 | Setup: func(parent *command.Command) error { 58 | 59 | { 60 | const ( 61 | key = config.KeyAccessKeyID 62 | longName = "accesskeyid" 63 | defaultValue = "" 64 | description = "Access Key Identifier" 65 | ) 66 | 67 | flags := parent.Cobra.Flags() 68 | flags.String(longName, defaultValue, description) 69 | viper.BindPFlag(key, flags.Lookup(longName)) 70 | } 71 | 72 | return nil 73 | }, 74 | } 75 | -------------------------------------------------------------------------------- /cmd/triton/cmd/packages/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package packages 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/packages/get" 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/packages/list" 17 | "github.com/spf13/cobra" 18 | "github.com/spf13/viper" 19 | ) 20 | 21 | var Cmd = &command.Command{ 22 | Cobra: &cobra.Command{ 23 | Use: "packages", 24 | Aliases: []string{"package", "pkgs"}, 25 | Short: "List and get Triton packages.", 26 | Long: `A package is a collection of attributes -- for example disk quota, 27 | amount of RAM -- used when creating an instance. They have a name 28 | and ID for identification.`, 29 | }, 30 | 31 | Setup: func(parent *command.Command) error { 32 | 33 | cmds := []*command.Command{ 34 | list.Cmd, 35 | get.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.KeyPackageID 46 | longName = "id" 47 | defaultValue = "" 48 | description = "Package ID" 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.KeyPackageName 59 | longName = "name" 60 | defaultValue = "" 61 | description = "Package 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/keys/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package keys 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/keys/create" 16 | keyDelete "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/keys/delete" 17 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/keys/get" 18 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/keys/list" 19 | "github.com/spf13/cobra" 20 | "github.com/spf13/viper" 21 | ) 22 | 23 | var Cmd = &command.Command{ 24 | Cobra: &cobra.Command{ 25 | Use: "keys", 26 | Aliases: []string{"key"}, 27 | Short: "List and manage Triton SSH Keys.", 28 | }, 29 | 30 | Setup: func(parent *command.Command) error { 31 | 32 | cmds := []*command.Command{ 33 | list.Cmd, 34 | get.Cmd, 35 | keyDelete.Cmd, 36 | create.Cmd, 37 | } 38 | 39 | for _, cmd := range cmds { 40 | cmd.Setup(cmd) 41 | parent.Cobra.AddCommand(cmd.Cobra) 42 | } 43 | 44 | { 45 | const ( 46 | key = config.KeySSHKeyFingerprint 47 | longName = "fingerprint" 48 | defaultValue = "" 49 | description = "SSH Key Fingerprint" 50 | ) 51 | 52 | flags := parent.Cobra.PersistentFlags() 53 | flags.String(longName, defaultValue, description) 54 | viper.BindPFlag(key, flags.Lookup(longName)) 55 | } 56 | 57 | { 58 | const ( 59 | key = config.KeySSHKeyName 60 | longName = "keyname" 61 | defaultValue = "" 62 | description = "SSH Key Name" 63 | ) 64 | 65 | flags := parent.Cobra.PersistentFlags() 66 | flags.String(longName, defaultValue, description) 67 | viper.BindPFlag(key, flags.Lookup(longName)) 68 | } 69 | 70 | return nil 71 | }, 72 | } 73 | -------------------------------------------------------------------------------- /account/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package account 11 | 12 | import ( 13 | "net/http" 14 | 15 | triton "github.com/TritonDataCenter/triton-go/v2" 16 | "github.com/TritonDataCenter/triton-go/v2/client" 17 | ) 18 | 19 | type AccountClient struct { 20 | Client *client.Client 21 | } 22 | 23 | func newAccountClient(client *client.Client) *AccountClient { 24 | return &AccountClient{ 25 | Client: client, 26 | } 27 | } 28 | 29 | // NewClient returns a new client for working with Account endpoints and 30 | // resources within CloudAPI 31 | func NewClient(config *triton.ClientConfig) (*AccountClient, error) { 32 | // TODO: Utilize config interface within the function itself 33 | client, err := client.New( 34 | config.TritonURL, 35 | config.MantaURL, 36 | config.AccountName, 37 | config.Signers..., 38 | ) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return newAccountClient(client), nil 43 | } 44 | 45 | // SetHeader allows a consumer of the current client to set custom headers for 46 | // the next backend HTTP request sent to CloudAPI 47 | func (c *AccountClient) SetHeader(header *http.Header) { 48 | c.Client.RequestHeader = header 49 | } 50 | 51 | // Config returns a c used for accessing functions pertaining 52 | // to Config functionality in the Triton API. 53 | func (c *AccountClient) Config() *ConfigClient { 54 | return &ConfigClient{c.Client} 55 | } 56 | 57 | // Keys returns a Compute client used for accessing functions pertaining to SSH 58 | // key functionality in the Triton API. 59 | func (c *AccountClient) Keys() *KeysClient { 60 | return &KeysClient{c.Client} 61 | } 62 | 63 | // AccessKeys returns a Compute Client used for accessing functions related to 64 | // Access Keys functionality 65 | func (c *AccountClient) AccessKeys() *AccessKeysClient { 66 | return &AccessKeysClient{c.Client} 67 | } 68 | -------------------------------------------------------------------------------- /cmd/triton/cmd/packages/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package get 11 | 12 | import ( 13 | "encoding/json" 14 | "errors" 15 | 16 | "bytes" 17 | 18 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 19 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 20 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 21 | "github.com/sean-/conswriter" 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | var Cmd = &command.Command{ 26 | Cobra: &cobra.Command{ 27 | Args: cobra.NoArgs, 28 | Use: "get", 29 | Short: "get triton package", 30 | SilenceUsage: true, 31 | PreRunE: func(cmd *cobra.Command, args []string) error { 32 | if cfg.GetPkgID() == "" && cfg.GetPkgName() == "" { 33 | return errors.New("Either `id` or `name` must be specified") 34 | } 35 | 36 | if cfg.GetPkgID() != "" && cfg.GetPkgName() != "" { 37 | return errors.New("Only 1 of `id` or `name` must be specified") 38 | } 39 | return nil 40 | }, 41 | RunE: func(cmd *cobra.Command, args []string) error { 42 | cons := conswriter.GetTerminal() 43 | 44 | c, err := cfg.NewTritonConfig() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | a, err := compute.NewComputeClient(c) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | pkg, err := a.GetPackage() 55 | if err != nil { 56 | return err 57 | } 58 | 59 | bytes, err := json.Marshal(pkg) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | output, _ := prettyPrintJSON(bytes) 65 | 66 | cons.Write(output) 67 | 68 | return nil 69 | }, 70 | }, 71 | Setup: func(parent *command.Command) error { 72 | return nil 73 | }, 74 | } 75 | 76 | func prettyPrintJSON(b []byte) ([]byte, error) { 77 | var out bytes.Buffer 78 | err := json.Indent(&out, b, "", " ") 79 | return out.Bytes(), err 80 | } 81 | -------------------------------------------------------------------------------- /identity/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package identity 11 | 12 | import ( 13 | "net/http" 14 | 15 | triton "github.com/TritonDataCenter/triton-go/v2" 16 | "github.com/TritonDataCenter/triton-go/v2/client" 17 | ) 18 | 19 | type IdentityClient struct { 20 | Client *client.Client 21 | } 22 | 23 | func newIdentityClient(client *client.Client) *IdentityClient { 24 | return &IdentityClient{ 25 | Client: client, 26 | } 27 | } 28 | 29 | // NewClient returns a new client for working with Identity endpoints and 30 | // resources within CloudAPI 31 | func NewClient(config *triton.ClientConfig) (*IdentityClient, error) { 32 | // TODO: Utilize config interface within the function itself 33 | client, err := client.New( 34 | config.TritonURL, 35 | config.MantaURL, 36 | config.AccountName, 37 | config.Signers..., 38 | ) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return newIdentityClient(client), nil 43 | } 44 | 45 | // SetHeaders allows a consumer of the current client to set custom headers for 46 | // the next backend HTTP request sent to CloudAPI 47 | func (c *IdentityClient) SetHeader(header *http.Header) { 48 | c.Client.RequestHeader = header 49 | } 50 | 51 | // Roles returns a Roles client used for accessing functions pertaining to 52 | // Role functionality in the Triton API. 53 | func (c *IdentityClient) Roles() *RolesClient { 54 | return &RolesClient{c.Client} 55 | } 56 | 57 | // Users returns a Users client used for accessing functions pertaining to 58 | // User functionality in the Triton API. 59 | func (c *IdentityClient) Users() *UsersClient { 60 | return &UsersClient{c.Client} 61 | } 62 | 63 | // Policies returns a Policies client used for accessing functions pertaining to 64 | // Policy functionality in the Triton API. 65 | func (c *IdentityClient) Policies() *PoliciesClient { 66 | return &PoliciesClient{c.Client} 67 | } 68 | -------------------------------------------------------------------------------- /cmd/triton/cmd/datacenters/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package datacenters 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 14 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 16 | "github.com/olekukonko/tablewriter" 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: "datacenters", 25 | Short: "Show datacenters in this cloud", 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 | dcs, err := a.GetDataCenterList() 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{"NAME", "URL"}) 60 | 61 | for _, dc := range dcs { 62 | table.Append([]string{dc.Name, dc.URL}) 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/services/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package services 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 14 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 16 | "github.com/olekukonko/tablewriter" 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: "services", 25 | Short: "Show services in this cloud", 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 | services, err := a.GetServiceList() 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{"NAME", "ENDPOINT"}) 60 | 61 | for _, service := range services { 62 | table.Append([]string{service.Name, service.Endpoint}) 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/list/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package list 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/account" 14 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 16 | "github.com/olekukonko/tablewriter" 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: "list", 25 | Short: "list Triton SSH Keys", 26 | Aliases: []string{"ls"}, 27 | SilenceUsage: true, 28 | PreRunE: func(cmd *cobra.Command, args []string) error { 29 | return nil 30 | }, 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | cons := conswriter.GetTerminal() 33 | 34 | c, err := cfg.NewTritonConfig() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | a, err := account.NewAccountClient(c) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | keys, err := a.ListKeys() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | table := tablewriter.NewWriter(cons) 50 | table.SetHeaderAlignment(tablewriter.ALIGN_CENTER) 51 | table.SetHeaderLine(false) 52 | table.SetAutoFormatHeaders(true) 53 | 54 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) 55 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 56 | table.SetCenterSeparator("") 57 | table.SetColumnSeparator("") 58 | table.SetRowSeparator("") 59 | 60 | table.SetHeader([]string{"FINGERPRINT", "NAME"}) 61 | 62 | for _, key := range keys { 63 | table.Append([]string{key.Fingerprint, key.Name}) 64 | } 65 | 66 | table.Render() 67 | 68 | return nil 69 | }, 70 | }, 71 | Setup: func(parent *command.Command) error { 72 | return nil 73 | }, 74 | } 75 | -------------------------------------------------------------------------------- /cmd/triton/cmd/accesskeys/list/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package list 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/account" 14 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 16 | "github.com/olekukonko/tablewriter" 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: "list", 25 | Short: "list Triton Access Keys", 26 | Aliases: []string{"ls"}, 27 | SilenceUsage: true, 28 | PreRunE: func(cmd *cobra.Command, args []string) error { 29 | return nil 30 | }, 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | cons := conswriter.GetTerminal() 33 | 34 | c, err := cfg.NewTritonConfig() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | a, err := account.NewAccountClient(c) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | accesskeys, err := a.ListAccessKeys() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | table := tablewriter.NewWriter(cons) 50 | table.SetHeaderAlignment(tablewriter.ALIGN_CENTER) 51 | table.SetHeaderLine(false) 52 | table.SetAutoFormatHeaders(true) 53 | 54 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) 55 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 56 | table.SetCenterSeparator("") 57 | table.SetColumnSeparator("") 58 | table.SetRowSeparator("") 59 | 60 | table.SetHeader([]string{"ID", "SECRET"}) 61 | 62 | for _, accesskey := range accesskeys { 63 | table.Append([]string{accesskey.AccessKeyID, accesskey.SecretAccessKey}) 64 | } 65 | 66 | table.Render() 67 | 68 | return nil 69 | }, 70 | }, 71 | Setup: func(parent *command.Command) error { 72 | return nil 73 | }, 74 | } 75 | -------------------------------------------------------------------------------- /cmd/internal/logger/level.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package logger 11 | 12 | import ( 13 | "fmt" 14 | "strings" 15 | 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 17 | "github.com/rs/zerolog" 18 | "github.com/spf13/viper" 19 | ) 20 | 21 | type Level int 22 | 23 | const ( 24 | LevelBegin Level = iota - 2 25 | LevelDebug 26 | LevelInfo // Default, zero-initialized value 27 | LevelWarn 28 | LevelError 29 | LevelFatal 30 | 31 | LevelEnd 32 | ) 33 | 34 | func (f Level) String() string { 35 | switch f { 36 | case LevelDebug: 37 | return "debug" 38 | case LevelInfo: 39 | return "info" 40 | case LevelWarn: 41 | return "warn" 42 | case LevelError: 43 | return "error" 44 | case LevelFatal: 45 | return "fatal" 46 | default: 47 | panic(fmt.Sprintf("unknown log level: %d", f)) 48 | } 49 | } 50 | 51 | func logLevels() []Level { 52 | levels := make([]Level, 0, LevelEnd-LevelBegin) 53 | for i := LevelBegin + 1; i < LevelEnd; i++ { 54 | levels = append(levels, i) 55 | } 56 | 57 | return levels 58 | } 59 | 60 | func logLevelsStr() []string { 61 | intLevels := logLevels() 62 | levels := make([]string, 0, len(intLevels)) 63 | for _, lvl := range intLevels { 64 | levels = append(levels, lvl.String()) 65 | } 66 | return levels 67 | } 68 | 69 | func setLogLevel() (logLevel Level, err error) { 70 | switch strLevel := strings.ToLower(viper.GetString(config.KeyLogLevel)); strLevel { 71 | case "debug": 72 | zerolog.SetGlobalLevel(zerolog.DebugLevel) 73 | logLevel = LevelDebug 74 | case "info": 75 | zerolog.SetGlobalLevel(zerolog.InfoLevel) 76 | logLevel = LevelInfo 77 | case "warn": 78 | zerolog.SetGlobalLevel(zerolog.WarnLevel) 79 | logLevel = LevelWarn 80 | case "error": 81 | zerolog.SetGlobalLevel(zerolog.ErrorLevel) 82 | logLevel = LevelError 83 | case "fatal": 84 | zerolog.SetGlobalLevel(zerolog.FatalLevel) 85 | logLevel = LevelFatal 86 | default: 87 | return LevelDebug, fmt.Errorf("unsupported error level: %q (supported levels: %s)", logLevel, 88 | strings.Join(logLevelsStr(), " ")) 89 | } 90 | 91 | return logLevel, nil 92 | } 93 | -------------------------------------------------------------------------------- /storage/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package storage 11 | 12 | import ( 13 | "net/http" 14 | 15 | triton "github.com/TritonDataCenter/triton-go/v2" 16 | "github.com/TritonDataCenter/triton-go/v2/client" 17 | ) 18 | 19 | type StorageClient struct { 20 | Client *client.Client 21 | } 22 | 23 | func newStorageClient(client *client.Client) *StorageClient { 24 | return &StorageClient{ 25 | Client: client, 26 | } 27 | } 28 | 29 | // NewClient returns a new client for working with Storage endpoints and 30 | // resources within CloudAPI 31 | func NewClient(config *triton.ClientConfig) (*StorageClient, error) { 32 | // TODO: Utilize config interface within the function itself 33 | client, err := client.New( 34 | config.TritonURL, 35 | config.MantaURL, 36 | config.AccountName, 37 | config.Signers..., 38 | ) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return newStorageClient(client), nil 43 | } 44 | 45 | // SetHeader allows a consumer of the current client to set a custom header for 46 | // the next backend HTTP request sent to CloudAPI. 47 | func (c *StorageClient) SetHeader(header *http.Header) { 48 | c.Client.RequestHeader = header 49 | } 50 | 51 | // Dir returns a DirectoryClient used for accessing functions pertaining to 52 | // Directories functionality of the Manta API. 53 | func (c *StorageClient) Dir() *DirectoryClient { 54 | return &DirectoryClient{c.Client} 55 | } 56 | 57 | // Jobs returns a JobClient used for accessing functions pertaining to Jobs 58 | // functionality of the Triton Object Storage API. 59 | func (c *StorageClient) Jobs() *JobClient { 60 | return &JobClient{c.Client} 61 | } 62 | 63 | // Objects returns an ObjectsClient used for accessing functions pertaining to 64 | // Objects functionality of the Triton Object Storage API. 65 | func (c *StorageClient) Objects() *ObjectsClient { 66 | return &ObjectsClient{c.Client} 67 | } 68 | 69 | // SnapLinks returns an SnapLinksClient used for accessing functions pertaining to 70 | // SnapLinks functionality of the Triton Object Storage API. 71 | func (c *StorageClient) SnapLinks() *SnapLinksClient { 72 | return &SnapLinksClient{c.Client} 73 | } 74 | -------------------------------------------------------------------------------- /storage/snaplink_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package storage_test 11 | 12 | import ( 13 | "context" 14 | "net/http" 15 | "path" 16 | "strings" 17 | "testing" 18 | 19 | "github.com/TritonDataCenter/triton-go/v2/storage" 20 | "github.com/TritonDataCenter/triton-go/v2/testutils" 21 | "github.com/pkg/errors" 22 | ) 23 | 24 | const accountURL = "testing" 25 | 26 | var ( 27 | putSnapLinkErrorType = errors.New("unable to put snaplink") 28 | linkPath = "/stor/foobar.json" 29 | brokenLinkPath = "/missingfolder/foo.json" 30 | sourcePath = "/stor/foo.json" 31 | ) 32 | 33 | func MockStorageClient() *storage.StorageClient { 34 | return &storage.StorageClient{ 35 | Client: testutils.NewMockClient(testutils.MockClientInput{ 36 | AccountName: accountURL, 37 | }), 38 | } 39 | } 40 | 41 | func TestPutSnaplink(t *testing.T) { 42 | storageClient := MockStorageClient() 43 | 44 | do := func(ctx context.Context, sc *storage.StorageClient) error { 45 | defer testutils.DeactivateClient() 46 | 47 | return sc.SnapLinks().Put(ctx, &storage.PutSnapLinkInput{ 48 | LinkPath: linkPath, 49 | SourcePath: sourcePath, 50 | }) 51 | } 52 | 53 | t.Run("successful", func(t *testing.T) { 54 | testutils.RegisterResponder("PUT", path.Join("/", accountURL, linkPath), putSnapLinkSuccess) 55 | 56 | err := do(context.Background(), storageClient) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | }) 61 | 62 | t.Run("error", func(t *testing.T) { 63 | testutils.RegisterResponder("PUT", path.Join("/", accountURL, brokenLinkPath), putSnapLinkError) 64 | 65 | err := do(context.Background(), storageClient) 66 | if err == nil { 67 | t.Fatal(err) 68 | } 69 | 70 | if !strings.Contains(err.Error(), "unable to put snaplink") { 71 | t.Errorf("expected error to equal testError: found %v", err) 72 | } 73 | }) 74 | } 75 | 76 | func putSnapLinkSuccess(req *http.Request) (*http.Response, error) { 77 | header := http.Header{} 78 | header.Add("Content-Type", "application/json") 79 | 80 | return &http.Response{ 81 | StatusCode: http.StatusNoContent, 82 | Header: header, 83 | }, nil 84 | } 85 | 86 | func putSnapLinkError(req *http.Request) (*http.Response, error) { 87 | return nil, putSnapLinkErrorType 88 | } 89 | -------------------------------------------------------------------------------- /cmd/triton/cmd/shell/autocomplete/bash/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package bash 11 | 12 | import ( 13 | "fmt" 14 | 15 | "os" 16 | 17 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 18 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 19 | "github.com/pkg/errors" 20 | "github.com/rs/zerolog/log" 21 | "github.com/spf13/cobra" 22 | "github.com/spf13/viper" 23 | ) 24 | 25 | var Cmd = &command.Command{ 26 | Cobra: &cobra.Command{ 27 | Use: "bash", 28 | Short: "Generates shell autocompletion file for Triton", 29 | Long: `Generates a shell autocompletion script for Triton. 30 | 31 | By default, the file is written directly to /etc/bash_completion.d 32 | for convenience, and the command may need superuser rights, e.g.: 33 | 34 | $ sudo triton shell autocomplete bash 35 | 36 | Add ` + "`--bash-autocomplete-dir=/path/to/file`" + ` flag to set alternative 37 | folder location. 38 | 39 | Logout and in again to reload the completion scripts, 40 | or just source them in directly: 41 | 42 | $ . /etc/bash_completion`, 43 | 44 | PreRunE: func(cmd *cobra.Command, args []string) error { 45 | return nil 46 | }, 47 | RunE: func(cmd *cobra.Command, args []string) error { 48 | target := viper.GetString(config.KeyBashAutoCompletionTarget) 49 | if _, err := os.Stat(target); os.IsNotExist(err) { 50 | if err := os.MkdirAll(target, 0777); err != nil { 51 | return errors.Wrapf(err, "unable to make bash-autocomplete-target %q", target) 52 | } 53 | } 54 | bashFile := fmt.Sprintf("%s/triton.sh", target) 55 | err := cmd.Root().GenBashCompletionFile(bashFile) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | log.Info().Msg("Installation completed successfully.") 61 | 62 | return nil 63 | }, 64 | }, 65 | Setup: func(parent *command.Command) error { 66 | 67 | { 68 | const ( 69 | key = config.KeyBashAutoCompletionTarget 70 | longName = "bash-autocomplete-dir" 71 | defaultValue = "/etc/bash_completion.d" 72 | description = "autocompletion directory" 73 | ) 74 | 75 | flags := parent.Cobra.PersistentFlags() 76 | flags.String(longName, defaultValue, description) 77 | viper.BindPFlag(key, flags.Lookup(longName)) 78 | } 79 | 80 | return nil 81 | }, 82 | } 83 | -------------------------------------------------------------------------------- /cmd/manta/cmd/shell/autocomplete/bash/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package bash 11 | 12 | import ( 13 | "fmt" 14 | "os" 15 | 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 17 | "github.com/TritonDataCenter/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 Manta Object Storage CLI", 28 | Long: `Generates a shell autocompletion script for Manta Object Storage CLI. 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 manta 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 | -------------------------------------------------------------------------------- /account/config.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package account 11 | 12 | import ( 13 | "context" 14 | "encoding/json" 15 | "net/http" 16 | "path" 17 | 18 | "github.com/TritonDataCenter/triton-go/v2/client" 19 | "github.com/pkg/errors" 20 | ) 21 | 22 | type ConfigClient struct { 23 | client *client.Client 24 | } 25 | 26 | // Config represents configuration for your account. 27 | type Config struct { 28 | // DefaultNetwork is the network that docker containers are provisioned on. 29 | DefaultNetwork string `json:"default_network"` 30 | } 31 | 32 | type GetConfigInput struct{} 33 | 34 | // GetConfig outputs configuration for your account. 35 | func (c *ConfigClient) Get(ctx context.Context, input *GetConfigInput) (*Config, error) { 36 | fullPath := path.Join("/", c.client.AccountName, "config") 37 | reqInputs := client.RequestInput{ 38 | Method: http.MethodGet, 39 | Path: fullPath, 40 | } 41 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 42 | if respReader != nil { 43 | defer respReader.Close() 44 | } 45 | if err != nil { 46 | return nil, errors.Wrap(err, "unable to get account config") 47 | } 48 | 49 | var result *Config 50 | decoder := json.NewDecoder(respReader) 51 | if err = decoder.Decode(&result); err != nil { 52 | return nil, errors.Wrap(err, "unable to decode get account config response") 53 | } 54 | 55 | return result, nil 56 | } 57 | 58 | type UpdateConfigInput struct { 59 | // DefaultNetwork is the network that docker containers are provisioned on. 60 | DefaultNetwork string `json:"default_network"` 61 | } 62 | 63 | // UpdateConfig updates configuration values for your account. 64 | func (c *ConfigClient) Update(ctx context.Context, input *UpdateConfigInput) (*Config, error) { 65 | fullPath := path.Join("/", c.client.AccountName, "config") 66 | reqInputs := client.RequestInput{ 67 | Method: http.MethodPost, 68 | Path: fullPath, 69 | Body: input, 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 update account config") 77 | } 78 | 79 | var result *Config 80 | decoder := json.NewDecoder(respReader) 81 | if err = decoder.Decode(&result); err != nil { 82 | return nil, errors.Wrap(err, "unable to decode update account config response") 83 | } 84 | 85 | return result, nil 86 | } 87 | -------------------------------------------------------------------------------- /cmd/triton/cmd/account/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package get 11 | 12 | import ( 13 | "fmt" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/account" 16 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 17 | "github.com/TritonDataCenter/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: "get", 26 | Short: "Show account information", 27 | SilenceUsage: true, 28 | PreRunE: func(cmd *cobra.Command, args []string) error { 29 | return nil 30 | }, 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | cons := conswriter.GetTerminal() 33 | 34 | c, err := cfg.NewTritonConfig() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | a, err := account.NewAccountClient(c) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | accDetails, err := a.Get() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | cons.Write([]byte(fmt.Sprintf("id: %s", accDetails.ID))) 50 | cons.Write([]byte(fmt.Sprintf("\nlogin: %s", accDetails.Login))) 51 | cons.Write([]byte(fmt.Sprintf("\nemail: %s", accDetails.Email))) 52 | cons.Write([]byte(fmt.Sprintf("\ncompanyName: %s", accDetails.CompanyName))) 53 | cons.Write([]byte(fmt.Sprintf("\nfirstName: %s", accDetails.FirstName))) 54 | cons.Write([]byte(fmt.Sprintf("\nlastName: %s", accDetails.LastName))) 55 | cons.Write([]byte(fmt.Sprintf("\npostalCode: %s", accDetails.PostalCode))) 56 | cons.Write([]byte(fmt.Sprintf("\ntriton_cns_enabled: %t", accDetails.TritonCNSEnabled))) 57 | cons.Write([]byte(fmt.Sprintf("\naddress: %s", accDetails.Address))) 58 | cons.Write([]byte(fmt.Sprintf("\ncity: %s", accDetails.City))) 59 | cons.Write([]byte(fmt.Sprintf("\nstate: %s", accDetails.State))) 60 | cons.Write([]byte(fmt.Sprintf("\ncountry: %s", accDetails.Country))) 61 | cons.Write([]byte(fmt.Sprintf("\nphone: %s", accDetails.Phone))) 62 | cons.Write([]byte(fmt.Sprintf("\nupdated: %s (%s)", accDetails.Updated.String(), cfg.FormatTime(accDetails.Updated)))) 63 | cons.Write([]byte(fmt.Sprintf("\ncreated: %s (%s)", accDetails.Created.String(), cfg.FormatTime(accDetails.Created)))) 64 | 65 | return nil 66 | }, 67 | }, 68 | 69 | Setup: func(parent *command.Command) error { 70 | return nil 71 | }, 72 | } 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/get/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package get 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 17 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 18 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 19 | "github.com/olekukonko/tablewriter" 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 a triton instance", 29 | SilenceUsage: true, 30 | PreRunE: func(cmd *cobra.Command, args []string) error { 31 | if cfg.GetMachineID() == "" && cfg.GetMachineName() == "" { 32 | return errors.New("Either `id` or `name` must be specified") 33 | } 34 | 35 | if cfg.GetMachineID() != "" && cfg.GetMachineName() != "" { 36 | return errors.New("Only 1 of `id` or `name` must be specified") 37 | } 38 | 39 | return nil 40 | }, 41 | RunE: func(cmd *cobra.Command, args []string) error { 42 | cons := conswriter.GetTerminal() 43 | 44 | c, err := cfg.NewTritonConfig() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | a, err := compute.NewComputeClient(c) 50 | if err != nil { 51 | return err 52 | } 53 | 54 | instance, err := a.GetInstance() 55 | if err != nil { 56 | return err 57 | } 58 | 59 | table := tablewriter.NewWriter(cons) 60 | table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 61 | table.SetHeaderLine(false) 62 | table.SetAutoFormatHeaders(true) 63 | 64 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_LEFT}) 65 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 66 | table.SetCenterSeparator("") 67 | table.SetColumnSeparator("") 68 | table.SetRowSeparator("") 69 | 70 | table.SetHeader([]string{"------", "------"}) 71 | 72 | table.Append([]string{"id", instance.ID}) 73 | table.Append([]string{"name", instance.Name}) 74 | table.Append([]string{"package", instance.Package}) 75 | table.Append([]string{"image", instance.Image}) 76 | table.Append([]string{"brand", instance.Brand}) 77 | table.Append([]string{"firewall enabled", fmt.Sprintf("%t", instance.FirewallEnabled)}) 78 | 79 | table.Render() 80 | 81 | return nil 82 | }, 83 | }, 84 | 85 | Setup: func(parent *command.Command) error { 86 | return nil 87 | }, 88 | } 89 | -------------------------------------------------------------------------------- /testutils/statebag.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package testutils 11 | 12 | import ( 13 | "sync" 14 | 15 | triton "github.com/TritonDataCenter/triton-go/v2" 16 | ) 17 | 18 | type TritonStateBag interface { 19 | Get(string) interface{} 20 | GetOk(string) (interface{}, bool) 21 | Put(string, interface{}) 22 | Remove(string) 23 | 24 | Config() *triton.ClientConfig 25 | 26 | Client() interface{} 27 | PutClient(interface{}) 28 | 29 | AppendError(error) 30 | ErrorsOrNil() []error 31 | } 32 | 33 | // TritonStateBag implements StateBag by using a normal map underneath 34 | // protected by a RWMutex. 35 | type basicTritonStateBag struct { 36 | TritonConfig *triton.ClientConfig 37 | TritonClient interface{} 38 | 39 | errors []error 40 | data map[string]interface{} 41 | 42 | l sync.RWMutex 43 | once sync.Once 44 | } 45 | 46 | func (b *basicTritonStateBag) Config() *triton.ClientConfig { 47 | b.l.RLock() 48 | defer b.l.RUnlock() 49 | 50 | return b.TritonConfig 51 | } 52 | 53 | func (b *basicTritonStateBag) Client() interface{} { 54 | b.l.RLock() 55 | defer b.l.RUnlock() 56 | 57 | return b.TritonClient 58 | } 59 | 60 | func (b *basicTritonStateBag) PutClient(client interface{}) { 61 | b.l.Lock() 62 | defer b.l.Unlock() 63 | 64 | b.TritonClient = client 65 | } 66 | 67 | func (b *basicTritonStateBag) AppendError(err error) { 68 | b.l.Lock() 69 | defer b.l.Unlock() 70 | 71 | if b.errors == nil { 72 | b.errors = make([]error, 0, 1) 73 | } 74 | 75 | b.errors = append(b.errors, err) 76 | } 77 | 78 | func (b *basicTritonStateBag) ErrorsOrNil() []error { 79 | b.l.RLock() 80 | defer b.l.RUnlock() 81 | 82 | return b.errors 83 | } 84 | 85 | func (b *basicTritonStateBag) Get(k string) interface{} { 86 | result, _ := b.GetOk(k) 87 | return result 88 | } 89 | 90 | func (b *basicTritonStateBag) GetOk(k string) (interface{}, bool) { 91 | b.l.RLock() 92 | defer b.l.RUnlock() 93 | 94 | result, ok := b.data[k] 95 | return result, ok 96 | } 97 | 98 | func (b *basicTritonStateBag) Put(k string, v interface{}) { 99 | b.l.Lock() 100 | defer b.l.Unlock() 101 | 102 | // Make sure the map is initialized one time, on write 103 | b.once.Do(func() { 104 | b.data = make(map[string]interface{}) 105 | }) 106 | 107 | // Write the data 108 | b.data[k] = v 109 | } 110 | 111 | func (b *basicTritonStateBag) Remove(k string) { 112 | b.l.Lock() 113 | defer b.l.Unlock() 114 | 115 | delete(b.data, k) 116 | } 117 | -------------------------------------------------------------------------------- /compute/datacenters.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package compute 11 | 12 | import ( 13 | "context" 14 | "encoding/json" 15 | "fmt" 16 | "net/http" 17 | "path" 18 | "sort" 19 | 20 | "github.com/TritonDataCenter/triton-go/v2/client" 21 | "github.com/TritonDataCenter/triton-go/v2/errors" 22 | pkgerrors "github.com/pkg/errors" 23 | ) 24 | 25 | type DataCentersClient struct { 26 | client *client.Client 27 | } 28 | 29 | type DataCenter struct { 30 | Name string `json:"name"` 31 | URL string `json:"url"` 32 | } 33 | 34 | type ListDataCentersInput struct{} 35 | 36 | func (c *DataCentersClient) List(ctx context.Context, _ *ListDataCentersInput) ([]*DataCenter, error) { 37 | fullPath := path.Join("/", c.client.AccountName, "datacenters") 38 | 39 | reqInputs := client.RequestInput{ 40 | Method: http.MethodGet, 41 | Path: fullPath, 42 | } 43 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 44 | if respReader != nil { 45 | defer respReader.Close() 46 | } 47 | if err != nil { 48 | return nil, pkgerrors.Wrap(err, "unable to list data centers") 49 | } 50 | 51 | var intermediate map[string]string 52 | decoder := json.NewDecoder(respReader) 53 | if err = decoder.Decode(&intermediate); err != nil { 54 | return nil, pkgerrors.Wrap(err, "unable to decode list data centers response") 55 | } 56 | 57 | keys := make([]string, len(intermediate)) 58 | i := 0 59 | for k := range intermediate { 60 | keys[i] = k 61 | i++ 62 | } 63 | sort.Strings(keys) 64 | 65 | result := make([]*DataCenter, len(intermediate)) 66 | i = 0 67 | for _, key := range keys { 68 | result[i] = &DataCenter{ 69 | Name: key, 70 | URL: intermediate[key], 71 | } 72 | i++ 73 | } 74 | 75 | return result, nil 76 | } 77 | 78 | type GetDataCenterInput struct { 79 | Name string 80 | } 81 | 82 | func (c *DataCentersClient) Get(ctx context.Context, input *GetDataCenterInput) (*DataCenter, error) { 83 | dcs, err := c.List(ctx, &ListDataCentersInput{}) 84 | if err != nil { 85 | return nil, pkgerrors.Wrap(err, "unable to get data center") 86 | } 87 | 88 | for _, dc := range dcs { 89 | if dc.Name == input.Name { 90 | return &DataCenter{ 91 | Name: input.Name, 92 | URL: dc.URL, 93 | }, nil 94 | } 95 | } 96 | 97 | return nil, &errors.APIError{ 98 | StatusCode: http.StatusNotFound, 99 | Code: "ResourceNotFound", 100 | Message: fmt.Sprintf("data center %q not found", input.Name), 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /network/network.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package network 11 | 12 | import ( 13 | "context" 14 | "encoding/json" 15 | "net/http" 16 | "path" 17 | 18 | "github.com/TritonDataCenter/triton-go/v2/client" 19 | "github.com/pkg/errors" 20 | ) 21 | 22 | type Network struct { 23 | Id string `json:"id"` 24 | Name string `json:"name"` 25 | Public bool `json:"public"` 26 | Fabric bool `json:"fabric"` 27 | Description string `json:"description"` 28 | Subnet string `json:"subnet"` 29 | ProvisioningStartIP string `json:"provision_start_ip"` 30 | ProvisioningEndIP string `json:"provision_end_ip"` 31 | Gateway string `json:"gateway"` 32 | Resolvers []string `json:"resolvers"` 33 | Routes map[string]string `json:"routes"` 34 | InternetNAT bool `json:"internet_nat"` 35 | } 36 | 37 | type ListInput struct{} 38 | 39 | func (c *NetworkClient) List(ctx context.Context, _ *ListInput) ([]*Network, error) { 40 | fullPath := path.Join("/", c.Client.AccountName, "networks") 41 | reqInputs := client.RequestInput{ 42 | Method: http.MethodGet, 43 | Path: fullPath, 44 | } 45 | respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) 46 | if respReader != nil { 47 | defer respReader.Close() 48 | } 49 | if err != nil { 50 | return nil, errors.Wrap(err, "unable to list networks") 51 | } 52 | 53 | var result []*Network 54 | decoder := json.NewDecoder(respReader) 55 | if err = decoder.Decode(&result); err != nil { 56 | return nil, errors.Wrap(err, "unable to decode list networks response") 57 | } 58 | 59 | return result, nil 60 | } 61 | 62 | type GetInput struct { 63 | ID string 64 | } 65 | 66 | func (c *NetworkClient) Get(ctx context.Context, input *GetInput) (*Network, error) { 67 | fullPath := path.Join("/", c.Client.AccountName, "networks", input.ID) 68 | reqInputs := client.RequestInput{ 69 | Method: http.MethodGet, 70 | Path: fullPath, 71 | } 72 | respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) 73 | if respReader != nil { 74 | defer respReader.Close() 75 | } 76 | if err != nil { 77 | return nil, errors.Wrap(err, "unable to get network") 78 | } 79 | 80 | var result *Network 81 | decoder := json.NewDecoder(respReader) 82 | if err = decoder.Decode(&result); err != nil { 83 | return nil, errors.Wrap(err, "unable to decode get network response") 84 | } 85 | 86 | return result, nil 87 | } 88 | -------------------------------------------------------------------------------- /cmd/manta/cmd/docs/man/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package man 11 | 12 | import ( 13 | "fmt" 14 | "os" 15 | "path" 16 | "strconv" 17 | "strings" 18 | 19 | "github.com/TritonDataCenter/triton-go/v2" 20 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 21 | "github.com/TritonDataCenter/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 Manta Object Storage cli man pages", 33 | Long: `This command automatically generates up-to-date man pages of Manta 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: "Manta", 43 | Section: strconv.Itoa(config.ManSect), 44 | Source: strings.Join([]string{"Manta", 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/man/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package man 11 | 12 | import ( 13 | "fmt" 14 | "os" 15 | "strconv" 16 | 17 | "path" 18 | "strings" 19 | 20 | "github.com/TritonDataCenter/triton-go/v2" 21 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 22 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 23 | "github.com/pkg/errors" 24 | "github.com/rs/zerolog/log" 25 | "github.com/spf13/cobra" 26 | "github.com/spf13/cobra/doc" 27 | "github.com/spf13/viper" 28 | ) 29 | 30 | var Cmd = &command.Command{ 31 | Cobra: &cobra.Command{ 32 | Use: "man", 33 | Short: "Generates and installs triton cli man pages", 34 | Long: `This command automatically generates up-to-date man pages of Triton CLI 35 | command-line interface. By default, it creates the man page files 36 | in the "docs/man" directory under the current directory.`, 37 | 38 | PreRunE: func(cmd *cobra.Command, args []string) error { 39 | return nil 40 | }, 41 | RunE: func(cmd *cobra.Command, args []string) error { 42 | header := &doc.GenManHeader{ 43 | Manual: "Triton", 44 | Section: strconv.Itoa(config.ManSect), 45 | Source: strings.Join([]string{"Triton", triton.Version}, " "), 46 | } 47 | 48 | manDir := viper.GetString(config.KeyDocManDir) 49 | 50 | manSectDir := path.Join(manDir, fmt.Sprintf("man%d", config.ManSect)) 51 | if _, err := os.Stat(manSectDir); os.IsNotExist(err) { 52 | if err := os.MkdirAll(manSectDir, 0777); err != nil { 53 | return errors.Wrapf(err, "unable to make mandir %q", manSectDir) 54 | } 55 | } 56 | 57 | cmd.Root().DisableAutoGenTag = true 58 | log.Info().Str("MANDIR", manDir).Int("section", config.ManSect).Msg("Installing man(1) pages") 59 | 60 | err := doc.GenManTree(cmd.Root(), header, manSectDir) 61 | if err != nil { 62 | return errors.Wrap(err, "unable to generate man(1) pages") 63 | } 64 | 65 | log.Info().Msg("Installation completed successfully.") 66 | 67 | return nil 68 | }, 69 | }, 70 | Setup: func(parent *command.Command) error { 71 | 72 | { 73 | const ( 74 | key = config.KeyDocManDir 75 | longName = "man-dir" 76 | shortName = "m" 77 | description = "Specify the MANDIR to use" 78 | defaultValue = config.DefaultManDir 79 | ) 80 | 81 | flags := parent.Cobra.PersistentFlags() 82 | flags.StringP(longName, shortName, defaultValue, description) 83 | viper.BindPFlag(key, flags.Lookup(longName)) 84 | viper.BindEnv(key, "MANDIR") 85 | viper.SetDefault(key, defaultValue) 86 | } 87 | 88 | return nil 89 | }, 90 | } 91 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /compute/client_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package compute_test 11 | 12 | import ( 13 | "context" 14 | "io/ioutil" 15 | "net/http" 16 | "path" 17 | "strings" 18 | "testing" 19 | 20 | "github.com/TritonDataCenter/triton-go/v2/compute" 21 | "github.com/TritonDataCenter/triton-go/v2/testutils" 22 | ) 23 | 24 | // MockComputeClient is used to mock out compute.ComputeClient for all tests 25 | // under the triton-go/compute package 26 | func MockComputeClient() *compute.ComputeClient { 27 | return &compute.ComputeClient{ 28 | Client: testutils.NewMockClient(testutils.MockClientInput{ 29 | AccountName: accountURL, 30 | }), 31 | } 32 | } 33 | 34 | const ( 35 | testHeaderName = "X-Test-Header" 36 | testHeaderVal1 = "number one" 37 | testHeaderVal2 = "number two" 38 | ) 39 | 40 | func TestSetHeader(t *testing.T) { 41 | computeClient := MockComputeClient() 42 | 43 | do := func(ctx context.Context, cc *compute.ComputeClient) error { 44 | defer testutils.DeactivateClient() 45 | 46 | header := &http.Header{} 47 | header.Add(testHeaderName, testHeaderVal1) 48 | header.Add(testHeaderName, testHeaderVal2) 49 | cc.SetHeader(header) 50 | 51 | _, err := cc.Datacenters().List(ctx, &compute.ListDataCentersInput{}) 52 | 53 | return err 54 | } 55 | 56 | t.Run("override header", func(t *testing.T) { 57 | testutils.RegisterResponder("GET", path.Join("/", accountURL, "datacenters"), overrideHeaderTest(t)) 58 | 59 | err := do(context.Background(), computeClient) 60 | if err != nil { 61 | t.Error(err) 62 | } 63 | }) 64 | } 65 | 66 | func overrideHeaderTest(t *testing.T) func(req *http.Request) (*http.Response, error) { 67 | return func(req *http.Request) (*http.Response, error) { 68 | // test existence of custom headers at all 69 | if req.Header.Get(testHeaderName) == "" { 70 | t.Errorf("request header should contain '%s'", testHeaderName) 71 | } 72 | testHeader := strings.Join(req.Header[testHeaderName], ",") 73 | // test override of initial header 74 | if !strings.Contains(testHeader, testHeaderVal1) { 75 | t.Errorf("request header should not contain %q: got %q", testHeaderVal1, testHeader) 76 | } 77 | if strings.Contains(testHeader, testHeaderVal2) { 78 | t.Errorf("request header should contain '%s': got '%s'", testHeaderVal2, testHeader) 79 | } 80 | 81 | header := http.Header{} 82 | header.Add("Content-Type", "application/json") 83 | 84 | body := strings.NewReader(`{ 85 | "us-central-1": "https://us-central-1.api.mnx.io" 86 | } 87 | `) 88 | 89 | return &http.Response{ 90 | StatusCode: http.StatusOK, 91 | Header: header, 92 | Body: ioutil.NopCloser(body), 93 | }, nil 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /examples/storage/mls/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | 19 | "encoding/pem" 20 | 21 | triton "github.com/TritonDataCenter/triton-go/v2" 22 | "github.com/TritonDataCenter/triton-go/v2/authentication" 23 | "github.com/TritonDataCenter/triton-go/v2/storage" 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 | MantaURL: os.Getenv("TRITON_URL"), 83 | AccountName: accountName, 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 | ctx := context.Background() 94 | input := &storage.ListDirectoryInput{} 95 | output, err := client.Dir().List(ctx, input) 96 | if err != nil { 97 | log.Fatalf("storage.Dir.List: %v", err) 98 | } 99 | 100 | for _, dir := range output.Entries { 101 | fmt.Println(dir.Name) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /compute/client.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package compute 11 | 12 | import ( 13 | "net/http" 14 | 15 | triton "github.com/TritonDataCenter/triton-go/v2" 16 | "github.com/TritonDataCenter/triton-go/v2/client" 17 | ) 18 | 19 | type ComputeClient struct { 20 | Client *client.Client 21 | } 22 | 23 | func newComputeClient(client *client.Client) *ComputeClient { 24 | return &ComputeClient{ 25 | Client: client, 26 | } 27 | } 28 | 29 | // NewClient returns a new client for working with Compute endpoints and 30 | // resources within CloudAPI 31 | func NewClient(config *triton.ClientConfig) (*ComputeClient, error) { 32 | // TODO: Utilize config interface within the function itself 33 | client, err := client.New( 34 | config.TritonURL, 35 | config.MantaURL, 36 | config.AccountName, 37 | config.Signers..., 38 | ) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return newComputeClient(client), nil 43 | } 44 | 45 | // SetHeaders allows a consumer of the current client to set custom headers for 46 | // the next backend HTTP request sent to CloudAPI 47 | func (c *ComputeClient) SetHeader(header *http.Header) { 48 | c.Client.RequestHeader = header 49 | } 50 | 51 | // Datacenters returns a Compute client used for accessing functions pertaining 52 | // to DataCenter functionality in the Triton API. 53 | func (c *ComputeClient) Datacenters() *DataCentersClient { 54 | return &DataCentersClient{c.Client} 55 | } 56 | 57 | // Images returns a Compute client used for accessing functions pertaining to 58 | // Images functionality in the Triton API. 59 | func (c *ComputeClient) Images() *ImagesClient { 60 | return &ImagesClient{c.Client} 61 | } 62 | 63 | // Machine returns a Compute client used for accessing functions pertaining to 64 | // machine functionality in the Triton API. 65 | func (c *ComputeClient) Instances() *InstancesClient { 66 | return &InstancesClient{c.Client} 67 | } 68 | 69 | // Packages returns a Compute client used for accessing functions pertaining to 70 | // Packages functionality in the Triton API. 71 | func (c *ComputeClient) Packages() *PackagesClient { 72 | return &PackagesClient{c.Client} 73 | } 74 | 75 | // Services returns a Compute client used for accessing functions pertaining to 76 | // Services functionality in the Triton API. 77 | func (c *ComputeClient) Services() *ServicesClient { 78 | return &ServicesClient{c.Client} 79 | } 80 | 81 | // Snapshots returns a Compute client used for accessing functions pertaining to 82 | // Snapshots functionality in the Triton API. 83 | func (c *ComputeClient) Snapshots() *SnapshotsClient { 84 | return &SnapshotsClient{c.Client} 85 | } 86 | 87 | // Snapshots returns a Compute client used for accessing functions pertaining to 88 | // Snapshots functionality in the Triton API. 89 | func (c *ComputeClient) Volumes() *VolumesClient { 90 | return &VolumesClient{c.Client} 91 | } 92 | -------------------------------------------------------------------------------- /examples/compute/volumes/list_volumes/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "context" 14 | "encoding/pem" 15 | "fmt" 16 | "io/ioutil" 17 | "log" 18 | "os" 19 | 20 | "github.com/TritonDataCenter/triton-go/v2" 21 | "github.com/TritonDataCenter/triton-go/v2/authentication" 22 | "github.com/TritonDataCenter/triton-go/v2/compute" 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: %v", 50 | keyMaterial, err) 51 | } 52 | block, _ := pem.Decode(keyBytes) 53 | if block == nil { 54 | log.Fatalf( 55 | "Failed to read key material %q: no key found", keyMaterial) 56 | } 57 | 58 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 59 | log.Fatalf( 60 | "Failed to read key %q: 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 | c, err := compute.NewClient(config) 88 | if err != nil { 89 | log.Fatalf("compute.NewClient: %v", err) 90 | } 91 | 92 | listInput := &compute.ListVolumesInput{} 93 | volumes, err := c.Volumes().List(context.Background(), listInput) 94 | if err != nil { 95 | log.Fatalf("compute.Volumes.List: %v", err) 96 | } 97 | numInstances := 0 98 | for _, volume := range volumes { 99 | numInstances++ 100 | fmt.Println(fmt.Sprintf("-- Volume: %s", volume.Name)) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/compute/instances/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "context" 14 | "encoding/pem" 15 | "fmt" 16 | "io/ioutil" 17 | "log" 18 | "os" 19 | 20 | triton "github.com/TritonDataCenter/triton-go/v2" 21 | "github.com/TritonDataCenter/triton-go/v2/authentication" 22 | "github.com/TritonDataCenter/triton-go/v2/compute" 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 | c, err := compute.NewClient(config) 88 | if err != nil { 89 | log.Fatalf("compute.NewClient: %v", err) 90 | } 91 | 92 | listInput := &compute.ListInstancesInput{} 93 | instances, err := c.Instances().List(context.Background(), listInput) 94 | if err != nil { 95 | log.Fatalf("compute.Instances.List: %v", err) 96 | } 97 | numInstances := 0 98 | for _, instance := range instances { 99 | numInstances++ 100 | fmt.Println(fmt.Sprintf("-- Instance: %v", instance.Name)) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/storage/force_delete/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "encoding/pem" 14 | "io/ioutil" 15 | "log" 16 | "os" 17 | 18 | "context" 19 | 20 | "fmt" 21 | 22 | triton "github.com/TritonDataCenter/triton-go/v2" 23 | "github.com/TritonDataCenter/triton-go/v2/authentication" 24 | "github.com/TritonDataCenter/triton-go/v2/storage" 25 | ) 26 | 27 | func main() { 28 | keyID := os.Getenv("TRITON_KEY_ID") 29 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 30 | mantaUser := os.Getenv("MANTA_USER") 31 | mantaFolder := os.Getenv("MANTA_FOLDER") 32 | userName := os.Getenv("TRITON_USER") 33 | 34 | var signer authentication.Signer 35 | var err error 36 | 37 | if keyMaterial == "" { 38 | input := authentication.SSHAgentSignerInput{ 39 | KeyID: keyID, 40 | AccountName: mantaUser, 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 %s: %s", 53 | keyMaterial, err) 54 | } 55 | block, _ := pem.Decode(keyBytes) 56 | if block == nil { 57 | log.Fatalf( 58 | "Failed to read key material '%s': no key found", keyMaterial) 59 | } 60 | 61 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 62 | log.Fatalf( 63 | "Failed to read key '%s': password protected keys are\n"+ 64 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 65 | } 66 | 67 | } else { 68 | keyBytes = []byte(keyMaterial) 69 | } 70 | 71 | input := authentication.PrivateKeySignerInput{ 72 | KeyID: keyID, 73 | PrivateKeyMaterial: keyBytes, 74 | AccountName: mantaUser, 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: mantaUser, 86 | Username: userName, 87 | Signers: []authentication.Signer{signer}, 88 | } 89 | 90 | client, err := storage.NewClient(config) 91 | if err != nil { 92 | log.Fatalf("NewClient: %v", err) 93 | } 94 | 95 | err = client.Dir().Delete(context.Background(), &storage.DeleteDirectoryInput{ 96 | DirectoryName: mantaFolder, 97 | ForceDelete: true, 98 | }) 99 | 100 | if err != nil { 101 | log.Fatalf("Error deleting nested folder structure: %v", err) 102 | } 103 | fmt.Println("Successfully deleted all nested objects") 104 | } 105 | -------------------------------------------------------------------------------- /examples/storage/sign_url/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "encoding/pem" 14 | "io/ioutil" 15 | "log" 16 | "os" 17 | 18 | "net/http" 19 | "time" 20 | 21 | triton "github.com/TritonDataCenter/triton-go/v2" 22 | "github.com/TritonDataCenter/triton-go/v2/authentication" 23 | "github.com/TritonDataCenter/triton-go/v2/storage" 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 | MantaURL: os.Getenv("TRITON_URL"), 83 | AccountName: accountName, 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 | input := &storage.SignURLInput{ 94 | ObjectPath: "/stor/books/treasure_island.txt", 95 | Method: http.MethodGet, 96 | ValidityPeriod: 5 * time.Minute, 97 | } 98 | signed, err := client.SignURL(input) 99 | if err != nil { 100 | log.Fatalf("SignURL: %v", err) 101 | } 102 | 103 | log.Printf("Signed URL: %s", signed.SignedURL("http")) 104 | } 105 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/list/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package list 11 | 12 | import ( 13 | "strings" 14 | 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/agent/compute" 16 | cfg "github.com/TritonDataCenter/triton-go/v2/cmd/config" 17 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 18 | tc "github.com/TritonDataCenter/triton-go/v2/compute" 19 | "github.com/olekukonko/tablewriter" 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: "list", 28 | Short: "list triton instances", 29 | Aliases: []string{"ls"}, 30 | SilenceUsage: true, 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.NewTritonConfig() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | a, err := compute.NewComputeClient(c) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | instances, err := a.GetInstanceList() 48 | if err != nil { 49 | return err 50 | } 51 | 52 | images, err := a.GetImagesList() 53 | if err != nil { 54 | return err 55 | } 56 | 57 | table := tablewriter.NewWriter(cons) 58 | table.SetHeaderAlignment(tablewriter.ALIGN_RIGHT) 59 | table.SetHeaderLine(false) 60 | table.SetAutoFormatHeaders(true) 61 | 62 | table.SetColumnAlignment([]int{tablewriter.ALIGN_LEFT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT, tablewriter.ALIGN_RIGHT}) 63 | table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) 64 | table.SetCenterSeparator("") 65 | table.SetColumnSeparator("") 66 | table.SetRowSeparator("") 67 | 68 | table.SetHeader([]string{"SHORTID", "NAME", "IMG", "STATE", "FLAGS", "AGE"}) 69 | 70 | var numInstances uint 71 | for _, instance := range instances { 72 | table.Append([]string{string(instance.ID[:8]), instance.Name, a.FormatImageName(images, instance.Image), instance.State, formatInstanceFlags(instance), cfg.FormatTime(instance.Created)}) 73 | numInstances++ 74 | } 75 | 76 | table.Render() 77 | 78 | return nil 79 | }, 80 | }, 81 | Setup: func(parent *command.Command) error { 82 | return nil 83 | }, 84 | } 85 | 86 | func formatInstanceFlags(instance *tc.Instance) string { 87 | flags := []string{} 88 | 89 | if instance.Docker { 90 | flags = append(flags, "D") 91 | } 92 | if strings.ToLower(instance.Brand) == "kvm" { 93 | flags = append(flags, "K") 94 | } 95 | if instance.FirewallEnabled { 96 | flags = append(flags, "F") 97 | } 98 | 99 | return strings.Join(flags, "") 100 | 101 | } 102 | -------------------------------------------------------------------------------- /examples/storage/object_put/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | 19 | "encoding/pem" 20 | 21 | triton "github.com/TritonDataCenter/triton-go/v2" 22 | "github.com/TritonDataCenter/triton-go/v2/authentication" 23 | "github.com/TritonDataCenter/triton-go/v2/storage" 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 | MantaURL: os.Getenv("TRITON_URL"), 83 | AccountName: accountName, 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/foo.txt", 101 | ObjectReader: reader, 102 | }) 103 | if err != nil { 104 | log.Fatalf("storage.Objects.Put: %v", err) 105 | } 106 | fmt.Println("Successfully uploaded /tmp/foo.txt!") 107 | } 108 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/storage/list_directory/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "context" 14 | "encoding/pem" 15 | "fmt" 16 | "io/ioutil" 17 | "log" 18 | "os" 19 | 20 | triton "github.com/TritonDataCenter/triton-go/v2" 21 | "github.com/TritonDataCenter/triton-go/v2/authentication" 22 | "github.com/TritonDataCenter/triton-go/v2/storage" 23 | ) 24 | 25 | const path = "/stor" 26 | 27 | func main() { 28 | var ( 29 | signer authentication.Signer 30 | err error 31 | 32 | keyID = os.Getenv("MANTA_KEY_ID") 33 | accountName = os.Getenv("MANTA_USER") 34 | keyMaterial = os.Getenv("MANTA_KEY_MATERIAL") 35 | userName = os.Getenv("TRITON_USER") 36 | ) 37 | 38 | if keyMaterial == "" { 39 | input := authentication.SSHAgentSignerInput{ 40 | KeyID: keyID, 41 | AccountName: accountName, 42 | Username: userName, 43 | } 44 | signer, err = authentication.NewSSHAgentSigner(input) 45 | if err != nil { 46 | log.Fatalf("error creating SSH agent signer: %v", err) 47 | } 48 | } else { 49 | var keyBytes []byte 50 | if _, err = os.Stat(keyMaterial); err == nil { 51 | keyBytes, err = ioutil.ReadFile(keyMaterial) 52 | if err != nil { 53 | log.Fatalf("error reading key material from %q: %v", 54 | keyMaterial, err) 55 | } 56 | block, _ := pem.Decode(keyBytes) 57 | if block == nil { 58 | log.Fatalf( 59 | "failed to read key material %q: no key found", keyMaterial) 60 | } 61 | 62 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 63 | log.Fatalf("failed to read key %q: password protected keys are\n"+ 64 | "not currently supported, decrypt key prior to use", 65 | keyMaterial) 66 | } 67 | 68 | } else { 69 | keyBytes = []byte(keyMaterial) 70 | } 71 | 72 | input := authentication.PrivateKeySignerInput{ 73 | KeyID: keyID, 74 | PrivateKeyMaterial: keyBytes, 75 | AccountName: accountName, 76 | Username: userName, 77 | } 78 | signer, err = authentication.NewPrivateKeySigner(input) 79 | if err != nil { 80 | log.Fatalf("error creating SSH private key signer: %v", err) 81 | } 82 | } 83 | 84 | config := &triton.ClientConfig{ 85 | MantaURL: os.Getenv("MANTA_URL"), 86 | AccountName: accountName, 87 | Username: userName, 88 | Signers: []authentication.Signer{signer}, 89 | } 90 | 91 | client, err := storage.NewClient(config) 92 | if err != nil { 93 | log.Fatalf("failed to init storage client: %v", err) 94 | } 95 | 96 | ctx := context.Background() 97 | output, err := client.Dir().List(ctx, &storage.ListDirectoryInput{ 98 | DirectoryName: path, 99 | }) 100 | if err != nil { 101 | fmt.Printf("could not find %q\n", path) 102 | return 103 | } 104 | 105 | for _, item := range output.Entries { 106 | fmt.Print("******* ITEM *******\n") 107 | fmt.Printf("Name: %s\n", item.Name) 108 | fmt.Printf("Type: %s\n", item.Type) 109 | fmt.Print("\n") 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /examples/storage/force_put/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "encoding/pem" 14 | "io/ioutil" 15 | "log" 16 | "os" 17 | 18 | "context" 19 | 20 | "fmt" 21 | 22 | triton "github.com/TritonDataCenter/triton-go/v2" 23 | "github.com/TritonDataCenter/triton-go/v2/authentication" 24 | "github.com/TritonDataCenter/triton-go/v2/storage" 25 | ) 26 | 27 | func main() { 28 | keyID := os.Getenv("TRITON_KEY_ID") 29 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 30 | mantaUser := os.Getenv("MANTA_USER") 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 | reader, err := os.Open("/tmp/foo.txt") 95 | if err != nil { 96 | log.Fatalf("os.Open: %v", err) 97 | } 98 | defer reader.Close() 99 | 100 | err = client.Objects().Put(context.Background(), &storage.PutObjectInput{ 101 | ObjectPath: "/stor/folder1/folder2/folder3/folder4/foo.txt", 102 | ObjectReader: reader, 103 | ForceInsert: true, 104 | }) 105 | 106 | if err != nil { 107 | log.Fatalf("Error creating nested folder structure: %v", err) 108 | } 109 | fmt.Println("Successfully uploaded /tmp/foo.txt to /stor/folder1/folder2/folder3/folder4/foo.txt") 110 | } 111 | -------------------------------------------------------------------------------- /examples/account/keys/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "log" 16 | "os" 17 | 18 | "encoding/pem" 19 | "io/ioutil" 20 | 21 | triton "github.com/TritonDataCenter/triton-go/v2" 22 | "github.com/TritonDataCenter/triton-go/v2/account" 23 | "github.com/TritonDataCenter/triton-go/v2/authentication" 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 | a, err := account.NewClient(config) 89 | if err != nil { 90 | log.Fatalf("failed to init a new account client: %v", err) 91 | } 92 | 93 | keys, err := a.Keys().List(context.Background(), &account.ListKeysInput{}) 94 | if err != nil { 95 | log.Fatalf("failed to list keys: %v", err) 96 | } 97 | 98 | for _, key := range keys { 99 | fmt.Println("Key Name:", key.Name) 100 | } 101 | 102 | if key := keys[0]; key != nil { 103 | input := &account.GetKeyInput{ 104 | KeyName: key.Name, 105 | } 106 | 107 | key, err := a.Keys().Get(context.Background(), input) 108 | if err != nil { 109 | log.Fatalf("failed to get key: %v", err) 110 | } 111 | 112 | fmt.Println("First Key:", key.Key) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /cmd/internal/logger/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package logger 11 | 12 | import ( 13 | "fmt" 14 | "io" 15 | "io/ioutil" 16 | stdlog "log" 17 | "os" 18 | "time" 19 | 20 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 21 | "github.com/mattn/go-isatty" 22 | "github.com/pkg/errors" 23 | "github.com/rs/zerolog" 24 | "github.com/rs/zerolog/log" 25 | "github.com/sean-/conswriter" 26 | "github.com/spf13/viper" 27 | ) 28 | 29 | const ( 30 | // Use a log format that resembles time.RFC3339Nano but includes all trailing 31 | // zeros so that we get fixed-width logging. 32 | logTimeFormat = "2006-01-02T15:04:05.000000000Z07:00" 33 | ) 34 | 35 | var stdLogger *stdlog.Logger 36 | 37 | func init() { 38 | // Initialize zerolog with a set set of defaults. Re-initialization of 39 | // logging with user-supplied configuration parameters happens in Setup(). 40 | 41 | // os.Stderr isn't guaranteed to be thread-safe, wrap in a sync writer. Files 42 | // are guaranteed to be safe, terminals are not. 43 | w := zerolog.ConsoleWriter{ 44 | Out: os.Stderr, 45 | NoColor: true, 46 | } 47 | zlog := zerolog.New(zerolog.SyncWriter(w)).With().Timestamp().Logger() 48 | 49 | zerolog.DurationFieldUnit = time.Microsecond 50 | zerolog.DurationFieldInteger = true 51 | zerolog.TimeFieldFormat = logTimeFormat 52 | zerolog.SetGlobalLevel(zerolog.InfoLevel) 53 | 54 | log.Logger = zlog 55 | 56 | stdlog.SetFlags(0) 57 | stdlog.SetOutput(zlog) 58 | } 59 | 60 | func Setup() error { 61 | logLevel, err := setLogLevel() 62 | if err != nil { 63 | return errors.Wrap(err, "unable to set log level") 64 | } 65 | 66 | var logWriter io.Writer 67 | if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) { 68 | logWriter = conswriter.GetTerminal() 69 | } else { 70 | logWriter = os.Stderr 71 | } 72 | 73 | logFmt, err := getLogFormat() 74 | if err != nil { 75 | return errors.Wrap(err, "unable to parse log format") 76 | } 77 | 78 | if logFmt == FormatAuto { 79 | if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) { 80 | logFmt = FormatHuman 81 | } else { 82 | logFmt = FormatZerolog 83 | } 84 | } 85 | 86 | var zlog zerolog.Logger 87 | switch logFmt { 88 | case FormatZerolog: 89 | zlog = zerolog.New(logWriter).With().Timestamp().Logger() 90 | case FormatHuman: 91 | useColor := viper.GetBool(config.KeyLogTermColor) 92 | w := zerolog.ConsoleWriter{ 93 | Out: logWriter, 94 | NoColor: !useColor, 95 | } 96 | zlog = zerolog.New(w).With().Timestamp().Logger() 97 | default: 98 | return fmt.Errorf("unsupported log format: %q", logFmt) 99 | } 100 | 101 | log.Logger = zlog 102 | 103 | stdlog.SetFlags(0) 104 | stdlog.SetOutput(zlog) 105 | stdLogger = &stdlog.Logger{} 106 | 107 | // In order to prevent random libraries from hooking the standard logger and 108 | // filling the logger with garbage, discard all log entries. At debug level, 109 | // however, let it all through. 110 | if logLevel != LevelDebug { 111 | stdLogger.SetOutput(ioutil.Discard) 112 | } else { 113 | stdLogger.SetOutput(zlog) 114 | } 115 | 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /examples/account/account_info/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | 19 | "encoding/pem" 20 | 21 | triton "github.com/TritonDataCenter/triton-go/v2" 22 | "github.com/TritonDataCenter/triton-go/v2/account" 23 | "github.com/TritonDataCenter/triton-go/v2/authentication" 24 | ) 25 | 26 | func printAccount(acct *account.Account) { 27 | fmt.Println("Account ID:", acct.ID) 28 | fmt.Println("Account Email:", acct.Email) 29 | fmt.Println("Account Login:", acct.Login) 30 | } 31 | 32 | func main() { 33 | keyID := os.Getenv("TRITON_KEY_ID") 34 | accountName := os.Getenv("TRITON_ACCOUNT") 35 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 36 | userName := os.Getenv("TRITON_USER") 37 | 38 | var signer authentication.Signer 39 | var err error 40 | 41 | if keyMaterial == "" { 42 | input := authentication.SSHAgentSignerInput{ 43 | KeyID: keyID, 44 | AccountName: accountName, 45 | Username: userName, 46 | } 47 | signer, err = authentication.NewSSHAgentSigner(input) 48 | if err != nil { 49 | log.Fatalf("Error Creating SSH Agent Signer: %v", err) 50 | } 51 | } else { 52 | var keyBytes []byte 53 | if _, err = os.Stat(keyMaterial); err == nil { 54 | keyBytes, err = ioutil.ReadFile(keyMaterial) 55 | if err != nil { 56 | log.Fatalf("Error reading key material from %s: %s", 57 | keyMaterial, err) 58 | } 59 | block, _ := pem.Decode(keyBytes) 60 | if block == nil { 61 | log.Fatalf( 62 | "Failed to read key material '%s': no key found", keyMaterial) 63 | } 64 | 65 | if block.Headers["Proc-Type"] == "4,ENCRYPTED" { 66 | log.Fatalf( 67 | "Failed to read key '%s': password protected keys are\n"+ 68 | "not currently supported. Please decrypt the key prior to use.", keyMaterial) 69 | } 70 | 71 | } else { 72 | keyBytes = []byte(keyMaterial) 73 | } 74 | 75 | input := authentication.PrivateKeySignerInput{ 76 | KeyID: keyID, 77 | PrivateKeyMaterial: keyBytes, 78 | AccountName: accountName, 79 | Username: userName, 80 | } 81 | signer, err = authentication.NewPrivateKeySigner(input) 82 | if err != nil { 83 | log.Fatalf("Error Creating SSH Private Key Signer: %v", err) 84 | } 85 | } 86 | 87 | config := &triton.ClientConfig{ 88 | TritonURL: os.Getenv("TRITON_URL"), 89 | AccountName: accountName, 90 | Username: userName, 91 | Signers: []authentication.Signer{signer}, 92 | } 93 | 94 | a, err := account.NewClient(config) 95 | if err != nil { 96 | log.Fatalf("compute.NewClient: %v", err) 97 | } 98 | 99 | acct, err := a.Get(context.Background(), &account.GetInput{}) 100 | if err != nil { 101 | log.Fatalf("account.Get: %v", err) 102 | } 103 | 104 | fmt.Println("New ----") 105 | printAccount(acct) 106 | 107 | input := &account.UpdateInput{ 108 | CompanyName: fmt.Sprintf("%s-old", acct.CompanyName), 109 | } 110 | 111 | updatedAcct, err := a.Update(context.Background(), input) 112 | if err != nil { 113 | log.Fatalf("account.Update: %v", err) 114 | } 115 | 116 | fmt.Println("New ----") 117 | printAccount(updatedAcct) 118 | } 119 | -------------------------------------------------------------------------------- /cmd/triton/cmd/docs/md/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package md 11 | 12 | import ( 13 | "fmt" 14 | "os" 15 | "path" 16 | "path/filepath" 17 | "strings" 18 | "time" 19 | 20 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 21 | "github.com/TritonDataCenter/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 | const template = `--- 30 | date: %s 31 | title: "%s" 32 | slug: %s 33 | url: %s 34 | --- 35 | ` 36 | 37 | var Cmd = &command.Command{ 38 | Cobra: &cobra.Command{ 39 | Use: "doc", 40 | Short: "Generates and installs triton cli documentation in markdown", 41 | Long: `Generate Markdown documentation for the Triton CLI. 42 | 43 | It creates one Markdown file per command `, 44 | 45 | PreRunE: func(cmd *cobra.Command, args []string) error { 46 | return nil 47 | }, 48 | RunE: func(cmd *cobra.Command, args []string) error { 49 | mdDir := viper.GetString(config.KeyDocMarkdownDir) 50 | 51 | if _, err := os.Stat(mdDir); os.IsNotExist(err) { 52 | if err := os.MkdirAll(mdDir, 0777); err != nil { 53 | return errors.Wrapf(err, "unable to make mddir %q", mdDir) 54 | } 55 | } 56 | 57 | log.Info().Str(config.KeyDocMarkdownDir, mdDir).Msg("Installing markdown documentation") 58 | 59 | now := time.Now().UTC().Format(time.RFC3339) 60 | prefix := viper.GetString(config.KeyDocMarkdownURLPrefix) 61 | prepender := func(filename string) string { 62 | name := filepath.Base(filename) 63 | base := strings.TrimSuffix(name, path.Ext(name)) 64 | url := prefix + path.Join("/", strings.ToLower(base), "/") 65 | return fmt.Sprintf(template, now, strings.Replace(base, "_", " ", -1), base, url) 66 | } 67 | 68 | linkHandler := func(name string) string { 69 | base := strings.TrimSuffix(name, path.Ext(name)) 70 | return prefix + path.Join("/", strings.ToLower(base), "/") 71 | } 72 | 73 | doc.GenMarkdownTreeCustom(cmd.Root(), mdDir, prepender, linkHandler) 74 | 75 | log.Info().Msg("Installation completed successfully.") 76 | 77 | return nil 78 | }, 79 | }, 80 | Setup: func(parent *command.Command) error { 81 | 82 | { 83 | const ( 84 | key = config.KeyDocMarkdownDir 85 | longName = "dir" 86 | shortName = "d" 87 | description = "Specify the directory for generated Markdown files" 88 | defaultValue = config.DefaultMarkdownDir 89 | ) 90 | 91 | flags := parent.Cobra.Flags() 92 | flags.StringP(longName, shortName, defaultValue, description) 93 | viper.BindPFlag(key, flags.Lookup(longName)) 94 | viper.SetDefault(key, defaultValue) 95 | } 96 | 97 | { 98 | const ( 99 | key = config.KeyDocMarkdownURLPrefix 100 | longName = "url-prefix" 101 | shortName = "p" 102 | description = "Specify the prefix for links generated by Markdown" 103 | defaultValue = config.DefaultMarkdownURLPrefix 104 | ) 105 | 106 | flags := parent.Cobra.Flags() 107 | flags.String(longName, defaultValue, description) 108 | viper.BindPFlag(key, flags.Lookup(longName)) 109 | viper.SetDefault(key, defaultValue) 110 | } 111 | 112 | return nil 113 | }, 114 | } 115 | -------------------------------------------------------------------------------- /cmd/manta/cmd/docs/md/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package md 11 | 12 | import ( 13 | "fmt" 14 | "os" 15 | "path" 16 | "path/filepath" 17 | "strings" 18 | "time" 19 | 20 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 21 | "github.com/TritonDataCenter/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 | const template = `--- 30 | date: %s 31 | title: "%s" 32 | slug: %s 33 | url: %s 34 | --- 35 | ` 36 | 37 | var Cmd = &command.Command{ 38 | Cobra: &cobra.Command{ 39 | Use: "doc", 40 | Short: "Generates and installs Manta Object Storage cli documentation in markdown", 41 | Long: `Generate Markdown documentation for the Manta CLI. 42 | 43 | It creates one Markdown file per command `, 44 | 45 | PreRunE: func(cmd *cobra.Command, args []string) error { 46 | return nil 47 | }, 48 | RunE: func(cmd *cobra.Command, args []string) error { 49 | mdDir := viper.GetString(config.KeyDocMarkdownDir) 50 | 51 | if _, err := os.Stat(mdDir); os.IsNotExist(err) { 52 | if err := os.MkdirAll(mdDir, 0777); err != nil { 53 | return errors.Wrapf(err, "unable to make mddir %q", mdDir) 54 | } 55 | } 56 | 57 | log.Info().Str(config.KeyDocMarkdownDir, mdDir).Msg("Installing markdown documentation") 58 | 59 | now := time.Now().UTC().Format(time.RFC3339) 60 | prefix := viper.GetString(config.KeyDocMarkdownURLPrefix) 61 | prepender := func(filename string) string { 62 | name := filepath.Base(filename) 63 | base := strings.TrimSuffix(name, path.Ext(name)) 64 | url := prefix + path.Join("/", strings.ToLower(base), "/") 65 | return fmt.Sprintf(template, now, strings.Replace(base, "_", " ", -1), base, url) 66 | } 67 | 68 | linkHandler := func(name string) string { 69 | base := strings.TrimSuffix(name, path.Ext(name)) 70 | return prefix + path.Join("/", strings.ToLower(base), "/") 71 | } 72 | 73 | doc.GenMarkdownTreeCustom(cmd.Root(), mdDir, prepender, linkHandler) 74 | 75 | log.Info().Msg("Installation completed successfully.") 76 | 77 | return nil 78 | }, 79 | }, 80 | Setup: func(parent *command.Command) error { 81 | 82 | { 83 | const ( 84 | key = config.KeyDocMarkdownDir 85 | longName = "dir" 86 | shortName = "d" 87 | description = "Specify the directory for generated Markdown files" 88 | defaultValue = config.DefaultMarkdownDir 89 | ) 90 | 91 | flags := parent.Cobra.Flags() 92 | flags.StringP(longName, shortName, defaultValue, description) 93 | viper.BindPFlag(key, flags.Lookup(longName)) 94 | viper.SetDefault(key, defaultValue) 95 | } 96 | 97 | { 98 | const ( 99 | key = config.KeyDocMarkdownURLPrefix 100 | longName = "url-prefix" 101 | shortName = "p" 102 | description = "Specify the prefix for links generated by Markdown" 103 | defaultValue = config.DefaultMarkdownURLPrefix 104 | ) 105 | 106 | flags := parent.Cobra.Flags() 107 | flags.String(longName, defaultValue, description) 108 | viper.BindPFlag(key, flags.Lookup(longName)) 109 | viper.SetDefault(key, defaultValue) 110 | } 111 | 112 | return nil 113 | }, 114 | } 115 | -------------------------------------------------------------------------------- /account/account.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package account 11 | 12 | import ( 13 | "context" 14 | "encoding/json" 15 | "net/http" 16 | "path" 17 | "time" 18 | 19 | "github.com/TritonDataCenter/triton-go/v2/client" 20 | "github.com/pkg/errors" 21 | ) 22 | 23 | type Account struct { 24 | ID string `json:"id"` 25 | Login string `json:"login"` 26 | Email string `json:"email"` 27 | CompanyName string `json:"companyName"` 28 | FirstName string `json:"firstName"` 29 | LastName string `json:"lastName"` 30 | Address string `json:"address"` 31 | PostalCode string `json:"postalCode"` 32 | City string `json:"city"` 33 | State string `json:"state"` 34 | Country string `json:"country"` 35 | Phone string `json:"phone"` 36 | Created time.Time `json:"created"` 37 | Updated time.Time `json:"updated"` 38 | TritonCNSEnabled bool `json:"triton_cns_enabled"` 39 | } 40 | 41 | type GetInput struct{} 42 | 43 | func (c AccountClient) Get(ctx context.Context, input *GetInput) (*Account, error) { 44 | fullPath := path.Join("/", c.Client.AccountName) 45 | reqInputs := client.RequestInput{ 46 | Method: http.MethodGet, 47 | Path: fullPath, 48 | } 49 | respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) 50 | if respReader != nil { 51 | defer respReader.Close() 52 | } 53 | if err != nil { 54 | return nil, errors.Wrap(err, "unable to get account details") 55 | } 56 | 57 | var result *Account 58 | decoder := json.NewDecoder(respReader) 59 | if err = decoder.Decode(&result); err != nil { 60 | return nil, errors.Wrap(err, "unable to decode get account details") 61 | } 62 | 63 | return result, nil 64 | } 65 | 66 | type UpdateInput struct { 67 | Email string `json:"email,omitempty"` 68 | CompanyName string `json:"companyName,omitempty"` 69 | FirstName string `json:"firstName,omitempty"` 70 | LastName string `json:"lastName,omitempty"` 71 | Address string `json:"address,omitempty"` 72 | PostalCode string `json:"postalCode,omitempty"` 73 | City string `json:"city,omitempty"` 74 | State string `json:"state,omitempty"` 75 | Country string `json:"country,omitempty"` 76 | Phone string `json:"phone,omitempty"` 77 | TritonCNSEnabled bool `json:"triton_cns_enabled,omitempty"` 78 | } 79 | 80 | // UpdateAccount updates your account details with the given parameters. 81 | // TODO(jen20) Work out a safe way to test this 82 | func (c AccountClient) Update(ctx context.Context, input *UpdateInput) (*Account, error) { 83 | fullPath := path.Join("/", c.Client.AccountName) 84 | reqInputs := client.RequestInput{ 85 | Method: http.MethodPost, 86 | Path: fullPath, 87 | Body: input, 88 | } 89 | respReader, err := c.Client.ExecuteRequest(ctx, reqInputs) 90 | if err != nil { 91 | return nil, errors.Wrap(err, "unable to update account") 92 | } 93 | if respReader != nil { 94 | defer respReader.Close() 95 | } 96 | 97 | var result *Account 98 | decoder := json.NewDecoder(respReader) 99 | if err = decoder.Decode(&result); err != nil { 100 | return nil, errors.Wrap(err, "unable to decode update account response") 101 | } 102 | 103 | return result, nil 104 | } 105 | -------------------------------------------------------------------------------- /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/TritonDataCenter/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 | -------------------------------------------------------------------------------- /examples/account/config/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package main 11 | 12 | import ( 13 | "context" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | 19 | "encoding/pem" 20 | 21 | triton "github.com/TritonDataCenter/triton-go/v2" 22 | "github.com/TritonDataCenter/triton-go/v2/account" 23 | "github.com/TritonDataCenter/triton-go/v2/authentication" 24 | "github.com/TritonDataCenter/triton-go/v2/network" 25 | ) 26 | 27 | func main() { 28 | keyID := os.Getenv("TRITON_KEY_ID") 29 | accountName := os.Getenv("TRITON_ACCOUNT") 30 | keyMaterial := os.Getenv("TRITON_KEY_MATERIAL") 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: accountName, 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: accountName, 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 | TritonURL: os.Getenv("TRITON_URL"), 84 | AccountName: accountName, 85 | Username: userName, 86 | Signers: []authentication.Signer{signer}, 87 | } 88 | 89 | nc, err := network.NewClient(config) 90 | if err != nil { 91 | log.Fatalf("network.NewClient: %v", err) 92 | } 93 | 94 | ac, err := account.NewClient(config) 95 | if err != nil { 96 | log.Fatalf("account.NewClient: %v", err) 97 | } 98 | 99 | cfg, err := ac.Config().Get(context.Background(), &account.GetConfigInput{}) 100 | if err != nil { 101 | log.Fatalf("account.Config.Get: %v", err) 102 | } 103 | currentNet := cfg.DefaultNetwork 104 | fmt.Println("Current Network:", currentNet) 105 | 106 | var defaultNet string 107 | networks, err := nc.List(context.Background(), &network.ListInput{}) 108 | if err != nil { 109 | log.Fatalf("network.List: %v", err) 110 | } 111 | for _, iterNet := range networks { 112 | if iterNet.Id != currentNet { 113 | defaultNet = iterNet.Id 114 | } 115 | } 116 | fmt.Println("Chosen Network:", defaultNet) 117 | 118 | input := &account.UpdateConfigInput{ 119 | DefaultNetwork: defaultNet, 120 | } 121 | _, err = ac.Config().Update(context.Background(), input) 122 | if err != nil { 123 | log.Fatalf("account.Config.Update: %v", err) 124 | } 125 | 126 | cfg, err = ac.Config().Get(context.Background(), &account.GetConfigInput{}) 127 | if err != nil { 128 | log.Fatalf("account.Config.Get: %v", err) 129 | } 130 | fmt.Println("Default Network:", cfg.DefaultNetwork) 131 | } 132 | -------------------------------------------------------------------------------- /testutils/runner.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package testutils 11 | 12 | import ( 13 | "errors" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | "testing" 19 | 20 | triton "github.com/TritonDataCenter/triton-go/v2" 21 | "github.com/TritonDataCenter/triton-go/v2/authentication" 22 | ) 23 | 24 | const TestEnvVar = "TRITON_TEST" 25 | 26 | type TestCase struct { 27 | Steps []Step 28 | State TritonStateBag 29 | } 30 | 31 | func AccTest(t *testing.T, c TestCase) { 32 | // We only run acceptance tests if an env var is set because they're 33 | // slow and generally require some outside configuration. 34 | if os.Getenv(TestEnvVar) == "" { 35 | t.Skip(fmt.Sprintf( 36 | "Acceptance tests skipped unless env '%s' set", 37 | TestEnvVar)) 38 | return 39 | } 40 | 41 | // Disable extra logging output unless TRITON_VERBOSE_TESTS is set. 42 | if triton.GetEnv("VERBOSE_TESTS") == "" { 43 | log.SetOutput(ioutil.Discard) 44 | } 45 | 46 | tritonURL := triton.GetEnv("URL") 47 | tritonAccount := triton.GetEnv("ACCOUNT") 48 | tritonKeyID := triton.GetEnv("KEY_ID") 49 | tritonKeyMaterial := triton.GetEnv("KEY_MATERIAL") 50 | userName := triton.GetEnv("USER") 51 | mantaURL := triton.GetEnv("MANTA_URL") 52 | 53 | var prerollErrors []error 54 | if tritonURL == "" { 55 | prerollErrors = append(prerollErrors, 56 | errors.New("The TRITON_URL environment variable must be set to run acceptance tests")) 57 | } 58 | if tritonAccount == "" { 59 | prerollErrors = append(prerollErrors, 60 | errors.New("The TRITON_ACCOUNT environment variable must be set to run acceptance tests")) 61 | } 62 | if tritonKeyID == "" { 63 | prerollErrors = append(prerollErrors, 64 | errors.New("The TRITON_KEY_ID environment variable must be set to run acceptance tests")) 65 | } 66 | if len(prerollErrors) > 0 { 67 | for _, err := range prerollErrors { 68 | t.Error(err) 69 | } 70 | t.FailNow() 71 | } 72 | 73 | var signer authentication.Signer 74 | var err error 75 | if tritonKeyMaterial != "" { 76 | log.Println("[INFO] Creating Triton Client with Private Key Signer...") 77 | input := authentication.PrivateKeySignerInput{ 78 | KeyID: tritonKeyID, 79 | PrivateKeyMaterial: []byte(tritonKeyMaterial), 80 | AccountName: tritonAccount, 81 | Username: userName, 82 | } 83 | signer, err = authentication.NewPrivateKeySigner(input) 84 | if err != nil { 85 | t.Fatalf("Error creating private key signer: %s", err) 86 | } 87 | } else { 88 | log.Println("[INFO] Creating Triton Client with SSH Key Signer...") 89 | input := authentication.SSHAgentSignerInput{ 90 | KeyID: tritonKeyID, 91 | AccountName: tritonAccount, 92 | Username: userName, 93 | } 94 | signer, err = authentication.NewSSHAgentSigner(input) 95 | if err != nil { 96 | t.Fatalf("Error creating SSH Agent signer: %s", err) 97 | } 98 | } 99 | 100 | // Old world... we spun up a universal client. This is pushed deeper into 101 | // the process within `testutils.StepClient`. 102 | // 103 | // client, err := NewClient(tritonURL, tritonAccount, signer) 104 | // if err != nil { 105 | // t.Fatalf("Error creating Triton Client: %s", err) 106 | // } 107 | 108 | config := &triton.ClientConfig{ 109 | TritonURL: tritonURL, 110 | MantaURL: mantaURL, 111 | AccountName: tritonAccount, 112 | Username: userName, 113 | Signers: []authentication.Signer{signer}, 114 | } 115 | 116 | state := &basicTritonStateBag{ 117 | TritonConfig: config, 118 | } 119 | 120 | runner := &basicRunner{ 121 | Steps: c.Steps, 122 | } 123 | 124 | runner.Run(state) 125 | 126 | if errs := state.ErrorsOrNil(); errs != nil { 127 | for _, err := range errs { 128 | t.Error(err) 129 | } 130 | t.Fatal("\n\nThere may be dangling resources in your Triton account!") 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /compute/ping_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package compute_test 11 | 12 | import ( 13 | "context" 14 | "errors" 15 | "io/ioutil" 16 | "net/http" 17 | "reflect" 18 | "strings" 19 | "testing" 20 | 21 | "github.com/TritonDataCenter/triton-go/v2/compute" 22 | "github.com/TritonDataCenter/triton-go/v2/testutils" 23 | ) 24 | 25 | var ( 26 | mockVersions = []string{"7.0.0", "7.1.0", "7.2.0", "7.3.0", "8.0.0"} 27 | testError = errors.New("unable to ping") 28 | ) 29 | 30 | func TestPing(t *testing.T) { 31 | computeClient := MockComputeClient() 32 | 33 | do := func(ctx context.Context, pc *compute.ComputeClient) (*compute.PingOutput, error) { 34 | defer testutils.DeactivateClient() 35 | 36 | ping, err := pc.Ping(ctx) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return ping, nil 41 | } 42 | 43 | t.Run("successful", func(t *testing.T) { 44 | testutils.RegisterResponder("GET", "/--ping", pingSuccessFunc) 45 | 46 | resp, err := do(context.Background(), computeClient) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | if resp.Ping != "pong" { 52 | t.Errorf("ping was not pong: expected %s", resp.Ping) 53 | } 54 | 55 | if !reflect.DeepEqual(resp.CloudAPI.Versions, mockVersions) { 56 | t.Errorf("ping did not contain CloudAPI versions: expected %s", mockVersions) 57 | } 58 | }) 59 | 60 | t.Run("EOF decode", func(t *testing.T) { 61 | testutils.RegisterResponder("GET", "/--ping", pingEmptyFunc) 62 | 63 | _, err := do(context.Background(), computeClient) 64 | if err == nil { 65 | t.Fatal(err) 66 | } 67 | 68 | if !strings.Contains(err.Error(), "EOF") { 69 | t.Errorf("expected error to contain EOF: found %s", err) 70 | } 71 | }) 72 | 73 | t.Run("error", func(t *testing.T) { 74 | testutils.RegisterResponder("GET", "/--ping", pingErrorFunc) 75 | 76 | out, err := do(context.Background(), computeClient) 77 | if err == nil { 78 | t.Fatal(err) 79 | } 80 | if out != nil { 81 | t.Error("expected pingOut to be nil") 82 | } 83 | 84 | if !strings.Contains(err.Error(), "unable to ping") { 85 | t.Errorf("expected error to equal testError: found %s", err) 86 | } 87 | }) 88 | 89 | t.Run("bad decode", func(t *testing.T) { 90 | testutils.RegisterResponder("GET", "/--ping", pingDecodeFunc) 91 | 92 | _, err := do(context.Background(), computeClient) 93 | if err == nil { 94 | t.Fatal(err) 95 | } 96 | 97 | if !strings.Contains(err.Error(), "invalid character") { 98 | t.Errorf("expected decode to fail: found %s", err) 99 | } 100 | }) 101 | } 102 | 103 | func pingSuccessFunc(req *http.Request) (*http.Response, error) { 104 | header := http.Header{} 105 | header.Add("Content-Type", "application/json") 106 | 107 | body := strings.NewReader(`{ 108 | "ping": "pong", 109 | "cloudapi": { 110 | "versions": ["7.0.0", "7.1.0", "7.2.0", "7.3.0", "8.0.0"] 111 | } 112 | }`) 113 | 114 | return &http.Response{ 115 | StatusCode: 200, 116 | Header: header, 117 | Body: ioutil.NopCloser(body), 118 | }, nil 119 | } 120 | 121 | func pingEmptyFunc(req *http.Request) (*http.Response, error) { 122 | header := http.Header{} 123 | header.Add("Content-Type", "application/json") 124 | 125 | return &http.Response{ 126 | StatusCode: 200, 127 | Header: header, 128 | Body: ioutil.NopCloser(strings.NewReader("")), 129 | }, nil 130 | } 131 | 132 | func pingErrorFunc(req *http.Request) (*http.Response, error) { 133 | return nil, testError 134 | } 135 | 136 | func pingDecodeFunc(req *http.Request) (*http.Response, error) { 137 | header := http.Header{} 138 | header.Add("Content-Type", "application/json") 139 | 140 | body := strings.NewReader(`{ 141 | (ham!(// 142 | }`) 143 | 144 | return &http.Response{ 145 | StatusCode: 200, 146 | Header: header, 147 | Body: ioutil.NopCloser(body), 148 | }, nil 149 | } 150 | -------------------------------------------------------------------------------- /cmd/triton/cmd/instances/public.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package instances 11 | 12 | import ( 13 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/command" 14 | "github.com/TritonDataCenter/triton-go/v2/cmd/internal/config" 15 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/instances/count" 16 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/instances/create" 17 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/instances/delete" 18 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/instances/get" 19 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/instances/ip" 20 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/instances/list" 21 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/instances/reboot" 22 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/instances/start" 23 | "github.com/TritonDataCenter/triton-go/v2/cmd/triton/cmd/instances/stop" 24 | "github.com/spf13/cobra" 25 | "github.com/spf13/pflag" 26 | "github.com/spf13/viper" 27 | ) 28 | 29 | var Cmd = &command.Command{ 30 | Cobra: &cobra.Command{ 31 | Use: "instances", 32 | Aliases: []string{"instance", "vms", "machines"}, 33 | Short: "Instances (aka VMs/Machines/Containers)", 34 | }, 35 | 36 | Setup: func(parent *command.Command) error { 37 | 38 | cmds := []*command.Command{ 39 | list.Cmd, 40 | create.Cmd, 41 | delete.Cmd, 42 | get.Cmd, 43 | count.Cmd, 44 | reboot.Cmd, 45 | start.Cmd, 46 | stop.Cmd, 47 | ip.Cmd, 48 | } 49 | 50 | for _, cmd := range cmds { 51 | cmd.Setup(cmd) 52 | parent.Cobra.AddCommand(cmd.Cobra) 53 | } 54 | 55 | { 56 | const ( 57 | key = config.KeyInstanceID 58 | longName = "id" 59 | defaultValue = "" 60 | description = "Instance ID" 61 | ) 62 | 63 | flags := parent.Cobra.PersistentFlags() 64 | flags.String(longName, defaultValue, description) 65 | viper.BindPFlag(key, flags.Lookup(longName)) 66 | } 67 | 68 | { 69 | const ( 70 | key = config.KeyInstanceName 71 | longName = "name" 72 | shortName = "n" 73 | defaultValue = "" 74 | description = "Instance Name" 75 | ) 76 | 77 | flags := parent.Cobra.PersistentFlags() 78 | flags.StringP(longName, shortName, defaultValue, description) 79 | viper.BindPFlag(key, flags.Lookup(longName)) 80 | } 81 | 82 | { 83 | const ( 84 | key = config.KeyInstanceTag 85 | longName = "tag" 86 | shortName = "t" 87 | description = "Instance Tags. This flag can be used multiple times" 88 | ) 89 | 90 | flags := parent.Cobra.PersistentFlags() 91 | flags.StringSliceP(longName, shortName, nil, description) 92 | viper.BindPFlag(key, flags.Lookup(longName)) 93 | } 94 | 95 | { 96 | flags := parent.Cobra.PersistentFlags() 97 | flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName { 98 | switch name { 99 | case "tag": 100 | name = "tags" 101 | break 102 | } 103 | 104 | return pflag.NormalizedName(name) 105 | }) 106 | } 107 | 108 | { 109 | const ( 110 | key = config.KeyInstanceState 111 | longName = "state" 112 | defaultValue = "" 113 | description = "Instance state (e.g. running)" 114 | ) 115 | 116 | flags := parent.Cobra.PersistentFlags() 117 | flags.String(longName, defaultValue, description) 118 | viper.BindPFlag(key, flags.Lookup(longName)) 119 | } 120 | 121 | { 122 | const ( 123 | key = config.KeyInstanceBrand 124 | longName = "brand" 125 | defaultValue = "" 126 | description = "Instance brand (e.g. lx, kvm)" 127 | ) 128 | 129 | flags := parent.Cobra.PersistentFlags() 130 | flags.String(longName, defaultValue, description) 131 | viper.BindPFlag(key, flags.Lookup(longName)) 132 | } 133 | 134 | return nil 135 | }, 136 | } 137 | -------------------------------------------------------------------------------- /account/keys.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2020 Joyent, Inc. All rights reserved. 3 | // Copyright 2025 MNX Cloud, Inc. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this 7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 | // 9 | 10 | package account 11 | 12 | import ( 13 | "context" 14 | "encoding/json" 15 | "net/http" 16 | "path" 17 | 18 | "github.com/TritonDataCenter/triton-go/v2/client" 19 | "github.com/pkg/errors" 20 | ) 21 | 22 | type KeysClient struct { 23 | client *client.Client 24 | } 25 | 26 | // Key represents a public key 27 | type Key struct { 28 | // Name of the key 29 | Name string `json:"name"` 30 | 31 | // Key fingerprint 32 | Fingerprint string `json:"fingerprint"` 33 | 34 | // OpenSSH-formatted public key 35 | Key string `json:"key"` 36 | } 37 | 38 | type ListKeysInput struct{} 39 | 40 | // ListKeys lists all public keys we have on record for the specified 41 | // account. 42 | func (c *KeysClient) List(ctx context.Context, _ *ListKeysInput) ([]*Key, error) { 43 | fullPath := path.Join("/", c.client.AccountName, "keys") 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 list keys") 54 | } 55 | 56 | var result []*Key 57 | decoder := json.NewDecoder(respReader) 58 | if err = decoder.Decode(&result); err != nil { 59 | return nil, errors.Wrap(err, "unable to decode list keys response") 60 | } 61 | 62 | return result, nil 63 | } 64 | 65 | type GetKeyInput struct { 66 | KeyName string 67 | } 68 | 69 | func (c *KeysClient) Get(ctx context.Context, input *GetKeyInput) (*Key, error) { 70 | fullPath := path.Join("/", c.client.AccountName, "keys", input.KeyName) 71 | reqInputs := client.RequestInput{ 72 | Method: http.MethodGet, 73 | Path: fullPath, 74 | } 75 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 76 | if respReader != nil { 77 | defer respReader.Close() 78 | } 79 | if err != nil { 80 | return nil, errors.Wrap(err, "unable to get key") 81 | } 82 | 83 | var result *Key 84 | decoder := json.NewDecoder(respReader) 85 | if err = decoder.Decode(&result); err != nil { 86 | return nil, errors.Wrap(err, "unable to decode get key response") 87 | } 88 | 89 | return result, nil 90 | } 91 | 92 | type DeleteKeyInput struct { 93 | KeyName string 94 | } 95 | 96 | func (c *KeysClient) Delete(ctx context.Context, input *DeleteKeyInput) error { 97 | fullPath := path.Join("/", c.client.AccountName, "keys", input.KeyName) 98 | reqInputs := client.RequestInput{ 99 | Method: http.MethodDelete, 100 | Path: fullPath, 101 | } 102 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 103 | if respReader != nil { 104 | defer respReader.Close() 105 | } 106 | if err != nil { 107 | return errors.Wrap(err, "unable to delete key") 108 | } 109 | 110 | return nil 111 | } 112 | 113 | // CreateKeyInput represents the option that can be specified 114 | // when creating a new key. 115 | type CreateKeyInput struct { 116 | // Name of the key. Optional. 117 | Name string `json:"name,omitempty"` 118 | 119 | // OpenSSH-formatted public key. 120 | Key string `json:"key"` 121 | } 122 | 123 | // CreateKey uploads a new OpenSSH key to Triton for use in HTTP signing and SSH. 124 | func (c *KeysClient) Create(ctx context.Context, input *CreateKeyInput) (*Key, error) { 125 | fullPath := path.Join("/", c.client.AccountName, "keys") 126 | reqInputs := client.RequestInput{ 127 | Method: http.MethodPost, 128 | Path: fullPath, 129 | Body: input, 130 | } 131 | respReader, err := c.client.ExecuteRequest(ctx, reqInputs) 132 | if respReader != nil { 133 | defer respReader.Close() 134 | } 135 | if err != nil { 136 | return nil, errors.Wrap(err, "unable to create key") 137 | } 138 | 139 | var result *Key 140 | decoder := json.NewDecoder(respReader) 141 | if err = decoder.Decode(&result); err != nil { 142 | return nil, errors.Wrap(err, "unable to decode create key response") 143 | } 144 | 145 | return result, nil 146 | } 147 | --------------------------------------------------------------------------------