From f2d887e11b2fc456ad5bc6af45911571dada56ab Mon Sep 17 00:00:00 2001 From: Josh Sacks Date: Mon, 10 Feb 2025 09:44:21 -0800 Subject: [PATCH] add 'sync' command to sync remote split assignments with local schema (#64) Sometimes we want to sync our local TestTrack assignments with the weights in the production environment. Creating a TestTrack CLI command to easily take all the assignments from remote json split registry and assign them to local yaml assignment file. --- Makefile | 2 +- cmds/sync.go | 60 ++++++++++++++++++++++++++++++++++++++ serializers/serializers.go | 12 +++++++- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 cmds/sync.go diff --git a/Makefile b/Makefile index a8d3773..03c2a81 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ SHELL = /bin/sh -VERSION=1.5.0 +VERSION=1.6.0 BUILD=`git rev-parse HEAD` LDFLAGS=-ldflags "-w -s \ diff --git a/cmds/sync.go b/cmds/sync.go new file mode 100644 index 0000000..b3491e2 --- /dev/null +++ b/cmds/sync.go @@ -0,0 +1,60 @@ +package cmds + +import ( + "github.com/Betterment/testtrack-cli/schema" + "github.com/Betterment/testtrack-cli/serializers" + "github.com/Betterment/testtrack-cli/servers" + "github.com/Betterment/testtrack-cli/splits" + "github.com/spf13/cobra" +) + +var syncDoc = ` +Sync the local schema TestTrack assignments with the remote production TestTrack assignments. +` + +func init() { + rootCmd.AddCommand(syncCommand) +} + +var syncCommand = &cobra.Command{ + Use: "sync", + Short: "Sync TestTrack assignments with production", + Long: syncDoc, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + return Sync() + }, +} + +// Sync synchronizes the local schema TestTrack assignments with the remote production TestTrack assignments. +func Sync() error { + server, err := servers.New() + if err != nil { + return err + } + + var splitRegistry serializers.RemoteRegistry + err = server.Get("api/v2/split_registry.json", &splitRegistry) + if err != nil { + return err + } + + localSchema, err := schema.Read() + if err != nil { + return err + } + + for ind, localSplit := range localSchema.Splits { + remoteSplit, exists := splitRegistry.Splits[localSplit.Name] + if exists { + remoteWeights := splits.Weights(remoteSplit.Weights) + localSchema.Splits[ind].Weights = remoteWeights.ToYAML() + } + } + + if err := schema.Write(localSchema); err != nil { + return err + } + + return nil +} diff --git a/serializers/serializers.go b/serializers/serializers.go index 62fa537..7188161 100644 --- a/serializers/serializers.go +++ b/serializers/serializers.go @@ -45,12 +45,22 @@ type SplitYAML struct { Owner string `yaml:"owner,omitempty"` } -// SplitJSON is is the JSON-marshalabe representation of a Split +// SplitJSON is the JSON-marshalabe representation of a Split type SplitJSON struct { Name string `json:"name"` WeightingRegistry map[string]int `json:"weighting_registry"` } +// RemoteRegistrySplit is the JSON-marshalable representation of a server-provided split configuration +type RemoteRegistrySplit struct { + Weights map[string]int `json:"weights"` +} + +// RemoteRegistry is the JSON-marshalable representation of a server-provided split registry +type RemoteRegistry struct { + Splits map[string]RemoteRegistrySplit `json:"splits"` +} + // SplitRetirement is the JSON and YAML-marshalable representation of a SplitRetirement type SplitRetirement struct { Split string `json:"split"`