You've already forked haproxy-ingress
mirror of
https://github.com/encounter/haproxy-ingress.git
synced 2026-03-30 11:12:55 -07:00
235 lines
7.1 KiB
Go
235 lines
7.1 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package controller
|
|
|
|
import (
|
|
"github.com/golang/glog"
|
|
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress"
|
|
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/controller"
|
|
"github.com/jcmoraisjr/haproxy-ingress/pkg/common/ingress/defaults"
|
|
"github.com/jcmoraisjr/haproxy-ingress/pkg/types"
|
|
"github.com/jcmoraisjr/haproxy-ingress/pkg/version"
|
|
"github.com/spf13/pflag"
|
|
"io/ioutil"
|
|
api "k8s.io/api/core/v1"
|
|
extensions "k8s.io/api/extensions/v1beta1"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
// HAProxyController has internal data of a HAProxyController instance
|
|
type HAProxyController struct {
|
|
controller *controller.GenericController
|
|
configMap *api.ConfigMap
|
|
storeLister *ingress.StoreLister
|
|
command string
|
|
reloadStrategy *string
|
|
configFile string
|
|
template *template
|
|
currentConfig *types.ControllerConfig
|
|
}
|
|
|
|
// NewHAProxyController constructor
|
|
func NewHAProxyController() *HAProxyController {
|
|
return &HAProxyController{}
|
|
}
|
|
|
|
// Info provides controller name and repository infos
|
|
func (haproxy *HAProxyController) Info() *ingress.BackendInfo {
|
|
return &ingress.BackendInfo{
|
|
Name: "HAProxy",
|
|
Release: version.RELEASE,
|
|
Build: version.COMMIT,
|
|
Repository: version.REPO,
|
|
}
|
|
}
|
|
|
|
// Start starts the controller
|
|
func (haproxy *HAProxyController) Start() {
|
|
haproxy.controller = controller.NewIngressController(haproxy)
|
|
haproxy.controller.Start()
|
|
}
|
|
|
|
// Stop shutdown the controller process
|
|
func (haproxy *HAProxyController) Stop() error {
|
|
err := haproxy.controller.Stop()
|
|
return err
|
|
}
|
|
|
|
// Name provides the complete name of the controller
|
|
func (haproxy *HAProxyController) Name() string {
|
|
return "HAProxy Ingress Controller"
|
|
}
|
|
|
|
// DefaultIngressClass returns the ingress class name
|
|
func (haproxy *HAProxyController) DefaultIngressClass() string {
|
|
return "haproxy"
|
|
}
|
|
|
|
// Check health check implementation
|
|
func (haproxy *HAProxyController) Check(_ *http.Request) error {
|
|
return nil
|
|
}
|
|
|
|
// SetListers give access to the store listers
|
|
func (haproxy *HAProxyController) SetListers(lister *ingress.StoreLister) {
|
|
haproxy.storeLister = lister
|
|
}
|
|
|
|
// UpdateIngressStatus custom callback used to update the status in an Ingress rule
|
|
// If the function returns nil the standard functions will be executed.
|
|
func (haproxy *HAProxyController) UpdateIngressStatus(*extensions.Ingress) []api.LoadBalancerIngress {
|
|
return nil
|
|
}
|
|
|
|
// ConfigureFlags allow to configure more flags before the parsing of
|
|
// command line arguments
|
|
func (haproxy *HAProxyController) ConfigureFlags(flags *pflag.FlagSet) {
|
|
haproxy.reloadStrategy = flags.String("reload-strategy", "native",
|
|
`Name of the reload strategy. Options are: native (default) or multibinder`)
|
|
ingressClass := flags.Lookup("ingress-class")
|
|
if ingressClass != nil {
|
|
ingressClass.Value.Set("haproxy")
|
|
ingressClass.DefValue = "haproxy"
|
|
}
|
|
}
|
|
|
|
// OverrideFlags allows controller to override command line parameter flags
|
|
func (haproxy *HAProxyController) OverrideFlags(flags *pflag.FlagSet) {
|
|
if *haproxy.reloadStrategy == "native" {
|
|
haproxy.configFile = "/etc/haproxy/haproxy.cfg"
|
|
haproxy.template = newTemplate("haproxy.tmpl", "/etc/haproxy/template/haproxy.tmpl")
|
|
} else if *haproxy.reloadStrategy == "multibinder" {
|
|
haproxy.configFile = "/etc/haproxy/haproxy.cfg.erb"
|
|
haproxy.template = newTemplate("haproxy.cfg.erb.tmpl", "/etc/haproxy/haproxy.cfg.erb.tmpl")
|
|
} else {
|
|
glog.Fatalf("Unsupported reload strategy: %v", *haproxy.reloadStrategy)
|
|
}
|
|
haproxy.command = "/haproxy-reload.sh"
|
|
}
|
|
|
|
// SetConfig receives the ConfigMap the user has configured
|
|
func (haproxy *HAProxyController) SetConfig(configMap *api.ConfigMap) {
|
|
haproxy.configMap = configMap
|
|
}
|
|
|
|
// BackendDefaults defines default values to the ingress core
|
|
func (haproxy *HAProxyController) BackendDefaults() defaults.Backend {
|
|
return newHAProxyConfig(haproxy).Backend
|
|
}
|
|
|
|
// DefaultEndpoint returns the Endpoint to use as default when the
|
|
// referenced service does not exists
|
|
func (haproxy *HAProxyController) DefaultEndpoint() ingress.Endpoint {
|
|
return ingress.Endpoint{
|
|
Address: "127.0.0.1",
|
|
Port: "8181",
|
|
Target: &api.ObjectReference{},
|
|
}
|
|
}
|
|
|
|
// OnUpdate regenerate the configuration file of the backend
|
|
func (haproxy *HAProxyController) OnUpdate(cfg ingress.Configuration) error {
|
|
updatedConfig := newControllerConfig(&cfg, haproxy)
|
|
|
|
reloadRequired := reconfigureBackends(haproxy.currentConfig, updatedConfig)
|
|
haproxy.currentConfig = updatedConfig
|
|
|
|
data, err := haproxy.template.execute(updatedConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = rewriteConfigFiles(data, *haproxy.reloadStrategy, haproxy.configFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !reloadRequired {
|
|
glog.Infoln("HAProxy updated through socket, reload not required")
|
|
return nil
|
|
}
|
|
|
|
out, err := haproxy.reloadHaproxy()
|
|
if len(out) > 0 {
|
|
glog.Infof("HAProxy output:\n%v", string(out))
|
|
}
|
|
return err
|
|
}
|
|
|
|
// RewriteConfigFiles safely replaces configuration files with new contents after validation
|
|
func rewriteConfigFiles(data []byte, reloadStrategy, configFile string) error {
|
|
tmpf := "/etc/haproxy/new_cfg.erb"
|
|
|
|
err := ioutil.WriteFile(tmpf, data, 644)
|
|
if err != nil {
|
|
glog.Warningln("Error writing rendered template to file")
|
|
return err
|
|
}
|
|
|
|
if reloadStrategy == "multibinder" {
|
|
generated, err := multibinderERBOnly(tmpf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = os.Rename(generated, "/etc/haproxy/haproxy.cfg")
|
|
if err != nil {
|
|
glog.Warningln("Error updating config file")
|
|
return err
|
|
}
|
|
} else {
|
|
err = checkValidity(tmpf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err = os.Rename(tmpf, configFile)
|
|
if err != nil {
|
|
glog.Warningln("Error updating config file")
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// multibinderERBOnly generates a config file from ERB template by invoking multibinder-haproxy-erb
|
|
func multibinderERBOnly(configFile string) (string, error) {
|
|
out, err := exec.Command("multibinder-haproxy-erb", "/usr/local/sbin/haproxy", "-f", configFile, "-c", "-q").CombinedOutput()
|
|
if err != nil {
|
|
glog.Warningf("Error validating config file:\n%v", string(out))
|
|
return "", err
|
|
}
|
|
return configFile[:strings.LastIndex(configFile, ".erb")], nil
|
|
}
|
|
|
|
// checkValidity runs a HAProxy configuration validity check on a file
|
|
func checkValidity(configFile string) error {
|
|
out, err := exec.Command("haproxy", "-c", "-f", configFile).CombinedOutput()
|
|
if err != nil {
|
|
glog.Warningf("Error validating config file:\n%v", string(out))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (haproxy *HAProxyController) reloadHaproxy() ([]byte, error) {
|
|
out, err := exec.Command(haproxy.command, *haproxy.reloadStrategy, haproxy.configFile).CombinedOutput()
|
|
return out, err
|
|
}
|