diff --git a/cli/.env.example b/cli/.env.example index 4662c2871e..6254d8be5c 100644 --- a/cli/.env.example +++ b/cli/.env.example @@ -1,7 +1,8 @@ COSMO_API_KEY=cosmo_669b576aaadc10ee1ae81d9193425705 COSMO_API_URL=http://localhost:3001 CDN_URL=http://localhost:11000 -PLUGIN_REGISTRY_URL= +PLUGIN_REGISTRY_URL=host.docker.internal:5050 +PLUGIN_REGISTRY_INSECURE=true DEFAULT_TELEMETRY_ENDPOINT=http://localhost:4318 GRAPHQL_METRICS_COLLECTOR_ENDPOINT=http://localhost:4005 diff --git a/cli/src/commands/demo/util.ts b/cli/src/commands/demo/util.ts index 361cffa219..5115853ff6 100644 --- a/cli/src/commands/demo/util.ts +++ b/cli/src/commands/demo/util.ts @@ -462,6 +462,7 @@ export async function runRouterContainer({ ['CDN_URL', config.cdnURL], ['REGISTRY_URL', config.pluginRegistryURL], ['PLUGINS_REGISTRY_URL', config.pluginRegistryURL], + ['PLUGINS_REGISTRY_INSECURE', config.pluginRegistryInsecure ? 'true' : undefined], ['CONTROLPLANE_URL', config.baseURL], ['DEFAULT_TELEMETRY_ENDPOINT', config.defaultTelemetryEndpoint], ['GRAPHQL_METRICS_COLLECTOR_ENDPOINT', config.graphqlMetricsCollectorEndpoint], diff --git a/cli/src/core/config.ts b/cli/src/core/config.ts index 45652e410b..b4e0bfc7af 100644 --- a/cli/src/core/config.ts +++ b/cli/src/core/config.ts @@ -36,6 +36,7 @@ export const config = { checkCommitSha: process.env.COSMO_VCS_COMMIT || '', checkBranch: process.env.COSMO_VCS_BRANCH || '', pluginRegistryURL: process.env.PLUGIN_REGISTRY_URL || 'cosmo-registry.wundergraph.com', + pluginRegistryInsecure: process.env.PLUGIN_REGISTRY_INSECURE === 'true', demoLabelMatcher: 'graph=demo' as const, demoGraphName: 'demo' as const, demoNamespace: 'default' as const, diff --git a/docker-compose.yml b/docker-compose.yml index 159515b12c..178318e8ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -191,7 +191,7 @@ services: depends_on: rustfs_perms: condition: service_completed_successfully - command: "rustfs server /data" + command: 'rustfs server /data' environment: RUSTFS_CONSOLE_ENABLE: 'true' RUSTFS_ACCESS_KEY: ${S3_ACCESS_KEY_ID:-admin} @@ -354,6 +354,40 @@ services: profiles: - dev + # Local OCI plugin registry for gRPC plugin development. + # No auth, plain HTTP. For `wgc router plugin publish` to work: + # + # 1. Trust the registry in the Docker daemon (needed for `docker login`). + # OrbStack: edit ~/.orbstack/config/docker.json, then `orb restart docker`: + # { "insecure-registries": ["host.docker.internal:5050"] } + # Docker Desktop: Settings -> Docker Engine, merge the same key, Apply & Restart. + # + # 2. Create a buildx builder with insecure registry support (needed for push): + # cat > /tmp/buildkitd.toml << 'EOF' + # [registry."host.docker.internal:5050"] + # http = true + # insecure = true + # EOF + # docker buildx create --name local-registry --driver docker-container --config /tmp/buildkitd.toml --use + # + # 3. Set PLUGIN_REGISTRY_URL=host.docker.internal:5050 in cli/.env + # (host.docker.internal is required because buildx runs inside a container) + # + # 4. For the router to pull plugins from this registry, set in router/.env: + # PLUGINS_REGISTRY_URL=localhost:5050 + # PLUGINS_REGISTRY_INSECURE=true + # (localhost here because the router runs on the host, not in a container) + plugin-registry: + image: registry:2 + ports: + - '127.0.0.1:5050:5000' + volumes: + - plugin-registry:/var/lib/registry + networks: + - primary + profiles: + - dev + kafka: image: bitnamilegacy/kafka:3.7.0 ports: @@ -399,3 +433,4 @@ volumes: redis-cluster-node-1: redis-cluster-node-2: redis-cluster-node-3: + plugin-registry: diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index f188a4b2d5..adf7180cc7 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -1549,7 +1549,8 @@ func configureRouter(listenerAddr string, testConfig *Config, routerConfig *node return nil, fmt.Errorf("RegistryURL must be a host-only OCI registry address (no http:// or https:// scheme), got %q", testConfig.Plugins.RegistryURL) } pluginsCfg.Registry = config.PluginRegistryConfiguration{ - URL: testConfig.Plugins.RegistryURL, + URL: testConfig.Plugins.RegistryURL, + Insecure: true, } } routerOpts = append(routerOpts, core.WithPlugins(pluginsCfg)) diff --git a/router/.env.example b/router/.env.example index 622c8a11f4..2d66f8167f 100644 --- a/router/.env.example +++ b/router/.env.example @@ -12,6 +12,11 @@ CONFIG_PATH=demo.config.yaml #CONFIG_PATH=debug.config.yaml #GRAPH_CONFIG_SIGN_KEY=7kZKCz7DaLpvHKtaFEupDsBvDD9EEmUB +# For local plugin development with docker-compose registry, set: +PLUGINS_ENABLED=true +PLUGINS_REGISTRY_INSECURE=true +PLUGINS_REGISTRY_URL=localhost:5050 + DO_NOT_TRACK=1 # HTTP(S)_PROXY configuration diff --git a/router/core/graph_server.go b/router/core/graph_server.go index 97574ccc28..39bbcf0ee8 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -1927,6 +1927,7 @@ func (s *graphServer) setupConnector( Logger: s.logger, ImageRef: ref, RegistryToken: s.graphApiToken, + RegistryInsecure: s.plugins.Registry.Insecure, StartupConfig: startupConfig, Tracer: tracer, GetTraceAttributes: getTraceAttributes, diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index 8646eae8f6..f9363eae3a 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -1267,7 +1267,8 @@ type PluginsConfiguration struct { } type PluginRegistryConfiguration struct { - URL string `yaml:"url" env:"URL" envDefault:"cosmo-registry.wundergraph.com"` + URL string `yaml:"url" env:"URL" envDefault:"cosmo-registry.wundergraph.com"` + Insecure bool `yaml:"insecure" env:"INSECURE" envDefault:"false"` } type IntrospectionConfiguration struct { diff --git a/router/pkg/config/config.schema.json b/router/pkg/config/config.schema.json index d300d76a80..28b9f91ced 100644 --- a/router/pkg/config/config.schema.json +++ b/router/pkg/config/config.schema.json @@ -2538,11 +2538,7 @@ "default": ["sig"], "items": { "type": "string", - "enum": [ - "sig", - "enc", - "" - ] + "enum": ["sig", "enc", ""] } }, "algorithms": { @@ -3967,6 +3963,11 @@ "type": "string", "default": "cosmo-registry.wundergraph.com", "description": "The URL of the plugin registry." + }, + "insecure": { + "type": "boolean", + "default": false, + "description": "If true, the plugin registry is accessed over plaintext HTTP without authentication. Intended for local development against an unauthenticated registry; never enable in production." } } } diff --git a/router/pkg/config/testdata/config_defaults.json b/router/pkg/config/testdata/config_defaults.json index 5c7bbc25da..3edcd11623 100644 --- a/router/pkg/config/testdata/config_defaults.json +++ b/router/pkg/config/testdata/config_defaults.json @@ -640,7 +640,8 @@ "Enabled": false, "Path": "plugins", "Registry": { - "URL": "cosmo-registry.wundergraph.com" + "URL": "cosmo-registry.wundergraph.com", + "Insecure": false } }, "WatchConfig": { diff --git a/router/pkg/config/testdata/config_full.json b/router/pkg/config/testdata/config_full.json index f084bf9f28..14ae1e3529 100644 --- a/router/pkg/config/testdata/config_full.json +++ b/router/pkg/config/testdata/config_full.json @@ -1091,7 +1091,8 @@ "Enabled": true, "Path": "some/path/to/plugins", "Registry": { - "URL": "cosmo-registry.wundergraph.com" + "URL": "cosmo-registry.wundergraph.com", + "Insecure": false } }, "WatchConfig": { diff --git a/router/pkg/grpcconnector/grpcpluginoci/grpc_oci_plugin.go b/router/pkg/grpcconnector/grpcpluginoci/grpc_oci_plugin.go index 6bc2f918f4..b39628d1e0 100644 --- a/router/pkg/grpcconnector/grpcpluginoci/grpc_oci_plugin.go +++ b/router/pkg/grpcconnector/grpcpluginoci/grpc_oci_plugin.go @@ -25,6 +25,7 @@ type GRPCPluginConfig struct { Logger *zap.Logger ImageRef string RegistryToken string + RegistryInsecure bool StartupConfig grpccommon.GRPCStartupParams Tracer trace.Tracer GetTraceAttributes grpccommon.GRPCTraceAttributeGetter @@ -45,6 +46,7 @@ type GRPCPlugin struct { registryUsername string registryPassword string + registryInsecure bool client *grpccommon.GRPCPluginClient @@ -63,7 +65,7 @@ func NewGRPCOCIPlugin(config GRPCPluginConfig) (*GRPCPlugin, error) { return nil, fmt.Errorf("image source is required") } - if config.RegistryToken == "" { + if config.RegistryToken == "" && !config.RegistryInsecure { return nil, fmt.Errorf("registry token is required") } @@ -78,6 +80,7 @@ func NewGRPCOCIPlugin(config GRPCPluginConfig) (*GRPCPlugin, error) { registryUsername: "router", registryPassword: config.RegistryToken, + registryInsecure: config.RegistryInsecure, tracer: config.Tracer, startupConfig: config.StartupConfig, @@ -175,16 +178,22 @@ func (p *GRPCPlugin) cleanupPluginWorkDir() { // Start implements Plugin. func (p *GRPCPlugin) Start(ctx context.Context) error { - desc, err := crane.Get(p.imgRef, - crane.WithAuth(&authn.Basic{ - Username: p.registryUsername, - Password: p.registryPassword, - }), + opts := []crane.Option{ crane.WithPlatform(&v1.Platform{ Architecture: runtime.GOARCH, OS: runtime.GOOS, }), - ) + } + if p.registryInsecure { + opts = append(opts, crane.Insecure) + } else { + opts = append(opts, crane.WithAuth(&authn.Basic{ + Username: p.registryUsername, + Password: p.registryPassword, + })) + } + + desc, err := crane.Get(p.imgRef, opts...) if err != nil { return fmt.Errorf("pulling image %s: %w", p.imgRef, err)