Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions internal/sync/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,12 @@ type AttachmentReader interface {
ReadAttachment(projectKey string, kind syncdomain.AttachmentKind, key string) (syncdomain.Attachment, error)
}

// ManifestStore reads and writes the sync baseline.
type ManifestStore interface {
Load(projectKeys []string) (syncmanifest.Manifest, error)
Update(previous, next syncmanifest.Manifest) (syncmanifest.Manifest, error)
}

// Options are the dependencies and the input of one add.
type Options struct {
Catalog Catalog
Attachments AttachmentReader
Store synclocal.Store
Manifest ManifestStore
Baselines syncmanifest.Baselines
Input io.Reader
Output io.Writer
// Initial is true when the workspace has no .launchdarkly directory.
Expand Down Expand Up @@ -242,7 +236,7 @@ func finishSelection(options Options, files []synclocal.VariationFile) error {
for _, file := range files {
projectKeys = append(projectKeys, file.ProjectKey)
}
manifest, err := options.Manifest.Load(projectKeys)
baseline, err := options.Baselines.Load(projectKeys)
if err != nil {
return err
}
Expand All @@ -256,9 +250,9 @@ func finishSelection(options Options, files []synclocal.VariationFile) error {
if err != nil {
return err
}
next, err := recordCreatedVariations(manifest, options.Store, files)
next, err := recordCreatedVariations(baseline.Lock, options.Store, files)
if err == nil {
_, err = options.Manifest.Update(manifest, next)
_, err = options.Baselines.Save(baseline, next)
}
if err != nil {
return errors.Join(err, options.Store.RollbackCreation(creation))
Expand Down
54 changes: 27 additions & 27 deletions internal/sync/bootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,18 @@ func TestFinishSelectionWritesInitialManifest(t *testing.T) {
secondVariation.Name = "Variation 2"

err := finishSelection(Options{
Store: synclocal.NewStore(root),
Manifest: manifestStore,
Output: &output,
Initial: true,
Store: synclocal.NewStore(root),
Baselines: manifestStore,
Output: &output,
Initial: true,
}, []synclocal.VariationFile{
{ProjectKey: "project", ConfigKey: "config", Upsert: true, Variation: variation},
{ProjectKey: "project", ConfigKey: "config", Upsert: true, Variation: secondVariation},
})

require.NoError(t, err)
manifest, err := manifestStore.Load([]string{"project"})
baseline, err := manifestStore.Load([]string{"project"})
manifest := baseline.Lock
require.NoError(t, err)
require.Len(t, manifest.Resources, 3)
attachmentFingerprint, err := syncdomain.FingerprintAttachment("project", variation.Attachments[0])
Expand Down Expand Up @@ -221,7 +222,7 @@ func TestFinishSelectionFingerprintsExistingAttachmentContent(t *testing.T) {
Tools: []syncdomain.AttachmentRef{{Key: "search"}}, Attachments: []syncdomain.Attachment{localAttachment},
}
require.NoError(t, finishSelection(Options{
Store: store, Manifest: manifestStore, Output: io.Discard, Initial: true,
Store: store, Baselines: manifestStore, Output: io.Discard, Initial: true,
}, []synclocal.VariationFile{{
ProjectKey: "project", ConfigKey: "config", Variation: first,
}}))
Expand All @@ -241,7 +242,7 @@ func TestFinishSelectionFingerprintsExistingAttachmentContent(t *testing.T) {
Tools: []syncdomain.AttachmentRef{{Key: "search"}}, Attachments: []syncdomain.Attachment{serverAttachment},
}
require.NoError(t, finishSelection(Options{
Store: store, Manifest: manifestStore, Output: io.Discard,
Store: store, Baselines: manifestStore, Output: io.Discard,
}, []synclocal.VariationFile{{
ProjectKey: "project", ConfigKey: "config", Variation: second,
}}))
Expand All @@ -258,7 +259,8 @@ func TestFinishSelectionFingerprintsExistingAttachmentContent(t *testing.T) {
}
require.NotEmpty(t, expectedFingerprint)

manifest, err := manifestStore.Load([]string{"project"})
baseline, err := manifestStore.Load([]string{"project"})
manifest := baseline.Lock
require.NoError(t, err)
for _, resource := range manifest.Resources {
if resource.ResourceKind == syncdomain.KindVariation && resource.LookupKey == "config/second" {
Expand All @@ -285,19 +287,20 @@ func TestFinishSelectionAddsMultipleVersionedVariationsToExistingManifest(t *tes
ModelConfigKey: "model", ModelConfigVersion: 3,
}
require.NoError(t, finishSelection(Options{
Store: store, Manifest: manifestStore, Output: io.Discard, Initial: true,
Store: store, Baselines: manifestStore, Output: io.Discard, Initial: true,
}, []synclocal.VariationFile{{
ProjectKey: "project", ConfigKey: "config", Variation: first,
}}))

require.NoError(t, finishSelection(Options{
Store: store, Manifest: manifestStore, Output: io.Discard,
Store: store, Baselines: manifestStore, Output: io.Discard,
}, []synclocal.VariationFile{
{ProjectKey: "project", ConfigKey: "config", Variation: second},
{ProjectKey: "project", ConfigKey: "config", Variation: third},
}))

manifest, err := manifestStore.Load([]string{"project"})
baseline, err := manifestStore.Load([]string{"project"})
manifest := baseline.Lock
require.NoError(t, err)
require.Len(t, manifest.Resources, 3)
assert.Equal(t, "config/first", manifest.Resources[0].LookupKey)
Expand All @@ -317,10 +320,10 @@ func TestFinishSelectionRollsBackFilesWhenManifestWriteFails(t *testing.T) {
}

err := finishSelection(Options{
Store: synclocal.NewStore(root),
Manifest: failingManifestStore{},
Output: io.Discard,
Initial: true,
Store: synclocal.NewStore(root),
Baselines: failingManifestStore{},
Output: io.Discard,
Initial: true,
}, []synclocal.VariationFile{{
ProjectKey: "project", ConfigKey: "config", Variation: variation,
}})
Expand Down Expand Up @@ -350,7 +353,7 @@ func TestFinishSelectionRollsBackNewAttachmentWithoutRemovingExistingWorkspace(t
}

err = finishSelection(Options{
Store: store, Manifest: failingManifestStore{}, Output: io.Discard,
Store: store, Baselines: failingManifestStore{}, Output: io.Discard,
}, []synclocal.VariationFile{{
ProjectKey: "project", ConfigKey: "config", Variation: variation,
}})
Expand Down Expand Up @@ -429,27 +432,24 @@ func (catalog *fakeCatalog) Config(string, string) (syncapi.Config, error) {

type failingManifestStore struct{}

func (failingManifestStore) Load([]string) (syncmanifest.Manifest, error) {
return syncmanifest.New(), nil
func (failingManifestStore) Load([]string) (syncmanifest.Baseline, error) {
return syncmanifest.Baseline{Lock: syncmanifest.New()}, nil
}

func (failingManifestStore) Update(syncmanifest.Manifest, syncmanifest.Manifest) (syncmanifest.Manifest, error) {
return syncmanifest.Manifest{}, errors.New("write manifest")
func (failingManifestStore) Save(syncmanifest.Baseline, syncmanifest.Manifest) (syncmanifest.Baseline, error) {
return syncmanifest.Baseline{}, errors.New("write manifest")
}

type memoryManifestStore struct {
manifest syncmanifest.Manifest
}

func (store *memoryManifestStore) Load([]string) (syncmanifest.Manifest, error) {
return store.manifest, nil
func (store *memoryManifestStore) Load([]string) (syncmanifest.Baseline, error) {
return syncmanifest.Baseline{Lock: store.manifest}, nil
}

func (store *memoryManifestStore) Update(
_ syncmanifest.Manifest,
next syncmanifest.Manifest,
) (syncmanifest.Manifest, error) {
func (store *memoryManifestStore) Save(_ syncmanifest.Baseline, next syncmanifest.Manifest) (syncmanifest.Baseline, error) {
next.Sort()
store.manifest = next
return next, nil
return syncmanifest.Baseline{Lock: next}, nil
}
48 changes: 21 additions & 27 deletions internal/sync/detach/detach.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,11 @@ import (
syncmanifest "github.com/launchdarkly/ldcli/internal/sync/manifest"
)

// ManifestStore reads and writes the sync baseline.
type ManifestStore interface {
Load(projectKeys []string) (syncmanifest.Manifest, error)
Update(previous, next syncmanifest.Manifest) (syncmanifest.Manifest, error)
}

// Options are the dependencies and the input of one detach.
type Options struct {
RepositoryRoot string
Store synclocal.Store
Manifest ManifestStore
Baselines syncmanifest.Baselines
// ProjectKeys are the projects that have local files.
ProjectKeys []string
Input io.Reader
Expand All @@ -44,7 +38,7 @@ func Run(options Options) error {
slices.Sort(projectKeys)
projectKeys = slices.Compact(projectKeys)

synced, manifest, err := loadResources(options.RepositoryRoot, options.Manifest, projectKeys)
synced, baseline, err := loadResources(options.RepositoryRoot, options.Baselines, projectKeys)
if err != nil {
return err
}
Expand All @@ -66,7 +60,7 @@ func Run(options Options) error {
return err
}
}
if err := detachResources(options, manifest, selected); err != nil {
if err := detachResources(options, baseline, selected); err != nil {
return err
}

Expand Down Expand Up @@ -110,24 +104,24 @@ func validateSelections(synced, selected []syncdomain.ResourceID) error {
return nil
}

// loadResources returns each variation that the manifest tracks or that has
// a local file, in identity order.
// loadResources returns each variation that the lock tracks or that has a
// local file, in identity order.
func loadResources(
repositoryRoot string,
manifestStore ManifestStore,
baselines syncmanifest.Baselines,
projectKeys []string,
) ([]syncdomain.ResourceID, syncmanifest.Manifest, error) {
manifest, err := manifestStore.Load(projectKeys)
) ([]syncdomain.ResourceID, syncmanifest.Baseline, error) {
baseline, err := baselines.Load(projectKeys)
if err != nil {
return nil, syncmanifest.Manifest{}, err
return nil, syncmanifest.Baseline{}, err
}
files, err := synclocal.SourceFiles(repositoryRoot)
if err != nil {
return nil, syncmanifest.Manifest{}, err
return nil, syncmanifest.Baseline{}, err
}

var synced []syncdomain.ResourceID
for _, resource := range manifest.Resources {
for _, resource := range baseline.Lock.Resources {
if resource.ResourceKind == syncdomain.KindVariation {
synced = append(synced, resource.ID())
}
Expand All @@ -138,16 +132,16 @@ func loadResources(
}
}
slices.SortFunc(synced, syncdomain.CompareResourceIDs)
return slices.Compact(synced), manifest, nil
return slices.Compact(synced), baseline, nil
}

// detachResources removes the selected variations from the manifest, and then
// deletes their local files. If the delete fails, it restores the manifest.
func detachResources(options Options, original syncmanifest.Manifest, selected []syncdomain.ResourceID) error {
// detachResources removes the selected variations from the baseline, and then
// deletes their local files. If the delete fails, it restores the baseline.
func detachResources(options Options, original syncmanifest.Baseline, selected []syncdomain.ResourceID) error {
isSelected := func(id syncdomain.ResourceID) bool { return slices.Contains(selected, id) }

next := syncmanifest.New()
for _, resource := range original.Resources {
for _, resource := range original.Lock.Resources {
if !isSelected(resource.ID()) {
next.Resources = append(next.Resources, resource)
}
Expand All @@ -160,12 +154,12 @@ func detachResources(options Options, original syncmanifest.Manifest, selected [
})
next.RemoveUnusedAttachments(remaining)
}
persisted, err := options.Manifest.Update(original, next)
saved, err := options.Baselines.Save(original, next)
if err != nil {
return err
}
restoreManifest := func(cause error) error {
_, restoreErr := options.Manifest.Update(persisted, original)
restoreBaseline := func(cause error) error {
_, restoreErr := options.Baselines.Save(saved, original.Lock)
return errors.Join(cause, restoreErr)
}

Expand All @@ -177,7 +171,7 @@ func detachResources(options Options, original syncmanifest.Manifest, selected [
}
exists, err := options.Store.VariationExists(resource.ProjectKey, configKey, variationKey)
if err != nil {
return restoreManifest(err)
return restoreBaseline(err)
}
if exists {
deletions = append(deletions, synclocal.VariationDeletion{
Expand All @@ -186,7 +180,7 @@ func detachResources(options Options, original syncmanifest.Manifest, selected [
}
}
if _, err := options.Store.DeleteVariations(deletions); err != nil {
return restoreManifest(err)
return restoreBaseline(err)
}
return nil
}
Loading
Loading