Skip to content
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist/
/hypeman
.env
hypeman/**
bin/hypeman
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-da3f4038bb544acae375f44527f515dc58308f67822905258b155192041e65ed.yml
openapi_spec_hash: 4c7f6f453c20eda7fd8689e8917c65f9
config_hash: a7d0557c72de54fd6baded5b189777c3
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
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ require (
github.com/gorilla/websocket v1.5.3
github.com/itchyny/json2yaml v0.1.4
github.com/muesli/reflow v0.3.0
github.com/onkernel/hypeman-go v0.5.0
github.com/onkernel/hypeman-go v0.6.0
github.com/tidwall/gjson v1.18.0
github.com/tidwall/pretty v1.2.1
github.com/tidwall/sjson v1.2.5
github.com/urfave/cli-docs/v3 v3.0.0-alpha6
github.com/urfave/cli/v3 v3.3.2
golang.org/x/term v0.37.0
Expand Down Expand Up @@ -60,6 +59,7 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
Expand All @@ -74,3 +74,5 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.1 // indirect
)

replace github.com/onkernel/hypeman-go => github.com/stainless-sdks/hypeman-go v0.0.0-20251210223055-431af203f52d
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/onkernel/hypeman-go v0.5.0 h1:ILe+n18aN5MXx0ARxDJ/ZYqcX2MdfJqWrE4sn14gJ5I=
github.com/onkernel/hypeman-go v0.5.0/go.mod h1:BPT1yh0gbby1E+As/xLM3GVjw7752+2C5SaEiJV9rRc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
Expand All @@ -123,6 +121,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stainless-sdks/hypeman-go v0.0.0-20251210223055-431af203f52d h1:kZHZ9PflX6BMr1cP6jFT+PsR66Et4fVfHQJC1GJmDUg=
github.com/stainless-sdks/hypeman-go v0.0.0-20251210223055-431af203f52d/go.mod h1:BPT1yh0gbby1E+As/xLM3GVjw7752+2C5SaEiJV9rRc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
Expand Down
3 changes: 3 additions & 0 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func init() {
&psCmd,
&logsCmd,
&rmCmd,
&ingressCmd,
{
Name: "health",
Category: "API RESOURCE",
Expand Down Expand Up @@ -101,6 +102,8 @@ func init() {
&instancesLogs,
&instancesDelete,
&instancesStandby,
&instancesStart,
&instancesStop,
},
},
{
Expand Down
244 changes: 244 additions & 0 deletions pkg/cmd/ingresscmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package cmd

import (
"context"
"fmt"
"os"
"strings"

"github.com/onkernel/hypeman-go"
"github.com/onkernel/hypeman-go/option"
"github.com/urfave/cli/v3"
)

var ingressCmd = cli.Command{
Name: "ingress",
Usage: "Manage ingresses",
Commands: []*cli.Command{
&ingressCreateCmd,
&ingressListCmd,
&ingressDeleteCmd,
},
HideHelpCommand: true,
}

var ingressCreateCmd = cli.Command{
Name: "create",
Usage: "Create an ingress for an instance",
ArgsUsage: "<instance>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "hostname",
Aliases: []string{"H"},
Usage: "Hostname to match (exact match on Host header)",
Required: true,
},
&cli.IntFlag{
Name: "port",
Aliases: []string{"p"},
Usage: "Target port on the instance",
Required: true,
},
&cli.IntFlag{
Name: "host-port",
Usage: "Host port to listen on (default: 80)",
Value: 80,
},
&cli.BoolFlag{
Name: "tls",
Usage: "Enable TLS termination (certificate auto-issued via ACME)",
},
&cli.BoolFlag{
Name: "redirect-http",
Usage: "Auto-create HTTP to HTTPS redirect (only applies when --tls is enabled)",
},
&cli.StringFlag{
Name: "name",
Usage: "Ingress name (auto-generated from hostname if not provided)",
},
},
Action: handleIngressCreate,
HideHelpCommand: true,
}

var ingressListCmd = cli.Command{
Name: "list",
Usage: "List ingresses",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "quiet",
Aliases: []string{"q"},
Usage: "Only display ingress IDs",
},
},
Action: handleIngressList,
HideHelpCommand: true,
}

var ingressDeleteCmd = cli.Command{
Name: "delete",
Usage: "Delete an ingress",
ArgsUsage: "<id>",
Action: handleIngressDelete,
HideHelpCommand: true,
}

func handleIngressCreate(ctx context.Context, cmd *cli.Command) error {
args := cmd.Args().Slice()
if len(args) < 1 {
return fmt.Errorf("instance name or ID required\nUsage: hypeman ingress create <instance> --hostname <hostname> --port <port>")
}

instance := args[0]
hostname := cmd.String("hostname")
port := cmd.Int("port")
hostPort := cmd.Int("host-port")
tls := cmd.Bool("tls")
redirectHTTP := cmd.Bool("redirect-http")
name := cmd.String("name")

// Auto-generate name from hostname if not provided
if name == "" {
name = generateIngressName(hostname)
}

client := hypeman.NewClient(getDefaultRequestOptions(cmd)...)

var opts []option.RequestOption
if cmd.Root().Bool("debug") {
opts = append(opts, debugMiddlewareOption)
}

params := hypeman.IngressNewParams{
Name: name,
Rules: []hypeman.IngressRuleParam{
{
Match: hypeman.IngressMatchParam{
Hostname: hostname,
Port: hypeman.Int(int64(hostPort)),
},
Target: hypeman.IngressTargetParam{
Instance: instance,
Port: int64(port),
},
Tls: hypeman.Bool(tls),
RedirectHTTP: hypeman.Bool(redirectHTTP),
},
},
}

fmt.Fprintf(os.Stderr, "Creating ingress %s...\n", name)

result, err := client.Ingresses.New(ctx, params, opts...)
if err != nil {
return err
}

fmt.Println(result.ID)
return nil
}

func handleIngressList(ctx context.Context, cmd *cli.Command) error {
client := hypeman.NewClient(getDefaultRequestOptions(cmd)...)

var opts []option.RequestOption
if cmd.Root().Bool("debug") {
opts = append(opts, debugMiddlewareOption)
}

ingresses, err := client.Ingresses.List(ctx, opts...)
if err != nil {
return err
}

quietMode := cmd.Bool("quiet")

if quietMode {
for _, ing := range *ingresses {
fmt.Println(ing.ID)
}
return nil
}

if len(*ingresses) == 0 {
fmt.Fprintln(os.Stderr, "No ingresses found.")
return nil
}

table := NewTableWriter(os.Stdout, "ID", "NAME", "HOSTNAME", "TARGET", "TLS", "CREATED")
for _, ing := range *ingresses {
// Extract first rule's hostname and target for display
hostname := ""
target := ""
tlsEnabled := "-"
if len(ing.Rules) > 0 {
rule := ing.Rules[0]
hostname = rule.Match.Hostname
target = fmt.Sprintf("%s:%d", rule.Target.Instance, rule.Target.Port)
if rule.Tls {
tlsEnabled = "yes"
} else {
tlsEnabled = "no"
}
}

table.AddRow(
TruncateID(ing.ID),
TruncateString(ing.Name, 20),
TruncateString(hostname, 25),
target,
tlsEnabled,
FormatTimeAgo(ing.CreatedAt),
)
}
table.Render()

return nil
}

func handleIngressDelete(ctx context.Context, cmd *cli.Command) error {
args := cmd.Args().Slice()
if len(args) < 1 {
return fmt.Errorf("ingress ID or name required\nUsage: hypeman ingress delete <id>")
}

id := args[0]

client := hypeman.NewClient(getDefaultRequestOptions(cmd)...)

var opts []option.RequestOption
if cmd.Root().Bool("debug") {
opts = append(opts, debugMiddlewareOption)
}

err := client.Ingresses.Delete(ctx, id, opts...)
if err != nil {
return err
}

fmt.Fprintf(os.Stderr, "Ingress %s deleted.\n", id)
return nil
}

// generateIngressName generates an ingress name from hostname
func generateIngressName(hostname string) string {
// Replace dots with dashes
name := strings.ReplaceAll(hostname, ".", "-")
name = strings.ToLower(name)

// Remove invalid characters (only allow a-z, 0-9, and -)
var cleaned strings.Builder
for _, r := range name {
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
cleaned.WriteRune(r)
}
}
name = cleaned.String()

// Trim leading/trailing dashes
name = strings.Trim(name, "-")

// Add random suffix
suffix := randomSuffix(4)
return fmt.Sprintf("%s-%s", name, suffix)
}
Loading
Loading