Skip to content

Commit

Permalink
Add the ability to have Nginx version checks in templates (#4831)
Browse files Browse the repository at this point in the history
Add the ability to add version dependent template elements
  • Loading branch information
oseoin authored Dec 21, 2023
1 parent 27ec53d commit 95f8da6
Show file tree
Hide file tree
Showing 11 changed files with 266 additions and 14 deletions.
17 changes: 9 additions & 8 deletions cmd/nginx-ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func main() {
appProtectVersion = getAppProtectVersionInfo()
}

updateSelfWithVersionInfo(kubeClient, version, nginxVersion, appProtectVersion)
updateSelfWithVersionInfo(kubeClient, version, nginxVersion.String(), appProtectVersion)

templateExecutor, templateExecutorV2 := createTemplateExecutors()

Expand Down Expand Up @@ -118,6 +118,7 @@ func main() {
EnableCertManager: *enableCertManager,
DynamicSSLReload: *enableDynamicSSLReload,
StaticSSLPath: nginxManager.GetSecretsDir(),
NginxVersion: nginxVersion,
}

processNginxConfig(staticCfgParams, cfgParams, templateExecutor, nginxManager)
Expand Down Expand Up @@ -146,6 +147,7 @@ func main() {
IsPrometheusEnabled: *enablePrometheusMetrics,
IsLatencyMetricsEnabled: *enableLatencyMetrics,
IsDynamicSSLReloadEnabled: *enableDynamicSSLReload,
NginxVersion: nginxVersion,
})

controllerNamespace := os.Getenv("POD_NAMESPACE")
Expand Down Expand Up @@ -400,17 +402,16 @@ func createNginxManager(managerCollector collectors.ManagerCollector) (nginx.Man
return nginxManager, useFakeNginxManager
}

func getNginxVersionInfo(nginxManager nginx.Manager) string {
nginxVersion := nginxManager.Version()
isPlus := strings.Contains(nginxVersion, "plus")
glog.Infof("Using %s", nginxVersion)
func getNginxVersionInfo(nginxManager nginx.Manager) nginx.Version {
nginxInfo := nginxManager.Version()
glog.Infof("Using %s", nginxInfo.String())

if *nginxPlus && !isPlus {
if *nginxPlus && !nginxInfo.IsPlus {
glog.Fatal("NGINX Plus flag enabled (-nginx-plus) without NGINX Plus binary")
} else if !*nginxPlus && isPlus {
} else if !*nginxPlus && nginxInfo.IsPlus {
glog.Fatal("NGINX Plus binary found without NGINX Plus flag (-nginx-plus)")
}
return nginxVersion
return nginxInfo
}

func getAppProtectVersionInfo() string {
Expand Down
6 changes: 5 additions & 1 deletion internal/configs/config_params.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package configs

import conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1"
import (
"github.com/nginxinc/kubernetes-ingress/internal/nginx"
conf_v1 "github.com/nginxinc/kubernetes-ingress/pkg/apis/configuration/v1"
)

// ConfigParams holds NGINX configuration parameters that affect the main NGINX config
// as well as configs for Ingress resources.
Expand Down Expand Up @@ -136,6 +139,7 @@ type StaticConfigParams struct {
EnableCertManager bool
DynamicSSLReload bool
StaticSSLPath string
NginxVersion nginx.Version
}

// GlobalConfigParams holds global configuration parameters. For now, it only holds listeners.
Expand Down
1 change: 1 addition & 0 deletions internal/configs/configmaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ func GenerateNginxMainConfig(staticCfgParams *StaticConfigParams, config *Config
OIDC: staticCfgParams.EnableOIDC,
DynamicSSLReloadEnabled: staticCfgParams.DynamicSSLReload,
StaticSSLPath: staticCfgParams.StaticSSLPath,
NginxVersion: staticCfgParams.NginxVersion,
}
return nginxCfg
}
1 change: 1 addition & 0 deletions internal/configs/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ type ConfiguratorParams struct {
IsWildcardEnabled bool
IsLatencyMetricsEnabled bool
IsDynamicSSLReloadEnabled bool
NginxVersion nginx.Version
}

// NewConfigurator creates a new Configurator.
Expand Down
2 changes: 2 additions & 0 deletions internal/configs/configurator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func createTestStaticConfigParams() *StaticConfigParams {
NginxStatusAllowCIDRs: []string{"127.0.0.1"},
NginxStatusPort: 8080,
StubStatusOverUnixSocketForOSS: false,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}
}

Expand Down Expand Up @@ -53,6 +54,7 @@ func createTestConfigurator(t *testing.T) *Configurator {
IsWildcardEnabled: false,
IsPrometheusEnabled: false,
IsLatencyMetricsEnabled: false,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
})
cnf.isReloadsEnabled = true
return cnf
Expand Down
3 changes: 3 additions & 0 deletions internal/configs/version1/config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package version1

import "github.com/nginxinc/kubernetes-ingress/internal/nginx"

// UpstreamLabels describes the Prometheus labels for an NGINX upstream.
type UpstreamLabels struct {
Service string
Expand Down Expand Up @@ -234,6 +236,7 @@ type MainConfig struct {
OIDC bool
DynamicSSLReloadEnabled bool
StaticSSLPath string
NginxVersion nginx.Version
}

// NewUpstreamWithDefaultServer creates an upstream with the default server.
Expand Down
6 changes: 6 additions & 0 deletions internal/configs/version1/nginx-plus.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,9 @@ stream {

include /etc/nginx/stream-conf.d/*.conf;
}

{{- if (.NginxVersion.PlusGreaterThanOrEqualTo "nginx-plus-r31") }}
mgmt {
usage_report interval=0s;
}
{{- end}}
49 changes: 49 additions & 0 deletions internal/configs/version1/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"strings"
"testing"
"text/template"

"github.com/nginxinc/kubernetes-ingress/internal/nginx"
)

func TestExecuteMainTemplateForNGINXPlus(t *testing.T) {
Expand All @@ -20,6 +22,19 @@ func TestExecuteMainTemplateForNGINXPlus(t *testing.T) {
t.Log(buf.String())
}

func TestExecuteMainTemplateForNGINXPlusR31(t *testing.T) {
t.Parallel()

tmpl := newNGINXPlusMainTmpl(t)
buf := &bytes.Buffer{}

err := tmpl.Execute(buf, mainCfgR31)
if err != nil {
t.Error(err)
}
t.Log(buf.String())
}

func TestExecuteMainTemplateForNGINX(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -1291,6 +1306,33 @@ var (
KeepaliveRequests: 100,
VariablesHashBucketSize: 256,
VariablesHashMaxSize: 1024,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}

mainCfgR31 = MainConfig{
DefaultHTTPListenerPort: 80,
DefaultHTTPSListenerPort: 443,
ServerNamesHashMaxSize: "512",
ServerTokens: "off",
WorkerProcesses: "auto",
WorkerCPUAffinity: "auto",
WorkerShutdownTimeout: "1m",
WorkerConnections: "1024",
WorkerRlimitNofile: "65536",
LogFormat: []string{"$remote_addr", "$remote_user"},
LogFormatEscaping: "default",
StreamSnippets: []string{"# comment"},
StreamLogFormat: []string{"$remote_addr", "$remote_user"},
StreamLogFormatEscaping: "none",
ResolverAddresses: []string{"example.com", "127.0.0.1"},
ResolverIPV6: false,
ResolverValid: "10s",
ResolverTimeout: "15s",
KeepaliveTimeout: "65s",
KeepaliveRequests: 100,
VariablesHashBucketSize: 256,
VariablesHashMaxSize: 1024,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}

mainCfgHTTP2On = MainConfig{
Expand All @@ -1317,6 +1359,7 @@ var (
KeepaliveRequests: 100,
VariablesHashBucketSize: 256,
VariablesHashMaxSize: 1024,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}

mainCfgCustomTLSPassthroughPort = MainConfig{
Expand All @@ -1342,6 +1385,7 @@ var (
VariablesHashMaxSize: 1024,
TLSPassthrough: true,
TLSPassthroughPort: 8443,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}

mainCfgWithoutTLSPassthrough = MainConfig{
Expand All @@ -1367,6 +1411,7 @@ var (
VariablesHashMaxSize: 1024,
TLSPassthrough: false,
TLSPassthroughPort: 8443,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}

mainCfgDefaultTLSPassthroughPort = MainConfig{
Expand All @@ -1392,6 +1437,7 @@ var (
VariablesHashMaxSize: 1024,
TLSPassthrough: true,
TLSPassthroughPort: 443,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}

mainCfgCustomDefaultHTTPAndHTTPSListenerPorts = MainConfig{
Expand All @@ -1417,6 +1463,7 @@ var (
KeepaliveRequests: 100,
VariablesHashBucketSize: 256,
VariablesHashMaxSize: 1024,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}

mainCfgCustomDefaultHTTPListenerPort = MainConfig{
Expand All @@ -1442,6 +1489,7 @@ var (
KeepaliveRequests: 100,
VariablesHashBucketSize: 256,
VariablesHashMaxSize: 1024,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}

mainCfgCustomDefaultHTTPSListenerPort = MainConfig{
Expand All @@ -1467,6 +1515,7 @@ var (
KeepaliveRequests: 100,
VariablesHashBucketSize: 256,
VariablesHashMaxSize: 1024,
NginxVersion: nginx.NewVersion("nginx version: nginx/1.25.3 (nginx-plus-r31)"),
}

// Vars for Mergable Ingress Master - Minion tests
Expand Down
4 changes: 2 additions & 2 deletions internal/nginx/fake_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ func (fm *FakeManager) CreateDHParam(_ string) (string, error) {
}

// Version provides a fake implementation of Version.
func (*FakeManager) Version() string {
func (*FakeManager) Version() Version {
glog.V(3).Info("Printing nginx version")
return "fake version plus"
return Version{}
}

// Start provides a fake implementation of Start.
Expand Down
97 changes: 94 additions & 3 deletions internal/nginx/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -46,6 +47,19 @@ const (
appProtectDosAgentStartDebugCmd = "/usr/bin/admd -d --standalone --log debug"
)

var (
re = regexp.MustCompile(`(?P<name>\S+)/(?P<version>\S+)`)
plusre = regexp.MustCompile(`(?P<name>\S+)/(?P<version>\S+).\((?P<plus>\S+plus\S+)\)`)
)

// Version holds the parsed output from `nginx -v`
type Version struct {
raw string
OSS string
IsPlus bool
Plus string
}

// ServerConfig holds the config data for an upstream server in NGINX Plus.
type ServerConfig struct {
MaxFails int
Expand All @@ -72,7 +86,7 @@ type Manager interface {
CreateDHParam(content string) (string, error)
CreateOpenTracingTracerConfig(content string) error
Start(done chan error)
Version() string
Version() Version
Reload(isEndpointsUpdate bool) error
Quit()
UpdateConfigVersionFile(openTracing bool)
Expand Down Expand Up @@ -334,13 +348,13 @@ func (lm *LocalManager) Quit() {
}

// Version returns NGINX version
func (lm *LocalManager) Version() string {
func (lm *LocalManager) Version() Version {
binaryFilename := getBinaryFileName(lm.debug)
out, err := exec.Command(binaryFilename, "-v").CombinedOutput()
if err != nil {
glog.Fatalf("Failed to get nginx version: %v", err)
}
return string(out)
return NewVersion(string(out))
}

// UpdateConfigVersionFile writes the config version file.
Expand Down Expand Up @@ -432,6 +446,83 @@ func (lm *LocalManager) CreateOpenTracingTracerConfig(content string) error {
return nil
}

// Return the raw Nginx version string from `nginx -v`
func (v *Version) String() string {
return v.raw
}

// PlusGreaterThanOrEqualTo compares the supplied nginx-plus version string with the Version{} struct
func (v Version) PlusGreaterThanOrEqualTo(target string) (bool, error) {
r, p, err := extractPlusVersionValues(v.String())
if err != nil {
return false, err
}
tr, tp, err := extractPlusVersionValues(target)
if err != nil {
return false, err
}

return (r > tr || (r == tr && p >= tp)), nil
}

// NewVersion will take the output from `nginx -v` and explodes it into the `nginx.Version` struct
func NewVersion(line string) Version {
matches := re.FindStringSubmatch(line)
plusmatches := plusre.FindStringSubmatch(line)
nv := Version{
raw: line,
}

if len(plusmatches) > 0 {
subNames := plusre.SubexpNames()
nv.IsPlus = true
for i, v := range plusmatches {
switch subNames[i] {
case "plus":
nv.Plus = v
case "version":
nv.OSS = v
}
}
}

if len(matches) > 0 {
for i, key := range re.SubexpNames() {
val := matches[i]
if key == "version" {
nv.OSS = val
}
}
}

return nv
}

// extractPlusVersionValues
func extractPlusVersionValues(input string) (int, int, error) {
var rValue, pValue int
re := regexp.MustCompile(`-r(\d+)(?:-p(\d+))?`)
matches := re.FindStringSubmatch(input)

if len(matches) < 2 {
return 0, 0, fmt.Errorf("no matches found in the input string")
}

rValue, err := strconv.Atoi(matches[1])
if err != nil {
return 0, 0, fmt.Errorf("failed to convert rValue to integer: %w", err)
}

if len(matches) > 2 && len(matches[2]) > 0 {
pValue, err = strconv.Atoi(matches[2])
if err != nil {
return 0, 0, fmt.Errorf("failed to convert pValue to integer: %w", err)
}
}

return rValue, pValue, nil
}

// verifyConfigVersion is used to check if the worker process that the API client is connected
// to is using the latest version of nginx config. This way we avoid making changes on
// a worker processes that is being shut down.
Expand Down
Loading

0 comments on commit 95f8da6

Please sign in to comment.