Skip to content

Commit 27175dc

Browse files
committed
Move commands to internal package
Signed-off-by: Adolfo García Veytia (Puerco) <adolfo.garcia@uservers.net>
1 parent 1c09859 commit 27175dc

File tree

5 files changed

+139
-273
lines changed

5 files changed

+139
-273
lines changed

cmd/baseline.go

Lines changed: 0 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -1,211 +1 @@
11
package main
2-
3-
import (
4-
"fmt"
5-
"log"
6-
"os"
7-
"path/filepath"
8-
"strings"
9-
"text/template"
10-
11-
"gopkg.in/yaml.v3"
12-
)
13-
14-
// Struct for representing each entry
15-
type Criterion struct {
16-
ID string `yaml:"id"`
17-
MaturityLevel int `yaml:"maturity_level"`
18-
Category string `yaml:"category"`
19-
CriterionText string `yaml:"criterion"`
20-
Rationale string `yaml:"rationale"`
21-
Implementation string `yaml:"implementation"`
22-
Details string `yaml:"details"`
23-
ControlMappings map[string]string `yaml:"control_mappings"`
24-
SecurityInsightsValue string `yaml:"security_insights_value"`
25-
}
26-
27-
// Struct for holding the entire YAML structure
28-
type Baseline struct {
29-
Categories map[string]Category
30-
Lexicon []LexiconEntry
31-
}
32-
33-
type Category struct {
34-
CategoryName string `yaml:"category"`
35-
Description string `yaml:"description"`
36-
Criteria []Criterion `yaml:"criteria"`
37-
}
38-
39-
type LexiconEntry struct {
40-
Term string `yaml:"term"`
41-
Definition string `yaml:"definition"`
42-
Synonyms []string `yaml:"synonyms"`
43-
References []string `yaml:"references"`
44-
}
45-
46-
func hardcodedCategories() []string {
47-
return []string{
48-
"AC",
49-
"BR",
50-
"DO",
51-
"GV",
52-
"LE",
53-
"QA",
54-
"SA",
55-
"VM",
56-
}
57-
}
58-
59-
func filename(name string) string {
60-
return filepath.Join(ContentDir, fmt.Sprintf("OSPS-%s.yaml", name))
61-
}
62-
63-
func newBaseline() (Baseline, error) {
64-
lexicon, err := newLexicon()
65-
if err != nil {
66-
return Baseline{}, fmt.Errorf("error reading lexicon: %w", err)
67-
}
68-
b := Baseline{
69-
Categories: make(map[string]Category),
70-
Lexicon: lexicon,
71-
}
72-
var failed bool
73-
for _, categoryName := range hardcodedCategories() {
74-
category, err := newCategory(categoryName)
75-
if err != nil {
76-
failed = true
77-
log.Printf("error reading category %s: %s", categoryName, err.Error())
78-
}
79-
b.Categories[categoryName] = category
80-
}
81-
if failed {
82-
return b, fmt.Errorf("error setting up baseline")
83-
}
84-
return b, b.validate()
85-
}
86-
87-
func (b Baseline) validate() error {
88-
var entryIDs []string
89-
var failed bool
90-
for _, category := range b.Categories {
91-
for _, entry := range category.Criteria {
92-
if contains(entryIDs, entry.ID) {
93-
failed = true
94-
log.Printf("duplicate ID for 'criterion' for %s", entry.ID)
95-
}
96-
if entry.ID == "" {
97-
failed = true
98-
log.Printf("missing ID for 'criterion' %s", entry.ID)
99-
}
100-
if entry.CriterionText == "" {
101-
failed = true
102-
log.Printf("missing 'criterion' text for %s", entry.ID)
103-
}
104-
// For after all fields are populated:
105-
// if entry.Rationale == "" {
106-
// failed = true
107-
// log.Printf("missing 'rationale' for %s", entry.ID)
108-
// }
109-
// if entry.Details == "" {
110-
// failed = true
111-
// log.Printf("missing 'details' for %s", entry.ID)
112-
// }
113-
entryIDs = append(entryIDs, entry.ID)
114-
}
115-
}
116-
if failed {
117-
return fmt.Errorf("error validating baseline")
118-
}
119-
return nil
120-
}
121-
122-
func contains(list []string, term string) bool {
123-
for _, item := range list {
124-
if item == term {
125-
return true
126-
}
127-
}
128-
return false
129-
}
130-
131-
func newCategory(categoryName string) (Category, error) {
132-
file, err := os.Open(filename(categoryName))
133-
if err != nil {
134-
return Category{}, fmt.Errorf("error opening file: %v", err)
135-
}
136-
defer file.Close()
137-
138-
var category Category
139-
140-
decoder := yaml.NewDecoder(file)
141-
decoder.KnownFields(true)
142-
if err := decoder.Decode(&category); err != nil {
143-
return category, fmt.Errorf("error decoding YAML: %v", err)
144-
}
145-
return category, nil
146-
}
147-
148-
func newLexicon() ([]LexiconEntry, error) {
149-
file, err := os.Open(LexiconPath)
150-
if err != nil {
151-
return nil, fmt.Errorf("error opening file: %v", err)
152-
}
153-
defer file.Close()
154-
155-
var lexicon []LexiconEntry
156-
157-
decoder := yaml.NewDecoder(file)
158-
decoder.KnownFields(true)
159-
if err := decoder.Decode(&lexicon); err != nil {
160-
return nil, fmt.Errorf("error decoding YAML: %v", err)
161-
}
162-
return lexicon, nil
163-
}
164-
165-
func (b *Baseline) Generate() error {
166-
// Open or create the output file
167-
oDir := filepath.Dir(OutputPath)
168-
err := os.MkdirAll(oDir, os.ModePerm)
169-
if err != nil {
170-
return fmt.Errorf("error creating output directory %s: %w", oDir, err)
171-
}
172-
outputFile, err := os.Create(OutputPath)
173-
if err != nil {
174-
return fmt.Errorf("error creating output file %s: %w", OutputPath, err)
175-
}
176-
defer outputFile.Close()
177-
178-
// Read the markdown template from the external file
179-
templateContent, err := os.ReadFile(TemplatePath)
180-
if err != nil {
181-
return fmt.Errorf("error reading template file: %w", err)
182-
}
183-
184-
// Create and parse the template
185-
tmpl, err := template.New("baseline").Funcs(template.FuncMap{
186-
// Template function to remove newlines and collapse text
187-
"collapseNewlines": func(s string) string {
188-
return strings.ReplaceAll(s, "\n", " ")
189-
},
190-
"addLinks": func(s string) string {
191-
return addLinksTemplateFunction(s)
192-
},
193-
"asLink": func(s string) string {
194-
return asLinkTemplateFunction(s)
195-
},
196-
"subtract": func(a, b int) int {
197-
return a - b
198-
},
199-
}).Parse(string(templateContent))
200-
if err != nil {
201-
return fmt.Errorf("error parsing template: %w", err)
202-
}
203-
204-
// Execute the template and write to the output file
205-
err = tmpl.Execute(outputFile, b)
206-
if err != nil {
207-
return fmt.Errorf("error executing template: %w", err)
208-
}
209-
210-
return nil
211-
}

cmd/internal/cmd/compile.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 The OSPS Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package cmd
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
"os"
10+
11+
"github.com/ossf/security-baseline/pkg/baseline"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
type compileOptions struct {
16+
outPath string
17+
baselinePath string
18+
templatePath string
19+
}
20+
21+
// Validate the options in context with arguments
22+
func (o *compileOptions) Validate() error {
23+
errs := []error{}
24+
25+
if o.outPath == "" {
26+
errs = append(errs, errors.New("output path not specified"))
27+
}
28+
29+
if o.baselinePath == "" {
30+
errs = append(errs, errors.New("baseline path not specified"))
31+
}
32+
return errors.Join(errs...)
33+
}
34+
35+
func (o *compileOptions) AddFlags(cmd *cobra.Command) {
36+
cmd.PersistentFlags().StringVarP(
37+
&o.baselinePath, "baseline", "b", "../baseline", "path to directory containing the baseline YAML data",
38+
)
39+
40+
cmd.PersistentFlags().StringVarP(
41+
&o.outPath, "output", "o", "", "path to output file (defaults to STDOUT)",
42+
)
43+
44+
cmd.PersistentFlags().StringVarP(
45+
&o.templatePath, "template", "t", "template.md", "path to the markdown template file",
46+
)
47+
}
48+
49+
// addCompile adds the compile subcommand to the parent command
50+
func addCompile(parentCmd *cobra.Command) {
51+
opts := compileOptions{}
52+
compileCmd := &cobra.Command{
53+
Use: "compile [file]",
54+
Short: "Compile a YAML file of security criteria",
55+
SilenceUsage: false,
56+
SilenceErrors: true,
57+
PreRunE: func(_ *cobra.Command, args []string) error {
58+
if opts.outPath != "" && len(args) > 1 && opts.outPath != args[1] {
59+
return fmt.Errorf("output path specified twice")
60+
}
61+
62+
if len(args) > 1 {
63+
opts.outPath = args[1]
64+
}
65+
return nil
66+
},
67+
RunE: func(cmd *cobra.Command, args []string) error {
68+
if err := opts.Validate(); err != nil {
69+
return err
70+
}
71+
72+
cmd.SilenceUsage = true
73+
74+
loader := baseline.NewLoader()
75+
loader.DataPath = opts.baselinePath
76+
77+
bline, err := loader.Load()
78+
if err != nil {
79+
return err
80+
}
81+
82+
// Generate the rendered version
83+
gen := baseline.NewGenerator()
84+
gen.TemplatePath = opts.templatePath
85+
86+
if err := gen.ExportMarkdown(bline, opts.outPath); err != nil {
87+
return fmt.Errorf("writing mardown render: %w", err)
88+
}
89+
90+
fmt.Fprintf(os.Stderr, "\nBaseline rendered to %s:\n\nCategories:\n", opts.outPath)
91+
for c := range bline.Categories {
92+
fmt.Fprintf(os.Stderr, " OSPS-%s: %d criteria\n", c, len(bline.Categories[c].Criteria))
93+
}
94+
fmt.Fprintf(os.Stderr, "\n+ %d lexicon entries\n", len(bline.Lexicon))
95+
96+
return nil
97+
},
98+
}
99+
opts.AddFlags(compileCmd)
100+
parentCmd.AddCommand(compileCmd)
101+
}

cmd/internal/cmd/root.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 The OSPS Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package cmd
5+
6+
import "github.com/spf13/cobra"
7+
8+
func Execute() error {
9+
rootCmd := &cobra.Command{
10+
Use: "baseline-compiler",
11+
Long: `Baseline Compiler reads the Basline YAML and outputs it as a markdown document.`,
12+
Run: func(cmd *cobra.Command, args []string) {
13+
cmd.Help() //nolint
14+
},
15+
}
16+
addCompile(rootCmd)
17+
18+
return rootCmd.Execute()
19+
}

0 commit comments

Comments
 (0)