Skip to content

Commit

Permalink
Detect if the user to init on Tenant already has a project on OSO (OS…
Browse files Browse the repository at this point in the history
…IO#1446)

Add function to list projects on OSO for the user, given his/her OSO token.
Check that there's no project, or a single project named after the user.
Include option in User service to support custom HTTP transport.
Rename vars in controller.Tenant to avoid collision with packages (`user` and `cluster`).
Add test on the controller.TenantController.Setup() method using `go-vcr`.
Add a function in `openshift` pkg to retrieve the name of the user's projects (and factorize
the code to perform the GET request on the Openshift API endpoint).

Fixes openshiftio/openshift.io#1446

Signed-off-by: Xavier Coulon <[email protected]>
  • Loading branch information
xcoulon committed Mar 9, 2018
1 parent 3558f59 commit 57b2b97
Show file tree
Hide file tree
Showing 20 changed files with 885 additions and 102 deletions.
3 changes: 1 addition & 2 deletions auth/auth_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

authclient "github.com/fabric8-services/fabric8-tenant/auth/client"
"github.com/fabric8-services/fabric8-tenant/configuration"
"github.com/fabric8-services/fabric8-wit/log"
goaclient "github.com/goadesign/goa/client"
)

Expand All @@ -33,7 +32,7 @@ func NewClient(authURL, token string, options ...configuration.HTTPClientOption)
})
client.Host = u.Host
client.Scheme = u.Scheme
log.Debug(nil, map[string]interface{}{"host": client.Host, "scheme": client.Scheme}, "initializing auth client")
// log.Debug(nil, map[string]interface{}{"host": client.Host, "scheme": client.Scheme}, "initializing auth client")
return client, nil
}

Expand Down
3 changes: 3 additions & 0 deletions cluster/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cluster
import (
"context"
"fmt"

"github.com/fabric8-services/fabric8-wit/log"
)

// Resolve a func to resolve a cluster
Expand All @@ -12,6 +14,7 @@ type Resolve func(ctx context.Context, target string) (*Cluster, error)
func NewResolve(clusters []*Cluster) Resolve {
return func(ctx context.Context, target string) (*Cluster, error) {
for _, cluster := range clusters {
log.Debug(nil, map[string]interface{}{"target_url": cleanURL(target), "cluster_url": cleanURL(cluster.APIURL)}, "comparing URLs...")
if cleanURL(target) == cleanURL(cluster.APIURL) {
return cluster, nil
}
Expand Down
7 changes: 6 additions & 1 deletion cluster/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ func TestResolveCluster(t *testing.T) {
defer r.Stop()
authURL := "http://authservice"
resolveToken := token.NewResolve(authURL, configuration.WithRoundTripper(r.Transport))
saToken, err := testsupport.NewToken("tenant_service", "../test/private_key.pem")
saToken, err := testsupport.NewToken(
map[string]interface{}{
"sub": "tenant_service",
},
"../test/private_key.pem",
)
require.NoError(t, err)

t.Run("ok", func(t *testing.T) {
Expand Down
106 changes: 66 additions & 40 deletions controller/tenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,50 +57,76 @@ func NewTenantController(

// Setup runs the setup action.
func (c *TenantController) Setup(ctx *app.SetupTenantContext) error {
userToken := goajwt.ContextJWT(ctx)
if userToken == nil {
usrToken := goajwt.ContextJWT(ctx)
if usrToken == nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Missing JWT token"))
}
ttoken := &TenantToken{token: userToken}
exists := c.tenantService.Exists(ttoken.Subject())
if exists {
return ctx.Conflict()
tenantToken := &TenantToken{token: usrToken}
if c.tenantService.Exists(tenantToken.Subject()) {
log.Error(ctx, map[string]interface{}{"tenant_id": tenantToken.Subject()}, "a tenant with the same ID already exists")
return jsonapi.JSONErrorResponse(ctx, errors.NewDataConflictError(fmt.Sprintf("a tenant with the same ID already exists: %s", tenantToken.Subject())))
}

// fetch the cluster the user belongs to
user, err := c.userService.GetUser(ctx, ttoken.Subject())
usr, err := c.userService.GetUser(ctx, tenantToken.Subject())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}

if user.Cluster == nil {
if usr.Cluster == nil {
log.Error(ctx, nil, "no cluster defined for tenant")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, fmt.Errorf("unable to provision to undefined cluster")))
}

// fetch the users cluster token
openshiftUsername, openshiftUserToken, err := c.resolveTenant(ctx, *user.Cluster, userToken.Raw)
openshiftUsername, openshiftUserToken, err := c.resolveTenant(ctx, *usr.Cluster, usrToken.Raw)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch tenant token from auth")
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Could not resolve user token"))
}
log.Debug(ctx, map[string]interface{}{
"openshift_username": openshiftUsername,
"openshift_token": openshiftUserToken},
"resolved user on cluster")

// fetch the cluster info
cluster, err := c.resolveCluster(ctx, *user.Cluster)
clustr, err := c.resolveCluster(ctx, *usr.Cluster)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch cluster")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, err))
}
log.Debug(ctx, map[string]interface{}{
"cluster_api_url": clustr.APIURL,
"user_id": tenantToken.Subject().String()},
"resolved cluster for user")

// check if the user already has a project on the cluster
userProjects, err := openshift.ListProjects(ctx, clustr.APIURL, openshiftUserToken)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *usr.Cluster,
}, "unable to fetch user's projects on the cluster")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, err))
}
log.Debug(ctx, map[string]interface{}{"cluster_api_url": clustr.APIURL, "user_id": tenantToken.Subject().String()}, "resolved cluster for user")
if len(userProjects) == 1 && userProjects[0] != openshiftUsername {
log.Error(ctx, map[string]interface{}{
"cluster_url": *usr.Cluster,
"openshift_user_project": userProjects[0],
"openshift_user_name": openshiftUsername,
}, "user already has a project on the cluster, with a different name")
return jsonapi.JSONErrorResponse(ctx, errors.NewDataConflictError(fmt.Sprintf("user already has a project on the cluster, with a different name: %s", userProjects[0])))
}

// create openshift config
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, user, cluster.User, cluster.Token, cluster.APIURL)
tenant := &tenant.Tenant{ID: ttoken.Subject(), Email: ttoken.Email()}
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, usr, clustr.User, clustr.Token, clustr.APIURL)
tenant := &tenant.Tenant{ID: tenantToken.Subject(), Email: tenantToken.Email()}
err = c.tenantService.SaveTenant(tenant)
if err != nil {
log.Error(ctx, map[string]interface{}{
Expand Down Expand Up @@ -134,48 +160,48 @@ func (c *TenantController) Setup(ctx *app.SetupTenantContext) error {

// Update runs the setup action.
func (c *TenantController) Update(ctx *app.UpdateTenantContext) error {
userToken := goajwt.ContextJWT(ctx)
if userToken == nil {
usrToken := goajwt.ContextJWT(ctx)
if usrToken == nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Missing JWT token"))
}
ttoken := &TenantToken{token: userToken}
tenant, err := c.tenantService.GetTenant(ttoken.Subject())
tenantToken := &TenantToken{token: usrToken}
tenant, err := c.tenantService.GetTenant(tenantToken.Subject())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewNotFoundError("tenants", ttoken.Subject().String()))
return jsonapi.JSONErrorResponse(ctx, errors.NewNotFoundError("tenants", tenantToken.Subject().String()))
}

// fetch the cluster the user belongs to
user, err := c.userService.GetUser(ctx, ttoken.Subject())
usr, err := c.userService.GetUser(ctx, tenantToken.Subject())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}

if user.Cluster == nil {
if usr.Cluster == nil {
log.Error(ctx, nil, "no cluster defined for tenant")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, fmt.Errorf("unable to provision to undefined cluster")))
}

// fetch the users cluster token
openshiftUsername, _, err := c.resolveTenant(ctx, *user.Cluster, userToken.Raw)
openshiftUsername, _, err := c.resolveTenant(ctx, *usr.Cluster, usrToken.Raw)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch tenant token from auth")
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Could not resolve user token"))
}

cluster, err := c.resolveCluster(ctx, *user.Cluster)
clustr, err := c.resolveCluster(ctx, *usr.Cluster)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch cluster")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, err))
}

log.Info(ctx, map[string]interface{}{"cluster_api_url": clustr.APIURL, "user_id": tenantToken.Subject().String()}, "resolved cluster for user")
// create openshift config
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, user, cluster.User, cluster.Token, cluster.APIURL)
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, usr, clustr.User, clustr.Token, clustr.APIURL)

go func() {
ctx := ctx
Expand All @@ -201,46 +227,46 @@ func (c *TenantController) Update(ctx *app.UpdateTenantContext) error {

// Clean runs the setup action for the tenant namespaces.
func (c *TenantController) Clean(ctx *app.CleanTenantContext) error {
userToken := goajwt.ContextJWT(ctx)
if userToken == nil {
usrToken := goajwt.ContextJWT(ctx)
if usrToken == nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Missing JWT token"))
}
ttoken := &TenantToken{token: userToken}
tenantToken := &TenantToken{token: usrToken}

// fetch the cluster the user belongs to
user, err := c.userService.GetUser(ctx, ttoken.Subject())
usr, err := c.userService.GetUser(ctx, tenantToken.Subject())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}

// fetch the users cluster token
openshiftUsername, _, err := c.resolveTenant(ctx, *user.Cluster, userToken.Raw)
openshiftUsername, _, err := c.resolveTenant(ctx, *usr.Cluster, usrToken.Raw)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch tenant token from auth")
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Could not resolve user token"))
}

cluster, err := c.resolveCluster(ctx, *user.Cluster)
clustr, err := c.resolveCluster(ctx, *usr.Cluster)
if err != nil {
log.Error(ctx, map[string]interface{}{
"err": err,
"cluster_url": *user.Cluster,
"cluster_url": *usr.Cluster,
}, "unable to fetch cluster")
return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, err))
}

// create openshift config
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, user, cluster.User, cluster.Token, cluster.APIURL)
openshiftConfig := openshift.NewConfig(c.defaultOpenshiftConfig, usr, clustr.User, clustr.Token, clustr.APIURL)

err = openshift.CleanTenant(ctx, openshiftConfig, openshiftUsername, c.templateVars, ctx.Remove)
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}
if ctx.Remove {
err = c.tenantService.DeleteAll(ttoken.Subject())
err = c.tenantService.DeleteAll(tenantToken.Subject())
if err != nil {
return jsonapi.JSONErrorResponse(ctx, err)
}
Expand All @@ -255,8 +281,8 @@ func (c *TenantController) Show(ctx *app.ShowTenantContext) error {
return jsonapi.JSONErrorResponse(ctx, errors.NewUnauthorizedError("Missing JWT token"))
}

ttoken := &TenantToken{token: token}
tenantID := ttoken.Subject()
tenantToken := &TenantToken{token: token}
tenantID := tenantToken.Subject()
tenant, err := c.tenantService.GetTenant(tenantID)
if err != nil {
return jsonapi.JSONErrorResponse(ctx, errors.NewNotFoundError("tenants", tenantID.String()))
Expand Down
Loading

0 comments on commit 57b2b97

Please sign in to comment.