Add a --write and --diff flag; require --write to be set before making any changes, for safety
This commit is contained in:
5
go.mod
5
go.mod
@@ -3,3 +3,8 @@ module cheese
|
||||
go 1.24.4
|
||||
|
||||
require gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
require (
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@@ -1,3 +1,7 @@
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func generateEnv(yamlFile string) {
|
||||
func generateEnv(yamlFile string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||
config := loadConfig(yamlFile)
|
||||
|
||||
// Check that all local directories exist
|
||||
@@ -24,7 +24,7 @@ func generateEnv(yamlFile string) {
|
||||
|
||||
// Create combined roots.pem file
|
||||
rootsPemPath := filepath.Join(logEntry.LocalDirectory, "roots.pem")
|
||||
err := createCombinedRootsPemWithStatus(config.Roots, logEntry.ExtraRoots, rootsPemPath)
|
||||
err := createCombinedRootsPemWithStatus(config.Roots, logEntry.ExtraRoots, rootsPemPath, wantDiff, allowWrite, useColor)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create %s: %v", rootsPemPath, err)
|
||||
}
|
||||
@@ -53,14 +53,14 @@ func generateEnv(yamlFile string) {
|
||||
tesseractArgs := strings.Join(args, " ")
|
||||
envContent := fmt.Sprintf("TESSERACT_ARGS=\"%s\"\nOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318\n", tesseractArgs)
|
||||
|
||||
err = writeFileWithStatus(envPath, []byte(envContent))
|
||||
err = writeFileWithStatus(envPath, []byte(envContent), wantDiff, allowWrite, useColor)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to write %s: %v", envPath, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createCombinedRootsPemWithStatus(rootsFile, extraRootsFile, outputPath string) error {
|
||||
func createCombinedRootsPemWithStatus(rootsFile, extraRootsFile, outputPath string, wantDiff bool, allowWrite bool, useColor bool) error {
|
||||
// Read main roots file
|
||||
var combinedContent []byte
|
||||
if rootsFile != "" {
|
||||
@@ -80,5 +80,5 @@ func createCombinedRootsPemWithStatus(rootsFile, extraRootsFile, outputPath stri
|
||||
combinedContent = append(combinedContent, extraRootsData...)
|
||||
}
|
||||
|
||||
return writeFileWithStatus(outputPath, combinedContent)
|
||||
return writeFileWithStatus(outputPath, combinedContent, wantDiff, allowWrite, useColor)
|
||||
}
|
||||
|
@@ -110,7 +110,7 @@ type TemporalInterval struct {
|
||||
EndExclusive string `json:"end_exclusive"`
|
||||
}
|
||||
|
||||
func generateHTML(yamlFile string) {
|
||||
func generateHTML(yamlFile string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||
config := loadConfig(yamlFile)
|
||||
|
||||
// Check that all local directories exist
|
||||
@@ -145,14 +145,14 @@ func generateHTML(yamlFile string) {
|
||||
}
|
||||
|
||||
// Write file with status
|
||||
err = writeFileWithStatus(indexPath, buf.Bytes())
|
||||
err = writeFileWithStatus(indexPath, buf.Bytes(), wantDiff, allowWrite, useColor)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to write HTML to %s: %v", indexPath, err)
|
||||
}
|
||||
|
||||
// Generate log.v3.json for this log
|
||||
jsonPath := filepath.Join(logEntry.LocalDirectory, "log.v3.json")
|
||||
err = generateLogJSONWithStatus(logEntry, jsonPath)
|
||||
err = generateLogJSONWithStatus(logEntry, jsonPath, wantDiff, allowWrite, useColor)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate %s: %v", jsonPath, err)
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func computeKeyInfo(logEntry *Log) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateLogJSONWithStatus(logEntry Log, outputPath string) error {
|
||||
func generateLogJSONWithStatus(logEntry Log, outputPath string, wantDiff bool, allowWrite bool, useColor bool) error {
|
||||
logJSON := LogV3JSON{
|
||||
Description: fmt.Sprintf("%s.log.ct.ipng.ch", logEntry.ShortName),
|
||||
SubmissionURL: fmt.Sprintf("%s/", logEntry.SubmissionPrefix),
|
||||
@@ -228,5 +228,5 @@ func generateLogJSONWithStatus(logEntry Log, outputPath string) error {
|
||||
return fmt.Errorf("failed to marshal JSON: %v", err)
|
||||
}
|
||||
|
||||
return writeFileWithStatus(outputPath, jsonData)
|
||||
return writeFileWithStatus(outputPath, jsonData, wantDiff, allowWrite, useColor)
|
||||
}
|
||||
|
@@ -12,7 +12,12 @@ import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func generateKeys(yamlFile string) {
|
||||
func generateKeys(yamlFile string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||
if !allowWrite {
|
||||
fmt.Printf("Key generation requires --write flag\n")
|
||||
return
|
||||
}
|
||||
|
||||
config := loadConfig(yamlFile)
|
||||
|
||||
// Generate keys for each log
|
||||
|
@@ -5,8 +5,12 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hexops/gotextdiff"
|
||||
"github.com/hexops/gotextdiff/myers"
|
||||
"github.com/hexops/gotextdiff/span"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -41,6 +45,9 @@ type Log struct {
|
||||
|
||||
func main() {
|
||||
configFile := flag.String("c", "./tesseract-staging.yaml", "Path to the YAML configuration file")
|
||||
wantDiff := flag.Bool("diff", false, "Show unified diff of changes")
|
||||
allowWrite := flag.Bool("write", false, "Allow writing files (required for actual file modifications)")
|
||||
noColor := flag.Bool("no-color", false, "Disable colored diff output")
|
||||
flag.Parse()
|
||||
|
||||
args := flag.Args()
|
||||
@@ -51,15 +58,15 @@ func main() {
|
||||
|
||||
switch args[0] {
|
||||
case "gen-html":
|
||||
generateHTML(*configFile)
|
||||
generateHTML(*configFile, *wantDiff, *allowWrite, !*noColor)
|
||||
case "gen-env":
|
||||
generateEnv(*configFile)
|
||||
generateEnv(*configFile, *wantDiff, *allowWrite, !*noColor)
|
||||
case "gen-key":
|
||||
generateKeys(*configFile)
|
||||
generateKeys(*configFile, *wantDiff, *allowWrite, !*noColor)
|
||||
case "gen-nginx":
|
||||
generateNginx(*configFile)
|
||||
generateNginx(*configFile, *wantDiff, *allowWrite, !*noColor)
|
||||
case "gen-roots":
|
||||
generateRoots(args[1:])
|
||||
generateRoots(args[1:], *wantDiff, *allowWrite, !*noColor)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", args[0])
|
||||
showHelp()
|
||||
@@ -97,17 +104,94 @@ func loadConfig(yamlFile string) Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func writeFileWithStatus(filename string, content []byte) error {
|
||||
// ANSI color codes
|
||||
const (
|
||||
colorReset = "\033[0m"
|
||||
colorRed = "\033[31m"
|
||||
colorGreen = "\033[32m"
|
||||
colorYellow = "\033[33m"
|
||||
colorCyan = "\033[36m"
|
||||
)
|
||||
|
||||
// colorizeUnifiedDiff adds ANSI color codes to unified diff output
|
||||
func colorizeUnifiedDiff(diff string) string {
|
||||
lines := strings.Split(diff, "\n")
|
||||
var colorizedLines []string
|
||||
|
||||
for _, line := range lines {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "---"):
|
||||
// File deletion header in cyan
|
||||
colorizedLines = append(colorizedLines, colorCyan+line+colorReset)
|
||||
case strings.HasPrefix(line, "+++"):
|
||||
// File addition header in cyan
|
||||
colorizedLines = append(colorizedLines, colorCyan+line+colorReset)
|
||||
case strings.HasPrefix(line, "@@"):
|
||||
// Hunk header in yellow
|
||||
colorizedLines = append(colorizedLines, colorYellow+line+colorReset)
|
||||
case strings.HasPrefix(line, "-"):
|
||||
// Deleted lines in red
|
||||
colorizedLines = append(colorizedLines, colorRed+line+colorReset)
|
||||
case strings.HasPrefix(line, "+"):
|
||||
// Added lines in green
|
||||
colorizedLines = append(colorizedLines, colorGreen+line+colorReset)
|
||||
default:
|
||||
// Context lines unchanged
|
||||
colorizedLines = append(colorizedLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(colorizedLines, "\n")
|
||||
}
|
||||
|
||||
func writeFileWithStatus(filename string, content []byte, wantDiff bool, allowWrite bool, useColor bool) error {
|
||||
existingContent, err := os.ReadFile(filename)
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Printf("Creating %s\n", filename)
|
||||
isNew := os.IsNotExist(err)
|
||||
isUnchanged := false
|
||||
|
||||
if isNew {
|
||||
if allowWrite {
|
||||
fmt.Printf("Creating %s\n", filename)
|
||||
} else {
|
||||
fmt.Printf("Would create %s\n", filename)
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to read existing file %s: %v", filename, err)
|
||||
} else if string(existingContent) == string(content) {
|
||||
fmt.Printf("Unchanged %s\n", filename)
|
||||
return nil
|
||||
isUnchanged = true
|
||||
} else {
|
||||
fmt.Printf("Updating %s\n", filename)
|
||||
if allowWrite {
|
||||
fmt.Printf("Updating %s\n", filename)
|
||||
} else {
|
||||
fmt.Printf("Would update %s\n", filename)
|
||||
}
|
||||
}
|
||||
|
||||
if wantDiff && !isUnchanged {
|
||||
if isNew {
|
||||
// For new files, show the entire content as added
|
||||
edits := myers.ComputeEdits(span.URIFromPath(filename), "", string(content))
|
||||
diff := fmt.Sprint(gotextdiff.ToUnified("/dev/null", filename, "", edits))
|
||||
if useColor {
|
||||
fmt.Print(colorizeUnifiedDiff(diff))
|
||||
} else {
|
||||
fmt.Print(diff)
|
||||
}
|
||||
} else {
|
||||
// For existing files, show the diff
|
||||
edits := myers.ComputeEdits(span.URIFromPath(filename), string(existingContent), string(content))
|
||||
diff := fmt.Sprint(gotextdiff.ToUnified(filename, filename+".new", string(existingContent), edits))
|
||||
if useColor {
|
||||
fmt.Print(colorizeUnifiedDiff(diff))
|
||||
} else {
|
||||
fmt.Print(diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isUnchanged || !allowWrite {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = os.WriteFile(filename, content, 0644)
|
||||
@@ -119,8 +203,17 @@ func writeFileWithStatus(filename string, content []byte) error {
|
||||
|
||||
func showHelp() {
|
||||
fmt.Printf("Usage: %s [options] <command>\n\n", os.Args[0])
|
||||
fmt.Printf("Note: Flags must come before the command name.\n\n")
|
||||
fmt.Printf("Options:\n")
|
||||
fmt.Printf(" -c <file> Path to YAML configuration file (default: ./tesseract-staging.yaml)\n\n")
|
||||
fmt.Printf(" -c <file> Path to YAML configuration file (default: ./tesseract-staging.yaml)\n")
|
||||
fmt.Printf(" --diff Show unified diff of changes without writing files\n")
|
||||
fmt.Printf(" --write Allow writing files (required for actual file modifications)\n")
|
||||
fmt.Printf(" --no-color Disable colored diff output\n\n")
|
||||
fmt.Printf("Examples:\n")
|
||||
fmt.Printf(" %s --diff gen-html # Show colored diffs without writing\n", os.Args[0])
|
||||
fmt.Printf(" %s --diff --no-color gen-html # Show plain diffs without writing\n", os.Args[0])
|
||||
fmt.Printf(" %s --write gen-html # Write files\n", os.Args[0])
|
||||
fmt.Printf(" %s --diff --write gen-html # Show colored diffs and write files\n\n", os.Args[0])
|
||||
fmt.Printf("Commands:\n")
|
||||
fmt.Printf(" gen-html Generate index.html and log.v3.json files in each log's localdirectory.\n")
|
||||
fmt.Printf(" Creates HTML pages with log information and CT log metadata JSON.\n")
|
||||
|
@@ -76,7 +76,7 @@ type NginxTemplateData struct {
|
||||
ListenPort string
|
||||
}
|
||||
|
||||
func generateNginx(yamlFile string) {
|
||||
func generateNginx(yamlFile string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||
config := loadConfig(yamlFile)
|
||||
|
||||
// Extract port from first listen address
|
||||
@@ -123,7 +123,7 @@ func generateNginx(yamlFile string) {
|
||||
}
|
||||
|
||||
// Write file with status
|
||||
err = writeFileWithStatus(outputPath, buf.Bytes())
|
||||
err = writeFileWithStatus(outputPath, buf.Bytes(), wantDiff, allowWrite, useColor)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write nginx config file %s: %v\n", outputPath, err)
|
||||
continue
|
||||
|
@@ -16,7 +16,7 @@ type CTLogRootsResponse struct {
|
||||
Certificates []string `json:"certificates"`
|
||||
}
|
||||
|
||||
func generateRoots(args []string) {
|
||||
func generateRoots(args []string, wantDiff bool, allowWrite bool, useColor bool) {
|
||||
sourceURL := "https://rennet2027h2.log.ct.ipng.ch/"
|
||||
outputFile := "roots.pem"
|
||||
|
||||
@@ -107,7 +107,7 @@ func generateRoots(args []string) {
|
||||
}
|
||||
|
||||
// Write all certificates to file with status
|
||||
err = writeFileWithStatus(outputFile, pemBuffer.Bytes())
|
||||
err = writeFileWithStatus(outputFile, pemBuffer.Bytes(), wantDiff, allowWrite, useColor)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to write output file %s: %v", outputFile, err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user