yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/loadbalancerlistenerrule.go (about) 1 // Copyright 2019 Yunion 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package aws 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "strings" 22 23 "github.com/aws/aws-sdk-go/service/elbv2" 24 25 "yunion.io/x/jsonutils" 26 "yunion.io/x/log" 27 "yunion.io/x/pkg/errors" 28 29 api "yunion.io/x/cloudmux/pkg/apis/compute" 30 "yunion.io/x/cloudmux/pkg/cloudprovider" 31 "yunion.io/x/cloudmux/pkg/multicloud" 32 ) 33 34 type SElbListenerRule struct { 35 multicloud.SResourceBase 36 multicloud.SLoadbalancerRedirectBase 37 AwsTags 38 listener *SElbListener 39 region *SRegion 40 41 Priority string `json:"Priority"` 42 IsDefaultRule bool `json:"IsDefault"` 43 Actions []Action `json:"Actions"` 44 RuleArn string `json:"RuleArn"` 45 Conditions []Condition `json:"Conditions"` 46 } 47 48 type Action struct { 49 TargetGroupArn string `json:"TargetGroupArn"` 50 Type string `json:"Type"` 51 } 52 53 type Condition struct { 54 Field string `json:"field"` 55 HTTPRequestMethodConfig *Config `json:"httpRequestMethodConfig,omitempty"` 56 Values []string `json:"values"` 57 SourceIPConfig *Config `json:"sourceIpConfig,omitempty"` 58 QueryStringConfig *QueryStringConfig `json:"queryStringConfig,omitempty"` 59 HTTPHeaderConfig *HTTPHeaderConfig `json:"httpHeaderConfig,omitempty"` 60 PathPatternConfig *Config `json:"pathPatternConfig,omitempty"` 61 HostHeaderConfig *Config `json:"hostHeaderConfig,omitempty"` 62 } 63 64 type HTTPHeaderConfig struct { 65 HTTPHeaderName string `json:"HttpHeaderName"` 66 Values []string `json:"values"` 67 } 68 69 type Config struct { 70 Values []string `json:"values"` 71 } 72 73 type QueryStringConfig struct { 74 Values []Query `json:"values"` 75 } 76 77 type Query struct { 78 Key string `json:"key"` 79 Value string `json:"value"` 80 } 81 82 func (self *SElbListenerRule) GetId() string { 83 return self.RuleArn 84 } 85 86 func (self *SElbListenerRule) GetName() string { 87 segs := strings.Split(self.RuleArn, "/") 88 return segs[len(segs)-1] 89 } 90 91 func (self *SElbListenerRule) GetGlobalId() string { 92 return self.GetId() 93 } 94 95 func (self *SElbListenerRule) GetStatus() string { 96 return api.LB_STATUS_ENABLED 97 } 98 99 func (self *SElbListenerRule) Refresh() error { 100 rule, err := self.region.GetElbListenerRuleById(self.GetId()) 101 if err != nil { 102 return err 103 } 104 105 err = jsonutils.Update(self, rule) 106 if err != nil { 107 return err 108 } 109 110 return nil 111 } 112 113 func (self *SElbListenerRule) IsDefault() bool { 114 return self.IsDefaultRule 115 } 116 117 func (self *SElbListenerRule) IsEmulated() bool { 118 return false 119 } 120 121 func (self *SElbListenerRule) GetProjectId() string { 122 return "" 123 } 124 125 func (self *SElbListenerRule) GetDomain() string { 126 for _, condition := range self.Conditions { 127 if condition.Field == "host-header" { 128 return strings.Join(condition.Values, ",") 129 } 130 } 131 132 return "" 133 } 134 135 func (self *SElbListenerRule) GetCondition() string { 136 conditon, err := json.Marshal(self.Conditions) 137 if err != nil { 138 log.Errorf("GetCondition %s", err) 139 return "" 140 } 141 142 return string(conditon) 143 } 144 145 func (self *SElbListenerRule) GetPath() string { 146 for _, condition := range self.Conditions { 147 if condition.Field == "path-pattern" { 148 return strings.Join(condition.Values, ",") 149 } 150 } 151 152 return "" 153 } 154 155 func (self *SElbListenerRule) GetBackendGroupId() string { 156 for _, action := range self.Actions { 157 if action.Type == "forward" { 158 return action.TargetGroupArn 159 } 160 } 161 162 return "" 163 } 164 165 func (self *SElbListenerRule) Delete(ctx context.Context) error { 166 return self.region.DeleteElbListenerRule(self.GetId()) 167 } 168 169 func (self *SRegion) DeleteElbListenerRule(ruleId string) error { 170 client, err := self.GetElbV2Client() 171 if err != nil { 172 return err 173 } 174 175 params := &elbv2.DeleteRuleInput{} 176 params.SetRuleArn(ruleId) 177 _, err = client.DeleteRule(params) 178 if err != nil { 179 return err 180 } 181 182 return nil 183 } 184 185 func (self *SRegion) CreateElbListenerRule(listenerId string, config *cloudprovider.SLoadbalancerListenerRule) (*SElbListenerRule, error) { 186 client, err := self.GetElbV2Client() 187 if err != nil { 188 return nil, errors.Wrap(err, "GetElbV2Client") 189 } 190 191 forward := "forward" 192 action := &elbv2.Action{ 193 TargetGroupArn: &config.BackendGroupID, 194 Type: &forward, 195 } 196 197 condtions, err := parseConditions(config.Condition) 198 if err != nil { 199 return nil, errors.Wrap(err, "parseConditions") 200 } 201 202 params := &elbv2.CreateRuleInput{} 203 params.SetListenerArn(listenerId) 204 params.SetActions([]*elbv2.Action{action}) 205 params.SetConditions(condtions) 206 params.SetPriority(int64(1)) 207 ret, err := client.CreateRule(params) 208 if err != nil { 209 return nil, errors.Wrap(err, "CreateRule") 210 } 211 212 if len(ret.Rules) == 0 { 213 return nil, errors.Wrap(fmt.Errorf("empty rules"), "Region.CreateElbListenerRule.len") 214 } 215 216 rule := SElbListenerRule{} 217 err = unmarshalAwsOutput(ret.Rules[0], "", &rule) 218 if err != nil { 219 return nil, errors.Wrap(err, "unmarshalAwsOutput.rule") 220 } 221 222 rule.region = self 223 return &rule, nil 224 } 225 226 func parseConditions(conditions string) ([]*elbv2.RuleCondition, error) { 227 obj, err := jsonutils.ParseString(conditions) 228 if err != nil { 229 return nil, errors.Wrap(err, "ParseString.conditions") 230 } 231 232 conditionArray, ok := obj.(*jsonutils.JSONArray) 233 if !ok { 234 return nil, fmt.Errorf("parseConditions invalid condition fromat.") 235 } 236 237 ret := []*elbv2.RuleCondition{} 238 cs, _ := conditionArray.GetArray() 239 for i := range cs { 240 c, err := parseCondition(cs[i]) 241 if err != nil { 242 return nil, errors.Wrap(err, "parseCondition") 243 } 244 245 ret = append(ret, c) 246 } 247 248 return ret, nil 249 } 250 251 func parseCondition(condition jsonutils.JSONObject) (*elbv2.RuleCondition, error) { 252 conditionDict, ok := condition.(*jsonutils.JSONDict) 253 if !ok { 254 return nil, fmt.Errorf("parseCondition invalid condition fromat.") 255 } 256 257 dict, _ := conditionDict.GetMap() 258 field, ok := dict["field"] 259 if !ok { 260 return nil, fmt.Errorf("parseCondition invalid condition, missing field: %#v", condition) 261 } 262 263 f, _ := field.GetString() 264 switch f { 265 case "http-header": 266 return parseHttpHeaderCondition(conditionDict) 267 case "path-pattern": 268 return parsePathPatternCondition(conditionDict) 269 case "http-request-method": 270 return parseRequestModthdCondition(conditionDict) 271 case "host-header": 272 return parseHostHeaderCondition(conditionDict) 273 case "query-string": 274 return parseQueryStringCondition(conditionDict) 275 case "source-ip": 276 return parseSourceIpCondition(conditionDict) 277 default: 278 return nil, fmt.Errorf("parseCondition invalid condition key %#v", field) 279 } 280 } 281 282 func parseHttpHeaderCondition(conditon *jsonutils.JSONDict) (*elbv2.RuleCondition, error) { 283 ret := &elbv2.RuleCondition{} 284 ret.SetField("http-header") 285 286 values, err := conditon.GetMap("httpHeaderConfig") 287 if err != nil { 288 return nil, errors.Wrap(err, "GetMap") 289 } 290 291 name, ok := values["HttpHeaderName"] 292 if !ok { 293 return nil, fmt.Errorf("parseHttpHeaderCondition missing filed HttpHeaderName") 294 } 295 296 nameObj, ok := name.(*jsonutils.JSONString) 297 if !ok { 298 return nil, fmt.Errorf("parseHttpHeaderCondition missing invalid data %#v", name) 299 } 300 301 headname, _ := nameObj.GetString() 302 config := &elbv2.HttpHeaderConditionConfig{} 303 config.SetHttpHeaderName(headname) 304 305 vs, ok := values["values"] 306 if !ok { 307 return nil, fmt.Errorf("parseHttpHeaderCondition missing filed values") 308 } 309 310 _vs, err := parseConditionStringArrayValues(vs) 311 if err != nil { 312 return nil, errors.Wrap(err, "parseConditionStringArrayValues") 313 } 314 config.SetValues(_vs) 315 ret.SetHttpHeaderConfig(config) 316 return ret, nil 317 } 318 319 func parsePathPatternCondition(condition *jsonutils.JSONDict) (*elbv2.RuleCondition, error) { 320 ret := &elbv2.RuleCondition{} 321 ret.SetField("path-pattern") 322 323 values, err := condition.GetMap("pathPatternConfig") 324 if err != nil { 325 return nil, errors.Wrap(err, "GetMap") 326 } 327 328 config := &elbv2.PathPatternConditionConfig{} 329 vs, ok := values["values"] 330 if !ok { 331 return nil, fmt.Errorf("parsePathPatternCondition missing filed values") 332 } 333 334 _vs, err := parseConditionStringArrayValues(vs) 335 if err != nil { 336 return nil, errors.Wrap(err, "parseConditionStringArrayValues") 337 } 338 config.SetValues(_vs) 339 ret.SetPathPatternConfig(config) 340 return ret, nil 341 342 } 343 344 func parseRequestModthdCondition(condition *jsonutils.JSONDict) (*elbv2.RuleCondition, error) { 345 ret := &elbv2.RuleCondition{} 346 ret.SetField("http-request-method") 347 348 values, err := condition.GetMap("httpRequestMethodConfig") 349 if err != nil { 350 return nil, errors.Wrap(err, "GetMap.httpRequestMethodConfig") 351 } 352 353 config := &elbv2.HttpRequestMethodConditionConfig{} 354 vs, ok := values["values"] 355 if !ok { 356 return nil, fmt.Errorf("parseRequestModthdCondition missing filed values") 357 } 358 359 _vs, err := parseConditionStringArrayValues(vs) 360 if err != nil { 361 return nil, errors.Wrap(err, "parseConditionStringArrayValues") 362 } 363 config.SetValues(_vs) 364 return ret, nil 365 } 366 367 func parseHostHeaderCondition(condition *jsonutils.JSONDict) (*elbv2.RuleCondition, error) { 368 ret := &elbv2.RuleCondition{} 369 ret.SetField("host-header") 370 371 values, err := condition.GetMap("hostHeaderConfig") 372 if err != nil { 373 return nil, errors.Wrap(err, "GetMap.hostHeaderConfig") 374 } 375 376 config := &elbv2.HostHeaderConditionConfig{} 377 vs, ok := values["values"] 378 if !ok { 379 return nil, fmt.Errorf("parseHostHeaderCondition missing filed values") 380 } 381 382 _vs, err := parseConditionStringArrayValues(vs) 383 if err != nil { 384 return nil, errors.Wrap(err, "parseConditionStringArrayValues") 385 } 386 config.SetValues(_vs) 387 ret.SetHostHeaderConfig(config) 388 return ret, nil 389 } 390 391 func parseQueryStringCondition(condition *jsonutils.JSONDict) (*elbv2.RuleCondition, error) { 392 ret := &elbv2.RuleCondition{} 393 ret.SetField("query-string") 394 395 values, err := condition.GetMap("queryStringConfig") 396 if err != nil { 397 return nil, errors.Wrap(err, "GetMap.queryStringConfig") 398 } 399 400 config := &elbv2.QueryStringConditionConfig{} 401 vs, ok := values["values"] 402 if !ok { 403 return nil, fmt.Errorf("parseQueryStringCondition missing filed values") 404 } 405 406 _vs, err := parseConditionDictArrayValues(vs) 407 if err != nil { 408 return nil, errors.Wrap(err, "parseConditionDictArrayValues") 409 } 410 config.SetValues(_vs) 411 ret.SetQueryStringConfig(config) 412 return ret, nil 413 } 414 415 func parseSourceIpCondition(condition *jsonutils.JSONDict) (*elbv2.RuleCondition, error) { 416 ret := &elbv2.RuleCondition{} 417 ret.SetField("source-ip") 418 419 values, err := condition.GetMap("sourceIpConfig") 420 if err != nil { 421 return nil, errors.Wrap(err, "GetMap.sourceIpConfig") 422 } 423 424 config := &elbv2.SourceIpConditionConfig{} 425 vs, ok := values["values"] 426 if !ok { 427 return nil, fmt.Errorf("parseSourceIpCondition missing filed values") 428 } 429 430 _vs, err := parseConditionStringArrayValues(vs) 431 if err != nil { 432 return nil, errors.Wrap(err, "parseConditionStringArrayValues") 433 } 434 config.SetValues(_vs) 435 return ret, nil 436 } 437 438 func parseConditionStringArrayValues(values jsonutils.JSONObject) ([]*string, error) { 439 objs, ok := values.(*jsonutils.JSONArray) 440 if !ok { 441 return nil, fmt.Errorf("parseConditionStringArrayValues invalid values format, required array: %#v", values) 442 } 443 444 ret := []*string{} 445 vs, _ := objs.GetArray() 446 for i := range vs { 447 v, ok := vs[i].(*jsonutils.JSONString) 448 if !ok { 449 return nil, fmt.Errorf("parseConditionStringArrayValues invalid value, required string: %#v", v) 450 } 451 452 _v, _ := v.GetString() 453 ret = append(ret, &_v) 454 } 455 456 return ret, nil 457 } 458 459 func parseConditionDictArrayValues(values jsonutils.JSONObject) ([]*elbv2.QueryStringKeyValuePair, error) { 460 objs, ok := values.(*jsonutils.JSONArray) 461 if !ok { 462 return nil, fmt.Errorf("parseConditionDictArrayValues invalid values format, required array: %#v", values) 463 } 464 465 ret := []*elbv2.QueryStringKeyValuePair{} 466 vs, _ := objs.GetArray() 467 for i := range vs { 468 v, ok := vs[i].(*jsonutils.JSONDict) 469 if !ok { 470 return nil, fmt.Errorf("parseConditionDictArrayValues invalid value, required dict: %#v", v) 471 } 472 473 key, err := v.GetString("key") 474 if err != nil { 475 return nil, errors.Wrap(err, "GetString.key") 476 } 477 478 value, err := v.GetString("value") 479 if err != nil { 480 return nil, errors.Wrap(err, "GetString.value") 481 } 482 483 pair := &elbv2.QueryStringKeyValuePair{} 484 pair.SetKey(key) 485 pair.SetValue(value) 486 ret = append(ret, pair) 487 } 488 489 return ret, nil 490 }