...
1 package manifest
2
3 import (
4 "fmt"
5 "io/fs"
6 "os"
7 "path/filepath"
8
9 "github.com/spf13/afero"
10 "k8s.io/apimachinery/pkg/runtime"
11 "sigs.k8s.io/yaml"
12
13 "edge-infra.dev/pkg/k8s/decoder"
14 "edge-infra.dev/pkg/k8s/unstructured"
15 )
16
17 type Manifest struct {
18 fs afero.Fs
19 path string
20 content runtime.Object
21 mode os.FileMode
22 }
23
24
25
26
27
28
29
30
31
32
33
34
35 func New(fs afero.Fs, path string, content runtime.Object, mode os.FileMode) Manifest {
36 return Manifest{
37 fs: fs,
38 path: path,
39 content: content,
40 mode: mode,
41 }
42 }
43
44
45
46
47
48 func (m *Manifest) WithCreate(fn func(obj runtime.Object) error) error {
49 if err := fn(m.content); err != nil {
50 return err
51 }
52
53 if m.mode == 0 {
54 m.mode = 0600
55 }
56 return m.Write()
57 }
58
59
60
61
62
63 func (m *Manifest) WithUpdate(fn func(obj runtime.Object) error) error {
64 syncMode := (m.mode == 0)
65
66 if err := m.Read(syncMode); err != nil {
67 return fmt.Errorf("failed to read etcd manifest data: %w", err)
68 }
69
70 if err := fn(m.content); err != nil {
71 return err
72 }
73 return m.Write()
74 }
75
76
77
78
79
80
81
82 func (m *Manifest) Read(syncMode bool) error {
83 manifestBytes, err := afero.ReadFile(m.fs, m.path)
84 if err != nil {
85 return err
86 }
87
88 if err := m.Load(manifestBytes); err != nil {
89 return err
90 }
91
92 if syncMode {
93 return m.syncFileMode()
94 }
95 return nil
96 }
97
98
99
100 func (m *Manifest) syncFileMode() error {
101 fileInfo, err := m.Stat()
102 if err != nil {
103 return fmt.Errorf("failed to stat file (%s): %w", m.path, err)
104 }
105
106 m.mode = fileInfo.Mode()
107 return nil
108 }
109
110
111
112 func (m *Manifest) Stat() (fs.FileInfo, error) {
113 file, err := m.fs.Open(m.path)
114 if err != nil {
115 return nil, fmt.Errorf("failed to open file: %w", err)
116 }
117
118 return file.Stat()
119 }
120
121
122
123
124
125 func (m *Manifest) Load(manifestBytes []byte) error {
126 manifests, err := decoder.DecodeYAML(manifestBytes)
127 if err != nil {
128 return fmt.Errorf("failed to decode YAML: %w", err)
129 }
130 if len(manifests) == 0 {
131 return fmt.Errorf("no manifests found in file")
132 }
133
134 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(manifests[0].UnstructuredContent(), m.content); err != nil {
135 return fmt.Errorf("failed to convert: %w", err)
136 }
137 return nil
138 }
139
140
141
142
143 func (m *Manifest) Write() error {
144 if m.content == nil {
145 return fmt.Errorf("unable to write nil content")
146 }
147 bytes, err := m.Bytes()
148 if err != nil {
149 return err
150 }
151
152 tmpFilename := "." + filepath.Base(m.path) + ".tmp"
153 tmpPath := filepath.Join(filepath.Dir(m.path), tmpFilename)
154 if err := afero.WriteFile(m.fs, tmpPath, bytes, m.mode); err != nil {
155 return err
156 }
157 return m.fs.Rename(tmpPath, m.path)
158 }
159
160
161 func (m *Manifest) Content() runtime.Object {
162 return m.content
163 }
164
165
166 func (m *Manifest) Bytes() ([]byte, error) {
167 uobj := unstructured.Unstructured{}
168 obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(m.content)
169 if err != nil {
170 return nil, err
171 }
172 uobj.Object = obj
173
174 json, err := uobj.MarshalJSON()
175 if err != nil {
176 return nil, err
177 }
178
179 return yaml.JSONToYAML(json)
180 }
181
View as plain text