Skip to content

Commit

Permalink
Add: Functionality to sync users and set their organization permissio…
Browse files Browse the repository at this point in the history
…ns (#4)

Co-authored-by: David Gubler <[email protected]>
  • Loading branch information
davidgubler and David Gubler authored Jul 3, 2023
1 parent ddc26dc commit 2f013c7
Show file tree
Hide file tree
Showing 5 changed files with 461 additions and 167 deletions.
50 changes: 35 additions & 15 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
orgs "github.com/appuio/control-api/apis/organization/v1"
controlapi "github.com/appuio/control-api/apis/v1"
controller "github.com/appuio/grafana-organizations-operator/pkg"
grafana "github.com/grafana/grafana-api-golang-client"
"k8s.io/apimachinery/pkg/runtime/serializer"
Expand Down Expand Up @@ -43,16 +44,41 @@ func main() {
klog.Infof("GRAFANA_URL: %s\n", GrafanaUrl)
klog.Infof("GRAFANA_USERNAME: %s\n", GrafanaUsername)

controlApiConfig := &rest.Config{}
controlApiConfig.BearerToken = ControlApiToken
controlApiConfig.Host = ControlApiUrl
controlApiConfig.GroupVersion = &orgs.GroupVersion
controlApiConfig.APIPath = "/apis"
controlApiConfig.NegotiatedSerializer = serializer.NewCodecFactory(scheme.Scheme)
// Because of the strange design of the k8s client we actually need two client objects, which both internally use the same httpClient.
// To make this work we also need three (!) config objects, a common one for the httpClient and one for each k8s client.
commonConfig := &rest.Config{}
commonConfig.BearerToken = ControlApiToken
httpClient, err := rest.HTTPClientFor(commonConfig)
if err != nil {
klog.Errorf("Could not create Control API httpClient: %v\n", err)
os.Exit(1)
}

organizationAppuioIoConfig := &rest.Config{}
organizationAppuioIoConfig.Host = ControlApiUrl
organizationAppuioIoConfig.APIPath = "/apis"
organizationAppuioIoConfig.GroupVersion = &orgs.GroupVersion
organizationAppuioIoConfig.NegotiatedSerializer = serializer.NewCodecFactory(scheme.Scheme)
organizationAppuioIoClient, err := rest.RESTClientForConfigAndClient(organizationAppuioIoConfig, httpClient)
if err != nil {
klog.Errorf("Could not create Control API client for organization.appuio.io: %v\n", err)
os.Exit(1)
}

appuioIoConfig := &rest.Config{}
appuioIoConfig.Host = ControlApiUrl
appuioIoConfig.APIPath = "/apis"
appuioIoConfig.GroupVersion = &controlapi.GroupVersion
appuioIoConfig.NegotiatedSerializer = serializer.NewCodecFactory(scheme.Scheme)
appuioIoClient, err := rest.RESTClientForConfigAndClient(appuioIoConfig, httpClient)
if err != nil {
klog.Errorf("Could not connect Control API client for appuio.io: %v\n", err)
os.Exit(1)
}

grafanaConfig := grafana.Config{Client: http.DefaultClient, BasicAuth: url.UserPassword(GrafanaUsername, GrafanaPassword)}

// ctx will be passed to lock and controller to signal termination
// ctx will be passed to controller to signal termination
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

Expand All @@ -65,12 +91,6 @@ func main() {
cancel()
}()

controlApiClient, err := rest.RESTClientFor(controlApiConfig)
if err != nil {
klog.Errorf("Could not connect to Control API: %v\n", err)
os.Exit(1)
}

db, err := os.ReadFile("default-dashboard.json")
if err != nil {
klog.Errorf("Could not read default dashboard: %v\n", err)
Expand All @@ -80,7 +100,7 @@ func main() {
json.Unmarshal(db, &dashboard)

klog.Info("Starting initial sync...")
err = controller.ReconcileAllOrgs(ctx, controlApiClient, grafanaConfig, GrafanaUrl, dashboard)
err = controller.ReconcileAllOrgs(ctx, organizationAppuioIoClient, appuioIoClient, grafanaConfig, GrafanaUrl, dashboard)
if err != nil {
klog.Errorf("Could not do initial reconciliation: %v\n", err)
os.Exit(1)
Expand All @@ -92,7 +112,7 @@ func main() {
case <-ctx.Done():
os.Exit(0)
}
err = controller.ReconcileAllOrgs(ctx, controlApiClient, grafanaConfig, GrafanaUrl, dashboard)
err = controller.ReconcileAllOrgs(ctx, organizationAppuioIoClient, appuioIoClient, grafanaConfig, GrafanaUrl, dashboard)
if err != nil {
klog.Errorf("Could not reconcile (will retry): %v\n", err)
}
Expand Down
60 changes: 60 additions & 0 deletions pkg/controlApi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package controller

import (
"context"
orgs "github.com/appuio/control-api/apis/organization/v1"
controlapi "github.com/appuio/control-api/apis/v1"
grafana "github.com/grafana/grafana-api-golang-client"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
)

// Generate list of control API organizations
func getControlApiOrganizations(ctx context.Context, organizationAppuioIoClient *rest.RESTClient) ([]orgs.Organization, error) {
controlApiOrgs := orgs.OrganizationList{}
err := organizationAppuioIoClient.Get().Resource("Organizations").Do(ctx).Into(&controlApiOrgs)
if err != nil {
return nil, err
}
controlApiOrgsList := []orgs.Organization{}
for _, org := range controlApiOrgs.Items {
controlApiOrgsList = append(controlApiOrgsList, org)
}
return controlApiOrgsList, nil
}

// Generate map containing all control API users. Key is the user ID, value is the user object.
func getControlApiUsersMap(ctx context.Context, appuioIoClient *rest.RESTClient) (map[string]controlapi.User, error) {
controlApiUsers := controlapi.UserList{}
err := appuioIoClient.Get().Resource("Users").Do(ctx).Into(&controlApiUsers)
if err != nil {
return nil, err
}
appuioControlApiUsersMap := make(map[string]controlapi.User)
for _, user := range controlApiUsers.Items {
appuioControlApiUsersMap[user.Name] = user
}
return appuioControlApiUsersMap, nil
}

// Generate map containing all Grafana users grouped by organization. Key is the organization ID, value is an array of users.
func getControlApiOrganizationUsersMap(ctx context.Context, grafanaUsersMap map[string]grafana.User, appuioIoClient *rest.RESTClient) (map[string][]grafana.User, error) {
appuioControlApiOrganizationMembers := controlapi.OrganizationMembersList{}
err := appuioIoClient.Get().Resource("OrganizationMembers").Do(ctx).Into(&appuioControlApiOrganizationMembers)
if err != nil {
return nil, err
}
controlApiOrganizationUsersMap := make(map[string][]grafana.User)
for _, memberlist := range appuioControlApiOrganizationMembers.Items {
users := []grafana.User{}
for _, userRef := range memberlist.Spec.UserRefs {
if grafanaUser, ok := grafanaUsersMap[userRef.Name]; ok {
users = append(users, grafanaUser)
} else {
klog.Warningf("Organization '%s' should have user %s but the user wasn't synced to Grafana, ignoring", memberlist.Namespace, userRef.Name)
}
}
controlApiOrganizationUsersMap[memberlist.Namespace] = users
}
return controlApiOrganizationUsersMap, nil
}
Loading

0 comments on commit 2f013c7

Please sign in to comment.