github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/testgrid/cmd/configurator/yaml2proto.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  
    24  	"github.com/golang/protobuf/proto"
    25  	"k8s.io/test-infra/testgrid/config"
    26  	"sigs.k8s.io/yaml"
    27  )
    28  
    29  // Config includes config and defaults to apply on unspecified values.
    30  type Config struct {
    31  	config        *config.Configuration
    32  	defaultConfig *config.DefaultConfiguration
    33  }
    34  
    35  // MissingFieldError is an error that includes the missing field.
    36  type MissingFieldError struct {
    37  	Field string
    38  }
    39  
    40  func (e MissingFieldError) Error() string {
    41  	return fmt.Sprintf("field missing or unset: %s", e.Field)
    42  }
    43  
    44  // ReconcileTestGroup sets unfilled currentTestGroup fields to the corresponding defaultTestGroup value.
    45  func ReconcileTestGroup(currentTestGroup *config.TestGroup, defaultTestGroup *config.TestGroup) {
    46  	if currentTestGroup.DaysOfResults == 0 {
    47  		currentTestGroup.DaysOfResults = defaultTestGroup.DaysOfResults
    48  	}
    49  
    50  	if currentTestGroup.TestsNamePolicy == config.TestGroup_TESTS_NAME_MIN {
    51  		currentTestGroup.TestsNamePolicy = defaultTestGroup.TestsNamePolicy
    52  	}
    53  
    54  	if currentTestGroup.IgnorePending == false {
    55  		currentTestGroup.IgnorePending = defaultTestGroup.IgnorePending
    56  	}
    57  
    58  	if currentTestGroup.ColumnHeader == nil {
    59  		currentTestGroup.ColumnHeader = defaultTestGroup.ColumnHeader
    60  	}
    61  
    62  	if currentTestGroup.NumColumnsRecent == 0 {
    63  		currentTestGroup.NumColumnsRecent = defaultTestGroup.NumColumnsRecent
    64  	}
    65  
    66  	if currentTestGroup.AlertStaleResultsHours == 0 {
    67  		currentTestGroup.AlertStaleResultsHours = defaultTestGroup.AlertStaleResultsHours
    68  	}
    69  
    70  	if currentTestGroup.NumFailuresToAlert == 0 {
    71  		currentTestGroup.NumFailuresToAlert = defaultTestGroup.NumFailuresToAlert
    72  	}
    73  	if currentTestGroup.CodeSearchPath == "" {
    74  		currentTestGroup.CodeSearchPath = defaultTestGroup.CodeSearchPath
    75  	}
    76  	if currentTestGroup.NumPassesToDisableAlert == 0 {
    77  		currentTestGroup.NumPassesToDisableAlert = defaultTestGroup.NumPassesToDisableAlert
    78  	}
    79  	// is_external and user_kubernetes_client should always be true
    80  	currentTestGroup.IsExternal = true
    81  	currentTestGroup.UseKubernetesClient = true
    82  }
    83  
    84  // ReconcileDashboardTab sets unfilled currentTab fields to the corresponding defaultTab value.
    85  func ReconcileDashboardTab(currentTab *config.DashboardTab, defaultTab *config.DashboardTab) {
    86  	if currentTab.BugComponent == 0 {
    87  		currentTab.BugComponent = defaultTab.BugComponent
    88  	}
    89  
    90  	if currentTab.CodeSearchPath == "" {
    91  		currentTab.CodeSearchPath = defaultTab.CodeSearchPath
    92  	}
    93  
    94  	if currentTab.NumColumnsRecent == 0 {
    95  		currentTab.NumColumnsRecent = defaultTab.NumColumnsRecent
    96  	}
    97  
    98  	if currentTab.OpenTestTemplate == nil {
    99  		currentTab.OpenTestTemplate = defaultTab.OpenTestTemplate
   100  	}
   101  
   102  	if currentTab.FileBugTemplate == nil {
   103  		currentTab.FileBugTemplate = defaultTab.FileBugTemplate
   104  	}
   105  
   106  	if currentTab.AttachBugTemplate == nil {
   107  		currentTab.AttachBugTemplate = defaultTab.AttachBugTemplate
   108  	}
   109  
   110  	if currentTab.ResultsText == "" {
   111  		currentTab.ResultsText = defaultTab.ResultsText
   112  	}
   113  
   114  	if currentTab.ResultsUrlTemplate == nil {
   115  		currentTab.ResultsUrlTemplate = defaultTab.ResultsUrlTemplate
   116  	}
   117  
   118  	if currentTab.CodeSearchUrlTemplate == nil {
   119  		currentTab.CodeSearchUrlTemplate = defaultTab.CodeSearchUrlTemplate
   120  	}
   121  
   122  	if currentTab.AlertOptions == nil {
   123  		currentTab.AlertOptions = defaultTab.AlertOptions
   124  	}
   125  }
   126  
   127  // updateDefaults reads any default configuration from yamlData and updates the
   128  // defaultConfig in c.
   129  //
   130  // Returns an error if the defaultConfig remains unset.
   131  func (c *Config) updateDefaults(yamlData []byte) error {
   132  	newDefaults := &config.DefaultConfiguration{}
   133  	err := yaml.Unmarshal(yamlData, newDefaults)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	if c.defaultConfig == nil {
   139  		c.defaultConfig = newDefaults
   140  	} else {
   141  		if newDefaults.DefaultTestGroup != nil {
   142  			c.defaultConfig.DefaultTestGroup = newDefaults.DefaultTestGroup
   143  		}
   144  		if newDefaults.DefaultDashboardTab != nil {
   145  			c.defaultConfig.DefaultDashboardTab = newDefaults.DefaultDashboardTab
   146  		}
   147  	}
   148  
   149  	if c.defaultConfig.DefaultTestGroup == nil {
   150  		return MissingFieldError{"DefaultTestGroup"}
   151  	}
   152  	if c.defaultConfig.DefaultDashboardTab == nil {
   153  		return MissingFieldError{"DefaultDashboardTab"}
   154  	}
   155  	return nil
   156  }
   157  
   158  // Update reads the config in yamlData and updates the config in c.
   159  // If yamlData does not contain any defaults, the defaults from a
   160  // previous call to Update are used instead.
   161  func (c *Config) Update(yamlData []byte) error {
   162  	if err := c.updateDefaults(yamlData); err != nil {
   163  		return err
   164  	}
   165  
   166  	curConfig := &config.Configuration{}
   167  	if err := yaml.Unmarshal(yamlData, curConfig); err != nil {
   168  		return err
   169  	}
   170  
   171  	if c.config == nil {
   172  		c.config = &config.Configuration{}
   173  	}
   174  
   175  	for _, testgroup := range curConfig.TestGroups {
   176  		ReconcileTestGroup(testgroup, c.defaultConfig.DefaultTestGroup)
   177  		c.config.TestGroups = append(c.config.TestGroups, testgroup)
   178  	}
   179  
   180  	for _, dashboard := range curConfig.Dashboards {
   181  		// validate dashboard tabs
   182  		for _, dashboardtab := range dashboard.DashboardTab {
   183  			ReconcileDashboardTab(dashboardtab, c.defaultConfig.DefaultDashboardTab)
   184  		}
   185  		c.config.Dashboards = append(c.config.Dashboards, dashboard)
   186  	}
   187  
   188  	for _, dashboardGroup := range curConfig.DashboardGroups {
   189  		c.config.DashboardGroups = append(c.config.DashboardGroups, dashboardGroup)
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  // validate checks that a configuration is well-formed, having test groups and dashboards set.
   196  func (c *Config) validate() error {
   197  	if c.config == nil {
   198  		return errors.New("Configuration unset")
   199  	}
   200  	if len(c.config.TestGroups) == 0 {
   201  		return MissingFieldError{"TestGroups"}
   202  	}
   203  	if len(c.config.Dashboards) == 0 {
   204  		return MissingFieldError{"Dashboards"}
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  // MarshalText writes a text version of the parsed configuration to the supplied io.Writer.
   211  // Returns an error if config is invalid or writing failed.
   212  func (c *Config) MarshalText(w io.Writer) error {
   213  	if err := c.validate(); err != nil {
   214  		return err
   215  	}
   216  	return proto.MarshalText(w, c.config)
   217  }
   218  
   219  // MarshalBytes returns the wire-encoded protobuf data for the parsed configuration.
   220  // Returns an error if config is invalid or encoding failed.
   221  func (c *Config) MarshalBytes() ([]byte, error) {
   222  	if err := c.validate(); err != nil {
   223  		return nil, err
   224  	}
   225  	return proto.Marshal(c.config)
   226  }
   227  
   228  // Raw returns the raw protocol buffer for the parsed configuration after validation.
   229  // Returns an error if validation fails.
   230  func (c *Config) Raw() (*config.Configuration, error) {
   231  	if err := c.validate(); err != nil {
   232  		return nil, err
   233  	}
   234  	return c.config, nil
   235  }