internal/changelog/fragment/creator.go (61 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package fragment
import (
"fmt"
"log"
"os"
"path"
"strings"
"time"
"github.com/spf13/afero"
)
// timestamp represent a function providing a timestamp.
// It's used to allow replacing the value with a known one during testing.
type timestamp func() time.Time
func NewCreator(fs afero.Fs, location string) FragmentCreator {
return FragmentCreator{
fs: fs,
location: location,
timestamp: time.Now,
}
}
// TestNewCreator sets up a FragmentCreator configured to be used in testing.
func TestNewCreator() FragmentCreator {
f := NewCreator(afero.NewMemMapFs(), "testdata")
tz, _ := time.LoadLocation("MST")
f.timestamp = func() time.Time { return time.Date(2006, time.January, 2, 15, 4, 5, 0, tz) }
return f
}
type FragmentCreator struct {
fs afero.Fs
location string
// timestamp allow overriding value in tests
timestamp timestamp
}
func (c FragmentCreator) Location() string {
return c.location
}
// filename computes the filename for the changelog fragment to be created.
// To provide unique names the provided slug is prepended with current timestamp.
func (f FragmentCreator) filename(slug string) string {
filename := fmt.Sprintf("%d-%s.yaml", f.timestamp().Unix(), sanitizeFilename(slug))
return filename
}
var fragmentLocPerm = os.FileMode(0770)
var fragmentPerm = os.FileMode(0660)
// Create marshal changelog fragment and persist it to file.
func (c FragmentCreator) Create(slug string) error {
if err := c.fs.MkdirAll(c.location, fragmentLocPerm); err != nil {
return fmt.Errorf("cannot create fragment location folder: %v", err)
}
template, err := Template(slug)
if err != nil {
return err
}
filePath := path.Join(c.location, c.filename(slug))
if err := afero.WriteFile(c.fs, filePath, template, fragmentPerm); err != nil {
return err
}
log.Print("created fragment ", filePath)
return nil
}
// sanitizeFilename takes care of removing dangerous elements from a string so it can be safely
// used as a filename.
// NOTE: does not prevent command injection or ensure complete escaping of input
func sanitizeFilename(s string) string {
s = strings.Replace(s, " ", "-", -1)
s = strings.Replace(s, "/", "-", -1)
s = strings.Replace(s, "\\", "-", -1)
s = strings.Replace(s, ":", "", -1)
s = strings.Replace(s, "'", "", -1)
return s
}