diff --git a/cmd/server/main.go b/cmd/server/main.go index 475c7be0f..2e569c845 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -83,7 +83,7 @@ var ( licenseKey = flag.String("license-key", "", "License key for the workflow engine (or set WORKFLOW_LICENSE_KEY env var)") // v1 API flags - dataDir = flag.String("data-dir", "./data", "Directory for SQLite database and persistent data") + dataDir = flag.String("data-dir", "./data", "Directory for SQLite database and persistent data") restoreAdmin = flag.Bool("restore-admin", false, "Restore admin config to embedded default on startup") loadWorkflows = flag.String("load-workflows", "", "Comma-separated paths to workflow YAML files or directories to load alongside admin") importBundle = flag.String("import-bundle", "", "Comma-separated paths to .tar.gz workflow bundles to import and deploy on startup") diff --git a/cmd/wfctl/publish.go b/cmd/wfctl/publish.go index 8c6277fd5..d97dc7fe5 100644 --- a/cmd/wfctl/publish.go +++ b/cmd/wfctl/publish.go @@ -20,21 +20,21 @@ import ( // registryManifest is the manifest format for the GoCodeAlone/workflow-registry. // It differs from plugin.PluginManifest in field names and structure to match the registry schema. type registryManifest struct { - Name string `json:"name"` - Version string `json:"version"` - Author string `json:"author"` - Description string `json:"description"` - Source string `json:"source,omitempty"` - Path string `json:"path,omitempty"` - Type string `json:"type"` - Tier string `json:"tier"` - License string `json:"license"` - MinEngineVersion string `json:"minEngineVersion,omitempty"` - Keywords []string `json:"keywords,omitempty"` - Homepage string `json:"homepage,omitempty"` - Repository string `json:"repository,omitempty"` + Name string `json:"name"` + Version string `json:"version"` + Author string `json:"author"` + Description string `json:"description"` + Source string `json:"source,omitempty"` + Path string `json:"path,omitempty"` + Type string `json:"type"` + Tier string `json:"tier"` + License string `json:"license"` + MinEngineVersion string `json:"minEngineVersion,omitempty"` + Keywords []string `json:"keywords,omitempty"` + Homepage string `json:"homepage,omitempty"` + Repository string `json:"repository,omitempty"` Capabilities *registryCapabilities `json:"capabilities,omitempty"` - Checksums map[string]string `json:"checksums,omitempty"` + Checksums map[string]string `json:"checksums,omitempty"` } // registryCapabilities holds plugin capability declarations for the registry. @@ -163,19 +163,19 @@ func loadRegistryManifest(path string) (*registryManifest, error) { // pluginJSONSchema mirrors the internal plugin.PluginManifest for loading plugin.json. type pluginJSONSchema struct { - Name string `json:"name"` - Version string `json:"version"` - Author string `json:"author"` - Description string `json:"description"` - License string `json:"license"` - Tags []string `json:"tags"` - Repository string `json:"repository"` - Tier string `json:"tier"` - ModuleTypes []string `json:"moduleTypes"` - StepTypes []string `json:"stepTypes"` - TriggerTypes []string `json:"triggerTypes"` + Name string `json:"name"` + Version string `json:"version"` + Author string `json:"author"` + Description string `json:"description"` + License string `json:"license"` + Tags []string `json:"tags"` + Repository string `json:"repository"` + Tier string `json:"tier"` + ModuleTypes []string `json:"moduleTypes"` + StepTypes []string `json:"stepTypes"` + TriggerTypes []string `json:"triggerTypes"` WorkflowTypes []string `json:"workflowTypes"` - WiringHooks []string `json:"wiringHooks"` + WiringHooks []string `json:"wiringHooks"` } // loadRegistryManifestFromPluginJSON converts an internal plugin.json to registry format. diff --git a/handlers/integration.go b/handlers/integration.go index 72032611f..468cf2224 100644 --- a/handlers/integration.go +++ b/handlers/integration.go @@ -139,7 +139,7 @@ func configureHTTPConnector(name string, config map[string]any) (module.Integrat } if allowPrivate, ok := config["allowPrivateIPs"].(bool); ok && allowPrivate { - httpConn.SetAllowPrivateIPs(true) + httpConn.AllowPrivateIPs() } return httpConn, nil diff --git a/handlers/integration_handler_test.go b/handlers/integration_handler_test.go index 681a470a5..2c9f61206 100644 --- a/handlers/integration_handler_test.go +++ b/handlers/integration_handler_test.go @@ -96,7 +96,7 @@ func TestIntegrationWorkflowHandler_ExecuteIntegrationWorkflow(t *testing.T) { h := NewIntegrationWorkflowHandler() registry := module.NewIntegrationRegistry("test-registry") conn := module.NewHTTPIntegrationConnector("test-api", server.URL) - conn.SetAllowPrivateIPs(true) + conn.AllowPrivateIPs() _ = conn.Connect(context.Background()) registry.RegisterConnector(conn) @@ -148,7 +148,7 @@ func TestIntegrationWorkflowHandler_ExecuteIntegrationWorkflow_NotConnected(t *t h := NewIntegrationWorkflowHandler() registry := module.NewIntegrationRegistry("test-registry") conn := module.NewHTTPIntegrationConnector("test-api", server.URL) - conn.SetAllowPrivateIPs(true) + conn.AllowPrivateIPs() // Not connecting - should auto-connect registry.RegisterConnector(conn) @@ -178,7 +178,7 @@ func TestIntegrationWorkflowHandler_ExecuteIntegrationWorkflow_VariableSubstitut h := NewIntegrationWorkflowHandler() registry := module.NewIntegrationRegistry("test-registry") conn := module.NewHTTPIntegrationConnector("test-api", server.URL) - conn.SetAllowPrivateIPs(true) + conn.AllowPrivateIPs() _ = conn.Connect(context.Background()) registry.RegisterConnector(conn) @@ -216,7 +216,7 @@ func TestIntegrationWorkflowHandler_ExecuteIntegrationWorkflow_WithOnError(t *te h := NewIntegrationWorkflowHandler() registry := module.NewIntegrationRegistry("test-registry") conn := module.NewHTTPIntegrationConnector("test-api", server.URL) - conn.SetAllowPrivateIPs(true) + conn.AllowPrivateIPs() _ = conn.Connect(context.Background()) registry.RegisterConnector(conn) @@ -247,7 +247,7 @@ func TestIntegrationWorkflowHandler_ExecuteIntegrationWorkflow_WithOnSuccess(t * h := NewIntegrationWorkflowHandler() registry := module.NewIntegrationRegistry("test-registry") conn := module.NewHTTPIntegrationConnector("test-api", server.URL) - conn.SetAllowPrivateIPs(true) + conn.AllowPrivateIPs() _ = conn.Connect(context.Background()) registry.RegisterConnector(conn) diff --git a/handlers/integration_test.go b/handlers/integration_test.go index 155662758..1a4ae07dd 100644 --- a/handlers/integration_test.go +++ b/handlers/integration_test.go @@ -81,9 +81,9 @@ func TestIntegrationWorkflow(t *testing.T) { // Pre-create and connect the HTTP connectors with the mock server URL crmConnector := module.NewHTTPIntegrationConnector("crm-connector", mockServer.URL) - crmConnector.SetAllowPrivateIPs(true) + crmConnector.AllowPrivateIPs() emailConnector := module.NewHTTPIntegrationConnector("email-connector", mockServer.URL) - emailConnector.SetAllowPrivateIPs(true) + emailConnector.AllowPrivateIPs() // Set default headers crmConnector.SetDefaultHeader("Content-Type", "application/json") diff --git a/handlers/testhelpers_test.go b/handlers/testhelpers_test.go index 1d0cef049..e3782736d 100644 --- a/handlers/testhelpers_test.go +++ b/handlers/testhelpers_test.go @@ -90,21 +90,24 @@ func registerCyclicPluginFactories(engine *workflow.StdEngine) { if p, ok := cfg["prefix"].(string); ok && p != "" { prefix = p } - spaFallback := true + var opts []module.StaticFileServerOption + spaFallback := false if sf, ok := cfg["spaFallback"].(bool); ok { spaFallback = sf } - cacheMaxAge := 3600 + if spaFallback { + opts = append(opts, module.WithSPAFallback()) + } if cma, ok := cfg["cacheMaxAge"].(int); ok { - cacheMaxAge = cma + opts = append(opts, module.WithCacheMaxAge(cma)) } else if cma, ok := cfg["cacheMaxAge"].(float64); ok { - cacheMaxAge = int(cma) + opts = append(opts, module.WithCacheMaxAge(int(cma))) } routerName := "" if rn, ok := cfg["router"].(string); ok { routerName = rn } - sfs := module.NewStaticFileServer(name, root, prefix, spaFallback, cacheMaxAge) + sfs := module.NewStaticFileServer(name, root, prefix, opts...) if routerName != "" { sfs.SetRouterName(routerName) } diff --git a/module/auth_oauth2_test.go b/module/auth_oauth2_test.go index 763bd006f..044633199 100644 --- a/module/auth_oauth2_test.go +++ b/module/auth_oauth2_test.go @@ -458,7 +458,7 @@ func TestOAuth2_GenericProviderMissingURLs(t *testing.T) { func TestOAuth2_MissingClientIDorSecret(t *testing.T) { pc := OAuth2ProviderConfig{ - Name: "google", + Name: "google", // No ClientID or ClientSecret } entry := buildProviderEntry(&pc) diff --git a/module/integration.go b/module/integration.go index 9690c8c5b..a23499014 100644 --- a/module/integration.go +++ b/module/integration.go @@ -151,10 +151,16 @@ func (c *HTTPIntegrationConnector) SetTimeout(timeout time.Duration) { c.client.Timeout = timeout } -// SetAllowPrivateIPs enables or disables requests to private/internal IP addresses. +// AllowPrivateIPs enables requests to private/internal IP addresses. // This should only be used for testing or trusted internal services. -func (c *HTTPIntegrationConnector) SetAllowPrivateIPs(allow bool) { - c.allowPrivateIPs = allow +func (c *HTTPIntegrationConnector) AllowPrivateIPs() { + c.allowPrivateIPs = true +} + +// DisallowPrivateIPs disables requests to private/internal IP addresses, +// restoring SSRF protection (this is the default). +func (c *HTTPIntegrationConnector) DisallowPrivateIPs() { + c.allowPrivateIPs = false } // SetRateLimit sets a rate limit for requests diff --git a/module/integration_test.go b/module/integration_test.go index 89e60389f..67e699cd6 100644 --- a/module/integration_test.go +++ b/module/integration_test.go @@ -166,7 +166,7 @@ func TestHTTPIntegrationConnector_ExecuteGET(t *testing.T) { defer server.Close() c := NewHTTPIntegrationConnector("test", server.URL) - c.SetAllowPrivateIPs(true) + c.AllowPrivateIPs() ctx := context.Background() _ = c.Connect(ctx) @@ -195,7 +195,7 @@ func TestHTTPIntegrationConnector_ExecutePOST(t *testing.T) { defer server.Close() c := NewHTTPIntegrationConnector("test", server.URL) - c.SetAllowPrivateIPs(true) + c.AllowPrivateIPs() ctx := context.Background() _ = c.Connect(ctx) @@ -221,7 +221,7 @@ func TestHTTPIntegrationConnector_ExecuteWithBasicAuth(t *testing.T) { defer server.Close() c := NewHTTPIntegrationConnector("test", server.URL) - c.SetAllowPrivateIPs(true) + c.AllowPrivateIPs() c.SetBasicAuth("admin", "secret") ctx := context.Background() _ = c.Connect(ctx) @@ -248,7 +248,7 @@ func TestHTTPIntegrationConnector_ExecuteWithBearerAuth(t *testing.T) { defer server.Close() c := NewHTTPIntegrationConnector("test", server.URL) - c.SetAllowPrivateIPs(true) + c.AllowPrivateIPs() c.SetBearerAuth("my-token") ctx := context.Background() _ = c.Connect(ctx) @@ -273,7 +273,7 @@ func TestHTTPIntegrationConnector_ExecuteCustomHeaders(t *testing.T) { defer server.Close() c := NewHTTPIntegrationConnector("test", server.URL) - c.SetAllowPrivateIPs(true) + c.AllowPrivateIPs() c.SetHeader("X-Custom", "my-value") ctx := context.Background() _ = c.Connect(ctx) @@ -293,7 +293,7 @@ func TestHTTPIntegrationConnector_ExecuteErrorStatus(t *testing.T) { defer server.Close() c := NewHTTPIntegrationConnector("test", server.URL) - c.SetAllowPrivateIPs(true) + c.AllowPrivateIPs() ctx := context.Background() _ = c.Connect(ctx) @@ -314,7 +314,7 @@ func TestHTTPIntegrationConnector_ExecuteNonJSONResponse(t *testing.T) { defer server.Close() c := NewHTTPIntegrationConnector("test", server.URL) - c.SetAllowPrivateIPs(true) + c.AllowPrivateIPs() ctx := context.Background() _ = c.Connect(ctx) @@ -334,7 +334,7 @@ func TestHTTPIntegrationConnector_ExecuteEmptyResponse(t *testing.T) { defer server.Close() c := NewHTTPIntegrationConnector("test", server.URL) - c.SetAllowPrivateIPs(true) + c.AllowPrivateIPs() ctx := context.Background() _ = c.Connect(ctx) @@ -468,7 +468,7 @@ func TestStdIntegrationRegistry_StartAndStop(t *testing.T) { defer server.Close() c := NewHTTPIntegrationConnector("test-conn", server.URL) - c.SetAllowPrivateIPs(true) + c.AllowPrivateIPs() r.RegisterConnector(c) if err := r.Start(); err != nil { diff --git a/module/license_module.go b/module/license_module.go index 113444a95..b45f4744c 100644 --- a/module/license_module.go +++ b/module/license_module.go @@ -155,10 +155,10 @@ func (m *LicenseModule) ServeHTTP(w http.ResponseWriter, r *http.Request) { } type statusResponse struct { - Valid bool `json:"valid"` + Valid bool `json:"valid"` License *licensing.LicenseInfo `json:"license,omitempty"` - Error string `json:"error,omitempty"` - CachedUntil time.Time `json:"cached_until"` + Error string `json:"error,omitempty"` + CachedUntil time.Time `json:"cached_until"` } resp := statusResponse{ diff --git a/module/license_module_test.go b/module/license_module_test.go index 44e51365a..5264cf662 100644 --- a/module/license_module_test.go +++ b/module/license_module_test.go @@ -192,7 +192,7 @@ func TestLicenseModule_CanLoadPlugin(t *testing.T) { }{ {"core", true}, {"community", true}, - {"premium", true}, // professional license allows premium + {"premium", true}, // professional license allows premium {"unknown", false}, } for _, tt := range tests { diff --git a/module/static_fileserver.go b/module/static_fileserver.go index 47b827261..e5babbdf1 100644 --- a/module/static_fileserver.go +++ b/module/static_fileserver.go @@ -21,21 +21,44 @@ type StaticFileServer struct { routerName string // optional: name of the router to attach to } -// NewStaticFileServer creates a new static file server module -func NewStaticFileServer(name, root, prefix string, spaFallback bool, cacheMaxAge int) *StaticFileServer { +// StaticFileServerOption is a functional option for configuring a StaticFileServer. +type StaticFileServerOption func(*StaticFileServer) + +// WithSPAFallback enables Single Page Application fallback: requests for +// unknown paths are served with index.html instead of a 404. +func WithSPAFallback() StaticFileServerOption { + return func(s *StaticFileServer) { + s.spaFallback = true + } +} + +// WithCacheMaxAge sets the Cache-Control max-age value (in seconds). +// Defaults to 3600 when not specified or when seconds <= 0. +func WithCacheMaxAge(seconds int) StaticFileServerOption { + return func(s *StaticFileServer) { + if seconds > 0 { + s.cacheMaxAge = seconds + } + } +} + +// NewStaticFileServer creates a new static file server module. +// Use WithSPAFallback() and WithCacheMaxAge() to customise behaviour. +func NewStaticFileServer(name, root, prefix string, opts ...StaticFileServerOption) *StaticFileServer { if prefix == "" { prefix = "/" } - if cacheMaxAge <= 0 { - cacheMaxAge = 3600 - } - return &StaticFileServer{ + s := &StaticFileServer{ name: name, root: root, prefix: prefix, - spaFallback: spaFallback, - cacheMaxAge: cacheMaxAge, + spaFallback: false, + cacheMaxAge: 3600, + } + for _, opt := range opts { + opt(s) } + return s } // Name returns the module name diff --git a/module/static_fileserver_test.go b/module/static_fileserver_test.go index 00d770814..51aebed56 100644 --- a/module/static_fileserver_test.go +++ b/module/static_fileserver_test.go @@ -27,7 +27,7 @@ func setupStaticFileServer(t *testing.T) (*StaticFileServer, string) { } app := CreateIsolatedApp(t) - srv := NewStaticFileServer("static-server", dir, "/", true, 3600) + srv := NewStaticFileServer("static-server", dir, "/", WithSPAFallback()) if err := srv.Init(app); err != nil { t.Fatal(err) } @@ -35,7 +35,7 @@ func setupStaticFileServer(t *testing.T) (*StaticFileServer, string) { } func TestStaticFileServer_Name(t *testing.T) { - srv := NewStaticFileServer("my-static", "/tmp", "/", true, 3600) + srv := NewStaticFileServer("my-static", "/tmp", "/") if srv.Name() != "my-static" { t.Errorf("expected name 'my-static', got '%s'", srv.Name()) } @@ -93,7 +93,7 @@ func TestStaticFileServer_NoSPAFallback_404(t *testing.T) { } app := CreateIsolatedApp(t) - srv := NewStaticFileServer("static-no-spa", dir, "/", false, 3600) + srv := NewStaticFileServer("static-no-spa", dir, "/") if err := srv.Init(app); err != nil { t.Fatal(err) } @@ -149,7 +149,7 @@ func TestStaticFileServer_PrefixRouting(t *testing.T) { } app := CreateIsolatedApp(t) - srv := NewStaticFileServer("prefixed", dir, "/static/", false, 7200) + srv := NewStaticFileServer("prefixed", dir, "/static/", WithCacheMaxAge(7200)) if err := srv.Init(app); err != nil { t.Fatal(err) } @@ -165,7 +165,7 @@ func TestStaticFileServer_PrefixRouting(t *testing.T) { func TestStaticFileServer_InvalidRootPath(t *testing.T) { app := CreateIsolatedApp(t) - srv := NewStaticFileServer("bad-root", "/nonexistent/path/that/doesnt/exist", "/", true, 3600) + srv := NewStaticFileServer("bad-root", "/nonexistent/path/that/doesnt/exist", "/", WithSPAFallback()) err := srv.Init(app) if err == nil { t.Error("expected error for nonexistent root path") @@ -173,7 +173,7 @@ func TestStaticFileServer_InvalidRootPath(t *testing.T) { } func TestStaticFileServer_ProvidesServices(t *testing.T) { - srv := NewStaticFileServer("static-test", "/tmp", "/", true, 3600) + srv := NewStaticFileServer("static-test", "/tmp", "/", WithSPAFallback()) services := srv.ProvidesServices() if len(services) != 1 { t.Fatalf("expected 1 service, got %d", len(services)) @@ -184,7 +184,7 @@ func TestStaticFileServer_ProvidesServices(t *testing.T) { } func TestStaticFileServer_DefaultValues(t *testing.T) { - srv := NewStaticFileServer("defaults", "/tmp", "", false, 0) + srv := NewStaticFileServer("defaults", "/tmp", "") if srv.prefix != "/" { t.Errorf("expected default prefix '/', got '%s'", srv.prefix) } diff --git a/module/storage_gcs_test.go b/module/storage_gcs_test.go index 3d949cd25..16bc9fe35 100644 --- a/module/storage_gcs_test.go +++ b/module/storage_gcs_test.go @@ -77,8 +77,8 @@ func (w *mockWriteCloser) Close() error { return w.closeErr } // mockBucketHandle routes Object() calls to per-key mockObjectHandles. type mockBucketHandle struct { - objects map[string]*mockObjectHandle - listErr error + objects map[string]*mockObjectHandle + listErr error listItems []*storage.ObjectAttrs } @@ -389,4 +389,3 @@ func TestGCSStorage_StatNotFound(t *testing.T) { t.Errorf("unexpected error: %v", err) } } - diff --git a/plugin/external/remote_trigger.go b/plugin/external/remote_trigger.go index 1c077aa27..37aac41fc 100644 --- a/plugin/external/remote_trigger.go +++ b/plugin/external/remote_trigger.go @@ -12,11 +12,11 @@ import ( // Trigger lifecycle (Start/Stop) is managed through the plugin's module RPCs, // treating trigger handles as module handles. type RemoteTrigger struct { - typeName string - name string - handleID string - client pb.PluginServiceClient - config map[string]any + typeName string + name string + handleID string + client pb.PluginServiceClient + config map[string]any } // NewRemoteTrigger creates a remote trigger proxy. diff --git a/plugin/external/ui_plugin.go b/plugin/external/ui_plugin.go index 48bdfbb46..f36a2a404 100644 --- a/plugin/external/ui_plugin.go +++ b/plugin/external/ui_plugin.go @@ -22,11 +22,11 @@ type UIPluginEntry struct { // UIPluginInfo is the JSON representation of a UI plugin for API responses. type UIPluginInfo struct { - Name string `json:"name"` - Version string `json:"version"` - Description string `json:"description,omitempty"` + Name string `json:"name"` + Version string `json:"version"` + Description string `json:"description,omitempty"` NavItems []UINavItem `json:"navItems,omitempty"` - Loaded bool `json:"loaded"` + Loaded bool `json:"loaded"` } // UIPluginManager discovers and manages UI plugins under a shared plugins @@ -266,7 +266,7 @@ func (p *UIPluginNativePlugin) UIPages() []plugin.UIPageDef { return p.manager.UIPages(p.name) } -func (p *UIPluginNativePlugin) RegisterRoutes(_ *http.ServeMux) {} +func (p *UIPluginNativePlugin) RegisterRoutes(_ *http.ServeMux) {} func (p *UIPluginNativePlugin) OnEnable(_ plugin.PluginContext) error { return nil } func (p *UIPluginNativePlugin) OnDisable(_ plugin.PluginContext) error { return nil } diff --git a/plugin/rbac/aws_test.go b/plugin/rbac/aws_test.go index 3cbc112ba..93882b00c 100644 --- a/plugin/rbac/aws_test.go +++ b/plugin/rbac/aws_test.go @@ -16,14 +16,14 @@ import ( // mockIAMClient is a test double for IAMClient. type mockIAMClient struct { - simulateFunc func(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error) - listAttachedRolesFunc func(ctx context.Context, params *iam.ListAttachedRolePoliciesInput, optFns ...func(*iam.Options)) (*iam.ListAttachedRolePoliciesOutput, error) - listAttachedUsersFunc func(ctx context.Context, params *iam.ListAttachedUserPoliciesInput, optFns ...func(*iam.Options)) (*iam.ListAttachedUserPoliciesOutput, error) - getPolicyFunc func(ctx context.Context, params *iam.GetPolicyInput, optFns ...func(*iam.Options)) (*iam.GetPolicyOutput, error) - getPolicyVersionFunc func(ctx context.Context, params *iam.GetPolicyVersionInput, optFns ...func(*iam.Options)) (*iam.GetPolicyVersionOutput, error) - createPolicyFunc func(ctx context.Context, params *iam.CreatePolicyInput, optFns ...func(*iam.Options)) (*iam.CreatePolicyOutput, error) - createPolicyVerFunc func(ctx context.Context, params *iam.CreatePolicyVersionInput, optFns ...func(*iam.Options)) (*iam.CreatePolicyVersionOutput, error) - attachRolePolicyFunc func(ctx context.Context, params *iam.AttachRolePolicyInput, optFns ...func(*iam.Options)) (*iam.AttachRolePolicyOutput, error) + simulateFunc func(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error) + listAttachedRolesFunc func(ctx context.Context, params *iam.ListAttachedRolePoliciesInput, optFns ...func(*iam.Options)) (*iam.ListAttachedRolePoliciesOutput, error) + listAttachedUsersFunc func(ctx context.Context, params *iam.ListAttachedUserPoliciesInput, optFns ...func(*iam.Options)) (*iam.ListAttachedUserPoliciesOutput, error) + getPolicyFunc func(ctx context.Context, params *iam.GetPolicyInput, optFns ...func(*iam.Options)) (*iam.GetPolicyOutput, error) + getPolicyVersionFunc func(ctx context.Context, params *iam.GetPolicyVersionInput, optFns ...func(*iam.Options)) (*iam.GetPolicyVersionOutput, error) + createPolicyFunc func(ctx context.Context, params *iam.CreatePolicyInput, optFns ...func(*iam.Options)) (*iam.CreatePolicyOutput, error) + createPolicyVerFunc func(ctx context.Context, params *iam.CreatePolicyVersionInput, optFns ...func(*iam.Options)) (*iam.CreatePolicyVersionOutput, error) + attachRolePolicyFunc func(ctx context.Context, params *iam.AttachRolePolicyInput, optFns ...func(*iam.Options)) (*iam.AttachRolePolicyOutput, error) } func (m *mockIAMClient) SimulatePrincipalPolicy(ctx context.Context, params *iam.SimulatePrincipalPolicyInput, optFns ...func(*iam.Options)) (*iam.SimulatePrincipalPolicyOutput, error) { @@ -530,7 +530,7 @@ func TestIsValidAccountID(t *testing.T) { }{ {"123456789012", true}, {"000000000000", true}, - {"12345678901", false}, // 11 digits + {"12345678901", false}, // 11 digits {"1234567890123", false}, // 13 digits {"role/test", false}, {"", false}, diff --git a/plugins/http/modules.go b/plugins/http/modules.go index 6a777bfcd..db85ea030 100644 --- a/plugins/http/modules.go +++ b/plugins/http/modules.go @@ -92,21 +92,24 @@ func staticFileServerFactory(name string, cfg map[string]any) modular.Module { if p, ok := cfg["prefix"].(string); ok && p != "" { prefix = p } - spaFallback := true + var opts []module.StaticFileServerOption + spaFallback := false if sf, ok := cfg["spaFallback"].(bool); ok { spaFallback = sf } - cacheMaxAge := 3600 + if spaFallback { + opts = append(opts, module.WithSPAFallback()) + } if cma, ok := cfg["cacheMaxAge"].(int); ok { - cacheMaxAge = cma + opts = append(opts, module.WithCacheMaxAge(cma)) } else if cma, ok := cfg["cacheMaxAge"].(float64); ok { - cacheMaxAge = int(cma) + opts = append(opts, module.WithCacheMaxAge(int(cma))) } routerName := "" if rn, ok := cfg["router"].(string); ok { routerName = rn } - sfs := module.NewStaticFileServer(name, root, prefix, spaFallback, cacheMaxAge) + sfs := module.NewStaticFileServer(name, root, prefix, opts...) if routerName != "" { sfs.SetRouterName(routerName) } diff --git a/provider/gcp/plugin.go b/provider/gcp/plugin.go index 94b573c15..5685d6d1d 100644 --- a/provider/gcp/plugin.go +++ b/provider/gcp/plugin.go @@ -379,13 +379,13 @@ func (p *GCPProvider) fetchMetric(ctx context.Context, metricType, serviceName s metricType, serviceName, ) params := url.Values{ - "filter": {filter}, - "interval.startTime": {startTime.Format(time.RFC3339)}, - "interval.endTime": {endTime.Format(time.RFC3339)}, - "aggregation.alignmentPeriod": {fmt.Sprintf("%ds", windowSec)}, - "aggregation.perSeriesAligner": {"ALIGN_MEAN"}, - "aggregation.crossSeriesReducer": {"REDUCE_SUM"}, - "view": {"FULL"}, + "filter": {filter}, + "interval.startTime": {startTime.Format(time.RFC3339)}, + "interval.endTime": {endTime.Format(time.RFC3339)}, + "aggregation.alignmentPeriod": {fmt.Sprintf("%ds", windowSec)}, + "aggregation.perSeriesAligner": {"ALIGN_MEAN"}, + "aggregation.crossSeriesReducer": {"REDUCE_SUM"}, + "view": {"FULL"}, } endpoint := fmt.Sprintf("https://monitoring.googleapis.com/v3/projects/%s/timeSeries?%s", p.config.ProjectID, params.Encode()) diff --git a/provider/gcp/registry.go b/provider/gcp/registry.go index bf74814a2..71f6629bf 100644 --- a/provider/gcp/registry.go +++ b/provider/gcp/registry.go @@ -18,7 +18,7 @@ import ( // projects.locations.repositories.dockerImages.list API. type artifactRegistryDockerImagesResponse struct { DockerImages []artifactRegistryDockerImage `json:"dockerImages"` - NextPageToken string `json:"nextPageToken"` + NextPageToken string `json:"nextPageToken"` } // artifactRegistryDockerImage represents a single Docker image in Artifact Registry. diff --git a/provider/gcp/registry_test.go b/provider/gcp/registry_test.go index aeb3421cb..a78a19e52 100644 --- a/provider/gcp/registry_test.go +++ b/provider/gcp/registry_test.go @@ -255,10 +255,10 @@ func TestPullImage_Unauthorized(t *testing.T) { // TestParseImageRef tests the image reference parser. func TestParseImageRef(t *testing.T) { tests := []struct { - input string - wantHost string - wantPath string - wantRef string + input string + wantHost string + wantPath string + wantRef string }{ {"gcr.io/project/image:v1", "gcr.io", "project/image", "v1"}, {"us-central1-docker.pkg.dev/proj/repo/img:latest", "us-central1-docker.pkg.dev", "proj/repo/img", "latest"},