2727package configuration
2828
2929import (
30+ "github.com/fatih/color"
31+ "github.com/olekukonko/tablewriter"
3032 "github.com/spf13/cobra"
3133 "github.com/spf13/viper"
3234 "github.com/briandowns/spinner"
@@ -35,6 +37,7 @@ import (
3537 "io"
3638 "github.com/sirupsen/logrus"
3739 "os"
40+ "sort"
3841 "github.com/AliceO2Group/Control/common/logger"
3942 "fmt"
4043 "strings"
@@ -64,6 +67,11 @@ const (
6467 logicError = iota // Logic/Output error
6568)
6669
70+ var (
71+ blue = color .New (color .FgHiBlue ).SprintFunc ()
72+ red = color .New (color .FgHiRed ).SprintFunc ()
73+ )
74+
6775func WrapCall (call ConfigurationCall ) RunFunc {
6876 return func (cmd * cobra.Command , args []string ) {
6977 endpoint := viper .GetString ("config_endpoint" )
@@ -262,6 +270,68 @@ func Show(cfg *configuration.ConsulSource, cmd *cobra.Command, args []string, o
262270 return nil , nonZero
263271}
264272
273+ func History (cfg * configuration.ConsulSource , cmd * cobra.Command , args []string , o io.Writer )(err error , code int ) {
274+ var key , component , entry string
275+
276+ if len (args ) < 1 || len (args ) > 2 {
277+ err = errors .New (fmt .Sprintf ("Accepts between 0 and 3 arg(s), but received %d" , len (args )))
278+ return err , invalidArgs
279+ }
280+ switch len (args ) {
281+ case 1 :
282+ if IsInputSingleValidWord (args [0 ]) {
283+ component = args [0 ]
284+ entry = ""
285+ } else if IsInputNameValid (args [0 ]) && ! strings .Contains (args [0 ], "@" ){
286+ splitCom := strings .Split (args [0 ], "/" )
287+ component = splitCom [0 ]
288+ entry = splitCom [1 ]
289+ } else {
290+ return errors .New (fmt .Sprintf ("Component and Entry name cannot contain `/ or `@`" )), invalidArgs
291+ }
292+ case 2 :
293+ if IsInputSingleValidWord (args [0 ]) && IsInputSingleValidWord (args [1 ]) {
294+ component = args [0 ]
295+ entry = args [1 ]
296+ } else {
297+ return errors .New (fmt .Sprintf ("Component and Entry name cannot contain `/ or `@`" )), invalidArgs
298+ }
299+ }
300+
301+ key = componentsPath + component + "/" + entry
302+ var keys sort.StringSlice
303+ keys , err = cfg .GetKeysByPrefix (key , "" )
304+
305+ if len (keys ) == 0 {
306+ return errors .New (fmt .Sprintf ("No data was found" )), emptyData
307+ } else {
308+ if entry != "" {
309+ sort .Sort (sort .Reverse (keys ))
310+ drawTableHistoryConfigs ([]string {}, keys , 0 , o )
311+ } else {
312+ maxLen := GetMaxLenOfKey (keys )
313+ var currentKeys sort.StringSlice
314+ _ , entry , _ := GetComponentEntryTimestamp (keys [0 ])
315+
316+ for _ , value := range keys {
317+ _ , currentEntry , _ := GetComponentEntryTimestamp (value )
318+ if currentEntry == entry {
319+ currentKeys = append (currentKeys , value )
320+ } else {
321+ fmt .Fprintln (o , "- " + entry )
322+ sort .Sort (sort .Reverse (currentKeys )) //sort in reverse of timestamps
323+ drawTableHistoryConfigs ([]string {}, currentKeys ,maxLen , o )
324+ currentKeys = []string {value }
325+ entry = currentEntry
326+ }
327+ }
328+ fmt .Fprintln (o , "- " + entry )
329+ drawTableHistoryConfigs ([]string {}, currentKeys ,maxLen , o )
330+ }
331+ }
332+ return nil , 0
333+ }
334+
265335func formatListOutput ( cmd * cobra.Command , output []string )(parsedOutput []byte , err error ) {
266336 format , err := cmd .Flags ().GetString ("output" )
267337 if err != nil {
@@ -289,6 +359,16 @@ func IsInputSingleValidWord(input string) bool {
289359 return ! strings .Contains (input , "/" ) && ! strings .Contains (input , "@" )
290360}
291361
362+ // Method to parse a timestamp in the specified format
363+ func GetTimestampInFormat (timestamp string , timeFormat string )(string , error ){
364+ timeStampAsInt , err := strconv .ParseInt (timestamp , 10 , 64 )
365+ if err != nil {
366+ return "" , errors .New (fmt .Sprintf ("Unable to identify timestamp" ))
367+ }
368+ tm := time .Unix (timeStampAsInt , 0 )
369+ return tm .Format (timeFormat ), nil
370+ }
371+
292372// Method to return the latest timestamp for a specified component & entry
293373// If no keys were passed an error and code exit 3 will be returned
294374func GetLatestTimestamp (keys []string , component string , entry string )(timestamp string , err error , code int ) {
@@ -343,4 +423,42 @@ func GetListOfComponentsAndOrWithTimestamps(keys []string, keyPrefix string, use
343423 }
344424 }
345425 return components , nil , nonZero
346- }
426+ }
427+
428+ func drawTableHistoryConfigs (headers []string , history []string , max int , o io.Writer ) {
429+ table := tablewriter .NewWriter (o )
430+ if len (headers ) > 0 {
431+ table .SetHeader (headers )
432+ }
433+ table .SetBorder (false )
434+ table .SetColMinWidth (0 , max )
435+
436+ for _ , value := range history {
437+ component , entry , timestamp := GetComponentEntryTimestamp (value )
438+ prettyTimestamp , err := GetTimestampInFormat (timestamp , time .RFC822 )
439+ if err != nil {
440+ prettyTimestamp = timestamp
441+ }
442+ configName := red (component ) + "/" + blue (entry ) + "@" + timestamp
443+ table .Append ([]string {configName , prettyTimestamp })
444+ }
445+ table .Render ()
446+ }
447+
448+ func GetComponentEntryTimestamp (key string )(string , string , string ) {
449+ key = strings .TrimPrefix (key , componentsPath )
450+ key = strings .TrimPrefix (key , "/'" )
451+ key = strings .TrimSuffix (key , "/" )
452+ elements := strings .Split (key , "/" )
453+ return elements [0 ], elements [1 ], elements [2 ]
454+ }
455+
456+ func GetMaxLenOfKey (keys []string ) (maxLen int ){
457+ maxLen = 0
458+ for _ , value := range keys {
459+ if len (value ) - len (componentsPath ) >= maxLen {
460+ maxLen = len (value ) - len (componentsPath )
461+ }
462+ }
463+ return
464+ }
0 commit comments