|
1 | 1 | 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 | | -} |
0 commit comments