File: configmaps.go

package info (click to toggle)
golang-github-containers-common 0.64.1%2Bds1-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 5,932 kB
  • sloc: makefile: 132; sh: 111
file content (285 lines) | stat: -rw-r--r-- 8,601 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package configmaps

import (
	"errors"
	"fmt"
	"maps"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"time"

	"github.com/containers/common/pkg/configmaps/filedriver"
	"github.com/containers/storage/pkg/lockfile"
	"github.com/containers/storage/pkg/regexp"
	"github.com/containers/storage/pkg/stringid"
)

// maxConfigMapSize is the max size for configMap data - 512kB
const maxConfigMapSize = 512000

// configMapIDLength is the character length of a configMap ID - 25
const configMapIDLength = 25

// errInvalidPath indicates that the configMaps path is invalid
var errInvalidPath = errors.New("invalid configmaps path")

// ErrNoSuchConfigMap indicates that the configMap does not exist
var ErrNoSuchConfigMap = errors.New("no such configmap")

// errConfigMapNameInUse indicates that the configMap name is already in use
var errConfigMapNameInUse = errors.New("configmap name in use")

// errInvalidConfigMapName indicates that the configMap name is invalid
var errInvalidConfigMapName = errors.New("invalid configmap name")

// errInvalidDriver indicates that the driver type is invalid
var errInvalidDriver = errors.New("invalid driver")

// errInvalidDriverOpt indicates that a driver option is invalid
var errInvalidDriverOpt = errors.New("invalid driver option")

// errAmbiguous indicates that a configMap is ambiguous
var errAmbiguous = errors.New("configmap is ambiguous")

// errDataSize indicates that the configMap data is too large or too small
var errDataSize = errors.New("configmap data must be larger than 0 and less than 512000 bytes")

// configMapsFile is the name of the file that the configMaps database will be stored in
var configMapsFile = "configmaps.json"

// configMapNameRegexp matches valid configMap names
// Allowed: 253 [a-zA-Z0-9-_.] characters, and the start and end character must be [a-zA-Z0-9]
var configMapNameRegexp = regexp.Delayed(`^[a-zA-Z0-9][a-zA-Z0-9_.-]*$`)

// ConfigMapManager holds information on handling configmaps
type ConfigMapManager struct {
	// configMapDBPath is the path to the db file where configmaps are stored
	configMapDBPath string
	// lockfile is the locker for the configmap file
	lockfile *lockfile.LockFile
	// db is an in-memory cache of the database of configMaps
	db *db
}

// ConfigMap defines a configMap
type ConfigMap struct {
	// Name is the name of the configmap
	Name string `json:"name"`
	// ID is the unique configMap ID
	ID string `json:"id"`
	// Metadata stores other metadata on the configMap
	Metadata map[string]string `json:"metadata,omitempty"`
	// CreatedAt is when the configMap was created
	CreatedAt time.Time `json:"createdAt"`
	// Driver is the driver used to store configMap data
	Driver string `json:"driver"`
	// DriverOptions is other metadata needed to use the driver
	DriverOptions map[string]string `json:"driverOptions"`
}

// ConfigMapsDriver interfaces with the configMaps data store.
// The driver stores the actual bytes of configMap data, as opposed to
// the configMap metadata.
//
// revive does not like the name because the package is already called configmaps
//
//nolint:revive
type ConfigMapsDriver interface {
	// List lists all configMap ids in the configMaps data store
	List() ([]string, error)
	// Lookup gets the configMap's data bytes
	Lookup(id string) ([]byte, error)
	// Store stores the configMap's data bytes
	Store(id string, data []byte) error
	// Delete deletes a configMap's data from the driver
	Delete(id string) error
}

// NewManager creates a new configMaps manager
// rootPath is the directory where the configMaps data file resides
func NewManager(rootPath string) (*ConfigMapManager, error) {
	manager := new(ConfigMapManager)

	if !filepath.IsAbs(rootPath) {
		return nil, fmt.Errorf("path must be absolute: %s: %w", rootPath, errInvalidPath)
	}
	// the lockfile functions require that the rootPath dir is executable
	if err := os.MkdirAll(rootPath, 0o700); err != nil {
		return nil, err
	}

	lock, err := lockfile.GetLockFile(filepath.Join(rootPath, "configMaps.lock"))
	if err != nil {
		return nil, err
	}
	manager.lockfile = lock
	manager.configMapDBPath = filepath.Join(rootPath, configMapsFile)
	manager.db = new(db)
	manager.db.ConfigMaps = make(map[string]ConfigMap)
	manager.db.NameToID = make(map[string]string)
	manager.db.IDToName = make(map[string]string)
	return manager, nil
}

// Store takes a name, creates a configMap and stores the configMap metadata and the configMap payload.
// It returns a generated ID that is associated with the configMap.
// The max size for configMap data is 512kB.
func (s *ConfigMapManager) Store(name string, data []byte, driverType string, driverOpts map[string]string) (string, error) {
	err := validateConfigMapName(name)
	if err != nil {
		return "", err
	}

	if len(data) == 0 || len(data) >= maxConfigMapSize {
		return "", errDataSize
	}

	s.lockfile.Lock()
	defer s.lockfile.Unlock()

	exist, err := s.exactConfigMapExists(name)
	if err != nil {
		return "", err
	}
	if exist {
		return "", fmt.Errorf("%s: %w", name, errConfigMapNameInUse)
	}

	secr := new(ConfigMap)
	secr.Name = name

	for {
		newID := stringid.GenerateNonCryptoID()
		// GenerateNonCryptoID() gives 64 characters, so we truncate to correct length
		newID = newID[0:configMapIDLength]
		_, err := s.lookupConfigMap(newID)
		if err != nil {
			if errors.Is(err, ErrNoSuchConfigMap) {
				secr.ID = newID
				break
			}
			return "", err
		}
	}

	secr.Driver = driverType
	secr.Metadata = make(map[string]string)
	secr.CreatedAt = time.Now()
	secr.DriverOptions = driverOpts

	driver, err := getDriver(driverType, driverOpts)
	if err != nil {
		return "", err
	}
	err = driver.Store(secr.ID, data)
	if err != nil {
		return "", fmt.Errorf("creating configMap %s: %w", name, err)
	}

	err = s.store(secr)
	if err != nil {
		return "", fmt.Errorf("creating configMap %s: %w", name, err)
	}

	return secr.ID, nil
}

// Delete removes all configMap metadata and configMap data associated with the specified configMap.
// Delete takes a name, ID, or partial ID.
func (s *ConfigMapManager) Delete(nameOrID string) (string, error) {
	err := validateConfigMapName(nameOrID)
	if err != nil {
		return "", err
	}

	s.lockfile.Lock()
	defer s.lockfile.Unlock()

	configMap, err := s.lookupConfigMap(nameOrID)
	if err != nil {
		return "", err
	}
	configMapID := configMap.ID

	driver, err := getDriver(configMap.Driver, configMap.DriverOptions)
	if err != nil {
		return "", err
	}

	err = driver.Delete(configMapID)
	if err != nil {
		return "", fmt.Errorf("deleting configMap %s: %w", nameOrID, err)
	}

	err = s.delete(configMapID)
	if err != nil {
		return "", fmt.Errorf("deleting configMap %s: %w", nameOrID, err)
	}
	return configMapID, nil
}

// Lookup gives a configMap's metadata given its name, ID, or partial ID.
func (s *ConfigMapManager) Lookup(nameOrID string) (*ConfigMap, error) {
	s.lockfile.Lock()
	defer s.lockfile.Unlock()

	return s.lookupConfigMap(nameOrID)
}

// List lists all configMaps.
func (s *ConfigMapManager) List() ([]ConfigMap, error) {
	s.lockfile.Lock()
	defer s.lockfile.Unlock()

	configMaps, err := s.lookupAll()
	if err != nil {
		return nil, err
	}
	return slices.Collect(maps.Values(configMaps)), nil
}

// LookupConfigMapData returns configMap metadata as well as configMap data in bytes.
// The configMap data can be looked up using its name, ID, or partial ID.
func (s *ConfigMapManager) LookupConfigMapData(nameOrID string) (*ConfigMap, []byte, error) {
	s.lockfile.Lock()
	defer s.lockfile.Unlock()

	configMap, err := s.lookupConfigMap(nameOrID)
	if err != nil {
		return nil, nil, err
	}
	driver, err := getDriver(configMap.Driver, configMap.DriverOptions)
	if err != nil {
		return nil, nil, err
	}
	data, err := driver.Lookup(configMap.ID)
	if err != nil {
		return nil, nil, err
	}
	return configMap, data, nil
}

// validateConfigMapName checks if the configMap name is valid.
func validateConfigMapName(name string) error {
	if !configMapNameRegexp.MatchString(name) ||
		len(name) > 253 ||
		strings.HasSuffix(name, "-") ||
		strings.HasSuffix(name, ".") ||
		strings.HasSuffix(name, "_") {
		return fmt.Errorf("only 253 [a-zA-Z0-9-_.] characters allowed, and the start and end character must be [a-zA-Z0-9]: %s: %w", name, errInvalidConfigMapName)
	}
	return nil
}

// getDriver creates a new driver.
func getDriver(name string, opts map[string]string) (ConfigMapsDriver, error) {
	if name == "file" {
		if path, ok := opts["path"]; ok {
			return filedriver.NewDriver(path)
		}
		return nil, fmt.Errorf("need path for filedriver: %w", errInvalidDriverOpt)
	}
	return nil, errInvalidDriver
}