-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from ryan-ph/cmd/validate/require-named
[cmd/validate] add a flag to require names for all resources
- Loading branch information
Showing
7 changed files
with
342 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package validate | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/drlau/akashi/pkg/ruleset" | ||
) | ||
|
||
// TODO: currently the only static validation we do is to check if names are | ||
// present for all resources in the ruleset if RequireName is set. Ideally we | ||
// should support more validations, so this struct will need to evolve to | ||
// contain more information about _why_ the resource is invalid. | ||
type ValidateResult struct { | ||
InvalidCreatedResources []*ruleset.ResourceIdentifier | ||
InvalidDestroyedResources []*ruleset.ResourceIdentifier | ||
InvalidUpdatedResources []*ruleset.ResourceIdentifier | ||
} | ||
|
||
func (r *ValidateResult) fill_defaults() { | ||
if r.InvalidCreatedResources == nil { | ||
r.InvalidCreatedResources = make([]*ruleset.ResourceIdentifier, 0) | ||
} | ||
if r.InvalidDestroyedResources == nil { | ||
r.InvalidDestroyedResources = make([]*ruleset.ResourceIdentifier, 0) | ||
} | ||
if r.InvalidUpdatedResources == nil { | ||
r.InvalidUpdatedResources = make([]*ruleset.ResourceIdentifier, 0) | ||
} | ||
} | ||
|
||
func formatResourceIDs(ids []*ruleset.ResourceIdentifier) []string { | ||
var lines []string | ||
for _, id := range ids { | ||
lines = append(lines, fmt.Sprintf("\t- %s", id.String())) | ||
} | ||
return lines | ||
} | ||
|
||
func (r *ValidateResult) String() string { | ||
if r.IsValid() { | ||
return "All resources valid!" | ||
} | ||
|
||
lines := []string{ | ||
"Found invalid resources in the ruleset:", | ||
"---------------------------------------", | ||
} | ||
if len(r.InvalidCreatedResources) != 0 { | ||
lines = append(lines, "Invalid Created Resources:") | ||
lines = append(lines, formatResourceIDs(r.InvalidCreatedResources)...) | ||
} | ||
if len(r.InvalidDestroyedResources) != 0 { | ||
lines = append(lines, "Invalid Destroyed Resources:") | ||
lines = append(lines, formatResourceIDs(r.InvalidDestroyedResources)...) | ||
} | ||
if len(r.InvalidUpdatedResources) != 0 { | ||
lines = append(lines, "Invalid Updated Resources:") | ||
lines = append(lines, formatResourceIDs(r.InvalidUpdatedResources)...) | ||
} | ||
return strings.Join(lines, "\n") | ||
} | ||
|
||
func (r *ValidateResult) IsValid() bool { | ||
createdValid := len(r.InvalidCreatedResources) == 0 | ||
destroyedValid := len(r.InvalidDestroyedResources) == 0 | ||
updatedValid := len(r.InvalidUpdatedResources) == 0 | ||
return createdValid && destroyedValid && updatedValid | ||
} | ||
|
||
func getUnnamedResources[T ruleset.Resource](rs []T) []*ruleset.ResourceIdentifier { | ||
var res []*ruleset.ResourceIdentifier | ||
for _, r := range rs { | ||
id := r.ID() | ||
if id.Name == "" { | ||
res = append(res, id) | ||
} | ||
} | ||
return res | ||
} | ||
|
||
func Validate(rs ruleset.Ruleset) *ValidateResult { | ||
res := &ValidateResult{} | ||
if rs.CreatedResources != nil && rs.CreatedResources.RequireName { | ||
ids := getUnnamedResources(rs.CreatedResources.Resources) | ||
res.InvalidCreatedResources = ids | ||
} | ||
if rs.DestroyedResources != nil && rs.DestroyedResources.RequireName { | ||
ids := getUnnamedResources(rs.DestroyedResources.Resources) | ||
res.InvalidDestroyedResources = ids | ||
} | ||
if rs.UpdatedResources != nil && rs.UpdatedResources.RequireName { | ||
ids := getUnnamedResources(rs.UpdatedResources.Resources) | ||
res.InvalidUpdatedResources = ids | ||
} | ||
return res | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package validate | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/drlau/akashi/pkg/ruleset" | ||
) | ||
|
||
func TestValidate(t *testing.T) { | ||
tests := map[string]struct { | ||
rs ruleset.Ruleset | ||
expected *ValidateResult | ||
}{ | ||
"valid ruleset without required names": { | ||
rs: ruleset.Ruleset{ | ||
CreatedResources: &ruleset.CreateDeleteResourceChanges{ | ||
Strict: false, | ||
RequireName: false, | ||
Resources: []ruleset.CreateDeleteResourceChange{ | ||
{ | ||
ResourceIdentifier: ruleset.ResourceIdentifier{ | ||
Type: "google_project_service", | ||
Name: "api", | ||
}, | ||
}, | ||
{ | ||
ResourceIdentifier: ruleset.ResourceIdentifier{ | ||
Type: "google_service_account", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
expected: &ValidateResult{}, | ||
}, | ||
"valid ruleset with required names": { | ||
rs: ruleset.Ruleset{ | ||
CreatedResources: &ruleset.CreateDeleteResourceChanges{ | ||
Strict: false, | ||
RequireName: true, | ||
Resources: []ruleset.CreateDeleteResourceChange{ | ||
{ | ||
ResourceIdentifier: ruleset.ResourceIdentifier{ | ||
Type: "google_project_service", | ||
Name: "api", | ||
}, | ||
}, | ||
{ | ||
ResourceIdentifier: ruleset.ResourceIdentifier{ | ||
Type: "google_service_account", | ||
Name: "my_service_account", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
expected: &ValidateResult{}, | ||
}, | ||
"invalid ruleset": { | ||
rs: ruleset.Ruleset{ | ||
CreatedResources: &ruleset.CreateDeleteResourceChanges{ | ||
Strict: false, | ||
RequireName: true, | ||
Resources: []ruleset.CreateDeleteResourceChange{ | ||
{ | ||
ResourceIdentifier: ruleset.ResourceIdentifier{ | ||
Type: "google_project_service", | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
expected: &ValidateResult{ | ||
InvalidCreatedResources: []*ruleset.ResourceIdentifier{ | ||
{Type: "google_project_service"}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
for name, test := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
res := Validate(test.rs) | ||
if diff := cmp.Diff(res, test.expected); diff != "" { | ||
t.Errorf("Validate() mismatch (-want +got):\n%s", diff) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestIsValid(t *testing.T) { | ||
tests := map[string]struct { | ||
res ValidateResult | ||
expected bool | ||
}{ | ||
"no invalid resources": { | ||
res: ValidateResult{}, | ||
expected: true, | ||
}, | ||
"invalid created resources": { | ||
res: ValidateResult{ | ||
InvalidCreatedResources: []*ruleset.ResourceIdentifier{ | ||
{Type: "fake_created_resource"}, | ||
}, | ||
}, | ||
expected: false, | ||
}, | ||
"invalid destroyed resources": { | ||
res: ValidateResult{ | ||
InvalidDestroyedResources: []*ruleset.ResourceIdentifier{ | ||
{Type: "fake_destroy_resource"}, | ||
}, | ||
}, | ||
expected: false, | ||
}, | ||
"invalid updated resources": { | ||
res: ValidateResult{ | ||
InvalidUpdatedResources: []*ruleset.ResourceIdentifier{ | ||
{Type: "fake_update_resource"}, | ||
}, | ||
}, | ||
expected: false, | ||
}, | ||
"multiple invalid resources changes": { | ||
res: ValidateResult{ | ||
InvalidCreatedResources: []*ruleset.ResourceIdentifier{ | ||
{Type: "fake_created_resource"}, | ||
}, | ||
InvalidUpdatedResources: []*ruleset.ResourceIdentifier{ | ||
{Type: "fake_update_resource"}, | ||
}, | ||
}, | ||
expected: false, | ||
}, | ||
} | ||
|
||
for name, test := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
if test.res.IsValid() != test.expected { | ||
t.Errorf("Expected %t, got %t: %v", test.expected, test.res.IsValid(), test.res) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package validate | ||
|
||
import ( | ||
"os" | ||
"fmt" | ||
|
||
"github.com/drlau/akashi/internal/validate" | ||
"github.com/drlau/akashi/pkg/ruleset" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
func NewCmd() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "validate <path to ruleset>", | ||
Short: "Validte the ruleset", | ||
Long: "Validate the ruleset, exiting with code 0 if the ruleset is valid", | ||
|
||
// NOTE: We explicitly do not set Args with ExactArgs(1) since that | ||
// will not print the help message. | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
if len(args) != 1 { | ||
cmd.Help() | ||
os.Exit(1) | ||
} | ||
return nil | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
ruleset, err := ruleset.ParseRuleset(args[0]) | ||
if err != nil { | ||
return fmt.Errorf("Could not parse ruleset: %v", err) | ||
} | ||
if res := validate.Validate(ruleset); !res.IsValid() { | ||
return fmt.Errorf("%s", res.String()) | ||
} | ||
fmt.Println("Ruleset is valid!") | ||
return nil | ||
}, | ||
} | ||
return cmd | ||
} |
Oops, something went wrong.