feat(secrets): add host secret file store
- commit
- b7019c4864175add9a2d5e45009c73b60f625625
- author
- brice · · 2026-10-08 20:22 UTC
- parent
- e55fb3b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +685 -0
M .mockery.yaml +1 -0
| @@ -17,6 +17,7 @@ packages: | |||
| 17 | 17 | EventSubscriber: | |
| 18 | 18 | EventHandler: | |
| 19 | 19 | SecretProvider: | |
| 20 | + | SecretFileStore: | |
| 20 | 21 | ContainerLogWriter: | |
| 21 | 22 | Metrics: | |
| 22 | 23 | LogExporter: | |
| @@ -17,6 +17,7 @@ packages: | |||||
| 17 | EventSubscriber: | 17 | EventSubscriber: | ||
| 18 | EventHandler: | 18 | EventHandler: | ||
| 19 | SecretProvider: | 19 | SecretProvider: | ||
| 20 | + | SecretFileStore: | |||
| 20 | ContainerLogWriter: | 21 | ContainerLogWriter: | ||
| 21 | Metrics: | 22 | Metrics: | ||
| 22 | LogExporter: | 23 | LogExporter: | ||
A internal/adapters/out/secretfiles/store.go +194 -0
| @@ -0,0 +1,194 @@ | |||
| 1 | + | // Package secretfiles stores file-delivered app secrets on the host. | |
| 2 | + | // | |
| 3 | + | // Layout: <data_dir>/secrets-run/<app>/<container-name>/<KEY>, directories | |
| 4 | + | // 0700 and files 0444. All access goes through an os.Root so symlinks | |
| 5 | + | // cannot escape the base directory. Values and paths are never logged. | |
| 6 | + | package secretfiles | |
| 7 | + | ||
| 8 | + | import ( | |
| 9 | + | "context" | |
| 10 | + | "errors" | |
| 11 | + | "fmt" | |
| 12 | + | "io/fs" | |
| 13 | + | "maps" | |
| 14 | + | "os" | |
| 15 | + | "path/filepath" | |
| 16 | + | "slices" | |
| 17 | + | "strings" | |
| 18 | + | "syscall" | |
| 19 | + | ||
| 20 | + | "github.com/bnema/gordon/internal/boundaries/out" | |
| 21 | + | "github.com/bnema/gordon/internal/domain" | |
| 22 | + | ) | |
| 23 | + | ||
| 24 | + | const ( | |
| 25 | + | dirMode os.FileMode = 0o700 | |
| 26 | + | fileMode os.FileMode = 0o444 | |
| 27 | + | // maxContainerName bounds a container directory name. | |
| 28 | + | maxContainerName = 255 | |
| 29 | + | ) | |
| 30 | + | ||
| 31 | + | // Store implements out.SecretFileStore on the local filesystem. | |
| 32 | + | type Store struct { | |
| 33 | + | dir string | |
| 34 | + | root *os.Root | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | var _ out.SecretFileStore = (*Store)(nil) | |
| 38 | + | ||
| 39 | + | // NewStore opens (creating if needed) <dataDir>/secrets-run. | |
| 40 | + | func NewStore(dataDir string) (*Store, error) { | |
| 41 | + | dir := filepath.Join(dataDir, "secrets-run") | |
| 42 | + | if err := os.MkdirAll(dir, dirMode); err != nil { | |
| 43 | + | return nil, fmt.Errorf("secret files: create base directory: %w", err) | |
| 44 | + | } | |
| 45 | + | info, err := os.Lstat(dir) | |
| 46 | + | if err != nil { | |
| 47 | + | return nil, fmt.Errorf("secret files: stat base directory: %w", err) | |
| 48 | + | } | |
| 49 | + | if !info.IsDir() { | |
| 50 | + | return nil, errors.New("secret files: base directory must be a directory, not a symlink") | |
| 51 | + | } | |
| 52 | + | if err := os.Chmod(dir, dirMode); err != nil { | |
| 53 | + | return nil, fmt.Errorf("secret files: chmod base directory: %w", err) | |
| 54 | + | } | |
| 55 | + | root, err := os.OpenRoot(dir) | |
| 56 | + | if err != nil { | |
| 57 | + | return nil, fmt.Errorf("secret files: open base directory: %w", err) | |
| 58 | + | } | |
| 59 | + | return &Store{dir: dir, root: root}, nil | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | // Dir returns the absolute base directory. | |
| 63 | + | func (s *Store) Dir() string { return s.dir } | |
| 64 | + | ||
| 65 | + | // Close releases the base directory handle. | |
| 66 | + | func (s *Store) Close() error { return s.root.Close() } | |
| 67 | + | ||
| 68 | + | // Write replaces the container's directory with one 0444 file per key. | |
| 69 | + | func (s *Store) Write(_ context.Context, app, container string, values map[string]string) (map[string]string, error) { | |
| 70 | + | if err := validateSet(app, container); err != nil { | |
| 71 | + | return nil, err | |
| 72 | + | } | |
| 73 | + | for key := range values { | |
| 74 | + | if err := domain.ValidateEnvKey(key); err != nil { | |
| 75 | + | return nil, fmt.Errorf("secret files: %w", err) | |
| 76 | + | } | |
| 77 | + | } | |
| 78 | + | if err := s.root.MkdirAll(app, dirMode); err != nil { | |
| 79 | + | return nil, fmt.Errorf("secret files: create app directory: %w", err) | |
| 80 | + | } | |
| 81 | + | if err := s.root.Chmod(app, dirMode); err != nil { | |
| 82 | + | return nil, fmt.Errorf("secret files: chmod app directory: %w", err) | |
| 83 | + | } | |
| 84 | + | rel := filepath.Join(app, container) | |
| 85 | + | if err := s.root.RemoveAll(rel); err != nil { | |
| 86 | + | return nil, fmt.Errorf("secret files: clear container directory: %w", err) | |
| 87 | + | } | |
| 88 | + | paths, err := s.writeFiles(rel, values) | |
| 89 | + | if err != nil { | |
| 90 | + | _ = s.root.RemoveAll(rel) | |
| 91 | + | return nil, err | |
| 92 | + | } | |
| 93 | + | return paths, nil | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | func (s *Store) writeFiles(rel string, values map[string]string) (map[string]string, error) { | |
| 97 | + | if err := s.root.Mkdir(rel, dirMode); err != nil { | |
| 98 | + | return nil, fmt.Errorf("secret files: create container directory: %w", err) | |
| 99 | + | } | |
| 100 | + | if err := s.root.Chmod(rel, dirMode); err != nil { | |
| 101 | + | return nil, fmt.Errorf("secret files: chmod container directory: %w", err) | |
| 102 | + | } | |
| 103 | + | paths := make(map[string]string, len(values)) | |
| 104 | + | for _, key := range slices.Sorted(maps.Keys(values)) { | |
| 105 | + | name := filepath.Join(rel, key) | |
| 106 | + | if err := s.writeFile(name, values[key]); err != nil { | |
| 107 | + | return nil, fmt.Errorf("secret files: write %s: %w", key, err) | |
| 108 | + | } | |
| 109 | + | paths[key] = filepath.Join(s.dir, name) | |
| 110 | + | } | |
| 111 | + | return paths, nil | |
| 112 | + | } | |
| 113 | + | ||
| 114 | + | func (s *Store) writeFile(name, value string) error { | |
| 115 | + | f, err := s.root.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, fileMode) | |
| 116 | + | if err != nil { | |
| 117 | + | return err | |
| 118 | + | } | |
| 119 | + | if _, err := f.WriteString(value); err != nil { | |
| 120 | + | _ = f.Close() | |
| 121 | + | return err | |
| 122 | + | } | |
| 123 | + | if err := f.Close(); err != nil { | |
| 124 | + | return err | |
| 125 | + | } | |
| 126 | + | return s.root.Chmod(name, fileMode) | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | // Read returns one file's content. | |
| 130 | + | func (s *Store) Read(_ context.Context, app, container, key string) (string, error) { | |
| 131 | + | if err := validateSet(app, container); err != nil { | |
| 132 | + | return "", err | |
| 133 | + | } | |
| 134 | + | if err := domain.ValidateEnvKey(key); err != nil { | |
| 135 | + | return "", fmt.Errorf("secret files: %w", err) | |
| 136 | + | } | |
| 137 | + | data, err := s.root.ReadFile(filepath.Join(app, container, key)) | |
| 138 | + | if err != nil { | |
| 139 | + | return "", fmt.Errorf("secret files: read %s: %w", key, err) | |
| 140 | + | } | |
| 141 | + | return string(data), nil | |
| 142 | + | } | |
| 143 | + | ||
| 144 | + | // Remove deletes the container's directory and the app directory when it | |
| 145 | + | // becomes empty. A missing directory is not an error. | |
| 146 | + | func (s *Store) Remove(_ context.Context, app, container string) error { | |
| 147 | + | if err := validateSet(app, container); err != nil { | |
| 148 | + | return err | |
| 149 | + | } | |
| 150 | + | if err := s.root.RemoveAll(filepath.Join(app, container)); err != nil { | |
| 151 | + | return fmt.Errorf("secret files: remove container directory: %w", err) | |
| 152 | + | } | |
| 153 | + | if err := s.root.Remove(app); err != nil && !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, syscall.ENOTEMPTY) && !errors.Is(err, syscall.EEXIST) { | |
| 154 | + | return fmt.Errorf("secret files: remove app directory: %w", err) | |
| 155 | + | } | |
| 156 | + | return nil | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | // List returns every container directory, sorted by app then container. | |
| 160 | + | func (s *Store) List(_ context.Context) ([]out.SecretFileSet, error) { | |
| 161 | + | fsys := s.root.FS() | |
| 162 | + | apps, err := fs.ReadDir(fsys, ".") | |
| 163 | + | if err != nil { | |
| 164 | + | return nil, fmt.Errorf("secret files: list apps: %w", err) | |
| 165 | + | } | |
| 166 | + | var sets []out.SecretFileSet | |
| 167 | + | for _, app := range apps { | |
| 168 | + | if !app.IsDir() { | |
| 169 | + | continue | |
| 170 | + | } | |
| 171 | + | containers, err := fs.ReadDir(fsys, app.Name()) | |
| 172 | + | if err != nil { | |
| 173 | + | return nil, fmt.Errorf("secret files: list containers: %w", err) | |
| 174 | + | } | |
| 175 | + | for _, c := range containers { | |
| 176 | + | if c.IsDir() { | |
| 177 | + | sets = append(sets, out.SecretFileSet{App: app.Name(), Container: c.Name()}) | |
| 178 | + | } | |
| 179 | + | } | |
| 180 | + | } | |
| 181 | + | // fs.ReadDir returns entries sorted by name, so sets are already ordered. | |
| 182 | + | return sets, nil | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | func validateSet(app, container string) error { | |
| 186 | + | if err := domain.ValidateAppName(app); err != nil { | |
| 187 | + | return fmt.Errorf("secret files: %w", err) | |
| 188 | + | } | |
| 189 | + | if container == "" || len(container) > maxContainerName || container == "." || container == ".." || | |
| 190 | + | filepath.Base(container) != container || strings.ContainsAny(container, "/\\\x00") { | |
| 191 | + | return errors.New("secret files: invalid container name") | |
| 192 | + | } | |
| 193 | + | return nil | |
| 194 | + | } | |
| @@ -0,0 +1,194 @@ | |||||
| 1 | + | // Package secretfiles stores file-delivered app secrets on the host. | |||
| 2 | + | // | |||
| 3 | + | // Layout: <data_dir>/secrets-run/<app>/<container-name>/<KEY>, directories | |||
| 4 | + | // 0700 and files 0444. All access goes through an os.Root so symlinks | |||
| 5 | + | // cannot escape the base directory. Values and paths are never logged. | |||
| 6 | + | package secretfiles | |||
| 7 | + | ||||
| 8 | + | import ( | |||
| 9 | + | "context" | |||
| 10 | + | "errors" | |||
| 11 | + | "fmt" | |||
| 12 | + | "io/fs" | |||
| 13 | + | "maps" | |||
| 14 | + | "os" | |||
| 15 | + | "path/filepath" | |||
| 16 | + | "slices" | |||
| 17 | + | "strings" | |||
| 18 | + | "syscall" | |||
| 19 | + | ||||
| 20 | + | "github.com/bnema/gordon/internal/boundaries/out" | |||
| 21 | + | "github.com/bnema/gordon/internal/domain" | |||
| 22 | + | ) | |||
| 23 | + | ||||
| 24 | + | const ( | |||
| 25 | + | dirMode os.FileMode = 0o700 | |||
| 26 | + | fileMode os.FileMode = 0o444 | |||
| 27 | + | // maxContainerName bounds a container directory name. | |||
| 28 | + | maxContainerName = 255 | |||
| 29 | + | ) | |||
| 30 | + | ||||
| 31 | + | // Store implements out.SecretFileStore on the local filesystem. | |||
| 32 | + | type Store struct { | |||
| 33 | + | dir string | |||
| 34 | + | root *os.Root | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | var _ out.SecretFileStore = (*Store)(nil) | |||
| 38 | + | ||||
| 39 | + | // NewStore opens (creating if needed) <dataDir>/secrets-run. | |||
| 40 | + | func NewStore(dataDir string) (*Store, error) { | |||
| 41 | + | dir := filepath.Join(dataDir, "secrets-run") | |||
| 42 | + | if err := os.MkdirAll(dir, dirMode); err != nil { | |||
| 43 | + | return nil, fmt.Errorf("secret files: create base directory: %w", err) | |||
| 44 | + | } | |||
| 45 | + | info, err := os.Lstat(dir) | |||
| 46 | + | if err != nil { | |||
| 47 | + | return nil, fmt.Errorf("secret files: stat base directory: %w", err) | |||
| 48 | + | } | |||
| 49 | + | if !info.IsDir() { | |||
| 50 | + | return nil, errors.New("secret files: base directory must be a directory, not a symlink") | |||
| 51 | + | } | |||
| 52 | + | if err := os.Chmod(dir, dirMode); err != nil { | |||
| 53 | + | return nil, fmt.Errorf("secret files: chmod base directory: %w", err) | |||
| 54 | + | } | |||
| 55 | + | root, err := os.OpenRoot(dir) | |||
| 56 | + | if err != nil { | |||
| 57 | + | return nil, fmt.Errorf("secret files: open base directory: %w", err) | |||
| 58 | + | } | |||
| 59 | + | return &Store{dir: dir, root: root}, nil | |||
| 60 | + | } | |||
| 61 | + | ||||
| 62 | + | // Dir returns the absolute base directory. | |||
| 63 | + | func (s *Store) Dir() string { return s.dir } | |||
| 64 | + | ||||
| 65 | + | // Close releases the base directory handle. | |||
| 66 | + | func (s *Store) Close() error { return s.root.Close() } | |||
| 67 | + | ||||
| 68 | + | // Write replaces the container's directory with one 0444 file per key. | |||
| 69 | + | func (s *Store) Write(_ context.Context, app, container string, values map[string]string) (map[string]string, error) { | |||
| 70 | + | if err := validateSet(app, container); err != nil { | |||
| 71 | + | return nil, err | |||
| 72 | + | } | |||
| 73 | + | for key := range values { | |||
| 74 | + | if err := domain.ValidateEnvKey(key); err != nil { | |||
| 75 | + | return nil, fmt.Errorf("secret files: %w", err) | |||
| 76 | + | } | |||
| 77 | + | } | |||
| 78 | + | if err := s.root.MkdirAll(app, dirMode); err != nil { | |||
| 79 | + | return nil, fmt.Errorf("secret files: create app directory: %w", err) | |||
| 80 | + | } | |||
| 81 | + | if err := s.root.Chmod(app, dirMode); err != nil { | |||
| 82 | + | return nil, fmt.Errorf("secret files: chmod app directory: %w", err) | |||
| 83 | + | } | |||
| 84 | + | rel := filepath.Join(app, container) | |||
| 85 | + | if err := s.root.RemoveAll(rel); err != nil { | |||
| 86 | + | return nil, fmt.Errorf("secret files: clear container directory: %w", err) | |||
| 87 | + | } | |||
| 88 | + | paths, err := s.writeFiles(rel, values) | |||
| 89 | + | if err != nil { | |||
| 90 | + | _ = s.root.RemoveAll(rel) | |||
| 91 | + | return nil, err | |||
| 92 | + | } | |||
| 93 | + | return paths, nil | |||
| 94 | + | } | |||
| 95 | + | ||||
| 96 | + | func (s *Store) writeFiles(rel string, values map[string]string) (map[string]string, error) { | |||
| 97 | + | if err := s.root.Mkdir(rel, dirMode); err != nil { | |||
| 98 | + | return nil, fmt.Errorf("secret files: create container directory: %w", err) | |||
| 99 | + | } | |||
| 100 | + | if err := s.root.Chmod(rel, dirMode); err != nil { | |||
| 101 | + | return nil, fmt.Errorf("secret files: chmod container directory: %w", err) | |||
| 102 | + | } | |||
| 103 | + | paths := make(map[string]string, len(values)) | |||
| 104 | + | for _, key := range slices.Sorted(maps.Keys(values)) { | |||
| 105 | + | name := filepath.Join(rel, key) | |||
| 106 | + | if err := s.writeFile(name, values[key]); err != nil { | |||
| 107 | + | return nil, fmt.Errorf("secret files: write %s: %w", key, err) | |||
| 108 | + | } | |||
| 109 | + | paths[key] = filepath.Join(s.dir, name) | |||
| 110 | + | } | |||
| 111 | + | return paths, nil | |||
| 112 | + | } | |||
| 113 | + | ||||
| 114 | + | func (s *Store) writeFile(name, value string) error { | |||
| 115 | + | f, err := s.root.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, fileMode) | |||
| 116 | + | if err != nil { | |||
| 117 | + | return err | |||
| 118 | + | } | |||
| 119 | + | if _, err := f.WriteString(value); err != nil { | |||
| 120 | + | _ = f.Close() | |||
| 121 | + | return err | |||
| 122 | + | } | |||
| 123 | + | if err := f.Close(); err != nil { | |||
| 124 | + | return err | |||
| 125 | + | } | |||
| 126 | + | return s.root.Chmod(name, fileMode) | |||
| 127 | + | } | |||
| 128 | + | ||||
| 129 | + | // Read returns one file's content. | |||
| 130 | + | func (s *Store) Read(_ context.Context, app, container, key string) (string, error) { | |||
| 131 | + | if err := validateSet(app, container); err != nil { | |||
| 132 | + | return "", err | |||
| 133 | + | } | |||
| 134 | + | if err := domain.ValidateEnvKey(key); err != nil { | |||
| 135 | + | return "", fmt.Errorf("secret files: %w", err) | |||
| 136 | + | } | |||
| 137 | + | data, err := s.root.ReadFile(filepath.Join(app, container, key)) | |||
| 138 | + | if err != nil { | |||
| 139 | + | return "", fmt.Errorf("secret files: read %s: %w", key, err) | |||
| 140 | + | } | |||
| 141 | + | return string(data), nil | |||
| 142 | + | } | |||
| 143 | + | ||||
| 144 | + | // Remove deletes the container's directory and the app directory when it | |||
| 145 | + | // becomes empty. A missing directory is not an error. | |||
| 146 | + | func (s *Store) Remove(_ context.Context, app, container string) error { | |||
| 147 | + | if err := validateSet(app, container); err != nil { | |||
| 148 | + | return err | |||
| 149 | + | } | |||
| 150 | + | if err := s.root.RemoveAll(filepath.Join(app, container)); err != nil { | |||
| 151 | + | return fmt.Errorf("secret files: remove container directory: %w", err) | |||
| 152 | + | } | |||
| 153 | + | if err := s.root.Remove(app); err != nil && !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, syscall.ENOTEMPTY) && !errors.Is(err, syscall.EEXIST) { | |||
| 154 | + | return fmt.Errorf("secret files: remove app directory: %w", err) | |||
| 155 | + | } | |||
| 156 | + | return nil | |||
| 157 | + | } | |||
| 158 | + | ||||
| 159 | + | // List returns every container directory, sorted by app then container. | |||
| 160 | + | func (s *Store) List(_ context.Context) ([]out.SecretFileSet, error) { | |||
| 161 | + | fsys := s.root.FS() | |||
| 162 | + | apps, err := fs.ReadDir(fsys, ".") | |||
| 163 | + | if err != nil { | |||
| 164 | + | return nil, fmt.Errorf("secret files: list apps: %w", err) | |||
| 165 | + | } | |||
| 166 | + | var sets []out.SecretFileSet | |||
| 167 | + | for _, app := range apps { | |||
| 168 | + | if !app.IsDir() { | |||
| 169 | + | continue | |||
| 170 | + | } | |||
| 171 | + | containers, err := fs.ReadDir(fsys, app.Name()) | |||
| 172 | + | if err != nil { | |||
| 173 | + | return nil, fmt.Errorf("secret files: list containers: %w", err) | |||
| 174 | + | } | |||
| 175 | + | for _, c := range containers { | |||
| 176 | + | if c.IsDir() { | |||
| 177 | + | sets = append(sets, out.SecretFileSet{App: app.Name(), Container: c.Name()}) | |||
| 178 | + | } | |||
| 179 | + | } | |||
| 180 | + | } | |||
| 181 | + | // fs.ReadDir returns entries sorted by name, so sets are already ordered. | |||
| 182 | + | return sets, nil | |||
| 183 | + | } | |||
| 184 | + | ||||
| 185 | + | func validateSet(app, container string) error { | |||
| 186 | + | if err := domain.ValidateAppName(app); err != nil { | |||
| 187 | + | return fmt.Errorf("secret files: %w", err) | |||
| 188 | + | } | |||
| 189 | + | if container == "" || len(container) > maxContainerName || container == "." || container == ".." || | |||
| 190 | + | filepath.Base(container) != container || strings.ContainsAny(container, "/\\\x00") { | |||
| 191 | + | return errors.New("secret files: invalid container name") | |||
| 192 | + | } | |||
| 193 | + | return nil | |||
| 194 | + | } | |||
A internal/adapters/out/secretfiles/store_test.go +138 -0
| @@ -0,0 +1,138 @@ | |||
| 1 | + | package secretfiles_test | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "os" | |
| 6 | + | "path/filepath" | |
| 7 | + | "testing" | |
| 8 | + | ||
| 9 | + | "github.com/stretchr/testify/assert" | |
| 10 | + | "github.com/stretchr/testify/require" | |
| 11 | + | ||
| 12 | + | "github.com/bnema/gordon/internal/adapters/out/secretfiles" | |
| 13 | + | "github.com/bnema/gordon/internal/boundaries/out" | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | func newStore(t *testing.T) (*secretfiles.Store, string) { | |
| 17 | + | t.Helper() | |
| 18 | + | dataDir := t.TempDir() | |
| 19 | + | store, err := secretfiles.NewStore(dataDir) | |
| 20 | + | require.NoError(t, err) | |
| 21 | + | t.Cleanup(func() { _ = store.Close() }) | |
| 22 | + | return store, dataDir | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | func mode(t *testing.T, p string) os.FileMode { | |
| 26 | + | t.Helper() | |
| 27 | + | info, err := os.Stat(p) | |
| 28 | + | require.NoError(t, err) | |
| 29 | + | return info.Mode().Perm() | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | func TestStore_WriteModesAndRead(t *testing.T) { | |
| 33 | + | store, dataDir := newStore(t) | |
| 34 | + | ctx := context.Background() | |
| 35 | + | assert.Equal(t, filepath.Join(dataDir, "secrets-run"), store.Dir()) | |
| 36 | + | ||
| 37 | + | paths, err := store.Write(ctx, "blog", "gordon-blog--web--a", map[string]string{"DB": "s3cr3t", "API": "k"}) | |
| 38 | + | require.NoError(t, err) | |
| 39 | + | assert.Equal(t, filepath.Join(store.Dir(), "blog", "gordon-blog--web--a", "DB"), paths["DB"]) | |
| 40 | + | assert.Len(t, paths, 2) | |
| 41 | + | ||
| 42 | + | assert.Equal(t, os.FileMode(0o700), mode(t, store.Dir())) | |
| 43 | + | assert.Equal(t, os.FileMode(0o700), mode(t, filepath.Join(store.Dir(), "blog"))) | |
| 44 | + | assert.Equal(t, os.FileMode(0o700), mode(t, filepath.Join(store.Dir(), "blog", "gordon-blog--web--a"))) | |
| 45 | + | assert.Equal(t, os.FileMode(0o444), mode(t, paths["DB"])) | |
| 46 | + | ||
| 47 | + | got, err := store.Read(ctx, "blog", "gordon-blog--web--a", "DB") | |
| 48 | + | require.NoError(t, err) | |
| 49 | + | assert.Equal(t, "s3cr3t", got) | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | func TestStore_WriteReplacesStaleKeys(t *testing.T) { | |
| 53 | + | store, _ := newStore(t) | |
| 54 | + | ctx := context.Background() | |
| 55 | + | _, err := store.Write(ctx, "blog", "c1", map[string]string{"OLD": "x"}) | |
| 56 | + | require.NoError(t, err) | |
| 57 | + | _, err = store.Write(ctx, "blog", "c1", map[string]string{"NEW": "y"}) | |
| 58 | + | require.NoError(t, err) | |
| 59 | + | ||
| 60 | + | _, err = store.Read(ctx, "blog", "c1", "OLD") | |
| 61 | + | require.Error(t, err) | |
| 62 | + | got, err := store.Read(ctx, "blog", "c1", "NEW") | |
| 63 | + | require.NoError(t, err) | |
| 64 | + | assert.Equal(t, "y", got) | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | func TestStore_RemoveAndList(t *testing.T) { | |
| 68 | + | store, _ := newStore(t) | |
| 69 | + | ctx := context.Background() | |
| 70 | + | for _, set := range []out.SecretFileSet{{App: "blog", Container: "c2"}, {App: "blog", Container: "c1"}, {App: "alpha", Container: "z"}} { | |
| 71 | + | _, err := store.Write(ctx, set.App, set.Container, map[string]string{"K": "v"}) | |
| 72 | + | require.NoError(t, err) | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | sets, err := store.List(ctx) | |
| 76 | + | require.NoError(t, err) | |
| 77 | + | assert.Equal(t, []out.SecretFileSet{{App: "alpha", Container: "z"}, {App: "blog", Container: "c1"}, {App: "blog", Container: "c2"}}, sets) | |
| 78 | + | ||
| 79 | + | require.NoError(t, store.Remove(ctx, "blog", "c1")) | |
| 80 | + | assert.NoDirExists(t, filepath.Join(store.Dir(), "blog", "c1")) | |
| 81 | + | assert.DirExists(t, filepath.Join(store.Dir(), "blog", "c2")) | |
| 82 | + | ||
| 83 | + | require.NoError(t, store.Remove(ctx, "blog", "c2")) | |
| 84 | + | assert.NoDirExists(t, filepath.Join(store.Dir(), "blog")) | |
| 85 | + | ||
| 86 | + | require.NoError(t, store.Remove(ctx, "blog", "missing")) | |
| 87 | + | require.NoError(t, store.Remove(ctx, "ghost", "missing")) | |
| 88 | + | ||
| 89 | + | sets, err = store.List(ctx) | |
| 90 | + | require.NoError(t, err) | |
| 91 | + | assert.Equal(t, []out.SecretFileSet{{App: "alpha", Container: "z"}}, sets) | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | func TestStore_InvalidNamesRejected(t *testing.T) { | |
| 95 | + | store, _ := newStore(t) | |
| 96 | + | ctx := context.Background() | |
| 97 | + | cases := []struct { | |
| 98 | + | name, app, container, key string | |
| 99 | + | }{ | |
| 100 | + | {"app traversal", "../x", "c", "K"}, | |
| 101 | + | {"app slash", "a/b", "c", "K"}, | |
| 102 | + | {"container dot", "blog", ".", "K"}, | |
| 103 | + | {"container dotdot", "blog", "..", "K"}, | |
| 104 | + | {"container slash", "blog", "a/b", "K"}, | |
| 105 | + | {"container empty", "blog", "", "K"}, | |
| 106 | + | {"bad key", "blog", "c", "bad-KEY!"}, | |
| 107 | + | {"key traversal", "blog", "c", "../K"}, | |
| 108 | + | } | |
| 109 | + | for _, tc := range cases { | |
| 110 | + | t.Run(tc.name, func(t *testing.T) { | |
| 111 | + | _, err := store.Write(ctx, tc.app, tc.container, map[string]string{tc.key: "v"}) | |
| 112 | + | require.Error(t, err) | |
| 113 | + | assert.NotContains(t, err.Error(), store.Dir()) | |
| 114 | + | }) | |
| 115 | + | } | |
| 116 | + | sets, err := store.List(ctx) | |
| 117 | + | require.NoError(t, err) | |
| 118 | + | assert.Empty(t, sets) | |
| 119 | + | } | |
| 120 | + | ||
| 121 | + | func TestStore_SymlinkEscapeRefused(t *testing.T) { | |
| 122 | + | store, _ := newStore(t) | |
| 123 | + | outside := t.TempDir() | |
| 124 | + | require.NoError(t, os.Symlink(outside, filepath.Join(store.Dir(), "blog"))) | |
| 125 | + | ||
| 126 | + | _, err := store.Write(context.Background(), "blog", "c1", map[string]string{"K": "v"}) | |
| 127 | + | require.Error(t, err) | |
| 128 | + | entries, err := os.ReadDir(outside) | |
| 129 | + | require.NoError(t, err) | |
| 130 | + | assert.Empty(t, entries) | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | func TestNewStore_RefusesSymlinkBase(t *testing.T) { | |
| 134 | + | dataDir := t.TempDir() | |
| 135 | + | require.NoError(t, os.Symlink(t.TempDir(), filepath.Join(dataDir, "secrets-run"))) | |
| 136 | + | _, err := secretfiles.NewStore(dataDir) | |
| 137 | + | require.Error(t, err) | |
| 138 | + | } | |
| @@ -0,0 +1,138 @@ | |||||
| 1 | + | package secretfiles_test | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "os" | |||
| 6 | + | "path/filepath" | |||
| 7 | + | "testing" | |||
| 8 | + | ||||
| 9 | + | "github.com/stretchr/testify/assert" | |||
| 10 | + | "github.com/stretchr/testify/require" | |||
| 11 | + | ||||
| 12 | + | "github.com/bnema/gordon/internal/adapters/out/secretfiles" | |||
| 13 | + | "github.com/bnema/gordon/internal/boundaries/out" | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | func newStore(t *testing.T) (*secretfiles.Store, string) { | |||
| 17 | + | t.Helper() | |||
| 18 | + | dataDir := t.TempDir() | |||
| 19 | + | store, err := secretfiles.NewStore(dataDir) | |||
| 20 | + | require.NoError(t, err) | |||
| 21 | + | t.Cleanup(func() { _ = store.Close() }) | |||
| 22 | + | return store, dataDir | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | func mode(t *testing.T, p string) os.FileMode { | |||
| 26 | + | t.Helper() | |||
| 27 | + | info, err := os.Stat(p) | |||
| 28 | + | require.NoError(t, err) | |||
| 29 | + | return info.Mode().Perm() | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | func TestStore_WriteModesAndRead(t *testing.T) { | |||
| 33 | + | store, dataDir := newStore(t) | |||
| 34 | + | ctx := context.Background() | |||
| 35 | + | assert.Equal(t, filepath.Join(dataDir, "secrets-run"), store.Dir()) | |||
| 36 | + | ||||
| 37 | + | paths, err := store.Write(ctx, "blog", "gordon-blog--web--a", map[string]string{"DB": "s3cr3t", "API": "k"}) | |||
| 38 | + | require.NoError(t, err) | |||
| 39 | + | assert.Equal(t, filepath.Join(store.Dir(), "blog", "gordon-blog--web--a", "DB"), paths["DB"]) | |||
| 40 | + | assert.Len(t, paths, 2) | |||
| 41 | + | ||||
| 42 | + | assert.Equal(t, os.FileMode(0o700), mode(t, store.Dir())) | |||
| 43 | + | assert.Equal(t, os.FileMode(0o700), mode(t, filepath.Join(store.Dir(), "blog"))) | |||
| 44 | + | assert.Equal(t, os.FileMode(0o700), mode(t, filepath.Join(store.Dir(), "blog", "gordon-blog--web--a"))) | |||
| 45 | + | assert.Equal(t, os.FileMode(0o444), mode(t, paths["DB"])) | |||
| 46 | + | ||||
| 47 | + | got, err := store.Read(ctx, "blog", "gordon-blog--web--a", "DB") | |||
| 48 | + | require.NoError(t, err) | |||
| 49 | + | assert.Equal(t, "s3cr3t", got) | |||
| 50 | + | } | |||
| 51 | + | ||||
| 52 | + | func TestStore_WriteReplacesStaleKeys(t *testing.T) { | |||
| 53 | + | store, _ := newStore(t) | |||
| 54 | + | ctx := context.Background() | |||
| 55 | + | _, err := store.Write(ctx, "blog", "c1", map[string]string{"OLD": "x"}) | |||
| 56 | + | require.NoError(t, err) | |||
| 57 | + | _, err = store.Write(ctx, "blog", "c1", map[string]string{"NEW": "y"}) | |||
| 58 | + | require.NoError(t, err) | |||
| 59 | + | ||||
| 60 | + | _, err = store.Read(ctx, "blog", "c1", "OLD") | |||
| 61 | + | require.Error(t, err) | |||
| 62 | + | got, err := store.Read(ctx, "blog", "c1", "NEW") | |||
| 63 | + | require.NoError(t, err) | |||
| 64 | + | assert.Equal(t, "y", got) | |||
| 65 | + | } | |||
| 66 | + | ||||
| 67 | + | func TestStore_RemoveAndList(t *testing.T) { | |||
| 68 | + | store, _ := newStore(t) | |||
| 69 | + | ctx := context.Background() | |||
| 70 | + | for _, set := range []out.SecretFileSet{{App: "blog", Container: "c2"}, {App: "blog", Container: "c1"}, {App: "alpha", Container: "z"}} { | |||
| 71 | + | _, err := store.Write(ctx, set.App, set.Container, map[string]string{"K": "v"}) | |||
| 72 | + | require.NoError(t, err) | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | sets, err := store.List(ctx) | |||
| 76 | + | require.NoError(t, err) | |||
| 77 | + | assert.Equal(t, []out.SecretFileSet{{App: "alpha", Container: "z"}, {App: "blog", Container: "c1"}, {App: "blog", Container: "c2"}}, sets) | |||
| 78 | + | ||||
| 79 | + | require.NoError(t, store.Remove(ctx, "blog", "c1")) | |||
| 80 | + | assert.NoDirExists(t, filepath.Join(store.Dir(), "blog", "c1")) | |||
| 81 | + | assert.DirExists(t, filepath.Join(store.Dir(), "blog", "c2")) | |||
| 82 | + | ||||
| 83 | + | require.NoError(t, store.Remove(ctx, "blog", "c2")) | |||
| 84 | + | assert.NoDirExists(t, filepath.Join(store.Dir(), "blog")) | |||
| 85 | + | ||||
| 86 | + | require.NoError(t, store.Remove(ctx, "blog", "missing")) | |||
| 87 | + | require.NoError(t, store.Remove(ctx, "ghost", "missing")) | |||
| 88 | + | ||||
| 89 | + | sets, err = store.List(ctx) | |||
| 90 | + | require.NoError(t, err) | |||
| 91 | + | assert.Equal(t, []out.SecretFileSet{{App: "alpha", Container: "z"}}, sets) | |||
| 92 | + | } | |||
| 93 | + | ||||
| 94 | + | func TestStore_InvalidNamesRejected(t *testing.T) { | |||
| 95 | + | store, _ := newStore(t) | |||
| 96 | + | ctx := context.Background() | |||
| 97 | + | cases := []struct { | |||
| 98 | + | name, app, container, key string | |||
| 99 | + | }{ | |||
| 100 | + | {"app traversal", "../x", "c", "K"}, | |||
| 101 | + | {"app slash", "a/b", "c", "K"}, | |||
| 102 | + | {"container dot", "blog", ".", "K"}, | |||
| 103 | + | {"container dotdot", "blog", "..", "K"}, | |||
| 104 | + | {"container slash", "blog", "a/b", "K"}, | |||
| 105 | + | {"container empty", "blog", "", "K"}, | |||
| 106 | + | {"bad key", "blog", "c", "bad-KEY!"}, | |||
| 107 | + | {"key traversal", "blog", "c", "../K"}, | |||
| 108 | + | } | |||
| 109 | + | for _, tc := range cases { | |||
| 110 | + | t.Run(tc.name, func(t *testing.T) { | |||
| 111 | + | _, err := store.Write(ctx, tc.app, tc.container, map[string]string{tc.key: "v"}) | |||
| 112 | + | require.Error(t, err) | |||
| 113 | + | assert.NotContains(t, err.Error(), store.Dir()) | |||
| 114 | + | }) | |||
| 115 | + | } | |||
| 116 | + | sets, err := store.List(ctx) | |||
| 117 | + | require.NoError(t, err) | |||
| 118 | + | assert.Empty(t, sets) | |||
| 119 | + | } | |||
| 120 | + | ||||
| 121 | + | func TestStore_SymlinkEscapeRefused(t *testing.T) { | |||
| 122 | + | store, _ := newStore(t) | |||
| 123 | + | outside := t.TempDir() | |||
| 124 | + | require.NoError(t, os.Symlink(outside, filepath.Join(store.Dir(), "blog"))) | |||
| 125 | + | ||||
| 126 | + | _, err := store.Write(context.Background(), "blog", "c1", map[string]string{"K": "v"}) | |||
| 127 | + | require.Error(t, err) | |||
| 128 | + | entries, err := os.ReadDir(outside) | |||
| 129 | + | require.NoError(t, err) | |||
| 130 | + | assert.Empty(t, entries) | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | func TestNewStore_RefusesSymlinkBase(t *testing.T) { | |||
| 134 | + | dataDir := t.TempDir() | |||
| 135 | + | require.NoError(t, os.Symlink(t.TempDir(), filepath.Join(dataDir, "secrets-run"))) | |||
| 136 | + | _, err := secretfiles.NewStore(dataDir) | |||
| 137 | + | require.Error(t, err) | |||
| 138 | + | } | |||
A internal/boundaries/out/mocks/mock_secret_file_store.go +331 -0
| @@ -0,0 +1,331 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package mocks | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | "context" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/gordon/internal/boundaries/out" | |
| 11 | + | mock "github.com/stretchr/testify/mock" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // NewMockSecretFileStore creates a new instance of MockSecretFileStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 15 | + | // The first argument is typically a *testing.T value. | |
| 16 | + | func NewMockSecretFileStore(t interface { | |
| 17 | + | mock.TestingT | |
| 18 | + | Cleanup(func()) | |
| 19 | + | }) *MockSecretFileStore { | |
| 20 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 21 | + | helper.Helper() | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | mock := &MockSecretFileStore{} | |
| 25 | + | mock.Mock.Test(t) | |
| 26 | + | ||
| 27 | + | t.Cleanup(func() { | |
| 28 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 29 | + | helper.Helper() | |
| 30 | + | } | |
| 31 | + | mock.AssertExpectations(t) | |
| 32 | + | }) | |
| 33 | + | ||
| 34 | + | return mock | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | // MockSecretFileStore is an autogenerated mock type for the SecretFileStore type | |
| 38 | + | type MockSecretFileStore struct { | |
| 39 | + | mock.Mock | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | type MockSecretFileStore_Expecter struct { | |
| 43 | + | mock *mock.Mock | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | func (_m *MockSecretFileStore) EXPECT() *MockSecretFileStore_Expecter { | |
| 47 | + | return &MockSecretFileStore_Expecter{mock: &_m.Mock} | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | // List provides a mock function for the type MockSecretFileStore | |
| 51 | + | func (_mock *MockSecretFileStore) List(ctx context.Context) ([]out.SecretFileSet, error) { | |
| 52 | + | ret := _mock.Called(ctx) | |
| 53 | + | ||
| 54 | + | if len(ret) == 0 { | |
| 55 | + | panic("no return value specified for List") | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | var r0 []out.SecretFileSet | |
| 59 | + | var r1 error | |
| 60 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]out.SecretFileSet, error)); ok { | |
| 61 | + | return returnFunc(ctx) | |
| 62 | + | } | |
| 63 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []out.SecretFileSet); ok { | |
| 64 | + | r0 = returnFunc(ctx) | |
| 65 | + | } else { | |
| 66 | + | if ret.Get(0) != nil { | |
| 67 | + | r0 = ret.Get(0).([]out.SecretFileSet) | |
| 68 | + | } | |
| 69 | + | } | |
| 70 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |
| 71 | + | r1 = returnFunc(ctx) | |
| 72 | + | } else { | |
| 73 | + | r1 = ret.Error(1) | |
| 74 | + | } | |
| 75 | + | return r0, r1 | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | // MockSecretFileStore_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' | |
| 79 | + | type MockSecretFileStore_List_Call struct { | |
| 80 | + | *mock.Call | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | // List is a helper method to define mock.On call | |
| 84 | + | // - ctx context.Context | |
| 85 | + | func (_e *MockSecretFileStore_Expecter) List(ctx any) *MockSecretFileStore_List_Call { | |
| 86 | + | return &MockSecretFileStore_List_Call{Call: _e.mock.On("List", ctx)} | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | func (_c *MockSecretFileStore_List_Call) Run(run func(ctx context.Context)) *MockSecretFileStore_List_Call { | |
| 90 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 91 | + | var arg0 context.Context | |
| 92 | + | if args[0] != nil { | |
| 93 | + | arg0 = args[0].(context.Context) | |
| 94 | + | } | |
| 95 | + | run( | |
| 96 | + | arg0, | |
| 97 | + | ) | |
| 98 | + | }) | |
| 99 | + | return _c | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | func (_c *MockSecretFileStore_List_Call) Return(secretFileSets []out.SecretFileSet, err error) *MockSecretFileStore_List_Call { | |
| 103 | + | _c.Call.Return(secretFileSets, err) | |
| 104 | + | return _c | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | func (_c *MockSecretFileStore_List_Call) RunAndReturn(run func(ctx context.Context) ([]out.SecretFileSet, error)) *MockSecretFileStore_List_Call { | |
| 108 | + | _c.Call.Return(run) | |
| 109 | + | return _c | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | // Read provides a mock function for the type MockSecretFileStore | |
| 113 | + | func (_mock *MockSecretFileStore) Read(ctx context.Context, app string, container string, key string) (string, error) { | |
| 114 | + | ret := _mock.Called(ctx, app, container, key) | |
| 115 | + | ||
| 116 | + | if len(ret) == 0 { | |
| 117 | + | panic("no return value specified for Read") | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | var r0 string | |
| 121 | + | var r1 error | |
| 122 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) (string, error)); ok { | |
| 123 | + | return returnFunc(ctx, app, container, key) | |
| 124 | + | } | |
| 125 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) string); ok { | |
| 126 | + | r0 = returnFunc(ctx, app, container, key) | |
| 127 | + | } else { | |
| 128 | + | r0 = ret.Get(0).(string) | |
| 129 | + | } | |
| 130 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok { | |
| 131 | + | r1 = returnFunc(ctx, app, container, key) | |
| 132 | + | } else { | |
| 133 | + | r1 = ret.Error(1) | |
| 134 | + | } | |
| 135 | + | return r0, r1 | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | // MockSecretFileStore_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read' | |
| 139 | + | type MockSecretFileStore_Read_Call struct { | |
| 140 | + | *mock.Call | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | // Read is a helper method to define mock.On call | |
| 144 | + | // - ctx context.Context | |
| 145 | + | // - app string | |
| 146 | + | // - container string | |
| 147 | + | // - key string | |
| 148 | + | func (_e *MockSecretFileStore_Expecter) Read(ctx any, app any, container any, key any) *MockSecretFileStore_Read_Call { | |
| 149 | + | return &MockSecretFileStore_Read_Call{Call: _e.mock.On("Read", ctx, app, container, key)} | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | func (_c *MockSecretFileStore_Read_Call) Run(run func(ctx context.Context, app string, container string, key string)) *MockSecretFileStore_Read_Call { | |
| 153 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 154 | + | var arg0 context.Context | |
| 155 | + | if args[0] != nil { | |
| 156 | + | arg0 = args[0].(context.Context) | |
| 157 | + | } | |
| 158 | + | var arg1 string | |
| 159 | + | if args[1] != nil { | |
| 160 | + | arg1 = args[1].(string) | |
| 161 | + | } | |
| 162 | + | var arg2 string | |
| 163 | + | if args[2] != nil { | |
| 164 | + | arg2 = args[2].(string) | |
| 165 | + | } | |
| 166 | + | var arg3 string | |
| 167 | + | if args[3] != nil { | |
| 168 | + | arg3 = args[3].(string) | |
| 169 | + | } | |
| 170 | + | run( | |
| 171 | + | arg0, | |
| 172 | + | arg1, | |
| 173 | + | arg2, | |
| 174 | + | arg3, | |
| 175 | + | ) | |
| 176 | + | }) | |
| 177 | + | return _c | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | func (_c *MockSecretFileStore_Read_Call) Return(s string, err error) *MockSecretFileStore_Read_Call { | |
| 181 | + | _c.Call.Return(s, err) | |
| 182 | + | return _c | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | func (_c *MockSecretFileStore_Read_Call) RunAndReturn(run func(ctx context.Context, app string, container string, key string) (string, error)) *MockSecretFileStore_Read_Call { | |
| 186 | + | _c.Call.Return(run) | |
| 187 | + | return _c | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | // Remove provides a mock function for the type MockSecretFileStore | |
| 191 | + | func (_mock *MockSecretFileStore) Remove(ctx context.Context, app string, container string) error { | |
| 192 | + | ret := _mock.Called(ctx, app, container) | |
| 193 | + | ||
| 194 | + | if len(ret) == 0 { | |
| 195 | + | panic("no return value specified for Remove") | |
| 196 | + | } | |
| 197 | + | ||
| 198 | + | var r0 error | |
| 199 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok { | |
| 200 | + | r0 = returnFunc(ctx, app, container) | |
| 201 | + | } else { | |
| 202 | + | r0 = ret.Error(0) | |
| 203 | + | } | |
| 204 | + | return r0 | |
| 205 | + | } | |
| 206 | + | ||
| 207 | + | // MockSecretFileStore_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove' | |
| 208 | + | type MockSecretFileStore_Remove_Call struct { | |
| 209 | + | *mock.Call | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | // Remove is a helper method to define mock.On call | |
| 213 | + | // - ctx context.Context | |
| 214 | + | // - app string | |
| 215 | + | // - container string | |
| 216 | + | func (_e *MockSecretFileStore_Expecter) Remove(ctx any, app any, container any) *MockSecretFileStore_Remove_Call { | |
| 217 | + | return &MockSecretFileStore_Remove_Call{Call: _e.mock.On("Remove", ctx, app, container)} | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | func (_c *MockSecretFileStore_Remove_Call) Run(run func(ctx context.Context, app string, container string)) *MockSecretFileStore_Remove_Call { | |
| 221 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 222 | + | var arg0 context.Context | |
| 223 | + | if args[0] != nil { | |
| 224 | + | arg0 = args[0].(context.Context) | |
| 225 | + | } | |
| 226 | + | var arg1 string | |
| 227 | + | if args[1] != nil { | |
| 228 | + | arg1 = args[1].(string) | |
| 229 | + | } | |
| 230 | + | var arg2 string | |
| 231 | + | if args[2] != nil { | |
| 232 | + | arg2 = args[2].(string) | |
| 233 | + | } | |
| 234 | + | run( | |
| 235 | + | arg0, | |
| 236 | + | arg1, | |
| 237 | + | arg2, | |
| 238 | + | ) | |
| 239 | + | }) | |
| 240 | + | return _c | |
| 241 | + | } | |
| 242 | + | ||
| 243 | + | func (_c *MockSecretFileStore_Remove_Call) Return(err error) *MockSecretFileStore_Remove_Call { | |
| 244 | + | _c.Call.Return(err) | |
| 245 | + | return _c | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | func (_c *MockSecretFileStore_Remove_Call) RunAndReturn(run func(ctx context.Context, app string, container string) error) *MockSecretFileStore_Remove_Call { | |
| 249 | + | _c.Call.Return(run) | |
| 250 | + | return _c | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // Write provides a mock function for the type MockSecretFileStore | |
| 254 | + | func (_mock *MockSecretFileStore) Write(ctx context.Context, app string, container string, values map[string]string) (map[string]string, error) { | |
| 255 | + | ret := _mock.Called(ctx, app, container, values) | |
| 256 | + | ||
| 257 | + | if len(ret) == 0 { | |
| 258 | + | panic("no return value specified for Write") | |
| 259 | + | } | |
| 260 | + | ||
| 261 | + | var r0 map[string]string | |
| 262 | + | var r1 error | |
| 263 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, map[string]string) (map[string]string, error)); ok { | |
| 264 | + | return returnFunc(ctx, app, container, values) | |
| 265 | + | } | |
| 266 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, map[string]string) map[string]string); ok { | |
| 267 | + | r0 = returnFunc(ctx, app, container, values) | |
| 268 | + | } else { | |
| 269 | + | if ret.Get(0) != nil { | |
| 270 | + | r0 = ret.Get(0).(map[string]string) | |
| 271 | + | } | |
| 272 | + | } | |
| 273 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, map[string]string) error); ok { | |
| 274 | + | r1 = returnFunc(ctx, app, container, values) | |
| 275 | + | } else { | |
| 276 | + | r1 = ret.Error(1) | |
| 277 | + | } | |
| 278 | + | return r0, r1 | |
| 279 | + | } | |
| 280 | + | ||
| 281 | + | // MockSecretFileStore_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write' | |
| 282 | + | type MockSecretFileStore_Write_Call struct { | |
| 283 | + | *mock.Call | |
| 284 | + | } | |
| 285 | + | ||
| 286 | + | // Write is a helper method to define mock.On call | |
| 287 | + | // - ctx context.Context | |
| 288 | + | // - app string | |
| 289 | + | // - container string | |
| 290 | + | // - values map[string]string | |
| 291 | + | func (_e *MockSecretFileStore_Expecter) Write(ctx any, app any, container any, values any) *MockSecretFileStore_Write_Call { | |
| 292 | + | return &MockSecretFileStore_Write_Call{Call: _e.mock.On("Write", ctx, app, container, values)} | |
| 293 | + | } | |
| 294 | + | ||
| 295 | + | func (_c *MockSecretFileStore_Write_Call) Run(run func(ctx context.Context, app string, container string, values map[string]string)) *MockSecretFileStore_Write_Call { | |
| 296 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 297 | + | var arg0 context.Context | |
| 298 | + | if args[0] != nil { | |
| 299 | + | arg0 = args[0].(context.Context) | |
| 300 | + | } | |
| 301 | + | var arg1 string | |
| 302 | + | if args[1] != nil { | |
| 303 | + | arg1 = args[1].(string) | |
| 304 | + | } | |
| 305 | + | var arg2 string | |
| 306 | + | if args[2] != nil { | |
| 307 | + | arg2 = args[2].(string) | |
| 308 | + | } | |
| 309 | + | var arg3 map[string]string | |
| 310 | + | if args[3] != nil { | |
| 311 | + | arg3 = args[3].(map[string]string) | |
| 312 | + | } | |
| 313 | + | run( | |
| 314 | + | arg0, | |
| 315 | + | arg1, | |
| 316 | + | arg2, | |
| 317 | + | arg3, | |
| 318 | + | ) | |
| 319 | + | }) | |
| 320 | + | return _c | |
| 321 | + | } | |
| 322 | + | ||
| 323 | + | func (_c *MockSecretFileStore_Write_Call) Return(stringToString map[string]string, err error) *MockSecretFileStore_Write_Call { | |
| 324 | + | _c.Call.Return(stringToString, err) | |
| 325 | + | return _c | |
| 326 | + | } | |
| 327 | + | ||
| 328 | + | func (_c *MockSecretFileStore_Write_Call) RunAndReturn(run func(ctx context.Context, app string, container string, values map[string]string) (map[string]string, error)) *MockSecretFileStore_Write_Call { | |
| 329 | + | _c.Call.Return(run) | |
| 330 | + | return _c | |
| 331 | + | } | |
| @@ -0,0 +1,331 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package mocks | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | "context" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/gordon/internal/boundaries/out" | |||
| 11 | + | mock "github.com/stretchr/testify/mock" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // NewMockSecretFileStore creates a new instance of MockSecretFileStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 15 | + | // The first argument is typically a *testing.T value. | |||
| 16 | + | func NewMockSecretFileStore(t interface { | |||
| 17 | + | mock.TestingT | |||
| 18 | + | Cleanup(func()) | |||
| 19 | + | }) *MockSecretFileStore { | |||
| 20 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 21 | + | helper.Helper() | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | mock := &MockSecretFileStore{} | |||
| 25 | + | mock.Mock.Test(t) | |||
| 26 | + | ||||
| 27 | + | t.Cleanup(func() { | |||
| 28 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 29 | + | helper.Helper() | |||
| 30 | + | } | |||
| 31 | + | mock.AssertExpectations(t) | |||
| 32 | + | }) | |||
| 33 | + | ||||
| 34 | + | return mock | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | // MockSecretFileStore is an autogenerated mock type for the SecretFileStore type | |||
| 38 | + | type MockSecretFileStore struct { | |||
| 39 | + | mock.Mock | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | type MockSecretFileStore_Expecter struct { | |||
| 43 | + | mock *mock.Mock | |||
| 44 | + | } | |||
| 45 | + | ||||
| 46 | + | func (_m *MockSecretFileStore) EXPECT() *MockSecretFileStore_Expecter { | |||
| 47 | + | return &MockSecretFileStore_Expecter{mock: &_m.Mock} | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | // List provides a mock function for the type MockSecretFileStore | |||
| 51 | + | func (_mock *MockSecretFileStore) List(ctx context.Context) ([]out.SecretFileSet, error) { | |||
| 52 | + | ret := _mock.Called(ctx) | |||
| 53 | + | ||||
| 54 | + | if len(ret) == 0 { | |||
| 55 | + | panic("no return value specified for List") | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | var r0 []out.SecretFileSet | |||
| 59 | + | var r1 error | |||
| 60 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) ([]out.SecretFileSet, error)); ok { | |||
| 61 | + | return returnFunc(ctx) | |||
| 62 | + | } | |||
| 63 | + | if returnFunc, ok := ret.Get(0).(func(context.Context) []out.SecretFileSet); ok { | |||
| 64 | + | r0 = returnFunc(ctx) | |||
| 65 | + | } else { | |||
| 66 | + | if ret.Get(0) != nil { | |||
| 67 | + | r0 = ret.Get(0).([]out.SecretFileSet) | |||
| 68 | + | } | |||
| 69 | + | } | |||
| 70 | + | if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { | |||
| 71 | + | r1 = returnFunc(ctx) | |||
| 72 | + | } else { | |||
| 73 | + | r1 = ret.Error(1) | |||
| 74 | + | } | |||
| 75 | + | return r0, r1 | |||
| 76 | + | } | |||
| 77 | + | ||||
| 78 | + | // MockSecretFileStore_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' | |||
| 79 | + | type MockSecretFileStore_List_Call struct { | |||
| 80 | + | *mock.Call | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | // List is a helper method to define mock.On call | |||
| 84 | + | // - ctx context.Context | |||
| 85 | + | func (_e *MockSecretFileStore_Expecter) List(ctx any) *MockSecretFileStore_List_Call { | |||
| 86 | + | return &MockSecretFileStore_List_Call{Call: _e.mock.On("List", ctx)} | |||
| 87 | + | } | |||
| 88 | + | ||||
| 89 | + | func (_c *MockSecretFileStore_List_Call) Run(run func(ctx context.Context)) *MockSecretFileStore_List_Call { | |||
| 90 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 91 | + | var arg0 context.Context | |||
| 92 | + | if args[0] != nil { | |||
| 93 | + | arg0 = args[0].(context.Context) | |||
| 94 | + | } | |||
| 95 | + | run( | |||
| 96 | + | arg0, | |||
| 97 | + | ) | |||
| 98 | + | }) | |||
| 99 | + | return _c | |||
| 100 | + | } | |||
| 101 | + | ||||
| 102 | + | func (_c *MockSecretFileStore_List_Call) Return(secretFileSets []out.SecretFileSet, err error) *MockSecretFileStore_List_Call { | |||
| 103 | + | _c.Call.Return(secretFileSets, err) | |||
| 104 | + | return _c | |||
| 105 | + | } | |||
| 106 | + | ||||
| 107 | + | func (_c *MockSecretFileStore_List_Call) RunAndReturn(run func(ctx context.Context) ([]out.SecretFileSet, error)) *MockSecretFileStore_List_Call { | |||
| 108 | + | _c.Call.Return(run) | |||
| 109 | + | return _c | |||
| 110 | + | } | |||
| 111 | + | ||||
| 112 | + | // Read provides a mock function for the type MockSecretFileStore | |||
| 113 | + | func (_mock *MockSecretFileStore) Read(ctx context.Context, app string, container string, key string) (string, error) { | |||
| 114 | + | ret := _mock.Called(ctx, app, container, key) | |||
| 115 | + | ||||
| 116 | + | if len(ret) == 0 { | |||
| 117 | + | panic("no return value specified for Read") | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | var r0 string | |||
| 121 | + | var r1 error | |||
| 122 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) (string, error)); ok { | |||
| 123 | + | return returnFunc(ctx, app, container, key) | |||
| 124 | + | } | |||
| 125 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) string); ok { | |||
| 126 | + | r0 = returnFunc(ctx, app, container, key) | |||
| 127 | + | } else { | |||
| 128 | + | r0 = ret.Get(0).(string) | |||
| 129 | + | } | |||
| 130 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok { | |||
| 131 | + | r1 = returnFunc(ctx, app, container, key) | |||
| 132 | + | } else { | |||
| 133 | + | r1 = ret.Error(1) | |||
| 134 | + | } | |||
| 135 | + | return r0, r1 | |||
| 136 | + | } | |||
| 137 | + | ||||
| 138 | + | // MockSecretFileStore_Read_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Read' | |||
| 139 | + | type MockSecretFileStore_Read_Call struct { | |||
| 140 | + | *mock.Call | |||
| 141 | + | } | |||
| 142 | + | ||||
| 143 | + | // Read is a helper method to define mock.On call | |||
| 144 | + | // - ctx context.Context | |||
| 145 | + | // - app string | |||
| 146 | + | // - container string | |||
| 147 | + | // - key string | |||
| 148 | + | func (_e *MockSecretFileStore_Expecter) Read(ctx any, app any, container any, key any) *MockSecretFileStore_Read_Call { | |||
| 149 | + | return &MockSecretFileStore_Read_Call{Call: _e.mock.On("Read", ctx, app, container, key)} | |||
| 150 | + | } | |||
| 151 | + | ||||
| 152 | + | func (_c *MockSecretFileStore_Read_Call) Run(run func(ctx context.Context, app string, container string, key string)) *MockSecretFileStore_Read_Call { | |||
| 153 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 154 | + | var arg0 context.Context | |||
| 155 | + | if args[0] != nil { | |||
| 156 | + | arg0 = args[0].(context.Context) | |||
| 157 | + | } | |||
| 158 | + | var arg1 string | |||
| 159 | + | if args[1] != nil { | |||
| 160 | + | arg1 = args[1].(string) | |||
| 161 | + | } | |||
| 162 | + | var arg2 string | |||
| 163 | + | if args[2] != nil { | |||
| 164 | + | arg2 = args[2].(string) | |||
| 165 | + | } | |||
| 166 | + | var arg3 string | |||
| 167 | + | if args[3] != nil { | |||
| 168 | + | arg3 = args[3].(string) | |||
| 169 | + | } | |||
| 170 | + | run( | |||
| 171 | + | arg0, | |||
| 172 | + | arg1, | |||
| 173 | + | arg2, | |||
| 174 | + | arg3, | |||
| 175 | + | ) | |||
| 176 | + | }) | |||
| 177 | + | return _c | |||
| 178 | + | } | |||
| 179 | + | ||||
| 180 | + | func (_c *MockSecretFileStore_Read_Call) Return(s string, err error) *MockSecretFileStore_Read_Call { | |||
| 181 | + | _c.Call.Return(s, err) | |||
| 182 | + | return _c | |||
| 183 | + | } | |||
| 184 | + | ||||
| 185 | + | func (_c *MockSecretFileStore_Read_Call) RunAndReturn(run func(ctx context.Context, app string, container string, key string) (string, error)) *MockSecretFileStore_Read_Call { | |||
| 186 | + | _c.Call.Return(run) | |||
| 187 | + | return _c | |||
| 188 | + | } | |||
| 189 | + | ||||
| 190 | + | // Remove provides a mock function for the type MockSecretFileStore | |||
| 191 | + | func (_mock *MockSecretFileStore) Remove(ctx context.Context, app string, container string) error { | |||
| 192 | + | ret := _mock.Called(ctx, app, container) | |||
| 193 | + | ||||
| 194 | + | if len(ret) == 0 { | |||
| 195 | + | panic("no return value specified for Remove") | |||
| 196 | + | } | |||
| 197 | + | ||||
| 198 | + | var r0 error | |||
| 199 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok { | |||
| 200 | + | r0 = returnFunc(ctx, app, container) | |||
| 201 | + | } else { | |||
| 202 | + | r0 = ret.Error(0) | |||
| 203 | + | } | |||
| 204 | + | return r0 | |||
| 205 | + | } | |||
| 206 | + | ||||
| 207 | + | // MockSecretFileStore_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove' | |||
| 208 | + | type MockSecretFileStore_Remove_Call struct { | |||
| 209 | + | *mock.Call | |||
| 210 | + | } | |||
| 211 | + | ||||
| 212 | + | // Remove is a helper method to define mock.On call | |||
| 213 | + | // - ctx context.Context | |||
| 214 | + | // - app string | |||
| 215 | + | // - container string | |||
| 216 | + | func (_e *MockSecretFileStore_Expecter) Remove(ctx any, app any, container any) *MockSecretFileStore_Remove_Call { | |||
| 217 | + | return &MockSecretFileStore_Remove_Call{Call: _e.mock.On("Remove", ctx, app, container)} | |||
| 218 | + | } | |||
| 219 | + | ||||
| 220 | + | func (_c *MockSecretFileStore_Remove_Call) Run(run func(ctx context.Context, app string, container string)) *MockSecretFileStore_Remove_Call { | |||
| 221 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 222 | + | var arg0 context.Context | |||
| 223 | + | if args[0] != nil { | |||
| 224 | + | arg0 = args[0].(context.Context) | |||
| 225 | + | } | |||
| 226 | + | var arg1 string | |||
| 227 | + | if args[1] != nil { | |||
| 228 | + | arg1 = args[1].(string) | |||
| 229 | + | } | |||
| 230 | + | var arg2 string | |||
| 231 | + | if args[2] != nil { | |||
| 232 | + | arg2 = args[2].(string) | |||
| 233 | + | } | |||
| 234 | + | run( | |||
| 235 | + | arg0, | |||
| 236 | + | arg1, | |||
| 237 | + | arg2, | |||
| 238 | + | ) | |||
| 239 | + | }) | |||
| 240 | + | return _c | |||
| 241 | + | } | |||
| 242 | + | ||||
| 243 | + | func (_c *MockSecretFileStore_Remove_Call) Return(err error) *MockSecretFileStore_Remove_Call { | |||
| 244 | + | _c.Call.Return(err) | |||
| 245 | + | return _c | |||
| 246 | + | } | |||
| 247 | + | ||||
| 248 | + | func (_c *MockSecretFileStore_Remove_Call) RunAndReturn(run func(ctx context.Context, app string, container string) error) *MockSecretFileStore_Remove_Call { | |||
| 249 | + | _c.Call.Return(run) | |||
| 250 | + | return _c | |||
| 251 | + | } | |||
| 252 | + | ||||
| 253 | + | // Write provides a mock function for the type MockSecretFileStore | |||
| 254 | + | func (_mock *MockSecretFileStore) Write(ctx context.Context, app string, container string, values map[string]string) (map[string]string, error) { | |||
| 255 | + | ret := _mock.Called(ctx, app, container, values) | |||
| 256 | + | ||||
| 257 | + | if len(ret) == 0 { | |||
| 258 | + | panic("no return value specified for Write") | |||
| 259 | + | } | |||
| 260 | + | ||||
| 261 | + | var r0 map[string]string | |||
| 262 | + | var r1 error | |||
| 263 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, map[string]string) (map[string]string, error)); ok { | |||
| 264 | + | return returnFunc(ctx, app, container, values) | |||
| 265 | + | } | |||
| 266 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, map[string]string) map[string]string); ok { | |||
| 267 | + | r0 = returnFunc(ctx, app, container, values) | |||
| 268 | + | } else { | |||
| 269 | + | if ret.Get(0) != nil { | |||
| 270 | + | r0 = ret.Get(0).(map[string]string) | |||
| 271 | + | } | |||
| 272 | + | } | |||
| 273 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, map[string]string) error); ok { | |||
| 274 | + | r1 = returnFunc(ctx, app, container, values) | |||
| 275 | + | } else { | |||
| 276 | + | r1 = ret.Error(1) | |||
| 277 | + | } | |||
| 278 | + | return r0, r1 | |||
| 279 | + | } | |||
| 280 | + | ||||
| 281 | + | // MockSecretFileStore_Write_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Write' | |||
| 282 | + | type MockSecretFileStore_Write_Call struct { | |||
| 283 | + | *mock.Call | |||
| 284 | + | } | |||
| 285 | + | ||||
| 286 | + | // Write is a helper method to define mock.On call | |||
| 287 | + | // - ctx context.Context | |||
| 288 | + | // - app string | |||
| 289 | + | // - container string | |||
| 290 | + | // - values map[string]string | |||
| 291 | + | func (_e *MockSecretFileStore_Expecter) Write(ctx any, app any, container any, values any) *MockSecretFileStore_Write_Call { | |||
| 292 | + | return &MockSecretFileStore_Write_Call{Call: _e.mock.On("Write", ctx, app, container, values)} | |||
| 293 | + | } | |||
| 294 | + | ||||
| 295 | + | func (_c *MockSecretFileStore_Write_Call) Run(run func(ctx context.Context, app string, container string, values map[string]string)) *MockSecretFileStore_Write_Call { | |||
| 296 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 297 | + | var arg0 context.Context | |||
| 298 | + | if args[0] != nil { | |||
| 299 | + | arg0 = args[0].(context.Context) | |||
| 300 | + | } | |||
| 301 | + | var arg1 string | |||
| 302 | + | if args[1] != nil { | |||
| 303 | + | arg1 = args[1].(string) | |||
| 304 | + | } | |||
| 305 | + | var arg2 string | |||
| 306 | + | if args[2] != nil { | |||
| 307 | + | arg2 = args[2].(string) | |||
| 308 | + | } | |||
| 309 | + | var arg3 map[string]string | |||
| 310 | + | if args[3] != nil { | |||
| 311 | + | arg3 = args[3].(map[string]string) | |||
| 312 | + | } | |||
| 313 | + | run( | |||
| 314 | + | arg0, | |||
| 315 | + | arg1, | |||
| 316 | + | arg2, | |||
| 317 | + | arg3, | |||
| 318 | + | ) | |||
| 319 | + | }) | |||
| 320 | + | return _c | |||
| 321 | + | } | |||
| 322 | + | ||||
| 323 | + | func (_c *MockSecretFileStore_Write_Call) Return(stringToString map[string]string, err error) *MockSecretFileStore_Write_Call { | |||
| 324 | + | _c.Call.Return(stringToString, err) | |||
| 325 | + | return _c | |||
| 326 | + | } | |||
| 327 | + | ||||
| 328 | + | func (_c *MockSecretFileStore_Write_Call) RunAndReturn(run func(ctx context.Context, app string, container string, values map[string]string) (map[string]string, error)) *MockSecretFileStore_Write_Call { | |||
| 329 | + | _c.Call.Return(run) | |||
| 330 | + | return _c | |||
| 331 | + | } | |||
A internal/boundaries/out/secret_files.go +21 -0
| @@ -0,0 +1,21 @@ | |||
| 1 | + | package out | |
| 2 | + | ||
| 3 | + | import "context" | |
| 4 | + | ||
| 5 | + | // SecretFileSet identifies one container's secret file directory. | |
| 6 | + | type SecretFileSet struct{ App, Container string } | |
| 7 | + | ||
| 8 | + | // SecretFileStore persists per-container secret files on the host. Values | |
| 9 | + | // cross this boundary in memory only; nothing is logged. | |
| 10 | + | type SecretFileStore interface { | |
| 11 | + | // Write replaces the container's directory with one 0444 file per key | |
| 12 | + | // and returns key -> absolute host path. | |
| 13 | + | Write(ctx context.Context, app, container string, values map[string]string) (map[string]string, error) | |
| 14 | + | // Read returns one file's content. | |
| 15 | + | Read(ctx context.Context, app, container, key string) (string, error) | |
| 16 | + | // Remove deletes the container's directory (missing is not an error) and | |
| 17 | + | // the app directory when it becomes empty. | |
| 18 | + | Remove(ctx context.Context, app, container string) error | |
| 19 | + | // List returns every existing container directory, sorted by app then container. | |
| 20 | + | List(ctx context.Context) ([]SecretFileSet, error) | |
| 21 | + | } | |
| @@ -0,0 +1,21 @@ | |||||
| 1 | + | package out | |||
| 2 | + | ||||
| 3 | + | import "context" | |||
| 4 | + | ||||
| 5 | + | // SecretFileSet identifies one container's secret file directory. | |||
| 6 | + | type SecretFileSet struct{ App, Container string } | |||
| 7 | + | ||||
| 8 | + | // SecretFileStore persists per-container secret files on the host. Values | |||
| 9 | + | // cross this boundary in memory only; nothing is logged. | |||
| 10 | + | type SecretFileStore interface { | |||
| 11 | + | // Write replaces the container's directory with one 0444 file per key | |||
| 12 | + | // and returns key -> absolute host path. | |||
| 13 | + | Write(ctx context.Context, app, container string, values map[string]string) (map[string]string, error) | |||
| 14 | + | // Read returns one file's content. | |||
| 15 | + | Read(ctx context.Context, app, container, key string) (string, error) | |||
| 16 | + | // Remove deletes the container's directory (missing is not an error) and | |||
| 17 | + | // the app directory when it becomes empty. | |||
| 18 | + | Remove(ctx context.Context, app, container string) error | |||
| 19 | + | // List returns every existing container directory, sorted by app then container. | |||
| 20 | + | List(ctx context.Context) ([]SecretFileSet, error) | |||
| 21 | + | } | |||