diff --git a/controllers/v1beta1/build_controller.go b/controllers/v1beta1/build_controller.go index 5526a58b..f7368d58 100644 --- a/controllers/v1beta1/build_controller.go +++ b/controllers/v1beta1/build_controller.go @@ -72,6 +72,7 @@ type LagoonBuildReconciler struct { BuildQoS BuildQoS NativeCronPodMinFrequency int LagoonTargetName string + LagoonFeatureFlags map[string]string ProxyConfig ProxyConfig } diff --git a/controllers/v1beta1/build_helpers.go b/controllers/v1beta1/build_helpers.go index 302b17ff..73288846 100644 --- a/controllers/v1beta1/build_helpers.go +++ b/controllers/v1beta1/build_helpers.go @@ -765,6 +765,13 @@ func (r *LagoonBuildReconciler) processBuild(ctx context.Context, opLog logr.Log Value: r.LFFDefaultRWX2RWO, }) } + // add any LAGOON_FEATURE_FLAG_ variables in the controller into the build pods + for fName, fValue := range r.LagoonFeatureFlags { + podEnvs = append(podEnvs, corev1.EnvVar{ + Name: fName, + Value: fValue, + }) + } // Use the build image in the controller definition buildImage := r.BuildImage if lagoonBuild.Spec.Build.Image != "" { diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go index cc0ac215..e095eb66 100644 --- a/internal/helpers/helpers.go +++ b/internal/helpers/helpers.go @@ -7,6 +7,7 @@ import ( "encoding/base32" "fmt" "math/rand" + "os" "regexp" "sort" "strconv" @@ -296,3 +297,46 @@ func ShortenEnvironment(project, environment string) string { } return environment } + +// GetLagoonFeatureFlags pull all the environment variables and check if there are any with the flag prefix +func GetLagoonFeatureFlags() map[string]string { + flags := make(map[string]string) + for _, envVar := range os.Environ() { + envVarSplit := strings.SplitN(envVar, "=", 2) + // if the variable name contains the flag prefix, add it to the map + if strings.Contains(envVarSplit[0], "LAGOON_FEATURE_FLAG_") { + flags[envVarSplit[0]] = envVarSplit[1] + } + } + return flags +} + +// GetEnv get environment variable +func GetEnv(key, fallback string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return fallback +} + +// GetEnvInt get environment variable as int +func GetEnvInt(key string, fallback int) int { + if value, ok := os.LookupEnv(key); ok { + valueInt, e := strconv.Atoi(value) + if e == nil { + return valueInt + } + } + return fallback +} + +// GetEnvBool get environment variable as bool +// accepts fallback values 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False +// anything else is false. +func GetEnvBool(key string, fallback bool) bool { + if value, ok := os.LookupEnv(key); ok { + rVal, _ := strconv.ParseBool(value) + return rVal + } + return fallback +} diff --git a/internal/helpers/helpers_test.go b/internal/helpers/helpers_test.go index 81c8936b..35a5e6c2 100644 --- a/internal/helpers/helpers_test.go +++ b/internal/helpers/helpers_test.go @@ -1,6 +1,8 @@ package helpers import ( + "os" + "reflect" "testing" ) @@ -205,3 +207,40 @@ func TestShortenEnvironment(t *testing.T) { }) } } + +func TestGetLagoonFeatureFlags(t *testing.T) { + tests := []struct { + name string + set map[string]string + want map[string]string + }{ + { + name: "test1", + set: map[string]string{ + "LAGOON_FEATURE_FLAG_ABC": "enabled", + "LAGOON_FEATURE_FLAG_123": "enabled", + "OTHERS": "path noises", + }, + want: map[string]string{ + "LAGOON_FEATURE_FLAG_ABC": "enabled", + "LAGOON_FEATURE_FLAG_123": "enabled", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for e, v := range tt.set { + os.Setenv(e, v) + } + if got := GetLagoonFeatureFlags(); !reflect.DeepEqual(got, tt.want) { + for e := range tt.set { + os.Unsetenv(e) + } + t.Errorf("GetLagoonFeatureFlags() = %v, want %v", got, tt.want) + } + for e := range tt.set { + os.Unsetenv(e) + } + }) + } +} diff --git a/main.go b/main.go index 4a614947..93407d14 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,6 @@ import ( "net/http" "net/url" "os" - "strconv" "strings" "time" @@ -36,6 +35,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "github.com/uselagoon/remote-controller/internal/harbor" + "github.com/uselagoon/remote-controller/internal/helpers" "github.com/uselagoon/remote-controller/internal/metrics" "gopkg.in/robfig/cron.v2" @@ -326,14 +326,14 @@ func main() { flag.Parse() // get overrides from environment variables - mqUser = getEnv("RABBITMQ_USERNAME", mqUser) - mqPass = getEnv("RABBITMQ_PASSWORD", mqPass) - mqHost = getEnv("RABBITMQ_HOSTNAME", mqHost) - lagoonTargetName = getEnv("LAGOON_TARGET_NAME", lagoonTargetName) - lagoonAppID = getEnv("LAGOON_APP_ID", lagoonAppID) - pendingMessageCron = getEnv("PENDING_MESSAGE_CRON", pendingMessageCron) - overrideBuildDeployImage = getEnv("OVERRIDE_BUILD_DEPLOY_DIND_IMAGE", overrideBuildDeployImage) - namespacePrefix = getEnv("NAMESPACE_PREFIX", namespacePrefix) + mqUser = helpers.GetEnv("RABBITMQ_USERNAME", mqUser) + mqPass = helpers.GetEnv("RABBITMQ_PASSWORD", mqPass) + mqHost = helpers.GetEnv("RABBITMQ_HOSTNAME", mqHost) + lagoonTargetName = helpers.GetEnv("LAGOON_TARGET_NAME", lagoonTargetName) + lagoonAppID = helpers.GetEnv("LAGOON_APP_ID", lagoonAppID) + pendingMessageCron = helpers.GetEnv("PENDING_MESSAGE_CRON", pendingMessageCron) + overrideBuildDeployImage = helpers.GetEnv("OVERRIDE_BUILD_DEPLOY_DIND_IMAGE", overrideBuildDeployImage) + namespacePrefix = helpers.GetEnv("NAMESPACE_PREFIX", namespacePrefix) if len(namespacePrefix) > 8 { // truncate the namespace prefix to 8 characters so that a really long prefix // does not become a problem, and namespaces are still somewhat identifiable. @@ -345,30 +345,30 @@ func main() { // this is also used by the controller as the namespace that resources will be created in initially when received by the queue // this can be defined using `valueFrom.fieldRef.fieldPath: metadata.namespace` in any deployments to get the // namespace from where the controller is running - controllerNamespace = getEnv("CONTROLLER_NAMESPACE", controllerNamespace) + controllerNamespace = helpers.GetEnv("CONTROLLER_NAMESPACE", controllerNamespace) if controllerNamespace == "" { setupLog.Error(fmt.Errorf("controller-namespace is empty"), "unable to start manager") os.Exit(1) } - lagoonAPIHost = getEnv("TASK_API_HOST", lagoonAPIHost) - lagoonSSHHost = getEnv("TASK_SSH_HOST", lagoonSSHHost) - lagoonSSHPort = getEnv("TASK_SSH_PORT", lagoonSSHPort) + lagoonAPIHost = helpers.GetEnv("TASK_API_HOST", lagoonAPIHost) + lagoonSSHHost = helpers.GetEnv("TASK_SSH_HOST", lagoonSSHHost) + lagoonSSHPort = helpers.GetEnv("TASK_SSH_PORT", lagoonSSHPort) - nativeCronPodMinFrequency = getEnvInt("NATIVE_CRON_POD_MINIMUM_FREQUENCY", nativeCronPodMinFrequency) + nativeCronPodMinFrequency = helpers.GetEnvInt("NATIVE_CRON_POD_MINIMUM_FREQUENCY", nativeCronPodMinFrequency) // harbor envvars - harborURL = getEnv("HARBOR_URL", harborURL) - harborAPI = getEnv("HARBOR_API", harborAPI) - harborUsername = getEnv("HARBOR_USERNAME", harborUsername) - harborPassword = getEnv("HARBOR_PASSWORD", harborPassword) - harborRobotPrefix = getEnv("HARBOR_ROBOT_PREFIX", harborRobotPrefix) - harborWebhookAdditionEnabled = getEnvBool("HARBOR_WEBHOOK_ADDITION_ENABLED", harborWebhookAdditionEnabled) - harborLagoonWebhook = getEnv("HARBOR_LAGOON_WEBHOOK", harborLagoonWebhook) - harborWebhookEventTypes = getEnv("HARBOR_WEBHOOK_EVENTTYPES", harborWebhookEventTypes) - harborRobotDeleteDisabled = getEnvBool("HARBOR_ROBOT_DELETE_DISABLED", harborRobotDeleteDisabled) - harborExpiryInterval = getEnv("HARBOR_EXPIRY_INTERVAL", harborExpiryInterval) - harborRotateInterval = getEnv("HARBOR_ROTATE_INTERVAL", harborRotateInterval) - harborRobotAccountExpiry = getEnv("HARBOR_ROTATE_ACCOUNT_EXPIRY", harborRobotAccountExpiry) + harborURL = helpers.GetEnv("HARBOR_URL", harborURL) + harborAPI = helpers.GetEnv("HARBOR_API", harborAPI) + harborUsername = helpers.GetEnv("HARBOR_USERNAME", harborUsername) + harborPassword = helpers.GetEnv("HARBOR_PASSWORD", harborPassword) + harborRobotPrefix = helpers.GetEnv("HARBOR_ROBOT_PREFIX", harborRobotPrefix) + harborWebhookAdditionEnabled = helpers.GetEnvBool("HARBOR_WEBHOOK_ADDITION_ENABLED", harborWebhookAdditionEnabled) + harborLagoonWebhook = helpers.GetEnv("HARBOR_LAGOON_WEBHOOK", harborLagoonWebhook) + harborWebhookEventTypes = helpers.GetEnv("HARBOR_WEBHOOK_EVENTTYPES", harborWebhookEventTypes) + harborRobotDeleteDisabled = helpers.GetEnvBool("HARBOR_ROBOT_DELETE_DISABLED", harborRobotDeleteDisabled) + harborExpiryInterval = helpers.GetEnv("HARBOR_EXPIRY_INTERVAL", harborExpiryInterval) + harborRotateInterval = helpers.GetEnv("HARBOR_ROTATE_INTERVAL", harborRotateInterval) + harborRobotAccountExpiry = helpers.GetEnv("HARBOR_ROTATE_ACCOUNT_EXPIRY", harborRobotAccountExpiry) harborExpiryIntervalDuration := 2 * 24 * time.Hour harborRotateIntervalDuration := 30 * 24 * time.Hour harborRobotAccountExpiryDuration := 30 * 24 * time.Hour @@ -393,18 +393,18 @@ func main() { // Fastly configuration options // the service id should be that for the cluster which will be used as the default no-cache passthrough - fastlyServiceID = getEnv("FASTLY_SERVICE_ID", fastlyServiceID) + fastlyServiceID = helpers.GetEnv("FASTLY_SERVICE_ID", fastlyServiceID) // this is used to control setting the service id into build pods - fastlyWatchStatus = getEnvBool("FASTLY_WATCH_STATUS", fastlyWatchStatus) + fastlyWatchStatus = helpers.GetEnvBool("FASTLY_WATCH_STATUS", fastlyWatchStatus) if enablePodProxy { - httpProxy = getEnv("HTTP_PROXY", httpProxy) - httpsProxy = getEnv("HTTPS_PROXY", httpsProxy) - noProxy = getEnv("HTTP_PROXY", noProxy) + httpProxy = helpers.GetEnv("HTTP_PROXY", httpProxy) + httpsProxy = helpers.GetEnv("HTTPS_PROXY", httpsProxy) + noProxy = helpers.GetEnv("HTTP_PROXY", noProxy) if podsUseDifferentProxy { - httpProxy = getEnv("LAGOON_HTTP_PROXY", httpProxy) - httpsProxy = getEnv("LAGOON_HTTPS_PROXY", httpsProxy) - noProxy = getEnv("LAGOON_HTTP_PROXY", noProxy) + httpProxy = helpers.GetEnv("LAGOON_HTTP_PROXY", httpProxy) + httpsProxy = helpers.GetEnv("LAGOON_HTTPS_PROXY", httpsProxy) + noProxy = helpers.GetEnv("LAGOON_HTTP_PROXY", noProxy) } } @@ -709,6 +709,7 @@ func main() { BuildQoS: buildQoSConfig, NativeCronPodMinFrequency: nativeCronPodMinFrequency, LagoonTargetName: lagoonTargetName, + LagoonFeatureFlags: helpers.GetLagoonFeatureFlags(), ProxyConfig: lagoonv1beta1ctrl.ProxyConfig{ HTTPProxy: httpProxy, HTTPSProxy: httpsProxy, @@ -769,33 +770,6 @@ func main() { } } -func getEnv(key, fallback string) string { - if value, ok := os.LookupEnv(key); ok { - return value - } - return fallback -} - -func getEnvInt(key string, fallback int) int { - if value, ok := os.LookupEnv(key); ok { - valueInt, e := strconv.Atoi(value) - if e == nil { - return valueInt - } - } - return fallback -} - -// accepts fallback values 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False -// anything else is false. -func getEnvBool(key string, fallback bool) bool { - if value, ok := os.LookupEnv(key); ok { - rVal, _ := strconv.ParseBool(value) - return rVal - } - return fallback -} - func init() { if tlsSkipVerify { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}