feat(extensions)!: normalize protocol negotiation (#3352)

* feat(extensions)!: normalize protocol negotiation

Closes #3057

Introduce a shared extension handshake, enforce protocol and capability compatibility across extension families, and expose immutable negotiated snapshots through gateway info and the Go SDK.

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* fix(credentials): fail fast on negotiation errors

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* fix(extensions): validate gateway handshake metadata

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* fix(go-sdk): re-export extension kind constants

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* fix(extensions): fail fast on credential handshake rejection

Signed-off-by: Seth Jennings <sjenning@redhat.com>

---------

Signed-off-by: Seth Jennings <sjenning@redhat.com>
This commit is contained in:
Seth Jennings
2026-09-21 13:19:39 -07:00
committed by GitHub
parent 251f77e2b8
commit 2493d415c2
62 changed files with 3651 additions and 1650 deletions
+8
View File
@@ -89,6 +89,14 @@ func copyGatewayInfo(info *types.GatewayInfo) *types.GatewayInfo {
cp.ComputeDrivers = make([]types.ComputeDriverInfo, len(info.ComputeDrivers))
copy(cp.ComputeDrivers, info.ComputeDrivers)
}
if info.Extensions != nil {
cp.Extensions = make([]types.ExtensionInfo, len(info.Extensions))
for index, extension := range info.Extensions {
cp.Extensions[index] = extension
cp.Extensions[index].SupportedCapabilities = copyStringSlice(extension.SupportedCapabilities)
cp.Extensions[index].RequiredCapabilities = copyStringSlice(extension.RequiredCapabilities)
}
}
return &cp
}
+8
View File
@@ -84,13 +84,21 @@ func TestHealth_GetGatewayInfo_DeepCopy(t *testing.T) {
ComputeDrivers: []types.ComputeDriverInfo{
{Name: "k8s"},
},
Extensions: []types.ExtensionInfo{{
ConfiguredName: "k8s",
SupportedCapabilities: []string{"openshell.compute.contract"},
}},
}))
info1, _ := fc.Health().GetGatewayInfo(context.Background())
info1.ComputeDrivers[0].Name = "mutated"
info1.Extensions[0].ConfiguredName = "mutated"
info1.Extensions[0].SupportedCapabilities[0] = "mutated"
info2, _ := fc.Health().GetGatewayInfo(context.Background())
assert.Equal(t, "k8s", info2.ComputeDrivers[0].Name)
assert.Equal(t, "k8s", info2.Extensions[0].ConfiguredName)
assert.Equal(t, "openshell.compute.contract", info2.Extensions[0].SupportedCapabilities[0])
}
func TestHealth_GetCurrentUser_Default(t *testing.T) {
+15
View File
@@ -18,6 +18,21 @@ type GatewayInfo = types.GatewayInfo
// ComputeDriverInfo describes a compute backend available on the gateway.
type ComputeDriverInfo = types.ComputeDriverInfo
// ExtensionInfo describes one successful gateway/extension negotiation.
type ExtensionInfo = types.ExtensionInfo
// ExtensionKind identifies one supported extension family.
type ExtensionKind = types.ExtensionKind
// ExtensionKind constants re-exported from the types package.
const (
ExtensionKindComputeDriver = types.ExtensionKindComputeDriver
ExtensionKindCredentialDriver = types.ExtensionKindCredentialDriver
ExtensionKindGatewayInterceptor = types.ExtensionKindGatewayInterceptor
ExtensionKindSupervisorMiddleware = types.ExtensionKindSupervisorMiddleware
ExtensionKindUnknown = types.ExtensionKindUnknown
)
// ServiceStatus describes the health state of the gateway.
type ServiceStatus = types.ServiceStatus
@@ -31,6 +31,14 @@ type mockHealthServer struct {
currentUserErr error
}
func TestExtensionKindConstantsAreReexported(t *testing.T) {
assert.Equal(t, ExtensionKind("ComputeDriver"), ExtensionKindComputeDriver)
assert.Equal(t, ExtensionKind("CredentialDriver"), ExtensionKindCredentialDriver)
assert.Equal(t, ExtensionKind("GatewayInterceptor"), ExtensionKindGatewayInterceptor)
assert.Equal(t, ExtensionKind("SupervisorMiddleware"), ExtensionKindSupervisorMiddleware)
assert.Equal(t, ExtensionKind("Unknown"), ExtensionKindUnknown)
}
func (s *mockHealthServer) Health(_ context.Context, _ *pb.HealthRequest) (*pb.HealthResponse, error) {
if s.err != nil {
return nil, s.err
@@ -18,11 +18,46 @@ func GatewayInfoFromProto(resp *pb.GetGatewayInfoResponse) *types.GatewayInfo {
for _, d := range resp.GetComputeDrivers() {
drivers = append(drivers, ComputeDriverInfoFromProto(d))
}
extensions := make([]types.ExtensionInfo, 0, len(resp.GetExtensions()))
for _, extension := range resp.GetExtensions() {
extensions = append(extensions, ExtensionInfoFromProto(extension))
}
return &types.GatewayInfo{
Status: ServiceStatusFromProto(resp.GetStatus()),
Version: resp.GetGatewayVersion(),
ComputeDrivers: drivers,
Extensions: extensions,
}
}
// ExtensionInfoFromProto converts a negotiated extension snapshot.
func ExtensionInfoFromProto(extension *pb.NegotiatedExtensionInfo) types.ExtensionInfo {
return types.ExtensionInfo{
Kind: ExtensionKindFromProto(extension.GetKind()),
ConfiguredName: extension.GetConfiguredName(),
ImplementationName: extension.GetImplementationName(),
ImplementationVersion: extension.GetImplementationVersion(),
ProtocolMajor: extension.GetProtocolMajor(),
ProtocolMinor: extension.GetProtocolMinor(),
SupportedCapabilities: CopyStringSlice(extension.GetSupportedCapabilities()),
RequiredCapabilities: CopyStringSlice(extension.GetRequiredCapabilities()),
}
}
// ExtensionKindFromProto converts the public extension family enum.
func ExtensionKindFromProto(kind pb.ExtensionKind) types.ExtensionKind {
switch kind {
case pb.ExtensionKind_EXTENSION_KIND_COMPUTE_DRIVER:
return types.ExtensionKindComputeDriver
case pb.ExtensionKind_EXTENSION_KIND_CREDENTIAL_DRIVER:
return types.ExtensionKindCredentialDriver
case pb.ExtensionKind_EXTENSION_KIND_GATEWAY_INTERCEPTOR:
return types.ExtensionKindGatewayInterceptor
case pb.ExtensionKind_EXTENSION_KIND_SUPERVISOR_MIDDLEWARE:
return types.ExtensionKindSupervisorMiddleware
default:
return types.ExtensionKindUnknown
}
}
@@ -32,6 +32,17 @@ func TestGatewayInfoFromProto(t *testing.T) {
},
},
},
Extensions: []*pb.NegotiatedExtensionInfo{
{
Kind: pb.ExtensionKind_EXTENSION_KIND_COMPUTE_DRIVER,
ConfiguredName: "k8s",
ImplementationName: "openshell/kubernetes",
ImplementationVersion: "1.5.0",
ProtocolMajor: 1,
SupportedCapabilities: []string{"openshell.compute.contract"},
RequiredCapabilities: []string{"openshell.compute.contract"},
},
},
}
info := GatewayInfoFromProto(proto)
@@ -45,6 +56,10 @@ func TestGatewayInfoFromProto(t *testing.T) {
assert.Equal(t, "2.1.0", info.ComputeDrivers[0].DriverVersion)
assert.Equal(t, "docker", info.ComputeDrivers[1].Name)
assert.Equal(t, "docker-engine", info.ComputeDrivers[1].DriverName)
require.Len(t, info.Extensions, 1)
assert.Equal(t, v1.ExtensionKindComputeDriver, info.Extensions[0].Kind)
assert.Equal(t, "openshell/kubernetes", info.Extensions[0].ImplementationName)
assert.Equal(t, uint32(1), info.Extensions[0].ProtocolMajor)
}
func TestGatewayInfoFromProto_NoDrivers(t *testing.T) {
@@ -72,12 +87,20 @@ func TestGatewayInfoFromProto_DeepCopy(t *testing.T) {
ComputeDrivers: []*pb.ComputeDriverInfo{
{Name: "k8s", Capabilities: &pb.ComputeDriverCapabilities{DriverName: "kubernetes"}},
},
Extensions: []*pb.NegotiatedExtensionInfo{{
ConfiguredName: "k8s",
SupportedCapabilities: []string{"openshell.compute.contract"},
}},
}
info := GatewayInfoFromProto(proto)
proto.ComputeDrivers[0].Name = "mutated"
proto.Extensions[0].ConfiguredName = "mutated"
proto.Extensions[0].SupportedCapabilities[0] = "mutated"
assert.Equal(t, "k8s", info.ComputeDrivers[0].Name)
assert.Equal(t, "k8s", info.Extensions[0].ConfiguredName)
assert.Equal(t, "openshell.compute.contract", info.Extensions[0].SupportedCapabilities[0])
}
func TestServiceStatusFromProto(t *testing.T) {
+25
View File
@@ -25,6 +25,31 @@ type GatewayInfo struct {
Status ServiceStatus
Version string
ComputeDrivers []ComputeDriverInfo
Extensions []ExtensionInfo
}
// ExtensionKind identifies one supported extension family.
type ExtensionKind string
// Extension kind constants.
const (
ExtensionKindComputeDriver ExtensionKind = "ComputeDriver"
ExtensionKindCredentialDriver ExtensionKind = "CredentialDriver"
ExtensionKindGatewayInterceptor ExtensionKind = "GatewayInterceptor"
ExtensionKindSupervisorMiddleware ExtensionKind = "SupervisorMiddleware"
ExtensionKindUnknown ExtensionKind = "Unknown"
)
// ExtensionInfo describes one successful gateway/extension negotiation.
type ExtensionInfo struct {
Kind ExtensionKind
ConfiguredName string
ImplementationName string
ImplementationVersion string
ProtocolMajor uint32
ProtocolMinor uint32
SupportedCapabilities []string
RequiredCapabilities []string
}
// ComputeDriverInfo describes a compute backend available on the gateway.
File diff suppressed because it is too large Load Diff