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

Import Role by Name #206

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,21 @@ Build binary:
make build
```

### Develop locally

Start KIND Cluster `local-dev`, build source code and package into an image.
Deploy that image on the `local-dev` Cluster
```console
make local-deploy
```

```
kubectl apply -f ./local-deploy/manifests/keycloak-provider-secret.yaml
kubectl apply -f ./local-deploy/manifests/keycloak-provider-config.yaml
kubectl patch DeploymentRuntimeConfig runtimeconfig-provider-keycloak --type='merge' --patch-file ./local-deploy/manifests/keycloak-provider-deployment-runtime-config.patch.yaml
```


## Regression Tests
TODO: Add regression test docs

Expand Down
7 changes: 5 additions & 2 deletions config/external_name.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ Copyright 2022 Upbound Inc.

package config

import "github.com/crossplane/upjet/pkg/config"
import (
"github.com/crossplane-contrib/provider-keycloak/config/role"
"github.com/crossplane/upjet/pkg/config"
)

// ExternalNameConfigs contains all external name configurations for this
// provider.
Expand All @@ -30,7 +33,7 @@ var ExternalNameConfigs = map[string]config.ExternalName{
"keycloak_openid_client_service_account_role": config.IdentifierFromProvider,
"keycloak_realm": config.IdentifierFromProvider,
"keycloak_required_action": config.IdentifierFromProvider,
"keycloak_role": config.IdentifierFromProvider,
"keycloak_role": role.IdentifierByNameLookup,
"keycloak_user_groups": config.IdentifierFromProvider,
"keycloak_user_roles": config.IdentifierFromProvider,
"keycloak_users_permissions": config.IdentifierFromProvider,
Expand Down
72 changes: 71 additions & 1 deletion config/role/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
package role

import "github.com/crossplane/upjet/pkg/config"
import (
"bytes"
"context"
"errors"
"github.com/crossplane-contrib/provider-keycloak/internal/clients"
"github.com/crossplane/upjet/pkg/config"
"github.com/keycloak/terraform-provider-keycloak/keycloak"
"strings"
"text/template"
)

// Configure configures individual resources by adding custom ResourceConfigurators.
func Configure(p *config.Provider) {
Expand All @@ -13,3 +22,64 @@
}
})
}

var IdentifierByNameLookup = config.ExternalName{

Check failure on line 26 in config/role/config.go

View workflow job for this annotation

GitHub Actions / lint

exported var `IdentifierByNameLookup` should have comment or be unexported (golint)
SetIdentifierArgumentFn: config.NopSetIdentifierArgument,
GetExternalNameFn: GetExternalNameFromRole,
GetIDFn: GetIdFromRole,
DisableNameInitializer: true,
}

func GetIdFromRole(ctx context.Context, externalName string, parameters map[string]any, terraformProviderConfig map[string]any) (string, error) {

Check failure on line 33 in config/role/config.go

View workflow job for this annotation

GitHub Actions / lint

exported function `GetIdFromRole` should have comment or be unexported (golint)

kcClient, err := clients.NewKeycloakClient(ctx, terraformProviderConfig)
if err != nil {
return "", err
}

realmId, realmIdExists := parameters["realm_id"]

Check failure on line 40 in config/role/config.go

View workflow job for this annotation

GitHub Actions / lint

var `realmId` should be `realmID` (golint)
if !realmIdExists {
return "", errors.New("realmId not set")
}

name, nameExists := parameters["name"]
if !nameExists {
return "", errors.New("name not set")
}

clientId, clientIdExists := parameters["client_id"]

Check failure on line 50 in config/role/config.go

View workflow job for this annotation

GitHub Actions / lint

var `clientId` should be `clientID` (golint)
if !clientIdExists {
clientId = ""
}

role, err := kcClient.GetRoleByName(ctx, realmId.(string), clientId.(string), name.(string))
if err != nil {
var apiErr *keycloak.ApiError
if errors.As(err, &apiErr) && apiErr.Code == 404 {
return "", nil
}

return "", err
}

return role.Id, nil
}

func GetExternalNameFromRole(tfState map[string]any) (string, error) {

Check failure on line 68 in config/role/config.go

View workflow job for this annotation

GitHub Actions / lint

exported function `GetExternalNameFromRole` should have comment or be unexported (golint)
t, err := template.New("getExternalName").Funcs(template.FuncMap{
"ToLower": strings.ToLower,
"ToUpper": strings.ToUpper,
}).Parse(`{{if eq .client_id ""}}{{ .realm_id }}/{{ .name }}{{else}}{{ .realm_id }}/{{ .client_id }}/{{ .name }}{{end}}`)

if err != nil {
return "", err
}

var buf bytes.Buffer
err = t.Execute(&buf, tfState)
if err != nil {
return "", err
}
externalName := buf.String()
return externalName, nil
}
69 changes: 69 additions & 0 deletions internal/clients/keycloak_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package clients

import (
"context"
"fmt"
"github.com/crossplane/upjet/pkg/terraform"
"github.com/keycloak/terraform-provider-keycloak/keycloak"
)

func NewKeycloakClient(ctx context.Context, terraformProviderConfig map[string]any) (*keycloak.KeycloakClient, error) {

Check failure on line 10 in internal/clients/keycloak_client.go

View workflow job for this annotation

GitHub Actions / lint

exported function `NewKeycloakClient` should have comment or be unexported (golint)
config := terraformProviderConfig["configuration"].(terraform.ProviderConfiguration)

url := tryGetString(config, "url", "")
basePath := tryGetString(config, "base_path", "")
clientId := tryGetString(config, "client_id", "")

Check failure on line 15 in internal/clients/keycloak_client.go

View workflow job for this annotation

GitHub Actions / lint

var `clientId` should be `clientID` (golint)
clientSecret := tryGetString(config, "client_secret", "")
username := tryGetString(config, "username", "")
password := tryGetString(config, "password", "")
realm := tryGetString(config, "realm", "master")
initialLogin := tryGetBool(config, "initial_login", true)
clientTimeout := tryGetInt(config, "client_timeout", 15)
tlsInsecureSkipVerify := tryGetBool(config, "tls_insecure_skip_verify", false)
rootCaCertificate := tryGetString(config, "root_ca_certificate", "")
redHatSSO := tryGetBool(config, "initial_login", false)
additionalHeaders := tryGetMap(config, "additional_headers")
userAgent := fmt.Sprintf("Crossplane Keycloak Provider")

Check failure on line 26 in internal/clients/keycloak_client.go

View workflow job for this annotation

GitHub Actions / lint

S1039: unnecessary use of fmt.Sprintf (gosimple)

keycloakClient, err := keycloak.NewKeycloakClient(ctx, url, basePath, clientId, clientSecret, realm, username, password, initialLogin, clientTimeout, rootCaCertificate, tlsInsecureSkipVerify, userAgent, redHatSSO, additionalHeaders)
if err != nil {
return nil, err
}
return keycloakClient, nil

}

func tryGetString(m map[string]any, key string, defaultValue string) string {
value, ok := m[key]
if ok {
return value.(string)
}
return defaultValue
}

func tryGetBool(m map[string]any, key string, defaultValue bool) bool {
value, ok := m[key]
if ok {
return value.(bool)
}
return defaultValue
}

func tryGetInt(m map[string]any, key string, defaultValue int) int {
value, ok := m[key]
if ok {
return value.(int)
}
return defaultValue
}

func tryGetMap(m map[string]any, key string) map[string]string {
value, ok := m[key]
result := make(map[string]string)
if ok {
for k, v := range value.(map[string]interface{}) {
result[k] = v.(string)
}
}
return result
}
13 changes: 13 additions & 0 deletions local-deploy/manifests/keycloak-provider-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
apiVersion: keycloak.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: keycloak-provider-config
namespace: upbound-system
spec:
credentials:
source: Secret
secretRef:
name: keycloak-credentials
key: credentials
namespace: upbound-system
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
spec:
deploymentTemplate:
spec:
replicas: 0
19 changes: 19 additions & 0 deletions local-deploy/manifests/keycloak-provider-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
apiVersion: v1
kind: Secret
metadata:
name: keycloak-credentials
namespace: upbound-system
labels:
type: provider-credentials
type: Opaque
stringData:
credentials: |
{
"client_id":"admin-cli",
"username": "<username>",
"password": "<password>",
"url": "http://<host>:<port>",
"base_path": "/",
"realm": "master"
}
14 changes: 14 additions & 0 deletions local-deploy/manifests/testclientrole.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
apiVersion: role.keycloak.crossplane.io/v1alpha1
kind: Role
metadata:
name: test-client
namespace: upbound-system
spec:
forProvider:
realmId: "master" # The realm to which this user belongs
name: "abc" # The username for this user
clientId: "terraform"
description: "abc"
providerConfigRef:
name: "keycloak-provider-config" # Reference to the provider configuration
13 changes: 13 additions & 0 deletions local-deploy/manifests/testrole.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
apiVersion: role.keycloak.crossplane.io/v1alpha1
kind: Role
metadata:
name: test
namespace: upbound-system
spec:
forProvider:
realmId: "master" # The realm to which this user belongs
name: "abc" # The username for this user
description: "abc"
providerConfigRef:
name: "keycloak-provider-config" # Reference to the provider configuration
Loading