Skip to content

Commit

Permalink
Merge pull request #16 from ryan-ph/ryan-ph/output-matching-resources
Browse files Browse the repository at this point in the history
[cmd/match] add new match command
  • Loading branch information
drlau authored Oct 29, 2024
2 parents b6355b2 + 325d22b commit 1593d5c
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 2 deletions.
2 changes: 2 additions & 0 deletions cmd/akashi.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/drlau/akashi/internal/compare"
comparecmd "github.com/drlau/akashi/pkg/cmd/compare"
diffcmd "github.com/drlau/akashi/pkg/cmd/diff"
matchcmd "github.com/drlau/akashi/pkg/cmd/match"
validatecmd "github.com/drlau/akashi/pkg/cmd/validate"
versioncmd "github.com/drlau/akashi/pkg/cmd/version"
"github.com/drlau/akashi/pkg/plan"
Expand Down Expand Up @@ -53,6 +54,7 @@ func NewCommand() *cobra.Command {

cmd.AddCommand(comparecmd.NewCmdCompare())
cmd.AddCommand(diffcmd.NewCmdDiff())
cmd.AddCommand(matchcmd.NewCmdMatch())
cmd.AddCommand(validatecmd.NewCmd())
cmd.AddCommand(versioncmd.NewCmdVersion(os.Stdout, version))

Expand Down
2 changes: 1 addition & 1 deletion internal/validate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package validate
import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/drlau/akashi/pkg/ruleset"
"github.com/google/go-cmp/cmp"
)

func TestValidate(t *testing.T) {
Expand Down
77 changes: 77 additions & 0 deletions pkg/cmd/match/match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package match

import (
"fmt"
"io"
"strings"

"github.com/drlau/akashi/internal/compare"
"github.com/drlau/akashi/pkg/plan"
"github.com/drlau/akashi/pkg/utils"

"github.com/spf13/cobra"
)

type MatchOptions struct {
File string
JSON bool
Invert bool
Separator string
}

func NewCmdMatch() *cobra.Command {
opts := &MatchOptions{}
cmd := &cobra.Command{
Use: "match <path to ruleset>",
Short: "Outputs resource paths which match the ruleset",
Long: `Outputs resource paths from "terraform plan" which are defined in the ruleset`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
comparers, err := compare.NewComparerSet(args[0])
if err != nil {
return err
}

plan, err := plan.NewResourcePlans(opts.File, opts.JSON)
if err != nil {
return err
}

out := utils.NewOutput(true)
cmd.SilenceErrors = true
runMatch(out, plan, comparers, opts)

return nil
},
}

cmd.Flags().StringVarP(&opts.File, "file", "f", "", "read plan output from file")
cmd.Flags().BoolVarP(&opts.JSON, "json", "j", false, "read the contents as the output from 'terraform state show -json'")
cmd.Flags().BoolVarP(&opts.Invert, "invert", "i", false, "outputs resources which do not match the ruleset")
cmd.Flags().StringVarP(&opts.Separator, "separator", "s", "\n", "separator between resource paths")

return cmd
}

func runMatch(out io.Writer, rc []plan.ResourcePlan, comparers compare.ComparerSet, opts *MatchOptions) {
createComparer := comparers.CreateComparer
destroyComparer := comparers.DestroyComparer
updateComparer := comparers.UpdateComparer

var matches []string
for _, r := range rc {
var match bool
if r.IsCreate() && createComparer != nil {
match = createComparer.Compare(r)
} else if r.IsDelete() && destroyComparer != nil {
match = destroyComparer.Compare(r)
} else if r.IsUpdate() && updateComparer != nil {
match = updateComparer.Compare(r)
}
if (!opts.Invert && match) || (opts.Invert && !match) {
matches = append(matches, r.GetAddress())
}
}

fmt.Fprintln(out, strings.Join(matches, opts.Separator))
}
128 changes: 128 additions & 0 deletions pkg/cmd/match/match_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package match

import (
"bytes"
"testing"

"github.com/drlau/akashi/internal/compare"
comparefakes "github.com/drlau/akashi/internal/compare/fakes"
"github.com/drlau/akashi/pkg/plan"
planfakes "github.com/drlau/akashi/pkg/plan/fakes"
)

func TestRunMatch(t *testing.T) {
cases := map[string]struct {
comparers compare.ComparerSet
resourcePlan []plan.ResourcePlan
opts *MatchOptions
expectedOutput string
}{
"outputs all matching resources from the ruleset": {
comparers: compare.ComparerSet{
CreateComparer: &comparefakes.FakeComparer{
CompareReturns: true,
},
DestroyComparer: &comparefakes.FakeComparer{
CompareReturns: false,
},
},
resourcePlan: []plan.ResourcePlan{
&planfakes.FakeResourcePlan{
CreateReturns: true,
AddressReturns: "address1",
NameReturns: "name",
TypeReturns: "type",
},
&planfakes.FakeResourcePlan{
CreateReturns: true,
AddressReturns: "address2",
NameReturns: "name",
TypeReturns: "type",
},
&planfakes.FakeResourcePlan{
DeleteReturns: true,
AddressReturns: "address3",
NameReturns: "name",
TypeReturns: "type",
},
},
opts: &MatchOptions{Separator: "\n"},
expectedOutput: "address1\naddress2\n",
},
"outputs all non-matching resources from ruleset when inverted": {
comparers: compare.ComparerSet{
CreateComparer: &comparefakes.FakeComparer{
CompareReturns: true,
},
DestroyComparer: &comparefakes.FakeComparer{
CompareReturns: false,
},
},
resourcePlan: []plan.ResourcePlan{
&planfakes.FakeResourcePlan{
CreateReturns: true,
AddressReturns: "address1",
NameReturns: "name",
TypeReturns: "type",
},
&planfakes.FakeResourcePlan{
CreateReturns: true,
AddressReturns: "address2",
NameReturns: "name",
TypeReturns: "type",
},
&planfakes.FakeResourcePlan{
DeleteReturns: true,
AddressReturns: "address3",
NameReturns: "name",
TypeReturns: "type",
},
},
opts: &MatchOptions{Separator: "\n", Invert: true},
expectedOutput: "address3\n",
},
"outputs all resources using custom separator": {
comparers: compare.ComparerSet{
CreateComparer: &comparefakes.FakeComparer{
CompareReturns: true,
},
DestroyComparer: &comparefakes.FakeComparer{
CompareReturns: false,
},
},
resourcePlan: []plan.ResourcePlan{
&planfakes.FakeResourcePlan{
CreateReturns: true,
AddressReturns: "address1",
NameReturns: "name",
TypeReturns: "type",
},
&planfakes.FakeResourcePlan{
CreateReturns: true,
AddressReturns: "address2",
NameReturns: "name",
TypeReturns: "type",
},
&planfakes.FakeResourcePlan{
DeleteReturns: true,
AddressReturns: "address3",
NameReturns: "name",
TypeReturns: "type",
},
},
opts: &MatchOptions{Separator: ","},
expectedOutput: "address1,address2\n",
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
var output bytes.Buffer
runMatch(&output, tc.resourcePlan, tc.comparers, tc.opts)

if output.String() != tc.expectedOutput {
t.Errorf("Expected: %q\nGot: %q\n", tc.expectedOutput, output.String())
}
})
}
}
2 changes: 1 addition & 1 deletion pkg/cmd/validate/validate.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package validate

import (
"os"
"fmt"
"os"

"github.com/drlau/akashi/internal/validate"
"github.com/drlau/akashi/pkg/ruleset"
Expand Down

0 comments on commit 1593d5c

Please sign in to comment.