This repository has been archived by the owner on Jan 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 327
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 #4815 from hashicorp/project-template-validators
RPC Validators for project templates
- Loading branch information
Showing
2 changed files
with
289 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package ptypes | ||
|
||
import ( | ||
validation "github.com/go-ozzo/ozzo-validation/v4" | ||
"github.com/imdario/mergo" | ||
"github.com/mitchellh/go-testing-interface" | ||
"github.com/stretchr/testify/require" | ||
"regexp" | ||
|
||
"github.com/hashicorp/waypoint/internal/pkg/validationext" | ||
pb "github.com/hashicorp/waypoint/pkg/server/gen" | ||
) | ||
|
||
const ( | ||
PROJECT_TEMPLATE_ID_LENGTH = 64 | ||
PROJECT_TEMPLATE_NAME_LENGTH = 64 | ||
PROJECT_TEMPLATE_TAG_LENGTH = 64 | ||
PROJECT_TEMPLATE_SUMMARY_LENGTH = 64 | ||
PROJECT_TEMPLATE_EXPANDED_SUMMARY_LENGTH = 512 | ||
PROJECT_TEMPLATE_README_LENGTH = 1024 ^ 2 | ||
PROJECT_TEMPLATE_WAYPOINT_HCL_LENGTH = 1024 ^ 2 | ||
|
||
TERRAFORM_NOCODE_MODULE_SOURCE_LENGTH = 256 | ||
TERRAFORM_NOCODE_MODULE_VERSION_LENGTH = 256 | ||
) | ||
|
||
func TestProjectTemplate(t testing.T, src *pb.ProjectTemplate) *pb.ProjectTemplate { | ||
t.Helper() | ||
|
||
if src == nil { | ||
src = &pb.ProjectTemplate{} | ||
} | ||
|
||
require.NoError(t, mergo.Merge(src, &pb.Project{ | ||
Name: "test", | ||
})) | ||
|
||
return src | ||
} | ||
|
||
func ValidateCreateProjectTemplateRequest(req *pb.CreateProjectTemplateRequest) error { | ||
return validationext.Error(validation.ValidateStruct(req, | ||
validation.Field(&req.ProjectTemplate, validation.Required), | ||
validationext.StructField(&req.ProjectTemplate, func() []*validation.FieldRules { | ||
return append( | ||
// Rules specific to creating a project template | ||
[]*validation.FieldRules{ | ||
validation.Field(&req.ProjectTemplate.Name, | ||
validation.Required, | ||
validation.Match(regexp.MustCompile(`\S+`)), // Disallow only whitespace | ||
), | ||
}, | ||
|
||
// General project template validation rules | ||
validateProjectTemplateRules(req.ProjectTemplate)..., | ||
) | ||
}), | ||
)) | ||
} | ||
|
||
func ValidateUpdateProjectTemplateRequest(req *pb.UpdateProjectTemplateRequest) error { | ||
return validationext.Error(validation.ValidateStruct(req, | ||
validation.Field(&req.ProjectTemplate, validation.Required), | ||
validationext.StructField(&req.ProjectTemplate, func() []*validation.FieldRules { | ||
return append( | ||
// Rules specific to creating a project template | ||
[]*validation.FieldRules{ | ||
// Require either Name or ID | ||
validation.Field(&req.ProjectTemplate.Id, validation.Required.When(req.ProjectTemplate.Name == "").Error("Either Name or ID is required.")), | ||
validation.Field(&req.ProjectTemplate.Name, validation.Required.When(req.ProjectTemplate.Id == "").Error("Either Name or ID is required.")), | ||
}, | ||
|
||
// General project template validation rules | ||
validateProjectTemplateRules(req.ProjectTemplate)..., | ||
) | ||
}), | ||
)) | ||
} | ||
|
||
// validateProjectTemplateRules validates the rules that must be true of any project template in any | ||
// request or response. | ||
func validateProjectTemplateRules(pt *pb.ProjectTemplate) []*validation.FieldRules { | ||
return []*validation.FieldRules{ | ||
validation.Field(&pt.Id, validation.Length(0, PROJECT_TEMPLATE_ID_LENGTH)), | ||
validation.Field(&pt.Name, validation.Length(0, PROJECT_TEMPLATE_NAME_LENGTH)), | ||
|
||
validationext.StructField(&pt.TerraformNocodeModule, func() []*validation.FieldRules { | ||
return validateTerraformNocodeModule(pt.TerraformNocodeModule) | ||
}), | ||
|
||
validation.Field(&pt.Summary, validation.Length(0, PROJECT_TEMPLATE_SUMMARY_LENGTH)), | ||
validation.Field(&pt.ExpandedSummary, validation.Length(0, PROJECT_TEMPLATE_EXPANDED_SUMMARY_LENGTH)), | ||
validation.Field(&pt.ReadmeMarkdownTemplate, validation.Length(0, PROJECT_TEMPLATE_README_LENGTH)), | ||
|
||
validation.Field(&pt.Tags, validation.Each( | ||
validation.Length(1, PROJECT_TEMPLATE_TAG_LENGTH), | ||
)), | ||
|
||
validationext.StructField(&pt.WaypointProject, func() []*validation.FieldRules { | ||
return []*validation.FieldRules{ | ||
validation.Field( | ||
&pt.WaypointProject.WaypointHclTemplate, | ||
validation.Length(1, PROJECT_TEMPLATE_WAYPOINT_HCL_LENGTH), | ||
), | ||
} | ||
}), | ||
} | ||
} | ||
|
||
func validateTerraformNocodeModule(t *pb.ProjectTemplate_TerraformNocodeModule) []*validation.FieldRules { | ||
return []*validation.FieldRules{ | ||
validation.Field(&t.Source, | ||
validation.Required, | ||
validation.Length(1, TERRAFORM_NOCODE_MODULE_SOURCE_LENGTH), | ||
), | ||
validation.Field(&t.Version, | ||
validation.Required, | ||
validation.Length(1, TERRAFORM_NOCODE_MODULE_VERSION_LENGTH), | ||
), | ||
} | ||
} |
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,168 @@ | ||
package ptypes | ||
|
||
import ( | ||
validation "github.com/go-ozzo/ozzo-validation/v4" | ||
"testing" | ||
|
||
pb "github.com/hashicorp/waypoint/pkg/server/gen" | ||
) | ||
|
||
func TestValidateCreateProjectTemplateRequest(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
req *pb.CreateProjectTemplateRequest | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "minimum valid request", | ||
req: &pb.CreateProjectTemplateRequest{ | ||
ProjectTemplate: &pb.ProjectTemplate{ | ||
Name: "test", | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "enforces name", | ||
req: &pb.CreateProjectTemplateRequest{ | ||
ProjectTemplate: &pb.ProjectTemplate{}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Inherits base validator rules (example: name length)", | ||
req: &pb.CreateProjectTemplateRequest{ | ||
ProjectTemplate: &pb.ProjectTemplate{ | ||
Name: string(make([]byte, PROJECT_TEMPLATE_NAME_LENGTH+1)), | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "enforces name to not be empty", | ||
req: &pb.CreateProjectTemplateRequest{ | ||
ProjectTemplate: &pb.ProjectTemplate{ | ||
Name: " "}, | ||
}, | ||
wantErr: true, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := ValidateCreateProjectTemplateRequest(tt.req) | ||
if err == nil && tt.wantErr { | ||
t.Errorf("expected error in ValidateCreateProjectTemplateRequest() but got none") | ||
} | ||
|
||
if err != nil && !tt.wantErr { | ||
t.Errorf("ValidateCreateProjectTemplateRequest() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
|
||
}) | ||
} | ||
} | ||
|
||
func TestValidateUpdateProjectTemplateRequest(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
req *pb.UpdateProjectTemplateRequest | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "Fails if no name or ID is given", | ||
req: &pb.UpdateProjectTemplateRequest{ | ||
ProjectTemplate: &pb.ProjectTemplate{}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "OK with just name", | ||
req: &pb.UpdateProjectTemplateRequest{ | ||
ProjectTemplate: &pb.ProjectTemplate{ | ||
Name: "test", | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "OK with just ID", | ||
req: &pb.UpdateProjectTemplateRequest{ | ||
ProjectTemplate: &pb.ProjectTemplate{ | ||
Id: "test", | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Also runs base project template validator", | ||
req: &pb.UpdateProjectTemplateRequest{ | ||
ProjectTemplate: &pb.ProjectTemplate{ | ||
Id: string(make([]byte, PROJECT_TEMPLATE_ID_LENGTH+1)), | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := ValidateUpdateProjectTemplateRequest(tt.req) | ||
|
||
if err == nil && tt.wantErr { | ||
t.Errorf("expected error in ValidateUpdateProjectTemplateRequest() but got none") | ||
} | ||
|
||
if err != nil && !tt.wantErr { | ||
t.Errorf("ValidateUpdateProjectTemplateRequest() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestValidateProjectTemplate(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
pt *pb.ProjectTemplate | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "Fine with empty values", | ||
pt: &pb.ProjectTemplate{}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "If name is set, enforces length limits", | ||
pt: &pb.ProjectTemplate{ | ||
Name: string(make([]byte, PROJECT_TEMPLATE_NAME_LENGTH+1)), | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Validates nexted TFC-related lengths", | ||
pt: &pb.ProjectTemplate{ | ||
TerraformNocodeModule: &pb.ProjectTemplate_TerraformNocodeModule{ | ||
Source: "", // Empty string shouldn't be allowed | ||
Version: "0.0.1", | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Tag lengths", | ||
pt: &pb.ProjectTemplate{ | ||
Tags: []string{string(make([]byte, PROJECT_TEMPLATE_TAG_LENGTH+1))}, | ||
}, | ||
wantErr: true, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := validation.ValidateStruct(tt.pt, validateProjectTemplateRules(tt.pt)...) | ||
if err == nil && tt.wantErr { | ||
t.Errorf("expected error in validation but got none") | ||
} | ||
if err != nil && !tt.wantErr { | ||
t.Errorf("validation error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
}) | ||
} | ||
} |