crossplane
145 строк · 3.9 Кб
1/*
2Copyright 2023 The Crossplane Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package config18
19import (20"encoding/json"21"io"22"os"23"path/filepath"24
25"github.com/spf13/afero"26)
27
28// Source is a source for interacting with a Config.
29type Source interface {30Initialize() error31GetConfig() (*Config, error)32UpdateConfig(*Config) error33}
34
35// NewFSSource constructs a new FSSource. Path must be supplied via modifier or
36// Initialize must be called to use default.
37// NOTE(hasheddan): using empty path by default is a bit of a footgun, so we
38// should consider refactoring here. The motivation for the current design is to
39// allow for flexibility in cases where the consumer does not want to create if
40// the path does not exist, or they want to provide the FSSource as the default
41// without handling an error in construction (see Docker credential helper for
42// example).
43func NewFSSource(modifiers ...FSSourceModifier) *FSSource {44src := &FSSource{45fs: afero.NewOsFs(),46}47for _, m := range modifiers {48m(src)49}50
51return src52}
53
54// FSSourceModifier modifies an FSSource.
55type FSSourceModifier func(*FSSource)56
57// WithPath sets the config path for the filesystem source.
58func WithPath(p string) FSSourceModifier {59return func(f *FSSource) {60f.path = filepath.Clean(p)61}62}
63
64// WithFS overrides the FSSource filesystem with the given filesystem.
65func WithFS(fs afero.Fs) FSSourceModifier {66return func(f *FSSource) {67f.fs = fs68}69}
70
71// FSSource provides a filesystem source for interacting with a Config.
72type FSSource struct {73fs afero.Fs74path string75}
76
77// Initialize creates a config in the filesystem if one does not exist. If path
78// is not defined the default path is constructed.
79func (src *FSSource) Initialize() error {80if src.path == "" {81p, err := GetDefaultPath()82if err != nil {83return err84}85src.path = p86}87if _, err := src.fs.Stat(src.path); err != nil {88if !os.IsNotExist(err) {89return err90}91if err := src.fs.MkdirAll(filepath.Dir(src.path), 0755); err != nil {92return err93}94f, err := src.fs.OpenFile(src.path, os.O_CREATE, 0600)95if err != nil {96return err97}98defer f.Close() //nolint:errcheck // we don't care about the error99}100return nil101}
102
103// GetConfig fetches the config from a filesystem.
104func (src *FSSource) GetConfig() (*Config, error) {105f, err := src.fs.Open(src.path)106if err != nil {107return nil, err108}109defer f.Close() //nolint:errcheck // we don't care about the error110b, err := io.ReadAll(f)111if err != nil {112return nil, err113}114conf := &Config{}115if len(b) == 0 {116return conf, nil117}118if err := json.Unmarshal(b, conf); err != nil {119return nil, err120}121return conf, nil122}
123
124// UpdateConfig updates the Config in the filesystem.
125func (src *FSSource) UpdateConfig(c *Config) error {126f, err := src.fs.OpenFile(src.path, os.O_RDWR|os.O_TRUNC, 0600)127if err != nil {128return err129}130// NOTE(hasheddan): We both defer and explicitly call Close() to ensure that131// we close the file in the case that we encounter an error before write,132// and that we return an error in the case that we write and then fail to133// close the file (i.e. write buffer is not flushed). In the latter case the134// deferred Close() will error (see https://golang.org/pkg/os/#File.Close),135// but we do not check it.136defer f.Close() //nolint:errcheck // we don't care about the error137b, err := json.MarshalIndent(c, "", "\t")138if err != nil {139return err140}141if _, err := f.Write(b); err != nil {142return err143}144return f.Close()145}
146