lib/check/check.go (149 lines of code) (raw):

// Copyright 2019 Google LLC // // Licensed 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. package check import ( "fmt" "regexp" "time" "github.com/golang/protobuf/proto" /* copybara-comment */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/httputils" /* copybara-comment: httputils */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/optional" /* copybara-comment: optional */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/timeutil" /* copybara-comment: timeutil */ cpb "github.com/GoogleCloudPlatform/healthcare-federated-access-services/proto/common/v1" /* copybara-comment: go_proto */ ) // ValidToWriteConfig assumes the caller is trying to modify the realm's config and therefore returns an error if it is read only. func ValidToWriteConfig(realm string, readOnlyMaster bool) error { if realm == storage.DefaultRealm { if readOnlyMaster { return fmt.Errorf(`config option "readOnlyMasterRealm" setting prevents updating the config on realm %q`, realm) } } return nil } // CheckStringOption checks if the string option valid. func CheckStringOption(opt, optName string, descriptors map[string]*cpb.Descriptor) error { desc, ok := descriptors[optName] if !ok { return fmt.Errorf("internal error: option descriptor %q not defined", optName) } if len(opt) == 0 { return nil } if len(desc.Regexp) > 0 { re, err := regexp.Compile(desc.Regexp) if err != nil { return fmt.Errorf("internal error: option descriptor %q regexp %q does not compile", optName, desc.Regexp) } if !re.Match([]byte(opt)) { return fmt.Errorf("option %q: value %q does not match regular expression %q", optName, opt, desc.Regexp) } } if len(desc.EnumValues) > 0 { found := false for _, v := range desc.EnumValues { if v == opt { found = true break } } if !found { return fmt.Errorf("option %q: value %q is not a valid, must be one of: %v", optName, opt, desc.EnumValues) } } if len(desc.Min) > 0 || len(desc.Max) > 0 { if desc.Type == "string:duration" { val, min, max, err := OptDuration(optName, opt, desc.Min, desc.Max) if err != nil { return err } if (min.IsPresent() && val < min.Get()) || (max.IsPresent() && val > max.Get()) { return fmt.Errorf("option %q: value %q is not within range (duration range is %s to %s)", optName, opt, desc.Min, desc.Max) } } else { min, err := optional.NewIntFromString(desc.Min) if err != nil { return err } max, err := optional.NewIntFromString(desc.Max) if err != nil { return err } if (min.IsPresent() && len(opt) < min.Get()) || (max.IsPresent() && len(opt) > max.Get()) { return fmt.Errorf("option %q: value %q is too short or too long (range is %s to %s)", optName, opt, desc.Min, desc.Max) } } } return nil } func CheckStringListOption(values []string, optName string, descriptors map[string]*cpb.Descriptor) error { for _, v := range values { if err := CheckStringOption(v, optName, descriptors); err != nil { return err } } return nil } // CheckIntOption checks if the int option valid. func CheckIntOption(opt int32, optName string, descriptors map[string]*cpb.Descriptor) error { desc, ok := descriptors[optName] if !ok { return fmt.Errorf("internal error: option descriptor %q not defined", optName) } if opt == 0 { // Is default value and does not need to meet min/max requirements. return nil } min, err := optional.NewIntFromString(desc.Min) if err != nil { return err } max, err := optional.NewIntFromString(desc.Max) if err != nil { return err } optInt := int(opt) if (min.IsPresent() && optInt < min.Get()) || (max.IsPresent() && optInt > max.Get()) { return fmt.Errorf("option %q: value %d is out of range (%s to %s)", optName, opt, desc.Min, desc.Max) } return nil } // OptDuration parses opt value, min and max. func OptDuration(optName, optVal, minVal, maxVal string) (time.Duration, *optional.Duration, *optional.Duration, error) { v, err := timeutil.ParseDuration(optVal) if err != nil { return 0, nil, nil, fmt.Errorf("option %q: value %q format error: %v", optName, optVal, err) } min, err := optional.NewDurationFromString(minVal) if err != nil { return 0, nil, nil, fmt.Errorf("option %q: minimum %q format error: %v", optName, minVal, err) } max, err := optional.NewDurationFromString(maxVal) if err != nil { return 0, nil, nil, fmt.Errorf("option %q: maximum %q format error: %v", optName, maxVal, err) } return v, min, max, nil } // CheckUI checks UI object in config. func CheckUI(ui map[string]string, requireDescription bool) (string, error) { if ui == nil { return "ui", fmt.Errorf("UI object missing") } if label := ui["label"]; len(label) == 0 { return httputils.StatusPath("ui", "label"), fmt.Errorf("UI object missing %q field", "label") } if !requireDescription { return "", nil } if desc := ui["description"]; len(desc) == 0 { return httputils.StatusPath("ui", "description"), fmt.Errorf("UI object missing %q field", "description") } return "", nil } // ClientsEqual compares two maps of Client protos and returns true if equal. func ClientsEqual(a, b map[string]*cpb.Client) bool { if len(a) != len(b) { return false } for k, va := range a { vb, ok := b[k] if !ok { return false } if !proto.Equal(va, vb) { return false } } return true }