Commit 801cb41b authored by Geoff Simmons's avatar Geoff Simmons

Add the select field to the VarnishConfig rewrites object.

Supports selecting a match when more than one pattern may match for
regex and prefix matching.
parent 85e912e0
......@@ -179,6 +179,15 @@ spec:
- append
- prepend
- delete
select:
type: string
enum:
- unique
- first
- last
- exact
- longest
- shortest
compare:
type: string
enum:
......
......@@ -155,3 +155,49 @@ spec:
- target: req.http.Prepend-Hdr-Target
source: req.http.Prepend-Hdr-Src
method: prepend
- target: resp.http.Select-First
source: req.url
rules:
- value: /tea/foo/bar/baz/quux
rewrite: Quux
- value: /tea/foo/bar/baz
rewrite: Baz
- value: /tea/foo/bar
rewrite: Bar
- value: /tea/foo
rewrite: Foo
compare: prefix
method: replace
select: first
- target: resp.http.Select-Longest
source: req.url
rules:
- value: /tea/foo
rewrite: Foo
- value: /tea/foo/bar/baz
rewrite: Baz
- value: /tea/foo/bar
rewrite: Bar
- value: /tea/foo/bar/baz/quux
rewrite: Quux
compare: prefix
method: replace
select: longest
- target: resp.http.Cookie-Select
source: req.http.Cookie
rules:
- value: \bcookie1\s*=\s*([^,;[:space:]]+)
rewrite: cookie1:\1
- value: \bcookie2\s*=\s*([^,;[:space:]]+)
rewrite: cookie2:\1
- value: \bcookie3\s*=\s*([^,;[:space:]]+)
rewrite: cookie3:\1
- value: \bcookie4\s*=\s*([^,;[:space:]]+)
rewrite: cookie4:\1
- value: \bcookie5\s*=\s*([^,;[:space:]]+)
rewrite: cookie5:\1
method: rewrite
select: last
......@@ -221,6 +221,17 @@ const (
BackendError = "backend_error"
)
type SelectType string
const (
Unique SelectType = "unique"
First = "first"
Last = "last"
Exact = "exact"
Longest = "longest"
Shortest = "shortest"
)
type RewriteSpec struct {
Rules []RewriteRule `json:"rules,omitempty"`
MatchFlags *MatchFlagsType `json:"match-flags,omitempty"`
......@@ -229,6 +240,7 @@ type RewriteSpec struct {
Method MethodType `json:"method,omitempty"`
Compare RewriteCompare `json:"compare,omitempty"`
VCLSub VCLSubType `json:"vcl-sub,omitempty"`
Select SelectType `json:"select,omitempty"`
}
// VarnishConfigStatus is the status for a VarnishConfig resource
......
......@@ -553,6 +553,23 @@ func (worker *NamespaceWorker) configRewrites(spec *vcl.Spec,
vclRw.VCLSub = vcl.Unspecified
}
switch rw.Select {
case vcr_v1alpha1.Unique:
vclRw.Select = vcl.Unique
case vcr_v1alpha1.First:
vclRw.Select = vcl.First
case vcr_v1alpha1.Last:
vclRw.Select = vcl.Last
case vcr_v1alpha1.Exact:
vclRw.Select = vcl.Exact
case vcr_v1alpha1.Longest:
vclRw.Select = vcl.Longest
case vcr_v1alpha1.Shortest:
vclRw.Select = vcl.Shortest
default:
vclRw.Select = vcl.Unique
}
if rw.MatchFlags != nil {
vclRw.MatchFlags = vcl.MatchFlagsType{
UTF8: rw.MatchFlags.UTF8,
......
......@@ -108,6 +108,14 @@ func validateRewrites(rewrites []vcr_v1alpha1.RewriteSpec) error {
"mix client and backend contexts", rw.Target,
rw.Source)
}
if rw.Compare != vcr_v1alpha1.Prefix &&
(rw.Select == vcr_v1alpha1.Exact ||
rw.Select == vcr_v1alpha1.Longest ||
rw.Select == vcr_v1alpha1.Shortest) {
return fmt.Errorf("select value %s not permitted with "+
"compare value %s", rw.Select, rw.Compare)
}
if rw.Compare != vcr_v1alpha1.RewriteMatch &&
rw.MatchFlags != nil &&
((rw.MatchFlags.MaxMem != nil &&
......
......@@ -22,6 +22,13 @@ sub vcl_init {
sub vcl_{{rewrSub $r}} {
{{- if needsMatcher $r}}
if ({{rewrName $i}}.{{rewrMatch $r}}({{$r.Source}})) {
{{- if needsUniqueCheck $r}}
if ({{rewrName $i}}.nmatches() != 1) {
std.log({{$r.Source}} + " had " +
{{rewrName $i}}.nmatches() + " matches");
return(fail);
}
{{- end}}
{{- end}}
{{- if rewrMethodDelete $r}}
unset {{$r.Target}};
......@@ -34,8 +41,10 @@ sub vcl_{{rewrSub $r}} {
{{- else}}
set {{$r.Target}} =
{{rewrName $i}}.{{rewrOp $r}}({{$r.Source}},
{{rewrName $i}}.string()
{{- if needsAll $r}}, all=true{{end -}}
{{rewrName $i}}.string({{rewrSelect $r}})
{{- if needsAll $r}}, all=true{{end -}}
{{- if needsSelectEnum $r -}}
, {{rewrSelect $r}}{{end -}}
);
{{- end}}
{{- if needsMatcher $r}}
......
......@@ -493,6 +493,206 @@ func TestRewritePrependHdr(t *testing.T) {
testTemplate(t, rewritePrependHdr, gold)
}
var rewriteSelectFirst = Spec{
Rewrites: []Rewrite{{
Target: "bereq.http.Hdr",
Source: "bereq.url",
Compare: Prefix,
Select: First,
Rules: []RewriteRule{
{
Value: `/tea/foo/bar/baz/quux`,
Rewrite: `Quux`,
},
{
Value: `/tea/foo/bar/baz`,
Rewrite: `Baz`,
},
{
Value: `/tea/foo/bar`,
Rewrite: `Bar`,
},
{
Value: `/tea/foo`,
Rewrite: `Foo`,
},
},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
}},
}
func TestRewriteSelectFirst(t *testing.T) {
gold := "rewrite_select_first.golden"
testTemplate(t, rewriteSelectFirst, gold)
}
var rewriteSelectPermutations = Spec{
Rewrites: []Rewrite{
{
Target: "req.url",
Source: "req.url",
Compare: Prefix,
Select: Unique,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `bar`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
{
Target: "req.url",
Source: "req.url",
Compare: Prefix,
Select: First,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `bar`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
{
Target: "req.url",
Source: "req.url",
Compare: Prefix,
Select: Last,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `bar`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
{
Target: "req.url",
Source: "req.url",
Compare: Prefix,
Select: Exact,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `bar`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
{
Target: "req.url",
Source: "req.url",
Compare: Prefix,
Select: Shortest,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `bar`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
{
Target: "req.url",
Source: "req.url",
Compare: Prefix,
Select: Longest,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `bar`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
},
}
func TestRewriteSelectPermutations(t *testing.T) {
gold := "rewrite_select_permute.golden"
testTemplate(t, rewriteSelectPermutations, gold)
}
var rewriteSelectOperations = Spec{
Rewrites: []Rewrite{
{
Target: "resp.http.Hdr",
Source: "req.url",
Compare: Prefix,
Select: First,
Method: Sub,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `foo`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
{
Target: "resp.http.Hdr",
Source: "req.url",
Compare: Prefix,
Select: Exact,
Method: Suball,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `foo`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
{
Target: "resp.http.Hdr",
Source: "req.url",
Select: First,
Method: Sub,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `foo`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
{
Target: "resp.http.Hdr",
Source: "req.url",
Select: Last,
Method: Suball,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `foo`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
{
Target: "resp.http.Hdr",
Source: "req.url",
Select: First,
Method: RewriteMethod,
Rules: []RewriteRule{{
Value: `/foo`,
Rewrite: `foo`,
}},
MatchFlags: MatchFlagsType{
CaseSensitive: true,
},
},
},
}
func TestRewriteSelectOperations(t *testing.T) {
gold := "rewrite_select_ops.golden"
testTemplate(t, rewriteSelectOperations, gold)
}
// Code boilerplate for writing the golden file.
// import ioutils
// func TestRewriteXXX(t *testing.T) {
......
......@@ -491,6 +491,36 @@ func (flags MatchFlagsType) hash(hash hash.Hash) {
}
}
type SelectType uint8
const (
Unique SelectType = iota
First
Last
Exact
Longest
Shortest
)
func (s SelectType) String() string {
switch s {
case Unique:
return "UNIQUE"
case First:
return "FIRST"
case Last:
return "LAST"
case Exact:
return "EXACT"
case Longest:
return "LONGEST"
case Shortest:
return "SHORTEST"
default:
return "__INVALID_SELECT__"
}
}
type Rewrite struct {
Rules []RewriteRule
MatchFlags MatchFlagsType
......@@ -499,6 +529,7 @@ type Rewrite struct {
Method MethodType
Compare RewriteCompare
VCLSub VCLSubType
Select SelectType
}
func (rw Rewrite) hash(hash hash.Hash) {
......@@ -512,6 +543,7 @@ func (rw Rewrite) hash(hash hash.Hash) {
hash.Write([]byte{byte(rw.Method)})
hash.Write([]byte{byte(rw.Compare)})
hash.Write([]byte{byte(rw.VCLSub)})
hash.Write([]byte{byte(rw.Select)})
}
// interface for sorting []Rewrite
......
......@@ -54,6 +54,7 @@ var fMap = template.FuncMap{
"rewrSub": func(rewr Rewrite) string { return rewrSub(rewr) },
"rewrMatch": func(rewr Rewrite) string { return rewrMatch(rewr) },
"rewrOp": func(rewr Rewrite) string { return rewrOp(rewr) },
"rewrSelect": func(rewr Rewrite) string { return rewrSelect(rewr) },
"aclCmp": func(comparand string) string {
return aclCmp(comparand)
},
......@@ -114,6 +115,22 @@ var fMap = template.FuncMap{
"rewrOperand2": func(rewr Rewrite, i int) string {
return rewrOperand2(rewr, i)
},
"needsUniqueCheck": func(rewr Rewrite) bool {
if rewr.Compare == RewriteEqual || rewr.Select != Unique {
return false
}
switch rewr.Method {
case Delete:
return false
case Sub, Suball, RewriteMethod:
return true
default:
return len(rewr.Rules) > 0 && rewr.Rules[0].Value != ""
}
},
"needsSelectEnum": func(rewr Rewrite) bool {
return rewr.Select != Unique
},
}
const (
......@@ -437,6 +454,13 @@ func rewrSub(rewr Rewrite) string {
}
}
func rewrSelect(rewr Rewrite) string {
if rewr.Select == Unique {
return ""
}
return "select=" + rewr.Select.String()
}
func rewrOperand1(rewr Rewrite) string {
if len(rewr.Rules) == 0 {
return rewr.Target
......@@ -449,7 +473,7 @@ func rewrOperand2(rewr Rewrite, i int) string {
return `"` + rewr.Rules[0].Rewrite + `"`
}
if len(rewr.Rules) > 0 && rewr.Rules[0].Value != "" {
return rewrName(i) + ".string()"
return rewrName(i) + ".string(" + rewrSelect(rewr) + ")"
}
return rewr.Source
}
......
......@@ -10,6 +10,11 @@ sub vcl_init {
sub vcl_deliver {
if (vk8s_rewrite_0.match(req.http.Append-Rule-Src)) {
if (vk8s_rewrite_0.nmatches() != 1) {
std.log(req.http.Append-Rule-Src + " had " +
vk8s_rewrite_0.nmatches() + " matches");
return(fail);
}
set resp.http.Append-Rule-Target = req.http.Append-Rule-Src + vk8s_rewrite_0.string();
}
}
......@@ -10,8 +10,13 @@ sub vcl_init {
sub vcl_recv {
if (vk8s_rewrite_0.match(req.url)) {
if (vk8s_rewrite_0.nmatches() != 1) {
std.log(req.url + " had " +
vk8s_rewrite_0.nmatches() + " matches");
return(fail);
}
set req.url =
vk8s_rewrite_0.extract(req.url,
vk8s_rewrite_0.string());
vk8s_rewrite_0.string());
}
}
......@@ -10,8 +10,13 @@ sub vcl_init {
sub vcl_recv {
if (vk8s_rewrite_0.match(req.http.Cookie)) {
if (vk8s_rewrite_0.nmatches() != 1) {
std.log(req.http.Cookie + " had " +
vk8s_rewrite_0.nmatches() + " matches");
return(fail);
}
set req.http.Session-Token =
vk8s_rewrite_0.extract(req.http.Cookie,
vk8s_rewrite_0.string());
vk8s_rewrite_0.string());
}
}
......@@ -12,6 +12,6 @@ sub vcl_backend_fetch {
if (vk8s_rewrite_0.match(bereq.url)) {
set bereq.url =
vk8s_rewrite_0.sub(bereq.url,
vk8s_rewrite_0.string());
vk8s_rewrite_0.string());
}
}
......@@ -10,8 +10,13 @@ sub vcl_init {
sub vcl_backend_fetch {
if (vk8s_rewrite_0.hasprefix(bereq.url)) {
if (vk8s_rewrite_0.nmatches() != 1) {
std.log(bereq.url + " had " +
vk8s_rewrite_0.nmatches() + " matches");
return(fail);
}
set bereq.url =
vk8s_rewrite_0.sub(bereq.url,
vk8s_rewrite_0.string());
vk8s_rewrite_0.string());
}
}
......@@ -10,8 +10,13 @@ sub vcl_init {
sub vcl_backend_fetch {
if (vk8s_rewrite_0.hasprefix(bereq.url)) {
if (vk8s_rewrite_0.nmatches() != 1) {
std.log(bereq.url + " had " +
vk8s_rewrite_0.nmatches() + " matches");
return(fail);
}
set bereq.url =
vk8s_rewrite_0.sub(bereq.url,
vk8s_rewrite_0.string(), all=true);
vk8s_rewrite_0.string(), all=true);
}
}
......@@ -11,8 +11,13 @@ sub vcl_init {
sub vcl_backend_fetch {
if (vk8s_rewrite_0.match(bereq.url)) {
if (vk8s_rewrite_0.nmatches() != 1) {
std.log(bereq.url + " had " +
vk8s_rewrite_0.nmatches() + " matches");
return(fail);
}
set bereq.url =
vk8s_rewrite_0.sub(bereq.url,
vk8s_rewrite_0.string());
vk8s_rewrite_0.string());
}
}
......@@ -10,6 +10,11 @@ sub vcl_init {
sub vcl_deliver {
if (vk8s_rewrite_0.match(req.http.X-Bazz)) {
if (vk8s_rewrite_0.nmatches() != 1) {
std.log(req.http.X-Bazz + " had " +
vk8s_rewrite_0.nmatches() + " matches");
return(fail);
}
set resp.http.X-Bazz = vk8s_rewrite_0.string() + req.http.X-Bazz;
}
}
import re2;
import selector;
sub vcl_init {
new vk8s_rewrite_0 = selector.set();
vk8s_rewrite_0.add("/tea/foo/bar/baz/quux", string="Quux");
vk8s_rewrite_0.add("/tea/foo/bar/baz", string="Baz");
vk8s_rewrite_0.add("/tea/foo/bar", string="Bar");
vk8s_rewrite_0.add("/tea/foo", string="Foo");
}
sub vcl_backend_fetch {
if (vk8s_rewrite_0.hasprefix(bereq.url)) {
set bereq.http.Hdr = vk8s_rewrite_0.string(select=FIRST);
}
}
import re2;
import selector;
sub vcl_init {
new vk8s_rewrite_0 = selector.set();
vk8s_rewrite_0.add("/foo", string="foo", regex="^\Q/foo\E");
}
sub vcl_deliver {
if (vk8s_rewrite_0.hasprefix(req.url)) {
set resp.http.Hdr =
vk8s_rewrite_0.sub(req.url,
vk8s_rewrite_0.string(select=FIRST), select=FIRST);
}
}sub vcl_init {
new vk8s_rewrite_1 = selector.set();
vk8s_rewrite_1.add("/foo", string="foo", regex="^\Q/foo\E");
}
sub vcl_deliver {
if (vk8s_rewrite_1.hasprefix(req.url)) {
set resp.http.Hdr =
vk8s_rewrite_1.sub(req.url,
vk8s_rewrite_1.string(select=EXACT), all=true, select=EXACT);
}
}sub vcl_init {
new vk8s_rewrite_2 = re2.set();
vk8s_rewrite_2.add("/foo", string="foo", save=true);
vk8s_rewrite_2.compile();
}
sub vcl_deliver {
if (vk8s_rewrite_2.match(req.url)) {
set resp.http.Hdr =
vk8s_rewrite_2.sub(req.url,
vk8s_rewrite_2.string(select=FIRST), select=FIRST);
}
}sub vcl_init {
new vk8s_rewrite_3 = re2.set();
vk8s_rewrite_3.add("/foo", string="foo", save=true);
vk8s_rewrite_3.compile();
}
sub vcl_deliver {
if (vk8s_rewrite_3.match(req.url)) {
set resp.http.Hdr =
vk8s_rewrite_3.suball(req.url,
vk8s_rewrite_3.string(select=LAST), select=LAST);
}
}sub vcl_init {
new vk8s_rewrite_4 = re2.set();
vk8s_rewrite_4.add("/foo", string="foo", save=true);
vk8s_rewrite_4.compile();
}
sub vcl_deliver {
if (vk8s_rewrite_4.match(req.url)) {
set resp.http.Hdr =
vk8s_rewrite_4.extract(req.url,
vk8s_rewrite_4.string(select=FIRST), select=FIRST);
}
}
import re2;
import selector;
sub vcl_init {
new vk8s_rewrite_0 = selector.set();
vk8s_rewrite_0.add("/foo", string="bar");
}
sub vcl_recv {
if (vk8s_rewrite_0.hasprefix(req.url)) {
if (vk8s_rewrite_0.nmatches() != 1) {
std.log(req.url + " had " +
vk8s_rewrite_0.nmatches() + " matches");
return(fail);
}
set req.url = vk8s_rewrite_0.string();
}
}sub vcl_init {
new vk8s_rewrite_1 = selector.set();
vk8s_rewrite_1.add("/foo", string="bar");
}
sub vcl_recv {
if (vk8s_rewrite_1.hasprefix(req.url)) {
set req.url = vk8s_rewrite_1.string(select=FIRST);
}
}sub vcl_init {
new vk8s_rewrite_2 = selector.set();
vk8s_rewrite_2.add("/foo", string="bar");
}
sub vcl_recv {
if (vk8s_rewrite_2.hasprefix(req.url)) {
set req.url = vk8s_rewrite_2.string(select=LAST);
}
}sub vcl_init {
new vk8s_rewrite_3 = selector.set();
vk8s_rewrite_3.add("/foo", string="bar");
}
sub vcl_recv {
if (vk8s_rewrite_3.hasprefix(req.url)) {
set req.url = vk8s_rewrite_3.string(select=EXACT);
}
}sub vcl_init {
new vk8s_rewrite_4 = selector.set();
vk8s_rewrite_4.add("/foo", string="bar");
}
sub vcl_recv {
if (vk8s_rewrite_4.hasprefix(req.url)) {
set req.url = vk8s_rewrite_4.string(select=SHORTEST);
}
}sub vcl_init {
new vk8s_rewrite_5 = selector.set();
vk8s_rewrite_5.add("/foo", string="bar");
}
sub vcl_recv {
if (vk8s_rewrite_5.hasprefix(req.url)) {
set req.url = vk8s_rewrite_5.string(select=LONGEST);
}
}
......@@ -11,8 +11,13 @@ sub vcl_init {
sub vcl_recv {
if (vk8s_rewrite_0.match(req.url)) {
if (vk8s_rewrite_0.nmatches() != 1) {
std.log(req.url + " had " +
vk8s_rewrite_0.nmatches() + " matches");
return(fail);
}
set req.url =
vk8s_rewrite_0.sub(req.url,
vk8s_rewrite_0.string());
vk8s_rewrite_0.string());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment