Skip to content

Commit b3d87bf

Browse files
committed
Add copy sync, mirror-drift use cases
1 parent 097e179 commit b3d87bf

11 files changed

+303
-39
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ node_modules/
66
todo.txt
77
.teller.writecase.yml
88
coverage.out
9+
fixtures/sync/target.env

README.md

+72-3
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,43 @@ If you omit `--in` Teller will take `stdin`, and if you omit `--out` Teller will
208208

209209
You can detect _secret drift_ by comparing values from different providers against each other. It might be that you want to pin a set of keys in different providers to always be the same value; when they aren't -- that means you have a drift.
210210

211-
For this, you first need to label values as `source` and couple with the appropriate sink as `sink` (use same label for both to couple them). Then, source keys will be compared against other keys in your configuration:
211+
In most cases, keys in providers would be similar which we call _mirrored_ providers. Example:
212+
213+
```
214+
Provider1:
215+
MG_PASS=foo***
216+
217+
Provider2:
218+
MG_PASS=foo*** # Both keys are called MG_PASS
219+
```
220+
221+
To detected mirror drifts, we use `teller mirror-drift`.
222+
223+
```bash
224+
$ teller mirror-drift --from global-dotenv --to my-dotenv
225+
226+
Drifts detected: 2
227+
228+
changed [] global-dotenv FOO_BAR "n***** != my-dotenv FOO_BAR ne*****
229+
missing [] global-dotenv FB 3***** ??
230+
```
231+
232+
As always, the specific provider definitions are in your `teller.yml` file.
233+
## :beetle: Detect secrets and value drift (non-mirrored providers)
234+
235+
Some times you want to check drift between two providers, and two unrelated keys. For example:
236+
237+
```
238+
Provider1:
239+
MG_PASS=foo***
240+
241+
Provider2:
242+
MAILGUN_PASS=foo***
243+
```
244+
245+
This poses a challenge. We need some way to "wire" the keys `MG_PASS` and `MAILGUN_PASS` and declare a relationship of source (`MG_PASS`) and destination, or sink (`MAILGUN_PASS`).
246+
247+
For this, you can label mappings as `source` and couple with the appropriate sink as `sink` (use same label value for both to wire them together). Then, source values will be compared against sink values in your configuration:
212248
213249
```yaml
214250
providers:
@@ -259,9 +295,42 @@ Will get you, assuming `FOO_BAR=Spock`:
259295
Hello, Spock!
260296
```
261297
262-
## :bike: Multi-write, rotation & sync
298+
## :arrows_counterclockwise: Copy/sync data between providers
299+
300+
In cases where you want to sync between providers, you can do that with `teller copy`.
301+
302+
303+
**Specific mapping key sync**
304+
305+
```bash
306+
$ teller copy --from dotenv1 --to dotenv2,heroku1
307+
```
308+
309+
This will:
310+
311+
1. Grab all mapped values from source (`dotenv1`)
312+
2. For each target provider, find the matching mapped key, and copy the value from source into it
313+
314+
**Full copy sync**
315+
316+
```bash
317+
$ teller copy --sync --from dotenv1 --to dotenv2,heroku1
318+
```
319+
This will:
320+
321+
1. Grab all mapped values from source (`dotenv1`)
322+
2. For each target provider, perform a full copy of values from source into the mapped `env_sync` key
323+
324+
325+
Notes:
326+
327+
* The mapping per provider is as configured in your `teller.yaml` file, in the `env_sync` or `env` properties.
328+
* This sync will try to copy _all_ values from the source.
329+
330+
331+
## :bike: Write and multi-write to providers
263332
264-
Teller providers supporting _write_ use cases which allow writing values _into_ the providers: **putting new values, synchronizing values, synchronizing providers, and fixing drift**.
333+
Teller providers supporting _write_ use cases which allow writing values _into_ providers.
265334
266335
Remember, for this feature it still revolves around definitions in your `teller.yml` file:
267336

fixtures/mirror-drift/source.env

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ONE=1
2+
TWO=2
3+
THREE=3

fixtures/mirror-drift/target.env

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ONE=5
2+
TWO=2

fixtures/mirror-drift/teller.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
project: mirror-drift
2+
3+
providers:
4+
source:
5+
kind: dotenv
6+
env_sync:
7+
path: ../fixtures/mirror-drift/source.env
8+
target:
9+
kind: dotenv
10+
env_sync:
11+
path: ../fixtures/mirror-drift/target.env

fixtures/sync/source.env

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ONE=1
2+
TWO=2
3+
THREE=3

fixtures/sync/target2.env

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FOO="2"
2+
ONE="1"
3+
THREE="3"
4+
TWO="2"

fixtures/sync/teller.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
project: sync
2+
3+
providers:
4+
source:
5+
kind: dotenv
6+
env_sync:
7+
path: ../fixtures/sync/source.env
8+
target:
9+
kind: dotenv
10+
env_sync:
11+
path: ../fixtures/sync/target.env
12+
target2:
13+
kind: dotenv
14+
env_sync:
15+
path: ../fixtures/sync/target2.env

main.go

+29
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ var CLI struct {
5757
Sync bool `optional name:"sync" help:"Sync all given k/vs to the env_sync key"`
5858
Path string `optional name:"path" help:"Take literal path and not from config"`
5959
} `cmd help:"Put a new value"`
60+
61+
Copy struct {
62+
From string `name:"from" help:"A provider name to sync from"`
63+
To []string `name:"to" help:"A list of provider names to copy values from the source provider to"`
64+
Sync bool `optional name:"sync" help:"Sync all given k/vs to the env_sync key"`
65+
} `cmd help:"Sync data from a source provider directly to multiple target providers"`
66+
67+
MirrorDrift struct {
68+
Source string `name:"source" help:"A source to check drift against"`
69+
Target string `name:"target" help:"A target to check against source"`
70+
} `cmd help:"Check same-key (mirror) value drift between source and target"`
6071
}
6172

6273
var (
@@ -116,6 +127,24 @@ func main() {
116127
os.Exit(1)
117128
}
118129
os.Exit(0)
130+
case "copy":
131+
err := teller.Sync(CLI.Copy.From, CLI.Copy.To, CLI.Copy.Sync)
132+
if err != nil {
133+
fmt.Printf("Error: %v\n", err)
134+
os.Exit(1)
135+
}
136+
os.Exit(0)
137+
case "mirror-drift":
138+
drifts, err := teller.MirrorDrift(CLI.MirrorDrift.Source, CLI.MirrorDrift.Target)
139+
if err != nil {
140+
fmt.Printf("Error: %v\n", err)
141+
os.Exit(1)
142+
}
143+
if len(drifts) > 0 {
144+
teller.Porcelain.PrintDrift(drifts)
145+
os.Exit(1)
146+
}
147+
os.Exit(0)
119148
}
120149

121150
// collecting

pkg/teller.go

+92-36
Original file line numberDiff line numberDiff line change
@@ -319,56 +319,67 @@ func updateParams(ent *core.EnvEntry, from *core.KeyPath, pname string) {
319319
}
320320
}
321321

322-
func (tl *Teller) CollectFromProviderMap(ps *ProvidersMap) ([]core.EnvEntry, error) {
322+
func (tl *Teller) CollectFromProvider(pname string) ([]core.EnvEntry, error) {
323323
entries := []core.EnvEntry{}
324-
for pname, conf := range *ps {
325-
p, err := tl.Providers.GetProvider(pname)
326-
if err != nil {
327-
// ok, maybe same provider, with 'kind'?
328-
p, err = tl.Providers.GetProvider(conf.Kind)
329-
}
324+
conf := tl.Config.Providers[pname]
325+
p, err := tl.Providers.GetProvider(pname)
326+
if err != nil {
327+
// ok, maybe same provider, with 'kind'?
328+
p, err = tl.Providers.GetProvider(conf.Kind)
329+
}
330330

331-
// still no provider? bail.
331+
// still no provider? bail.
332+
if err != nil {
333+
return nil, err
334+
}
335+
336+
if conf.EnvMapping != nil {
337+
es, err := p.GetMapping(tl.Populate.KeyPath(*conf.EnvMapping))
332338
if err != nil {
333339
return nil, err
334340
}
335341

336-
if conf.EnvMapping != nil {
337-
es, err := p.GetMapping(tl.Populate.KeyPath(*conf.EnvMapping))
338-
if err != nil {
339-
return nil, err
340-
}
341-
342-
//nolint
343-
for k, v := range es {
344-
// optionally remap environment variables synced from the provider
345-
if val, ok := conf.EnvMapping.Remap[v.Key]; ok {
346-
es[k].Key = val
347-
}
348-
updateParams(&es[k], conf.EnvMapping, pname)
342+
//nolint
343+
for k, v := range es {
344+
// optionally remap environment variables synced from the provider
345+
if val, ok := conf.EnvMapping.Remap[v.Key]; ok {
346+
es[k].Key = val
349347
}
350-
351-
entries = append(entries, es...)
348+
updateParams(&es[k], conf.EnvMapping, pname)
352349
}
353350

354-
if conf.Env != nil {
355-
//nolint
356-
for k, v := range *conf.Env {
357-
ent, err := p.Get(tl.Populate.KeyPath(v.WithEnv(k)))
358-
if err != nil {
359-
if v.Optional {
360-
continue
361-
} else {
362-
return nil, err
363-
}
351+
entries = append(entries, es...)
352+
}
353+
354+
if conf.Env != nil {
355+
//nolint
356+
for k, v := range *conf.Env {
357+
ent, err := p.Get(tl.Populate.KeyPath(v.WithEnv(k)))
358+
if err != nil {
359+
if v.Optional {
360+
continue
364361
} else {
365-
//nolint
366-
updateParams(ent, &v, pname)
367-
entries = append(entries, *ent)
362+
return nil, err
368363
}
364+
} else {
365+
//nolint
366+
updateParams(ent, &v, pname)
367+
entries = append(entries, *ent)
369368
}
370369
}
371370
}
371+
return entries, nil
372+
}
373+
374+
func (tl *Teller) CollectFromProviderMap(ps *ProvidersMap) ([]core.EnvEntry, error) {
375+
entries := []core.EnvEntry{}
376+
for pname := range *ps {
377+
pents, err := tl.CollectFromProvider(pname)
378+
if err != nil {
379+
return nil, err
380+
}
381+
entries = append(entries, pents...)
382+
}
372383

373384
sort.Sort(core.EntriesByKey(entries))
374385
return entries, nil
@@ -499,3 +510,48 @@ func (tl *Teller) Put(kvmap map[string]string, providerNames []string, sync bool
499510

500511
return nil
501512
}
513+
514+
func (tl *Teller) Sync(from string, to []string, sync bool) error {
515+
entries, err := tl.CollectFromProvider(from)
516+
if err != nil {
517+
return err
518+
}
519+
kvmap := map[string]string{}
520+
for i := range entries {
521+
ent := entries[i]
522+
kvmap[ent.Key] = ent.Value
523+
}
524+
525+
err = tl.Put(kvmap, to, sync, "")
526+
return err
527+
}
528+
529+
func (tl *Teller) MirrorDrift(source, target string) ([]core.DriftedEntry, error) {
530+
drifts := []core.DriftedEntry{}
531+
sourceEntries, err := tl.CollectFromProvider(source)
532+
if err != nil {
533+
return nil, err
534+
}
535+
536+
targetEntries, err := tl.CollectFromProvider(target)
537+
if err != nil {
538+
return nil, err
539+
}
540+
541+
for i := range sourceEntries {
542+
sent := sourceEntries[i]
543+
tent := funk.Find(targetEntries, func(ent core.EnvEntry) bool {
544+
return sent.Key == ent.Key
545+
})
546+
if tent == nil {
547+
drifts = append(drifts, core.DriftedEntry{Diff: "missing", Source: sent})
548+
continue
549+
}
550+
tentry := tent.(core.EnvEntry)
551+
if sent.Value != tentry.Value {
552+
drifts = append(drifts, core.DriftedEntry{Diff: "changed", Source: sent, Target: tentry})
553+
}
554+
}
555+
556+
return drifts, nil
557+
}

0 commit comments

Comments
 (0)