Skip to content

Commit 8328b53

Browse files
authored
Rewrite clean-nginx-conf.sh in Go to speed up admission webhook (#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 24bb739 commit 8328b53

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.14.1
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"
@@ -51,9 +51,15 @@ import (
5151
)
5252

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

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

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

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

122206
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/v1"
3436
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -179,6 +181,14 @@ proxy_pass http://upstream_balancer;`,
179181
}
180182
)
181183

184+
func getTestDataDir() (string, error) {
185+
pwd, err := os.Getwd()
186+
if err != nil {
187+
return "", err
188+
}
189+
return path.Join(pwd, "../../../../test/data"), nil
190+
}
191+
182192
func TestBuildLuaSharedDictionaries(t *testing.T) {
183193
invalidType := &ingress.Ingress{}
184194
expected := ""
@@ -1589,3 +1599,34 @@ func TestConvertGoSliceIntoLuaTablet(t *testing.T) {
15891599
}
15901600
}
15911601
}
1602+
1603+
func TestCleanConf(t *testing.T) {
1604+
testDataDir, err := getTestDataDir()
1605+
if err != nil {
1606+
t.Error("unexpected error reading conf file: ", err)
1607+
}
1608+
actual := &bytes.Buffer{}
1609+
{
1610+
data, err := ioutil.ReadFile(testDataDir + "/cleanConf.src.conf")
1611+
if err != nil {
1612+
t.Error("unexpected error reading conf file: ", err)
1613+
}
1614+
in := bytes.NewBuffer(data)
1615+
err = cleanConf(in, actual)
1616+
if err != nil {
1617+
t.Error("cleanConf failed: ", err)
1618+
}
1619+
}
1620+
1621+
expected, err := ioutil.ReadFile(testDataDir + "/cleanConf.expected.conf")
1622+
if err != nil {
1623+
t.Error("unexpected error reading conf file: ", err)
1624+
}
1625+
if !bytes.Equal(expected, actual.Bytes()) {
1626+
diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{A: strings.SplitAfter(string(expected), "\n"), B: strings.SplitAfter(actual.String(), "\n"), Context: 3})
1627+
if err != nil {
1628+
t.Error("failed to get diff for cleanConf", err)
1629+
}
1630+
t.Errorf("cleanConf result don't match with expected: %s", diff)
1631+
}
1632+
}

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)