ocket8888 commented on a change in pull request #3744: Rewrote jobs endpoints 
to go
URL: https://github.com/apache/trafficcontrol/pull/3744#discussion_r329754646
 
 

 ##########
 File path: lib/go-tc/invalidationjobs.go
 ##########
 @@ -0,0 +1,361 @@
+package tc
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import "errors"
+import "fmt"
+import "regexp"
+import "database/sql"
+import "math"
+import "strconv"
+import "strings"
+import "time"
+
+import "github.com/apache/trafficcontrol/lib/go-log"
+
+// This is the maximum value of TTL representable as a time.Duration object, 
which is used
+// internally by InvalidationJobInput objects to store the TTL.
+const MaxTTL = math.MaxInt64 / 3600000000000
+
+// Represents a content invalidation job as returned by the API.
+type InvalidationJob struct {
+       AssetURL        *string `json:"assetUrl"`
+       CreatedBy       *string `json:"createdBy"`
+       DeliveryService *string `json:"deliveryService"`
+       ID              *uint64 `json:"id"`
+       Keyword         *string `json:"keyword"`
+       Parameters      *string `json:"parameters"`
+
+       // The time at which the job will come into effect. Must be in the 
future, but will fail to
+       // Validate if it is further in the future than two days.
+       StartTime *Time `json:"startTime"`
+}
+
+// Represents user input intending to create or modify a content invalidation 
job.
+type InvalidationJobInput struct {
+
+       // This needs to be an identifier for a Delivery Service. It can be 
either a string - in which
+       // case it is treated as an XML_ID - or a float64 (because that's the 
type used by encoding/json
+       // to represent all JSON numbers) - in which case it's treated as an 
integral, unique identifier
+       // (and any fractional part is discarded, i.e. 2.34 -> 2)
+       DeliveryService *interface{} `json:"deliveryService"`
+
+       // A regular expression which not only must be valid, but should also 
start with '/'
+       // (or escaped: '\/')
+       Regex *string `json:"regex"`
+
+       // The time at which the job will come into effect. Must be in the 
future.
+       StartTime *Time `json:"startTime"`
+
+       // Indicates the Time-to-Live of the job. This can be either a valid 
string for
+       // time.ParseDuration, or a float64 indicating the number of hours. 
Note that regardless of the
+       // actual value here, Traffic Ops will only consider it rounded down to 
the nearest natural
+       // number
+       TTL *interface{} `json:"ttl"`
+
+       dsid *uint          `json:"-"`
+       ttl  *time.Duration `json:"-"`
+}
+
+// Represents legacy-style user input to the /user/current/jobs API endpoint. 
This is much less
+// flexible than InvalidationJobInput, which should be used instead when 
possible.
+type UserInvalidationJobInput struct {
+       DSID  *uint   `json:"dsId"`
+       Regex *string `json:"regex"`
+
+       // The time at which the job will come into effect. Must be in the 
future, but will fail to
+       // Validate if it is further in the future than two days.
+       StartTime *Time   `json:"startTime"`
+       TTL       *uint64 `json:"ttl"`
+       Urgent    *bool   `json:"urgent"`
+}
+
+// This is a full representation of content invalidation jobs as stored in the 
database, including
+// several unused fields.
+type UserInvalidationJob struct {
+
+       // Unused
+       Agent    *uint   `json:"agent"`
+       AssetURL *string `json:"assetUrl"`
+
+       // Unused
+       AssetType       *string `json:"assetType"`
+       DeliveryService *string `json:"deliveryService"`
+       EnteredTime     *Time   `json:"enteredTime"`
+       ID              *uint   `json:"id"`
+       Keyword         *string `json:"keyword"`
+
+       // Unused
+       ObjectName *string `json:"objectName"`
+
+       // Unused
+       ObjectType *string `json:"objectType"`
+       Parameters *string `json:"parameters"`
+       Username   *string `json:"username"`
+}
+
+// Gets the number of hours of the job's TTL - rounded down to the nearest 
natural number, or an
+// error if it is an invalid value.
+func (j *InvalidationJobInput) TTLHours() (uint, error) {
+       if j.ttl != nil {
+               return uint((*j.ttl).Hours()), nil
+       }
+       if j.TTL == nil {
+               return 0, errors.New("Attempted to convert a nil TTL into 
hours")
+       }
+
+       var ret uint
+       switch t := (*j.TTL).(type) {
+       case float64:
+               v := (*j.TTL).(float64)
+               if v < 0 {
+                       return 0, errors.New("TTL cannot be negative!")
+               }
+               if v >= MaxTTL {
+                       return 0, fmt.Errorf("TTL cannot exceed %d hours!", 
MaxTTL)
+               }
+               ttl := time.Duration(int64(v * 3600000000000))
+               j.ttl = &ttl
+               ret = uint(ttl.Hours())
+
+       case string:
+               d, err := time.ParseDuration((*j.TTL).(string))
+               if err != nil || d.Hours() < 1 {
+                       return 0, fmt.Errorf("Invalid duration entered for TTL! 
Must be at least one hour, but no more than %d hours!", MaxTTL)
+               }
+               j.ttl = &d
+               ret = uint(d.Hours())
+
+       default:
+               log.Errorf("unsupported TTL key type: %T\n", t)
+               return 0, errors.New("Unknown error occurred")
+       }
+
+       return ret, nil
+}
+
+// Gets the integral, unique identifier of the Delivery Service identified by
+// InvalidationJobInput.DeliveryService
+//
+// This requires a transaction connected to a Traffic Ops database, because if 
DeliveryService is
+// an xml_id, a database lookup will be necessary to get the unique, integral 
identifier. Thus,
+// this method also checks for the existence of the identified Delivery 
Service, and will return
+// an error if it does not exist.
+func (j *InvalidationJobInput) DSID(tx *sql.Tx) (uint, error) {
+       if j.dsid != nil {
+               return *j.dsid, nil
+       }
+
+       if j.DeliveryService == nil {
+               return 0, errors.New("Attempted to turn a nil DeliveryService 
into a DSID")
+       }
+       if tx == nil {
+               return 0, errors.New("Attempted to turn a DeliveryService into 
a DSID with no DB connection")
+       }
+
+       var ret uint
+       switch t := (*j.DeliveryService).(type) {
+       case float64:
+               v := (*j.DeliveryService).(float64)
+               if v < 0 {
+                       return 0, errors.New("Delivery Service ID cannot be 
negative")
+               }
+
+               u := uint(v)
+               var exists bool
+               row := tx.QueryRow(`SELECT EXISTS(SELECT * FROM deliveryservice 
WHERE id=$1)`, u)
+               if err := row.Scan(&exists); err != nil {
+                       log.Errorf("Error checking for deliveryservice 
existence in DSID: %v\n", err)
+                       return 0, errors.New("Unknown error occurred")
+               } else if !exists {
+                       return 0, fmt.Errorf("No Delivery Service exists 
matching identifier: %v", *j.DeliveryService)
+               }
+
+               j.dsid = &u
+               return u, nil
+
+       case string:
+               row := tx.QueryRow(`SELECT id FROM deliveryservice WHERE 
xml_id=$1`, *j.DeliveryService)
+               if err := row.Scan(&ret); err != nil {
+                       if err == sql.ErrNoRows {
+                               return 0, fmt.Errorf("No DeliveryService exists 
matching identifier: %v", *j.DeliveryService)
+                       }
+                       return 0, errors.New("Unknown error occurred")
+               }
+               j.dsid = &ret
+               return ret, nil
+
+       default:
+               log.Errorf("unsupported DS key type: %T\n", t)
+               return 0, errors.New("Unknown error occurred")
+
+       }
+}
+
+// Given a transaction connected to the Traffic Ops database, this validates 
that the user input
+// is correct. In particular, it enforces the constraints described on each 
field, as well as
+// ensuring they actually exist. This method calls InvalidationJobInput.DSID 
to validate the
+// DeliveryService field.
+//
+// Returns an array of strings that describe what, if any, problematic fields 
were encountered in
+// validation.
+func (job *InvalidationJobInput) Validate(tx *sql.Tx) []string {
+       errs := []string{}
+       if job.DeliveryService == nil {
+               errs = append(errs, "'deliveryService' is a required field!")
+       } else if _, err := job.DSID(tx); err != nil {
+               errs = append(errs, err.Error())
+       }
+
+       if job.Regex == nil || *job.Regex == "" {
+               errs = append(errs, "'regex' is a required field - and cannot 
be empty!")
+       } else {
+               if !strings.HasPrefix(*job.Regex, "\\/") && 
!strings.HasPrefix(*job.Regex, "/") {
+                       errs = append(errs, "'regex' must start with '/' (or an 
escaped '/')!")
+               }
+               if _, err := regexp.Compile(*job.Regex); err != nil {
+                       errs = append(errs, "'regex' is not a valid Regular 
Expression: "+err.Error())
+               }
+       }
+
+       if job.StartTime == nil {
+               errs = append(errs, "'startTime' is a required field!")
+       } else if job.StartTime.Time.Before(time.Now()) {
+               errs = append(errs, "'startTime' must be in the future!")
+       }
+
+       if job.TTL == nil {
+               errs = append(errs, "'ttl' is a required field!")
+       } else if _, err := job.TTLHours(); err != nil {
+               errs = append(errs, "'ttl' must be a number of hours, or a 
duration string e.g. '48h'!")
 
 Review comment:
   To some degree, yes. I've done what can be done with ozzo's library.

----------------------------------------------------------------
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

Reply via email to