From 7ef5ed6df05d0e2c56c371ec8e692f75cee8c815 Mon Sep 17 00:00:00 2001 From: Steven Miller Date: Wed, 11 Feb 2026 13:21:11 -0800 Subject: [PATCH 1/2] Remove stainless generated endpoints --- .goreleaser.yml | 6 +- .stats.yml | 4 - cmd/hypeman/main.go | 2 - go.mod | 1 - go.sum | 2 - internal/apiform/encoder.go | 227 ------------- internal/apiform/form.go | 20 -- internal/apiform/form_test.go | 109 ------- internal/apiquery/encoder.go | 166 ---------- internal/apiquery/query.go | 53 --- internal/apiquery/query_test.go | 128 -------- internal/requestflag/requestflag.go | 245 -------------- pkg/cmd/cmd.go | 59 ---- pkg/cmd/cmdutil.go | 4 - pkg/cmd/flagoptions.go | 134 -------- pkg/cmd/health.go | 53 --- pkg/cmd/image.go | 187 ----------- pkg/cmd/ingress.go | 189 ----------- pkg/cmd/instance.go | 490 ---------------------------- pkg/cmd/instancevolume.go | 144 -------- pkg/cmd/util.go | 149 --------- pkg/cmd/version.go | 55 +++- pkg/cmd/volume.go | 196 ----------- 23 files changed, 55 insertions(+), 2568 deletions(-) delete mode 100644 .stats.yml delete mode 100644 internal/apiform/encoder.go delete mode 100644 internal/apiform/form.go delete mode 100644 internal/apiform/form_test.go delete mode 100644 internal/apiquery/encoder.go delete mode 100644 internal/apiquery/query.go delete mode 100644 internal/apiquery/query_test.go delete mode 100644 internal/requestflag/requestflag.go delete mode 100644 pkg/cmd/flagoptions.go delete mode 100644 pkg/cmd/health.go delete mode 100644 pkg/cmd/image.go delete mode 100644 pkg/cmd/ingress.go delete mode 100644 pkg/cmd/instance.go delete mode 100644 pkg/cmd/instancevolume.go delete mode 100644 pkg/cmd/util.go delete mode 100644 pkg/cmd/volume.go diff --git a/.goreleaser.yml b/.goreleaser.yml index 6763609..b2f3fb9 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -17,7 +17,7 @@ builds: main: ./cmd/hypeman/main.go mod_timestamp: '{{ .CommitTimestamp }}' ldflags: - - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' + - '-s -w -X github.com/kernel/hypeman-cli/pkg/cmd.version={{.Version}}' - id: linux goos: [linux] @@ -28,7 +28,7 @@ builds: main: ./cmd/hypeman/main.go mod_timestamp: '{{ .CommitTimestamp }}' ldflags: - - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' + - '-s -w -X github.com/kernel/hypeman-cli/pkg/cmd.version={{.Version}}' - id: windows goos: [windows] @@ -37,7 +37,7 @@ builds: main: ./cmd/hypeman/main.go mod_timestamp: '{{ .CommitTimestamp }}' ldflags: - - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' + - '-s -w -X github.com/kernel/hypeman-cli/pkg/cmd.version={{.Version}}' archives: - id: linux-archive diff --git a/.stats.yml b/.stats.yml deleted file mode 100644 index e91df4e..0000000 --- a/.stats.yml +++ /dev/null @@ -1,4 +0,0 @@ -configured_endpoints: 24 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-51c1f6c7e28113c00cfcfea0595de40961dca2263b88bf2e47ef46b8ed458b07.yml -openapi_spec_hash: 07f24b9c8f0b757100655ac10d83b362 -config_hash: 510018ffa6ad6a17875954f66fe69598 diff --git a/cmd/hypeman/main.go b/cmd/hypeman/main.go index d6f254b..23c43b2 100644 --- a/cmd/hypeman/main.go +++ b/cmd/hypeman/main.go @@ -1,5 +1,3 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - package main import ( diff --git a/go.mod b/go.mod index e48cf2b..5aafcea 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/x/term v0.2.1 - github.com/goccy/go-yaml v1.18.0 github.com/google/go-containerregistry v0.20.7 github.com/gorilla/websocket v1.5.3 github.com/itchyny/json2yaml v0.1.4 diff --git a/go.sum b/go.sum index fe414c4..2e5c727 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,6 @@ 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/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go deleted file mode 100644 index 16ca44b..0000000 --- a/internal/apiform/encoder.go +++ /dev/null @@ -1,227 +0,0 @@ -package apiform - -import ( - "fmt" - "io" - "mime/multipart" - "net/textproto" - "path" - "reflect" - "sort" - "strconv" - "strings" -) - -// Marshal encodes a value as multipart form data using default settings -func Marshal(value any, writer *multipart.Writer) error { - e := &encoder{ - format: FormatRepeat, - } - return e.marshal(value, writer) -} - -// MarshalWithSettings encodes a value with custom array format -func MarshalWithSettings(value any, writer *multipart.Writer, arrayFormat FormFormat) error { - e := &encoder{ - format: arrayFormat, - } - return e.marshal(value, writer) -} - -type encoder struct { - format FormFormat -} - -func (e *encoder) marshal(value any, writer *multipart.Writer) error { - val := reflect.ValueOf(value) - if !val.IsValid() { - return nil - } - return e.encodeValue("", val, writer) -} - -func (e *encoder) encodeValue(key string, val reflect.Value, writer *multipart.Writer) error { - if !val.IsValid() { - return writer.WriteField(key, "") - } - - t := val.Type() - - if t.Implements(reflect.TypeOf((*io.Reader)(nil)).Elem()) { - return e.encodeReader(key, val, writer) - } - - switch t.Kind() { - case reflect.Pointer: - if val.IsNil() || !val.IsValid() { - return writer.WriteField(key, "") - } - return e.encodeValue(key, val.Elem(), writer) - - case reflect.Slice, reflect.Array: - return e.encodeArray(key, val, writer) - - case reflect.Map: - return e.encodeMap(key, val, writer) - - case reflect.Interface: - if val.IsNil() { - return writer.WriteField(key, "") - } - return e.encodeValue(key, val.Elem(), writer) - - case reflect.String: - return writer.WriteField(key, val.String()) - - case reflect.Bool: - if val.Bool() { - return writer.WriteField(key, "true") - } - return writer.WriteField(key, "false") - - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return writer.WriteField(key, strconv.FormatInt(val.Int(), 10)) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return writer.WriteField(key, strconv.FormatUint(val.Uint(), 10)) - - case reflect.Float32: - return writer.WriteField(key, strconv.FormatFloat(val.Float(), 'f', -1, 32)) - - case reflect.Float64: - return writer.WriteField(key, strconv.FormatFloat(val.Float(), 'f', -1, 64)) - - default: - return fmt.Errorf("unknown type: %s", t.String()) - } -} - -func (e *encoder) encodeArray(key string, val reflect.Value, writer *multipart.Writer) error { - if e.format == FormatComma { - var values []string - for i := 0; i < val.Len(); i++ { - item := val.Index(i) - var strValue string - switch item.Kind() { - case reflect.String: - strValue = item.String() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - strValue = strconv.FormatInt(item.Int(), 10) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - strValue = strconv.FormatUint(item.Uint(), 10) - case reflect.Float32, reflect.Float64: - strValue = strconv.FormatFloat(item.Float(), 'f', -1, 64) - case reflect.Bool: - strValue = strconv.FormatBool(item.Bool()) - default: - return fmt.Errorf("comma format not supported for complex array elements") - } - values = append(values, strValue) - } - return writer.WriteField(key, strings.Join(values, ",")) - } - - for i := 0; i < val.Len(); i++ { - var formattedKey string - switch e.format { - case FormatRepeat: - formattedKey = key - case FormatBrackets: - formattedKey = key + "[]" - case FormatIndicesDots: - if key == "" { - formattedKey = strconv.Itoa(i) - } else { - formattedKey = key + "." + strconv.Itoa(i) - } - case FormatIndicesBrackets: - if key == "" { - formattedKey = strconv.Itoa(i) - } else { - formattedKey = key + "[" + strconv.Itoa(i) + "]" - } - default: - return fmt.Errorf("apiform: unsupported array format") - } - - if err := e.encodeValue(formattedKey, val.Index(i), writer); err != nil { - return err - } - } - return nil -} - -var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") - -func escapeQuotes(s string) string { - return quoteEscaper.Replace(s) -} - -func (e *encoder) encodeReader(key string, val reflect.Value, writer *multipart.Writer) error { - reader, ok := val.Convert(reflect.TypeOf((*io.Reader)(nil)).Elem()).Interface().(io.Reader) - if !ok { - return nil - } - - // Set defaults - filename := "anonymous_file" - contentType := "application/octet-stream" - - // Get filename if available - if named, ok := reader.(interface{ Filename() string }); ok { - filename = named.Filename() - } else if named, ok := reader.(interface{ Name() string }); ok { - filename = path.Base(named.Name()) - } - - // Get content type if available - if typed, ok := reader.(interface{ ContentType() string }); ok { - contentType = typed.ContentType() - } - - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, - escapeQuotes(key), escapeQuotes(filename))) - h.Set("Content-Type", contentType) - - filewriter, err := writer.CreatePart(h) - if err != nil { - return err - } - _, err = io.Copy(filewriter, reader) - return err -} - -func (e *encoder) encodeMap(key string, val reflect.Value, writer *multipart.Writer) error { - type mapPair struct { - key string - value reflect.Value - } - - if key != "" { - key = key + "." - } - - // Collect and sort map entries for deterministic output - pairs := []mapPair{} - iter := val.MapRange() - for iter.Next() { - if iter.Key().Type().Kind() != reflect.String { - return fmt.Errorf("cannot encode a map with a non string key") - } - pairs = append(pairs, mapPair{key: iter.Key().String(), value: iter.Value()}) - } - - sort.Slice(pairs, func(i, j int) bool { - return pairs[i].key < pairs[j].key - }) - - // Process sorted pairs - for _, p := range pairs { - if err := e.encodeValue(key+p.key, p.value, writer); err != nil { - return err - } - } - - return nil -} diff --git a/internal/apiform/form.go b/internal/apiform/form.go deleted file mode 100644 index 024de27..0000000 --- a/internal/apiform/form.go +++ /dev/null @@ -1,20 +0,0 @@ -package apiform - -type Marshaler interface { - MarshalMultipart() ([]byte, string, error) -} - -type FormFormat int - -const ( - // FormatRepeat represents arrays as repeated keys with the same value - FormatRepeat FormFormat = iota - // Comma-separated values 1,2,3 - FormatComma - // FormatBrackets uses the key[] notation for arrays - FormatBrackets - // FormatIndicesDots uses key.0, key.1, etc. notation - FormatIndicesDots - // FormatIndicesBrackets uses key[0], key[1], etc. notation - FormatIndicesBrackets -) diff --git a/internal/apiform/form_test.go b/internal/apiform/form_test.go deleted file mode 100644 index 2cf5bdd..0000000 --- a/internal/apiform/form_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package apiform - -import ( - "bytes" - "mime/multipart" - "testing" -) - -// Define test cases -var tests = map[string]struct { - value any - format FormFormat - expected string -}{ - "nil": { - value: nil, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n\r\n--xxx--\r\n", - }, - "string": { - value: "hello", - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\nhello\r\n--xxx--\r\n", - }, - "int": { - value: 42, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n42\r\n--xxx--\r\n", - }, - "float": { - value: 3.14, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n3.14\r\n--xxx--\r\n", - }, - "bool": { - value: true, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\ntrue\r\n--xxx--\r\n", - }, - "empty slice": { - value: []string{}, - expected: "\r\n--xxx--\r\n", - }, - "nil slice": { - value: []string(nil), - expected: "\r\n--xxx--\r\n", - }, - "slice with dot indices": { - value: []string{"a", "b", "c"}, - format: FormatIndicesDots, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo.0\"\r\n\r\na\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.1\"\r\n\r\nb\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.2\"\r\n\r\nc\r\n--xxx--\r\n", - }, - "slice with bracket indices": { - value: []int{10, 20, 30}, - format: FormatIndicesBrackets, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo[0]\"\r\n\r\n10\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo[1]\"\r\n\r\n20\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo[2]\"\r\n\r\n30\r\n--xxx--\r\n", - }, - "slice with repeat": { - value: []int{10, 20, 30}, - format: FormatRepeat, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n10\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n20\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n30\r\n--xxx--\r\n", - }, - "slice with commas": { - value: []int{10, 20, 30}, - format: FormatComma, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo\"\r\n\r\n10,20,30\r\n--xxx--\r\n", - }, - "empty map": { - value: map[string]any{}, - expected: "\r\n--xxx--\r\n", - }, - "nil map": { - value: map[string]any(nil), - expected: "\r\n--xxx--\r\n", - }, - "map": { - value: map[string]any{"key1": "value1", "key2": "value2"}, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo.key1\"\r\n\r\nvalue1\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.key2\"\r\n\r\nvalue2\r\n--xxx--\r\n", - }, - "nested_map": { - value: map[string]any{"outer": map[string]int{"inner1": 10, "inner2": 20}}, - format: FormatIndicesDots, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo.outer.inner1\"\r\n\r\n10\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.outer.inner2\"\r\n\r\n20\r\n--xxx--\r\n", - }, - "mixed_map": { - value: map[string]any{"name": "John", "ages": []int{25, 30, 35}}, - format: FormatIndicesDots, - expected: "--xxx\r\nContent-Disposition: form-data; name=\"foo.ages.0\"\r\n\r\n25\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.ages.1\"\r\n\r\n30\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.ages.2\"\r\n\r\n35\r\n--xxx\r\nContent-Disposition: form-data; name=\"foo.name\"\r\n\r\nJohn\r\n--xxx--\r\n", - }, -} - -func TestEncode(t *testing.T) { - for name, test := range tests { - t.Run(name, func(t *testing.T) { - buf := bytes.NewBuffer(nil) - writer := multipart.NewWriter(buf) - writer.SetBoundary("xxx") - - form := map[string]any{"foo": test.value} - err := MarshalWithSettings(form, writer, test.format) - if err != nil { - t.Errorf("serialization of %v failed with error %v", test.value, err) - } - err = writer.Close() - if err != nil { - t.Errorf("serialization of %v failed with error %v", test.value, err) - } - result := buf.String() - if result != test.expected { - t.Errorf("expected %+#v to serialize to:\n\t%q\nbut got:\n\t%q", test.value, test.expected, result) - } - }) - } -} diff --git a/internal/apiquery/encoder.go b/internal/apiquery/encoder.go deleted file mode 100644 index 0d09dee..0000000 --- a/internal/apiquery/encoder.go +++ /dev/null @@ -1,166 +0,0 @@ -package apiquery - -import ( - "fmt" - "reflect" - "strconv" - "strings" -) - -type encoder struct { - settings QuerySettings -} - -type Pair struct { - key string - value string -} - -func (e *encoder) Encode(key string, value reflect.Value) ([]Pair, error) { - t := value.Type() - switch t.Kind() { - case reflect.Pointer: - if value.IsNil() || !value.IsValid() { - return []Pair{{key, ""}}, nil - } - return e.Encode(key, value.Elem()) - - case reflect.Array, reflect.Slice: - return e.encodeArray(key, value) - - case reflect.Map: - return e.encodeMap(key, value) - - case reflect.Interface: - if !value.Elem().IsValid() { - return []Pair{{key, ""}}, nil - } - return e.Encode(key, value.Elem()) - - default: - return e.encodePrimitive(key, value) - } -} - -func (e *encoder) encodeMap(key string, value reflect.Value) ([]Pair, error) { - var pairs []Pair - iter := value.MapRange() - for iter.Next() { - subkey := iter.Key().String() - keyPath := subkey - if len(key) > 0 { - if e.settings.NestedFormat == NestedQueryFormatDots { - keyPath = fmt.Sprintf("%s.%s", key, subkey) - } else { - keyPath = fmt.Sprintf("%s[%s]", key, subkey) - } - } - - subpairs, err := e.Encode(keyPath, iter.Value()) - if err != nil { - return nil, err - } - pairs = append(pairs, subpairs...) - } - return pairs, nil -} - -func (e *encoder) encodeArray(key string, value reflect.Value) ([]Pair, error) { - switch e.settings.ArrayFormat { - case ArrayQueryFormatComma: - elements := []string{} - for i := 0; i < value.Len(); i++ { - innerPairs, err := e.Encode("", value.Index(i)) - if err != nil { - return nil, err - } - for _, pair := range innerPairs { - elements = append(elements, pair.value) - } - } - return []Pair{{key, strings.Join(elements, ",")}}, nil - - case ArrayQueryFormatRepeat: - var pairs []Pair - for i := 0; i < value.Len(); i++ { - subpairs, err := e.Encode(key, value.Index(i)) - if err != nil { - return nil, err - } - pairs = append(pairs, subpairs...) - } - return pairs, nil - - case ArrayQueryFormatIndices: - var pairs []Pair - for i := 0; i < value.Len(); i++ { - subpairs, err := e.Encode(fmt.Sprintf("%s[%d]", key, i), value.Index(i)) - if err != nil { - return nil, err - } - pairs = append(pairs, subpairs...) - } - return pairs, nil - - case ArrayQueryFormatBrackets: - var pairs []Pair - for i := 0; i < value.Len(); i++ { - subpairs, err := e.Encode(key+"[]", value.Index(i)) - if err != nil { - return nil, err - } - pairs = append(pairs, subpairs...) - } - return pairs, nil - - default: - panic(fmt.Sprintf("Unknown ArrayFormat value: %d", e.settings.ArrayFormat)) - } -} - -func (e *encoder) encodePrimitive(key string, value reflect.Value) ([]Pair, error) { - switch value.Kind() { - case reflect.Pointer: - if !value.IsValid() || value.IsNil() { - return nil, nil - } - return e.encodePrimitive(key, value.Elem()) - - case reflect.String: - return []Pair{{key, value.String()}}, nil - - case reflect.Bool: - if value.Bool() { - return []Pair{{key, "true"}}, nil - } - return []Pair{{key, "false"}}, nil - - case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: - return []Pair{{key, strconv.FormatInt(value.Int(), 10)}}, nil - - case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return []Pair{{key, strconv.FormatUint(value.Uint(), 10)}}, nil - - case reflect.Float32, reflect.Float64: - return []Pair{{key, strconv.FormatFloat(value.Float(), 'f', -1, 64)}}, nil - - default: - return nil, nil - } -} - -func (e *encoder) encodeField(key string, value reflect.Value) ([]Pair, error) { - present := value.FieldByName("Present") - if !present.Bool() { - return nil, nil - } - null := value.FieldByName("Null") - if null.Bool() { - return nil, fmt.Errorf("apiquery: field cannot be null") - } - raw := value.FieldByName("Raw") - if !raw.IsNil() { - return e.Encode(key, raw) - } - return e.Encode(key, value.FieldByName("Value")) -} diff --git a/internal/apiquery/query.go b/internal/apiquery/query.go deleted file mode 100644 index fd07a2f..0000000 --- a/internal/apiquery/query.go +++ /dev/null @@ -1,53 +0,0 @@ -package apiquery - -import ( - "net/url" - "reflect" -) - -func MarshalWithSettings(value any, settings QuerySettings) (url.Values, error) { - val := reflect.ValueOf(value) - if !val.IsValid() { - return nil, nil - } - - e := encoder{settings} - pairs, err := e.Encode("", val) - if err != nil { - return nil, err - } - - kv := url.Values{} - for _, pair := range pairs { - kv.Add(pair.key, pair.value) - } - return kv, nil -} -func Marshal(value any) (url.Values, error) { - return MarshalWithSettings(value, QuerySettings{}) -} - -type Queryer interface { - URLQuery() (url.Values, error) -} - -type NestedQueryFormat int - -const ( - NestedQueryFormatBrackets NestedQueryFormat = iota - NestedQueryFormatDots -) - -type ArrayQueryFormat int - -const ( - ArrayQueryFormatComma ArrayQueryFormat = iota - ArrayQueryFormatRepeat - ArrayQueryFormatIndices - ArrayQueryFormatBrackets -) - -type QuerySettings struct { - NestedFormat NestedQueryFormat - ArrayFormat ArrayQueryFormat -} diff --git a/internal/apiquery/query_test.go b/internal/apiquery/query_test.go deleted file mode 100644 index 8bee784..0000000 --- a/internal/apiquery/query_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package apiquery - -import ( - "net/url" - "testing" -) - -func TestEncode(t *testing.T) { - tests := map[string]struct { - val any - settings QuerySettings - enc string - }{ - "null": { - val: nil, - enc: "query=", - }, - "string": { - val: "hello world", - enc: "query=hello world", - }, - "int": { - val: 42, - enc: "query=42", - }, - "float": { - val: 3.14, - enc: "query=3.14", - }, - "bool": { - val: true, - enc: "query=true", - }, - "empty_slice": { - val: []any{}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatComma}, - enc: "query=", - }, - "nil_slice": { - val: []any(nil), - settings: QuerySettings{ArrayFormat: ArrayQueryFormatComma}, - enc: "query=", - }, - "slice_of_ints": { - val: []any{10, 20, 30}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatComma}, - enc: "query=10,20,30", - }, - "slice_of_ints_repeat": { - val: []any{10, 20, 30}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatRepeat}, - enc: "query=10&query=20&query=30", - }, - "slice_of_ints_indices": { - val: []any{10, 20, 30}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatIndices}, - enc: "query[0]=10&query[1]=20&query[2]=30", - }, - "slice_of_ints_brackets": { - val: []any{10, 20, 30}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatBrackets}, - enc: "query[]=10&query[]=20&query[]=30", - }, - "slice_of_strings": { - val: []any{"a", "b", "c"}, - settings: QuerySettings{}, - enc: "query=a,b,c", - }, - "empty_map": { - val: map[string]any{}, - settings: QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - enc: "", - }, - "nil_map": { - val: map[string]any(nil), - settings: QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - enc: "", - }, - "map_string_to_int_brackets": { - val: map[string]any{"one": 1, "two": 2}, - settings: QuerySettings{NestedFormat: NestedQueryFormatBrackets}, - enc: "query[one]=1&query[two]=2", - }, - "map_string_to_int_dots": { - val: map[string]any{"one": 1, "two": 2}, - settings: QuerySettings{NestedFormat: NestedQueryFormatDots}, - enc: "query.one=1&query.two=2", - }, - "map_string_to_slice": { - val: map[string][]any{"nums": {10, 20, 30}}, - settings: QuerySettings{}, - enc: "query[nums]=10,20,30", - }, - "map_string_to_slice_repeat_dots": { - val: map[string][]any{"nums": {10, 20, 30}}, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatRepeat, NestedFormat: NestedQueryFormatDots}, - enc: "query.nums=10&query.nums=20&query.nums=30", - }, - "map_with_empties": { - val: map[string]any{ - "empty-array": []any{}, - "nil-array": []any(nil), - "null": nil, - }, - settings: QuerySettings{ArrayFormat: ArrayQueryFormatComma, NestedFormat: NestedQueryFormatDots}, - enc: "query.empty-array=&query.nil-array=&query.null=", - }, - "nested_map": { - val: map[string]map[string]any{"outer": {"inner": 42}}, - settings: QuerySettings{}, - enc: "query[outer][inner]=42", - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - query := map[string]any{"query": test.val} - values, err := MarshalWithSettings(query, test.settings) - if err != nil { - t.Fatalf("failed to marshal url %s", err) - } - str, _ := url.QueryUnescape(values.Encode()) - if str != test.enc { - t.Fatalf("expected %+#v to serialize to:\n\t%q\nbut got:\n\t%q", test.val, test.enc, str) - } - }) - } -} diff --git a/internal/requestflag/requestflag.go b/internal/requestflag/requestflag.go deleted file mode 100644 index 4edbf4f..0000000 --- a/internal/requestflag/requestflag.go +++ /dev/null @@ -1,245 +0,0 @@ -package requestflag - -import ( - "time" - - "github.com/goccy/go-yaml" - "github.com/urfave/cli/v3" -) - -type ( - YAMLFlag = cli.FlagBase[requestValue[any], RequestConfig, requestValueCreator[any]] - YAMLSlice = cli.SliceBase[requestValue[any], RequestConfig, requestValueCreator[any]] - YAMLSliceFlag = cli.FlagBase[[]requestValue[any], RequestConfig, YAMLSlice] - - StringFlag = cli.FlagBase[requestValue[string], RequestConfig, requestValueCreator[string]] - StringSlice = cli.SliceBase[requestValue[string], RequestConfig, requestValueCreator[string]] - StringSliceFlag = cli.FlagBase[[]requestValue[string], RequestConfig, StringSlice] - - IntFlag = cli.FlagBase[requestValue[int64], RequestConfig, requestValueCreator[int64]] - IntSlice = cli.SliceBase[requestValue[int64], RequestConfig, requestValueCreator[int64]] - IntSliceFlag = cli.FlagBase[[]requestValue[int64], RequestConfig, IntSlice] - - DateFlag = cli.FlagBase[requestValue[string], RequestConfig, dateCreator] - DateSlice = cli.SliceBase[requestValue[string], RequestConfig, dateCreator] - DateSliceFlag = cli.FlagBase[[]requestValue[string], RequestConfig, DateSlice] - - TimeFlag = cli.FlagBase[requestValue[string], RequestConfig, timeCreator] - TimeSlice = cli.SliceBase[requestValue[string], RequestConfig, timeCreator] - TimeSliceFlag = cli.FlagBase[[]requestValue[string], RequestConfig, TimeSlice] - - DateTimeFlag = cli.FlagBase[requestValue[string], RequestConfig, dateTimeCreator] - DateTimeSlice = cli.SliceBase[requestValue[string], RequestConfig, dateTimeCreator] - DateTimeSliceFlag = cli.FlagBase[[]requestValue[string], RequestConfig, DateTimeSlice] - - FloatFlag = cli.FlagBase[requestValue[float64], RequestConfig, requestValueCreator[float64]] - FloatSlice = cli.SliceBase[requestValue[float64], RequestConfig, requestValueCreator[float64]] - FloatSliceFlag = cli.FlagBase[[]requestValue[float64], RequestConfig, FloatSlice] - - BoolFlag = cli.FlagBase[requestValue[bool], RequestConfig, requestValueCreator[bool]] - BoolSlice = cli.SliceBase[requestValue[bool], RequestConfig, requestValueCreator[bool]] - BoolSliceFlag = cli.FlagBase[[]requestValue[bool], RequestConfig, BoolSlice] -) - -type RequestConfig struct { - BodyPath string - HeaderPath string - QueryPath string - CookiePath string -} - -type RequestValue interface { - RequestConfig() RequestConfig - RequestValue() any -} - -type requestValue[T any | string | int64 | float64 | bool] struct { - value T - config RequestConfig -} - -func Value[T any | string | int64 | float64 | bool](val T) requestValue[T] { - return requestValue[T]{val, RequestConfig{}} -} - -func (s requestValue[T]) RequestConfig() RequestConfig { - return s.config -} - -func (s requestValue[T]) RequestValue() any { - return s.value -} - -func CommandRequestValue[T any | string | int64 | float64 | bool](cmd *cli.Command, name string) T { - r := cmd.Value(name).(requestValue[T]) - return r.value -} - -func CommandRequestValues[T any | string | int64 | float64 | bool](cmd *cli.Command, name string) []T { - rs := cmd.Value(name).([]requestValue[T]) - values := make([]T, len(rs)) - for i, r := range rs { - values[i] = r.value - } - return values -} - -func CollectRequestValues(rs []RequestValue) []any { - values := make([]any, len(rs)) - for i, r := range rs { - values[i] = r.RequestValue() - } - return values -} - -type requestValueCreator[T any | string | int64 | float64 | bool] struct { - destination *requestValue[T] -} - -func (s requestValueCreator[T]) Create(defaultValue requestValue[T], p *requestValue[T], c RequestConfig) cli.Value { - p.value = defaultValue.value - p.config = c - return &requestValueCreator[T]{ - destination: p, - } -} - -func (s requestValueCreator[T]) ToString(val requestValue[T]) string { - data, err := yaml.Marshal(val) - if err != nil { - return "" - } - return string(data) -} - -func (s *requestValueCreator[T]) Set(str string) error { - var isStringType bool - var zeroVal T - _, isStringType = any(zeroVal).(string) - if isStringType { - s.destination.value = any(str).(T) - } else { - var val T - if err := yaml.Unmarshal([]byte(str), &val); err != nil { - return err - } - s.destination.value = val - } - return nil -} - -func (s *requestValueCreator[T]) Get() any { - return *s.destination -} - -func (s *requestValueCreator[T]) String() string { - if s.destination != nil { - return s.ToString(*s.destination) - } - return "" -} - -func (s requestValueCreator[T]) IsBoolFlag() bool { - var zero T - _, ok := any(zero).(bool) - return ok -} - -func parseTimeWithLayouts(str string, layouts []string) (time.Time, error) { - var t time.Time - var err error - for _, layout := range layouts { - t, err = time.Parse(layout, str) - if err == nil { - break - } - } - return t, err -} - -// Value creator for date, time, and datetime types -type timeFormatCreator struct { - requestValueCreator[string] - inputFormats []string - outputFormat string -} - -func (s timeFormatCreator) Create(defaultValue requestValue[string], p *requestValue[string], c RequestConfig) cli.Value { - *p = defaultValue - p.config = c - return &timeFormatCreator{ - requestValueCreator: requestValueCreator[string]{ - destination: p, - }, - inputFormats: s.inputFormats, - outputFormat: s.outputFormat, - } -} - -func (s *timeFormatCreator) Set(str string) error { - t, err := parseTimeWithLayouts(str, s.inputFormats) - s.destination.value = t.Format(s.outputFormat) - return err -} - -type dateCreator struct { - timeFormatCreator -} - -func (d dateCreator) Create(defaultValue requestValue[string], p *requestValue[string], c RequestConfig) cli.Value { - return timeFormatCreator{ - requestValueCreator: requestValueCreator[string]{ - destination: p, - }, - inputFormats: []string{ - "2006-01-02", - "01/02/2006", - "Jan 2, 2006", - "January 2, 2006", - "2-Jan-2006", - }, - outputFormat: "2006-01-02", - }.Create(defaultValue, p, c) -} - -type timeCreator struct { - timeFormatCreator -} - -func (t timeCreator) Create(defaultValue requestValue[string], p *requestValue[string], c RequestConfig) cli.Value { - return timeFormatCreator{ - requestValueCreator: requestValueCreator[string]{ - destination: p, - }, - inputFormats: []string{ - "15:04:05", - "3:04:05PM", - "3:04 PM", - "15:04", - time.Kitchen, - }, - outputFormat: "15:04:05", - }.Create(defaultValue, p, c) -} - -type dateTimeCreator struct { - timeFormatCreator -} - -func (dt dateTimeCreator) Create(defaultValue requestValue[string], p *requestValue[string], c RequestConfig) cli.Value { - return timeFormatCreator{ - requestValueCreator: requestValueCreator[string]{ - destination: p, - }, - inputFormats: []string{ - time.RFC3339, - "2006-01-02T15:04:05Z07:00", - "2006-01-02T15:04:05", - "2006-01-02 15:04:05", - time.RFC1123, - time.RFC822, - time.ANSIC, - }, - outputFormat: time.RFC3339, - }.Create(defaultValue, p, c) -} diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 9c06729..5898eca 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -1,5 +1,3 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - package cmd import ( @@ -84,63 +82,6 @@ func init() { &resourcesCmd, &deviceCmd, { - Name: "health", - Category: "API RESOURCE", - Commands: []*cli.Command{ - &healthCheck, - }, - }, - { - Name: "images", - Category: "API RESOURCE", - Commands: []*cli.Command{ - &imagesCreate, - &imagesList, - &imagesGet, - }, - }, - { - Name: "instances", - Category: "API RESOURCE", - Commands: []*cli.Command{ - &instancesCreate, - &instancesList, - &instancesGet, - &instancesRestore, - &instancesLogs, - &instancesDelete, - &instancesStandby, - &instancesStart, - &instancesStop, - }, - }, - { - Name: "instances:volumes", - Category: "API RESOURCE", - Commands: []*cli.Command{ - &instancesVolumesAttach, - &instancesVolumesDetach, - }, - }, - { - Name: "volumes", - Category: "API RESOURCE", - Commands: []*cli.Command{ - &volumesCreate, - &volumesList, - &volumesGet, - }, - }, - { - Name: "ingresses", - Category: "API RESOURCE", - Commands: []*cli.Command{ - &ingressesCreate, - &ingressesList, - &ingressesGet, - }, - }, - { Name: "@manpages", Usage: "Generate documentation for 'man'", UsageText: "hypeman @manpages [-o hypeman.1] [--gzip]", diff --git a/pkg/cmd/cmdutil.go b/pkg/cmd/cmdutil.go index 6860fe6..8093413 100644 --- a/pkg/cmd/cmdutil.go +++ b/pkg/cmd/cmdutil.go @@ -25,10 +25,6 @@ import ( func getDefaultRequestOptions(cmd *cli.Command) []option.RequestOption { opts := []option.RequestOption{ option.WithHeader("User-Agent", fmt.Sprintf("Hypeman/CLI %s", Version)), - option.WithHeader("X-Stainless-Lang", "cli"), - option.WithHeader("X-Stainless-Package-Version", Version), - option.WithHeader("X-Stainless-Runtime", "cli"), - option.WithHeader("X-Stainless-CLI-Command", cmd.FullName()), } // Override base URL if the --base-url flag is provided diff --git a/pkg/cmd/flagoptions.go b/pkg/cmd/flagoptions.go deleted file mode 100644 index ab7c0d5..0000000 --- a/pkg/cmd/flagoptions.go +++ /dev/null @@ -1,134 +0,0 @@ -package cmd - -import ( - "bytes" - "encoding/json" - "io" - "mime/multipart" - "os" - - "github.com/kernel/hypeman-cli/internal/apiform" - "github.com/kernel/hypeman-cli/internal/apiquery" - "github.com/kernel/hypeman-cli/internal/requestflag" - "github.com/kernel/hypeman-go/option" - - "github.com/urfave/cli/v3" -) - -type BodyContentType int - -const ( - MultipartFormEncoded BodyContentType = iota - ApplicationJSON -) - -func flagOptions( - cmd *cli.Command, - nestedFormat apiquery.NestedQueryFormat, - arrayFormat apiquery.ArrayQueryFormat, - bodyType BodyContentType, -) ([]option.RequestOption, error) { - var options []option.RequestOption - if cmd.Bool("debug") { - options = append(options, debugMiddlewareOption) - } - - queries := make(map[string]any) - headers := make(map[string]any) - body := make(map[string]any) - if isInputPiped() { - data, err := io.ReadAll(os.Stdin) - if err != nil { - return nil, err - } - if err := json.Unmarshal(data, &body); err != nil { - return nil, err - } - } - - for _, flag := range cmd.Flags { - if !flag.IsSet() { - continue - } - value := flag.Get() - if toSend, ok := value.(requestflag.RequestValue); ok { - config := toSend.RequestConfig() - if config.BodyPath != "" { - body[config.BodyPath] = toSend.RequestValue() - } else if config.QueryPath != "" { - queries[config.QueryPath] = toSend.RequestValue() - } else if config.HeaderPath != "" { - headers[config.HeaderPath] = toSend.RequestValue() - } - } else if toSend, ok := value.([]requestflag.RequestValue); ok { - config := toSend[0].RequestConfig() - if config.BodyPath != "" { - body[config.BodyPath] = requestflag.CollectRequestValues(toSend) - } else if config.QueryPath != "" { - queries[config.QueryPath] = requestflag.CollectRequestValues(toSend) - } else if config.HeaderPath != "" { - headers[config.HeaderPath] = requestflag.CollectRequestValues(toSend) - } - } - } - - querySettings := apiquery.QuerySettings{ - NestedFormat: nestedFormat, - ArrayFormat: arrayFormat, - } - - // Add query parameters: - if values, err := apiquery.MarshalWithSettings(queries, querySettings); err != nil { - return nil, err - } else { - for k, vs := range values { - if len(vs) == 0 { - options = append(options, option.WithQueryDel(k)) - } else { - options = append(options, option.WithQuery(k, vs[0])) - for _, v := range vs[1:] { - options = append(options, option.WithQueryAdd(k, v)) - } - } - } - } - - // Add header parameters - if values, err := apiquery.MarshalWithSettings(headers, querySettings); err != nil { - return nil, err - } else { - for k, vs := range values { - if len(vs) == 0 { - options = append(options, option.WithHeaderDel(k)) - } else { - options = append(options, option.WithHeader(k, vs[0])) - for _, v := range vs[1:] { - options = append(options, option.WithHeaderAdd(k, v)) - } - } - } - } - - switch bodyType { - case MultipartFormEncoded: - buf := new(bytes.Buffer) - writer := multipart.NewWriter(buf) - if err := apiform.MarshalWithSettings(body, writer, apiform.FormatComma); err != nil { - return nil, err - } - if err := writer.Close(); err != nil { - return nil, err - } - options = append(options, option.WithRequestBody(writer.FormDataContentType(), buf)) - case ApplicationJSON: - bodyBytes, err := json.Marshal(body) - if err != nil { - return nil, err - } - options = append(options, option.WithRequestBody("application/json", bodyBytes)) - default: - panic("Invalid body content type!") - } - - return options, nil -} diff --git a/pkg/cmd/health.go b/pkg/cmd/health.go deleted file mode 100644 index 4b0184e..0000000 --- a/pkg/cmd/health.go +++ /dev/null @@ -1,53 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - "os" - - "github.com/kernel/hypeman-cli/internal/apiquery" - "github.com/kernel/hypeman-go" - "github.com/kernel/hypeman-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var healthCheck = cli.Command{ - Name: "check", - Usage: "Health check", - Flags: []cli.Flag{}, - Action: handleHealthCheck, - HideHelpCommand: true, -} - -func handleHealthCheck(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Health.Check(ctx, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "health check", obj, format, transform) -} diff --git a/pkg/cmd/image.go b/pkg/cmd/image.go deleted file mode 100644 index afac226..0000000 --- a/pkg/cmd/image.go +++ /dev/null @@ -1,187 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - "net/url" - "os" - - "github.com/kernel/hypeman-cli/internal/apiquery" - "github.com/kernel/hypeman-cli/internal/requestflag" - "github.com/kernel/hypeman-go" - "github.com/kernel/hypeman-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var imagesCreate = cli.Command{ - Name: "create", - Usage: "Pull and convert OCI image", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "name", - Usage: "OCI image reference (e.g., docker.io/library/nginx:latest)", - Config: requestflag.RequestConfig{ - BodyPath: "name", - }, - }, - }, - Action: handleImagesCreate, - HideHelpCommand: true, -} - -var imagesList = cli.Command{ - Name: "list", - Usage: "List images", - Flags: []cli.Flag{}, - Action: handleImagesList, - HideHelpCommand: true, -} - -var imagesDelete = cli.Command{ - Name: "delete", - Usage: "Delete image", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "name", - }, - }, - Action: handleImagesDelete, - HideHelpCommand: true, -} - -var imagesGet = cli.Command{ - Name: "get", - Usage: "Get image details", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "name", - }, - }, - Action: handleImagesGet, - HideHelpCommand: true, -} - -func handleImagesCreate(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - params := hypeman.ImageNewParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Images.New(ctx, params, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "images create", obj, format, transform) -} - -func handleImagesList(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Images.List(ctx, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "images list", obj, format, transform) -} - -func handleImagesDelete(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("name") && len(unusedArgs) > 0 { - cmd.Set("name", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - // URL-encode the name to handle slashes in image references (e.g., docker.io/library/nginx:latest) - name := url.PathEscape(requestflag.CommandRequestValue[string](cmd, "name")) - return client.Images.Delete(ctx, name, options...) -} - -func handleImagesGet(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("name") && len(unusedArgs) > 0 { - cmd.Set("name", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - // URL-encode the name to handle slashes in image references (e.g., docker.io/library/nginx:latest) - name := url.PathEscape(requestflag.CommandRequestValue[string](cmd, "name")) - _, err = client.Images.Get(ctx, name, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "images get", obj, format, transform) -} diff --git a/pkg/cmd/ingress.go b/pkg/cmd/ingress.go deleted file mode 100644 index ec05bd8..0000000 --- a/pkg/cmd/ingress.go +++ /dev/null @@ -1,189 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - "os" - - "github.com/kernel/hypeman-cli/internal/apiquery" - "github.com/kernel/hypeman-cli/internal/requestflag" - "github.com/kernel/hypeman-go" - "github.com/kernel/hypeman-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var ingressesCreate = cli.Command{ - Name: "create", - Usage: "Create ingress", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "name", - Usage: "Human-readable name (lowercase letters, digits, and dashes only; cannot start or end with a dash)", - Config: requestflag.RequestConfig{ - BodyPath: "name", - }, - }, - &requestflag.YAMLSliceFlag{ - Name: "rule", - Usage: "Routing rules for this ingress", - Config: requestflag.RequestConfig{ - BodyPath: "rules", - }, - }, - }, - Action: handleIngressesCreate, - HideHelpCommand: true, -} - -var ingressesList = cli.Command{ - Name: "list", - Usage: "List ingresses", - Flags: []cli.Flag{}, - Action: handleIngressesList, - HideHelpCommand: true, -} - -var ingressesDelete = cli.Command{ - Name: "delete", - Usage: "Delete ingress", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleIngressesDelete, - HideHelpCommand: true, -} - -var ingressesGet = cli.Command{ - Name: "get", - Usage: "Get ingress details", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleIngressesGet, - HideHelpCommand: true, -} - -func handleIngressesCreate(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - params := hypeman.IngressNewParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Ingresses.New(ctx, params, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "ingresses create", obj, format, transform) -} - -func handleIngressesList(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Ingresses.List(ctx, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "ingresses list", obj, format, transform) -} - -func handleIngressesDelete(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - return client.Ingresses.Delete(ctx, requestflag.CommandRequestValue[string](cmd, "id"), options...) -} - -func handleIngressesGet(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Ingresses.Get(ctx, requestflag.CommandRequestValue[string](cmd, "id"), options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "ingresses get", obj, format, transform) -} diff --git a/pkg/cmd/instance.go b/pkg/cmd/instance.go deleted file mode 100644 index ec935a7..0000000 --- a/pkg/cmd/instance.go +++ /dev/null @@ -1,490 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - "os" - - "github.com/kernel/hypeman-cli/internal/apiquery" - "github.com/kernel/hypeman-cli/internal/requestflag" - "github.com/kernel/hypeman-go" - "github.com/kernel/hypeman-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var instancesCreate = cli.Command{ - Name: "create", - Usage: "Create and start instance", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "image", - Usage: "OCI image reference", - Config: requestflag.RequestConfig{ - BodyPath: "image", - }, - }, - &requestflag.StringFlag{ - Name: "name", - Usage: "Human-readable name (lowercase letters, digits, and dashes only; cannot start or end with a dash)", - Config: requestflag.RequestConfig{ - BodyPath: "name", - }, - }, - &requestflag.YAMLFlag{ - Name: "env", - Usage: "Environment variables", - Config: requestflag.RequestConfig{ - BodyPath: "env", - }, - }, - &requestflag.StringFlag{ - Name: "hotplug-size", - Usage: `Additional memory for hotplug (human-readable format like "3GB", "1G")`, - Value: requestflag.Value[string]("3GB"), - Config: requestflag.RequestConfig{ - BodyPath: "hotplug_size", - }, - }, - &requestflag.YAMLFlag{ - Name: "network", - Usage: "Network configuration for the instance", - Config: requestflag.RequestConfig{ - BodyPath: "network", - }, - }, - &requestflag.StringFlag{ - Name: "overlay-size", - Usage: `Writable overlay disk size (human-readable format like "10GB", "50G")`, - Value: requestflag.Value[string]("10GB"), - Config: requestflag.RequestConfig{ - BodyPath: "overlay_size", - }, - }, - &requestflag.StringFlag{ - Name: "size", - Usage: `Base memory size (human-readable format like "1GB", "512MB", "2G")`, - Value: requestflag.Value[string]("1GB"), - Config: requestflag.RequestConfig{ - BodyPath: "size", - }, - }, - &requestflag.IntFlag{ - Name: "vcpus", - Usage: "Number of virtual CPUs", - Value: requestflag.Value[int64](2), - Config: requestflag.RequestConfig{ - BodyPath: "vcpus", - }, - }, - &requestflag.YAMLSliceFlag{ - Name: "volume", - Usage: "Volumes to attach to the instance at creation time", - Config: requestflag.RequestConfig{ - BodyPath: "volumes", - }, - }, - }, - Action: handleInstancesCreate, - HideHelpCommand: true, -} - -var instancesList = cli.Command{ - Name: "list", - Usage: "List instances", - Flags: []cli.Flag{}, - Action: handleInstancesList, - HideHelpCommand: true, -} - -var instancesDelete = cli.Command{ - Name: "delete", - Usage: "Stop and delete instance", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleInstancesDelete, - HideHelpCommand: true, -} - -var instancesGet = cli.Command{ - Name: "get", - Usage: "Get instance details", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleInstancesGet, - HideHelpCommand: true, -} - -var instancesLogs = cli.Command{ - Name: "logs", - Usage: "Streams instance console logs as Server-Sent Events. Returns the last N lines\n(controlled by `tail` parameter), then optionally continues streaming new lines\nif `follow=true`.", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - &requestflag.BoolFlag{ - Name: "follow", - Usage: "Continue streaming new lines after initial output", - Config: requestflag.RequestConfig{ - QueryPath: "follow", - }, - }, - &requestflag.IntFlag{ - Name: "tail", - Usage: "Number of lines to return from end", - Value: requestflag.Value[int64](100), - Config: requestflag.RequestConfig{ - QueryPath: "tail", - }, - }, - }, - Action: handleInstancesLogs, - HideHelpCommand: true, -} - -var instancesRestore = cli.Command{ - Name: "restore", - Usage: "Restore instance from standby", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleInstancesRestore, - HideHelpCommand: true, -} - -var instancesStandby = cli.Command{ - Name: "standby", - Usage: "Put instance in standby (pause, snapshot, delete VMM)", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleInstancesStandby, - HideHelpCommand: true, -} - -var instancesStart = cli.Command{ - Name: "start", - Usage: "Start a stopped instance", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleInstancesStart, - HideHelpCommand: true, -} - -var instancesStop = cli.Command{ - Name: "stop", - Usage: "Stop instance (graceful shutdown)", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleInstancesStop, - HideHelpCommand: true, -} - -func handleInstancesCreate(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - params := hypeman.InstanceNewParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Instances.New(ctx, params, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "instances create", obj, format, transform) -} - -func handleInstancesList(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Instances.List(ctx, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "instances list", obj, format, transform) -} - -func handleInstancesDelete(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - return client.Instances.Delete(ctx, requestflag.CommandRequestValue[string](cmd, "id"), options...) -} - -func handleInstancesGet(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Instances.Get(ctx, requestflag.CommandRequestValue[string](cmd, "id"), options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "instances get", obj, format, transform) -} - -func handleInstancesLogs(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - params := hypeman.InstanceLogsParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - stream := client.Instances.LogsStreaming( - ctx, - requestflag.CommandRequestValue[string](cmd, "id"), - params, - options..., - ) - defer stream.Close() - for stream.Next() { - fmt.Printf("%s\n", stream.Current()) - } - return stream.Err() -} - -func handleInstancesRestore(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Instances.Restore(ctx, requestflag.CommandRequestValue[string](cmd, "id"), options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "instances restore", obj, format, transform) -} - -func handleInstancesStandby(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Instances.Standby(ctx, requestflag.CommandRequestValue[string](cmd, "id"), options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "instances standby", obj, format, transform) -} - -func handleInstancesStart(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Instances.Start( - ctx, - requestflag.CommandRequestValue[string](cmd, "id"), - options..., - ) - if err != nil { - return err - } - - json := gjson.Parse(string(res)) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "instances start", json, format, transform) -} - -func handleInstancesStop(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Instances.Stop( - ctx, - requestflag.CommandRequestValue[string](cmd, "id"), - options..., - ) - if err != nil { - return err - } - - json := gjson.Parse(string(res)) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "instances stop", json, format, transform) -} diff --git a/pkg/cmd/instancevolume.go b/pkg/cmd/instancevolume.go deleted file mode 100644 index 30694d9..0000000 --- a/pkg/cmd/instancevolume.go +++ /dev/null @@ -1,144 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - "os" - - "github.com/kernel/hypeman-cli/internal/apiquery" - "github.com/kernel/hypeman-cli/internal/requestflag" - "github.com/kernel/hypeman-go" - "github.com/kernel/hypeman-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var instancesVolumesAttach = cli.Command{ - Name: "attach", - Usage: "Attach volume to instance", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - &requestflag.StringFlag{ - Name: "volume-id", - }, - &requestflag.StringFlag{ - Name: "mount-path", - Usage: "Path where volume should be mounted", - Config: requestflag.RequestConfig{ - BodyPath: "mount_path", - }, - }, - &requestflag.BoolFlag{ - Name: "readonly", - Usage: "Mount as read-only", - Config: requestflag.RequestConfig{ - BodyPath: "readonly", - }, - }, - }, - Action: handleInstancesVolumesAttach, - HideHelpCommand: true, -} - -var instancesVolumesDetach = cli.Command{ - Name: "detach", - Usage: "Detach volume from instance", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - &requestflag.StringFlag{ - Name: "volume-id", - }, - }, - Action: handleInstancesVolumesDetach, - HideHelpCommand: true, -} - -func handleInstancesVolumesAttach(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("volume-id") && len(unusedArgs) > 0 { - cmd.Set("volume-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - params := hypeman.InstanceVolumeAttachParams{ - ID: requestflag.CommandRequestValue[string](cmd, "id"), - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Instances.Volumes.Attach( - ctx, - requestflag.CommandRequestValue[string](cmd, "volume-id"), - params, - options..., - ) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "instances:volumes attach", obj, format, transform) -} - -func handleInstancesVolumesDetach(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("volume-id") && len(unusedArgs) > 0 { - cmd.Set("volume-id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - params := hypeman.InstanceVolumeDetachParams{ - ID: requestflag.CommandRequestValue[string](cmd, "id"), - } - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Instances.Volumes.Detach( - ctx, - requestflag.CommandRequestValue[string](cmd, "volume-id"), - params, - options..., - ) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "instances:volumes detach", obj, format, transform) -} diff --git a/pkg/cmd/util.go b/pkg/cmd/util.go deleted file mode 100644 index e71c31b..0000000 --- a/pkg/cmd/util.go +++ /dev/null @@ -1,149 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "net/http/httputil" - "os" - "reflect" - "strings" - - "github.com/kernel/hypeman-go/option" - - "github.com/tidwall/sjson" - "github.com/urfave/cli/v3" -) - - -type fileReader struct { - Value io.Reader - Base64Encoded bool -} - -func (f *fileReader) Set(filename string) error { - reader, err := os.Open(filename) - if err != nil { - return fmt.Errorf("failed to read file %q: %w", filename, err) - } - f.Value = reader - return nil -} - -func (f *fileReader) String() string { - if f.Value == nil { - return "" - } - buf := new(bytes.Buffer) - buf.ReadFrom(f.Value) - if f.Base64Encoded { - return base64.StdEncoding.EncodeToString(buf.Bytes()) - } - return buf.String() -} - -func (f *fileReader) Get() any { - return f.String() -} - -func unmarshalWithReaders(data []byte, v any) error { - var fields map[string]json.RawMessage - if err := json.Unmarshal(data, &fields); err != nil { - return err - } - - rv := reflect.ValueOf(v).Elem() - rt := rv.Type() - - for i := 0; i < rv.NumField(); i++ { - fv := rv.Field(i) - ft := rt.Field(i) - - jsonKey := ft.Tag.Get("json") - if jsonKey == "" { - jsonKey = ft.Name - } else if idx := strings.Index(jsonKey, ","); idx != -1 { - jsonKey = jsonKey[:idx] - } - - rawVal, ok := fields[jsonKey] - if !ok { - continue - } - - if ft.Type == reflect.TypeOf((*io.Reader)(nil)).Elem() { - var s string - if err := json.Unmarshal(rawVal, &s); err != nil { - return fmt.Errorf("field %s: %w", ft.Name, err) - } - fv.Set(reflect.ValueOf(strings.NewReader(s))) - } else { - ptr := fv.Addr().Interface() - if err := json.Unmarshal(rawVal, ptr); err != nil { - return fmt.Errorf("field %s: %w", ft.Name, err) - } - } - } - - return nil -} - -func unmarshalStdinWithFlags(cmd *cli.Command, flags map[string]string, target any) error { - var data []byte - if isInputPiped() { - var err error - if data, err = io.ReadAll(os.Stdin); err != nil { - return err - } - } - - // Merge CLI flags into the body - for flag, path := range flags { - if cmd.IsSet(flag) { - var err error - data, err = sjson.SetBytes(data, path, cmd.Value(flag)) - if err != nil { - return err - } - } - } - - if data != nil { - if err := unmarshalWithReaders(data, target); err != nil { - return fmt.Errorf("failed to unmarshal JSON: %w", err) - } - } - - return nil -} - -func debugMiddleware(debug bool) option.Middleware { - return func(r *http.Request, mn option.MiddlewareNext) (*http.Response, error) { - if debug { - logger := log.Default() - - if reqBytes, err := httputil.DumpRequest(r, true); err == nil { - logger.Printf("Request Content:\n%s\n", reqBytes) - } - - resp, err := mn(r) - if err != nil { - return resp, err - } - - if respBytes, err := httputil.DumpResponse(resp, true); err == nil { - logger.Printf("Response Content:\n%s\n", respBytes) - } - - return resp, err - } - - return mn(r) - } -} diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go index f37c8ca..543cb2b 100644 --- a/pkg/cmd/version.go +++ b/pkg/cmd/version.go @@ -1,5 +1,54 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - package cmd -const Version = "0.7.0" // x-release-please-version +import "runtime/debug" + +// version can be overridden at build time via ldflags: +// +// -X github.com/kernel/hypeman-cli/pkg/cmd.version=1.2.3 +var version string + +// Version is the CLI version string, resolved at init time. +var Version = resolveVersion() + +func resolveVersion() string { + // 1. ldflags override (GoReleaser sets this) + if version != "" { + return version + } + + // 2. Build info from Go toolchain + info, ok := debug.ReadBuildInfo() + if !ok { + return "dev" + } + + // Module version is set by `go install module@vX.Y.Z` + if v := info.Main.Version; v != "" && v != "(devel)" { + return v + } + + // 3. VCS revision from git (embedded automatically by `go build`) + var revision string + var dirty bool + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + revision = s.Value + case "vcs.modified": + dirty = s.Value == "true" + } + } + + if revision != "" { + short := revision + if len(short) > 7 { + short = short[:7] + } + if dirty { + return short + "-dirty" + } + return short + } + + return "dev" +} diff --git a/pkg/cmd/volume.go b/pkg/cmd/volume.go deleted file mode 100644 index 551dc14..0000000 --- a/pkg/cmd/volume.go +++ /dev/null @@ -1,196 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package cmd - -import ( - "context" - "fmt" - "os" - - "github.com/kernel/hypeman-cli/internal/apiquery" - "github.com/kernel/hypeman-cli/internal/requestflag" - "github.com/kernel/hypeman-go" - "github.com/kernel/hypeman-go/option" - "github.com/tidwall/gjson" - "github.com/urfave/cli/v3" -) - -var volumesCreate = cli.Command{ - Name: "create", - Usage: "Creates a new volume. Supports two modes:", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "name", - Usage: "Volume name", - Config: requestflag.RequestConfig{ - BodyPath: "name", - }, - }, - &requestflag.IntFlag{ - Name: "size-gb", - Usage: "Size in gigabytes", - Config: requestflag.RequestConfig{ - BodyPath: "size_gb", - }, - }, - &requestflag.StringFlag{ - Name: "id", - Usage: "Optional custom identifier (auto-generated if not provided)", - Config: requestflag.RequestConfig{ - BodyPath: "id", - }, - }, - }, - Action: handleVolumesCreate, - HideHelpCommand: true, -} - -var volumesList = cli.Command{ - Name: "list", - Usage: "List volumes", - Flags: []cli.Flag{}, - Action: handleVolumesList, - HideHelpCommand: true, -} - -var volumesDelete = cli.Command{ - Name: "delete", - Usage: "Delete volume", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleVolumesDelete, - HideHelpCommand: true, -} - -var volumesGet = cli.Command{ - Name: "get", - Usage: "Get volume details", - Flags: []cli.Flag{ - &requestflag.StringFlag{ - Name: "id", - }, - }, - Action: handleVolumesGet, - HideHelpCommand: true, -} - -func handleVolumesCreate(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - params := hypeman.VolumeNewParams{} - - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Volumes.New(ctx, params, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "volumes create", obj, format, transform) -} - -func handleVolumesList(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Volumes.List(ctx, options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "volumes list", obj, format, transform) -} - -func handleVolumesDelete(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - return client.Volumes.Delete(ctx, requestflag.CommandRequestValue[string](cmd, "id"), options...) -} - -func handleVolumesGet(ctx context.Context, cmd *cli.Command) error { - client := hypeman.NewClient(getDefaultRequestOptions(cmd)...) - unusedArgs := cmd.Args().Slice() - if !cmd.IsSet("id") && len(unusedArgs) > 0 { - cmd.Set("id", unusedArgs[0]) - unusedArgs = unusedArgs[1:] - } - if len(unusedArgs) > 0 { - return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs) - } - options, err := flagOptions( - cmd, - apiquery.NestedQueryFormatBrackets, - apiquery.ArrayQueryFormatComma, - ApplicationJSON, - ) - if err != nil { - return err - } - - var res []byte - options = append(options, option.WithResponseBodyInto(&res)) - _, err = client.Volumes.Get(ctx, requestflag.CommandRequestValue[string](cmd, "id"), options...) - if err != nil { - return err - } - - obj := gjson.ParseBytes(res) - format := cmd.Root().String("format") - transform := cmd.Root().String("transform") - return ShowJSON(os.Stdout, "volumes get", obj, format, transform) -} From 5ba4a32818f8c48810b1af4c484ad14ecf7d1cd9 Mon Sep 17 00:00:00 2001 From: Steven Miller Date: Wed, 11 Feb 2026 13:25:55 -0800 Subject: [PATCH 2/2] Clean up version output --- pkg/cmd/cmd.go | 3 +++ pkg/cmd/version.go | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/cmd.go b/pkg/cmd/cmd.go index 5898eca..0087cd2 100644 --- a/pkg/cmd/cmd.go +++ b/pkg/cmd/cmd.go @@ -19,6 +19,9 @@ var ( ) func init() { + cli.VersionPrinter = func(cmd *cli.Command) { + fmt.Fprintf(os.Stdout, "Hypeman CLI version %s\n", cmd.Root().Version) + } Command = &cli.Command{ Name: "hypeman", Usage: "CLI for the hypeman API", diff --git a/pkg/cmd/version.go b/pkg/cmd/version.go index 543cb2b..f55a099 100644 --- a/pkg/cmd/version.go +++ b/pkg/cmd/version.go @@ -1,12 +1,18 @@ package cmd -import "runtime/debug" +import ( + "regexp" + "runtime/debug" +) // version can be overridden at build time via ldflags: // // -X github.com/kernel/hypeman-cli/pkg/cmd.version=1.2.3 var version string +// semverTag matches clean semver tags like v1.2.3 or v0.9.5 (no prerelease/pseudo-version suffix). +var semverTag = regexp.MustCompile(`^v\d+\.\d+\.\d+$`) + // Version is the CLI version string, resolved at init time. var Version = resolveVersion() @@ -22,11 +28,6 @@ func resolveVersion() string { return "dev" } - // Module version is set by `go install module@vX.Y.Z` - if v := info.Main.Version; v != "" && v != "(devel)" { - return v - } - // 3. VCS revision from git (embedded automatically by `go build`) var revision string var dirty bool @@ -39,6 +40,12 @@ func resolveVersion() string { } } + // Only use module version if it's a clean semver tag (e.g. v1.2.3), + // not a pseudo-version like v0.9.5-0.20260211212111-7ef5ed6df05d. + if v := info.Main.Version; semverTag.MatchString(v) { + return v + } + if revision != "" { short := revision if len(short) > 7 {