Skip to content

Commit

Permalink
Merge pull request educates#541 from GrahamDumpleton/lookup-service-f…
Browse files Browse the repository at this point in the history
…ixes

Lookup service updates.
  • Loading branch information
GrahamDumpleton authored Aug 11, 2024
2 parents 5087b7b + a0d4a85 commit 228c01a
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,23 @@ metadata:
rules:
#! We need ability to watch for changes to CRDs so kopf can tell if its own
#! custom resources have changed.
#! NOTE: Disabled as this results in Educates not being able to be uninstalled
#! when any of the lookup service configuration exists.
#! - apiGroups:
#! - apiextensions.k8s.io
#! resources:
#! - customresourcedefinitions
#! verbs:
#! - get
#! - list
#! - watch
#! We need the ability to watch for namespace changes. This is required by
#! kopf to know when to start and stop watching for changes to the specific
#! namespace is has been told to monitor.
- apiGroups:
- apiextensions.k8s.io
- ""
resources:
- customresourcedefinitions
- namespaces
verbs:
- get
- list
Expand All @@ -23,17 +36,6 @@ rules:
- events
verbs:
- create

#! We need the ability to watch for namespace changes. Also believe this is
#! required by kopf.
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
#! We need read/write access to the ClusterConfig, ClientConfig and
#! TenantConfig custom resources from the lookup.educates.dev API group.
- apiGroups:
Expand Down Expand Up @@ -78,22 +80,23 @@ kind: ClusterRole
metadata:
name: educates-remote-access
rules:
- apiGroups:
- training.educates.dev
resources:
- trainingportals
- workshopenvironments
- workshopsessions
- workshopallocations
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- customresourcedefinitions
verbs:
- get
- list
- watch
- apiGroups:
- training.educates.dev
resources:
- trainingportals
- workshopenvironments
- workshopsessions
- workshopallocations
- workshops
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- customresourcedefinitions
verbs:
- get
- list
- watch
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ spec:
type: object
required:
- client
- tenants
- roles
properties:
client:
Expand Down
1 change: 1 addition & 0 deletions client-programs/pkg/cmd/admin_cmd_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func (p *ProjectInfo) NewAdminCmdGroup() *cobra.Command {
Message: "Available Commands:",
Commands: []*cobra.Command{
p.NewAdminPlatformCmdGroup(),
p.NewAdminLookupCmdGroup(),
p.NewAdminDiagnosticsCmdGroup(),
},
},
Expand Down
32 changes: 32 additions & 0 deletions client-programs/pkg/cmd/admin_lookup_cmd_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cmd

import (
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/util/templates"
)

func (p *ProjectInfo) NewAdminLookupCmdGroup() *cobra.Command {
var c = &cobra.Command{
Use: "lookup",
Short: "Manage Educates lookup service",
}

// Use a command group as it allows us to dictate the order in which they
// are displayed in the help message, as otherwise they are displayed in
// sort order.

commandGroups := templates.CommandGroups{
{
Message: "Available Commands:",
Commands: []*cobra.Command{
p.NewAdminLookupKubeconfigCmd(),
},
},
}

commandGroups.Add(c)

templates.ActsAsRootCommand(c, []string{"--help"}, commandGroups...)

return c
}
133 changes: 133 additions & 0 deletions client-programs/pkg/cmd/admin_lookup_kubeconfig_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package cmd

import (
"context"
"encoding/base64"
"fmt"
"io/ioutil"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/vmware-tanzu-labs/educates-training-platform/client-programs/pkg/cluster"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type LookupConfigOptions struct {
KubeconfigOptions
OutputPath string
}

func (o *LookupConfigOptions) Run() error {
var err error

clusterConfig, err := cluster.NewClusterConfigIfAvailable(o.Kubeconfig, o.Context)
if err != nil {
return err
}

client, err := clusterConfig.GetClient()

if err != nil {
return err
}

// We need to fetch the secret called "remote-access-token" from the
// "educates" namespace. This contains a Kubernetes access token secret
// giving access to just the Educates custom resources.

secretsClient := client.CoreV1().Secrets("educates")

secret, err := secretsClient.Get(context.TODO(), "remote-access-token", metav1.GetOptions{})

if err != nil {
return errors.Wrapf(err, "unable to fetch remote-access secret")
}

// Within the secret are data fields for "ca.crt" and "token". We need to
// extract these and use them to create a kubeconfig file. Note that there
// is no "server" property in the secret, so when constructing the kubeconfig
// we need to use the server from the same cluster as we are requesting the
// secret from.

caCrt := secret.Data["ca.crt"]
token := secret.Data["token"]

// Get the server from the client for Kubernetes cluster access.

serverScheme := client.CoreV1().RESTClient().Get().URL().Scheme
serverHost := client.CoreV1().RESTClient().Get().URL().Host

serverUrl := fmt.Sprintf("%s://%s", serverScheme, serverHost)

// Construct the kubeconfig file. We need to base64 encode the ca.crt file
// as it is a binary file.

kubeconfig := fmt.Sprintf(`apiVersion: v1
kind: Config
clusters:
- name: training-platform
cluster:
server: %s
certificate-authority-data: %s
contexts:
- name: training-platform
context:
cluster: training-platform
user: remote-access
current-context: training-platform
users:
- name: remote-access
user:
token: %s
`, serverUrl, base64.StdEncoding.EncodeToString(caCrt), token)

// Write out the kubeconfig to the output path if provided, otherwise
// print it to stdout.

if o.OutputPath != "" {
err = ioutil.WriteFile(o.OutputPath, []byte(kubeconfig), 0644)

if err != nil {
return errors.Wrapf(err, "unable to write kubeconfig to %s", o.OutputPath)
}
} else {
fmt.Print(kubeconfig)
}

return nil
}

func (p *ProjectInfo) NewAdminLookupKubeconfigCmd() *cobra.Command {
var o LookupConfigOptions

var c = &cobra.Command{
Args: cobra.NoArgs,
Use: "kubeconfig",
Short: "Fetch kubeconfig for lookup service remote access",
RunE: func(cmd *cobra.Command, _ []string) error {
return o.Run()
},
}

c.Flags().StringVar(
&o.Kubeconfig,
"kubeconfig",
"",
"kubeconfig file to use instead of $KUBECONFIG or $HOME/.kube/config",
)
c.Flags().StringVar(
&o.Context,
"context",
"",
"Context to use from Kubeconfig",
)
c.Flags().StringVarP(
&o.OutputPath,
"output",
"o",
"",
"Path to write Kubeconfig file to",
)

return c
}
6 changes: 3 additions & 3 deletions client-programs/pkg/cmd/admin_platform_config_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var (
`
)

type PkatformConfigOptions struct {
type PlatformConfigOptions struct {
KubeconfigOptions
Domain string
Version string
Expand All @@ -41,7 +41,7 @@ type PkatformConfigOptions struct {
Verbose bool
}

func (o *PkatformConfigOptions) Run() error {
func (o *PlatformConfigOptions) Run() error {
installer := installer.NewInstaller()

if o.FromCluster {
Expand All @@ -64,7 +64,7 @@ func (o *PkatformConfigOptions) Run() error {
}

func (p *ProjectInfo) NewAdminPlatformConfigCmd() *cobra.Command {
var o PkatformConfigOptions
var o PlatformConfigOptions

var c = &cobra.Command{
Args: cobra.NoArgs,
Expand Down
29 changes: 27 additions & 2 deletions client-programs/pkg/cmd/cluster_portal_create_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"context"
"strings"

"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand All @@ -19,6 +20,7 @@ type ClusterConfigViewOptions struct {
Password string
ThemeName string
CookieDomain string
Labels []string
}

func (o *ClusterConfigViewOptions) Run(isPasswordSet bool) error {
Expand All @@ -44,7 +46,7 @@ func (o *ClusterConfigViewOptions) Run(isPasswordSet bool) error {

// Update the training portal, creating it if necessary.

err = createTrainingPortal(dynamicClient, o.Portal, o.Capacity, o.Password, isPasswordSet, o.ThemeName, o.CookieDomain)
err = createTrainingPortal(dynamicClient, o.Portal, o.Capacity, o.Password, isPasswordSet, o.ThemeName, o.CookieDomain, o.Labels)

if err != nil {
return err
Expand Down Expand Up @@ -110,11 +112,18 @@ func (p *ProjectInfo) NewClusterPortalCreateCmd() *cobra.Command {
"",
"override cookie domain used by training portal and workshops",
)
c.Flags().StringSliceVarP(
&o.Labels,
"labels",
"l",
[]string{},
"label overrides for portal",
)

return c
}

func createTrainingPortal(client dynamic.Interface, portal string, capacity uint, password string, isPasswordSet bool, themeName string, cookieDomain string) error {
func createTrainingPortal(client dynamic.Interface, portal string, capacity uint, password string, isPasswordSet bool, themeName string, cookieDomain string, labels []string) error {
trainingPortalClient := client.Resource(trainingPortalResource)

_, err := trainingPortalClient.Get(context.TODO(), portal, metav1.GetOptions{})
Expand All @@ -133,6 +142,21 @@ func createTrainingPortal(client dynamic.Interface, portal string, capacity uint
password = randomPassword(12)
}

type LabelDetails struct {
Name string `json:"name"`
Value string `json:"value"`
}

var labelOverrides []LabelDetails

for _, value := range labels {
parts := strings.SplitN(value, "=", 2)
labelOverrides = append(labelOverrides, LabelDetails{
Name: parts[0],
Value: parts[1],
})
}

trainingPortal.SetUnstructuredContent(map[string]interface{}{
"apiVersion": "training.educates.dev/v1beta1",
"kind": "TrainingPortal",
Expand Down Expand Up @@ -174,6 +198,7 @@ func createTrainingPortal(client dynamic.Interface, portal string, capacity uint
}{
Domain: cookieDomain,
},
"labels": labelOverrides,
},
"workshops": []interface{}{},
},
Expand Down
6 changes: 4 additions & 2 deletions lookup-service/service/caches/tenants.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ def allowed_access_to_cluster(self, cluster: ClusterConfig) -> bool:
"metadata": {
"name": cluster.name,
"uid": cluster.uid,
"labels": cluster.labels,
"labels": {
item["name"]: item["value"] for item in list(cluster.labels)
},
},
}

Expand All @@ -45,7 +47,7 @@ def allowed_access_to_portal(self, portal: TrainingPortal) -> bool:
resource = {
"metadata": {
"name": portal.name,
"labels": portal.labels,
"labels": {item["name"]: item["value"] for item in list(portal.labels)},
},
}

Expand Down
Loading

0 comments on commit 228c01a

Please sign in to comment.