From 9f3d7413f72265b0980f318f14d7a9e2ece9fea0 Mon Sep 17 00:00:00 2001 From: Milan Pavlik Date: Tue, 24 May 2022 19:50:46 +0000 Subject: [PATCH] [usage] Initial installer configuration --- .werft/jobs/build/installer/installer.ts | 5 + .../components-webapp/components.go | 2 + .../pkg/components/usage/constants.go | 9 ++ .../pkg/components/usage/deployment.go | 100 ++++++++++++++++++ .../installer/pkg/components/usage/objects.go | 40 +++++++ .../pkg/components/usage/objects_test.go | 51 +++++++++ .../pkg/components/usage/rolebinding.go | 56 ++++++++++ .../config/v1/experimental/experimental.go | 5 + .../installer/pkg/config/versions/versions.go | 1 + 9 files changed, 269 insertions(+) create mode 100644 install/installer/pkg/components/usage/constants.go create mode 100644 install/installer/pkg/components/usage/deployment.go create mode 100644 install/installer/pkg/components/usage/objects.go create mode 100644 install/installer/pkg/components/usage/objects_test.go create mode 100644 install/installer/pkg/components/usage/rolebinding.go diff --git a/.werft/jobs/build/installer/installer.ts b/.werft/jobs/build/installer/installer.ts index e567f7fd5d789e..48b5b3d9ad3a37 100644 --- a/.werft/jobs/build/installer/installer.ts +++ b/.werft/jobs/build/installer/installer.ts @@ -67,6 +67,7 @@ export class Installer { this.configureAuthProviders(slice) this.configureSSHGateway(slice) this.configurePublicAPIServer(slice) + this.configureUsage(slice) if (this.options.analytics) { this.includeAnalytics(slice) @@ -164,6 +165,10 @@ export class Installer { exec(`yq w -i ${this.options.installerConfigPath} experimental.webapp.publicApi.enabled true`, { slice: slice }) } + private configureUsage(slice: string) { + exec(`yq w -i ${this.options.installerConfigPath} experimental.webapp.usage.enabled true`, { slice: slice }) + } + private includeAnalytics(slice: string): void { exec(`yq w -i ${this.options.installerConfigPath} analytics.writer segment`, { slice: slice }); exec(`yq w -i ${this.options.installerConfigPath} analytics.segmentKey ${this.options.analytics.token}`, { slice: slice }); diff --git a/install/installer/pkg/components/components-webapp/components.go b/install/installer/pkg/components/components-webapp/components.go index 92264541ed428b..41868df7077a19 100644 --- a/install/installer/pkg/components/components-webapp/components.go +++ b/install/installer/pkg/components/components-webapp/components.go @@ -17,6 +17,7 @@ import ( public_api_server "github.com/gitpod-io/gitpod/installer/pkg/components/public-api-server" "github.com/gitpod-io/gitpod/installer/pkg/components/rabbitmq" "github.com/gitpod-io/gitpod/installer/pkg/components/server" + "github.com/gitpod-io/gitpod/installer/pkg/components/usage" wsmanagerbridge "github.com/gitpod-io/gitpod/installer/pkg/components/ws-manager-bridge" ) @@ -33,6 +34,7 @@ var Objects = common.CompositeRenderFunc( server.Objects, wsmanagerbridge.Objects, public_api_server.Objects, + usage.Objects, ) var Helm = common.CompositeHelmFunc( diff --git a/install/installer/pkg/components/usage/constants.go b/install/installer/pkg/components/usage/constants.go new file mode 100644 index 00000000000000..f2f1da6918afaa --- /dev/null +++ b/install/installer/pkg/components/usage/constants.go @@ -0,0 +1,9 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package usage + +const ( + Component = "usage" +) diff --git a/install/installer/pkg/components/usage/deployment.go b/install/installer/pkg/components/usage/deployment.go new file mode 100644 index 00000000000000..5f5f2ca3704ef2 --- /dev/null +++ b/install/installer/pkg/components/usage/deployment.go @@ -0,0 +1,100 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the MIT License. See License-MIT.txt in the project root for license information. + +package usage + +import ( + "fmt" + + "github.com/gitpod-io/gitpod/common-go/baseserver" + "github.com/gitpod-io/gitpod/installer/pkg/cluster" + "github.com/gitpod-io/gitpod/installer/pkg/common" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" +) + +func deployment(ctx *common.RenderContext) ([]runtime.Object, error) { + labels := common.DefaultLabels(Component) + return []runtime.Object{ + &appsv1.Deployment{ + TypeMeta: common.TypeMetaDeployment, + ObjectMeta: metav1.ObjectMeta{ + Name: Component, + Namespace: ctx.Namespace, + Labels: labels, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{MatchLabels: labels}, + Replicas: common.Replicas(ctx, Component), + Strategy: common.DeploymentStrategy, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: Component, + Namespace: ctx.Namespace, + Labels: labels, + }, + Spec: corev1.PodSpec{ + Affinity: common.NodeAffinity(cluster.AffinityLabelMeta), + ServiceAccountName: Component, + EnableServiceLinks: pointer.Bool(false), + DNSPolicy: "ClusterFirst", + RestartPolicy: "Always", + TerminationGracePeriodSeconds: pointer.Int64(30), + Containers: []corev1.Container{{ + Name: Component, + Image: ctx.ImageName(ctx.Config.Repository, Component, ctx.VersionManifest.Components.Usage.Version), + Args: []string{ + "run", + }, + ImagePullPolicy: corev1.PullIfNotPresent, + Resources: common.ResourceRequirements(ctx, Component, Component, corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("100m"), + "memory": resource.MustParse("64Mi"), + }, + }), + Ports: []corev1.ContainerPort{}, + SecurityContext: &corev1.SecurityContext{ + Privileged: pointer.Bool(false), + }, + Env: common.MergeEnv( + common.DefaultEnv(&ctx.Config), + ), + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/live", + Port: intstr.IntOrString{IntVal: baseserver.BuiltinHealthPort}, + Scheme: corev1.URISchemeHTTP, + }, + }, + FailureThreshold: 3, + SuccessThreshold: 1, + TimeoutSeconds: 1, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/ready", + Port: intstr.IntOrString{IntVal: baseserver.BuiltinHealthPort}, + Scheme: corev1.URISchemeHTTP, + }, + }, + FailureThreshold: 3, + SuccessThreshold: 1, + TimeoutSeconds: 1, + }, + }, + *common.KubeRBACProxyContainerWithConfig(ctx, 9500, fmt.Sprintf("http://127.0.0.1:%d/", baseserver.BuiltinMetricsPort)), + }, + }, + }, + }, + }, + }, nil +} diff --git a/install/installer/pkg/components/usage/objects.go b/install/installer/pkg/components/usage/objects.go new file mode 100644 index 00000000000000..1abb7ab404f0ed --- /dev/null +++ b/install/installer/pkg/components/usage/objects.go @@ -0,0 +1,40 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the MIT License. See License-MIT.txt in the project root for license information. + +package usage + +import ( + "github.com/gitpod-io/gitpod/common-go/log" + "github.com/gitpod-io/gitpod/installer/pkg/common" + "github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental" + "k8s.io/apimachinery/pkg/runtime" +) + +func Objects(ctx *common.RenderContext) ([]runtime.Object, error) { + cfg := getExperimentalConfig(ctx) + if cfg == nil { + return nil, nil + } + + log.Debug("Detected experimental.WebApp.Usage configuration", cfg) + return common.CompositeRenderFunc( + deployment, + rolebinding, + common.DefaultServiceAccount(Component), + )(ctx) +} + +func getExperimentalConfig(ctx *common.RenderContext) *experimental.UsageConfig { + var experimentalCfg *experimental.Config + + _ = ctx.WithExperimental(func(ucfg *experimental.Config) error { + experimentalCfg = ucfg + return nil + }) + + if experimentalCfg == nil || experimentalCfg.WebApp == nil || experimentalCfg.WebApp.Usage == nil { + return nil + } + + return experimentalCfg.WebApp.Usage +} diff --git a/install/installer/pkg/components/usage/objects_test.go b/install/installer/pkg/components/usage/objects_test.go new file mode 100644 index 00000000000000..d7cf18227701a7 --- /dev/null +++ b/install/installer/pkg/components/usage/objects_test.go @@ -0,0 +1,51 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the MIT License. See License-MIT.txt in the project root for license information. + +package usage + +import ( + "github.com/gitpod-io/gitpod/installer/pkg/common" + "github.com/gitpod-io/gitpod/installer/pkg/config/v1" + "github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental" + "github.com/gitpod-io/gitpod/installer/pkg/config/versions" + "github.com/stretchr/testify/require" + "testing" +) + +func TestObjects_NotRenderedByDefault(t *testing.T) { + ctx, err := common.NewRenderContext(config.Config{}, versions.Manifest{}, "test-namespace") + require.NoError(t, err) + + objects, err := Objects(ctx) + require.NoError(t, err) + require.Empty(t, objects, "no objects should be rendered with default config") +} + +func TestObjects_RenderedWhenExperimentalConfigSet(t *testing.T) { + ctx := renderContextWithUsageEnabled(t) + + objects, err := Objects(ctx) + require.NoError(t, err) + require.NotEmpty(t, objects, "must render objects because experimental config is specified") + require.Len(t, objects, 4, "should render expected k8s objects") +} + +func renderContextWithUsageEnabled(t *testing.T) *common.RenderContext { + ctx, err := common.NewRenderContext(config.Config{ + Domain: "test.domain.everything.awesome.is", + Experimental: &experimental.Config{ + WebApp: &experimental.WebAppConfig{ + Usage: &experimental.UsageConfig{Enabled: true}, + }, + }, + }, versions.Manifest{ + Components: versions.Components{ + Usage: versions.Versioned{ + Version: "commit-test-latest", + }, + }, + }, "test-namespace") + require.NoError(t, err) + + return ctx +} diff --git a/install/installer/pkg/components/usage/rolebinding.go b/install/installer/pkg/components/usage/rolebinding.go new file mode 100644 index 00000000000000..2229d9f486d79f --- /dev/null +++ b/install/installer/pkg/components/usage/rolebinding.go @@ -0,0 +1,56 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package usage + +import ( + "fmt" + + "github.com/gitpod-io/gitpod/installer/pkg/common" + + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func rolebinding(ctx *common.RenderContext) ([]runtime.Object, error) { + labels := common.DefaultLabels(Component) + + return []runtime.Object{ + &rbacv1.ClusterRoleBinding{ + TypeMeta: common.TypeMetaClusterRoleBinding, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s-rb-kube-rbac-proxy", ctx.Namespace, Component), + Labels: labels, + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: fmt.Sprintf("%s-kube-rbac-proxy", ctx.Namespace), + APIGroup: "rbac.authorization.k8s.io", + }, + Subjects: []rbacv1.Subject{{ + Kind: "ServiceAccount", + Name: Component, + Namespace: ctx.Namespace, + }}, + }, + &rbacv1.RoleBinding{ + TypeMeta: common.TypeMetaRoleBinding, + ObjectMeta: metav1.ObjectMeta{ + Name: Component, + Namespace: ctx.Namespace, + Labels: common.DefaultLabels(Component), + }, + RoleRef: rbacv1.RoleRef{ + Kind: "ClusterRole", + Name: fmt.Sprintf("%s-ns-psp:restricted-root-user", ctx.Namespace), + APIGroup: "rbac.authorization.k8s.io", + }, + Subjects: []rbacv1.Subject{{ + Kind: "ServiceAccount", + Name: Component, + }}, + }, + }, nil +} diff --git a/install/installer/pkg/config/v1/experimental/experimental.go b/install/installer/pkg/config/v1/experimental/experimental.go index e22be57cee885a..f983e2ed6f3a9a 100644 --- a/install/installer/pkg/config/v1/experimental/experimental.go +++ b/install/installer/pkg/config/v1/experimental/experimental.go @@ -102,6 +102,7 @@ type WebAppConfig struct { Tracing *Tracing `json:"tracing,omitempty"` UsePodAntiAffinity bool `json:"usePodAntiAffinity"` DisableMigration bool `json:"disableMigration"` + Usage *UsageConfig `json:"usage,omitempty"` } type WorkspaceDefaults struct { @@ -160,6 +161,10 @@ type PublicAPIConfig struct { Enabled bool `json:"enabled"` } +type UsageConfig struct { + Enabled bool `json:"enabled"` +} + type IDEConfig struct { // Disable resolution of latest images and use bundled latest versions instead ResolveLatest *bool `json:"resolveLatest,omitempty"` diff --git a/install/installer/pkg/config/versions/versions.go b/install/installer/pkg/config/versions/versions.go index 62888f0941248d..b66f36e1c2a2c0 100644 --- a/install/installer/pkg/config/versions/versions.go +++ b/install/installer/pkg/config/versions/versions.go @@ -37,6 +37,7 @@ type Components struct { RegistryFacade Versioned `json:"registryFacade"` Server Versioned `json:"server"` ServiceWaiter Versioned `json:"serviceWaiter"` + Usage Versioned `json:"usage"` Workspace struct { CodeImage Versioned `json:"codeImage"` DockerUp Versioned `json:"dockerUp"`