Skip to content

Commit 3d5e9c7

Browse files
authored
Merge pull request #78 from Charliekenney23/feat/proxy-protocol
allow configuring proxy protocol
2 parents 6e3f585 + 4184a2f commit 3d5e9c7

File tree

9 files changed

+249
-38
lines changed

9 files changed

+249
-38
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Annotation (Suffix) | Values | Default | Description
4444
---|---|---|---
4545
`throttle` | `0`-`20` (`0` to disable) | `20` | Client Connection Throttle, which limits the number of subsequent new connections per second from the same client IP
4646
`default-protocol` | `tcp`, `http`, `https` | `tcp` | This annotation is used to specify the default protocol for Linode NodeBalancer.
47+
`proxy-protocol` | `none`, `v1`, `v2` | `none` | Specifies whether to use a version of Proxy Protocol on the underlying NodeBalancer
4748
`port-*` | json (e.g. `{ "tls-secret-name": "prod-app-tls", "protocol": "https"}`) | | Specifies the secret and protocol for a port corresponding secrets. The secret type should be `kubernetes.io/tls`. `*` is the port being configured, e.g. `linode-loadbalancer-port-443`
4849
`check-type` | `none`, `connection`, `http`, `http_body` | | The type of health check to perform against back-ends to ensure they are serving requests
4950
`check-path` | string | | The URL path to check on each back-end during health checks

cloud/linode/fake_linode_test.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"strconv"
1414
"strings"
1515
"testing"
16-
"time"
1716

1817
"github.com/linode/linodego"
1918
)
@@ -56,8 +55,6 @@ func newFake(t *testing.T) *fakeAPI {
5655
ID: 123,
5756
Status: "running",
5857
Hypervisor: "kvm",
59-
CreatedStr: "2018-01-01T00:01:01",
60-
UpdatedStr: "2018-01-01T00:01:01",
6158
IPv4: []*net.IP{
6259
&publicIP,
6360
&privateIP,
@@ -341,13 +338,11 @@ func (f *fakeAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
341338
ip := net.IPv4(byte(rand.Intn(100)), byte(rand.Intn(100)), byte(rand.Intn(100)), byte(rand.Intn(100))).String()
342339
hostname := fmt.Sprintf("nb-%s.%s.linode.com", strings.Replace(ip, ".", "-", 4), strings.ToLower(nbco.Region))
343340
nb := linodego.NodeBalancer{
344-
ID: rand.Intn(9999),
345-
Label: nbco.Label,
346-
Region: nbco.Region,
347-
IPv4: &ip,
348-
Hostname: &hostname,
349-
CreatedStr: time.Now().Format("2006-01-02T15:04:05"),
350-
UpdatedStr: time.Now().Format("2006-01-02T15:04:05"),
341+
ID: rand.Intn(9999),
342+
Label: nbco.Label,
343+
Region: nbco.Region,
344+
IPv4: &ip,
345+
Hostname: &hostname,
351346
}
352347

353348
if nbco.ClientConnThrottle != nil {
@@ -368,6 +363,7 @@ func (f *fakeAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
368363
ID: rand.Intn(9999),
369364
Port: nbcco.Port,
370365
Protocol: nbcco.Protocol,
366+
ProxyProtocol: nbcco.ProxyProtocol,
371367
Algorithm: nbcco.Algorithm,
372368
Stickiness: nbcco.Stickiness,
373369
Check: nbcco.Check,
@@ -433,6 +429,7 @@ func (f *fakeAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
433429
ID: nbcid,
434430
Port: nbcco.Port,
435431
Protocol: nbcco.Protocol,
432+
ProxyProtocol: nbcco.ProxyProtocol,
436433
Algorithm: nbcco.Algorithm,
437434
Stickiness: nbcco.Stickiness,
438435
Check: nbcco.Check,
@@ -490,6 +487,7 @@ func (f *fakeAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
490487
ID: rand.Intn(9999),
491488
Port: nbcco.Port,
492489
Protocol: nbcco.Protocol,
490+
ProxyProtocol: nbcco.ProxyProtocol,
493491
Algorithm: nbcco.Algorithm,
494492
Stickiness: nbcco.Stickiness,
495493
Check: nbcco.Check,
@@ -599,6 +597,7 @@ func (f *fakeAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
599597
ID: nbcid,
600598
Port: nbcco.Port,
601599
Protocol: nbcco.Protocol,
600+
ProxyProtocol: nbcco.ProxyProtocol,
602601
Algorithm: nbcco.Algorithm,
603602
Stickiness: nbcco.Stickiness,
604603
Check: nbcco.Check,

cloud/linode/loadbalancers.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const (
2626
// for Linode load balancers. Options are tcp, http and https. Defaults to tcp.
2727
annLinodeDefaultProtocol = "service.beta.kubernetes.io/linode-loadbalancer-default-protocol"
2828
annLinodePortConfigPrefix = "service.beta.kubernetes.io/linode-loadbalancer-port-"
29+
annLinodeProxyProtocol = "service.beta.kubernetes.io/linode-loadbalancer-proxy-protocol"
2930

3031
annLinodeCheckPath = "service.beta.kubernetes.io/linode-loadbalancer-check-path"
3132
annLinodeCheckBody = "service.beta.kubernetes.io/linode-loadbalancer-check-body"
@@ -526,6 +527,17 @@ func (l *loadbalancers) buildNodeBalancerConfig(service *v1.Service, port int) (
526527
}
527528
config.CheckPassive = checkPassive
528529

530+
proxyProtocol := linodego.ProxyProtocolNone
531+
if pp, ok := service.Annotations[annLinodeProxyProtocol]; ok {
532+
switch linodego.ConfigProxyProtocol(pp) {
533+
case linodego.ProxyProtocolNone, linodego.ProxyProtocolV1, linodego.ProxyProtocolV2:
534+
proxyProtocol = linodego.ConfigProxyProtocol(pp)
535+
default:
536+
return config, fmt.Errorf("invalid NodeBalancer proxy protocol value '%s'", pp)
537+
}
538+
}
539+
config.ProxyProtocol = proxyProtocol
540+
529541
if portConfig.Protocol == linodego.ProtocolHTTPS {
530542
if err = l.addTLSCert(service, &config, portConfig); err != nil {
531543
return config, err

cloud/linode/loadbalancers_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ func TestCCMLoadBalancers(t *testing.T) {
130130
name: "Update Load Balancer - Specify NodeBalancerID",
131131
f: testUpdateLoadBalancerAddNodeBalancerID,
132132
},
133+
{
134+
name: "Update Load Balancer - Proxy Protocol",
135+
f: testUpdateLoadBalancerAddProxyProtocol,
136+
},
133137
{
134138
name: "Build Load Balancer Request",
135139
f: testBuildLoadBalancerRequest,
@@ -498,6 +502,108 @@ func testUpdateLoadBalancerAddTLSPort(t *testing.T, client *linodego.Client, _ *
498502
}
499503
}
500504

505+
func testUpdateLoadBalancerAddProxyProtocol(t *testing.T, client *linodego.Client, fakeAPI *fakeAPI) {
506+
nodes := []*v1.Node{
507+
{
508+
Status: v1.NodeStatus{
509+
Addresses: []v1.NodeAddress{
510+
{
511+
Type: v1.NodeInternalIP,
512+
Address: "127.0.0.1",
513+
},
514+
},
515+
},
516+
},
517+
}
518+
519+
lb := &loadbalancers{client, "us-west", nil}
520+
fakeClientset := fake.NewSimpleClientset()
521+
lb.kubeClient = fakeClientset
522+
523+
for _, tc := range []struct {
524+
name string
525+
proxyProtocolConfig linodego.ConfigProxyProtocol
526+
invalidErr bool
527+
}{
528+
{
529+
name: "with invalid Proxy Protocol",
530+
proxyProtocolConfig: "bogus",
531+
invalidErr: true,
532+
},
533+
{
534+
name: "with none",
535+
proxyProtocolConfig: linodego.ProxyProtocolNone,
536+
},
537+
{
538+
name: "with v1",
539+
proxyProtocolConfig: linodego.ProxyProtocolV1,
540+
},
541+
{
542+
name: "with v2",
543+
proxyProtocolConfig: linodego.ProxyProtocolV2,
544+
},
545+
} {
546+
t.Run(tc.name, func(t *testing.T) {
547+
svc := &v1.Service{
548+
ObjectMeta: metav1.ObjectMeta{
549+
Name: randString(10),
550+
UID: "foobar123",
551+
Annotations: map[string]string{},
552+
},
553+
Spec: v1.ServiceSpec{
554+
Ports: []v1.ServicePort{
555+
{
556+
Name: randString(10),
557+
Protocol: "tcp",
558+
Port: int32(80),
559+
NodePort: int32(8080),
560+
},
561+
},
562+
},
563+
}
564+
565+
defer lb.EnsureLoadBalancerDeleted(context.TODO(), "lnodelb", svc)
566+
nodeBalancer, err := client.CreateNodeBalancer(context.TODO(), linodego.NodeBalancerCreateOptions{
567+
Region: lb.zone,
568+
})
569+
570+
if err != nil {
571+
t.Fatalf("failed to create NodeBalancer: %s", err)
572+
}
573+
574+
svc.Status.LoadBalancer = *makeLoadBalancerStatus(nodeBalancer)
575+
svc.ObjectMeta.SetAnnotations(map[string]string{
576+
annLinodeProxyProtocol: string(tc.proxyProtocolConfig),
577+
})
578+
579+
stubService(fakeClientset, svc)
580+
if err = lb.UpdateLoadBalancer(context.TODO(), "lnodelb", svc, nodes); err != nil {
581+
expectedErrMessage := fmt.Sprintf("invalid NodeBalancer proxy protocol value '%s'", tc.proxyProtocolConfig)
582+
if tc.invalidErr && err.Error() == expectedErrMessage {
583+
return
584+
}
585+
t.Fatalf("UpdateLoadBalancer returned an unexpected error while updated annotations: %s", err)
586+
return
587+
}
588+
if tc.invalidErr {
589+
t.Fatal("expected UpdateLoadBalancer to return an error")
590+
}
591+
592+
nodeBalancerConfigs, err := client.ListNodeBalancerConfigs(context.TODO(), nodeBalancer.ID, nil)
593+
if err != nil {
594+
t.Fatalf("failed to get NodeBalancer: %s", err)
595+
}
596+
597+
for _, config := range nodeBalancerConfigs {
598+
proxyProtocol := config.ProxyProtocol
599+
if proxyProtocol != tc.proxyProtocolConfig {
600+
t.Errorf("expected ProxyProtocol to be %s; got %s", tc.proxyProtocolConfig, proxyProtocol)
601+
}
602+
}
603+
})
604+
}
605+
}
606+
501607
func testUpdateLoadBalancerAddNodeBalancerID(t *testing.T, client *linodego.Client, fakeAPI *fakeAPI) {
502608
svc := &v1.Service{
503609
ObjectMeta: metav1.ObjectMeta{

e2e/go.mod

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ require (
1414
github.com/googleapis/gnostic v0.2.0 // indirect
1515
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
1616
github.com/imdario/mergo v0.3.7 // indirect
17-
github.com/linode/linodego v0.11.0
17+
github.com/kr/pretty v0.1.0 // indirect
18+
github.com/linode/linodego v0.21.1
1819
github.com/onsi/ginkgo v1.8.0
1920
github.com/onsi/gomega v1.5.0
2021
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
2122
github.com/pkg/errors v0.8.0
22-
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
23+
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
2324
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
25+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
2426
gopkg.in/inf.v0 v0.9.1 // indirect
2527
gopkg.in/resty.v1 v1.12.0 // indirect
2628
k8s.io/api v0.0.0-20180904230853-4e7be11eab3f

e2e/go.sum

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
2020
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
2121
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
2222
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
23+
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY=
24+
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
2325
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
2426
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
2527
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
@@ -28,6 +30,7 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
2830
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
2931
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
3032
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
33+
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
3134
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
3235
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
3336
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
@@ -58,6 +61,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
5861
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
5962
github.com/linode/linodego v0.11.0 h1:3RjqvGd/d93l2zmC6zMytaEXWXTPjP5IjNbYmtN3p9w=
6063
github.com/linode/linodego v0.11.0/go.mod h1:cQFzVqVu5KeFy2ZSTWTA/qVNYYa9ZY8uePJZsFG7EYs=
64+
github.com/linode/linodego v0.21.1 h1:A3Ev9vnlO3Ov6nsA3cJ0/usXdJS9eMlS8gRIjGNom/g=
65+
github.com/linode/linodego v0.21.1/go.mod h1:UTpq1JUZD0CZsJ8rt+0CRkqbzrp1MbGakVPt2DXY5Mk=
6166
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
6267
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
6368
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
@@ -85,28 +90,37 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
8590
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
8691
golang.org/x/crypto v0.0.0-20180830192347-182538f80094 h1:rVTAlhYa4+lCfNxmAIEOGQRoD23UqP72M3+rSWVGDTg=
8792
golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
93+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
94+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
8895
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
8996
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
9097
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
9198
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
9299
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
93100
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
94101
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
102+
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
103+
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
95104
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
96105
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
97106
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
107+
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
108+
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
98109
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
99110
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
100111
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
101112
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
102113
golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
103114
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
104115
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
116+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
117+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
105118
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
106119
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
107120
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
108121
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
109122
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
123+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
110124
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
111125
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
112126
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

0 commit comments

Comments
 (0)