Skip to content
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

feat: reroute workspace main urls to the dashboard when workspace is stopped #1386

Closed
wants to merge 1 commit into from
Closed
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
67 changes: 51 additions & 16 deletions controllers/devworkspace/solver/che_routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,24 +439,30 @@ func provisionMainWorkspaceRoute(cheCluster *v2alpha1.CheCluster, routing *dwo.D
dwId := routing.Spec.DevWorkspaceId
dwNamespace := routing.Namespace

cfg := gateway.CreateCommonTraefikConfig(
dwId,
fmt.Sprintf("PathPrefix(`/%s`)", dwId),
100,
getServiceURL(wsGatewayPort, dwId, dwNamespace),
[]string{"/" + dwId})

if util.IsOpenShift4 {
// on OpenShift, we need to set authorization header.
// This MUST come before Auth, because Auth needs Authorization header to be properly set.
cfg.AddAuthHeaderRewrite(dwId)
}
cfg := gateway.CreateEmptyTraefikConfig()

// authorize against kube-rbac-proxy in che-gateway. This will be needed for k8s native auth as well.
cfg.AddAuth(dwId, "http://127.0.0.1:8089?namespace="+dwNamespace)
if (routing.IsWorkspaceStopped()) {
routeMainUrlsToDashboardService(cfg, dwId, routing.Spec.Endpoints)
} else {
cfg = gateway.CreateCommonTraefikConfig(
dwId,
fmt.Sprintf("PathPrefix(`/%s`)", dwId),
100,
getServiceURL(wsGatewayPort, dwId, dwNamespace),
[]string{"/" + dwId})

if util.IsOpenShift4 {
// on OpenShift, we need to set authorization header.
// This MUST come before Auth, because Auth needs Authorization header to be properly set.
cfg.AddAuthHeaderRewrite(dwId)
}

// authorize against kube-rbac-proxy in che-gateway. This will be needed for k8s native auth as well.
cfg.AddAuth(dwId, "http://127.0.0.1:8089?namespace="+dwNamespace)

// make '/healthz' path of main endpoints reachable from outside
routeForHealthzEndpoint(cfg, dwId, routing.Spec.Endpoints)
// make '/healthz' path of main endpoints reachable from outside
routeForHealthzEndpoint(cfg, dwId, routing.Spec.Endpoints)
}

if contents, err := yaml.Marshal(cfg); err != nil {
return nil, err
Expand All @@ -476,6 +482,35 @@ func provisionMainWorkspaceRoute(cheCluster *v2alpha1.CheCluster, routing *dwo.D
}
}

func routeMainUrlsToDashboardService(cfg *gateway.TraefikConfig, dwId string, endpoints map[string]dwo.EndpointList) {
for componentName, endpoints := range endpoints {
for _, e := range endpoints {
if e.Attributes.GetString(string(dwo.TypeEndpointAttribute), nil) == string(dwo.MainEndpointType) {
middlewares := []string{}
routeName, endpointPath := createEndpointPath(&e, componentName)
routerName := fmt.Sprintf("%s-%s", dwId, routeName)

dashboardService := dwId+"-dashboard"
cfg.AddService(dashboardService, "http://che-dashboard:8080") // TODO: need to get che flavor instead of hardcoding `che`

cfg.HTTP.Routers[routerName] = &gateway.TraefikConfigRouter{
Rule: fmt.Sprintf("PathPrefix(`/%s%s`)", dwId, endpointPath),
Service: dashboardService,
Middlewares: middlewares,
Priority: 100,
}

cfg.AddReplacePathRegex(routerName, fmt.Sprintf("^/%s%s/(.*)", dwId, endpointPath), "/dashboard/$1")

if util.IsOpenShift4 {
cfg.AddAuthHeaderRewrite(routerName)
}
}
}
}

}

// makes '/healthz' path of main endpoints reachable from the outside
func routeForHealthzEndpoint(cfg *gateway.TraefikConfig, dwId string, endpoints map[string]dwo.EndpointList) {
for componentName, endpoints := range endpoints {
Expand Down
43 changes: 43 additions & 0 deletions controllers/devworkspace/solver/che_routing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,49 @@ func TestFinalize(t *testing.T) {
}
}

func TestWorkspaceStopped(t *testing.T) {
util.IsOpenShift = false
util.IsOpenShift4 = false
routing := relocatableDevWorkspaceRouting()
routing.Annotations = map[string]string{constants.DevWorkspaceStartedStatusAnnotation: "false"}

cl, slv, _ := getSpecObjects(t, routing)

meta := solvers.DevWorkspaceMetadata{
DevWorkspaceId: routing.Spec.DevWorkspaceId,
Namespace: routing.GetNamespace(),
PodSelector: routing.Spec.PodSelector,
}

if err := slv.WorkspaceStopped(routing, meta); err != nil {
t.Fatal(err)
}

cms := &corev1.ConfigMapList{}
cl.List(context.TODO(), cms)

var workspaceMainCfg *corev1.ConfigMap
for _, cfg := range cms.Items {
if cfg.Name == "wsid-route" && cfg.Namespace == "ns" {
workspaceMainCfg = cfg.DeepCopy()
}
}
assert.NotNil(t, workspaceMainCfg)
traefikMainConfig := workspaceMainCfg.Data["wsid.yml"]
assert.NotEmpty(t, traefikMainConfig)

workspaceMainConfig := gateway.TraefikConfig{}
assert.NoError(t, yaml.Unmarshal([]byte(traefikMainConfig), &workspaceMainConfig))
assert.Len(t, workspaceMainConfig.HTTP.Routers, 1)

wsid := "wsid-9999"
assert.Contains(t, workspaceMainConfig.HTTP.Routers, wsid)
assert.Len(t, workspaceMainConfig.HTTP.Routers[wsid].Middlewares, 1)
assert.Len(t, workspaceMainConfig.HTTP.Middlewares, 1)
assert.Len(t, workspaceMainConfig.HTTP.Services, 1)
assert.Equal(t, "http://che-dashboard:8080", workspaceMainConfig.HTTP.Services["wsid-dashboard"].LoadBalancer.Servers[0].URL)
}

func TestEndpointsAlwaysOnSecureProtocolsWhenExposedThroughGateway(t *testing.T) {
util.IsOpenShift = false
util.IsOpenShift4 = false
Expand Down
8 changes: 8 additions & 0 deletions controllers/devworkspace/solver/solver.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,11 @@ func findCheManager(cheManagerKey client.ObjectKey) (*v2alpha1.CheCluster, error

return &v2alpha1.CheCluster{}, &solvers.RoutingNotReady{Retry: 10 * time.Second}
}

func (c *CheRoutingSolver) WorkspaceStopped(routing *controllerv1alpha1.DevWorkspaceRouting, workspaceMeta solvers.DevWorkspaceMetadata) error {
cheManager, err := cheManagerOfRouting(routing)
if err != nil {
return err
}
return c.provisionRouting(&solvers.RoutingObjects{}, cheManager, routing, workspaceMeta)
}
6 changes: 6 additions & 0 deletions pkg/deploy/gateway/traefik_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type TraefikConfigService struct {
type TraefikConfigMiddleware struct {
StripPrefix *TraefikConfigStripPrefix `json:"stripPrefix,omitempty"`
ForwardAuth *TraefikConfigForwardAuth `json:"forwardAuth,omitempty"`
ReplacePathRegex *TraefikConfigReplacePathRegex `json:"replacePathRegex,omitempty"`
Plugin *TraefikPlugin `json:"plugin,omitempty"`
}

Expand All @@ -57,6 +58,11 @@ type TraefikConfigForwardAuth struct {
TLS *TraefikConfigTLS `json:"tls,omitempty"`
}

type TraefikConfigReplacePathRegex struct {
Regex string `json:"regex"`
Replacement string `json:"replacement"`
}

type TraefikPlugin struct {
HeaderRewrite *TraefikPluginHeaderRewrite `json:"header-rewrite,omitempty"`
}
Expand Down
32 changes: 29 additions & 3 deletions pkg/deploy/gateway/traefik_config_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
package gateway

const (
StripPrefixMiddlewareSuffix = "-strip-prefix"
HeaderRewriteMiddlewareSuffix = "-header-rewrite"
AuthMiddlewareSuffix = "-auth"
StripPrefixMiddlewareSuffix = "-strip-prefix"
HeaderRewriteMiddlewareSuffix = "-header-rewrite"
AuthMiddlewareSuffix = "-auth"
ReplacePathRegexMiddlewareSuffix = "-replace-path-regex"
)

func CreateEmptyTraefikConfig() *TraefikConfig {
Expand Down Expand Up @@ -49,11 +50,25 @@ func (cfg *TraefikConfig) AddComponent(componentName string, rule string, priori
},
},
}
cfg.AddService(componentName, serviceAddr)

if len(stripPrefixes) > 0 {
cfg.AddStripPrefix(componentName, stripPrefixes)
}
}

func (cfg *TraefikConfig) AddService(componentName string, serviceAddr string) {
cfg.HTTP.Services[componentName] = &TraefikConfigService{
LoadBalancer: TraefikConfigLoadbalancer{
Servers: []TraefikConfigLoadbalancerServer{
{
URL: serviceAddr,
},
},
},
}
}

func (cfg *TraefikConfig) AddStripPrefix(componentName string, prefixes []string) {
middlewareName := componentName + StripPrefixMiddlewareSuffix
cfg.HTTP.Routers[componentName].Middlewares = append(cfg.HTTP.Routers[componentName].Middlewares, middlewareName)
Expand Down Expand Up @@ -101,3 +116,14 @@ func (cfg *TraefikConfig) AddAuth(componentName string, authAddress string) {
},
}
}

func (cfg *TraefikConfig) AddReplacePathRegex(componentName string, regex string, replacement string) {
middlewareName := componentName + ReplacePathRegexMiddlewareSuffix
cfg.HTTP.Routers[componentName].Middlewares = append(cfg.HTTP.Routers[componentName].Middlewares, middlewareName)
cfg.HTTP.Middlewares[middlewareName] = &TraefikConfigMiddleware{
ReplacePathRegex: &TraefikConfigReplacePathRegex{
Regex: regex,
Replacement: replacement,
},
}
}
7 changes: 7 additions & 0 deletions pkg/deploy/gateway/traefik_config_util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ func TestTraefikConfig_AddComponent(t *testing.T) {
assert.Empty(t, cfg.HTTP.Middlewares)
}

func TestAddService(t *testing.T) {
cfg := CreateEmptyTraefikConfig()
cfg.AddService(testComponentName, "http://svc")
assert.Contains(t, cfg.HTTP.Services, testComponentName)
assert.Empty(t, cfg.HTTP.Middlewares)
}

func TestStripPrefixesWhenCreating(t *testing.T) {
check := func(cfg *TraefikConfig) {
assert.Contains(t, cfg.HTTP.Routers[testComponentName].Middlewares, testComponentName+StripPrefixMiddlewareSuffix)
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.