1package migration 2 3import ( 4 "errors" 5 6 "github.com/coredns/corefile-migration/migration/corefile" 7) 8 9type plugin struct { 10 status string 11 replacedBy string 12 additional string 13 namedOptions map[string]option 14 patternOptions map[string]option 15 action pluginActionFn // action affecting this plugin only 16 add serverActionFn // action to add a new plugin to the server block 17 downAction pluginActionFn // downgrade action affecting this plugin only 18} 19 20type option struct { 21 name string 22 status string 23 replacedBy string 24 additional string 25 action optionActionFn // action affecting this option only 26 add pluginActionFn // action to add the option to the plugin 27 downAction optionActionFn // downgrade action affecting this option only 28} 29 30type corefileAction func(*corefile.Corefile) (*corefile.Corefile, error) 31type serverActionFn func(*corefile.Server) (*corefile.Server, error) 32type pluginActionFn func(*corefile.Plugin) (*corefile.Plugin, error) 33type optionActionFn func(*corefile.Option) (*corefile.Option, error) 34 35// plugins holds a map of plugin names and their migration rules per "version". "Version" here is meaningless outside 36// of the context of this code. Each change in options or migration actions for a plugin requires a new "version" 37// containing those new/removed options and migration actions. Plugins in CoreDNS are not versioned. 38var plugins = map[string]map[string]plugin{ 39 "kubernetes": { 40 "v1": plugin{ 41 namedOptions: map[string]option{ 42 "resyncperiod": {}, 43 "endpoint": {}, 44 "tls": {}, 45 "namespaces": {}, 46 "labels": {}, 47 "pods": {}, 48 "endpoint_pod_names": {}, 49 "upstream": {}, 50 "ttl": {}, 51 "noendpoints": {}, 52 "transfer": {}, 53 "fallthrough": {}, 54 "ignore": {}, 55 }, 56 }, 57 "v2": plugin{ 58 namedOptions: map[string]option{ 59 "resyncperiod": {}, 60 "endpoint": {}, 61 "tls": {}, 62 "namespaces": {}, 63 "labels": {}, 64 "pods": {}, 65 "endpoint_pod_names": {}, 66 "upstream": {}, 67 "ttl": {}, 68 "noendpoints": {}, 69 "transfer": {}, 70 "fallthrough": {}, 71 "ignore": {}, 72 "kubeconfig": {}, // new option 73 }, 74 }, 75 "v3": plugin{ 76 namedOptions: map[string]option{ 77 "resyncperiod": {}, 78 "endpoint": { // new deprecation 79 status: SevDeprecated, 80 action: useFirstArgumentOnly, 81 }, 82 "tls": {}, 83 "kubeconfig": {}, 84 "namespaces": {}, 85 "labels": {}, 86 "pods": {}, 87 "endpoint_pod_names": {}, 88 "upstream": {}, 89 "ttl": {}, 90 "noendpoints": {}, 91 "transfer": {}, 92 "fallthrough": {}, 93 "ignore": {}, 94 }, 95 }, 96 "v4": plugin{ 97 namedOptions: map[string]option{ 98 "resyncperiod": {}, 99 "endpoint": { 100 status: SevIgnored, 101 action: useFirstArgumentOnly, 102 }, 103 "tls": {}, 104 "kubeconfig": {}, 105 "namespaces": {}, 106 "labels": {}, 107 "pods": {}, 108 "endpoint_pod_names": {}, 109 "upstream": { // new deprecation 110 status: SevDeprecated, 111 action: removeOption, 112 }, 113 "ttl": {}, 114 "noendpoints": {}, 115 "transfer": {}, 116 "fallthrough": {}, 117 "ignore": {}, 118 }, 119 }, 120 "v5": plugin{ 121 namedOptions: map[string]option{ 122 "resyncperiod": { // new deprecation 123 status: SevDeprecated, 124 action: removeOption, 125 }, 126 "endpoint": { 127 status: SevIgnored, 128 action: useFirstArgumentOnly, 129 }, 130 "tls": {}, 131 "kubeconfig": {}, 132 "namespaces": {}, 133 "labels": {}, 134 "pods": {}, 135 "endpoint_pod_names": {}, 136 "upstream": { 137 status: SevIgnored, 138 action: removeOption, 139 }, 140 "ttl": {}, 141 "noendpoints": {}, 142 "transfer": {}, 143 "fallthrough": {}, 144 "ignore": {}, 145 }, 146 }, 147 "v6": plugin{ 148 namedOptions: map[string]option{ 149 "resyncperiod": { // now ignored 150 status: SevIgnored, 151 action: removeOption, 152 }, 153 "endpoint": { 154 status: SevIgnored, 155 action: useFirstArgumentOnly, 156 }, 157 "tls": {}, 158 "kubeconfig": {}, 159 "namespaces": {}, 160 "labels": {}, 161 "pods": {}, 162 "endpoint_pod_names": {}, 163 "upstream": { 164 status: SevIgnored, 165 action: removeOption, 166 }, 167 "ttl": {}, 168 "noendpoints": {}, 169 "transfer": {}, 170 "fallthrough": {}, 171 "ignore": {}, 172 }, 173 }, 174 "v7": plugin{ 175 namedOptions: map[string]option{ 176 "resyncperiod": { // new removal 177 status: SevRemoved, 178 action: removeOption, 179 }, 180 "endpoint": { 181 status: SevIgnored, 182 action: useFirstArgumentOnly, 183 }, 184 "tls": {}, 185 "kubeconfig": {}, 186 "namespaces": {}, 187 "labels": {}, 188 "pods": {}, 189 "endpoint_pod_names": {}, 190 "upstream": { // new removal 191 status: SevRemoved, 192 action: removeOption, 193 }, 194 "ttl": {}, 195 "noendpoints": {}, 196 "transfer": {}, 197 "fallthrough": {}, 198 "ignore": {}, 199 }, 200 }, 201 "v8 remove transfer option": plugin{ 202 namedOptions: map[string]option{ 203 "endpoint": { 204 status: SevIgnored, 205 action: useFirstArgumentOnly, 206 }, 207 "tls": {}, 208 "kubeconfig": {}, 209 "namespaces": {}, 210 "labels": {}, 211 "pods": {}, 212 "endpoint_pod_names": {}, 213 "ttl": {}, 214 "noendpoints": {}, 215 "transfer": { 216 status: SevRemoved, 217 action: removeOption, 218 }, 219 "fallthrough": {}, 220 "ignore": {}, 221 }, 222 }, 223 "v8": plugin{ 224 namedOptions: map[string]option{ 225 "endpoint": { 226 status: SevIgnored, 227 action: useFirstArgumentOnly, 228 }, 229 "tls": {}, 230 "kubeconfig": {}, 231 "namespaces": {}, 232 "labels": {}, 233 "pods": {}, 234 "endpoint_pod_names": {}, 235 "ttl": {}, 236 "noendpoints": {}, 237 "fallthrough": {}, 238 "ignore": {}, 239 }, 240 }, 241 }, 242 243 "errors": { 244 "v1": plugin{}, 245 "v2": plugin{ 246 namedOptions: map[string]option{ 247 "consolidate": {}, 248 }, 249 }, 250 }, 251 252 "health": { 253 "v1": plugin{ 254 namedOptions: map[string]option{ 255 "lameduck": {}, 256 }, 257 }, 258 "v1 add lameduck": plugin{ 259 namedOptions: map[string]option{ 260 "lameduck": { 261 status: SevNewDefault, 262 add: func(c *corefile.Plugin) (*corefile.Plugin, error) { 263 return addOptionToPlugin(c, &corefile.Option{Name: "lameduck 5s"}) 264 }, 265 downAction: removeOption, 266 }, 267 }, 268 }, 269 }, 270 271 "hosts": { 272 "v1": plugin{ 273 namedOptions: map[string]option{ 274 "ttl": {}, 275 "no_reverse": {}, 276 "reload": {}, 277 "fallthrough": {}, 278 }, 279 patternOptions: map[string]option{ 280 `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}`: {}, // close enough 281 `[0-9A-Fa-f]{1,4}:[:0-9A-Fa-f]+:[0-9A-Fa-f]{1,4}`: {}, // less close, but still close enough 282 }, 283 }, 284 }, 285 286 "rewrite": { 287 "v1": plugin{ 288 namedOptions: map[string]option{ 289 "type": {}, 290 "class": {}, 291 "name": {}, 292 "answer name": {}, 293 "edns0": {}, 294 }, 295 }, 296 "v2": plugin{ 297 namedOptions: map[string]option{ 298 "type": {}, 299 "class": {}, 300 "name": {}, 301 "answer name": {}, 302 "edns0": {}, 303 "ttl": {}, // new option 304 }, 305 }, 306 }, 307 308 "log": { 309 "v1": plugin{ 310 namedOptions: map[string]option{ 311 "class": {}, 312 }, 313 }, 314 }, 315 316 "cache": { 317 "v1": plugin{ 318 namedOptions: map[string]option{ 319 "success": {}, 320 "denial": {}, 321 "prefetch": {}, 322 }, 323 }, 324 "v2": plugin{ 325 namedOptions: map[string]option{ 326 "success": {}, 327 "denial": {}, 328 "prefetch": {}, 329 "serve_stale": {}, // new option 330 }, 331 }, 332 }, 333 334 "forward": { 335 "v1": plugin{ 336 namedOptions: map[string]option{ 337 "except": {}, 338 "force_tcp": {}, 339 "expire": {}, 340 "max_fails": {}, 341 "tls": {}, 342 "tls_servername": {}, 343 "policy": {}, 344 "health_check": {}, 345 }, 346 }, 347 "v2": plugin{ 348 namedOptions: map[string]option{ 349 "except": {}, 350 "force_tcp": {}, 351 "prefer_udp": {}, 352 "expire": {}, 353 "max_fails": {}, 354 "tls": {}, 355 "tls_servername": {}, 356 "policy": {}, 357 "health_check": {}, 358 }, 359 }, 360 "v3": plugin{ 361 namedOptions: map[string]option{ 362 "except": {}, 363 "force_tcp": {}, 364 "prefer_udp": {}, 365 "expire": {}, 366 "max_fails": {}, 367 "tls": {}, 368 "tls_servername": {}, 369 "policy": {}, 370 "health_check": {}, 371 "max_concurrent": {}, 372 }, 373 }, 374 "v3 add max_concurrent": plugin{ 375 namedOptions: map[string]option{ 376 "except": {}, 377 "force_tcp": {}, 378 "prefer_udp": {}, 379 "expire": {}, 380 "max_fails": {}, 381 "tls": {}, 382 "tls_servername": {}, 383 "policy": {}, 384 "health_check": {}, 385 "max_concurrent": { // new option 386 status: SevNewDefault, 387 add: func(c *corefile.Plugin) (*corefile.Plugin, error) { 388 return addOptionToPlugin(c, &corefile.Option{Name: "max_concurrent 1000"}) 389 }, 390 downAction: removeOption, 391 }, 392 }, 393 }, 394 }, 395 396 "k8s_external": { 397 "v1": plugin{ 398 namedOptions: map[string]option{ 399 "apex": {}, 400 "ttl": {}, 401 }, 402 }, 403 }, 404 405 "proxy": { 406 "v1": plugin{ 407 namedOptions: map[string]option{ 408 "policy": {}, 409 "fail_timeout": {}, 410 "max_fails": {}, 411 "health_check": {}, 412 "except": {}, 413 "spray": {}, 414 "protocol": { // https_google option ignored 415 status: SevIgnored, 416 action: proxyRemoveHttpsGoogleProtocol, 417 }, 418 }, 419 }, 420 "v2": plugin{ 421 namedOptions: map[string]option{ 422 "policy": {}, 423 "fail_timeout": {}, 424 "max_fails": {}, 425 "health_check": {}, 426 "except": {}, 427 "spray": {}, 428 "protocol": { // https_google option removed 429 status: SevRemoved, 430 action: proxyRemoveHttpsGoogleProtocol, 431 }, 432 }, 433 }, 434 "deprecation": plugin{ // proxy -> forward deprecation migration 435 status: SevDeprecated, 436 replacedBy: "forward", 437 action: proxyToForwardPluginAction, 438 namedOptions: proxyToForwardOptionsMigrations, 439 }, 440 "removal": plugin{ // proxy -> forward forced migration 441 status: SevRemoved, 442 replacedBy: "forward", 443 action: proxyToForwardPluginAction, 444 namedOptions: proxyToForwardOptionsMigrations, 445 }, 446 }, 447 448 "transfer": { 449 "v1": plugin{ 450 namedOptions: map[string]option{ 451 "to": {}, 452 }, 453 }, 454 }, 455} 456 457func removePlugin(*corefile.Plugin) (*corefile.Plugin, error) { return nil, nil } 458func removeOption(*corefile.Option) (*corefile.Option, error) { return nil, nil } 459 460func renamePlugin(p *corefile.Plugin, to string) (*corefile.Plugin, error) { 461 p.Name = to 462 return p, nil 463} 464 465func addToServerBlockWithPlugins(sb *corefile.Server, newPlugin *corefile.Plugin, with []string) (*corefile.Server, error) { 466 if len(with) == 0 { 467 // add to all blocks 468 sb.Plugins = append(sb.Plugins, newPlugin) 469 return sb, nil 470 } 471 for _, p := range sb.Plugins { 472 for _, w := range with { 473 if w == p.Name { 474 // add to this block 475 sb.Plugins = append(sb.Plugins, newPlugin) 476 return sb, nil 477 } 478 } 479 } 480 return sb, nil 481} 482 483func copyKubernetesTransferOptToPlugin(cf *corefile.Corefile) (*corefile.Corefile, error) { 484 for _, s := range cf.Servers { 485 var ( 486 to []string 487 zone string 488 ) 489 for _, p := range s.Plugins { 490 if p.Name != "kubernetes" { 491 continue 492 } 493 zone = p.Args[0] 494 for _, o := range p.Options { 495 if o.Name != "transfer" { 496 continue 497 } 498 to = o.Args 499 } 500 } 501 if len(to) < 2 { 502 continue 503 } 504 s.Plugins = append(s.Plugins, &corefile.Plugin{ 505 Name: "transfer", 506 Args: []string{zone}, 507 Options: []*corefile.Option{{Name: "to", Args: to[1:]}}, 508 }) 509 } 510 return cf, nil 511} 512 513func addToKubernetesServerBlocks(sb *corefile.Server, newPlugin *corefile.Plugin) (*corefile.Server, error) { 514 return addToServerBlockWithPlugins(sb, newPlugin, []string{"kubernetes"}) 515} 516 517func addToForwardingServerBlocks(sb *corefile.Server, newPlugin *corefile.Plugin) (*corefile.Server, error) { 518 return addToServerBlockWithPlugins(sb, newPlugin, []string{"forward", "proxy"}) 519} 520 521func addToAllServerBlocks(sb *corefile.Server, newPlugin *corefile.Plugin) (*corefile.Server, error) { 522 return addToServerBlockWithPlugins(sb, newPlugin, []string{}) 523} 524 525func addOptionToPlugin(pl *corefile.Plugin, newOption *corefile.Option) (*corefile.Plugin, error) { 526 pl.Options = append(pl.Options, newOption) 527 return pl, nil 528} 529 530var proxyToForwardOptionsMigrations = map[string]option{ 531 "policy": { 532 action: func(o *corefile.Option) (*corefile.Option, error) { 533 if len(o.Args) == 1 && o.Args[0] == "least_conn" { 534 o.Name = "force_tcp" 535 o.Args = nil 536 } 537 return o, nil 538 }, 539 }, 540 "except": {}, 541 "fail_timeout": {action: removeOption}, 542 "max_fails": {action: removeOption}, 543 "health_check": {action: removeOption}, 544 "spray": {action: removeOption}, 545 "protocol": { 546 action: func(o *corefile.Option) (*corefile.Option, error) { 547 if len(o.Args) >= 2 && o.Args[0] == "force_tcp" { 548 o.Name = "force_tcp" 549 o.Args = nil 550 return o, nil 551 } 552 return nil, nil 553 }, 554 }, 555} 556 557var proxyToForwardPluginAction = func(p *corefile.Plugin) (*corefile.Plugin, error) { 558 return renamePlugin(p, "forward") 559} 560 561var useFirstArgumentOnly = func(o *corefile.Option) (*corefile.Option, error) { 562 if len(o.Args) < 1 { 563 return o, nil 564 } 565 o.Args = o.Args[:1] 566 return o, nil 567} 568 569var proxyRemoveHttpsGoogleProtocol = func(o *corefile.Option) (*corefile.Option, error) { 570 if len(o.Args) > 0 && o.Args[0] == "https_google" { 571 return nil, nil 572 } 573 return o, nil 574} 575 576func breakForwardStubDomainsIntoServerBlocks(cf *corefile.Corefile) (*corefile.Corefile, error) { 577 for _, sb := range cf.Servers { 578 for j, fwd := range sb.Plugins { 579 if fwd.Name != "forward" { 580 continue 581 } 582 if len(fwd.Args) == 0 { 583 return nil, errors.New("found invalid forward plugin declaration") 584 } 585 if fwd.Args[0] == "." { 586 // dont move the default upstream 587 continue 588 } 589 if len(sb.DomPorts) != 1 { 590 return cf, errors.New("unhandled migration of multi-domain/port server block") 591 } 592 if sb.DomPorts[0] != "." && sb.DomPorts[0] != ".:53" { 593 return cf, errors.New("unhandled migration of non-default domain/port server block") 594 } 595 596 newSb := &corefile.Server{} // create a new server block 597 newSb.DomPorts = []string{fwd.Args[0]} // copy the forward zone to the server block domain 598 fwd.Args[0] = "." // the plugin's zone changes to "." for brevity 599 newSb.Plugins = append(newSb.Plugins, fwd) // add the plugin to its new server block 600 601 // Add appropriate addtl plugins to new server block 602 newSb.Plugins = append(newSb.Plugins, &corefile.Plugin{Name: "loop"}) 603 newSb.Plugins = append(newSb.Plugins, &corefile.Plugin{Name: "errors"}) 604 newSb.Plugins = append(newSb.Plugins, &corefile.Plugin{Name: "cache", Args: []string{"30"}}) 605 606 //add new server block to corefile 607 cf.Servers = append(cf.Servers, newSb) 608 609 //remove the forward plugin from the original server block 610 sb.Plugins = append(sb.Plugins[:j], sb.Plugins[j+1:]...) 611 } 612 } 613 return cf, nil 614} 615