Skip to content

Commit

Permalink
api: Add custom "required_for_put" tag
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthew Barnes committed Apr 9, 2024
1 parent 309c606 commit 7076a8d
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 17 deletions.
2 changes: 2 additions & 0 deletions internal/api/arm/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ func WriteUnmarshalError(err error, w http.ResponseWriter) {
message += fmt.Sprintf(" (must be one of: %s)", fieldErr.Param())
} else {
switch tag {
case "required_for_put": // custom tag
message = fmt.Sprintf("Missing required field '%s'", fieldErr.Field())
case "cidrv4":
message += " (must be a v4 CIDR address)"
case "ipv4":
Expand Down
32 changes: 16 additions & 16 deletions internal/api/hcpopenshiftcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,52 +12,52 @@ import (
// HCPOpenShiftCluster represents an ARO HCP OpenShift cluster resource.
type HCPOpenShiftCluster struct {
arm.TrackedResource
Properties HCPOpenShiftClusterProperties `json:"properties,omitempty"`
Properties HCPOpenShiftClusterProperties `json:"properties,omitempty" validate:"required_for_put"`
}

// HCPOpenShiftClusterProperties represents the property bag of a HCPOpenShiftCluster resource.
type HCPOpenShiftClusterProperties struct {
ProvisioningState arm.ProvisioningState `json:"provisioningState,omitempty" visibility:"read" validate:"omitempty,enum_provisioningstate"`
Spec ClusterSpec `json:"spec,omitempty" visibility:"read,create,update"`
Spec ClusterSpec `json:"spec,omitempty" visibility:"read,create,update" validate:"required_for_put"`
}

// ClusterSpec represents a high level cluster configuration.
type ClusterSpec struct {
Version VersionProfile `json:"version,omitempty" visibility:"read,create,update"`
Version VersionProfile `json:"version,omitempty" visibility:"read,create,update" validate:"required_for_put"`
DNS DNSProfile `json:"dns,omitempty" visibility:"read,create,update"`
Network NetworkProfile `json:"network,omitempty" visibility:"read,create"`
Console ConsoleProfile `json:"console,omitempty" visibility:"read"`
API APIProfile `json:"api,omitempty" visibility:"read,create"`
API APIProfile `json:"api,omitempty" visibility:"read,create" validate:"required_for_put"`
FIPS bool `json:"fips,omitempty" visibility:"read,create"`
EtcdEncryption bool `json:"etcdEncryption,omitempty" visibility:"read,create"`
DisableUserWorkloadMonitoring bool `json:"disableUserWorkloadMonitoring,omitempty" visibility:"read,create,update"`
Proxy ProxyProfile `json:"proxy,omitempty" visibility:"read,create,update"`
Platform PlatformProfile `json:"platform,omitempty" visibility:"read,create"`
Platform PlatformProfile `json:"platform,omitempty" visibility:"read,create" validate:"required_for_put"`
IssuerURL string `json:"issuerUrl,omitempty" visibility:"read" validate:"omitempty,url"`
ExternalAuth ExternalAuthConfigProfile `json:"externalAuth,omitempty" visibility:"read,create"`
Ingress []*IngressProfile `json:"ingressProfile,omitempty" visibility:"read,create"`
}

// VersionProfile represents the cluster control plane version.
type VersionProfile struct {
ID string `json:"id,omitempty" visibility:"read,create,update"`
ChannelGroup string `json:"channelGroup,omitempty" visibility:"read,create"`
ID string `json:"id,omitempty" visibility:"read,create,update" validate:"required_for_put"`
ChannelGroup string `json:"channelGroup,omitempty" visibility:"read,create" validate:"required_for_put"`
AvailableUpgrades []string `json:"availableUpgrades,omitempty" visibility:"read"`
}

// DNSProfile represents the DNS configuration of the cluster.
type DNSProfile struct {
BaseDomain string `json:"baseDomain,omitempty" visibility:"read"`
BaseDomainPrefix string `json:"baseDomainPrefix,omitempty" visibility:"read,create"`
BaseDomainPrefix string `json:"baseDomainPrefix,omitempty" visibility:"read,create" validate:"required_for_put"`
}

// NetworkProfile represents a cluster network configuration.
// Visibility for the entire struct is "read,create".
type NetworkProfile struct {
NetworkType NetworkType `json:"networkType,omitempty"`
PodCIDR string `json:"podCidr,omitempty" validate:"omitempty,cidrv4"`
ServiceCIDR string `json:"serviceCidr,omitempty" validate:"omitempty,cidrv4"`
MachineCIDR string `json:"machineCidr,omitempty" validate:"omitempty,cidrv4"`
PodCIDR string `json:"podCidr,omitempty" validate:"required_for_put,cidrv4"`
ServiceCIDR string `json:"serviceCidr,omitempty" validate:"required_for_put,cidrv4"`
MachineCIDR string `json:"machineCidr,omitempty" validate:"required_for_put,cidrv4"`
HostPrefix int32 `json:"hostPrefix,omitempty"`
}

Expand All @@ -71,7 +71,7 @@ type ConsoleProfile struct {
type APIProfile struct {
URL string `json:"url,omitempty" visibility:"read" validate:"omitempty,url"`
IP string `json:"ip,omitempty" visibility:"read" validate:"omitempty,ipv4"`
Visibility Visibility `json:"visibility,omitempty" visibility:"read,create" validate:"omitempty,enum_visibility"`
Visibility Visibility `json:"visibility,omitempty" visibility:"read,create" validate:"required_for_put,enum_visibility"`
}

// ProxyProfile represents the cluster proxy configuration.
Expand All @@ -86,10 +86,10 @@ type ProxyProfile struct {
// PlatformProfile represents the Azure platform configuration.
// Visibility for the entire struct is "read,create".
type PlatformProfile struct {
ManagedResourceGroup string `json:"managedResourceGroup,omitempty"`
SubnetID string `json:"subnetId,omitempty"`
ManagedResourceGroup string `json:"managedResourceGroup,omitempty" validate:"required_for_put"`
SubnetID string `json:"subnetId,omitempty" validate:"required_for_put"`
OutboundType OutboundType `json:"outboundType,omitempty" validate:"omitempty,enum_outboundtype"`
PreconfiguredNSGs bool `json:"preconfiguredNsgs,omitempty"`
PreconfiguredNSGs bool `json:"preconfiguredNsgs,omitempty" validate:"required_for_put"`
EtcdEncryptionSetID string `json:"etcdEncryptionSetId,omitempty"`
}

Expand All @@ -103,7 +103,7 @@ type ExternalAuthConfigProfile struct {
type IngressProfile struct {
IP string `json:"ip,omitempty" visibility:"read" validate:"omitempty,ipv4"`
URL string `json:"url,omitempty" visibility:"read" validate:"omitempty,url"`
Visibility Visibility `json:"visibility,omitempty" visibility:"read,create" validate:"omitempty,enum_visibility"`
Visibility Visibility `json:"visibility,omitempty" visibility:"read,create" validate:"required_for_put,enum_visibility"`
}

// Creates an HCPOpenShiftCluster with any non-zero default values.
Expand Down
39 changes: 39 additions & 0 deletions internal/api/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package api

import (
"fmt"
"net/http"
"reflect"
"strings"

Expand Down Expand Up @@ -65,5 +66,43 @@ func NewValidator() *validator.Validate {
return name
})

// Use this for fields required in PUT requests. Do not apply to read-only fields.
validate.RegisterValidation("required_for_put", func(fl validator.FieldLevel) bool {
val := fl.Top().FieldByName("Method")
if val.IsZero() {
panic("Method field not found for required_for_put")
}
if val.String() != http.MethodPut {
return true
}

// This is replicating the implementation of "required".
// See https://github.com/go-playground/validator/issues/492
// Sounds like "hasValue" is unlikely to be exported and
// "validate.Var" does not seem like a safe alternative.
field := fl.Field()
_, kind, nullable := fl.ExtractType(field)
switch kind {
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
return !field.IsNil()
default:
if nullable && field.Interface() != nil {
return true
}
return field.IsValid() && !field.IsZero()
}
})

return validate
}

type validateContext struct {
// Fields must be exported so valdator can access.
Method string
Updating bool
Resource any
}

func ValidateRequest(validate *validator.Validate, method string, updating bool, resource any) error {
return validate.Struct(validateContext{Method: method, Updating: updating, Resource: resource})
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func (v version) UnmarshalHCPOpenShiftCluster(data []byte, out *api.HCPOpenShift

resource.Normalize(out)

return validate.Struct(out)
return api.ValidateRequest(validate, method, updating, out)
}

func (c *HcpOpenShiftClusterResource) Normalize(out *api.HCPOpenShiftCluster) {
Expand Down

0 comments on commit 7076a8d

Please sign in to comment.