diff --git a/README.md b/README.md index bde3716..9ead14a 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,40 @@ HTTP client library. ## Builtin settings | Settings Name | Environment variable | Default | Description | -| ------------- | -------------------- | ------- | ---------------------------------------------------------------------------- | ------------------------------------ | +| ------------- | -------------------- | ------- | ---------------------------------------------------------------------------- | | `log_level` | - | `info` | Sets log level (`panic`, `fatal`, `error`, `warn`, `info`, `debug`, `trace`) | -| `skip_verify` | - | `false` | - | Skip verification of TLS certificate | +| `skip_verify` | - | `false` | Skip verification of TLS certificate | | | `SOCKS_PROXY` | _none_ | SOCKS5 proxy to use for connections | | | `SOCKS_PROXY_OFF` | _none_ | Do not use SOCKS5 proxy | +### Optional: HTTP proxy support + +HTTP proxy support is **opt-in** and must be explicitly enabled by the plugin author via `EnableHTTPProxy: true` in `plugin.Options`. When enabled, the following settings become available: + +| Settings Name | Environment variable | Default | Description | +| ------------- | -------------------- | ------- | ------------------------------------------------------ | +| `http_proxy` | `HTTP_PROXY` | _none_ | HTTP proxy URL for outgoing connections | +| `https_proxy` | `HTTPS_PROXY` | _none_ | HTTPS proxy URL for outgoing connections | +| `no_proxy` | `NO_PROXY` | _none_ | Comma-separated list of hosts to exclude from proxying | + +The settings are resolved in the following order of precedence: + +1. **Plugin settings** — `PLUGIN_HTTP_PROXY`, `PLUGIN_HTTPS_PROXY`, `PLUGIN_NO_PROXY` +2. **Lowercase env vars** — `http_proxy`, `https_proxy`, `no_proxy` +3. **Uppercase env vars** — `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` + +Example Woodpecker CI pipeline configuration: + +```yaml +steps: + - name: my-plugin + image: my-plugin:latest + settings: + http_proxy: http://proxy.example.com:3128 + https_proxy: http://proxy.example.com:3128 + no_proxy: "localhost,internal.example.com" +``` + ## Creating plugin ```go @@ -62,10 +90,11 @@ func main() { } p.Plugin = plugin.New(plugin.Options{ - Name: "sample-plugin", - Description: "Sample plugin", - Flags: p.Flags(), - Execute: p.Execute, + Name: "sample-plugin", + Description: "Sample plugin", + Flags: p.Flags(), + Execute: p.Execute, + EnableHTTPProxy: true, // opt-in to HTTP proxy support }) p.Run() diff --git a/http.go b/http.go index 0d18c7f..d02eb7e 100644 --- a/http.go +++ b/http.go @@ -20,6 +20,7 @@ import ( "crypto/x509" "net" "net/http" + "os" "time" "github.com/rs/zerolog/log" @@ -55,13 +56,60 @@ func httpClientFlags() []cli.Flag { } } -func HTTPClientFromContext(c *cli.Command) *http.Client { +// httpProxyFlags returns the optional HTTP proxy flags. +// Only added to the app when EnableHTTPProxy is set in Options. +func httpProxyFlags() []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "transport.http-proxy", + Usage: "HTTP proxy URL", + Sources: cli.EnvVars( + "PLUGIN_HTTP_PROXY", + ), + Hidden: true, + }, + &cli.StringFlag{ + Name: "transport.https-proxy", + Usage: "HTTPS proxy URL", + Sources: cli.EnvVars( + "PLUGIN_HTTPS_PROXY", + ), + Hidden: true, + }, + &cli.StringFlag{ + Name: "transport.no-proxy", + Usage: "Hosts to exclude from proxy (comma-separated)", + Sources: cli.EnvVars( + "PLUGIN_NO_PROXY", + ), + Hidden: true, + }, + } +} + +func HTTPClientFromContext(c *cli.Command, httpProxyEnabled bool) *http.Client { var ( skip = c.Bool("transport.skip-verify") socks = c.String("transport.socks-proxy") socksOff = c.Bool("transport.socks-proxy-off") ) + if httpProxyEnabled { + if v := c.String("transport.http-proxy"); v != "" { + os.Setenv("HTTP_PROXY", v) + os.Setenv("http_proxy", v) + } + if v := c.String("transport.https-proxy"); v != "" { + os.Setenv("HTTPS_PROXY", v) + os.Setenv("https_proxy", v) + + } + if v := c.String("transport.no-proxy"); v != "" { + os.Setenv("NO_PROXY", v) + os.Setenv("no_proxy", v) + } + } + certs, err := x509.SystemCertPool() if err != nil { log.Error().Err(err).Msg("failed to find system CA certs") @@ -105,4 +153,4 @@ func HTTPClientFromContext(c *cli.Command) *http.Client { return &http.Client{ Transport: transport, } -} +} \ No newline at end of file diff --git a/plugin.go b/plugin.go index ea2d800..fa4df9a 100644 --- a/plugin.go +++ b/plugin.go @@ -38,6 +38,10 @@ type Options struct { Execute ExecuteFunc // Context the plugin will use while executing. Context context.Context + // EnableHTTPProxy allows plugins to opt-in to HTTP/HTTPS proxy support. + // When true, PLUGIN_HTTP_PROXY, PLUGIN_HTTPS_PROXY and PLUGIN_NO_PROXY + // are accepted as settings and applied to the HTTP client. + EnableHTTPProxy bool } // Plugin defines the plugin instance. @@ -46,6 +50,8 @@ type Plugin struct { execute ExecuteFunc client *http.Client ctx context.Context + // enableHTTPProxy controls whether HTTP proxy flags are registered and applied. + enableHTTPProxy bool // Metadata of the current pipeline. Metadata Metadata } @@ -68,10 +74,16 @@ func New(opt Options) *Plugin { } plugin := &Plugin{ - App: app, - execute: opt.Execute, - ctx: opt.Context, + App: app, + execute: opt.Execute, + ctx: opt.Context, + enableHTTPProxy: opt.EnableHTTPProxy, } + + if opt.EnableHTTPProxy { + app.Flags = append(app.Flags, httpProxyFlags()...) + } + plugin.App.Action = plugin.action if plugin.ctx == nil { @@ -87,7 +99,7 @@ func (p *Plugin) action(ctx context.Context, c *cli.Command) error { } p.Metadata = MetadataFromContext(c) - p.client = HTTPClientFromContext(c) + p.client = HTTPClientFromContext(c, p.enableHTTPProxy) if p.execute == nil { panic("plugin execute function is not set") @@ -107,4 +119,4 @@ func (p *Plugin) Run() { log.Error().Err(err).Msg("execution failed") os.Exit(1) } -} +} \ No newline at end of file