Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ require (
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/cenkalti/backoff/v4 v4.3.0
github.com/gliderlabs/ssh v0.3.8
github.com/go-ozzo/ozzo-validation/v4 v4.3.0
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/jarcoal/httpmock v1.4.0
github.com/nebius/gosdk v0.0.0-20250826102719-940ad1dfb5de
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.11.0
golang.org/x/crypto v0.41.0
gopkg.in/validator.v2 v2.0.1
)

require (
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vS
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/bojanz/currency v1.3.1 h1:3BUAvy/5hU/Pzqg5nrQslVihV50QG+A2xKPoQw1RKH4=
github.com/bojanz/currency v1.3.1/go.mod h1:jNoZiJyRTqoU5DFoa+n+9lputxPUDa8Fz8BdDrW06Go=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
Expand All @@ -22,6 +24,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
Expand All @@ -46,6 +50,8 @@ github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX
github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/nebius/gosdk v0.0.0-20250826102719-940ad1dfb5de h1:7GbDUDyH22dvN7ata8HuNVuDlcyaDzUs/s+03Y3pDqU=
github.com/nebius/gosdk v0.0.0-20250826102719-940ad1dfb5de/go.mod h1:eVbm4Qc4GPzBn3EL4rLvy1WS9zqJDw+giksOA2NZERY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -55,6 +61,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
Expand Down Expand Up @@ -98,6 +105,9 @@ google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXn
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY=
gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
211 changes: 211 additions & 0 deletions internal/errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package errors

import (
stderrors "errors"
"fmt"
"runtime"
"strconv"
"strings"

pkgerrors "github.com/pkg/errors"
)

type ValidationError struct {
Message string
}

var _ error = ValidationError{}

func NewValidationError(message string) ValidationError {
return ValidationError{Message: message}
}

func (v ValidationError) Error() string {
return v.Message
}

var New = stderrors.New

var Errorf = fmt.Errorf

func Wrap(err error, msg string) error {
if err == nil {
return nil
}
return Errorf("%s: %w", msg, err)
}

var As = stderrors.As

var Unwrap = stderrors.Unwrap

func Unwraps(err error) []error {
u, ok := err.(interface {
Unwrap() []error
})
if !ok {
return nil
}
return u.Unwrap()
}

func Root(err error) error {
for Unwrap(err) != nil {
err = Unwrap(err)
}
joinedErrs := Unwraps(err)
if len(joinedErrs) == 0 {
return err
}
return Roots(joinedErrs)
}

func Roots(errs []error) error {
if len(errs) == 0 {
return nil
}
rootedErrs := make([]error, len(errs))
for i, e := range errs {
rootedErrs[i] = Root(e)
}
return Join(rootedErrs...)
}

// flattens error tree
func Flatten(err error) []error {
if err == nil {
return nil
}
joinedErrs := Unwraps(err)
if joinedErrs == nil {
return []error{err}
}
flatErrs := []error{}
for _, e := range joinedErrs {
flatErrs = append(flatErrs, Flatten(e)...)
}
return flatErrs
}

// var ReturnTrace = errtrace.Wrap

func Join(errs ...error) error {
noNilErrs := make([]error, 0, len(errs))
for _, err := range errs {
if err != nil {
noNilErrs = append(noNilErrs, err)
}
}
if len(noNilErrs) == 0 {
return nil
}
if len(noNilErrs) == 1 {
return noNilErrs[0]
}
return stderrors.Join(errs...) //nolint:wrapcheck // this is a wrapper
}

// if multi err, combine similar errors
func CombineByString(err error) error {
if err == nil {
return nil
}
errs := Flatten(err)
mapE := make(map[string]error)
mapEList := []error{}
for _, e := range errs {
_, ok := mapE[e.Error()]
if !ok {
mapE[e.Error()] = e
mapEList = append(mapEList, e)
}
}
errsOut := make([]error, 0, len(mapE))
for _, e := range mapEList {
errsOut = append(errsOut, e)
}
return Join(errsOut...)
}

var Is = stderrors.Is

var WrapAndTrace = WrapAndTraceInMsg

func WrapAndTraceInMsg(err error) error {
if err == nil {
return nil
}
return pkgerrors.Wrap(err, makeErrorMessage("", 0)) // this wrap also adds a stacktrace which can be nice
}

func WrapAndTrace2[T any](t T, err error) (T, error) {
if err == nil {
return t, nil
}
return t, pkgerrors.Wrap(err, makeErrorMessage("", 0))
}

func makeErrorMessage(message string, skip int) string {
skip += 2
pc, file, line, _ := runtime.Caller(skip)

funcName := "unknown"
fn := runtime.FuncForPC(pc)
if fn != nil {
funcName = fn.Name()
}

lineNum := strconv.Itoa(line)
return fmt.Sprintf("[error] %s\n%s\n%s:%s\n", message, funcName, file, lineNum)
}

func HandleErrDefer(f func() error) {
_ = f()
// logger.L().Error("", zap.Error(err))
}

func ErrorContainsAny(err error, substrs ...string) bool {
for _, substr := range substrs {
if ErrorContains(err, substr) {
return true
}
}
return false
}

func ErrorContains(err error, substr string) bool {
return err != nil && strings.Contains(err.Error(), substr)
}

func IsErrorExcept(err error, errs ...error) bool {
return err != nil && !IsAny(err, errs...)
}

func IsErrorExceptSubstr(err error, substr ...string) bool {
return err != nil && !ErrorContainsAny(err, substr...)
}

func IsAny(err error, errs ...error) bool {
for _, e := range errs {
if Is(err, e) {
return true
}
}
return false
}

// TruncateErrorForLogging truncates a long error message to a more manageable size
// while preserving the most important parts of the error message
func TruncateErrorForLogging(err error, maxLength int) error {
if err == nil {
return nil
}

errStr := err.Error()
if len(errStr) <= maxLength {
return err
}

// Otherwise truncate with indication
return New(fmt.Sprintf("ERROR (truncated): %s... (truncated)", errStr[:maxLength-20]))
}
13 changes: 13 additions & 0 deletions v1/hashutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package v1

import (
"crypto/sha256"
"encoding/hex"
)

func HashSensitiveString(s string) (string, error) {
// Preprocess the password with SHA-256
sha256Hash := sha256.Sum256([]byte(s))
sha256HashString := hex.EncodeToString(sha256Hash[:])
return sha256HashString, nil
}
1 change: 1 addition & 0 deletions v1/instancetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ type InstanceType struct {
Provider string
Cloud string
CanModifyFirewallRules bool
ReservedInstancePoolID string
}

func MakeGenericInstanceTypeID(instanceType InstanceType) InstanceTypeID {
Expand Down
12 changes: 10 additions & 2 deletions v1/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package v1
import (
"context"
"fmt"
"slices"
)

type CloudLocation interface {
Expand All @@ -26,15 +27,22 @@ type LocationsFilter []string

var All = []string{"all"}

func (l LocationsFilter) IsAll() bool {
for _, v := range l {
func (f LocationsFilter) IsAll() bool {
for _, v := range f {
if v == "*" || v == "all" {
return true
}
}
return false
}

func (f LocationsFilter) IsAllowed(location string) bool {
if f.IsAll() {
return true
}
return slices.Contains(f, location)
}

// ValidateGetLocations validates that the CloudLocation implementation returns at least one available location without error.
func ValidateGetLocations(ctx context.Context, client CloudLocation) error {
locs, err := client.GetLocations(ctx, GetLocationsArgs{})
Expand Down
28 changes: 28 additions & 0 deletions v1/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package v1

import "context"

type Field struct {
Key string
Value any
}

func LogField(key string, value any) Field {
return Field{Key: key, Value: value}
}

type Logger interface {
Debug(ctx context.Context, msg string, fields ...Field)
Info(ctx context.Context, msg string, fields ...Field)
Warn(ctx context.Context, msg string, args ...Field)
Error(ctx context.Context, err error, fields ...Field)
}

type NoopLogger struct{}

func (l *NoopLogger) Debug(_ context.Context, _ string, _ ...Field) {}
func (l *NoopLogger) Info(_ context.Context, _ string, _ ...Field) {}
func (l *NoopLogger) Warn(_ context.Context, _ string, _ ...Field) {}
func (l *NoopLogger) Error(_ context.Context, _ error, _ ...Field) {}

var _ Logger = &NoopLogger{}
20 changes: 20 additions & 0 deletions v1/providers/launchpad/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
SPEC_VERSION ?= v2.36.1
SPEC_FILE ?= /local/swagger-${SPEC_VERSION}.yaml
OUTPUT_DIR ?= launchpad
# To understand disallowAdditionalPropertiesIfNotPresent=false
# Check out https://openapi-generator.tech/docs/generators/go/#config-options
generate-launchpad-client:
rm -rf gen/${OUTPUT_DIR}
mkdir -p gen/${OUTPUT_DIR}
docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli:v7.8.0 generate \
--additional-properties disallowAdditionalPropertiesIfNotPresent=false \
-i ${SPEC_FILE} \
-g go \
--git-user-id brevdev \
--git-repo-id cloud \
--skip-validate-spec \
-o /local/gen/${OUTPUT_DIR}
sudo chown -R $(shell id -u):$(shell id -g) gen/${OUTPUT_DIR}//
find gen/${OUTPUT_DIR} -name "*.go" -type f -exec sed -i.bak 's|openapiclient "github.com/brevdev/cloud"|openapiclient "github.com/brevdev/cloud/v1/providers/launchpad/gen/launchpad"|g' {} \; && find gen/${OUTPUT_DIR} -name "*.go.bak" -delete
gofmt -s -w gen/${OUTPUT_DIR}
rm -rf gen/${OUTPUT_DIR}/go.mod gen/${OUTPUT_DIR}/go.sum
Loading
Loading