diff --git a/Dockerfile b/Dockerfile index 56cbad208..0b3631b2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ RUN --mount=type=cache,id=gomod,target=/go/pkg/mod \ # RUN useradd -u 12345 nonroot # USER nonroot -ENTRYPOINT ["go", "run", "-mod", "vendor", "cmd/vcluster/main.go"] +ENTRYPOINT ["go", "run", "-mod", "vendor", "cmd/vcluster/main.go", "start"] # we use alpine for easier debugging FROM alpine:3.19 diff --git a/chart/values.schema.json b/chart/values.schema.json index 182d29d65..797b7ef4c 100755 --- a/chart/values.schema.json +++ b/chart/values.schema.json @@ -1052,7 +1052,8 @@ "properties": { "enabled": { "type": "boolean", - "description": "Enabled defines if the embedded etcd should be used." + "description": "Enabled defines if the embedded etcd should be used.", + "pro": true }, "migrateFromDeployedEtcd": { "type": "boolean", @@ -1082,7 +1083,8 @@ }, "isolatedControlPlane": { "$ref": "#/$defs/ExperimentalIsolatedControlPlane", - "description": "IsolatedControlPlane is a feature to run the vCluster control plane in a different Kubernetes cluster than the workloads themselves." + "description": "IsolatedControlPlane is a feature to run the vCluster control plane in a different Kubernetes cluster than the workloads themselves.", + "pro": true }, "virtualClusterKubeConfig": { "$ref": "#/$defs/VirtualClusterKubeConfig", @@ -1093,7 +1095,8 @@ "$ref": "#/$defs/DenyRule" }, "type": "array", - "description": "DenyProxyRequests denies certain requests in the vCluster proxy." + "description": "DenyProxyRequests denies certain requests in the vCluster proxy.", + "pro": true } }, "additionalProperties": false, @@ -1793,7 +1796,8 @@ "$ref": "#/$defs/ResolveDNS" }, "type": "array", - "description": "ResolveDNS allows to define extra DNS rules. This only works if embedded coredns is configured." + "description": "ResolveDNS allows to define extra DNS rules. This only works if embedded coredns is configured.", + "pro": true }, "advanced": { "$ref": "#/$defs/NetworkingAdvanced", diff --git a/cmd/vcluster/cmd/start.go b/cmd/vcluster/cmd/start.go index bdc6fff4c..cf5aed03c 100644 --- a/cmd/vcluster/cmd/start.go +++ b/cmd/vcluster/cmd/start.go @@ -6,6 +6,8 @@ import ( "os" "runtime/debug" + "github.com/go-logr/logr" + vconfig "github.com/loft-sh/vcluster/config" "github.com/loft-sh/vcluster/pkg/config" "github.com/loft-sh/vcluster/pkg/leaderelection" "github.com/loft-sh/vcluster/pkg/plugin" @@ -51,6 +53,16 @@ func ExecuteStart(ctx context.Context, options *StartOptions) error { return err } + if vconfig.ShouldCheckForProFeatures() && vConfig.IsProFeatureEnabled() { + log, err := logr.FromContext(ctx) + if err != nil { + return err + } + + log.Info("In order to use a Pro feature, please contact us at https://www.vcluster.com/pro-demo or downgrade by running `vcluster upgrade --version v0.19.5`") + os.Exit(0) + } + // get current namespace vConfig.ControlPlaneConfig, vConfig.ControlPlaneNamespace, vConfig.ControlPlaneService, vConfig.WorkloadConfig, vConfig.WorkloadNamespace, vConfig.WorkloadService, err = pro.GetRemoteClient(vConfig) if err != nil { diff --git a/cmd/vclusterctl/cmd/create.go b/cmd/vclusterctl/cmd/create.go index ed24b7fd9..9166cf8ee 100644 --- a/cmd/vclusterctl/cmd/create.go +++ b/cmd/vclusterctl/cmd/create.go @@ -179,11 +179,6 @@ func (cmd *CreateCmd) Run(ctx context.Context, args []string) error { return err } - err = cmd.prepare(ctx, args[0]) - if err != nil { - return err - } - var newExtraValues []string for _, value := range cmd.Values { decodedString, err := getBase64DecodedString(value) @@ -232,6 +227,16 @@ func (cmd *CreateCmd) Run(ctx context.Context, args []string) error { } return err } + + if config.ShouldCheckForProFeatures() && cfg.IsProFeatureEnabled() { + cmd.log.Warnf("In order to use a Pro feature, please contact us at https://www.vcluster.com/pro-demo or downgrade by running `vcluster upgrade --version v0.19.5`") + os.Exit(0) + } + } + + err = cmd.prepare(ctx, args[0]) + if err != nil { + return err } // resetting this as the base64 encoded strings should be removed and only valid file names should be kept. diff --git a/cmd/vclusterctl/cmd/pro/start.go b/cmd/vclusterctl/cmd/pro/start.go index 7037eef28..0f7517703 100644 --- a/cmd/vclusterctl/cmd/pro/start.go +++ b/cmd/vclusterctl/cmd/pro/start.go @@ -3,6 +3,7 @@ package pro import ( "context" "fmt" + "os" loftctlflags "github.com/loft-sh/loftctl/v3/cmd/loftctl/flags" "github.com/loft-sh/loftctl/v3/pkg/start" @@ -10,6 +11,7 @@ import ( "github.com/loft-sh/log/survey" "github.com/loft-sh/log/terminal" "github.com/loft-sh/vcluster/cmd/vclusterctl/cmd/find" + "github.com/loft-sh/vcluster/config" "github.com/loft-sh/vcluster/pkg/procli" "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" @@ -75,6 +77,11 @@ before running this command: } func (cmd *StartCmd) Run(ctx context.Context) error { + if config.ShouldCheckForProFeatures() { + cmd.Log.Warnf("In order to use a Pro feature, please contact us at https://www.vcluster.com/pro-demo or downgrade by running `vcluster upgrade --version v0.19.5`") + os.Exit(0) + } + // get version to deploy if cmd.Version == "latest" || cmd.Version == "" { cmd.Version = procli.MinimumVersionTag diff --git a/config/config.go b/config/config.go index 5c6e03540..99b1ef5b7 100644 --- a/config/config.go +++ b/config/config.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "os" "reflect" "regexp" "strings" @@ -101,6 +102,74 @@ func (c *Config) DecodeYAML(r io.Reader) error { return nil } +func (c *Config) Distro() string { + if c.ControlPlane.Distro.K3S.Enabled { + return K3SDistro + } else if c.ControlPlane.Distro.K0S.Enabled { + return K0SDistro + } else if c.ControlPlane.Distro.K8S.Enabled { + return K8SDistro + } else if c.ControlPlane.Distro.EKS.Enabled { + return EKSDistro + } + + return K8SDistro +} + +func ShouldCheckForProFeatures() bool { + return os.Getenv("FORCE_VCLUSTER_PRO") != "true" +} + +func (c *Config) IsProFeatureEnabled() bool { + if len(c.Networking.ResolveDNS) > 0 { + return true + } + + if c.ControlPlane.CoreDNS.Embedded { + return true + } + + if c.Distro() == K8SDistro || c.Distro() == EKSDistro { + if c.ControlPlane.BackingStore.Database.External.Enabled { + return true + } + } + + if c.ControlPlane.BackingStore.Etcd.Embedded.Enabled { + return true + } + + if len(c.Policies.CentralAdmission.MutatingWebhooks) > 0 { + return true + } + + if len(c.Policies.CentralAdmission.ValidatingWebhooks) > 0 { + return true + } + + if c.ControlPlane.HostPathMapper.Central { + return true + } + + if c.Experimental.SyncSettings.DisableSync { + return true + } + + if c.Experimental.SyncSettings.RewriteKubernetesService { + return true + } + + if c.Experimental.IsolatedControlPlane.Enabled { + return true + } + + if len(c.Experimental.DenyProxyRequests) > 0 { + return true + } + + return false +} + // ExportKubeConfig describes how vCluster should export the vCluster kubeconfig. type ExportKubeConfig struct { // Context is the name of the context within the generated kubeconfig to use. @@ -300,12 +369,16 @@ type Networking struct { ReplicateServices ReplicateServices `json:"replicateServices,omitempty"` // ResolveDNS allows to define extra DNS rules. This only works if embedded coredns is configured. - ResolveDNS []ResolveDNS `json:"resolveDNS,omitempty"` + ResolveDNS []ResolveDNS `json:"resolveDNS,omitempty" product:"pro"` // Advanced holds advanced network options. Advanced NetworkingAdvanced `json:"advanced,omitempty"` } +func (n Networking) JSONSchemaExtend(base *jsonschema.Schema) { + addProToJSONSchema(base, reflect.TypeOf(n)) +} + type ReplicateServices struct { // ToHost defines the services that should get synced from virtual cluster to the host cluster. If services are // synced to a different namespace than the virtual cluster is in, additional permissions for the other namespace @@ -713,12 +786,16 @@ type Etcd struct { type EtcdEmbedded struct { // Enabled defines if the embedded etcd should be used. - Enabled bool `json:"enabled,omitempty"` + Enabled bool `json:"enabled,omitempty" product:"pro"` // MigrateFromDeployedEtcd signals that vCluster should migrate from the deployed external etcd to embedded etcd. MigrateFromDeployedEtcd bool `json:"migrateFromDeployedEtcd,omitempty"` } +func (e EtcdEmbedded) JSONSchemaExtend(base *jsonschema.Schema) { + addProToJSONSchema(base, reflect.TypeOf(e)) +} + type EtcdDeploy struct { // Enabled defines that an external etcd should be deployed. Enabled bool `json:"enabled,omitempty"` @@ -1429,13 +1506,17 @@ type Experimental struct { MultiNamespaceMode ExperimentalMultiNamespaceMode `json:"multiNamespaceMode,omitempty"` // IsolatedControlPlane is a feature to run the vCluster control plane in a different Kubernetes cluster than the workloads themselves. - IsolatedControlPlane ExperimentalIsolatedControlPlane `json:"isolatedControlPlane,omitempty"` + IsolatedControlPlane ExperimentalIsolatedControlPlane `json:"isolatedControlPlane,omitempty" product:"pro"` // VirtualClusterKubeConfig allows you to override distro specifics and specify where vCluster will find the required certificates and vCluster config. VirtualClusterKubeConfig VirtualClusterKubeConfig `json:"virtualClusterKubeConfig,omitempty"` // DenyProxyRequests denies certain requests in the vCluster proxy. - DenyProxyRequests []DenyRule `json:"denyProxyRequests,omitempty" pro:"true"` + DenyProxyRequests []DenyRule `json:"denyProxyRequests,omitempty" product:"pro"` +} + +func (e Experimental) JSONSchemaExtend(base *jsonschema.Schema) { + addProToJSONSchema(base, reflect.TypeOf(e)) } type ExperimentalMultiNamespaceMode struct { @@ -1448,7 +1529,7 @@ type ExperimentalMultiNamespaceMode struct { type ExperimentalIsolatedControlPlane struct { // Enabled specifies if the isolated control plane feature should be enabled. - Enabled bool `json:"enabled,omitempty"` + Enabled bool `json:"enabled,omitempty" product:"pro"` // Headless states that Helm should deploy the vCluster in headless mode for the isolated control plane. Headless bool `json:"headless,omitempty"` @@ -1465,10 +1546,10 @@ type ExperimentalIsolatedControlPlane struct { type ExperimentalSyncSettings struct { // DisableSync will not sync any resources and disable most control plane functionality. - DisableSync bool `json:"disableSync,omitempty"` + DisableSync bool `json:"disableSync,omitempty" product:"pro"` // RewriteKubernetesService will rewrite the Kubernetes service to point to the vCluster service if disableSync is enabled - RewriteKubernetesService bool `json:"rewriteKubernetesService,omitempty"` + RewriteKubernetesService bool `json:"rewriteKubernetesService,omitempty" product:"pro"` // TargetNamespace is the namespace where the workloads should get synced to. TargetNamespace string `json:"targetNamespace,omitempty"` diff --git a/config/config_test.go b/config/config_test.go index f5f0a2c94..1418807b9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,6 +5,8 @@ import ( _ "embed" "io" "testing" + + "gotest.tools/assert" ) func TestConfig_DecodeYAML(t *testing.T) { @@ -102,3 +104,230 @@ controlPlane: }) } } + +func TestConfig_IsProFeatureEnabled(t *testing.T) { + tests := []struct { + name string + config *Config + expected bool + }{ + { + name: "No pro features", + config: &Config{}, + expected: false, + }, + { + name: "Empty ResolveDNS", + config: &Config{ + Networking: Networking{ + ResolveDNS: []ResolveDNS{}, + }, + }, + expected: false, + }, + { + name: "ResolveDNS used", + config: &Config{ + Networking: Networking{ + ResolveDNS: []ResolveDNS{ + { + Hostname: "wikipedia.com", + Target: ResolveDNSTarget{ + Hostname: "en.wikipedia.org", + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "Central Admission Control validating webhooks used", + config: &Config{ + Policies: Policies{ + CentralAdmission: CentralAdmission{ + ValidatingWebhooks: []ValidatingWebhookConfiguration{ + { + Kind: "ValidatingWebhookConfiguration", + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "Central Admission Control mutating webhooks used", + config: &Config{ + Policies: Policies{ + CentralAdmission: CentralAdmission{ + MutatingWebhooks: []MutatingWebhookConfiguration{ + { + Kind: "MutatingWebhookConfiguration", + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "Embedded etcd not used", + config: &Config{ + ControlPlane: ControlPlane{ + BackingStore: BackingStore{ + Etcd: Etcd{ + Embedded: EtcdEmbedded{ + Enabled: false, + }, + }, + }, + }, + }, + expected: false, + }, + { + name: "Embedded etcd used", + config: &Config{ + ControlPlane: ControlPlane{ + BackingStore: BackingStore{ + Etcd: Etcd{ + Embedded: EtcdEmbedded{ + Enabled: true, + }, + }, + }, + }, + }, + expected: true, + }, + { + name: "Host Path Mapper not used", + config: &Config{ + ControlPlane: ControlPlane{ + HostPathMapper: HostPathMapper{ + Enabled: false, + }, + }, + }, + expected: false, + }, + { + name: "Host Path Mapper used", + config: &Config{ + ControlPlane: ControlPlane{ + HostPathMapper: HostPathMapper{ + Enabled: true, + }, + }, + }, + expected: false, + }, + { + name: "Central Host Path Mapper not used", + config: &Config{ + ControlPlane: ControlPlane{ + HostPathMapper: HostPathMapper{ + Central: false, + }, + }, + }, + expected: false, + }, + { + name: "Central Host Path Mapper used", + config: &Config{ + ControlPlane: ControlPlane{ + HostPathMapper: HostPathMapper{ + Central: true, + }, + }, + }, + expected: true, + }, + { + name: "Pro Sync Settings not used", + config: &Config{ + Experimental: Experimental{ + SyncSettings: ExperimentalSyncSettings{ + DisableSync: false, + RewriteKubernetesService: false, + }, + }, + }, + expected: false, + }, + { + name: "Pro Sync Setting disableSync used", + config: &Config{ + Experimental: Experimental{ + SyncSettings: ExperimentalSyncSettings{ + DisableSync: true, + }, + }, + }, + expected: true, + }, + { + name: "Pro Sync Setting rewriteKubernetesService used", + config: &Config{ + Experimental: Experimental{ + SyncSettings: ExperimentalSyncSettings{ + RewriteKubernetesService: true, + }, + }, + }, + expected: true, + }, + { + name: "Isolated Control Plane not used", + config: &Config{ + Experimental: Experimental{ + IsolatedControlPlane: ExperimentalIsolatedControlPlane{ + Enabled: false, + }, + }, + }, + expected: false, + }, + { + name: "Isolated Control Plane used", + config: &Config{ + Experimental: Experimental{ + IsolatedControlPlane: ExperimentalIsolatedControlPlane{ + Enabled: true, + }, + }, + }, + expected: true, + }, + { + name: "Deny Proxy Requests not used", + config: &Config{ + Experimental: Experimental{ + DenyProxyRequests: []DenyRule{}, + }, + }, + expected: false, + }, + { + name: "Deny Proxy Requests used", + config: &Config{ + Experimental: Experimental{ + DenyProxyRequests: []DenyRule{ + { + Name: "test", + }, + }, + }, + }, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.config.IsProFeatureEnabled(), tt.expected) + }) + } +} diff --git a/devspace.yaml b/devspace.yaml index 161cdd07f..0494e297d 100644 --- a/devspace.yaml +++ b/devspace.yaml @@ -7,6 +7,7 @@ vars: DEVSPACE_FLAGS: "-n vcluster" SYNCER_IMAGE: ghcr.io/loft-sh/loft-enterprise/dev-vcluster COMMON_VALUES: ./test/commonValues.yaml + PRO_VALUES: ./test/proValues.yaml VALUES_FILE: ./test/e2e/values.yaml # Images DevSpace will build for vcluster @@ -202,6 +203,15 @@ profiles: valuesFiles: - ${COMMON_VALUES} - ${VALUES_FILE} + - name: test-pro + patches: + - op: add + path: deployments.vcluster-k8s.helm + value: + valuesFiles: + - ${COMMON_VALUES} + - ${VALUES_FILE} + - ${PRO_VALUES} commands: # e.g. devspace run test k3s diff --git a/pkg/config/config.go b/pkg/config/config.go index b556f2cd5..693743c43 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -57,20 +57,6 @@ func (v VirtualClusterConfig) EmbeddedDatabase() bool { return !v.Config.ControlPlane.BackingStore.Database.External.Enabled && !v.Config.ControlPlane.BackingStore.Etcd.Embedded.Enabled && !v.Config.ControlPlane.BackingStore.Etcd.Deploy.Enabled } -func (v VirtualClusterConfig) Distro() string { - if v.Config.ControlPlane.Distro.K3S.Enabled { - return config.K3SDistro - } else if v.Config.ControlPlane.Distro.K0S.Enabled { - return config.K0SDistro - } else if v.Config.ControlPlane.Distro.K8S.Enabled { - return config.K8SDistro - } else if v.Config.ControlPlane.Distro.EKS.Enabled { - return config.EKSDistro - } - - return config.K8SDistro -} - func (v VirtualClusterConfig) VirtualClusterKubeConfig() config.VirtualClusterKubeConfig { distroConfig := config.VirtualClusterKubeConfig{} switch v.Distro() { diff --git a/test/proValues.yaml b/test/proValues.yaml new file mode 100644 index 000000000..2a1123d9f --- /dev/null +++ b/test/proValues.yaml @@ -0,0 +1,5 @@ +networking: + resolveDNS: + - hostname: wikipedia.com + target: + hostname: en.wikipedia.org