rawlinp commented on a change in pull request #3989: Rewrote /deliveryservices/request to Go URL: https://github.com/apache/trafficcontrol/pull/3989#discussion_r337198277
########## File path: lib/go-tc/deliveryservice_requests.go ########## @@ -20,15 +20,394 @@ import ( "encoding/json" "errors" "fmt" + "html/template" "strconv" "strings" - log "github.com/apache/trafficcontrol/lib/go-log" + "github.com/apache/trafficcontrol/lib/go-log" + "github.com/apache/trafficcontrol/lib/go-util" + + "github.com/go-ozzo/ozzo-validation" + "github.com/go-ozzo/ozzo-validation/is" ) +// EmailTemplate is an html/template.Template for formatting DeliveryServiceRequestRequests into +// text/html email bodies. Its direct use is discouraged, instead use +// DeliveryServiceRequestRequest.Format. +var EmailTemplate = template.Must(template.New("Email Template").Parse(`<!DOCTYPE html> +<html lang="en-US"> +<head> +<meta charset="utf-8"/> +<title>Delivery Service Request for {{.Customer}}</title> +<style> +aside { + padding: 0 1em; + color: #6A737D; + border-left: .25em solid #DFE2E5; +} +body { + font-family: sans; + background-color: white; +} +pre { + padding: 5px; + background-color: lightgray; +} +</style> +</head> +<body> +<h1>Delivery Service Request for {{.Customer}}</h1> +<p>{{.ServiceDesc}}</p> +<section> + <details> + <summary><h2>Service Description</h2></summary> + <h3>Content Type</h3> + <p>{{.ContentType}}</p> + <h3>Delivery Protocol</h3> + <p>{{.DeliveryProtocol.String}}</p> + <h3>Routing Type</h3> + <p>{{.RoutingType.String}}</p> + </details> +</section> +<section> + <details> + <summary><h2>Traffic & Library Estimates</h2></summary> + <h3>Peak Bandwidth Estimate</h3> + <p>{{.PeakBPSEstimate}}Bps</p> + <h3>Peak Transactions per Second Estimate</h3> + <p>{{.PeakTPSEstimate}}Tps</p> + <h3>Max Library Size Estimate</h3> + <p>{{.MaxLibrarySizeEstimate}}GB</p> + </details> +</section> +<section> + <details> + <summary><h2>Origin Security</h2></summary> + <h3>Origin Server URL</h3> + <p><a href="{{.OriginURL}}">{{.OriginURL}}</a></p> + <h3>Origin Dynamic Remap</h3> + <p>{{.HasOriginDynamicRemap}}</p> + <h3>Origin Test File</h3> + <p>{{.OriginTestFile}}</p> + <h3>ACL/Whitelist to Access Origin</h3> + <p>{{.HasOriginACLWhitelist}}</p> + {{if .OriginHeaders}}<h3>Header(s) to Access Origin</h3> + <ul>{{range .OriginHeaders}} + <li>{{.}}</li>{{end}} + </ul>{{end}} + <h3>Other Origin Security</h3> + <p>{{if .OtherOriginSecurity}}{{.OtherOriginSecurity}}{{else}}None{{end}}</p> + </details> +</section> +<section> + <details> + <summary><h2>Core Features</h2></summary> + <h3>Query String Handling</h3> + <p>{{.QueryStringHandling}}</p> + <h3>Range Request Handling</h3> + <p>{{.RangeRequestHandling}}</p> + <h3>Signed URLs / URL Tokenization</h3> + <p>{{.HasSignedURLs}}</p> + <h3>Negative Caching Customization</h3> + <p>{{.HasNegativeCachingCustomization}}</p> + {{if or .HasNegativeCachingCustomization .NegativeCachingCustomizationNote }}<aside> + <p>{{.NegativeCachingCustomizationNote}}</p> + </aside>{{else if .HasNegativeCachingCustomization}}<aside> + <p><b>No instructions given!</b></p> + </aside>{{end}} + {{if .ServiceAliases}}<h3>Service Aliases</h3> + <ul>{{range .ServiceAliases}} + <li>{{.}}</li>{{end}} + </ul>{{end}} + </details> +</section> +{{if or .RateLimitingGBPS .RateLimitingTPS .OverflowService}}<section> + <details> + <summary><h2>Service Limits</h2></summary> + {{if .RateLimitingGBPS}}<h3>Bandwidth Limit</h3> + <p>{{.RateLimitingGBPS}}GBps</p>{{end}} + {{if .RateLimitingTPS}}<h3>Transactions per Second Limit</h3> + <p>{{.RateLimitingTPS}}Tps</p>{{end}} + {{if .OverflowService}}<h3>Overflow Service</h3> + <p>{{.OverflowService}}</p>{{end}} + </details> +</section>{{end}} +{{if or .HeaderRewriteEdge .HeaderRewriteMid .HeaderRewriteRedirectRouter}}<section> + <details> + <summary><h2>Header Customization</h2></summary> + {{if .HeaderRewriteEdge}}<h3>Header Rewrite - Edge Tier</h3> + <pre>{{.HeaderRewriteEdge}}</pre>{{end}} + {{if .HeaderRewriteMid}}<h3>Header Rewrite - Mid Tier</h3> + <pre>{{.HeaderRewriteMid}}</pre>{{end}} + {{if .HeaderRewriteRedirectRouter}}<h3>Header Rewrite - Router</h3> + <pre>{{.HeaderRewriteRedirectRouter}}</pre>{{end}} + </details> +</section>{{end}} +{{if .Notes}}<section> + <details> + <summary><h2>Additional Notes</h2></summary> + <p>{{.Notes}}</p> + </details> +</section>{{end}} +</body> +</html> +`)) + // IDNoMod type is used to suppress JSON unmarshalling type IDNoMod int +// DeliveryServiceRequestRequest is a literal request to make a Delivery Service. +type DeliveryServiceRequestRequest struct { + // EmailTo is the email address that is ultimately the destination of a formatted DeliveryServiceRequestRequest. + EmailTo string `json:"emailTo"` + // Details holds the actual request in a data structure. + Details DeliveryServiceRequestDetails `json:"details"` +} + +// DeliveryServiceRequestDetails +type DeliveryServiceRequestDetails struct { + // ContentType is the type of content to be delivered, e.g. "static", "VOD" etc. + ContentType string `json:"contentType"` + // Customer is the requesting customer - typically this is a Tenant. + Customer string `json:"customer"` + // DeepCachingType represents whether or not the Delivery Service should use Deep Caching. + DeepCachingType *DeepCachingType `json:"deepCachingType"` + // Delivery Protocol is the protocol clients should use to connect to the Delivery Service. + DeliveryProtocol *Protocol `json:"deliveryProtocol"` + // HasNegativeCachingCustomization indicates whether or not the resulting Delivery Service should + // customize the use of negative caching. When this is `true`, NegativeCachingCustomizationNote + // should be consulted for instructions on the customization. + HasNegativeCachingCustomization *bool `json:"hasNegativeCachingCustomization"` + // HasOriginACLWhitelist indicates whether or not the Origin has an ACL whitelist. When this is + // `true`, Notes should ideally contain the actual whitelist (or viewing instructions). + HasOriginACLWhitelist *bool `json:"hasOriginACLWhitelist"` + // Has OriginDynamicRemap indicates whether or not the OriginURL can dynamically map to multiple + // different actual origin servers. + HasOriginDynamicRemap *bool `json:"hasOriginDynamicRemap"` + // HasSignedURLs indicates whether or not the resulting Delivery Service should sign its URLs. + HasSignedURLs *bool `json:"hasSignedURLs"` + // HeaderRewriteEdge is an optional HeaderRewrite rule to apply at the Edge tier. + HeaderRewriteEdge *string `json:"headerRewriteEdge"` + // HeaderRewriteMid is an optional HeaderRewrite rule to apply at the Mid tier. + HeaderRewriteMid *string `json:"headerRewriteMid"` + // HeaderRewriteRedirectRouter is an optional HeaderRewrite rule to apply at routing time by + // the Traffic Router. + HeaderRewriteRedirectRouter *string `json:"headerRewriteRedirectRouter"` + // MaxLibrarySizeEstimate is an estimation of the total size of content that will be delivered + // through the resulting Delivery Service. + MaxLibrarySizeEstimate string `json:"maxLibrarySizeEstimate"` + // NegativeCachingCustomizationNote is an optional note describing the customization to be + // applied to Negative Caching. This should never be `nil` (or empty) if + // HasNegativeCachingCustomization is `true`, but in that case the recipient ought to contact + // Customer for instructions. + NegativeCachingCustomizationNote *string `json:"negativeCachingCustomizationNote"` + // Notes is an optional set of extra information supplied to describe the requested Delivery + // Service. + Notes *string `json:"notes"` + // OriginHeaders is an optional list of HTTP headers that must be sent in requests to the Origin. When + // parsing from JSON, this field can be either an actual array of headers, or a string containing + // a comma-delimited list of said headers. + OriginHeaders *OriginHeaders `json:"originHeaders"` + // OriginTestFile is the path to a file on the origin that can be requested to test the server's + // operational readiness, e.g. '/test.xml'. + OriginTestFile string `json:"originTestFile"` + // OriginURL is the URL of the origin server that has the content to be served by the requested + // Delivery Service. + OriginURL string `json:"originURL"` + // OtherOriginSecurity is an optional note about any and all other Security employed by the origin + // server (beyond an ACL whitelist, which has its own field: HasOriginACLWhitelist). + OtherOriginSecurity *string `json:"otherOriginSecurity"` + // OverflowService is an optional IP Address or URL to which clients should be redirected when + // the requested Delivery Service exceeds its operational capacity. + OverflowService *string `json:"overflowService"` + // PeakBPSEstimate is an estimate of the bytes per second expected at peak operation. + PeakBPSEstimate string `json:"peakBPSEstimate"` + // PeakTPSEstimate is an estimate of the transactions per second expected at peak operation. + PeakTPSEstimate string `json:"peakTPSEstimate"` + // QueryStringHandling describes the manner in which the CDN should handle query strings in client + // requests. Generally one of "use", "drop", or "ignore-in-cache-key-and-pass-up". + QueryStringHandling string `json:"queryStringHandling"` + // RangeRequestHandling describes the manner in which HTTP requests are handled. + RangeRequestHandling string `json:"rangeRequestHandling"` + // RateLimitingGBPS is an optional rate limit for the requested Delivery Service in gigabytes per + // second. + RateLimitingGBPS *uint `json:"rateLimitingGBPS"` + // RateLimitingTPS is an optional rate limit for the requested Delivery Service in transactions + // per second. + RateLimitingTPS *uint `json:"rateLimitingTPS"` + // RoutingName is the top-level DNS label under which the Delivery Service should be requested. + RoutingName string `json:"routingName"` + // RoutingType is the type of routing Traffic Router should perform for the requested Delivery + // Service. + RoutingType *DSType `json:"routingType"` + // ServiceAliases is an optional list of alternative names for the requested Delivery Service. + ServiceAliases []string `json:"serviceAliases"` + // ServiceDesc is a basic description of the requested Delivery Service. + ServiceDesc string `json:"serviceDesc"` +} + +// Format formats the DeliveryServiceRequestDetails into the text/html body of an email. The template +// used is EmailTemplate. +func (d DeliveryServiceRequestDetails) Format() (string, error) { + b := &strings.Builder{} + + if err := EmailTemplate.Execute(b, d); err != nil { + return "", fmt.Errorf("Failed to apply template: %v", err) + } + return b.String(), nil +} + +// Validate validates that the delivery service request has all of the required fields. In some cases, +// e.g. the top-level EmailTo field, the format is also checked for correctness. +func (d *DeliveryServiceRequestRequest) Validate() error { + errs := make([]error, 0, 2) + + err := validation.ValidateStruct(d, + validation.Field(&d.EmailTo, validation.Required, is.Email), + ) + if err != nil { + errs = append(errs, err) + } + + details := d.Details + err = validation.ValidateStruct(&details, + validation.Field(&details.ContentType, validation.Required), + validation.Field(&details.Customer, validation.Required), + validation.Field(&details.DeepCachingType, validation.By( + func(t interface{}) error { + if t == nil { + return errors.New("deepCachingType: required") + } + if *t.(*DeepCachingType) == DeepCachingTypeInvalid { + return errors.New("deepCachingType: invalid Deep Caching Type") + } + return nil + })), + validation.Field(&details.DeliveryProtocol, validation.By( + func(p interface{}) error { + if p == nil { + return errors.New("deliveryProtocol: required") + } + if *p.(*Protocol) == ProtocolInvalid { + return errors.New("deliveryProtocol: invalid Protocol") + } + return nil + })), + validation.Field(&details.HasNegativeCachingCustomization, validation.By( + func (h interface{}) error { + if h == nil { + return errors.New("hasNegativeCachingCustomization: required") + } + return nil + })), + validation.Field(&details.HasOriginACLWhitelist, validation.By( + func (h interface{}) error { + if h == nil { + return errors.New("hasNegativeCachingCustomization: required") + } + return nil + })), + validation.Field(&details.HasOriginDynamicRemap, validation.By( + func (h interface{}) error { + if h == nil { + return errors.New("hasNegativeCachingCustomization: required") + } + return nil + })), + validation.Field(&details.HasSignedURLs, validation.By( + func (h interface{}) error { + if h == nil { + return errors.New("hasNegativeCachingCustomization: required") + } + return nil + })), + validation.Field(&details.MaxLibrarySizeEstimate, validation.Required), + validation.Field(&details.OriginHeaders, validation.By( + func (h interface{}) error { + if h == nil { + return nil + } + if len(*h.(*OriginHeaders)) < 1 { + return errors.New("originHeaders: cannot be an empty list (use 'null' if none)") + } + return nil + })), + validation.Field(&details.OriginTestFile, validation.Required), + validation.Field(&details.OriginURL, validation.Required, is.URL), + validation.Field(&details.PeakBPSEstimate, validation.Required), + validation.Field(&details.PeakTPSEstimate, validation.Required), + validation.Field(&details.QueryStringHandling, validation.Required), + validation.Field(&details.RangeRequestHandling, validation.Required), + validation.Field(&details.RoutingName, validation.Required), + validation.Field(&details.RoutingType, validation.By( + func (t interface{}) error { + if t == nil || *(t.(*DSType)) == "" { + return errors.New("routingType: required") + } + *t.(*DSType) = DSTypeFromString(string(*t.(*DSType))) + switch *(t.(*DSType)) { Review comment: Instead of this switch statement can we simply check if the result from `DSTypeFromString` is `DSTypeInvalid` or not? That function basically already contains this switch statement. ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services