Skip to content
Open
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
7 changes: 6 additions & 1 deletion network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,19 @@
type Route struct {
ctrlplane.Entity

TenantID string `db:"tenant_id" json:"tenant_id"`

Check failure on line 32 in network/network.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: db:"tenant_id" json:"tenant_id" (tagalign)
InstanceID id.ID `db:"instance_id" json:"instance_id"`

Check failure on line 33 in network/network.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: db:"instance_id" json:"instance_id" (tagalign)
ServiceName string `db:"service_name" json:"service_name,omitempty"`

Check failure on line 34 in network/network.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: db:"service_name" json:"service_name,omitempty" (tagalign)
Path string `db:"path" json:"path"`

Check failure on line 35 in network/network.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: db:"path" json:"path" (tagalign)
Port int `db:"port" json:"port"`

Check failure on line 36 in network/network.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: db:"port" json:"port" (tagalign)
Protocol string `db:"protocol" json:"protocol"`

Check failure on line 37 in network/network.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: db:"protocol" json:"protocol" (tagalign)
Weight int `db:"weight" json:"weight"`

Check failure on line 38 in network/network.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: db:"weight" json:"weight" (tagalign)
StripPrefix bool `db:"strip_prefix" json:"strip_prefix"`
StripPrefix bool `db:"strip_prefix" json:"strip_prefix"`
PathMode string `db:"path_mode" json:"path_mode,omitempty"`
RewriteRedirects bool `db:"rewrite_redirects" json:"rewrite_redirects,omitempty"`
RewriteCookiePath bool `db:"rewrite_cookie_path" json:"rewrite_cookie_path,omitempty"`
UpstreamOrigin string `db:"upstream_origin" json:"upstream_origin,omitempty"`
TLSVerify bool `db:"tls_verify" json:"tls_verify"`
// Hostname, when set, scopes the route to a single host (the
// workspace's API hostname). The OctopusRouter uses it as the
// Gateway API HTTPRoute's `hostnames` entry so per-workspace path
Expand Down
9 changes: 7 additions & 2 deletions network/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,19 @@
// multi-service instance — leave empty to route to the instance's
// Main service (the default for single-service workloads).
type AddRouteRequest struct {
InstanceID id.ID `json:"instance_id" validate:"required"`

Check failure on line 55 in network/service.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: json:"instance_id" validate:"required" (tagalign)
ServiceName string `json:"service_name,omitempty"`
Path string `json:"path" validate:"required"`

Check failure on line 57 in network/service.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: json:"path" validate:"required" (tagalign)
Port int `json:"port" validate:"required"`

Check failure on line 58 in network/service.go

View workflow job for this annotation

GitHub Actions / Lint

tag is not aligned, should be: json:"port" validate:"required" (tagalign)
Protocol string `default:"http" json:"protocol"`
Weight int `default:"100" json:"weight"`
StripPrefix bool `json:"strip_prefix,omitempty"`
Hostname string `json:"hostname,omitempty"`
StripPrefix bool `json:"strip_prefix,omitempty"`
PathMode string `json:"path_mode,omitempty"`
RewriteRedirects bool `json:"rewrite_redirects,omitempty"`
RewriteCookiePath bool `json:"rewrite_cookie_path,omitempty"`
UpstreamOrigin string `json:"upstream_origin,omitempty"`
TLSVerify *bool `json:"tls_verify,omitempty"`
Hostname string `json:"hostname,omitempty"`
}

// UpdateRouteRequest holds the parameters for modifying a route.
Expand Down
9 changes: 7 additions & 2 deletions network/service_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,13 @@ func (s *service) AddRoute(ctx context.Context, req AddRouteRequest) (*Route, er
Port: req.Port,
Protocol: protocol,
Weight: weight,
StripPrefix: req.StripPrefix,
Hostname: req.Hostname,
StripPrefix: req.StripPrefix,
PathMode: req.PathMode,
RewriteRedirects: req.RewriteRedirects,
RewriteCookiePath: req.RewriteCookiePath,
UpstreamOrigin: req.UpstreamOrigin,
TLSVerify: req.TLSVerify == nil || *req.TLSVerify,
Hostname: req.Hostname,
}

if err := s.store.InsertRoute(ctx, route); err != nil {
Expand Down
44 changes: 32 additions & 12 deletions store/mongo/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,16 +604,23 @@ func fromDomainModel(m *domainModel) *network.Domain {
type routeModel struct {
grove.BaseModel `grove:"table:cp_routes"`

ID string `bson:"_id" grove:"id,pk"`
TenantID string `bson:"tenant_id" grove:"tenant_id"`
InstanceID string `bson:"instance_id" grove:"instance_id"`
Path string `bson:"path" grove:"path"`
Port int `bson:"port" grove:"port"`
Protocol string `bson:"protocol,omitempty" grove:"protocol"`
Weight int `bson:"weight" grove:"weight"`
StripPrefix bool `bson:"strip_prefix" grove:"strip_prefix"`
CreatedAt time.Time `bson:"created_at" grove:"created_at"`
UpdatedAt time.Time `bson:"updated_at" grove:"updated_at"`
ID string `bson:"_id" grove:"id,pk"`
TenantID string `bson:"tenant_id" grove:"tenant_id"`
InstanceID string `bson:"instance_id" grove:"instance_id"`
Path string `bson:"path" grove:"path"`
Port int `bson:"port" grove:"port"`
Protocol string `bson:"protocol,omitempty" grove:"protocol"`
Weight int `bson:"weight" grove:"weight"`
StripPrefix bool `bson:"strip_prefix" grove:"strip_prefix"`

PathMode string `bson:"path_mode" grove:"path_mode"`
RewriteRedirects bool `bson:"rewrite_redirects" grove:"rewrite_redirects"`
RewriteCookiePath bool `bson:"rewrite_cookie_path" grove:"rewrite_cookie_path"`
UpstreamOrigin string `bson:"upstream_origin" grove:"upstream_origin"`
TLSVerify bool `bson:"tls_verify" grove:"tls_verify"`

CreatedAt time.Time `bson:"created_at" grove:"created_at"`
UpdatedAt time.Time `bson:"updated_at" grove:"updated_at"`
}

func toRouteModel(r *network.Route) *routeModel {
Expand All @@ -626,8 +633,15 @@ func toRouteModel(r *network.Route) *routeModel {
Protocol: r.Protocol,
Weight: r.Weight,
StripPrefix: r.StripPrefix,
CreatedAt: r.CreatedAt,
UpdatedAt: r.UpdatedAt,

PathMode: r.PathMode,
RewriteRedirects: r.RewriteRedirects,
RewriteCookiePath: r.RewriteCookiePath,
UpstreamOrigin: r.UpstreamOrigin,
TLSVerify: r.TLSVerify,

CreatedAt: r.CreatedAt,
UpdatedAt: r.UpdatedAt,
}
}

Expand All @@ -645,6 +659,12 @@ func fromRouteModel(m *routeModel) *network.Route {
Protocol: m.Protocol,
Weight: m.Weight,
StripPrefix: m.StripPrefix,

PathMode: m.PathMode,
RewriteRedirects: m.RewriteRedirects,
RewriteCookiePath: m.RewriteCookiePath,
UpstreamOrigin: m.UpstreamOrigin,
TLSVerify: m.TLSVerify,
}
}

Expand Down
20 changes: 20 additions & 0 deletions store/postgres/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,26 @@ CREATE INDEX IF NOT EXISTS idx_cp_workloads_state ON cp_workloads (state);
Down: func(ctx context.Context, exec migrate.Executor) error {
_, err := exec.Exec(ctx, `DROP TABLE IF EXISTS cp_workloads`)

return err
},
},
// VirtualGateway proxy-mode fields on routes. Octopus reads these
// to decide redirect/cookie rewriting, the upstream origin, and
// whether to verify upstream TLS. Additive — existing routes get
// safe defaults (empty/false; tls_verify defaults TRUE).
// Single comma-separated ALTER so grove's `;`-split executor runs
// it as one statement.
&migrate.Migration{
Name: "add_proxy_fields_to_cp_routes",
Version: "20240101000026",
Up: func(ctx context.Context, exec migrate.Executor) error {
_, err := exec.Exec(ctx, `ALTER TABLE cp_routes ADD COLUMN IF NOT EXISTS path_mode TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS rewrite_redirects BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS rewrite_cookie_path BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS upstream_origin TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS tls_verify BOOLEAN NOT NULL DEFAULT TRUE`)

return err
},
Down: func(ctx context.Context, exec migrate.Executor) error {
_, err := exec.Exec(ctx, `ALTER TABLE cp_routes DROP COLUMN IF EXISTS path_mode, DROP COLUMN IF EXISTS rewrite_redirects, DROP COLUMN IF EXISTS rewrite_cookie_path, DROP COLUMN IF EXISTS upstream_origin, DROP COLUMN IF EXISTS tls_verify`)

return err
},
},
Expand Down
38 changes: 26 additions & 12 deletions store/postgres/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,16 +216,23 @@ type domainModel struct {
type routeModel struct {
grove.BaseModel `grove:"table:cp_routes"`

ID string `grove:"id,pk"`
TenantID string `grove:"tenant_id,notnull"`
InstanceID string `grove:"instance_id,notnull"`
Path string `grove:"path,notnull"`
Port int `grove:"port,notnull"`
Protocol string `grove:"protocol"`
Weight int `grove:"weight"`
StripPrefix bool `grove:"strip_prefix"`
CreatedAt time.Time `grove:"created_at,notnull"`
UpdatedAt time.Time `grove:"updated_at,notnull"`
ID string `grove:"id,pk"`
TenantID string `grove:"tenant_id,notnull"`
InstanceID string `grove:"instance_id,notnull"`
Path string `grove:"path,notnull"`
Port int `grove:"port,notnull"`
Protocol string `grove:"protocol"`
Weight int `grove:"weight"`
StripPrefix bool `grove:"strip_prefix"`

PathMode string `grove:"path_mode"`
RewriteRedirects bool `grove:"rewrite_redirects"`
RewriteCookiePath bool `grove:"rewrite_cookie_path"`
UpstreamOrigin string `grove:"upstream_origin"`
TLSVerify bool `grove:"tls_verify"`

CreatedAt time.Time `grove:"created_at,notnull"`
UpdatedAt time.Time `grove:"updated_at,notnull"`
}

// certificateModel is the database model for network.Certificate.
Expand Down Expand Up @@ -523,8 +530,15 @@ func toRouteModel(route *network.Route) *routeModel {
Protocol: route.Protocol,
Weight: route.Weight,
StripPrefix: route.StripPrefix,
CreatedAt: route.CreatedAt,
UpdatedAt: route.UpdatedAt,

PathMode: route.PathMode,
RewriteRedirects: route.RewriteRedirects,
RewriteCookiePath: route.RewriteCookiePath,
UpstreamOrigin: route.UpstreamOrigin,
TLSVerify: route.TLSVerify,

CreatedAt: route.CreatedAt,
UpdatedAt: route.UpdatedAt,
}
}

Expand Down
6 changes: 6 additions & 0 deletions store/postgres/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,12 @@ func fromRouteModel(m *routeModel) *network.Route {
Protocol: m.Protocol,
Weight: m.Weight,
StripPrefix: m.StripPrefix,

PathMode: m.PathMode,
RewriteRedirects: m.RewriteRedirects,
RewriteCookiePath: m.RewriteCookiePath,
UpstreamOrigin: m.UpstreamOrigin,
TLSVerify: m.TLSVerify,
}
}

Expand Down
57 changes: 57 additions & 0 deletions store/postgres/network_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package postgres

import (
"testing"

ctrlplane "github.com/xraph/ctrlplane"
"github.com/xraph/ctrlplane/id"
"github.com/xraph/ctrlplane/network"
)

// TestRouteModel_ProxyFieldsRoundTrip guards the VirtualGateway
// proxy-mode fields through the model⇄domain mapping. Before they were
// wired into toRouteModel/fromRouteModel, octopus would read zero values
// (no redirect rewrite, tls_verify implicitly off) after a store reload.
func TestRouteModel_ProxyFieldsRoundTrip(t *testing.T) {
t.Parallel()

in := &network.Route{
Entity: ctrlplane.NewEntity(id.PrefixRoute),
TenantID: "tenant-x",
InstanceID: id.New(id.PrefixInstance),
Path: "/twinos",
Port: 7900,
Protocol: "http",
Weight: 1,
StripPrefix: true,

PathMode: "strip",
RewriteRedirects: true,
RewriteCookiePath: true,
UpstreamOrigin: "https://x:443",
TLSVerify: false,
}

m := toRouteModel(in)
if m.PathMode != "strip" || !m.RewriteRedirects || !m.RewriteCookiePath ||
m.UpstreamOrigin != "https://x:443" || m.TLSVerify {
t.Fatalf("toRouteModel dropped a proxy field: %+v", m)
}

out := fromRouteModel(m)
if out.PathMode != in.PathMode {
t.Fatalf("PathMode round-trip: got %q want %q", out.PathMode, in.PathMode)
}
if out.RewriteRedirects != in.RewriteRedirects {
t.Fatalf("RewriteRedirects round-trip: got %v want %v", out.RewriteRedirects, in.RewriteRedirects)
}
if out.RewriteCookiePath != in.RewriteCookiePath {
t.Fatalf("RewriteCookiePath round-trip: got %v want %v", out.RewriteCookiePath, in.RewriteCookiePath)
}
if out.UpstreamOrigin != in.UpstreamOrigin {
t.Fatalf("UpstreamOrigin round-trip: got %q want %q", out.UpstreamOrigin, in.UpstreamOrigin)
}
if out.TLSVerify != in.TLSVerify {
t.Fatalf("TLSVerify round-trip: got %v want %v", out.TLSVerify, in.TLSVerify)
}
}
39 changes: 39 additions & 0 deletions store/sqlite/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,5 +687,44 @@ CREATE TABLE IF NOT EXISTS cp_bootstrap_workloads (
return err
},
},
// VirtualGateway proxy-mode fields on routes. Mirrors postgres
// migration 20240101000026. SQLite allows only ONE column per
// ALTER, so each ADD/DROP is its own statement. Booleans are
// INTEGER; tls_verify defaults 1 (TRUE). DROP COLUMN needs SQLite
// 3.35+ (Mar 2021), same as migration 20240101000017's Down.
&migrate.Migration{
Name: "add_proxy_fields_to_cp_routes",
Version: "20240101000020",
Up: func(ctx context.Context, exec migrate.Executor) error {
for _, stmt := range []string{
`ALTER TABLE cp_routes ADD COLUMN path_mode TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE cp_routes ADD COLUMN rewrite_redirects INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE cp_routes ADD COLUMN rewrite_cookie_path INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE cp_routes ADD COLUMN upstream_origin TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE cp_routes ADD COLUMN tls_verify INTEGER NOT NULL DEFAULT 1`,
} {
if _, err := exec.Exec(ctx, stmt); err != nil {
return err
}
}

return nil
},
Down: func(ctx context.Context, exec migrate.Executor) error {
for _, stmt := range []string{
`ALTER TABLE cp_routes DROP COLUMN path_mode`,
`ALTER TABLE cp_routes DROP COLUMN rewrite_redirects`,
`ALTER TABLE cp_routes DROP COLUMN rewrite_cookie_path`,
`ALTER TABLE cp_routes DROP COLUMN upstream_origin`,
`ALTER TABLE cp_routes DROP COLUMN tls_verify`,
} {
if _, err := exec.Exec(ctx, stmt); err != nil {
return err
}
}

return nil
},
},
)
}
44 changes: 32 additions & 12 deletions store/sqlite/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,23 @@ type domainModel struct {
type routeModel struct {
grove.BaseModel `grove:"table:cp_routes"`

ID string `grove:"id,pk"`
TenantID string `grove:"tenant_id,notnull"`
InstanceID string `grove:"instance_id,notnull"`
Path string `grove:"path,notnull"`
Port int `grove:"port,notnull"`
Protocol string `grove:"protocol"`
Weight int `grove:"weight"`
StripPrefix bool `grove:"strip_prefix"`
CreatedAt time.Time `grove:"created_at,notnull"`
UpdatedAt time.Time `grove:"updated_at,notnull"`
ID string `grove:"id,pk"`
TenantID string `grove:"tenant_id,notnull"`
InstanceID string `grove:"instance_id,notnull"`
Path string `grove:"path,notnull"`
Port int `grove:"port,notnull"`
Protocol string `grove:"protocol"`
Weight int `grove:"weight"`
StripPrefix bool `grove:"strip_prefix"`

PathMode string `grove:"path_mode"`
RewriteRedirects bool `grove:"rewrite_redirects"`
RewriteCookiePath bool `grove:"rewrite_cookie_path"`
UpstreamOrigin string `grove:"upstream_origin"`
TLSVerify bool `grove:"tls_verify"`

CreatedAt time.Time `grove:"created_at,notnull"`
UpdatedAt time.Time `grove:"updated_at,notnull"`
}

// certificateModel is the database model for network.Certificate.
Expand Down Expand Up @@ -534,8 +541,15 @@ func toRouteModel(route *network.Route) *routeModel {
Protocol: route.Protocol,
Weight: route.Weight,
StripPrefix: route.StripPrefix,
CreatedAt: route.CreatedAt,
UpdatedAt: route.UpdatedAt,

PathMode: route.PathMode,
RewriteRedirects: route.RewriteRedirects,
RewriteCookiePath: route.RewriteCookiePath,
UpstreamOrigin: route.UpstreamOrigin,
TLSVerify: route.TLSVerify,

CreatedAt: route.CreatedAt,
UpdatedAt: route.UpdatedAt,
}
}

Expand All @@ -553,6 +567,12 @@ func fromRouteModel(m *routeModel) *network.Route {
Protocol: m.Protocol,
Weight: m.Weight,
StripPrefix: m.StripPrefix,

PathMode: m.PathMode,
RewriteRedirects: m.RewriteRedirects,
RewriteCookiePath: m.RewriteCookiePath,
UpstreamOrigin: m.UpstreamOrigin,
TLSVerify: m.TLSVerify,
}
}

Expand Down
Loading