Repository: zalando-incubator/kube-ingress-aws-controller
Pull request #285 (2019-10-31 16:40:54)

Switch to kingpin as flag lib for simpler validation
Switch to kingpin as flag lib for simpler validation

Fix: #283

Also changed from variable names and removed unused flag `cf-custom-template`.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/26680658/badge)](https://coveralls.io/builds/26680658)

Coverage increased (+0.5%) to 49.286% when pulling **518f5f83d7127143f4fd00164b2be1f3a1449ce7 on kingpin-flags** into **bc2bb97437854ad7aa0f2854dc5e8d4b269bad17 on master**.

szuecs: :+1:
mikkeloscar: :+1: 

Patch:
diff --git a/README.md b/README.md
index 68e3d58..75d7d9f 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,16 @@ This information is used to manage AWS resources for each ingress objects of the
 
 ## Upgrade
 
+### <0.9.0 to >=v0.9.0
+
+Version `v0.9.0` changes the internal flag parsing library to
+[kingpin][kingpin] this means flags are now defined with `--` (two dashes)
+instead of a single dash. You need to change all the flags like this:
+`-stack-termination-protection` -> `--stack-termination-protection` before
+running `v0.9.0` of the controller.
+
+[kingpin]: https://github.com/alecthomas/kingpin
+
 ### <v0.8.0 to >=v0.8.0
 
 Version `v0.8.0` added certificate verification check to automatically ignore
@@ -104,7 +114,7 @@ SecurityGroup auto detection needs the following AWS Tags on the
 SecurityGroup:
 - `kubernetes.io/cluster/<cluster-id>=owned`
 - `kubernetes:application=<controller-id>`, controller-id defaults to
-`kube-ingress-aws-controller` and can be set by flag `-controller-id=<my-ctrl-id>`.
+`kube-ingress-aws-controller` and can be set by flag `--controller-id=<my-ctrl-id>`.
 
 AutoScalingGroup auto detection needs the same AWS tags  on the
 AutoScalingGroup as defined for the SecurityGroup.
@@ -225,7 +235,7 @@ latter will use the automatically selected certificates.
 
 By default the ingress-controller will aggregate all ingresses under as few
 Application Load Balancers as possible (unless running with
-`-disable-sni-support`). If you like to provision an Application Load Balancer
+`--disable-sni-support`). If you like to provision an Application Load Balancer
 that is unique for an ingress you can use the annotation
 `zalando.org/aws-load-balancer-shared: "false"`.
 
@@ -286,7 +296,7 @@ You can only select from `internet-facing` (default) and `internal` options.
 
 You can select the default
 [SSLPolicy](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies),
-with the flag `-ssl-policy=ELBSecurityPolicy-TLS-1-2-2017-01`. This
+with the flag `--ssl-policy=ELBSecurityPolicy-TLS-1-2-2017-01`. This
 choice can be overriden by the Kubernetes Ingress annotation
 `zalando.org/aws-load-balancer-ssl-policy` to any valid value. Valid
 values will be checked by the controller.
@@ -386,7 +396,7 @@ In some cases it might be useful to run multiple instances of this controller:
 * Using a different set of traffic processing nodes
 * Using different frontend routers (e.g.: Skipper and Traefik)
 
-You can use the flag `-controller-id` to set a token that will be used to isolate resources between controller instances.
+You can use the flag `--controller-id` to set a token that will be used to isolate resources between controller instances.
 This value will be used to tag those resources.
 
 If you don't pass an ID, the default `kube-ingress-aws-controller` will be used.
@@ -408,8 +418,8 @@ By default, the controller will expose both HTTP and HTTPS ports on the load bal
 
 ### Backward Compatibility
 
-The controller used to have only the `-health-check-port` flag available, and would use the same port as health check and the target port.
-Those ports are now configured individually. If you relied on this behavior, please include the `-target-port` in your configuration.
+The controller used to have only the `--health-check-port` flag available, and would use the same port as health check and the target port.
+Those ports are now configured individually. If you relied on this behavior, please include the `--target-port` in your configuration.
 
 ## Trying it out
 
diff --git a/aws/adapter.go b/aws/adapter.go
index 003f180..eea0b3c 100644
--- a/aws/adapter.go
+++ b/aws/adapter.go
@@ -37,7 +37,6 @@ type Adapter struct {
 	iam            iamiface.IAMAPI
 	cloudformation cloudformationiface.CloudFormationAPI
 
-	customTemplate             string
 	manifest                   *manifest
 	healthCheckPath            string
 	healthCheckPort            uint
@@ -57,7 +56,7 @@ type Adapter struct {
 	albLogsS3Bucket            string
 	albLogsS3Prefix            string
 	wafWebAclId                string
-	httpRedirectToHttps        bool
+	httpRedirectToHTTPS        bool
 	nlbCrossZone               bool
 	customFilter               string
 }
@@ -136,6 +135,15 @@ var (
 		"ELBSecurityPolicy-2015-05":             true,
 		"ELBSecurityPolicy-TLS-1-0-2015-04":     true,
 	}
+	SSLPoliciesList = []string{
+		"ELBSecurityPolicy-2016-08",
+		"ELBSecurityPolicy-FS-2018-06",
+		"ELBSecurityPolicy-TLS-1-2-2017-01",
+		"ELBSecurityPolicy-TLS-1-2-Ext-2018-06",
+		"ELBSecurityPolicy-TLS-1-1-2017-01",
+		"ELBSecurityPolicy-2015-05",
+		"ELBSecurityPolicy-TLS-1-0-2015-04",
+	}
 )
 
 var configProvider = defaultConfigProvider
@@ -247,13 +255,6 @@ func (a *Adapter) WithIdleConnectionTimeout(interval time.Duration) *Adapter {
 	return a
 }
 
-// WithCustomTemplate returns the receiver adapter after changing the CloudFormation template that should be used
-// to create Load Balancer stacks
-func (a *Adapter) WithCustomTemplate(template string) *Adapter {
-	a.customTemplate = template
-	return a
-}
-
 // WithControllerID returns the receiver adapter after changing the CloudFormation template that should be used
 // to create Load Balancer stacks
 func (a *Adapter) WithControllerID(id string) *Adapter {
@@ -301,9 +302,9 @@ func (a *Adapter) WithWafWebAclId(wafWebAclId string) *Adapter {
 	return a
 }
 
-// WithHttpRedirectToHttps returns the receiver adapter after changing the flag to effect HTTP->HTTPS redirection
-func (a *Adapter) WithHttpRedirectToHttps(httpRedirectToHttps bool) *Adapter {
-	a.httpRedirectToHttps = httpRedirectToHttps
+// WithHTTPRedirectToHTTPS returns the receiver adapter after changing the flag to effect HTTP->HTTPS redirection
+func (a *Adapter) WithHTTPRedirectToHTTPS(httpRedirectToHTTPS bool) *Adapter {
+	a.httpRedirectToHTTPS = httpRedirectToHTTPS
 	return a
 }
 
@@ -508,7 +509,7 @@ func (a *Adapter) CreateStack(certificateARNs []string, scheme, securityGroup, o
 		albLogsS3Prefix:              a.albLogsS3Prefix,
 		wafWebAclId:                  a.wafWebAclId,
 		cwAlarms:                     cwAlarms,
-		httpRedirectToHttps:          a.httpRedirectToHttps,
+		httpRedirectToHTTPS:          a.httpRedirectToHTTPS,
 		nlbCrossZone:                 a.nlbCrossZone,
 	}
 
@@ -545,7 +546,7 @@ func (a *Adapter) UpdateStack(stackName string, certificateARNs map[string]time.
 		albLogsS3Prefix:              a.albLogsS3Prefix,
 		wafWebAclId:                  a.wafWebAclId,
 		cwAlarms:                     cwAlarms,
-		httpRedirectToHttps:          a.httpRedirectToHttps,
+		httpRedirectToHTTPS:          a.httpRedirectToHTTPS,
 	}
 
 	return updateStack(a.cloudformation, spec)
diff --git a/aws/cf.go b/aws/cf.go
index ef7d737..acf18bf 100644
--- a/aws/cf.go
+++ b/aws/cf.go
@@ -137,7 +137,7 @@ type stackSpec struct {
 	albLogsS3Prefix              string
 	wafWebAclId                  string
 	cwAlarms                     CloudWatchAlarmList
-	httpRedirectToHttps          bool
+	httpRedirectToHTTPS          bool
 	nlbCrossZone                 bool
 }
 
diff --git a/aws/cf_template.go b/aws/cf_template.go
index b9e6dfc..e931bd9 100644
--- a/aws/cf_template.go
+++ b/aws/cf_template.go
@@ -86,7 +86,7 @@ func generateTemplate(spec *stackSpec) (string, error) {
 		tlsProtocol = "TLS"
 	}
 
-	if spec.loadbalancerType == LoadBalancerTypeApplication && spec.httpRedirectToHttps {
+	if spec.loadbalancerType == LoadBalancerTypeApplication && spec.httpRedirectToHTTPS {
 		template.AddResource("HTTPListener", &cloudformation.ElasticLoadBalancingV2Listener{
 			DefaultActions: &cloudformation.ElasticLoadBalancingV2ListenerActionList{
 				{
diff --git a/controller.go b/controller.go
index 9a2feeb..e2d586c 100644
--- a/controller.go
+++ b/controller.go
@@ -7,26 +7,24 @@ import (
 	"os"
 	"os/signal"
 	"path"
+	"strconv"
 	"strings"
 	"syscall"
-
-	"flag"
 	"time"
 
-	"io/ioutil"
-
 	"github.com/aws/aws-sdk-go/service/elbv2"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
 	log "github.com/sirupsen/logrus"
 	"github.com/zalando-incubator/kube-ingress-aws-controller/aws"
 	"github.com/zalando-incubator/kube-ingress-aws-controller/certs"
 	"github.com/zalando-incubator/kube-ingress-aws-controller/kubernetes"
+	"gopkg.in/alecthomas/kingpin.v2"
 )
 
 const (
-	defaultDisableSNISupport   = false
-	defaultHttpRedirectToHttps = false
-	defaultCertTTL             = 30 * time.Minute
+	defaultDisableSNISupport   = "false"
+	defaultHTTPRedirectToHTTPS = "false"
+	defaultCertTTL             = "30m"
 	customTagFilterEnvVarName  = "CUSTOM_FILTERS"
 )
 
@@ -37,7 +35,6 @@ var (
 	versionFlag                bool
 	apiServerBaseURL           string
 	pollingInterval            time.Duration
-	cfCustomTemplate           string
 	creationTimeout            time.Duration
 	certPollingInterval        time.Duration
 	healthCheckPath            string
@@ -53,13 +50,13 @@ var (
 	controllerID               string
 	maxCertsPerALB             int
 	sslPolicy                  string
-	blacklistCertARN           string
+	blacklistCertARNs          []string
 	blacklistCertArnMap        map[string]bool
 	ipAddressType              string
 	albLogsS3Bucket            string
 	albLogsS3Prefix            string
 	wafWebAclId                string
-	httpRedirectToHttps        bool
+	httpRedirectToHTTPS        bool
 	debugFlag                  bool
 	quietFlag                  bool
 	firstRun                   bool = true
@@ -70,79 +67,70 @@ var (
 )
 
 func loadSettings() error {
-	flag.Usage = usage
-	flag.BoolVar(&versionFlag, "version", false, "Print version and exit")
-	flag.BoolVar(&debugFlag, "debug", false, "Enables debug logging level")
-	flag.BoolVar(&quietFlag, "quiet", false, "Enables quiet logging")
-	flag.StringVar(&apiServerBaseURL, "api-server-base-url", "", "sets the kubernetes api "+
-		"server base url. If empty will try to use the configuration from the running cluster, else it will use InsecureConfig, that does not use encryption or authentication (use case to develop with kubectl proxy).")
-	flag.DurationVar(&pollingInterval, "polling-interval", 30*time.Second, "sets the polling interval for "+
-		"ingress resources. The flag accepts a value acceptable to time.ParseDuration")
-	flag.StringVar(&cfCustomTemplate, "cf-custom-template", "",
-		"filename for a custom cloud formation template to use instead of the built in")
-	flag.DurationVar(&creationTimeout, "creation-timeout", aws.DefaultCreationTimeout,
-		"sets the stack creation timeout. The flag accepts a value acceptable to time.ParseDuration. "+
-			"Should be >= 1min")
-	flag.DurationVar(&certPollingInterval, "cert-polling-interval", aws.DefaultCertificateUpdateInterval,
-		"sets the polling interval for the certificates cache refresh. The flag accepts a value "+
-			"acceptable to time.ParseDuration")
-	flag.BoolVar(&disableSNISupport, "disable-sni-support", defaultDisableSNISupport, "disables SNI support limiting the number of certificates per ALB to 1.")
-	flag.BoolVar(&stackTerminationProtection, "stack-termination-protection", false, "enables stack termination protection for the stacks managed by the controller.")
-	flag.DurationVar(&certTTL, "cert-ttl-timeout", defaultCertTTL,
-		"sets the timeout of how long a certificate is kept on an old ALB to be decommissioned.")
-	flag.StringVar(&healthCheckPath, "health-check-path", aws.DefaultHealthCheckPath,
-		"sets the health check path for the created target groups")
-	flag.UintVar(&healthCheckPort, "health-check-port", aws.DefaultHealthCheckPort,
-		"sets the health check port for the created target groups")
-	flag.UintVar(&targetPort, "target-port", 9999,
-		"sets the target port for the created target groups")
-	flag.DurationVar(&healthCheckInterval, "health-check-interval", aws.DefaultHealthCheckInterval,
-		"sets the health check interval for the created target groups. The flag accepts a value "+
-			"acceptable to time.ParseDuration")
-	flag.DurationVar(&idleConnectionTimeout, "idle-connection-timeout", aws.DefaultIdleConnectionTimeout,
-		"sets the idle connection timeout of all ALBs. The flag accepts a value acceptable to time.ParseDuration and are between 1s and 4000s.")
-	flag.StringVar(&metricsAddress, "metrics-address", ":7979", "defines where to serve metrics")
-	flag.StringVar(&ingressClassFilters, "ingress-class-filter", "", "optional comma-seperated list of kubernetes.io/ingress.class annotation values to filter behaviour on. ")
-	flag.StringVar(&controllerID, "controller-id", aws.DefaultControllerID, "controller ID used to differentiate resources from multiple aws ingress controller instances")
-	flag.IntVar(&maxCertsPerALB, "max-certs-alb", aws.DefaultMaxCertsPerALB,
-		fmt.Sprintf("sets the maximum number of certificates to be attached to an ALB. Cannot be higher than %d", aws.DefaultMaxCertsPerALB))
-	flag.StringVar(&sslPolicy, "ssl-policy", aws.DefaultSslPolicy, "Security policy that will define the protocols/ciphers accepts by the SSL listener")
-	flag.StringVar(&blacklistCertARN, "blacklist-certificate-arns", "", "Certificate ARNs to not consider by the controller: arn1,arn2,..")
-	flag.StringVar(&ipAddressType, "ip-addr-type", aws.DefaultIpAddressType, "IP Address type to use, one of 'ipv4' or 'dualstack'")
-	flag.StringVar(&albLogsS3Bucket, "logs-s3-bucket", aws.DefaultAlbS3LogsBucket, "S3 bucket to be used for ALB logging")
-	flag.StringVar(&albLogsS3Prefix, "logs-s3-prefix", aws.DefaultAlbS3LogsPrefix, "Prefix within S3 bucket to be used for ALB logging")
-	flag.StringVar(&wafWebAclId, "aws-waf-web-acl-id", aws.DefaultWafWebAclId, "Waf web acl id to be associated with the ALB")
-	flag.StringVar(&cwAlarmConfigMap, "cloudwatch-alarms-config-map", "", "ConfigMap location of the form 'namespace/config-map-name' where to read CloudWatch Alarm configuration from. Ignored if empty.")
-	flag.BoolVar(&httpRedirectToHttps, "redirect-http-to-https", defaultHttpRedirectToHttps, "Configure HTTP listener to redirect to HTTPS")
-	flag.StringVar(&loadBalancerType, "load-balancer-type", aws.LoadBalancerTypeApplication, "Sets default Load Balancer type (application or network).")
-	flag.BoolVar(&nlbCrossZone, "nlb-cross-zone", aws.DefaultNLBCrossZone, "Specify whether Network Load Balancers should balance cross availablity zones. This setting only apply to 'network' Load Balancers.")
-
-	flag.Parse()
+	kingpin.Flag("version", "Print version and exit").Default("false").BoolVar(&versionFlag)
+	kingpin.Flag("debug", "Enables debug logging level").Default("false").BoolVar(&debugFlag)
+	kingpin.Flag("quiet", "Enables quiet logging").Default("false").BoolVar(&quietFlag)
+	kingpin.Flag("api-server-base-url", "sets the kubernetes api server base url. If empty will try to use the configuration from the running cluster, else it will use InsecureConfig, that does not use encryption or authentication (use case to develop with kubectl proxy).").
+		Envar("API_SERVER_BASE_URL").StringVar(&apiServerBaseURL)
+	kingpin.Flag("polling-interval", "sets the polling interval for ingress resources. The flag accepts a value acceptable to time.ParseDuration").
+		Envar("POLLING_INTERVAL").Default("30s").DurationVar(&pollingInterval)
+	kingpin.Flag("creation-timeout", "sets the stack creation timeout. The flag accepts a value acceptable to time.ParseDuration. Should be >= 1min").
+		Envar("CREATION_TIMEOUT").Default(aws.DefaultCreationTimeout.String()).DurationVar(&creationTimeout)
+	kingpin.Flag("cert-polling-interval", "sets the polling interval for the certificates cache refresh. The flag accepts a value acceptable to time.ParseDuration").
+		Envar("CERT_POLLING_INTERVAL").Default(aws.DefaultCertificateUpdateInterval.String()).DurationVar(&certPollingInterval)
+	kingpin.Flag("disable-sni-support", "disables SNI support limiting the number of certificates per ALB to 1.").
+		Default(defaultDisableSNISupport).BoolVar(&disableSNISupport)
+	kingpin.Flag("stack-termination-protection", "enables stack termination protection for the stacks managed by the controller.").
+		Default("false").BoolVar(&stackTerminationProtection)
+	kingpin.Flag("cert-ttl-timeout", "sets the timeout of how long a certificate is kept on an old ALB to be decommissioned.").
+		Default(defaultCertTTL).DurationVar(&certTTL)
+	kingpin.Flag("health-check-path", "sets the health check path for the created target groups").
+		Default(aws.DefaultHealthCheckPath).StringVar(&healthCheckPath)
+	kingpin.Flag("health-check-port", "sets the health check port for the created target groups").
+		Default(strconv.FormatUint(aws.DefaultHealthCheckPort, 10)).UintVar(&healthCheckPort)
+	kingpin.Flag("target-port", "sets the target port for the created target groups").
+		Default(strconv.FormatUint(aws.DefaultTargetPort, 10)).UintVar(&targetPort)
+	kingpin.Flag("health-check-interval", "sets the health check interval for the created target groups. The flag accepts a value acceptable to time.ParseDuration").
+		Default(aws.DefaultHealthCheckInterval.String()).DurationVar(&healthCheckInterval)
+	kingpin.Flag("idle-connection-timeout", "sets the idle connection timeout of all ALBs. The flag accepts a value acceptable to time.ParseDuration and are between 1s and 4000s.").
+		Default(aws.DefaultIdleConnectionTimeout.String()).DurationVar(&idleConnectionTimeout)
+	kingpin.Flag("metrics-address", "defines where to serve metrics").Default(":7979").StringVar(&metricsAddress)
+	kingpin.Flag("ingress-class-filter", "optional comma-seperated list of kubernetes.io/ingress.class annotation values to filter behaviour on.").
+		StringVar(&ingressClassFilters)
+	kingpin.Flag("controller-id", "controller ID used to differentiate resources from multiple aws ingress controller instances").
+		Default(aws.DefaultControllerID).StringVar(&controllerID)
+	kingpin.Flag("max-certs-alb", fmt.Sprintf("sets the maximum number of certificates to be attached to an ALB. Cannot be higher than %d", aws.DefaultMaxCertsPerALB)).
+		Default(strconv.Itoa(aws.DefaultMaxCertsPerALB)).IntVar(&maxCertsPerALB) // TODO: max
+	kingpin.Flag("ssl-policy", "Security policy that will define the protocols/ciphers accepts by the SSL listener").
+		Default(aws.DefaultSslPolicy).EnumVar(&sslPolicy, aws.SSLPoliciesList...)
+	kingpin.Flag("blacklist-certificate-arns", "Certificate ARNs to not consider by the controller.").StringsVar(&blacklistCertARNs)
+	kingpin.Flag("ip-addr-type", "IP Address type to use.").
+		Default(aws.DefaultIpAddressType).EnumVar(&ipAddressType, aws.IPAddressTypeIPV4, aws.IPAddressTypeDualstack)
+	kingpin.Flag("logs-s3-bucket", "S3 bucket to be used for ALB logging").
+		Default(aws.DefaultAlbS3LogsBucket).StringVar(&albLogsS3Bucket)
+	kingpin.Flag("logs-s3-prefix", "Prefix within S3 bucket to be used for ALB logging").
+		Default(aws.DefaultAlbS3LogsPrefix).StringVar(&albLogsS3Prefix)
+	kingpin.Flag("aws-waf-web-acl-id", "Waf web acl id to be associated with the ALB").
+		Default(aws.DefaultWafWebAclId).StringVar(&wafWebAclId)
+	kingpin.Flag("cloudwatch-alarms-config-map", "ConfigMap location of the form 'namespace/config-map-name' where to read CloudWatch Alarm configuration from. Ignored if empty.").
+		StringVar(&cwAlarmConfigMap)
+	kingpin.Flag("redirect-http-to-https", "Configure HTTP listener to redirect to HTTPS").
+		Default(defaultHTTPRedirectToHTTPS).BoolVar(&httpRedirectToHTTPS)
+	kingpin.Flag("load-balancer-type", "Sets default Load Balancer type (application or network).").
+		Default(aws.LoadBalancerTypeApplication).EnumVar(&loadBalancerType, aws.LoadBalancerTypeApplication, aws.LoadBalancerTypeNetwork)
+	kingpin.Flag("nlb-cross-zone", "Specify whether Network Load Balancers should balance cross availablity zones. This setting only apply to 'network' Load Balancers.").
+		Default("false").BoolVar(&nlbCrossZone)
+	kingpin.Parse()
 
 	blacklistCertArnMap = make(map[string]bool)
-	for _, s := range strings.Split(blacklistCertARN, ",") {
+	for _, s := range blacklistCertARNs {
 		blacklistCertArnMap[s] = true
 	}
 
-	if tmp, defined := os.LookupEnv("API_SERVER_BASE_URL"); defined {
-		apiServerBaseURL = tmp
-	}
-
-	if err := loadDurationFromEnv("POLLING_INTERVAL", &pollingInterval); err != nil {
-		return err
-	}
-
-	if err := loadDurationFromEnv("CREATION_TIMEOUT", &creationTimeout); err != nil {
-		return err
-	}
 	if creationTimeout < 1*time.Minute {
 		return fmt.Errorf("invalid creation timeout %d. please specify a value > 1min", creationTimeout)
 	}
 
-	if err := loadDurationFromEnv("CERT_POLLING_INTERVAL", &certPollingInterval); err != nil {
-		return err
-	}
-
 	if healthCheckPort == 0 || healthCheckPort > 65535 {
 		return fmt.Errorf("invalid health check port: %d. please use a valid TCP port", healthCheckPort)
 	}
@@ -151,14 +139,6 @@ func loadSettings() error {
 		return fmt.Errorf("invalid target port: %d. please use a valid TCP port", targetPort)
 	}
 
-	if cfCustomTemplate != "" {
-		buf, err := ioutil.ReadFile(cfCustomTemplate)
-		if err != nil {
-			return err
-		}
-		cfCustomTemplate = string(buf)
-	}
-
 	if maxCertsPerALB > aws.DefaultMaxCertsPerALB {
 		return fmt.Errorf("invalid max number of certificates per ALB: %d. AWS does not allow more than %d", maxCertsPerALB, aws.DefaultMaxCertsPerALB)
 	}
@@ -172,10 +152,6 @@ func loadSettings() error {
 		cwAlarmConfigMapLocation = loc
 	}
 
-	if loadBalancerType != aws.LoadBalancerTypeApplication && loadBalancerType != aws.LoadBalancerTypeNetwork {
-		return fmt.Errorf("invalid load-balancer-type, must be 'application' or 'network'")
-	}
-
 	if quietFlag && debugFlag {
 		log.Warn("--quiet and --debug flags are both set. Debug will be used as logging level.")
 	}
@@ -204,13 +180,6 @@ func loadDurationFromEnv(varName string, dest *time.Duration) error {
 	return nil
 }
 
-func usage() {
-	fmt.Fprintf(os.Stderr, "usage: %s [options]\n", os.Args[0])
-	fmt.Fprintln(os.Stderr, "where options can be:")
-	flag.PrintDefaults()
-	os.Exit(2)
-}
-
 func main() {
 	log.Infof("starting %s", os.Args[0])
 	var (
@@ -249,7 +218,6 @@ func main() {
 		WithHealthCheckInterval(healthCheckInterval).
 		WithTargetPort(targetPort).
 		WithCreationTimeout(creationTimeout).
-		WithCustomTemplate(cfCustomTemplate).
 		WithStackTerminationProtection(stackTerminationProtection).
 		WithIdleConnectionTimeout(idleConnectionTimeout).
 		WithControllerID(controllerID).
@@ -258,7 +226,7 @@ func main() {
 		WithAlbLogsS3Bucket(albLogsS3Bucket).
 		WithAlbLogsS3Prefix(albLogsS3Prefix).
 		WithWafWebAclId(wafWebAclId).
-		WithHttpRedirectToHttps(httpRedirectToHttps).
+		WithHTTPRedirectToHTTPS(httpRedirectToHTTPS).
 		WithNLBCrossZone(nlbCrossZone).
 		WithCustomFilter(customFilter)
 
@@ -306,7 +274,7 @@ func main() {
 	log.Infof("\tPublic subnet IDs: %s", awsAdapter.FindLBSubnets(elbv2.LoadBalancerSchemeEnumInternetFacing))
 	log.Infof("\tEC2 filters: %s", awsAdapter.FiltersString())
 	log.Infof("\tCertificates per ALB: %d (SNI: %t)", certificatesPerALB, certificatesPerALB > 1)
-	log.Infof("\tBlacklisted Certificate ARNs (%d): %s", len(blacklistCertArnMap), blacklistCertARN)
+	log.Infof("\tBlacklisted Certificate ARNs (%d): %s", len(blacklistCertARNs), strings.Join(blacklistCertARNs, ","))
 	log.Infof("\tIngress class filters: %s", kubeAdapter.IngressFiltersString())
 	log.Infof("\tALB Logging S3 Bucket: %s", awsAdapter.S3Bucket())
 	log.Infof("\tALB Logging S3 Prefix: %s", awsAdapter.S3Prefix())
diff --git a/deploy/ingress-controller.yaml.example b/deploy/ingress-controller.yaml.example
index a7d2d29..4f1322e 100644
--- a/deploy/ingress-controller.yaml.example
+++ b/deploy/ingress-controller.yaml.example
@@ -29,4 +29,4 @@ spec:
         - name: AWS_REGION
           value: <REGIOn>
         args:
-          - "-ip-addr-type=dualstack" OR "-ip-addr-type=ipv4" OR OMIT TO HAVE ipv4
+          - "--ip-addr-type=dualstack" OR "--ip-addr-type=ipv4" OR OMIT TO HAVE ipv4
diff --git a/go.mod b/go.mod
index 22856c2..e10bb58 100644
--- a/go.mod
+++ b/go.mod
@@ -15,6 +15,7 @@ require (
 	github.com/prometheus/client_golang v1.1.0
 	github.com/sirupsen/logrus v1.4.2
 	github.com/stretchr/testify v1.3.0
+	gopkg.in/alecthomas/kingpin.v2 v2.2.6
 	gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
 	gopkg.in/go-playground/validator.v9 v9.30.0 // indirect
 )
diff --git a/go.sum b/go.sum
index dc5de81..eb00189 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,6 @@
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/aws/aws-sdk-go v1.25.8 h1:n7I+HUUXjun2CsX7JK+1hpRIkZrlKhd3nayeb+Xmavs=
 github.com/aws/aws-sdk-go v1.25.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@@ -91,6 +93,7 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdO
 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=


Test patch:
diff --git a/aws/cf_template_test.go b/aws/cf_template_test.go
index dad0ade..8e4b78a 100644
--- a/aws/cf_template_test.go
+++ b/aws/cf_template_test.go
@@ -100,7 +100,7 @@ func TestGenerateTemplate(t *testing.T) {
 			name: "http -> https redirect should be enabled for Application load balancers",
 			spec: &stackSpec{
 				loadbalancerType:    LoadBalancerTypeApplication,
-				httpRedirectToHttps: true,
+				httpRedirectToHTTPS: true,
 			},
 			validate: func(t *testing.T, template *cloudformation.Template) {
 				require.NotNil(t, template.Resources["HTTPListener"])
@@ -116,7 +116,7 @@ func TestGenerateTemplate(t *testing.T) {
 			name: "http -> https redirect should NOT be enabled for Network load balancers",
 			spec: &stackSpec{
 				loadbalancerType:    LoadBalancerTypeNetwork,
-				httpRedirectToHttps: true,
+				httpRedirectToHTTPS: true,
 			},
 			validate: func(t *testing.T, template *cloudformation.Template) {
 				require.NotNil(t, template.Resources["HTTPListener"])