mirror of
https://github.com/smallstep/cli.git
synced 2025-04-19 10:42:15 +03:00
161 lines
4.1 KiB
Go
161 lines
4.1 KiB
Go
package utils
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/smallstep/cli-utils/command"
|
|
"github.com/smallstep/cli-utils/errs"
|
|
"github.com/smallstep/cli-utils/ui"
|
|
)
|
|
|
|
var (
|
|
// ErrFileExists is the error returned if a file exists.
|
|
ErrFileExists = errors.New("file exists")
|
|
|
|
// ErrIsDir is the error returned if the file is a directory.
|
|
ErrIsDir = errors.New("file is a directory")
|
|
|
|
// SnippetHeader is the header of a step generated snippet in a
|
|
// configuration file.
|
|
SnippetHeader = "# autogenerated by step"
|
|
|
|
// SnippetFooter is the header of a step generated snippet in a
|
|
// configuration file.
|
|
SnippetFooter = "# end"
|
|
)
|
|
|
|
// WriteFile wraps os.WriteFile with a prompt to overwrite a file if
|
|
// the file exists. It returns ErrFileExists if the user picks to not overwrite
|
|
// the file. If force is set to true, the prompt will not be presented and the
|
|
// file if exists will be overwritten.
|
|
func WriteFile(filename string, data []byte, perm os.FileMode) error {
|
|
if command.IsForce() {
|
|
return os.WriteFile(filename, data, perm)
|
|
}
|
|
|
|
st, err := os.Stat(filename)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return os.WriteFile(filename, data, perm)
|
|
}
|
|
return errors.Wrapf(err, "error reading information for %s", filename)
|
|
}
|
|
|
|
if st.IsDir() {
|
|
return ErrIsDir
|
|
}
|
|
|
|
str, err := ui.Prompt(fmt.Sprintf("Would you like to overwrite %s [y/n]", filename), ui.WithValidateYesNo())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch strings.ToLower(strings.TrimSpace(str)) {
|
|
case "y", "yes":
|
|
case "n", "no":
|
|
return ErrFileExists
|
|
}
|
|
|
|
return os.WriteFile(filename, data, perm)
|
|
}
|
|
|
|
// AppendNewLine appends the given data at the end of the file. If the last
|
|
// character of the file does not contain an LF it prepends it to the data.
|
|
func AppendNewLine(filename string, data []byte, perm os.FileMode) error {
|
|
f, err := OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, perm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Read last character
|
|
if st, err := f.File.Stat(); err == nil && st.Size() != 0 {
|
|
last := make([]byte, 1)
|
|
f.Seek(-1, 2)
|
|
f.Read(last)
|
|
if last[0] != '\n' {
|
|
f.WriteString("\n")
|
|
}
|
|
}
|
|
f.Write(data)
|
|
return f.Close()
|
|
}
|
|
|
|
// WriteSnippet writes the given data on the given filename. It surrounds the
|
|
// data with a header and footer, and it will replace the previous one.
|
|
func WriteSnippet(filename string, data []byte, perm os.FileMode) error {
|
|
// Get file permissions
|
|
if st, err := os.Stat(filename); err == nil {
|
|
perm = st.Mode()
|
|
} else if !os.IsNotExist(err) {
|
|
return errs.FileError(err, filename)
|
|
}
|
|
|
|
// Read file contents
|
|
b, err := os.ReadFile(filename)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return errs.FileError(err, filename)
|
|
}
|
|
|
|
// Detect previous configuration
|
|
_, start, end := findConfiguration(bytes.NewReader(b))
|
|
|
|
// Replace previous configuration
|
|
f, err := OpenFile(filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perm)
|
|
if err != nil {
|
|
return errs.FileError(err, filename)
|
|
}
|
|
if len(b) > 0 {
|
|
f.Write(b[:start])
|
|
if start == end {
|
|
f.WriteString("\n")
|
|
}
|
|
}
|
|
fmt.Fprintf(f, "%s @ %s\n", SnippetHeader, time.Now().UTC().Format(time.RFC3339))
|
|
f.Write(data)
|
|
if !bytes.HasSuffix(data, []byte("\n")) {
|
|
f.WriteString("\n")
|
|
}
|
|
f.WriteString(SnippetFooter + "\n")
|
|
if len(b) > 0 {
|
|
f.Write(b[end:])
|
|
}
|
|
return f.Close()
|
|
}
|
|
|
|
type offsetCounter struct {
|
|
offset int64
|
|
}
|
|
|
|
func (o *offsetCounter) ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
advance, token, err = bufio.ScanLines(data, atEOF)
|
|
o.offset += int64(advance)
|
|
return
|
|
}
|
|
|
|
func findConfiguration(r io.Reader) (lines []string, start, end int64) {
|
|
var inConfig bool
|
|
counter := new(offsetCounter)
|
|
scanner := bufio.NewScanner(r)
|
|
scanner.Split(counter.ScanLines)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
switch {
|
|
case !inConfig && strings.HasPrefix(line, SnippetHeader):
|
|
inConfig = true
|
|
start = counter.offset - int64(len(line)+1)
|
|
case inConfig && strings.HasPrefix(line, SnippetFooter):
|
|
return lines, start, counter.offset
|
|
case inConfig:
|
|
lines = append(lines, line)
|
|
}
|
|
}
|
|
|
|
return lines, counter.offset, counter.offset
|
|
}
|