sg/internal/policy/package_spec.go (61 lines of code) (raw):

package policy import ( "bytes" "fmt" "os" "path/filepath" "strings" "text/template" "gopkg.in/yaml.v3" ) // RuleSpec specifies the policy rule settings. type RuleSpec struct { // DocLink specifies the policy rule document link format. // // The value will be formatted using text.Template. Following variables are available: // // - {{.Name}}: the name of the rule. // - {{.Kind}}: the kind of the rule. See `QueryKind` for available options. // - {{.SourceFileName}}: the source file name (without the .rego extension) of the rule. // If the rule is not defined in a source file, this will be empty. DocLink string `json:"doc_link,omitempty" yaml:"doc_link,omitempty"` } // PackageSpec specifies the package settings. type PackageSpec struct { // Rule specifies the policy rule settings. Rule *RuleSpec `json:"rule,omitempty" yaml:"rule,omitempty"` } // rule: // doc_link: https://example.com/docs/{{.Kind}}/{{.SourceFileName}}.md func defaultPackageSpec() PackageSpec { return PackageSpec{ Rule: &RuleSpec{}, } } // PackageSpecFileName is the default name of the package specification file. const PackageSpecFileName = "sg-package.yaml" func loadPackageSpecFromDir(dir string) (PackageSpec, error) { specFile := filepath.Join(dir, PackageSpecFileName) if b, err := os.ReadFile(specFile); err != nil { if os.IsNotExist(err) { return defaultPackageSpec(), nil } return PackageSpec{}, fmt.Errorf("failed to read package spec file: %w", err) } else { var spec PackageSpec if err := yaml.Unmarshal(b, &spec); err != nil { return PackageSpec{}, fmt.Errorf("failed to unmarshal package spec: %w", err) } return spec, nil } } // ResolveRuleDocLink resolves the rule document link. func ResolveRuleDocLink(spec PackageSpec, rule Rule) (string, error) { if spec.Rule == nil || spec.Rule.DocLink == "" { // not set return "", nil } tmpl, err := template.New("doc-link").Parse(spec.Rule.DocLink) if err != nil { return "", fmt.Errorf("parse %q as template: %w", spec.Rule.DocLink, err) } var b bytes.Buffer tmplPayload := map[string]interface{}{ "Name": rule.Name, "Kind": rule.Kind, "SourceFileName": "", } if rule.SourceLocation != nil { f := filepath.Base(rule.SourceLocation.File) f = strings.TrimSuffix(f, filepath.Ext(f)) tmplPayload["SourceFileName"] = f } if err := tmpl.Execute(&b, tmplPayload); err != nil { return "", fmt.Errorf("execute template: %w", err) } return b.String(), nil }