Thank you for your interest in contributing to kspec! This guide covers the most common contribution: adding a new provider.
kspec uses a self-registration pattern for providers. Adding a new provider requires only two files:
provider/<name>/provider.go- Provider implementationprovider/<name>/register.go- Self-registration
mkdir -p provider/myproviderCreate provider/myprovider/provider.go:
package myprovider
import (
"context"
"github.com/kopexa-grc/kspec/core"
)
// Provider implements core.Provider for MyProvider.
type Provider struct {
config map[string]string
}
// NewProvider creates a new MyProvider provider instance.
func NewProvider() *Provider {
return &Provider{}
}
// Name returns the provider identifier.
func (p *Provider) Name() string {
return "myprovider"
}
// Configure sets up the provider with the given configuration.
func (p *Provider) Configure(config map[string]string) error {
p.config = config
// Validate required configuration here
return nil
}
// Resources returns the resource specifications this provider can fetch.
func (p *Provider) Resources() []core.ResourceSpec {
return []core.ResourceSpec{
// Add your resource implementations here
}
}Create provider/myprovider/register.go:
package myprovider
import (
"github.com/kopexa-grc/kspec/core"
"github.com/kopexa-grc/kspec/provider/registry"
)
func init() {
registry.Register(®istry.ProviderDefinition{
Name: "myprovider",
Aliases: []string{"mp"}, // Optional aliases
Description: "Scan MyProvider resources for compliance",
Flags: []registry.FlagDefinition{
{
Name: "api-token",
Description: "API token for authentication",
EnvVar: "MYPROVIDER_API_TOKEN",
Required: true,
},
{
Name: "region",
Description: "Region to scan",
Default: "us-east-1",
},
},
AssetTypes: []registry.AssetDefinition{
{
Name: "account",
Description: "Scan entire account",
// No args = no positional arguments required
},
},
Factory: func() core.Provider {
return NewProvider()
},
})
}Add your provider to provider/all/all.go:
import (
// ... existing imports ...
_ "github.com/kopexa-grc/kspec/provider/myprovider"
)Your provider is now available:
kspec scan myprovider -d policies
kspec scan mp -d policies # Using alias| Field | Type | Description |
|---|---|---|
Name |
string |
Primary provider name (used in CLI) |
Aliases |
[]string |
Alternative names for the provider |
Description |
string |
Help text shown in CLI |
Flags |
[]FlagDefinition |
Provider-specific CLI flags |
AssetTypes |
[]AssetDefinition |
Supported asset types |
ConfigMapping |
map[string]string |
Maps flag names to config keys |
Factory |
func() core.Provider |
Creates provider instance |
| Field | Type | Description |
|---|---|---|
Name |
string |
Flag name (e.g., "api-token" → --api-token) |
Short |
string |
Single-char shorthand (e.g., "t" → -t) |
Description |
string |
Help text for the flag |
Default |
string |
Default value |
Required |
bool |
Whether flag is required |
EnvVar |
string |
Environment variable fallback |
IsBool |
bool |
Whether flag is a boolean |
| Field | Type | Description |
|---|---|---|
Name |
string |
Asset type name (e.g., "org", "repo") |
Aliases |
[]string |
Alternative names |
Description |
string |
Help text |
Args |
[]ArgDefinition |
Positional arguments |
DefaultName |
string |
Default asset name if no args |
ScannerKey |
string |
Override for scanner lookup key |
| Field | Type | Description |
|---|---|---|
Name |
string |
Argument name (shown in usage) |
Description |
string |
Help text |
Required |
bool |
Whether argument is required |
ConfigKey |
string |
Key used in config map |
Providers with one asset type run directly on the provider command:
AssetTypes: []registry.AssetDefinition{
{
Name: "account",
Description: "Scan AWS account",
},
},Usage: kspec scan aws -d policies
Providers with multiple asset types get subcommands:
AssetTypes: []registry.AssetDefinition{
{
Name: "org",
Description: "Scan GitHub organization",
Args: []registry.ArgDefinition{
{Name: "owner", Required: true, ConfigKey: "owner"},
},
},
{
Name: "repo",
Description: "Scan GitHub repository",
Args: []registry.ArgDefinition{
{Name: "repository", Required: true, ConfigKey: "repository"},
},
},
},Usage:
kspec scan github org my-org -d policieskspec scan github repo owner/repo -d policies
When asset types need different scanner behavior:
AssetTypes: []registry.AssetDefinition{
{
Name: "file",
Description: "Scan single SBOM file",
ScannerKey: "sbom-file",
Args: []registry.ArgDefinition{
{Name: "path", Required: true, ConfigKey: "sbom_path"},
},
},
{
Name: "dir",
Description: "Scan directory of SBOMs",
ScannerKey: "sbom-directory",
Args: []registry.ArgDefinition{
{Name: "path", Required: true, ConfigKey: "sbom_path"},
},
},
},Run tests for your provider:
go test ./provider/myprovider/...Run linter:
golangci-lint run ./provider/myprovider/...- Follow Go conventions and existing code style
- Add comments for exported types and functions
- Use meaningful variable names
- Handle errors appropriately
Open an issue at https://github.com/kopexa-grc/kspec/issues