Skip to content

Commit 2c13a84

Browse files
cgorbitAleksandr Grishutin
authored and
Aleksandr Grishutin
committed
Rewrite clean-nginx-conf.sh in Go to speed up admission webhook (kubernetes#7076)
* Rewrite clean-nginx-conf.sh to speed up admission webhook * Less diff with original clean-nginx-conf.sh * Add error handling, add documentation, add unit test * indent code * Don't ignore Getwd() error
1 parent 8fc040c commit 2c13a84

File tree

8 files changed

+462
-60
lines changed

8 files changed

+462
-60
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/onsi/ginkgo v1.16.4
2121
github.com/opencontainers/runc v1.0.0-rc92
2222
github.com/pkg/errors v0.9.1
23+
github.com/pmezard/go-difflib v1.0.0
2324
github.com/prometheus/client_golang v1.7.1
2425
github.com/prometheus/client_model v0.2.0
2526
github.com/prometheus/common v0.14.0

internal/ingress/controller/template/template.go

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import (
2323
"encoding/hex"
2424
"encoding/json"
2525
"fmt"
26+
"io"
2627
"io/ioutil"
2728
"math/rand" // #nosec
2829
"net"
2930
"net/url"
3031
"os"
31-
"os/exec"
3232
"reflect"
3333
"regexp"
3434
"sort"
@@ -50,9 +50,15 @@ import (
5050
)
5151

5252
const (
53-
slash = "/"
54-
nonIdempotent = "non_idempotent"
55-
defBufferSize = 65535
53+
slash = "/"
54+
nonIdempotent = "non_idempotent"
55+
defBufferSize = 65535
56+
writeIndentOnEmptyLines = true // backward-compatibility
57+
)
58+
59+
const (
60+
stateCode = iota
61+
stateComment
5662
)
5763

5864
// TemplateWriter is the interface to render a template
@@ -86,6 +92,87 @@ func NewTemplate(file string) (*Template, error) {
8692
}, nil
8793
}
8894

95+
// 1. Removes carriage return symbol (\r)
96+
// 2. Collapses multiple empty lines to single one
97+
// 3. Re-indent
98+
// (ATW: always returns nil)
99+
func cleanConf(in *bytes.Buffer, out *bytes.Buffer) error {
100+
depth := 0
101+
lineStarted := false
102+
emptyLineWritten := false
103+
state := stateCode
104+
for {
105+
c, err := in.ReadByte()
106+
if err != nil {
107+
if err == io.EOF {
108+
return nil
109+
}
110+
return err // unreachable
111+
}
112+
113+
needOutput := false
114+
nextDepth := depth
115+
nextLineStarted := lineStarted
116+
117+
switch state {
118+
case stateCode:
119+
switch c {
120+
case '{':
121+
needOutput = true
122+
nextDepth = depth + 1
123+
nextLineStarted = true
124+
case '}':
125+
needOutput = true
126+
depth--
127+
nextDepth = depth
128+
nextLineStarted = true
129+
case ' ', '\t':
130+
needOutput = lineStarted
131+
case '\r':
132+
case '\n':
133+
needOutput = !(!lineStarted && emptyLineWritten)
134+
nextLineStarted = false
135+
case '#':
136+
needOutput = true
137+
nextLineStarted = true
138+
state = stateComment
139+
default:
140+
needOutput = true
141+
nextLineStarted = true
142+
}
143+
case stateComment:
144+
switch c {
145+
case '\r':
146+
case '\n':
147+
needOutput = true
148+
nextLineStarted = false
149+
state = stateCode
150+
default:
151+
needOutput = true
152+
}
153+
}
154+
155+
if needOutput {
156+
if !lineStarted && (writeIndentOnEmptyLines || c != '\n') {
157+
for i := 0; i < depth; i++ {
158+
err = out.WriteByte('\t') // always nil
159+
if err != nil {
160+
return err
161+
}
162+
}
163+
}
164+
emptyLineWritten = !lineStarted
165+
err = out.WriteByte(c) // always nil
166+
if err != nil {
167+
return err
168+
}
169+
}
170+
171+
depth = nextDepth
172+
lineStarted = nextLineStarted
173+
}
174+
}
175+
89176
// Write populates a buffer using a template with NGINX configuration
90177
// and the servers and upstreams created by Ingress rules
91178
func (t *Template) Write(conf config.TemplateConfig) ([]byte, error) {
@@ -110,12 +197,9 @@ func (t *Template) Write(conf config.TemplateConfig) ([]byte, error) {
110197

111198
// squeezes multiple adjacent empty lines to be single
112199
// spaced this is to avoid the use of regular expressions
113-
cmd := exec.Command("/ingress-controller/clean-nginx-conf.sh")
114-
cmd.Stdin = tmplBuf
115-
cmd.Stdout = outCmdBuf
116-
if err := cmd.Run(); err != nil {
117-
klog.Warningf("unexpected error cleaning template: %v", err)
118-
return tmplBuf.Bytes(), nil
200+
err = cleanConf(tmplBuf, outCmdBuf)
201+
if err != nil {
202+
return nil, err
119203
}
120204

121205
return outCmdBuf.Bytes(), nil

internal/ingress/controller/template/template_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package template
1818

1919
import (
20+
"bytes"
2021
"encoding/base64"
2122
"fmt"
2223
"io/ioutil"
@@ -29,6 +30,7 @@ import (
2930
"testing"
3031

3132
jsoniter "github.com/json-iterator/go"
33+
"github.com/pmezard/go-difflib/difflib"
3234
apiv1 "k8s.io/api/core/v1"
3335
networking "k8s.io/api/networking/v1beta1"
3436
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -178,6 +180,14 @@ proxy_pass http://upstream_balancer;`,
178180
}
179181
)
180182

183+
func getTestDataDir() (string, error) {
184+
pwd, err := os.Getwd()
185+
if err != nil {
186+
return "", err
187+
}
188+
return path.Join(pwd, "../../../../test/data"), nil
189+
}
190+
181191
func TestBuildLuaSharedDictionaries(t *testing.T) {
182192
invalidType := &ingress.Ingress{}
183193
expected := ""
@@ -1576,3 +1586,34 @@ func TestConvertGoSliceIntoLuaTablet(t *testing.T) {
15761586
}
15771587
}
15781588
}
1589+
1590+
func TestCleanConf(t *testing.T) {
1591+
testDataDir, err := getTestDataDir()
1592+
if err != nil {
1593+
t.Error("unexpected error reading conf file: ", err)
1594+
}
1595+
actual := &bytes.Buffer{}
1596+
{
1597+
data, err := ioutil.ReadFile(testDataDir + "/cleanConf.src.conf")
1598+
if err != nil {
1599+
t.Error("unexpected error reading conf file: ", err)
1600+
}
1601+
in := bytes.NewBuffer(data)
1602+
err = cleanConf(in, actual)
1603+
if err != nil {
1604+
t.Error("cleanConf failed: ", err)
1605+
}
1606+
}
1607+
1608+
expected, err := ioutil.ReadFile(testDataDir + "/cleanConf.expected.conf")
1609+
if err != nil {
1610+
t.Error("unexpected error reading conf file: ", err)
1611+
}
1612+
if !bytes.Equal(expected, actual.Bytes()) {
1613+
diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{A: strings.SplitAfter(string(expected), "\n"), B: strings.SplitAfter(actual.String(), "\n"), Context: 3})
1614+
if err != nil {
1615+
t.Error("failed to get diff for cleanConf", err)
1616+
}
1617+
t.Errorf("cleanConf result don't match with expected: %s", diff)
1618+
}
1619+
}

rootfs/Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ RUN apk update \
4040
&& rm -rf /var/cache/apk/*
4141

4242
COPY --chown=www-data:www-data etc /etc
43-
COPY --chown=www-data:www-data ingress-controller /ingress-controller
4443

4544
COPY --chown=www-data:www-data bin/${TARGETARCH}/dbg /
4645
COPY --chown=www-data:www-data bin/${TARGETARCH}/nginx-ingress-controller /

rootfs/ingress-controller/clean-nginx-conf.sh

Lines changed: 0 additions & 27 deletions
This file was deleted.

rootfs/ingress-controller/indent.sh

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)