Skip to content
This repository has been archived by the owner on Jan 8, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4815 from hashicorp/project-template-validators
Browse files Browse the repository at this point in the history
RPC Validators for project templates
  • Loading branch information
izaaklauer committed Aug 8, 2023
2 parents 7958a15 + 28c6913 commit 641c6b0
Show file tree
Hide file tree
Showing 2 changed files with 289 additions and 0 deletions.
121 changes: 121 additions & 0 deletions pkg/server/ptypes/project_template.go
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),
),
}
}
168 changes: 168 additions & 0 deletions pkg/server/ptypes/project_template_test.go
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)
}
})
}
}

0 comments on commit 641c6b0

Please sign in to comment.