Skip to content

[installer]: promote proxy service type from experimental #11006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions install/installer/cmd/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ func renderKubernetesObjects(cfgVersion string, cfg *configv1.Config) ([]string,
fmt.Fprintln(os.Stderr, "configuration is invalid")
os.Exit(1)
}

// Warnings are printed to stderr
for _, r := range res.Warnings {
fmt.Fprintf(os.Stderr, "%s\n", r)
}
}

ctx, err := common.NewRenderContext(*cfg, *versionMF, renderOpts.Namespace)
Expand Down
25 changes: 14 additions & 11 deletions install/installer/pkg/components/proxy/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,28 @@ var allowedServiceTypes = map[corev1.ServiceType]struct{}{
}

func service(ctx *common.RenderContext) ([]runtime.Object, error) {
serviceType := corev1.ServiceTypeLoadBalancer

loadBalancerIP := ""
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.ProxyConfig != nil {
if cfg.WebApp.ProxyConfig.StaticIP != "" {
loadBalancerIP = cfg.WebApp.ProxyConfig.StaticIP
}
st := cfg.WebApp.ProxyConfig.ServiceType
if st != nil {
_, allowed := allowedServiceTypes[corev1.ServiceType(*st)]
if allowed {
serviceType = *st
}
}
}
return nil
})

serviceType := corev1.ServiceTypeLoadBalancer
if ctx.Config.Components != nil && ctx.Config.Components.Proxy != nil && ctx.Config.Components.Proxy.Service != nil {
st := ctx.Config.Components.Proxy.Service.ServiceType
if st != nil {
_, allowed := allowedServiceTypes[corev1.ServiceType(*st)]
if allowed {
serviceType = *st
}
}
}

var annotations map[string]string
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.ProxyConfig != nil {
Expand Down Expand Up @@ -78,10 +81,10 @@ func service(ctx *common.RenderContext) ([]runtime.Object, error) {
service.Spec.Type = serviceType
if serviceType == corev1.ServiceTypeLoadBalancer {
service.Spec.LoadBalancerIP = loadBalancerIP
}

service.Annotations["external-dns.alpha.kubernetes.io/hostname"] = fmt.Sprintf("%s,*.%s,*.ws.%s", ctx.Config.Domain, ctx.Config.Domain, ctx.Config.Domain)
service.Annotations["cloud.google.com/neg"] = `{"exposed_ports": {"80":{},"443": {}}}`
service.Annotations["external-dns.alpha.kubernetes.io/hostname"] = fmt.Sprintf("%s,*.%s,*.ws.%s", ctx.Config.Domain, ctx.Config.Domain, ctx.Config.Domain)
service.Annotations["cloud.google.com/neg"] = `{"exposed_ports": {"80":{},"443": {}}}`
}

for k, v := range annotations {
service.Annotations[k] = v
Expand Down
98 changes: 87 additions & 11 deletions install/installer/pkg/components/proxy/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package proxy

import (
"fmt"
"testing"

"github.com/gitpod-io/gitpod/installer/pkg/common"
Expand All @@ -12,11 +13,12 @@ import (
"github.com/gitpod-io/gitpod/installer/pkg/config/versions"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/pointer"
)

func TestServiceLoadBalancerIP(t *testing.T) {
const loadBalancerIP = "123.456.789.0"
ctx := renderContextWithProxyConfig(t, &experimental.ProxyConfig{StaticIP: loadBalancerIP})
ctx := renderContextWithProxyConfig(t, &experimental.ProxyConfig{StaticIP: loadBalancerIP}, nil)

objects, err := service(ctx)
require.NoError(t, err)
Expand All @@ -28,24 +30,98 @@ func TestServiceLoadBalancerIP(t *testing.T) {
}

func TestServiceAnnotations(t *testing.T) {
annotations := map[string]string{"hello": "world"}
testCases := []struct {
Name string
Annotations map[string]string
Components *config.Components
Expect func(ctx *common.RenderContext, svc *corev1.Service, annotations map[string]string)
}{
{
Name: "Default to LoadBalancer",
Annotations: map[string]string{"hello": "world"},
Expect: func(ctx *common.RenderContext, svc *corev1.Service, annotations map[string]string) {
// Check standard load balancer annotations
annotations = loadBalancerAnnotations(ctx, annotations)

ctx := renderContextWithProxyConfig(t, &experimental.ProxyConfig{ServiceAnnotations: annotations})
for k, v := range annotations {
require.Equalf(t, annotations[k], svc.Annotations[k],
"expected to find annotation %q:%q on proxy service, but found %q:%q", k, v, k, svc.Annotations[k])
}
},
},
{
Name: "Set to LoadBalancer",
Components: &config.Components{
Proxy: &config.ProxyComponent{
Service: &config.ComponentTypeService{
ServiceType: (*corev1.ServiceType)(pointer.String(string(corev1.ServiceTypeLoadBalancer))),
},
},
},
Annotations: map[string]string{"hello": "world", "hello2": "world2"},
Expect: func(ctx *common.RenderContext, svc *corev1.Service, annotations map[string]string) {
// Check standard load balancer annotations
annotations = loadBalancerAnnotations(ctx, annotations)

objects, err := service(ctx)
require.NoError(t, err)
for k, v := range annotations {
require.Equalf(t, annotations[k], svc.Annotations[k],
"expected to find annotation %q:%q on proxy service, but found %q:%q", k, v, k, svc.Annotations[k])
}
},
},
{
Name: "Set to ClusterIP",
Components: &config.Components{
Proxy: &config.ProxyComponent{
Service: &config.ComponentTypeService{
ServiceType: (*corev1.ServiceType)(pointer.String(string(corev1.ServiceTypeClusterIP))),
},
},
},
Annotations: map[string]string{"hello": "world"},
Expect: func(ctx *common.RenderContext, svc *corev1.Service, annotations map[string]string) {
// Check standard load balancer annotations not present
lbAnnotations := loadBalancerAnnotations(ctx, make(map[string]string, 0))

require.Len(t, objects, 1, "must render only one object")
for k := range lbAnnotations {
require.NotContains(t, annotations, k)
}

svc := objects[0].(*corev1.Service)
for k, v := range annotations {
require.Equalf(t, annotations[k], svc.Annotations[k],
"expected to find annotation %q:%q on proxy service, but found %q:%q", k, v, k, svc.Annotations[k])
for k, v := range annotations {
require.Equalf(t, annotations[k], svc.Annotations[k],
"expected to find annotation %q:%q on proxy service, but found %q:%q", k, v, k, svc.Annotations[k])
}
},
},
}

for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
ctx := renderContextWithProxyConfig(t, &experimental.ProxyConfig{ServiceAnnotations: testCase.Annotations}, testCase.Components)

objects, err := service(ctx)
require.NoError(t, err)

require.Len(t, objects, 1, "must render only one object")

svc := objects[0].(*corev1.Service)

testCase.Expect(ctx, svc, testCase.Annotations)
})
}
}

func renderContextWithProxyConfig(t *testing.T, proxyConfig *experimental.ProxyConfig) *common.RenderContext {
func loadBalancerAnnotations(ctx *common.RenderContext, annotations map[string]string) map[string]string {
annotations["external-dns.alpha.kubernetes.io/hostname"] = fmt.Sprintf("%s,*.%s,*.ws.%s", ctx.Config.Domain, ctx.Config.Domain, ctx.Config.Domain)
annotations["cloud.google.com/neg"] = `{"exposed_ports": {"80":{},"443": {}}}`

return annotations
}

func renderContextWithProxyConfig(t *testing.T, proxyConfig *experimental.ProxyConfig, components *config.Components) *common.RenderContext {
ctx, err := common.NewRenderContext(config.Config{
Domain: "some-domain",
Components: components,
Experimental: &experimental.Config{
WebApp: &experimental.WebAppConfig{
ProxyConfig: proxyConfig,
Expand Down
4 changes: 4 additions & 0 deletions install/installer/pkg/config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ type ConfigVersion interface {

// ClusterValidation introduces configuration specific cluster validation checks
ClusterValidation(cfg interface{}) cluster.ValidationChecks

// CheckDeprecated checks for deprecated config params.
// Returns key/value pair of deprecated params/values and any error messages (used for conflicting params)
CheckDeprecated(cfg interface{}) (map[string]interface{}, []string)
}

// AddVersion adds a new version.
Expand Down
42 changes: 42 additions & 0 deletions install/installer/pkg/config/v1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,34 @@ func (v version) Defaults(in interface{}) error {
return nil
}

func (v version) CheckDeprecated(rawCfg interface{}) (map[string]interface{}, []string) {
warnings := make(map[string]interface{}, 0)
conflicts := make([]string, 0)
cfg := rawCfg.(*Config)

if cfg.Experimental != nil && cfg.Experimental.WebApp != nil && cfg.Experimental.WebApp.ProxyConfig != nil && cfg.Experimental.WebApp.ProxyConfig.ServiceType != nil {
warnings["experimental.webapp.proxy.serviceType"] = *cfg.Experimental.WebApp.ProxyConfig.ServiceType

if cfg.Components != nil && cfg.Components.Proxy != nil && cfg.Components.Proxy.Service != nil && cfg.Components.Proxy.Service.ServiceType != nil {
conflicts = append(conflicts, "Cannot set proxy service type in both components and experimental")
} else {
// Promote the experimental value to the components
if cfg.Components == nil {
cfg.Components = &Components{}
}
if cfg.Components.Proxy == nil {
cfg.Components.Proxy = &ProxyComponent{}
}
if cfg.Components.Proxy.Service == nil {
cfg.Components.Proxy.Service = &ComponentTypeService{}
}
cfg.Components.Proxy.Service.ServiceType = cfg.Experimental.WebApp.ProxyConfig.ServiceType
}
}

return warnings, conflicts
}

// Config defines the v1 version structure of the gitpod config file
type Config struct {
// Installation type to run - for most users, this will be Full
Expand Down Expand Up @@ -113,6 +141,8 @@ type Config struct {

Customization *[]Customization `json:"customization,omitempty"`

Components *Components `json:"components,omitempty"`

Experimental *experimental.Config `json:"experimental,omitempty"`
}

Expand Down Expand Up @@ -340,3 +370,15 @@ type Customization struct {
type CustomizationSpec struct {
Env []corev1.EnvVar `json:"env"`
}

type Components struct {
Proxy *ProxyComponent `json:"proxy,omitempty"`
}

type ProxyComponent struct {
Service *ComponentTypeService `json:"service,omitempty"`
}

type ComponentTypeService struct {
ServiceType *corev1.ServiceType `json:"serviceType,omitempty" validate:"omitempty,service_config_type"`
}
8 changes: 5 additions & 3 deletions install/installer/pkg/config/v1/experimental/experimental.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,11 @@ type BlockedRepository struct {
}

type ProxyConfig struct {
StaticIP string `json:"staticIP"`
ServiceAnnotations map[string]string `json:"serviceAnnotations"`
ServiceType *corev1.ServiceType `json:"serviceType,omitempty" validate:"omitempty,service_config_type"`
StaticIP string `json:"staticIP"`
ServiceAnnotations map[string]string `json:"serviceAnnotations"`

// @deprecated use components.proxy.service.serviceType instead
ServiceType *corev1.ServiceType `json:"serviceType,omitempty" validate:"omitempty,service_config_type"`
}

type PublicAPIConfig struct {
Expand Down
8 changes: 8 additions & 0 deletions install/installer/pkg/config/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ func Validate(version ConfigVersion, cfg interface{}) (r *ValidationResult, err
}

var res ValidationResult

warnings, conflicts := version.CheckDeprecated(cfg)

for k, v := range warnings {
res.Warnings = append(res.Warnings, fmt.Sprintf("Deprecated config parameter: %s=%v", k, v))
}
res.Fatal = append(res.Fatal, conflicts...)

err = validate.Struct(cfg)
if err != nil {
validationErrors := err.(validator.ValidationErrors)
Expand Down
2 changes: 1 addition & 1 deletion install/kots/manifests/gitpod-installation-status.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ spec:
containers:
- name: installation-status
# This will normally be the release tag
image: "eu.gcr.io/gitpod-core-dev/build/installer:tar-preview-telemetry.25"
image: "eu.gcr.io/gitpod-core-dev/build/installer:sje-installer-clusterip.8"
command:
- /bin/sh
- -c
Expand Down
13 changes: 12 additions & 1 deletion install/kots/manifests/gitpod-installer-job.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ spec:
containers:
- name: installer
# This will normally be the release tag
image: "eu.gcr.io/gitpod-core-dev/build/installer:tar-preview-telemetry.25"
image: "eu.gcr.io/gitpod-core-dev/build/installer:sje-installer-clusterip.8"
volumeMounts:
- mountPath: /config-patch
name: config-patch
Expand Down Expand Up @@ -263,6 +263,15 @@ spec:

if [ '{{repl ConfigOptionEquals "advanced_mode_enabled" "1" }}' = "true" ];
then
echo "Gitpod: Applying advanced configuration"

if [ '{{repl ConfigOptionNotEquals "component_proxy_service_serviceType" "" }}' = "true" ];
then
# Empty string defaults to LoadBalancer. This maintains backwards compatibility with the deprecated experimental value
echo "Gitpod: Applying Proxy service type"
yq e -i ".components.proxy.service.serviceType = \"{{repl ConfigOption "component_proxy_service_serviceType" }}\"" "${CONFIG_FILE}"
fi

if [ '{{repl ConfigOptionNotEquals "customization_patch" "" }}' = "true" ];
then
CUSTOMIZATION='{{repl ConfigOptionData "customization_patch" | Base64Encode }}'
Expand All @@ -271,6 +280,8 @@ spec:
# Apply the customization property - if something else is set, this will be ignored
yq e -i ".customization = $(echo "${CUSTOMIZATION}" | base64 -d | yq e -o json '.customization' - | jq -rc) // []" "${CONFIG_FILE}"
fi
else
echo "Gitpod: No advanced configuration applied"
fi

echo "Gitpod: Update platform telemetry value"
Expand Down
29 changes: 25 additions & 4 deletions install/kots/manifests/kots-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -370,14 +370,14 @@ spec:
Add the domain only (eg, `gitpod.io`). Separate multiple domains with spaces.

- name: advanced
title: Additional Options
description: Here are additional options that you should only make use of in coordination with us or when you know what you are doing.
title: Advanced Options
description: Here are advanced options that you should only make use of in coordination with us or when you know what you are doing.
items:
- name: advanced_mode_enabled
title: Enable additional options
title: Enable advanced options
type: bool
default: "0"
help_text: Enables additional customization options. Enable only when you know what you are doing!
help_text: Enables advanced customization options. Enable only when you know what you are doing!

- name: customization_patch
title: Gitpod customization patch (YAML file)
Expand All @@ -400,3 +400,24 @@ spec:
required: false
when: '{{repl ConfigOptionEquals "advanced_mode_enabled" "1" }}'
help_text: A file with Gitpod config that will be used to patch the generated Gitpod config. Usually provided by Gitpod as a way to tailor your installation.

- name: components
title: Components
description: Customize your component configuration
when: '{{repl ConfigOptionEquals "advanced_mode_enabled" "1" }}'
items:
- name: component_proxy_service_serviceType
title: Proxy service type
default: ""
help_text: |
Select the [Service Type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types)
for the Proxy service. If using anything other than "Load Balancer", you are responsible for configuring your network to route
traffic through to the `proxy` service.
type: select_one
items:
- name: ""
title: Load balancer
- name: ClusterIP
title: Cluster IP
- name: NodePort
title: Node port