mirror of
https://github.com/dokku/dokku.git
synced 2026-08-29 10:08:53 +02:00
`storage:set` now takes `<name> <property> [<value>]` like every other `:set` command, where omitting the value unsets the property. Previously it took flags and could not distinguish an empty value from an omitted one, so nothing it set could ever be cleared. The flag form keeps working and emits a deprecation warning. Annotations and labels move to `storage:annotations:set`, `storage:annotations:report`, `storage:labels:set`, and `storage:labels:report`, matching the `scheduler-k3s` equivalents. These operate on a single key, so clearing one leaves the rest in place rather than replacing the whole map as the `--annotation` and `--label` flags do.
165 lines
5.7 KiB
Go
165 lines
5.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
// stageDockerLocalEntry writes a docker-local entry at the default host
|
|
// path so the metadata commands have something to operate on.
|
|
func stageDockerLocalEntry(t *testing.T, name string) *Entry {
|
|
t.Helper()
|
|
entry := &Entry{
|
|
Name: name,
|
|
Scheduler: SchedulerDockerLocal,
|
|
HostPath: filepath.Join(GetStorageDirectory(), name),
|
|
}
|
|
if err := SaveEntry(entry); err != nil {
|
|
t.Fatalf("SaveEntry: %v", err)
|
|
}
|
|
return entry
|
|
}
|
|
|
|
func TestSetEntryMapKeyAddsAndOverwrites(t *testing.T) {
|
|
RegisterTestingT(t)
|
|
withTempLibRoot(t)
|
|
stageDockerLocalEntry(t, "demo")
|
|
|
|
Expect(setEntryMapKey(annotationsField, "demo", "first", "one")).To(Succeed())
|
|
Expect(setEntryMapKey(annotationsField, "demo", "second", "two")).To(Succeed())
|
|
|
|
loaded, err := LoadEntry("demo")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(loaded.Annotations).To(Equal(map[string]string{"first": "one", "second": "two"}))
|
|
|
|
Expect(setEntryMapKey(annotationsField, "demo", "first", "rewritten")).To(Succeed())
|
|
loaded, err = LoadEntry("demo")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(loaded.Annotations).To(Equal(map[string]string{"first": "rewritten", "second": "two"}))
|
|
}
|
|
|
|
// TestSetEntryMapKeyDeletesOneKey is the behavior the wholesale
|
|
// --annotation flag could never express: clearing one key without
|
|
// disturbing its siblings.
|
|
func TestSetEntryMapKeyDeletesOneKey(t *testing.T) {
|
|
RegisterTestingT(t)
|
|
withTempLibRoot(t)
|
|
stageDockerLocalEntry(t, "demo")
|
|
|
|
Expect(setEntryMapKey(annotationsField, "demo", "first", "one")).To(Succeed())
|
|
Expect(setEntryMapKey(annotationsField, "demo", "second", "two")).To(Succeed())
|
|
Expect(setEntryMapKey(annotationsField, "demo", "first", "")).To(Succeed())
|
|
|
|
loaded, err := LoadEntry("demo")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(loaded.Annotations).To(Equal(map[string]string{"second": "two"}))
|
|
}
|
|
|
|
// TestSetEntryMapKeyDropsEmptyMap keeps the omitempty tag honest: once the
|
|
// last key is gone the field should be absent from the JSON, not an empty
|
|
// object, and should still round-trip.
|
|
func TestSetEntryMapKeyDropsEmptyMap(t *testing.T) {
|
|
RegisterTestingT(t)
|
|
withTempLibRoot(t)
|
|
stageDockerLocalEntry(t, "demo")
|
|
|
|
Expect(setEntryMapKey(labelsField, "demo", "only", "value")).To(Succeed())
|
|
Expect(setEntryMapKey(labelsField, "demo", "only", "")).To(Succeed())
|
|
|
|
loaded, err := LoadEntry("demo")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(loaded.Labels).To(BeEmpty())
|
|
|
|
Expect(SaveEntry(loaded)).To(Succeed())
|
|
reloaded, err := LoadEntry("demo")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(reloaded.Labels).To(BeEmpty())
|
|
}
|
|
|
|
func TestSetEntryMapKeyKeepsAnnotationsAndLabelsSeparate(t *testing.T) {
|
|
RegisterTestingT(t)
|
|
withTempLibRoot(t)
|
|
stageDockerLocalEntry(t, "demo")
|
|
|
|
Expect(setEntryMapKey(annotationsField, "demo", "shared", "annotation")).To(Succeed())
|
|
Expect(setEntryMapKey(labelsField, "demo", "shared", "label")).To(Succeed())
|
|
|
|
loaded, err := LoadEntry("demo")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(loaded.Annotations).To(Equal(map[string]string{"shared": "annotation"}))
|
|
Expect(loaded.Labels).To(Equal(map[string]string{"shared": "label"}))
|
|
}
|
|
|
|
// TestSetEntryMapKeyPreservesSlashKeys covers Kubernetes-style keys, which
|
|
// are the common case for both annotations and labels.
|
|
func TestSetEntryMapKeyPreservesSlashKeys(t *testing.T) {
|
|
RegisterTestingT(t)
|
|
withTempLibRoot(t)
|
|
stageDockerLocalEntry(t, "demo")
|
|
|
|
key := "backup.velero.io/backup-volumes"
|
|
Expect(setEntryMapKey(annotationsField, "demo", key, "demo")).To(Succeed())
|
|
|
|
loaded, err := LoadEntry("demo")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(loaded.Annotations).To(HaveKeyWithValue(key, "demo"))
|
|
}
|
|
|
|
func TestSetEntryMapKeyValidatesInputs(t *testing.T) {
|
|
RegisterTestingT(t)
|
|
withTempLibRoot(t)
|
|
stageDockerLocalEntry(t, "demo")
|
|
|
|
err := setEntryMapKey(annotationsField, "", "key", "value")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("name is required"))
|
|
|
|
err = setEntryMapKey(annotationsField, "demo", "", "value")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("No annotation key specified"))
|
|
|
|
err = setEntryMapKey(labelsField, "demo", "", "value")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("No label key specified"))
|
|
|
|
err = setEntryMapKey(annotationsField, "missing", "key", "value")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("does not exist"))
|
|
}
|
|
|
|
func TestReportEntryMapValidatesFormatAndInfoFlag(t *testing.T) {
|
|
RegisterTestingT(t)
|
|
withTempLibRoot(t)
|
|
stageDockerLocalEntry(t, "demo")
|
|
|
|
err := reportEntryMap(annotationsField, "demo", "yaml", "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("Invalid format"))
|
|
|
|
err = reportEntryMap(annotationsField, "demo", "json", "--storage-annotations.first")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("cannot be specified when specifying an info flag"))
|
|
|
|
// An info flag has no single answer across every entry, so it needs a name.
|
|
err = reportEntryMap(annotationsField, "", "stdout", "--storage-annotations.first")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("requires a storage entry name"))
|
|
|
|
err = reportEntryMap(annotationsField, "missing", "stdout", "")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("does not exist"))
|
|
}
|
|
|
|
func TestReportEntryMapRejectsUnknownInfoFlag(t *testing.T) {
|
|
RegisterTestingT(t)
|
|
withTempLibRoot(t)
|
|
stageDockerLocalEntry(t, "demo")
|
|
Expect(setEntryMapKey(annotationsField, "demo", "first", "one")).To(Succeed())
|
|
|
|
err := reportEntryMap(annotationsField, "demo", "stdout", "--storage-annotations.nope")
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("Invalid flag passed, valid flags: --storage-annotations.first"))
|
|
}
|