Merge pull request #54003 from vvoland/update-ext
vendor: github.com/moby/extensions d2819d88bcbe
This commit is contained in:
15 files changed
+964
-499
No files matched your search
@@ -66,7 +66,7 @@ require (
|
||||
github.com/mitchellh/copystructure v1.2.0
|
||||
github.com/moby/buildkit v0.34.1
|
||||
github.com/moby/docker-image-spec v1.3.1
|
||||
github.com/moby/extensions v0.0.0-20260918151408-e4c23d866037
|
||||
github.com/moby/extensions v0.0.0-20261020100352-d2819d88bcbe
|
||||
github.com/moby/go-archive v0.3.3
|
||||
github.com/moby/ipvs v1.1.0
|
||||
github.com/moby/locker v1.0.1
|
||||
|
||||
@@ -471,8 +471,8 @@ github.com/moby/buildkit v0.34.1 h1:jVdxsXX0UpU0q1Et+9lFezPmh9sGE6vF0TrKZaGlfy4=
|
||||
github.com/moby/buildkit v0.34.1/go.mod h1:e+Za70y2a3nL1U2Y0P8TvhPyKp2Ok2NS+Q4siSArDiM=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/extensions v0.0.0-20260918151408-e4c23d866037 h1:i1M45d/grN16YYOstJ1aGWgo4QOnItgAAa/X4z2+Y9Q=
|
||||
github.com/moby/extensions v0.0.0-20260918151408-e4c23d866037/go.mod h1:4craSfD3oQ+965ywyz5AzG/Eq3j0lflhEB+GTdRmyyU=
|
||||
github.com/moby/extensions v0.0.0-20261020100352-d2819d88bcbe h1:wHRo58MA+vgX2sXtL4uT/NcSYPiyJarqHZ4aDS8P0ys=
|
||||
github.com/moby/extensions v0.0.0-20261020100352-d2819d88bcbe/go.mod h1:5OTGReLaEAAQ0XoeIQzL6Ep+J3kwlFVTRSP86RwVoso=
|
||||
github.com/moby/go-archive v0.3.3 h1:OxxR9paxsluYi+zDUEXTTaIxtkK3viymW+Ka7vRhhME=
|
||||
github.com/moby/go-archive v0.3.3/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE=
|
||||
github.com/moby/ipvs v1.1.0 h1:ONN4pGaZQgAx+1Scz5RvWV4Q7Gb+mvfRh3NsPS+1XQQ=
|
||||
|
||||
+112
-328
@@ -1,378 +1,162 @@
|
||||
# Extensions
|
||||
|
||||
> [!WARNING]
|
||||
> ⚠️ This project is a work in progress.
|
||||
> This project is still a work in progress.
|
||||
|
||||
Moby extensions are one model for adding engine behavior.
|
||||
They replace the older per-feature plugin systems for network, volume, and log drivers.
|
||||
Moby extensions provide a common model for extending engine behavior.
|
||||
They are intended to replace the separate plugin systems currently used by network, volume, and logging drivers.
|
||||
|
||||
## Mental model
|
||||
An extension can run inside the Host process or as a separate process.
|
||||
In both cases, it implements the same Go interface.
|
||||
|
||||
- A **Point** is a versioned, namespaced Go interface and its message types.
|
||||
- A daemon- or Host-owned capability is an ordinary Point too. Its in-process
|
||||
implementation is registered through an extension `Declaration`, rather than
|
||||
through a separate hook or plugin mechanism.
|
||||
- An extension declares the Points it provides and the typed dependencies it
|
||||
needs. A consumer declares `Point.Dependency()` and resolves the typed Go
|
||||
interface during `Declaration.Init`.
|
||||
- A Point contract is transport-neutral. The same interface and messages work
|
||||
for a direct in-process call or for a launched process over gRPC.
|
||||
- Placement is assembled with generated wiring: `ServerPoint` serves an
|
||||
ordinary Point and `ClientPoint` presents the same typed interface over a
|
||||
launched-provider or dependency-callback connection.
|
||||
- The Host registers the fixed extension set, initializes it in dependency order,
|
||||
and shuts it down in reverse dependency order. Importing a package does not
|
||||
activate an extension.
|
||||
See the [authoring guide](./docs/AUTHORING.md) for examples and setup instructions.
|
||||
For details about the architecture, wire protocol, current limitations, and discovery security, see the [design guide](./docs/DESIGN.md).
|
||||
|
||||
## Principles
|
||||
## How it works
|
||||
|
||||
- **Everything is a Point.**
|
||||
Host capabilities, engine hooks, and extension-to-extension calls use
|
||||
ordinary Point contracts and the same provider and resolution model.
|
||||
- **Dependencies are typed and explicit.**
|
||||
`Point.Dependency()` controls presence and initialization order;
|
||||
`Point.Single`, `Point.All`, and `Point.ByExtension` perform typed resolution.
|
||||
A dependency declaration does not inject a field automatically.
|
||||
- **Contracts do not choose placement.**
|
||||
Direct Go and gRPC are two ways to place the same Point. Generated
|
||||
`ServerPoint` and `ClientPoint` wiring makes that choice without changing the
|
||||
contract.
|
||||
- **The Host owns lifecycle.**
|
||||
Provider extensions must finish their `Init` work before returning. The Host
|
||||
then initializes dependents and later shuts them down in reverse dependency
|
||||
order.
|
||||
- **No private activation paths.**
|
||||
Consumers keep Point interfaces, not concrete Host or daemon pointers, and
|
||||
the Host lifecycle replaces later manual `Activate` calls.
|
||||
- **Publication is opt-in.**
|
||||
An ordinary Point is externally reachable only when it is offered, Host
|
||||
policy allows publication, and generated server wiring is supplied.
|
||||
Published services enforce their own access control.
|
||||
- **Fan-out order is not a contract.**
|
||||
A Point that needs ordering defines its own phases or ordering rules.
|
||||
- **Legacy plugins are replaced.**
|
||||
Network, volume, and log drivers become ordinary extension Points rather than
|
||||
separate plugin systems.
|
||||
Extensions are built around Points.
|
||||
A Point is an interface that describes a capability, such as running a workload.
|
||||
Providers implement Points, and consumers call them.
|
||||
The Host registers these components, connects consumers to providers, and manages their lifecycle.
|
||||
|
||||
## Authoring a host capability
|
||||
For example, a daemon can expose its workload runtime as a Point.
|
||||
An extension can then use that capability without depending on the daemon's internal runtime implementation.
|
||||
The same mechanism is used for daemon-owned capabilities and communication between extensions.
|
||||
|
||||
A Host capability is an ordinary Point owned by the daemon or application. The
|
||||
contract, daemon-owned provider, and consumer can live in separate packages.
|
||||
Imports are omitted from these examples. `workloadv0` is the handwritten
|
||||
contract package. After generation, import its generated `protogen` package as
|
||||
`workloadpb`; it provides `workloadpb.ServerPoint` and
|
||||
`workloadpb.ClientPoint`.
|
||||
To add an extension:
|
||||
|
||||
The contract package defines one deciding Point:
|
||||
1. **Define the contract.**
|
||||
|
||||
```go
|
||||
// Package workloadv0 defines the daemon workload runtime Point.
|
||||
package workloadv0
|
||||
A Point assigns a namespaced, versioned identity to a Go interface and its message types.
|
||||
|
||||
type Runtime interface {
|
||||
Run(context.Context, *Request) (*Response, error)
|
||||
}
|
||||
2. **Declare what the extension provides and depends on.**
|
||||
|
||||
type Request struct {
|
||||
Name string `pb:"1"`
|
||||
}
|
||||
Providers implement Points.
|
||||
Consumers declare their dependencies using `Point.Dependency()`.
|
||||
|
||||
type Response struct {
|
||||
Message string `pb:"1"`
|
||||
}
|
||||
3. **Register the extension with the Host.**
|
||||
|
||||
var Point = extensions.DefineSinglePoint[Runtime](
|
||||
"org.example.daemon.workload.v0",
|
||||
)
|
||||
```
|
||||
The daemon or application decides which extensions to load and supplies any generated wiring required for cross-process calls.
|
||||
Merely importing an extension package does not activate it.
|
||||
|
||||
Keep the contract expressible by the generated wire representation. Do not put
|
||||
concrete Host types, internal backend structs, function callbacks, or channels
|
||||
in a Point interface or its messages.
|
||||
4. **Resolve dependencies during initialization.**
|
||||
|
||||
The daemon package owns the provider and registers it through the contract
|
||||
Point:
|
||||
In `Declaration.Init`, consumers resolve their declared dependencies using `Point.Single`, `Point.All`, or `Point.ByExtension`.
|
||||
The resolved implementation is not automatically assigned to a field.
|
||||
|
||||
```go
|
||||
type runtime struct{}
|
||||
5. **Call the Point interface.**
|
||||
|
||||
func (runtime) Run(
|
||||
_ context.Context,
|
||||
req *workloadv0.Request,
|
||||
) (*workloadv0.Response, error) {
|
||||
return &workloadv0.Response{Message: "ran " + req.Name}, nil
|
||||
}
|
||||
Consumers retain and call the resolved interface rather than a concrete Host or daemon type.
|
||||
Calls go directly to an in-process provider or through generated gRPC code when the provider runs in another process.
|
||||
|
||||
var runtimeExtension extensions.Extension = extensions.New(extensions.Declaration{
|
||||
ID: "org.example.daemon-runtime.v1",
|
||||
Providers: []extensions.Provider{
|
||||
workloadv0.Point.Provide(runtime{}),
|
||||
},
|
||||
})
|
||||
```
|
||||
The Host initializes providers before their consumers and shuts them down in the opposite order.
|
||||
A provider must be ready for use when its `Init` method returns.
|
||||
|
||||
The consumer package keeps the resolved interface on its stateful extension:
|
||||
Keep the Host returned by `host.New` and call `Shutdown` when the application exits so that lifecycle resources are released.
|
||||
|
||||
```go
|
||||
type consumer struct {
|
||||
runtime workloadv0.Runtime
|
||||
}
|
||||
Breaking changes require a new Point version.
|
||||
The `.v0` suffix identifies an experimental contract.
|
||||
For fan-out calls, provider order is unspecified unless the Point defines its own ordering rules.
|
||||
|
||||
func (c *consumer) Declaration() extensions.Declaration {
|
||||
return extensions.Declaration{
|
||||
ID: "org.example.workload-consumer.v1",
|
||||
Dependencies: []extensions.Dependency{
|
||||
workloadv0.Point.Dependency(),
|
||||
},
|
||||
Init: func(
|
||||
_ context.Context,
|
||||
_ extensions.Config,
|
||||
resolver extensions.Resolver,
|
||||
) error {
|
||||
runtime, err := workloadv0.Point.Single(resolver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.runtime = runtime
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
## Running extensions
|
||||
|
||||
In-process composition uses both extensions. Retain the returned Host and call
|
||||
`Shutdown` so it owns and releases lifecycle resources:
|
||||
A Point defines an interface, not where its implementation runs.
|
||||
In-process consumers call the Go implementation directly.
|
||||
Cross-process consumers use generated gRPC wiring.
|
||||
|
||||
```go
|
||||
h, err := host.New(ctx, host.WithExtensions(runtimeExtension, &consumer{}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer h.Shutdown(context.Background())
|
||||
```
|
||||
Because the same contract may cross a process boundary, Point interfaces and messages should not contain Host types, internal backend structures, callbacks, or channels.
|
||||
|
||||
For a launched consumer, the Host registers the daemon-owned provider and
|
||||
serves it on the dependency callback:
|
||||
The `mobyextgen` generator creates the required wire code:
|
||||
|
||||
```go
|
||||
h, err := host.New(ctx,
|
||||
host.WithRuntimeDir(runtimeDir),
|
||||
host.WithDirs(extensionDir),
|
||||
host.WithExtensions(runtimeExtension),
|
||||
host.WithDependencyProviders(workloadpb.ServerPoint),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer h.Shutdown(context.Background())
|
||||
```
|
||||
* `ServerPoint` exposes a Point implementation over gRPC.
|
||||
* `ClientPoint` presents the remote implementation through the same typed Go interface.
|
||||
|
||||
The consumer process has no ordinary Point of its own, so it registers without
|
||||
a `ServerPoint`, installs the generated client wiring, and listens:
|
||||
The exact setup depends on where the consumer and provider run:
|
||||
|
||||
```go
|
||||
func run(ctx context.Context) error {
|
||||
srv := sdk.NewServer()
|
||||
if err := srv.Register(&consumer{}); err != nil {
|
||||
return err
|
||||
}
|
||||
srv.Depends(workloadpb.ClientPoint)
|
||||
return srv.Listen(ctx)
|
||||
}
|
||||
```
|
||||
* Register in-process extensions with `host.WithExtensions`.
|
||||
* When the Host calls a provider in a launched process, configure the Host with `host.WithClientProviders` and the generated `ClientPoint`.
|
||||
The provider registers its generated `ServerPoint` with the SDK.
|
||||
* When a launched consumer calls a Host provider, configure the Host with `host.WithDependencyProviders` and the generated `ServerPoint`.
|
||||
In the consumer, pass the generated `ClientPoint` to `Server.Depends`.
|
||||
|
||||
`Declaration.Dependencies`—here populated by `workloadv0.Point.Dependency()`—is
|
||||
the logical dependency. `WithDependencyProviders` does not declare a dependency
|
||||
or create a provider; it exposes an already registered effective provider over
|
||||
the callback socket. `Depends` does not declare a dependency or create a
|
||||
provider either; it installs the generated client wiring used to resolve that
|
||||
declared dependency. A process that also provides ordinary Points passes every
|
||||
corresponding generated `ServerPoint` to `Register`.
|
||||
The dependency callback exposes one effective provider for each Point. The SDK
|
||||
closes its callback connection before the launched extension's `Shutdown`, so
|
||||
do not use a retained callback dependency during shutdown.
|
||||
The last case exposes an existing Host provider to the launched process.
|
||||
The consumer must still declare the dependency itself.
|
||||
|
||||
### Initialization and readiness
|
||||
A dependency callback currently exposes one effective provider for each Point.
|
||||
Do not retain and call that dependency during `Shutdown`, because the SDK closes the connection before shutdown begins.
|
||||
|
||||
The extension providing a capability must establish the readiness it promises
|
||||
before its `Init` returns. Because the consumer declares
|
||||
`workloadv0.Point.Dependency()`, dependency ordering makes the provider's
|
||||
`Init` finish before the consumer's `Init` begins. A successful `host.New`
|
||||
means that all extension initializers returned successfully.
|
||||
For launched processes, the `ready\n` handshake only confirms that the listener is ready.
|
||||
It does not mean that the extension's `Init` method has completed.
|
||||
|
||||
For a launched process, the `ready\n` line means only process
|
||||
listener/runtime-handshake readiness: the SDK has bound its extension socket
|
||||
and registered its runtime service. It does not mean that the extension's
|
||||
`Init` has completed. Do not retain concrete Host or daemon pointers as
|
||||
capability dependencies, and do not manually call `Activate` after Host
|
||||
startup; declare a Point dependency and perform setup in `Init` instead.
|
||||
See the [authoring guide](./docs/AUTHORING.md) for complete examples covering code generation, SDK registration, dependency resolution, and deployment.
|
||||
|
||||
## Ordinary Point publication
|
||||
## Publishing services
|
||||
|
||||
Publication uses the same ordinary Point contract; there is no separate service
|
||||
contract. This example defines a `greeterv0` contract:
|
||||
Implementing a Point makes it available within the extension system, but does not automatically expose it to external callers.
|
||||
|
||||
```go
|
||||
// Package greeterv0 defines the published Greeter Point contract.
|
||||
package greeterv0
|
||||
Publishing a Point requires three things:
|
||||
|
||||
type Greeter interface {
|
||||
Greet(context.Context, *HelloRequest) (*HelloReply, error)
|
||||
}
|
||||
* The extension offers it using `servicev0.Offer`.
|
||||
* Host policy permits it through `host.WithProviderPolicy`.
|
||||
* The generated server wiring is registered.
|
||||
|
||||
type HelloRequest struct {
|
||||
Name string `pb:"1"`
|
||||
}
|
||||
For an in-process provider, supply the server wiring through `host.WithPointServers`.
|
||||
A provider running in a separate process registers its `ServerPoint` with the SDK.
|
||||
|
||||
type HelloReply struct {
|
||||
Message string `pb:"1"`
|
||||
}
|
||||
An offer is only a request to publish a Point; it does not grant access or register the transport.
|
||||
If no provider policy is configured, external publication is disabled, while providers remain available to internal consumers.
|
||||
|
||||
var Point = extensions.DefinePoint[Greeter](
|
||||
"org.mobyproject.extension.example.greeter.v0",
|
||||
)
|
||||
```
|
||||
Published services are responsible for enforcing their own access control.
|
||||
|
||||
`mobyextgen` infers the fully qualified service name as
|
||||
`<PointID>.<InterfaceName>`, here
|
||||
`org.mobyproject.extension.example.greeter.v0.Greeter`. The interface name is
|
||||
part of the wire identity, so keep it stable within a Point version. After
|
||||
generation, import the generated `protogen` package as `greeterpb`. It provides
|
||||
`ServerPoint` to serve the ordinary Point, `ClientPoint` to construct a typed
|
||||
provider over a launched connection, and `NewClient(conn)` to construct the
|
||||
handwritten Go interface over a Host connection.
|
||||
|
||||
The extension implements the ordinary Point and separately offers it:
|
||||
|
||||
```go
|
||||
type greeter struct{}
|
||||
|
||||
func (greeter) Greet(
|
||||
_ context.Context,
|
||||
req *greeterv0.HelloRequest,
|
||||
) (*greeterv0.HelloReply, error) {
|
||||
return &greeterv0.HelloReply{Message: "hello " + req.Name}, nil
|
||||
}
|
||||
|
||||
var extension = extensions.New(extensions.Declaration{
|
||||
ID: "org.example.greeter.v1",
|
||||
Providers: []extensions.Provider{
|
||||
greeterv0.Point.Provide(greeter{}),
|
||||
servicev0.Offer(greeterv0.Point),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
An offer can select a subset of several implemented Points:
|
||||
|
||||
```go
|
||||
Providers: []extensions.Provider{
|
||||
foov1.Point.Provide(fooImpl{}),
|
||||
barv1.Point.Provide(barImpl{}),
|
||||
servicev0.Offer(foov1.Point),
|
||||
}
|
||||
```
|
||||
|
||||
`Offer` is eligibility, not authorization or transport registration. It does
|
||||
not select gRPC or register a service. The service metadata Point carries the
|
||||
offer; Host policy makes the publication decision for the service generated
|
||||
from the ordinary Point.
|
||||
|
||||
The policy receives the Host-attested extension identity and requested Point.
|
||||
For example, an in-process Host can allow this extension's ordinary Point and
|
||||
its publication metadata Point:
|
||||
|
||||
```go
|
||||
h, err := host.New(ctx,
|
||||
host.WithExtensions(extension),
|
||||
host.WithProviderPolicy(host.PointPolicyFunc(
|
||||
func(
|
||||
identity extensions.ExtensionIdentity,
|
||||
point extensions.PointID,
|
||||
) host.PointPolicyResult {
|
||||
allowedPoint := point == greeterv0.Point.ID() ||
|
||||
point == servicev0.Point.ID()
|
||||
if identity.ID == "org.example.greeter.v1" && allowedPoint {
|
||||
return host.Allow()
|
||||
}
|
||||
return host.Drop()
|
||||
},
|
||||
)),
|
||||
host.WithPointServers(greeterpb.ServerPoint),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer h.Shutdown(context.Background())
|
||||
```
|
||||
|
||||
A nil policy preserves all internally wired providers but drops publication.
|
||||
`host.Allow()` keeps the requested ordinary provider or publishes the offered
|
||||
set. `host.Drop()` silently omits an ordinary provider without unloading its
|
||||
extension or leaves the offered set private. `host.Reject(cause)` fails Host
|
||||
construction while preserving its error cause. The zero value of
|
||||
`host.PointPolicyResult` is invalid or unspecified and rejects the request. A
|
||||
typed-nil `host.PointPolicyFunc(nil)` is still a policy value and rejects the
|
||||
request as well. For an in-process offer, allow the ordinary Point ID as well
|
||||
as `servicev0.Point.ID()` so provider admission and publication both succeed;
|
||||
an offered-only process consults policy only with the service metadata Point
|
||||
ID.
|
||||
|
||||
For a separate binary, pass every ordinary provider's generated `ServerPoint`
|
||||
through the SDK:
|
||||
|
||||
```go
|
||||
sdk.Main(extension, sdk.WithServerPoints(greeterpb.ServerPoint))
|
||||
```
|
||||
|
||||
An in-process provider has no private SDK server, so the Host needs
|
||||
`host.WithPointServers(greeterpb.ServerPoint)` to translate requests into calls
|
||||
on the Go implementation. Use
|
||||
`host.WithClientProviders(greeterpb.ClientPoint)` only when the Host itself
|
||||
or another in-process consumer needs to call a launched provider. That is the
|
||||
opposite direction from a dependency callback: `WithClientProviders` wires
|
||||
Host-to-process calls, while `WithDependencyProviders` and `Depends` wire a
|
||||
launched consumer's calls back to an already registered Host provider.
|
||||
|
||||
External callers use the generated handwritten client over the Host connection:
|
||||
|
||||
```go
|
||||
client := greeterpb.NewClient(hostConn)
|
||||
reply, err := client.Greet(ctx, &greeterv0.HelloRequest{Name: "world"})
|
||||
```
|
||||
|
||||
The current backend is gRPC. Generated service registration records the fully
|
||||
qualified service name, and the proxy forwards the raw gRPC stream, including
|
||||
metadata and status. Arbitrary raw gRPC publication is not part of the
|
||||
extension API.
|
||||
See the [publication examples](./docs/AUTHORING.md#publishing-an-ordinary-point) for complete configurations and generated client usage.
|
||||
|
||||
## Glossary
|
||||
|
||||
| Term | Definition |
|
||||
|---|---|
|
||||
| **Point** | A versioned, namespaced, transport-neutral Go interface and message contract. It may be provided by an in-process or separate-process extension, including a Host capability wrapper; a breaking change gets a new Point version, and `.v0` is experimental. |
|
||||
| **Host capability** | A capability owned by a daemon or application and exposed as an ordinary Point through an in-process extension declaration. |
|
||||
| **Host** | The framework component embedded in or configured by a daemon/application. It registers extensions, resolves Points, assembles generated wiring, and manages lifecycle; it is not synonymous with the daemon/application. |
|
||||
| **Extension** | A deployable unit that runs in-process with the Host or in a separate process, and declares providers, dependencies, conflicts, and lifecycle callbacks. |
|
||||
| **Extension identity** | The logical extension ID plus the Host-attested origin (`builtin` or `executable`). The extension declares the logical ID; the Host validates it and supplies the origin. |
|
||||
| **Provider** | An implementation of a Point declared by an extension. It has no separate ID, is associated with the declaring extension's Host-attested identity, and each extension may declare at most one implementation per Point. |
|
||||
| **Consumer / dependent** | A daemon/application flow or extension that resolves and calls a Point. An extension dependent declares the Point or extension dependency that controls initialization order. |
|
||||
| **Dependency** | A declared need for a Point provider or extension, resolved before the dependent initializes. |
|
||||
| **ClientPoint** | Generated wiring for an ordinary Point. On the Host side, it turns a launched provider connection into the typed interface used by Host consumers; on the process side, it turns the dependency-callback connection into the typed interface used by a launched consumer. |
|
||||
| **ServerPoint** | Generated registration that serves an ordinary Point from an SDK server, a dependency callback, or an in-process publication adapter. |
|
||||
| **Offer** | Extension metadata marking selected ordinary Points as eligible for external publication; Host policy still makes the publication decision. |
|
||||
| **Publication** | Host-controlled exposure of an offered Point's generated service to external callers. |
|
||||
| **Dependency callback** | The Host gRPC server through which a launched consumer calls an already registered Host provider. |
|
||||
| **Broker** | The Host component that registers extensions, resolves dependencies, initializes them, and shuts them down. |
|
||||
| **Adapter** | Generated code that makes an out-of-process gRPC provider look like the same in-process Go interface to consumers. |
|
||||
| **Engine** | The daemon or application that embeds or configures a Host and runs the flows that consume and provide Points. |
|
||||
| **Legacy plugin** | The older Moby plugin system being replaced, or containerd plugins when discussed as prior art. The new unit is an extension. |
|
||||
### Point
|
||||
|
||||
## Start here
|
||||
A named and versioned Go interface, together with its message types.
|
||||
It describes a capability independently of its implementation or location.
|
||||
|
||||
- The mental model, principles, authoring flow, and glossary provide the short normative rules and definitions.
|
||||
- [DESIGN.md](./docs/DESIGN.md) - current behavior, constraints, wire protocol, and discovery security.
|
||||
- [AUTHORING.md](./docs/AUTHORING.md) - procedures, commands, code, and checklists.
|
||||
### Extension
|
||||
|
||||
A component that can provide Points, depend on other Points or extensions, and perform initialization and shutdown work.
|
||||
It may run inside the Host or in a separate process.
|
||||
|
||||
### Host
|
||||
|
||||
The framework component embedded in a daemon or application.
|
||||
It registers extensions, resolves their dependencies, and manages their lifecycle.
|
||||
|
||||
### Provider
|
||||
|
||||
An extension that implements a Point.
|
||||
|
||||
### Consumer
|
||||
|
||||
Code that resolves and calls a Point.
|
||||
An extension may be both a consumer and a provider.
|
||||
|
||||
### Dependency
|
||||
|
||||
A declared requirement for a Point provider or another extension.
|
||||
The Host uses dependencies to verify that requirements are available and to determine initialization order.
|
||||
|
||||
### Host capability
|
||||
|
||||
Functionality owned by the daemon or application and exposed as a Point through an in-process extension.
|
||||
|
||||
### ClientPoint / ServerPoint
|
||||
|
||||
Generated code used to call a Point across a process boundary.
|
||||
`ClientPoint` presents the Point's Go interface to the consumer, while `ServerPoint` forwards incoming calls to the implementation.
|
||||
|
||||
### Offer
|
||||
|
||||
A request from an extension to make one or more of its Points available to external callers.
|
||||
|
||||
### Publication
|
||||
|
||||
The Host exposing an offered Point to external callers after applying its policy and registering the required server wiring.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+9
-2
@@ -80,11 +80,18 @@ func fileDescriptor(pt point) (*descriptorpb.FileDescriptorProto, error) {
|
||||
|
||||
svc := &descriptorpb.ServiceDescriptorProto{Name: new(pt.service)}
|
||||
for _, m := range pt.methods {
|
||||
svc.Method = append(svc.Method, &descriptorpb.MethodDescriptorProto{
|
||||
method := &descriptorpb.MethodDescriptorProto{
|
||||
Name: new(m.name),
|
||||
InputType: new("." + pt.id + "." + m.request),
|
||||
OutputType: new("." + pt.id + "." + m.response),
|
||||
})
|
||||
}
|
||||
if m.kind == methodClientStreaming {
|
||||
method.ClientStreaming = new(true)
|
||||
}
|
||||
if m.kind == methodServerStreaming {
|
||||
method.ServerStreaming = new(true)
|
||||
}
|
||||
svc.Method = append(svc.Method, method)
|
||||
}
|
||||
fd.Service = []*descriptorpb.ServiceDescriptorProto{svc}
|
||||
return fd, nil
|
||||
|
||||
+12
-2
@@ -6,7 +6,6 @@ package main
|
||||
// model
|
||||
|
||||
type point struct {
|
||||
pkgName string // Go package name
|
||||
importPath string // package import path
|
||||
protoPath string // module-relative path of the .proto
|
||||
licenseHeader string // SPDX notices from the contract interface's source file
|
||||
@@ -15,6 +14,7 @@ type point struct {
|
||||
iface string // Go service interface name
|
||||
isPoint bool // whether the contract declares an extensions.Point
|
||||
isSingle bool // whether the point was declared with DefineSinglePoint
|
||||
doc string // documentation on the provider interface
|
||||
methods []method
|
||||
messages []message
|
||||
}
|
||||
@@ -25,12 +25,22 @@ func (p point) grpcService() string { return p.id + "." + p.service }
|
||||
func (p point) protogenImport() string { return p.importPath + "/protogen" }
|
||||
|
||||
type method struct {
|
||||
kind methodKind
|
||||
name string // method/rpc name
|
||||
request string // request message name
|
||||
response string // response message name
|
||||
bareError bool // true for `M(ctx, *Req) error`; response is a generated empty message
|
||||
bareError bool // true only for legacy unary `M(ctx, *Req) error`
|
||||
doc string // method documentation
|
||||
}
|
||||
|
||||
type methodKind int
|
||||
|
||||
const (
|
||||
methodUnary methodKind = iota
|
||||
methodServerStreaming
|
||||
methodClientStreaming
|
||||
)
|
||||
|
||||
type message struct {
|
||||
name string
|
||||
fields []field
|
||||
|
||||
+324
-31
@@ -10,6 +10,7 @@ import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -20,6 +21,8 @@ import (
|
||||
|
||||
// parsing
|
||||
|
||||
const streamImportPath = "github.com/moby/extensions/stream"
|
||||
|
||||
func parsePoint(dir string) (point, error) {
|
||||
return parseContract(dir, "")
|
||||
}
|
||||
@@ -38,21 +41,21 @@ func parseContract(dir, serviceIdentity string) (point, error) {
|
||||
}
|
||||
|
||||
var files []*ast.File
|
||||
var pkgName string
|
||||
foundPackage := false
|
||||
for name, pkg := range pkgs {
|
||||
if strings.HasSuffix(name, "_test") {
|
||||
continue
|
||||
}
|
||||
pkgName = name
|
||||
foundPackage = true
|
||||
for _, f := range pkg.Files {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
if pkgName == "" {
|
||||
if !foundPackage {
|
||||
return point{}, fmt.Errorf("no package found in %s", dir)
|
||||
}
|
||||
|
||||
pt := point{pkgName: pkgName}
|
||||
var pt point
|
||||
|
||||
if serviceIdentity != "" {
|
||||
pkg, service, ok := splitServiceIdentity(serviceIdentity)
|
||||
@@ -77,6 +80,7 @@ func parseContract(dir, serviceIdentity string) (point, error) {
|
||||
if ifaceType == nil {
|
||||
return point{}, fmt.Errorf("interface %q not found", pt.iface)
|
||||
}
|
||||
pt.doc = interfaceComment(files, pt.iface)
|
||||
// Inherit the contract's copyright and license rather than imposing the
|
||||
// generator's notices on output produced for other projects.
|
||||
for _, f := range files {
|
||||
@@ -98,7 +102,7 @@ func parseContract(dir, serviceIdentity string) (point, error) {
|
||||
if pt.licenseHeader != "" {
|
||||
pt.licenseHeader += "\n"
|
||||
}
|
||||
pt.methods, err = parseMethods(ifaceType)
|
||||
pt.methods, err = parseMethods(files, ifaceType)
|
||||
if err != nil {
|
||||
return point{}, err
|
||||
}
|
||||
@@ -220,7 +224,34 @@ func structHasPBTag(st *ast.StructType) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func parseMethods(iface *ast.InterfaceType) ([]method, error) {
|
||||
func interfaceComment(files []*ast.File, name string) string {
|
||||
for _, file := range files {
|
||||
for _, decl := range file.Decls {
|
||||
group, ok := decl.(*ast.GenDecl)
|
||||
if !ok || group.Tok != token.TYPE {
|
||||
continue
|
||||
}
|
||||
for _, spec := range group.Specs {
|
||||
typeSpec := spec.(*ast.TypeSpec)
|
||||
if typeSpec.Name.Name != name {
|
||||
continue
|
||||
}
|
||||
doc := typeSpec.Doc
|
||||
// Like go/doc, a group comment documents only a single-type group.
|
||||
if doc == nil && len(group.Specs) == 1 {
|
||||
doc = group.Doc
|
||||
}
|
||||
if doc != nil {
|
||||
return strings.TrimSpace(doc.Text())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseMethods(files []*ast.File, iface *ast.InterfaceType) ([]method, error) {
|
||||
var methods []method
|
||||
for _, m := range iface.Methods.List {
|
||||
ft, ok := m.Type.(*ast.FuncType)
|
||||
@@ -228,31 +259,287 @@ func parseMethods(iface *ast.InterfaceType) ([]method, error) {
|
||||
continue
|
||||
}
|
||||
name := m.Names[0].Name
|
||||
if ft.Params == nil || len(ft.Params.List) == 0 {
|
||||
return nil, fmt.Errorf("method %q: expected a request parameter", name)
|
||||
}
|
||||
reqType := ft.Params.List[len(ft.Params.List)-1].Type
|
||||
req, err := pointerIdent(reqType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("method %q request: %w", name, err)
|
||||
paramFields := fieldsPerName(ft.Params)
|
||||
params := make([]methodParameter, len(paramFields))
|
||||
for i, param := range paramFields {
|
||||
var err error
|
||||
params[i], err = classifyMethodParameter(files, param.Type, i)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("method %q parameter %d: %w", name, i+1, err)
|
||||
}
|
||||
}
|
||||
res := results(ft)
|
||||
switch {
|
||||
case len(res) == 1 && isIdent(res[0], "error"):
|
||||
methods = append(methods, method{name: name, request: req, response: name + "Response", bareError: true})
|
||||
case len(res) == 2 && isIdent(res[1], "error"):
|
||||
resp, err := pointerIdent(res[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("method %q response: %w", name, err)
|
||||
for _, result := range res {
|
||||
if _, isStream, _ := parseStreamParameter(files, result); isStream {
|
||||
return nil, methodShapeError(name)
|
||||
}
|
||||
methods = append(methods, method{name: name, request: req, response: resp})
|
||||
}
|
||||
|
||||
doc := fieldComment(m)
|
||||
switch {
|
||||
case len(params) == 2 && params[0].kind == methodContextParameter && params[1].kind == methodMessageParameter:
|
||||
var (
|
||||
response string
|
||||
bareError bool
|
||||
)
|
||||
switch {
|
||||
case len(res) == 1 && isIdent(res[0], "error"):
|
||||
response, bareError = name+"Response", true
|
||||
case len(res) == 2 && isIdent(res[1], "error"):
|
||||
var err error
|
||||
response, err = pointerIdent(res[0])
|
||||
if err != nil {
|
||||
return nil, methodShapeError(name)
|
||||
}
|
||||
default:
|
||||
return nil, methodShapeError(name)
|
||||
}
|
||||
methods = append(methods, method{
|
||||
name: name, request: params[1].message, response: response,
|
||||
bareError: bareError, doc: doc,
|
||||
})
|
||||
case len(params) == 3 && params[0].kind == methodContextParameter &&
|
||||
params[1].kind == methodMessageParameter && params[2].kind == methodSenderParameter:
|
||||
if len(res) != 1 || !isIdent(res[0], "error") {
|
||||
return nil, methodShapeError(name)
|
||||
}
|
||||
methods = append(methods, method{
|
||||
kind: methodServerStreaming, name: name,
|
||||
request: params[1].message, response: params[2].message, doc: doc,
|
||||
})
|
||||
case len(params) == 2 && params[0].kind == methodContextParameter && params[1].kind == methodReceiverParameter:
|
||||
if len(res) != 2 || !isIdent(res[1], "error") {
|
||||
return nil, methodShapeError(name)
|
||||
}
|
||||
response, err := pointerIdent(res[0])
|
||||
if err != nil {
|
||||
return nil, methodShapeError(name)
|
||||
}
|
||||
methods = append(methods, method{
|
||||
kind: methodClientStreaming, name: name,
|
||||
request: params[1].message, response: response, doc: doc,
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("method %q: result must be `error` or `(*Resp, error)`", name)
|
||||
return nil, methodShapeError(name)
|
||||
}
|
||||
}
|
||||
return methods, nil
|
||||
}
|
||||
|
||||
type methodParameterKind int
|
||||
|
||||
const (
|
||||
methodContextParameter methodParameterKind = iota + 1
|
||||
methodMessageParameter
|
||||
methodSenderParameter
|
||||
methodReceiverParameter
|
||||
)
|
||||
|
||||
type methodParameter struct {
|
||||
kind methodParameterKind
|
||||
message string
|
||||
}
|
||||
|
||||
const acceptedMethodShapes = "accepted shapes are M(context.Context, *Request) (*Reply, error), " +
|
||||
"M(context.Context, *Request) error, " +
|
||||
"M(context.Context, *Request, stream.Sender[Chunk]) error, and " +
|
||||
"M(context.Context, stream.Receiver[Chunk]) (*Reply, error)"
|
||||
|
||||
func methodShapeError(name string) error {
|
||||
return fmt.Errorf("method %q: unsupported signature; %s", name, acceptedMethodShapes)
|
||||
}
|
||||
|
||||
func classifyMethodParameter(files []*ast.File, expr ast.Expr, index int) (methodParameter, error) {
|
||||
param, isStream, err := parseStreamParameter(files, expr)
|
||||
switch {
|
||||
case err != nil:
|
||||
return methodParameter{}, err
|
||||
case isStream && index == 0:
|
||||
return methodParameter{}, errors.New("stream parameters cannot replace context.Context")
|
||||
case isStream:
|
||||
return param, nil
|
||||
case index == 0 && !isContextType(files, expr):
|
||||
return methodParameter{}, errors.New("must be context.Context")
|
||||
case index == 0:
|
||||
return methodParameter{kind: methodContextParameter}, nil
|
||||
}
|
||||
message, err := pointerIdent(expr)
|
||||
if err != nil {
|
||||
return methodParameter{}, err
|
||||
}
|
||||
return methodParameter{kind: methodMessageParameter, message: message}, nil
|
||||
}
|
||||
|
||||
// parseStreamParameter reports whether expr names stream.Sender or
|
||||
// stream.Receiver and, if so, validates its import and message argument.
|
||||
func parseStreamParameter(files []*ast.File, expr ast.Expr) (methodParameter, bool, error) {
|
||||
var (
|
||||
selector *ast.SelectorExpr
|
||||
args []ast.Expr
|
||||
)
|
||||
switch expr := expr.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
selector = expr
|
||||
case *ast.IndexExpr:
|
||||
selector, _ = expr.X.(*ast.SelectorExpr)
|
||||
args = []ast.Expr{expr.Index}
|
||||
case *ast.IndexListExpr:
|
||||
selector, _ = expr.X.(*ast.SelectorExpr)
|
||||
args = expr.Indices
|
||||
default:
|
||||
return methodParameter{}, false, nil
|
||||
}
|
||||
if selector == nil || (selector.Sel.Name != "Sender" && selector.Sel.Name != "Receiver") {
|
||||
return methodParameter{}, false, nil
|
||||
}
|
||||
use := "stream." + selector.Sel.Name
|
||||
file := fileForExpr(files, expr)
|
||||
if file == nil {
|
||||
return methodParameter{}, true, fmt.Errorf("%s must be imported from %q", use, streamImportPath)
|
||||
}
|
||||
if err := resolveStreamImport(file, selector.X, use); err != nil {
|
||||
return methodParameter{}, true, err
|
||||
}
|
||||
if len(args) != 1 {
|
||||
return methodParameter{}, true, fmt.Errorf("%s must have exactly one local message type argument", use)
|
||||
}
|
||||
message, err := streamMessageArgument(args[0])
|
||||
if err != nil {
|
||||
return methodParameter{}, true, fmt.Errorf("%s: %w", use, err)
|
||||
}
|
||||
if err := validateStreamMessage(files, message); err != nil {
|
||||
return methodParameter{}, true, fmt.Errorf("%s message %q: %w", use, message, err)
|
||||
}
|
||||
kind := methodSenderParameter
|
||||
if selector.Sel.Name == "Receiver" {
|
||||
kind = methodReceiverParameter
|
||||
}
|
||||
return methodParameter{kind: kind, message: message}, true, nil
|
||||
}
|
||||
|
||||
func fileForExpr(files []*ast.File, expr ast.Expr) *ast.File {
|
||||
for _, file := range files {
|
||||
if file.Pos() <= expr.Pos() && expr.End() <= file.End() {
|
||||
return file
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveStreamImport(file *ast.File, packageExpr ast.Expr, use string) error {
|
||||
ident, ok := packageExpr.(*ast.Ident)
|
||||
if !ok {
|
||||
return fmt.Errorf("%s must use a package imported from %q", use, streamImportPath)
|
||||
}
|
||||
importPath, ok := importedPath(file, ident.Name)
|
||||
if !ok {
|
||||
return fmt.Errorf("%s package name %q is not imported from %q", use, ident.Name, streamImportPath)
|
||||
}
|
||||
if importPath != streamImportPath {
|
||||
return fmt.Errorf("%s must be imported from %q", use, streamImportPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// importedPath returns the path of the package that file imports as name.
|
||||
func importedPath(file *ast.File, name string) (string, bool) {
|
||||
for _, spec := range file.Imports {
|
||||
importPath, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
local := path.Base(importPath)
|
||||
if spec.Name != nil {
|
||||
local = spec.Name.Name
|
||||
}
|
||||
if local == name {
|
||||
return importPath, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// isContextType reports whether expr names context.Context under any import
|
||||
// name.
|
||||
func isContextType(files []*ast.File, expr ast.Expr) bool {
|
||||
selector, ok := expr.(*ast.SelectorExpr)
|
||||
if !ok || selector.Sel.Name != "Context" {
|
||||
return false
|
||||
}
|
||||
ident, ok := selector.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
file := fileForExpr(files, expr)
|
||||
if file == nil {
|
||||
return false
|
||||
}
|
||||
importPath, ok := importedPath(file, ident.Name)
|
||||
return ok && importPath == "context"
|
||||
}
|
||||
|
||||
func streamMessageArgument(expr ast.Expr) (string, error) {
|
||||
if ident, ok := expr.(*ast.Ident); ok {
|
||||
return ident.Name, nil
|
||||
}
|
||||
switch expr.(type) {
|
||||
case *ast.StarExpr:
|
||||
return "", errors.New("message type argument must not be a pointer")
|
||||
case *ast.SelectorExpr:
|
||||
return "", errors.New("imported message types are unsupported; use a local struct message")
|
||||
case *ast.IndexExpr, *ast.IndexListExpr:
|
||||
return "", errors.New("message type argument must be a non-generic local named type")
|
||||
default:
|
||||
return "", errors.New("message type argument must be a local named type")
|
||||
}
|
||||
}
|
||||
|
||||
func validateStreamMessage(files []*ast.File, name string) error {
|
||||
var found *ast.TypeSpec
|
||||
for _, file := range files {
|
||||
for _, decl := range file.Decls {
|
||||
group, ok := decl.(*ast.GenDecl)
|
||||
if !ok || group.Tok != token.TYPE {
|
||||
continue
|
||||
}
|
||||
for _, spec := range group.Specs {
|
||||
typeSpec := spec.(*ast.TypeSpec)
|
||||
if typeSpec.Name.Name != name {
|
||||
continue
|
||||
}
|
||||
if found != nil {
|
||||
return errors.New("is ambiguous; declare one local struct message")
|
||||
}
|
||||
found = typeSpec
|
||||
}
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
return errors.New("is unresolved")
|
||||
}
|
||||
if found.Assign.IsValid() {
|
||||
return errors.New("is an alias")
|
||||
}
|
||||
if found.TypeParams != nil && len(found.TypeParams.List) != 0 {
|
||||
return errors.New("must not be generic")
|
||||
}
|
||||
if _, ok := found.Type.(*ast.StructType); !ok {
|
||||
return errors.New("must be a local struct declaration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fieldComment(field *ast.Field) string {
|
||||
group := field.Doc
|
||||
if group == nil {
|
||||
group = field.Comment
|
||||
}
|
||||
if group == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(group.Text())
|
||||
}
|
||||
|
||||
func parseMessages(files []*ast.File, msgNames map[string]bool) ([]message, error) {
|
||||
var messages []message
|
||||
for _, f := range files {
|
||||
@@ -474,17 +761,23 @@ func isIdent(expr ast.Expr, name string) bool {
|
||||
}
|
||||
|
||||
func results(ft *ast.FuncType) []ast.Expr {
|
||||
if ft.Results == nil {
|
||||
var out []ast.Expr
|
||||
for _, f := range fieldsPerName(ft.Results) {
|
||||
out = append(out, f.Type)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// fieldsPerName repeats each field once per declared name, so a grouped
|
||||
// declaration such as `a, b *T` counts as two entries.
|
||||
func fieldsPerName(list *ast.FieldList) []*ast.Field {
|
||||
if list == nil {
|
||||
return nil
|
||||
}
|
||||
var out []ast.Expr
|
||||
for _, f := range ft.Results.List {
|
||||
if len(f.Names) == 0 {
|
||||
out = append(out, f.Type)
|
||||
continue
|
||||
}
|
||||
for range f.Names {
|
||||
out = append(out, f.Type)
|
||||
var out []*ast.Field
|
||||
for _, f := range list.List {
|
||||
for range max(1, len(f.Names)) {
|
||||
out = append(out, f)
|
||||
}
|
||||
}
|
||||
return out
|
||||
|
||||
+13
-1
@@ -21,9 +21,21 @@ func emitProto(pt point) ([]byte, error) {
|
||||
fmt.Fprintf(&b, "package %s;\n\n", pt.id)
|
||||
fmt.Fprintf(&b, "option go_package = %q;\n\n", pt.protogenImport())
|
||||
|
||||
emitComment(&b, "", pt.doc)
|
||||
fmt.Fprintf(&b, "service %s {\n", pt.service)
|
||||
for _, m := range pt.methods {
|
||||
fmt.Fprintf(&b, " rpc %s(%s) returns (%s);\n", m.name, m.request, m.response)
|
||||
emitComment(&b, " ", m.doc)
|
||||
request, response := m.request, m.response
|
||||
switch m.kind {
|
||||
case methodUnary:
|
||||
case methodServerStreaming:
|
||||
response = "stream " + response
|
||||
case methodClientStreaming:
|
||||
request = "stream " + request
|
||||
default:
|
||||
return nil, fmt.Errorf("%s: unknown method kind %d", m.name, m.kind)
|
||||
}
|
||||
fmt.Fprintf(&b, " rpc %s(%s) returns (%s);\n", m.name, request, response)
|
||||
}
|
||||
fmt.Fprintln(&b, "}")
|
||||
|
||||
|
||||
+241
-15
@@ -14,19 +14,35 @@ import (
|
||||
|
||||
func emitWire(pt point) ([]byte, error) {
|
||||
var b strings.Builder
|
||||
cpkg := pt.pkgName
|
||||
const cpkg = "mobyextcontract"
|
||||
svc, iface := pt.service, pt.iface
|
||||
streamIndices := make(map[string]int)
|
||||
for _, m := range pt.methods {
|
||||
if m.kind != methodUnary {
|
||||
streamIndices[m.name] = len(streamIndices)
|
||||
}
|
||||
}
|
||||
hasStreaming := len(streamIndices) > 0
|
||||
b.WriteString(pt.licenseHeader)
|
||||
fmt.Fprintln(&b, "// Code generated by mobyextgen. DO NOT EDIT.")
|
||||
fmt.Fprintf(&b, "package %s\n\n", path.Base(pt.protogenImport()))
|
||||
fmt.Fprintln(&b, "import (")
|
||||
fmt.Fprintln(&b, ` context "context"`)
|
||||
if hasStreaming {
|
||||
fmt.Fprintln(&b, ` errors "errors"`)
|
||||
fmt.Fprintln(&b, ` io "io"`)
|
||||
}
|
||||
if pt.isPoint {
|
||||
fmt.Fprintln(&b, ` extensions "github.com/moby/extensions"`)
|
||||
fmt.Fprintln(&b, ` clientpoint "github.com/moby/extensions/clientpoint"`)
|
||||
fmt.Fprintln(&b, ` serverpoint "github.com/moby/extensions/serverpoint"`)
|
||||
}
|
||||
fmt.Fprintf(&b, "\t%s %q\n", cpkg, pt.importPath)
|
||||
if hasStreaming {
|
||||
fmt.Fprintln(&b, ` extensionstream "github.com/moby/extensions/stream"`)
|
||||
fmt.Fprintln(&b, ` codes "google.golang.org/grpc/codes"`)
|
||||
fmt.Fprintln(&b, ` status "google.golang.org/grpc/status"`)
|
||||
}
|
||||
fmt.Fprintln(&b, ` grpc "google.golang.org/grpc"`)
|
||||
fmt.Fprintln(&b, ")")
|
||||
|
||||
@@ -59,7 +75,15 @@ func emitWire(pt point) ([]byte, error) {
|
||||
`, svc)
|
||||
}
|
||||
for _, m := range pt.methods {
|
||||
fmt.Fprintf(&b, "\t%s(context.Context, *%s) (*%s, error)\n", m.name, m.request, m.response)
|
||||
emitComment(&b, "\t", m.doc)
|
||||
switch m.kind {
|
||||
case methodUnary:
|
||||
fmt.Fprintf(&b, "\t%s(context.Context, *%s) (*%s, error)\n", m.name, m.request, m.response)
|
||||
case methodServerStreaming:
|
||||
fmt.Fprintf(&b, "\t%s(*%s, grpc.ServerStreamingServer[%s]) error\n", m.name, m.request, m.response)
|
||||
case methodClientStreaming:
|
||||
fmt.Fprintf(&b, "\t%s(grpc.ClientStreamingServer[%s, %s]) error\n", m.name, m.request, m.response)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(&b, "}")
|
||||
|
||||
@@ -78,12 +102,33 @@ func emitWire(pt point) ([]byte, error) {
|
||||
Methods: []grpc.MethodDesc{
|
||||
`, svc, pt.protoPath)
|
||||
for _, m := range pt.methods {
|
||||
fmt.Fprintf(&b, "\t\t{MethodName: %q, Handler: %s},\n", m.name, handlerName(m))
|
||||
if m.kind == methodUnary {
|
||||
fmt.Fprintf(&b, "\t\t{MethodName: %q, Handler: %s},\n", m.name, handlerName(m))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(&b, "\t},\n}")
|
||||
fmt.Fprintln(&b, "\t},")
|
||||
if hasStreaming {
|
||||
fmt.Fprintln(&b, "\t\tStreams: []grpc.StreamDesc{")
|
||||
for _, m := range pt.methods {
|
||||
if m.kind == methodUnary {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, "\t\t\t{StreamName: %q, Handler: %s", m.name, handlerName(m))
|
||||
if m.kind == methodServerStreaming {
|
||||
fmt.Fprint(&b, ", ServerStreams: true")
|
||||
} else {
|
||||
fmt.Fprint(&b, ", ClientStreams: true")
|
||||
}
|
||||
fmt.Fprintln(&b, "},")
|
||||
}
|
||||
fmt.Fprintln(&b, "\t\t},")
|
||||
}
|
||||
fmt.Fprintln(&b, "}")
|
||||
|
||||
for _, m := range pt.methods {
|
||||
fmt.Fprintf(&b, `
|
||||
switch m.kind {
|
||||
case methodUnary:
|
||||
fmt.Fprintf(&b, `
|
||||
func %[1]s(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) {
|
||||
in := new(%[2]s)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -98,6 +143,23 @@ func emitWire(pt point) ([]byte, error) {
|
||||
})
|
||||
}
|
||||
`, handlerName(m), m.request, svc, m.name, methodConst(m))
|
||||
case methodServerStreaming:
|
||||
fmt.Fprintf(&b, `
|
||||
func %[1]s(srv any, stream grpc.ServerStream) error {
|
||||
in := new(%[2]s)
|
||||
if err := stream.RecvMsg(in); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(%[3]sServer).%[4]s(in, &grpc.GenericServerStream[%[2]s, %[5]s]{ServerStream: stream})
|
||||
}
|
||||
`, handlerName(m), m.request, svc, m.name, m.response)
|
||||
case methodClientStreaming:
|
||||
fmt.Fprintf(&b, `
|
||||
func %[1]s(srv any, stream grpc.ServerStream) error {
|
||||
return srv.(%[2]sServer).%[3]s(&grpc.GenericServerStream[%[4]s, %[5]s]{ServerStream: stream})
|
||||
}
|
||||
`, handlerName(m), svc, m.name, m.request, m.response)
|
||||
}
|
||||
}
|
||||
|
||||
if pt.isPoint {
|
||||
@@ -115,7 +177,15 @@ func emitWire(pt point) ([]byte, error) {
|
||||
`, svc)
|
||||
}
|
||||
for _, m := range pt.methods {
|
||||
fmt.Fprintf(&b, "\t%s(ctx context.Context, in *%s, opts ...grpc.CallOption) (*%s, error)\n", m.name, m.request, m.response)
|
||||
emitComment(&b, "\t", m.doc)
|
||||
switch m.kind {
|
||||
case methodUnary:
|
||||
fmt.Fprintf(&b, "\t%s(ctx context.Context, in *%s, opts ...grpc.CallOption) (*%s, error)\n", m.name, m.request, m.response)
|
||||
case methodServerStreaming:
|
||||
fmt.Fprintf(&b, "\t%s(ctx context.Context, in *%s, opts ...grpc.CallOption) (grpc.ServerStreamingClient[%s], error)\n", m.name, m.request, m.response)
|
||||
case methodClientStreaming:
|
||||
fmt.Fprintf(&b, "\t%s(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[%s, %s], error)\n", m.name, m.request, m.response)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(&b, "}")
|
||||
|
||||
@@ -130,7 +200,9 @@ func emitWire(pt point) ([]byte, error) {
|
||||
`, svc)
|
||||
|
||||
for _, m := range pt.methods {
|
||||
fmt.Fprintf(&b, `
|
||||
switch m.kind {
|
||||
case methodUnary:
|
||||
fmt.Fprintf(&b, `
|
||||
func (c *serviceClient) %[1]s(ctx context.Context, in *%[2]s, opts ...grpc.CallOption) (*%[3]s, error) {
|
||||
out := new(%[3]s)
|
||||
if err := c.cc.Invoke(ctx, %[4]s, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil {
|
||||
@@ -139,6 +211,34 @@ func emitWire(pt point) ([]byte, error) {
|
||||
return out, nil
|
||||
}
|
||||
`, m.name, m.request, m.response, methodConst(m))
|
||||
case methodServerStreaming:
|
||||
fmt.Fprintf(&b, `
|
||||
func (c *serviceClient) %[1]s(ctx context.Context, in *%[2]s, opts ...grpc.CallOption) (grpc.ServerStreamingClient[%[3]s], error) {
|
||||
stream, err := c.cc.NewStream(ctx, &serviceDesc.Streams[%[4]d], %[5]s, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[%[2]s, %[3]s]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
`, m.name, m.request, m.response, streamIndices[m.name], methodConst(m))
|
||||
case methodClientStreaming:
|
||||
fmt.Fprintf(&b, `
|
||||
func (c *serviceClient) %[1]s(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[%[2]s, %[3]s], error) {
|
||||
stream, err := c.cc.NewStream(ctx, &serviceDesc.Streams[%[4]d], %[5]s, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &grpc.GenericClientStream[%[2]s, %[3]s]{ClientStream: stream}, nil
|
||||
}
|
||||
`, m.name, m.request, m.response, streamIndices[m.name], methodConst(m))
|
||||
}
|
||||
}
|
||||
|
||||
if pt.isPoint {
|
||||
@@ -187,8 +287,10 @@ func emitWire(pt point) ([]byte, error) {
|
||||
`, cpkg, iface)
|
||||
|
||||
for _, m := range pt.methods {
|
||||
if m.bareError {
|
||||
fmt.Fprintf(&b, `
|
||||
switch m.kind {
|
||||
case methodUnary:
|
||||
if m.bareError {
|
||||
fmt.Fprintf(&b, `
|
||||
func (s *grpcServer) %[1]s(ctx context.Context, req *%[2]s) (*%[3]s, error) {
|
||||
if err := s.impl.%[1]s(ctx, %[4]sFromProto(req)); err != nil {
|
||||
return nil, err
|
||||
@@ -196,8 +298,8 @@ func emitWire(pt point) ([]byte, error) {
|
||||
return &%[3]s{}, nil
|
||||
}
|
||||
`, m.name, m.request, m.response, lowerFirst(m.request))
|
||||
} else {
|
||||
fmt.Fprintf(&b, `
|
||||
} else {
|
||||
fmt.Fprintf(&b, `
|
||||
func (s *grpcServer) %[1]s(ctx context.Context, req *%[2]s) (*%[3]s, error) {
|
||||
resp, err := s.impl.%[1]s(ctx, %[4]sFromProto(req))
|
||||
if err != nil {
|
||||
@@ -206,6 +308,48 @@ func emitWire(pt point) ([]byte, error) {
|
||||
return %[5]sToProto(resp), nil
|
||||
}
|
||||
`, m.name, m.request, m.response, lowerFirst(m.request), lowerFirst(m.response))
|
||||
}
|
||||
case methodServerStreaming:
|
||||
fmt.Fprintf(&b, `
|
||||
func (s *grpcServer) %[1]s(req *%[2]s, out grpc.ServerStreamingServer[%[3]s]) error {
|
||||
sender := &mobyext%[1]sSender{stream: out}
|
||||
return s.impl.%[1]s(out.Context(), %[4]sFromProto(req), sender)
|
||||
}
|
||||
|
||||
type mobyext%[1]sSender struct {
|
||||
stream grpc.ServerStreamingServer[%[3]s]
|
||||
}
|
||||
|
||||
func (s *mobyext%[1]sSender) Send(message *%[5]s.%[6]s) error {
|
||||
if message == nil {
|
||||
return status.Error(codes.Internal, %[7]q)
|
||||
}
|
||||
return s.stream.Send(%[8]sToProto(message))
|
||||
}
|
||||
`, m.name, m.request, m.response, lowerFirst(m.request), cpkg, m.response, m.name+": nil message", lowerFirst(m.response))
|
||||
case methodClientStreaming:
|
||||
fmt.Fprintf(&b, `
|
||||
func (s *grpcServer) %[1]s(stream grpc.ClientStreamingServer[%[2]s, %[3]s]) error {
|
||||
receiver := &mobyext%[1]sReceiver{stream: stream}
|
||||
resp, err := s.impl.%[1]s(stream.Context(), receiver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return stream.SendAndClose(%[4]sToProto(resp))
|
||||
}
|
||||
|
||||
type mobyext%[1]sReceiver struct {
|
||||
stream grpc.ClientStreamingServer[%[2]s, %[3]s]
|
||||
}
|
||||
|
||||
func (r *mobyext%[1]sReceiver) Recv() (*%[5]s.%[6]s, error) {
|
||||
message, err := r.stream.Recv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return %[7]sFromProto(message), nil
|
||||
}
|
||||
`, m.name, m.request, m.response, lowerFirst(m.response), cpkg, m.request, lowerFirst(m.request))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,15 +359,17 @@ func emitWire(pt point) ([]byte, error) {
|
||||
}
|
||||
`, svc)
|
||||
for _, m := range pt.methods {
|
||||
if m.bareError {
|
||||
fmt.Fprintf(&b, `
|
||||
switch m.kind {
|
||||
case methodUnary:
|
||||
if m.bareError {
|
||||
fmt.Fprintf(&b, `
|
||||
func (c *grpcClient) %[1]s(ctx context.Context, req *%[2]s.%[3]s) error {
|
||||
_, err := c.client.%[1]s(ctx, %[4]sToProto(req))
|
||||
return err
|
||||
}
|
||||
`, m.name, cpkg, m.request, lowerFirst(m.request))
|
||||
} else {
|
||||
fmt.Fprintf(&b, `
|
||||
} else {
|
||||
fmt.Fprintf(&b, `
|
||||
func (c *grpcClient) %[1]s(ctx context.Context, req *%[2]s.%[3]s) (*%[2]s.%[5]s, error) {
|
||||
resp, err := c.client.%[1]s(ctx, %[4]sToProto(req))
|
||||
if err != nil {
|
||||
@@ -232,6 +378,73 @@ func emitWire(pt point) ([]byte, error) {
|
||||
return %[6]sFromProto(resp), nil
|
||||
}
|
||||
`, m.name, cpkg, m.request, lowerFirst(m.request), m.response, lowerFirst(m.response))
|
||||
}
|
||||
case methodServerStreaming:
|
||||
fmt.Fprintf(&b, `
|
||||
func (c *grpcClient) %[1]s(ctx context.Context, req *%[2]s.%[3]s, sink extensionstream.Sender[%[2]s.%[4]s]) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
stream, err := c.client.%[1]s(ctx, %[5]sToProto(req))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return status.FromContextError(err).Err()
|
||||
}
|
||||
message, err := stream.Recv()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := sink.Send(%[6]sFromProto(message)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
`, m.name, cpkg, m.request, m.response, lowerFirst(m.request), lowerFirst(m.response))
|
||||
case methodClientStreaming:
|
||||
fmt.Fprintf(&b, `
|
||||
func (c *grpcClient) %[1]s(ctx context.Context, source extensionstream.Receiver[%[2]s.%[3]s]) (*%[2]s.%[4]s, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
stream, err := c.client.%[1]s(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
message, err := source.Recv()
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := stream.CloseAndRecv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return %[5]sFromProto(resp), nil
|
||||
}
|
||||
if message == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, %[6]q)
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, status.FromContextError(err).Err()
|
||||
}
|
||||
if err := stream.Send(%[7]sToProto(message)); err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := stream.CloseAndRecv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return %[5]sFromProto(resp), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
`, m.name, cpkg, m.request, m.response, lowerFirst(m.response), m.name+": nil message", lowerFirst(m.request))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +455,19 @@ func emitWire(pt point) ([]byte, error) {
|
||||
return format.Source([]byte(b.String()))
|
||||
}
|
||||
|
||||
func emitComment(b *strings.Builder, indent, doc string) {
|
||||
if doc == "" {
|
||||
return
|
||||
}
|
||||
for line := range strings.SplitSeq(doc, "\n") {
|
||||
if line == "" {
|
||||
fmt.Fprintf(b, "%s//\n", indent)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(b, "%s// %s\n", indent, line)
|
||||
}
|
||||
}
|
||||
|
||||
func emitConversions(b *strings.Builder, cpkg string, msg message) {
|
||||
conv := lowerFirst(msg.name)
|
||||
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ package org.mobyproject.extension.example.greeter.v0;
|
||||
|
||||
option go_package = "github.com/moby/extensions/example/greeter/v0/protogen";
|
||||
|
||||
// Greeter is the example service.
|
||||
service Greeter {
|
||||
rpc Greet(HelloRequest) returns (HelloReply);
|
||||
}
|
||||
|
||||
+15
-15
@@ -8,7 +8,7 @@ import (
|
||||
context "context"
|
||||
extensions "github.com/moby/extensions"
|
||||
clientpoint "github.com/moby/extensions/clientpoint"
|
||||
greeterv0 "github.com/moby/extensions/example/greeter/v0"
|
||||
mobyextcontract "github.com/moby/extensions/example/greeter/v0"
|
||||
serverpoint "github.com/moby/extensions/serverpoint"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
@@ -76,29 +76,29 @@ func (c *serviceClient) Greet(ctx context.Context, in *HelloRequest, opts ...grp
|
||||
// ServerPoint serves the Greeter point: it registers the point's gRPC service for
|
||||
// a provider with an SDK server. A binary passes it to (*sdk.Server).Register.
|
||||
var ServerPoint = serverpoint.Registration{
|
||||
Point: greeterv0.Point.ID(),
|
||||
Point: mobyextcontract.Point.ID(),
|
||||
Register: func(r grpc.ServiceRegistrar, impl any) {
|
||||
r.RegisterService(&serviceDesc, &grpcServer{impl: impl.(greeterv0.Greeter)})
|
||||
r.RegisterService(&serviceDesc, &grpcServer{impl: impl.(mobyextcontract.Greeter)})
|
||||
},
|
||||
}
|
||||
|
||||
// ClientProvider builds a broker provider for the Greeter point from an
|
||||
// out-of-process gRPC connection.
|
||||
func ClientProvider(conn grpc.ClientConnInterface) extensions.Provider {
|
||||
return greeterv0.Point.Provide(NewClient(conn))
|
||||
return mobyextcontract.Point.Provide(NewClient(conn))
|
||||
}
|
||||
|
||||
// ClientPoint registers ClientProvider for the Greeter point with a host.
|
||||
var ClientPoint = clientpoint.Registration{Point: greeterv0.Point.ID(), Provider: ClientProvider}
|
||||
var ClientPoint = clientpoint.Registration{Point: mobyextcontract.Point.ID(), Provider: ClientProvider}
|
||||
|
||||
// NewClient returns a greeterv0.Greeter that calls the Greeter point over conn.
|
||||
func NewClient(conn grpc.ClientConnInterface) greeterv0.Greeter {
|
||||
// NewClient returns a mobyextcontract.Greeter that calls the Greeter point over conn.
|
||||
func NewClient(conn grpc.ClientConnInterface) mobyextcontract.Greeter {
|
||||
return &grpcClient{client: NewGreeterClient(conn)}
|
||||
}
|
||||
|
||||
// grpcServer serves an implementation of the contract's Go interface.
|
||||
type grpcServer struct {
|
||||
impl greeterv0.Greeter
|
||||
impl mobyextcontract.Greeter
|
||||
}
|
||||
|
||||
func (s *grpcServer) Greet(ctx context.Context, req *HelloRequest) (*HelloReply, error) {
|
||||
@@ -113,7 +113,7 @@ type grpcClient struct {
|
||||
client GreeterClient
|
||||
}
|
||||
|
||||
func (c *grpcClient) Greet(ctx context.Context, req *greeterv0.HelloRequest) (*greeterv0.HelloReply, error) {
|
||||
func (c *grpcClient) Greet(ctx context.Context, req *mobyextcontract.HelloRequest) (*mobyextcontract.HelloReply, error) {
|
||||
resp, err := c.client.Greet(ctx, helloRequestToProto(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -121,7 +121,7 @@ func (c *grpcClient) Greet(ctx context.Context, req *greeterv0.HelloRequest) (*g
|
||||
return helloReplyFromProto(resp), nil
|
||||
}
|
||||
|
||||
func helloReplyToProto(in *greeterv0.HelloReply) *HelloReply {
|
||||
func helloReplyToProto(in *mobyextcontract.HelloReply) *HelloReply {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -130,16 +130,16 @@ func helloReplyToProto(in *greeterv0.HelloReply) *HelloReply {
|
||||
return out
|
||||
}
|
||||
|
||||
func helloReplyFromProto(in *HelloReply) *greeterv0.HelloReply {
|
||||
func helloReplyFromProto(in *HelloReply) *mobyextcontract.HelloReply {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &greeterv0.HelloReply{}
|
||||
out := &mobyextcontract.HelloReply{}
|
||||
out.Message = in.GetMessage()
|
||||
return out
|
||||
}
|
||||
|
||||
func helloRequestToProto(in *greeterv0.HelloRequest) *HelloRequest {
|
||||
func helloRequestToProto(in *mobyextcontract.HelloRequest) *HelloRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -148,11 +148,11 @@ func helloRequestToProto(in *greeterv0.HelloRequest) *HelloRequest {
|
||||
return out
|
||||
}
|
||||
|
||||
func helloRequestFromProto(in *HelloRequest) *greeterv0.HelloRequest {
|
||||
func helloRequestFromProto(in *HelloRequest) *mobyextcontract.HelloRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &greeterv0.HelloRequest{}
|
||||
out := &mobyextcontract.HelloRequest{}
|
||||
out.Name = in.GetName()
|
||||
return out
|
||||
}
|
||||
+160
-59
@@ -55,8 +55,8 @@ func Allow() PointPolicyResult {
|
||||
return PointPolicyResult{action: pointPolicyActionAllow}
|
||||
}
|
||||
|
||||
// Drop returns a decision that silently omits an ordinary provider or leaves an
|
||||
// offered set private.
|
||||
// Drop omits an ordinary provider (including its offer), or keeps all offers
|
||||
// private when applied to servicev0.Point.ID().
|
||||
func Drop() PointPolicyResult {
|
||||
return PointPolicyResult{action: pointPolicyActionDrop}
|
||||
}
|
||||
@@ -85,8 +85,8 @@ func (result PointPolicyResult) resolve() (pointPolicyAction, error) {
|
||||
}
|
||||
|
||||
// PointPolicy decides whether an extension identity may provide a Point.
|
||||
// A decision on servicev0.Point.ID() controls publication of the complete
|
||||
// offered set.
|
||||
// A decision on servicev0.Point.ID() controls publication of offered points
|
||||
// whose providers were not dropped by policy.
|
||||
// A nil policy allows ordinary providers and drops publication.
|
||||
type PointPolicy interface {
|
||||
Decide(identity extensions.ExtensionIdentity, point extensions.PointID) PointPolicyResult
|
||||
@@ -237,6 +237,14 @@ type registeredExtension struct {
|
||||
providers []extensions.Provider
|
||||
}
|
||||
|
||||
type publicationState struct {
|
||||
policy PointPolicy
|
||||
servers map[extensions.PointID]serverpoint.Registration
|
||||
published map[extensions.ExtensionID]map[extensions.PointID][]string
|
||||
owners map[string]extensions.ExtensionID
|
||||
reserved map[string]bool
|
||||
}
|
||||
|
||||
// hostedExtension is the runtime-neutral declaration and lifecycle surface
|
||||
// needed to adapt an externally hosted extension to the broker.
|
||||
type hostedExtension struct {
|
||||
@@ -307,8 +315,13 @@ func New(ctx context.Context, optionList ...Option) (_ *Host, retErr error) {
|
||||
conns := make(map[extensions.ExtensionID]grpc.ClientConnInterface)
|
||||
var loaded []loadedExtension
|
||||
var registered []registeredExtension
|
||||
publishedServices := make(map[extensions.ExtensionID]map[extensions.PointID][]string)
|
||||
publishedOwners := make(map[string]extensions.ExtensionID)
|
||||
publication := publicationState{
|
||||
policy: policy,
|
||||
servers: pointServers,
|
||||
published: make(map[extensions.ExtensionID]map[extensions.PointID][]string),
|
||||
owners: make(map[string]extensions.ExtensionID),
|
||||
reserved: reservedServices,
|
||||
}
|
||||
var inProcessServices []servicegrpc.Service
|
||||
var callback *grpc.Server
|
||||
// The broker only shuts down initialized extensions, so construction failures
|
||||
@@ -343,7 +356,23 @@ func New(ctx context.Context, optionList ...Option) (_ *Host, retErr error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services, err := collectInProcessPublications(identity, ext, policy, pointServers, publishedServices, publishedOwners, reservedServices)
|
||||
if err := broker.ValidateDeclaration(decl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if extensionFullyDropped(decl.Providers, admittedProviders) {
|
||||
// Dropping the extension must not hide malformed offers or a policy rejection.
|
||||
offered, err := offeredInProcessPoints(decl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(offered) > 0 {
|
||||
if _, err := publicationPolicyAction(identity, policy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
services, err := collectInProcessPublications(identity, ext, &publication, droppedProviderPoints(decl.Providers, admittedProviders))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -379,9 +408,22 @@ func New(ctx context.Context, optionList ...Option) (_ *Host, retErr error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := approveProcessPublications(identity, started, policy, publishedServices, publishedOwners, reservedServices); err != nil {
|
||||
if err := broker.ValidateDeclaration(decl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := approveProcessPublications(identity, started, &publication, droppedProviderPoints(decl.Providers, admittedProviders)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Offered-only points have no provider for policy to drop, so they keep
|
||||
// the process only when their offer is published.
|
||||
if extensionFullyDropped(decl.Providers, admittedProviders) && len(publication.published[identity.ID]) == 0 {
|
||||
// The process was launched for Describe; stop it before returning the host.
|
||||
if err := started.Close(context.WithoutCancel(ctx)); err != nil {
|
||||
return nil, fmt.Errorf("close dropped extension %q: %w", identity.ID, err)
|
||||
}
|
||||
loaded = loaded[:len(loaded)-1]
|
||||
continue
|
||||
}
|
||||
decl.Providers = admittedProviders
|
||||
if err := b.Register(identity, extensions.New(decl)); err != nil {
|
||||
return nil, err
|
||||
@@ -433,7 +475,7 @@ func New(ctx context.Context, optionList ...Option) (_ *Host, retErr error) {
|
||||
}
|
||||
log.G(ctx).WithFields(fields).Info("loaded extension")
|
||||
}
|
||||
return &Host{broker: b, conns: conns, loaded: loaded, publishedServices: publishedServices, inProcessServices: inProcessServices, callback: callback}, nil
|
||||
return &Host{broker: b, conns: conns, loaded: loaded, publishedServices: publication.published, inProcessServices: inProcessServices, callback: callback}, nil
|
||||
}
|
||||
|
||||
func validateHostIdentity(identity extensions.ExtensionIdentity, decl extensions.Declaration) error {
|
||||
@@ -470,45 +512,131 @@ func admitProviders(identity extensions.ExtensionIdentity, providers []extension
|
||||
return admitted, nil
|
||||
}
|
||||
|
||||
func approveProcessPublications(identity extensions.ExtensionIdentity, started *launcher.Launched, policy PointPolicy, published map[extensions.ExtensionID]map[extensions.PointID][]string, owners map[string]extensions.ExtensionID, reserved map[string]bool) error {
|
||||
// extensionFullyDropped ignores metadata providers and keeps extensions that
|
||||
// declared no ordinary providers: policy had nothing to drop from them.
|
||||
func extensionFullyDropped(declared, admitted []extensions.Provider) bool {
|
||||
hadProvider := false
|
||||
for _, provider := range declared {
|
||||
if !extensions.IsMetadataPoint(provider.Point) {
|
||||
hadProvider = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hadProvider {
|
||||
return false
|
||||
}
|
||||
for _, provider := range admitted {
|
||||
if !extensions.IsMetadataPoint(provider.Point) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// droppedProviderPoints excludes offered-only process points, which have no
|
||||
// internal provider for policy to drop and remain governed by servicev0.
|
||||
func droppedProviderPoints(declared, admitted []extensions.Provider) map[extensions.PointID]bool {
|
||||
if len(declared) == len(admitted) {
|
||||
return nil
|
||||
}
|
||||
dropped := make(map[extensions.PointID]bool, len(declared)-len(admitted))
|
||||
for _, provider := range declared {
|
||||
dropped[provider.Point] = true
|
||||
}
|
||||
for _, provider := range admitted {
|
||||
delete(dropped, provider.Point)
|
||||
}
|
||||
return dropped
|
||||
}
|
||||
|
||||
func approveProcessPublications(identity extensions.ExtensionIdentity, started *launcher.Launched, publication *publicationState, dropped map[extensions.PointID]bool) error {
|
||||
if len(started.OfferedPoints) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := Drop()
|
||||
if policy != nil {
|
||||
result = policy.Decide(identity, servicev0.Point.ID())
|
||||
action, err := publicationPolicyAction(identity, publication.policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
action, cause := result.resolve()
|
||||
switch action {
|
||||
case pointPolicyActionDrop:
|
||||
if action == pointPolicyActionDrop {
|
||||
return nil
|
||||
case pointPolicyActionReject:
|
||||
return policyRejectionError("publish offered points", identity, servicev0.Point.ID(), cause)
|
||||
}
|
||||
for _, point := range started.OfferedPoints {
|
||||
if dropped[point] {
|
||||
continue
|
||||
}
|
||||
names := started.ProviderServices[point]
|
||||
if len(names) == 0 {
|
||||
return fmt.Errorf("extension %q offered point %q without a gRPC service", identity.ID, point)
|
||||
}
|
||||
for _, service := range names {
|
||||
if reserved[service] {
|
||||
if publication.reserved[service] {
|
||||
return fmt.Errorf("extension %q cannot publish reserved gRPC service %q", identity.ID, service)
|
||||
}
|
||||
if owner, exists := owners[service]; exists {
|
||||
if owner, exists := publication.owners[service]; exists {
|
||||
return fmt.Errorf("extensions %q and %q both publish gRPC service %q", owner, identity.ID, service)
|
||||
}
|
||||
owners[service] = identity.ID
|
||||
publication.owners[service] = identity.ID
|
||||
}
|
||||
if published[identity.ID] == nil {
|
||||
published[identity.ID] = make(map[extensions.PointID][]string)
|
||||
if publication.published[identity.ID] == nil {
|
||||
publication.published[identity.ID] = make(map[extensions.PointID][]string)
|
||||
}
|
||||
published[identity.ID][point] = append([]string(nil), names...)
|
||||
publication.published[identity.ID][point] = append([]string(nil), names...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectInProcessPublications(identity extensions.ExtensionIdentity, ext extensions.Extension, policy PointPolicy, servers map[extensions.PointID]serverpoint.Registration, published map[extensions.ExtensionID]map[extensions.PointID][]string, owners map[string]extensions.ExtensionID, reserved map[string]bool) ([]servicegrpc.Service, error) {
|
||||
func collectInProcessPublications(identity extensions.ExtensionIdentity, ext extensions.Extension, publication *publicationState, dropped map[extensions.PointID]bool) ([]servicegrpc.Service, error) {
|
||||
decl := ext.Declaration()
|
||||
offered, err := offeredInProcessPoints(decl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(offered) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
action, err := publicationPolicyAction(identity, publication.policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if action == pointPolicyActionDrop {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
providers := make(map[extensions.PointID]any, len(decl.Providers))
|
||||
for _, provider := range decl.Providers {
|
||||
providers[provider.Point] = provider.Impl
|
||||
}
|
||||
var services []servicegrpc.Service
|
||||
for _, point := range offered {
|
||||
if dropped[point] {
|
||||
continue
|
||||
}
|
||||
impl := providers[point]
|
||||
registration, ok := publication.servers[point]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("extension %q: allowed in-process offer for point %q has no server registration", decl.ID, point)
|
||||
}
|
||||
service, err := servicegrpc.Adapt(registration, impl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extension %q: publish point %q: %w", decl.ID, point, err)
|
||||
}
|
||||
if publication.reserved[service.Name] {
|
||||
return nil, fmt.Errorf("extension %q cannot publish reserved gRPC service %q", decl.ID, service.Name)
|
||||
}
|
||||
if owner, exists := publication.owners[service.Name]; exists {
|
||||
return nil, fmt.Errorf("extensions %q and %q both publish gRPC service %q", owner, decl.ID, service.Name)
|
||||
}
|
||||
publication.owners[service.Name] = decl.ID
|
||||
services = append(services, service)
|
||||
if publication.published[decl.ID] == nil {
|
||||
publication.published[decl.ID] = make(map[extensions.PointID][]string)
|
||||
}
|
||||
publication.published[decl.ID][point] = []string{service.Name}
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func offeredInProcessPoints(decl extensions.Declaration) ([]extensions.PointID, error) {
|
||||
providers := make(map[extensions.PointID]any, len(decl.Providers))
|
||||
for _, provider := range decl.Providers {
|
||||
if _, exists := providers[provider.Point]; exists {
|
||||
@@ -545,46 +673,19 @@ func collectInProcessPublications(identity extensions.ExtensionIdentity, ext ext
|
||||
offered = append(offered, point)
|
||||
}
|
||||
}
|
||||
if len(offered) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return offered, nil
|
||||
}
|
||||
|
||||
func publicationPolicyAction(identity extensions.ExtensionIdentity, policy PointPolicy) (pointPolicyAction, error) {
|
||||
result := Drop()
|
||||
if policy != nil {
|
||||
result = policy.Decide(identity, servicev0.Point.ID())
|
||||
}
|
||||
action, cause := result.resolve()
|
||||
switch action {
|
||||
case pointPolicyActionDrop:
|
||||
return nil, nil
|
||||
case pointPolicyActionReject:
|
||||
return nil, policyRejectionError("publish offered points", identity, servicev0.Point.ID(), cause)
|
||||
if action == pointPolicyActionReject {
|
||||
return action, policyRejectionError("publish offered points", identity, servicev0.Point.ID(), cause)
|
||||
}
|
||||
|
||||
var services []servicegrpc.Service
|
||||
for _, point := range offered {
|
||||
impl := providers[point]
|
||||
registration, ok := servers[point]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("extension %q: allowed in-process offer for point %q has no server registration", decl.ID, point)
|
||||
}
|
||||
service, err := servicegrpc.Adapt(registration, impl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extension %q: publish point %q: %w", decl.ID, point, err)
|
||||
}
|
||||
if reserved[service.Name] {
|
||||
return nil, fmt.Errorf("extension %q cannot publish reserved gRPC service %q", decl.ID, service.Name)
|
||||
}
|
||||
if owner, exists := owners[service.Name]; exists {
|
||||
return nil, fmt.Errorf("extensions %q and %q both publish gRPC service %q", owner, decl.ID, service.Name)
|
||||
}
|
||||
owners[service.Name] = decl.ID
|
||||
services = append(services, service)
|
||||
if published[decl.ID] == nil {
|
||||
published[decl.ID] = make(map[extensions.PointID][]string)
|
||||
}
|
||||
published[decl.ID][point] = []string{service.Name}
|
||||
}
|
||||
return services, nil
|
||||
return action, nil
|
||||
}
|
||||
|
||||
func policyRejectionError(operation string, identity extensions.ExtensionIdentity, point extensions.PointID, cause error) error {
|
||||
|
||||
+21
-11
@@ -44,6 +44,27 @@ func (b *Broker) Register(identity extensions.ExtensionIdentity, ext extensions.
|
||||
if identity.ID != decl.ID {
|
||||
return fmt.Errorf("extension identity id %q does not match declared id %q", identity.ID, decl.ID)
|
||||
}
|
||||
if err := ValidateDeclaration(decl); err != nil {
|
||||
return err
|
||||
}
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if _, ok := b.extensions[decl.ID]; ok {
|
||||
return fmt.Errorf("extension %q is already registered", decl.ID)
|
||||
}
|
||||
if err := b.checkConflicts(decl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.extensions[decl.ID] = &extensionState{extension: decl, identity: identity}
|
||||
b.order = append(b.order, decl.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateDeclaration checks the fields of a declaration independently of
|
||||
// registration, so hosts can reject malformed extensions even when policy
|
||||
// drops all their providers.
|
||||
func ValidateDeclaration(decl extensions.Declaration) error {
|
||||
seenPoints := make(map[extensions.PointID]struct{})
|
||||
for _, provider := range decl.Providers {
|
||||
if provider.Point == "" {
|
||||
@@ -73,17 +94,6 @@ func (b *Broker) Register(identity extensions.ExtensionIdentity, ext extensions.
|
||||
return fmt.Errorf("extension %q conflicts with itself", decl.ID)
|
||||
}
|
||||
}
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if _, ok := b.extensions[decl.ID]; ok {
|
||||
return fmt.Errorf("extension %q is already registered", decl.ID)
|
||||
}
|
||||
if err := b.checkConflicts(decl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.extensions[decl.ID] = &extensionState{extension: decl, identity: identity}
|
||||
b.order = append(b.order, decl.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -9,8 +9,19 @@ package moby.extension.runtime.v1;
|
||||
|
||||
option go_package = "github.com/moby/extensions/sdk/sdkapi/protogen";
|
||||
|
||||
// Extension is served by every out-of-process extension.
|
||||
//
|
||||
// The generator command names the gRPC service fully qualified because, unlike
|
||||
// a point, there is no id to take the proto package from. Both halves are wire
|
||||
// identifiers: extensions built against them are separate binaries, possibly
|
||||
// built from an older tree, so neither may change without a new version.
|
||||
service Extension {
|
||||
// Describe returns the extension's declaration.
|
||||
rpc Describe(DescribeRequest) returns (DescribeResponse);
|
||||
// Initialize runs the extension's Init. The daemon calls it after connecting
|
||||
// every extension, in dependency order, so that by the time an extension
|
||||
// initializes its dependencies are already initialized and reachable over the
|
||||
// callback channel. An Init error is returned as the RPC status.
|
||||
rpc Initialize(InitializeRequest) returns (InitializeResponse);
|
||||
}
|
||||
|
||||
|
||||
+41
-31
@@ -6,7 +6,7 @@ package protogen
|
||||
|
||||
import (
|
||||
context "context"
|
||||
sdkapi "github.com/moby/extensions/sdk/sdkapi"
|
||||
mobyextcontract "github.com/moby/extensions/sdk/sdkapi"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
@@ -22,7 +22,12 @@ const (
|
||||
// proto-level shape of the service, not the handwritten Go interface: a contract
|
||||
// method returning a bare error returns an empty response message here.
|
||||
type ExtensionServer interface {
|
||||
// Describe returns the extension's declaration.
|
||||
Describe(context.Context, *DescribeRequest) (*DescribeResponse, error)
|
||||
// Initialize runs the extension's Init. The daemon calls it after connecting
|
||||
// every extension, in dependency order, so that by the time an extension
|
||||
// initializes its dependencies are already initialized and reachable over the
|
||||
// callback channel. An Init error is returned as the RPC status.
|
||||
Initialize(context.Context, *InitializeRequest) (*InitializeResponse, error)
|
||||
}
|
||||
|
||||
@@ -69,7 +74,12 @@ func handleInitialize(srv any, ctx context.Context, dec func(any) error, interce
|
||||
// ExtensionClient calls the contract's raw gRPC service. It is exported so a client
|
||||
// outside the framework can reach the service with a plain gRPC client.
|
||||
type ExtensionClient interface {
|
||||
// Describe returns the extension's declaration.
|
||||
Describe(ctx context.Context, in *DescribeRequest, opts ...grpc.CallOption) (*DescribeResponse, error)
|
||||
// Initialize runs the extension's Init. The daemon calls it after connecting
|
||||
// every extension, in dependency order, so that by the time an extension
|
||||
// initializes its dependencies are already initialized and reachable over the
|
||||
// callback channel. An Init error is returned as the RPC status.
|
||||
Initialize(ctx context.Context, in *InitializeRequest, opts ...grpc.CallOption) (*InitializeResponse, error)
|
||||
}
|
||||
|
||||
@@ -95,18 +105,18 @@ func (c *serviceClient) Initialize(ctx context.Context, in *InitializeRequest, o
|
||||
}
|
||||
|
||||
// RegisterServer serves impl as the Extension service on r.
|
||||
func RegisterServer(r grpc.ServiceRegistrar, impl sdkapi.Extension) {
|
||||
func RegisterServer(r grpc.ServiceRegistrar, impl mobyextcontract.Extension) {
|
||||
r.RegisterService(&serviceDesc, &grpcServer{impl: impl})
|
||||
}
|
||||
|
||||
// NewClient returns a sdkapi.Extension that calls the Extension service over conn.
|
||||
func NewClient(conn grpc.ClientConnInterface) sdkapi.Extension {
|
||||
// NewClient returns a mobyextcontract.Extension that calls the Extension service over conn.
|
||||
func NewClient(conn grpc.ClientConnInterface) mobyextcontract.Extension {
|
||||
return &grpcClient{client: NewExtensionClient(conn)}
|
||||
}
|
||||
|
||||
// grpcServer serves an implementation of the contract's Go interface.
|
||||
type grpcServer struct {
|
||||
impl sdkapi.Extension
|
||||
impl mobyextcontract.Extension
|
||||
}
|
||||
|
||||
func (s *grpcServer) Describe(ctx context.Context, req *DescribeRequest) (*DescribeResponse, error) {
|
||||
@@ -129,7 +139,7 @@ type grpcClient struct {
|
||||
client ExtensionClient
|
||||
}
|
||||
|
||||
func (c *grpcClient) Describe(ctx context.Context, req *sdkapi.DescribeRequest) (*sdkapi.DescribeResponse, error) {
|
||||
func (c *grpcClient) Describe(ctx context.Context, req *mobyextcontract.DescribeRequest) (*mobyextcontract.DescribeResponse, error) {
|
||||
resp, err := c.client.Describe(ctx, describeRequestToProto(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -137,7 +147,7 @@ func (c *grpcClient) Describe(ctx context.Context, req *sdkapi.DescribeRequest)
|
||||
return describeResponseFromProto(resp), nil
|
||||
}
|
||||
|
||||
func (c *grpcClient) Initialize(ctx context.Context, req *sdkapi.InitializeRequest) (*sdkapi.InitializeResponse, error) {
|
||||
func (c *grpcClient) Initialize(ctx context.Context, req *mobyextcontract.InitializeRequest) (*mobyextcontract.InitializeResponse, error) {
|
||||
resp, err := c.client.Initialize(ctx, initializeRequestToProto(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -145,7 +155,7 @@ func (c *grpcClient) Initialize(ctx context.Context, req *sdkapi.InitializeReque
|
||||
return initializeResponseFromProto(resp), nil
|
||||
}
|
||||
|
||||
func declarationToProto(in *sdkapi.Declaration) *Declaration {
|
||||
func declarationToProto(in *mobyextcontract.Declaration) *Declaration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -165,11 +175,11 @@ func declarationToProto(in *sdkapi.Declaration) *Declaration {
|
||||
return out
|
||||
}
|
||||
|
||||
func declarationFromProto(in *Declaration) *sdkapi.Declaration {
|
||||
func declarationFromProto(in *Declaration) *mobyextcontract.Declaration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &sdkapi.Declaration{}
|
||||
out := &mobyextcontract.Declaration{}
|
||||
out.ID = in.GetId()
|
||||
for _, e := range in.GetProviders() {
|
||||
out.Providers = append(out.Providers, *pointDeclarationFromProto(e))
|
||||
@@ -185,7 +195,7 @@ func declarationFromProto(in *Declaration) *sdkapi.Declaration {
|
||||
return out
|
||||
}
|
||||
|
||||
func dependencyToProto(in *sdkapi.Dependency) *Dependency {
|
||||
func dependencyToProto(in *mobyextcontract.Dependency) *Dependency {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -196,18 +206,18 @@ func dependencyToProto(in *sdkapi.Dependency) *Dependency {
|
||||
return out
|
||||
}
|
||||
|
||||
func dependencyFromProto(in *Dependency) *sdkapi.Dependency {
|
||||
func dependencyFromProto(in *Dependency) *mobyextcontract.Dependency {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &sdkapi.Dependency{}
|
||||
out := &mobyextcontract.Dependency{}
|
||||
out.Point = in.GetPoint()
|
||||
out.Extension = in.GetExtension()
|
||||
out.Optional = in.GetOptional()
|
||||
return out
|
||||
}
|
||||
|
||||
func describeRequestToProto(in *sdkapi.DescribeRequest) *DescribeRequest {
|
||||
func describeRequestToProto(in *mobyextcontract.DescribeRequest) *DescribeRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -215,15 +225,15 @@ func describeRequestToProto(in *sdkapi.DescribeRequest) *DescribeRequest {
|
||||
return out
|
||||
}
|
||||
|
||||
func describeRequestFromProto(in *DescribeRequest) *sdkapi.DescribeRequest {
|
||||
func describeRequestFromProto(in *DescribeRequest) *mobyextcontract.DescribeRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &sdkapi.DescribeRequest{}
|
||||
out := &mobyextcontract.DescribeRequest{}
|
||||
return out
|
||||
}
|
||||
|
||||
func describeResponseToProto(in *sdkapi.DescribeResponse) *DescribeResponse {
|
||||
func describeResponseToProto(in *mobyextcontract.DescribeResponse) *DescribeResponse {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -232,16 +242,16 @@ func describeResponseToProto(in *sdkapi.DescribeResponse) *DescribeResponse {
|
||||
return out
|
||||
}
|
||||
|
||||
func describeResponseFromProto(in *DescribeResponse) *sdkapi.DescribeResponse {
|
||||
func describeResponseFromProto(in *DescribeResponse) *mobyextcontract.DescribeResponse {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &sdkapi.DescribeResponse{}
|
||||
out := &mobyextcontract.DescribeResponse{}
|
||||
out.Declaration = declarationFromProto(in.GetDeclaration())
|
||||
return out
|
||||
}
|
||||
|
||||
func initializeRequestToProto(in *sdkapi.InitializeRequest) *InitializeRequest {
|
||||
func initializeRequestToProto(in *mobyextcontract.InitializeRequest) *InitializeRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -249,15 +259,15 @@ func initializeRequestToProto(in *sdkapi.InitializeRequest) *InitializeRequest {
|
||||
return out
|
||||
}
|
||||
|
||||
func initializeRequestFromProto(in *InitializeRequest) *sdkapi.InitializeRequest {
|
||||
func initializeRequestFromProto(in *InitializeRequest) *mobyextcontract.InitializeRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &sdkapi.InitializeRequest{}
|
||||
out := &mobyextcontract.InitializeRequest{}
|
||||
return out
|
||||
}
|
||||
|
||||
func initializeResponseToProto(in *sdkapi.InitializeResponse) *InitializeResponse {
|
||||
func initializeResponseToProto(in *mobyextcontract.InitializeResponse) *InitializeResponse {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -265,15 +275,15 @@ func initializeResponseToProto(in *sdkapi.InitializeResponse) *InitializeRespons
|
||||
return out
|
||||
}
|
||||
|
||||
func initializeResponseFromProto(in *InitializeResponse) *sdkapi.InitializeResponse {
|
||||
func initializeResponseFromProto(in *InitializeResponse) *mobyextcontract.InitializeResponse {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &sdkapi.InitializeResponse{}
|
||||
out := &mobyextcontract.InitializeResponse{}
|
||||
return out
|
||||
}
|
||||
|
||||
func pointDeclarationToProto(in *sdkapi.PointDeclaration) *PointDeclaration {
|
||||
func pointDeclarationToProto(in *mobyextcontract.PointDeclaration) *PointDeclaration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -282,16 +292,16 @@ func pointDeclarationToProto(in *sdkapi.PointDeclaration) *PointDeclaration {
|
||||
return out
|
||||
}
|
||||
|
||||
func pointDeclarationFromProto(in *PointDeclaration) *sdkapi.PointDeclaration {
|
||||
func pointDeclarationFromProto(in *PointDeclaration) *mobyextcontract.PointDeclaration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &sdkapi.PointDeclaration{}
|
||||
out := &mobyextcontract.PointDeclaration{}
|
||||
out.ID = in.GetId()
|
||||
return out
|
||||
}
|
||||
|
||||
func providerServicesToProto(in *sdkapi.ProviderServices) *ProviderServices {
|
||||
func providerServicesToProto(in *mobyextcontract.ProviderServices) *ProviderServices {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -301,11 +311,11 @@ func providerServicesToProto(in *sdkapi.ProviderServices) *ProviderServices {
|
||||
return out
|
||||
}
|
||||
|
||||
func providerServicesFromProto(in *ProviderServices) *sdkapi.ProviderServices {
|
||||
func providerServicesFromProto(in *ProviderServices) *mobyextcontract.ProviderServices {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := &sdkapi.ProviderServices{}
|
||||
out := &mobyextcontract.ProviderServices{}
|
||||
out.Point = in.GetPoint()
|
||||
out.Services = in.GetServices()
|
||||
return out
|
||||
|
||||
Vendored
+1
-1
@@ -1270,7 +1270,7 @@ github.com/moby/buildkit/worker/label
|
||||
# github.com/moby/docker-image-spec v1.3.1
|
||||
## explicit; go 1.18
|
||||
github.com/moby/docker-image-spec/specs-go/v1
|
||||
# github.com/moby/extensions v0.0.0-20260918151408-e4c23d866037
|
||||
# github.com/moby/extensions v0.0.0-20261020100352-d2819d88bcbe
|
||||
## explicit; go 1.26.0
|
||||
github.com/moby/extensions
|
||||
github.com/moby/extensions/client
|
||||
|
||||
Reference in new issue
Block a user