mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-26 16:09:45 -05:00
005e865a13
* [wip] use state file in CLI Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> tidy Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * use state file in CLI Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> take clusterConfig from IDFile for compat Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> various fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> wip Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add GCP-specific values in Helm loader test Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove unnecessary pointer Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * write ClusterValues in one step Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * move stub to test file Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove mention of id-file Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * move output to `migrateTerraform` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * unconditional assignments converting from idFile Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * move require block in go modules file Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fall back to id file on upgrade Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * tidy Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix linter check Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add notice to remove Terraform state check on manual migration Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add `name` field Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> fix name tests Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * return early if no Terraform diff Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * tidy Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * return infrastructure state even if no diff exists Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add TODO to remove comment Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * use state-file in miniconstellation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * cli: remove id-file (#2402) * remove id-file from `constellation create` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add file renaming to handler * rename id-file after upgrade * use idFile on `constellation init` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from `constellation verify` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * linter fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from `constellation mini` * remove id-file from `constellation recover` * linter fixes * remove id-file from `constellation terminate` * fix initSecret type * fix recover argument precedence * fix terminate test * generate * add TODO to remove id-file removal * Update cli/internal/cmd/init.go Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com> * fix verify arg parse logic Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add version test Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from docs * add file not found log * use state-file in miniconstellation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from `constellation iam destroy` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * remove id-file from `cdbg deploy` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com> * use state-file in CI Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * update orchestration docs --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com>
240 lines
6.0 KiB
Go
240 lines
6.0 KiB
Go
/*
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
/*
|
|
Package file provides functions that combine file handling, JSON marshaling
|
|
and file system abstraction.
|
|
*/
|
|
package file
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
|
|
"github.com/spf13/afero"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Option is a bitmask of options for file operations.
|
|
type Option struct {
|
|
uint
|
|
}
|
|
|
|
// hasOption determines if a set of options contains the given option.
|
|
func hasOption(options []Option, op Option) bool {
|
|
for _, ops := range options {
|
|
if ops == op {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
const (
|
|
// OptNone is a no-op.
|
|
optNone uint = iota
|
|
// OptOverwrite overwrites an existing file.
|
|
optOverwrite
|
|
// OptMkdirAll creates the path to the file.
|
|
optMkdirAll
|
|
// OptAppend appends to the file.
|
|
optAppend
|
|
)
|
|
|
|
var (
|
|
// OptNone is a no-op.
|
|
OptNone = Option{optNone}
|
|
// OptOverwrite overwrites an existing file.
|
|
OptOverwrite = Option{optOverwrite}
|
|
// OptMkdirAll creates the path to the file.
|
|
OptMkdirAll = Option{optMkdirAll}
|
|
// OptAppend appends to the file.
|
|
OptAppend = Option{optAppend}
|
|
)
|
|
|
|
// Handler handles file interaction.
|
|
type Handler struct {
|
|
fs *afero.Afero
|
|
}
|
|
|
|
// NewHandler returns a new file handler.
|
|
func NewHandler(fs afero.Fs) Handler {
|
|
afs := &afero.Afero{Fs: fs}
|
|
return Handler{fs: afs}
|
|
}
|
|
|
|
// Read reads the file given name and returns the bytes read.
|
|
func (h *Handler) Read(name string) ([]byte, error) {
|
|
file, err := h.fs.OpenFile(name, os.O_RDONLY, 0o600)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
return io.ReadAll(file)
|
|
}
|
|
|
|
// Write writes the data bytes into the file with the given name.
|
|
func (h *Handler) Write(name string, data []byte, options ...Option) error {
|
|
if hasOption(options, OptMkdirAll) {
|
|
if err := h.fs.MkdirAll(path.Dir(name), os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
flags := os.O_WRONLY | os.O_CREATE | os.O_EXCL
|
|
if hasOption(options, OptOverwrite) {
|
|
flags = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
|
|
}
|
|
if hasOption(options, OptAppend) {
|
|
flags = os.O_WRONLY | os.O_CREATE | os.O_APPEND
|
|
}
|
|
file, err := h.fs.OpenFile(name, flags, 0o600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = file.Write(data)
|
|
if errTmp := file.Close(); errTmp != nil && err == nil {
|
|
err = errTmp
|
|
}
|
|
return err
|
|
}
|
|
|
|
// ReadJSON reads a JSON file from name and unmarshals it into the content interface.
|
|
// The interface content must be a pointer to a JSON marchalable object.
|
|
func (h *Handler) ReadJSON(name string, content any) error {
|
|
data, err := h.Read(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(data, content)
|
|
}
|
|
|
|
// WriteJSON marshals the content interface to JSON and writes it to the path with the given name.
|
|
func (h *Handler) WriteJSON(name string, content any, options ...Option) error {
|
|
jsonData, err := json.MarshalIndent(content, "", "\t")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return h.Write(name, jsonData, options...)
|
|
}
|
|
|
|
// ReadYAML reads a YAML file from name and unmarshals it into the content interface.
|
|
// The interface content must be a pointer to a YAML marshalable object.
|
|
func (h *Handler) ReadYAML(name string, content any) error {
|
|
return h.readYAML(name, content, false)
|
|
}
|
|
|
|
// ReadYAMLStrict does the same as ReadYAML, but fails if YAML contains fields
|
|
// that are not specified in content.
|
|
func (h *Handler) ReadYAMLStrict(name string, content any) error {
|
|
return h.readYAML(name, content, true)
|
|
}
|
|
|
|
func (h *Handler) readYAML(name string, content any, strict bool) error {
|
|
data, err := h.Read(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
decoder := yaml.NewDecoder(bytes.NewBuffer(data))
|
|
decoder.KnownFields(strict)
|
|
return decoder.Decode(content)
|
|
}
|
|
|
|
// WriteYAML marshals the content interface to YAML and writes it to the path with the given name.
|
|
func (h *Handler) WriteYAML(name string, content any, options ...Option) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = errors.New("recovered from panic")
|
|
}
|
|
}()
|
|
data, err := encoder.NewEncoder(content).Encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return h.Write(name, data, options...)
|
|
}
|
|
|
|
// Remove deletes the file with the given name.
|
|
func (h *Handler) Remove(name string) error {
|
|
return h.fs.Remove(name)
|
|
}
|
|
|
|
// RemoveAll deletes the file or directory with the given name.
|
|
func (h *Handler) RemoveAll(name string) error {
|
|
return h.fs.RemoveAll(name)
|
|
}
|
|
|
|
// Stat returns a FileInfo describing the named file, or an error, if any
|
|
// happens.
|
|
func (h *Handler) Stat(name string) (fs.FileInfo, error) {
|
|
return h.fs.Stat(name)
|
|
}
|
|
|
|
// MkdirAll creates a directory path and all parents that does not exist yet.
|
|
func (h *Handler) MkdirAll(name string) error {
|
|
return h.fs.MkdirAll(name, 0o700)
|
|
}
|
|
|
|
// CopyDir copies the src directory recursively into dst with the given options. OptMkdirAll
|
|
// is always set. CopyDir does not follow symlinks.
|
|
func (h *Handler) CopyDir(src, dst string, opts ...Option) error {
|
|
opts = append(opts, OptMkdirAll)
|
|
root := filepath.Join(src, string(filepath.Separator))
|
|
|
|
walkFunc := func(path string, info fs.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
pathWithoutRoot := strings.TrimPrefix(path, root)
|
|
return h.CopyFile(path, filepath.Join(dst, pathWithoutRoot), opts...)
|
|
}
|
|
|
|
return h.fs.Walk(src, walkFunc)
|
|
}
|
|
|
|
// CopyFile copies the file from src to dst with the given options, respecting file permissions.
|
|
func (h *Handler) CopyFile(src, dst string, opts ...Option) error {
|
|
srcInfo, err := h.fs.Stat(src)
|
|
if err != nil {
|
|
return fmt.Errorf("stat source file: %w", err)
|
|
}
|
|
|
|
content, err := h.fs.ReadFile(src)
|
|
if err != nil {
|
|
return fmt.Errorf("read source file: %w", err)
|
|
}
|
|
|
|
err = h.Write(dst, content, opts...)
|
|
if err != nil {
|
|
return fmt.Errorf("write destination file: %w", err)
|
|
}
|
|
|
|
err = h.fs.Chmod(dst, srcInfo.Mode())
|
|
if err != nil {
|
|
return fmt.Errorf("chmod destination file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RenameFile renames a file, overwriting any existing file at the destination.
|
|
func (h *Handler) RenameFile(old, new string) error {
|
|
return h.fs.Rename(old, new)
|
|
}
|