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 }