Skip to content

Commit

Permalink
feat(tags): implement tags package (#62)
Browse files Browse the repository at this point in the history
The tags package allows to;
- add tags
- remove tags
- remove unknown tags
- list tags

and has the accompanying CLI commands
  • Loading branch information
Tieske authored Jun 26, 2023
1 parent 9372c2a commit a8b95e9
Show file tree
Hide file tree
Showing 7 changed files with 1,051 additions and 0 deletions.
103 changes: 103 additions & 0 deletions cmd/addtags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"fmt"
"log"
"strings"

"github.com/kong/go-apiops/deckformat"
"github.com/kong/go-apiops/filebasics"
"github.com/kong/go-apiops/logbasics"
"github.com/kong/go-apiops/tags"
"github.com/spf13/cobra"
)

// Executes the CLI command "add-tags"
func executeAddTags(cmd *cobra.Command, tagsToAdd []string) error {
verbosity, _ := cmd.Flags().GetInt("verbose")
logbasics.Initialize(log.LstdFlags, verbosity)

inputFilename, err := cmd.Flags().GetString("state")
if err != nil {
return fmt.Errorf("failed getting cli argument 'state'; %w", err)
}

outputFilename, err := cmd.Flags().GetString("output-file")
if err != nil {
return fmt.Errorf("failed getting cli argument 'output-file'; %w", err)
}

var outputFormat string
{
outputFormat, err = cmd.Flags().GetString("format")
if err != nil {
return fmt.Errorf("failed getting cli argument 'format'; %w", err)
}
outputFormat = strings.ToUpper(outputFormat)
}

var selectors []string
{
selectors, err = cmd.Flags().GetStringArray("selector")
if err != nil {
return fmt.Errorf("failed getting cli argument 'selector'; %w", err)
}
}

// do the work: read/add-tags/write
data, err := filebasics.DeserializeFile(inputFilename)
if err != nil {
return fmt.Errorf("failed to read input file '%s'; %w", inputFilename, err)
}

tagger := tags.Tagger{}
tagger.SetData(data)
err = tagger.SetSelectors(selectors)
if err != nil {
return fmt.Errorf("failed to set selectors; %w", err)
}
err = tagger.AddTags(tagsToAdd)
if err != nil {
return fmt.Errorf("failed to add tags; %w", err)
}
data = tagger.GetData()

trackInfo := deckformat.HistoryNewEntry("add-tags")
trackInfo["input"] = inputFilename
trackInfo["output"] = outputFilename
trackInfo["tags"] = tagsToAdd
trackInfo["selectors"] = selectors
deckformat.HistoryAppend(data, trackInfo)

return filebasics.WriteSerializedFile(outputFilename, data, outputFormat)
}

//
//
// Define the CLI data for the add-tags command
//
//

var addTagsCmd = &cobra.Command{
Use: "add-tags [flags] tag [...tag]",
Short: "Adds tags to objects in a decK file",
Long: `Adds tags to objects in a decK file.
The tags are added to all objects that match the selector expressions. If no
selectors are given, all Kong entities are tagged.`,
RunE: executeAddTags,
Args: cobra.MinimumNArgs(1),
}

func init() {
rootCmd.AddCommand(addTagsCmd)
addTagsCmd.Flags().StringP("state", "s", "-", "decK file to process. Use - to read from stdin")
addTagsCmd.Flags().StringArray("selector", []string{}, "JSON path expression to select "+
"objects to add tags to,\ndefaults to all Kong entities (repeat for multiple selectors)")
addTagsCmd.Flags().StringP("output-file", "o", "-", "output file to write. Use - to write to stdout")
addTagsCmd.Flags().StringP("format", "", filebasics.OutputFormatYaml, "output format: "+
filebasics.OutputFormatJSON+" or "+filebasics.OutputFormatYaml)
}
102 changes: 102 additions & 0 deletions cmd/listtags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"fmt"
"log"
"strings"

"github.com/kong/go-apiops/filebasics"
"github.com/kong/go-apiops/logbasics"
"github.com/kong/go-apiops/tags"
"github.com/spf13/cobra"
)

// Executes the CLI command "list-tags"
func executeListTags(cmd *cobra.Command, _ []string) error {
verbosity, _ := cmd.Flags().GetInt("verbose")
logbasics.Initialize(log.LstdFlags, verbosity)

inputFilename, err := cmd.Flags().GetString("state")
if err != nil {
return fmt.Errorf("failed getting cli argument 'state'; %w", err)
}

outputFilename, err := cmd.Flags().GetString("output-file")
if err != nil {
return fmt.Errorf("failed getting cli argument 'output-file'; %w", err)
}

var outputFormat string
{
outputFormat, err = cmd.Flags().GetString("format")
if err != nil {
return fmt.Errorf("failed getting cli argument 'format'; %w", err)
}
outputFormat = strings.ToUpper(outputFormat)
}

var selectors []string
{
selectors, err = cmd.Flags().GetStringArray("selector")
if err != nil {
return fmt.Errorf("failed getting cli argument 'selector'; %w", err)
}
}

// do the work: read/list-tags/write
data, err := filebasics.DeserializeFile(inputFilename)
if err != nil {
return fmt.Errorf("failed to read input file '%s'; %w", inputFilename, err)
}

tagger := tags.Tagger{}
tagger.SetData(data)
err = tagger.SetSelectors(selectors)
if err != nil {
return fmt.Errorf("failed to set selectors; %w", err)
}
list, err := tagger.ListTags()
if err != nil {
return fmt.Errorf("failed to list tags; %w", err)
}

if outputFormat == "PLAIN" {
// return as a plain text format, unix style; line separated
result := []byte(strings.Join(list, "\n"))
return filebasics.WriteFile(outputFilename, &result)
}
// return as yaml/json, create an object containing only a tags-array
result := make(map[string]interface{})
result["tags"] = list
return filebasics.WriteSerializedFile(outputFilename, result, outputFormat)
}

//
//
// Define the CLI data for the list-tags command
//
//

var ListTagsCmd = &cobra.Command{
Use: "list-tags [flags]",
Short: "Lists current tags to objects in a decK file",
Long: `Lists current tags to objects in a decK file.
The tags will be collected from all objects that match the selector expressions. If no
selectors are given, all Kong entities will be scanned.`,
RunE: executeListTags,
Args: cobra.NoArgs,
}

func init() {
rootCmd.AddCommand(ListTagsCmd)
ListTagsCmd.Flags().StringP("state", "s", "-", "decK file to process. Use - to read from stdin")
ListTagsCmd.Flags().StringArray("selector", []string{}, "JSON path expression to select "+
"objects to scan for tags,\ndefaults to all Kong entities (repeat for multiple selectors)")
ListTagsCmd.Flags().StringP("output-file", "o", "-", "output file to write. Use - to write to stdout")
ListTagsCmd.Flags().StringP("format", "", "PLAIN", "output format: "+
filebasics.OutputFormatJSON+", "+filebasics.OutputFormatYaml+", or PLAIN")
}
130 changes: 130 additions & 0 deletions cmd/removetags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"fmt"
"log"
"strings"

"github.com/kong/go-apiops/deckformat"
"github.com/kong/go-apiops/filebasics"
"github.com/kong/go-apiops/logbasics"
"github.com/kong/go-apiops/tags"
"github.com/spf13/cobra"
)

// Executes the CLI command "remove-tags"
func executeRemoveTags(cmd *cobra.Command, tagsToRemove []string) error {
verbosity, _ := cmd.Flags().GetInt("verbose")
logbasics.Initialize(log.LstdFlags, verbosity)

inputFilename, err := cmd.Flags().GetString("state")
if err != nil {
return fmt.Errorf("failed getting cli argument 'state'; %w", err)
}

outputFilename, err := cmd.Flags().GetString("output-file")
if err != nil {
return fmt.Errorf("failed getting cli argument 'output-file'; %w", err)
}

var outputFormat string
{
outputFormat, err = cmd.Flags().GetString("format")
if err != nil {
return fmt.Errorf("failed getting cli argument 'format'; %w", err)
}
outputFormat = strings.ToUpper(outputFormat)
}

var selectors []string
{
selectors, err = cmd.Flags().GetStringArray("selector")
if err != nil {
return fmt.Errorf("failed getting cli argument 'selector'; %w", err)
}
}

var keepEmptyArrays bool
{
keepEmptyArrays, err = cmd.Flags().GetBool("keep-empty")
if err != nil {
return fmt.Errorf("failed getting cli argument 'keep-array'; %w", err)
}
}

var keepOnlyTags bool
{
keepOnlyTags, err = cmd.Flags().GetBool("keep-only")
if err != nil {
return fmt.Errorf("failed getting cli argument 'keep-only'; %w", err)
}
}

if !keepOnlyTags && len(tagsToRemove) == 0 {
return fmt.Errorf("no tags to remove")
}

// do the work: read/remove-tags/write
data, err := filebasics.DeserializeFile(inputFilename)
if err != nil {
return fmt.Errorf("failed to read input file '%s'; %w", inputFilename, err)
}

tagger := tags.Tagger{}
tagger.SetData(data)
err = tagger.SetSelectors(selectors)
if err != nil {
return fmt.Errorf("failed to set selectors; %w", err)
}
if keepOnlyTags {
err = tagger.RemoveUnknownTags(tagsToRemove, !keepEmptyArrays)
} else {
err = tagger.RemoveTags(tagsToRemove, !keepEmptyArrays)
}
if err != nil {
return fmt.Errorf("failed to remove tags; %w", err)
}
data = tagger.GetData()

trackInfo := deckformat.HistoryNewEntry("remove-tags")
trackInfo["input"] = inputFilename
trackInfo["output"] = outputFilename
trackInfo["tags"] = tagsToRemove
trackInfo["keep-empty"] = keepEmptyArrays
trackInfo["selectors"] = selectors
deckformat.HistoryAppend(data, trackInfo)

return filebasics.WriteSerializedFile(outputFilename, data, outputFormat)
}

//
//
// Define the CLI data for the remove-tags command
//
//

var RemoveTagsCmd = &cobra.Command{
Use: "remove-tags [flags] tag [...tag]",
Short: "Removes tags from objects in a decK file",
Long: `Removes tags from objects in a decK file.
The listed tags are removed from all objects that match the selector expressions.
If no selectors are given, all Kong entities will be selected.`,
RunE: executeRemoveTags,
}

func init() {
rootCmd.AddCommand(RemoveTagsCmd)
RemoveTagsCmd.Flags().Bool("keep-empty", false, "keep empty tag-arrays in output")
RemoveTagsCmd.Flags().Bool("keep-only", false, "setting this flag will remove all tags except the ones listed\n"+
"(if none are listed, all tags will be removed)")
RemoveTagsCmd.Flags().StringP("state", "s", "-", "decK file to process. Use - to read from stdin")
RemoveTagsCmd.Flags().StringArray("selector", []string{}, "JSON path expression to select "+
"objects to remove tags from,\ndefaults to all Kong entities (repeat for multiple selectors)")
RemoveTagsCmd.Flags().StringP("output-file", "o", "-", "output file to write. Use - to write to stdout")
RemoveTagsCmd.Flags().StringP("format", "", filebasics.OutputFormatYaml, "output format: "+
filebasics.OutputFormatJSON+" or "+filebasics.OutputFormatYaml)
}
Loading

0 comments on commit a8b95e9

Please sign in to comment.