-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: user-identity-mapper job (#24)
code coming from https://github.com/codeready-toolchain/sandbox-sre/blob/master/cmd/user-identity-mapper/ Signed-off-by: Xavier Coulon <[email protected]> Co-authored-by: Matous Jobanek <[email protected]>
- Loading branch information
1 parent
a7e9be9
commit 2705510
Showing
6 changed files
with
380 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
################################################################################################ | ||
# Builder image | ||
# See https://hub.docker.com/_/golang/ | ||
################################################################################################ | ||
FROM golang:1.20 as builder | ||
|
||
ARG OS=linux | ||
ARG ARCH=amd64 | ||
|
||
WORKDIR /usr/src/app | ||
|
||
# pre-copy/cache parent go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change | ||
COPY go.mod go.sum ./ | ||
RUN go mod download && go mod verify | ||
|
||
COPY pkg ./pkg | ||
COPY cmd/user-identity-mapper ./cmd/user-identity-mapper | ||
|
||
RUN go build -v -o user-identity-mapper cmd/user-identity-mapper/*.go | ||
|
||
################################################################################################ | ||
# user-identity-mapper image to be run by the job on OpenShift | ||
################################################################################################ | ||
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest as user-identity-mapper | ||
|
||
# Copy the generated binary into the $PATH so it can be invoked | ||
COPY --from=builder /usr/src/app/user-identity-mapper /usr/local/bin/ | ||
|
||
# Run as non-root user | ||
USER 1001 | ||
|
||
CMD ["/usr/local/bin/user-identity-mapper"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/charmbracelet/log" | ||
userv1 "github.com/openshift/api/user/v1" | ||
"github.com/spf13/cobra" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/client/config" | ||
) | ||
|
||
func main() { | ||
// cmd the command that maps an identity to its parent user | ||
cmd := &cobra.Command{ | ||
Use: "user-identity-mapper", | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
|
||
logger := log.New(cmd.OutOrStderr()) | ||
// Get a config to talk to the apiserver | ||
cfg, err := config.GetConfig() | ||
if err != nil { | ||
logger.Error("unable to load config", "error", err) | ||
os.Exit(1) | ||
} | ||
|
||
// create client that will be used for retrieving the host operator secret & ToolchainCluster CRs | ||
scheme := runtime.NewScheme() | ||
if err := userv1.Install(scheme); err != nil { | ||
logger.Error("unable to install scheme", "error", err) | ||
os.Exit(1) | ||
} | ||
cl, err := runtimeclient.New(cfg, runtimeclient.Options{ | ||
Scheme: scheme, | ||
}) | ||
if err != nil { | ||
logger.Error("unable to create a client", "error", err) | ||
os.Exit(1) | ||
} | ||
return CreateUserIdentityMappings(cmd.Context(), logger, cl) | ||
}, | ||
} | ||
|
||
if err := cmd.Execute(); err != nil { | ||
fmt.Println(err) | ||
os.Exit(1) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/charmbracelet/log" | ||
userv1 "github.com/openshift/api/user/v1" | ||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
func CreateUserIdentityMappings(ctx context.Context, logger *log.Logger, cl runtimeclient.Client) error { | ||
logger.Info("listing users...") | ||
users := &userv1.UserList{} | ||
if err := cl.List(ctx, users, runtimeclient.MatchingLabels{ | ||
"provider": "sandbox-sre", | ||
}); err != nil { | ||
return fmt.Errorf("unable to list users: %w", err) | ||
} | ||
for _, user := range users.Items { | ||
logger.Info("listing identities", "username", user.Name) | ||
identities := userv1.IdentityList{} | ||
if err := cl.List(ctx, &identities, runtimeclient.MatchingLabels{ | ||
"provider": "sandbox-sre", | ||
"username": user.Name, | ||
}); err != nil { | ||
return fmt.Errorf("unable to list identities: %w", err) | ||
} | ||
if len(identities.Items) == 0 { | ||
logger.Errorf("no identity associated with user %q", user.Name) | ||
continue | ||
} | ||
for _, identity := range identities.Items { | ||
logger.Info("creating/updating identity mapping", "user", user.Name, "identity", identity.Name) | ||
if err := cl.Create(ctx, &userv1.UserIdentityMapping{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: identity.Name, | ||
}, | ||
User: corev1.ObjectReference{ | ||
Name: user.Name, | ||
}, | ||
Identity: corev1.ObjectReference{ | ||
Name: identity.Name, | ||
}, | ||
}); err != nil && !errors.IsAlreadyExists(err) { | ||
return fmt.Errorf("unable to create identity mapping for username %q and identity %q: %w", user.Name, identity.Name, err) | ||
} | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
package main_test | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/codeready-toolchain/toolchain-common/pkg/test" | ||
useridentitymapper "github.com/kubesaw/ksctl/cmd/user-identity-mapper" | ||
|
||
"github.com/charmbracelet/log" | ||
userv1 "github.com/openshift/api/user/v1" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/client-go/kubernetes/scheme" | ||
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
func TestUserIdentityMapper(t *testing.T) { | ||
|
||
// given | ||
s := scheme.Scheme | ||
err := userv1.Install(s) | ||
require.NoError(t, err) | ||
user1 := &userv1.User{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "user1", | ||
Labels: map[string]string{ | ||
"provider": "sandbox-sre", | ||
}, | ||
}, | ||
} | ||
identity1 := &userv1.Identity{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "identity1", | ||
Labels: map[string]string{ | ||
"provider": "sandbox-sre", | ||
"username": "user1", | ||
}, | ||
}, | ||
} | ||
user2 := &userv1.User{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "user2", | ||
Labels: map[string]string{ | ||
"provider": "sandbox-sre", | ||
}, | ||
}, | ||
} | ||
identity2 := &userv1.Identity{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "identity2", | ||
Labels: map[string]string{ | ||
"provider": "sandbox-sre", | ||
"username": "user2", | ||
}, | ||
}, | ||
} | ||
user3 := &userv1.User{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "user3", | ||
// not managed by sandbox-sre | ||
}, | ||
} | ||
identity3 := &userv1.Identity{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: "identity3", | ||
Labels: map[string]string{ | ||
"provider": "sandbox-sre", | ||
"username": "user3", | ||
}, | ||
}, | ||
} | ||
|
||
t.Run("success", func(t *testing.T) { | ||
// given | ||
out := new(bytes.Buffer) | ||
logger := log.New(out) | ||
cl := test.NewFakeClient(t, user1, identity1, user2, identity2, user3, identity3) | ||
|
||
// when | ||
err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) | ||
|
||
// then | ||
require.NoError(t, err) | ||
assert.NotContains(t, out.String(), "unable to list identities") | ||
uim := &userv1.UserIdentityMapping{} | ||
// `user1` and `user2` are not managed by sandbox (ie, labelled with `provider: sandbox-sre`), hence the `UserIdentityMappings` exist | ||
require.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: identity1.Name}, uim)) | ||
assert.Equal(t, identity1.Name, uim.Identity.Name) | ||
assert.Equal(t, user1.Name, uim.User.Name) | ||
require.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: identity2.Name}, uim)) | ||
assert.Equal(t, identity2.Name, uim.Identity.Name) | ||
assert.Equal(t, user2.Name, uim.User.Name) | ||
}) | ||
|
||
t.Run("failures", func(t *testing.T) { | ||
|
||
t.Run("user and identities not labelled", func(t *testing.T) { | ||
// given | ||
out := new(bytes.Buffer) | ||
logger := log.New(out) | ||
cl := test.NewFakeClient(t, user3, identity3) | ||
|
||
// when | ||
err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) | ||
|
||
// then | ||
require.NoError(t, err) | ||
assert.NotContains(t, out.String(), "unable to list identities") | ||
// `user3` is not managed by sandbox (ie, not labelled with `provider: sandbox-sre`), , hence the `UserIdentityMappings` does not exist | ||
require.EqualError(t, cl.Get(context.TODO(), types.NamespacedName{Name: identity3.Name}, &userv1.UserIdentityMapping{}), `useridentitymappings.user.openshift.io "identity3" not found`) | ||
}) | ||
|
||
t.Run("missing identity", func(t *testing.T) { | ||
// given | ||
out := new(bytes.Buffer) | ||
logger := log.New(out) | ||
cl := test.NewFakeClient(t, user1) | ||
|
||
// when | ||
err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) | ||
|
||
// then | ||
require.NoError(t, err) | ||
assert.Contains(t, out.String(), `no identity associated with user "user1"`) | ||
require.EqualError(t, cl.Get(context.TODO(), types.NamespacedName{Name: identity1.Name}, &userv1.UserIdentityMapping{}), `useridentitymappings.user.openshift.io "identity1" not found`) | ||
}) | ||
|
||
t.Run("cannot list users", func(t *testing.T) { | ||
// given | ||
out := new(bytes.Buffer) | ||
logger := log.New(out) | ||
cl := test.NewFakeClient(t, user1, identity1) | ||
cl.MockList = func(ctx context.Context, list runtimeclient.ObjectList, opts ...runtimeclient.ListOption) error { | ||
if _, ok := list.(*userv1.UserList); ok { | ||
return fmt.Errorf("mock error") | ||
} | ||
return cl.Client.List(ctx, list, opts...) | ||
} | ||
// when | ||
err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) | ||
|
||
// then | ||
assert.EqualError(t, err, "unable to list users: mock error") | ||
}) | ||
|
||
t.Run("cannot list identities", func(t *testing.T) { | ||
// given | ||
out := new(bytes.Buffer) | ||
logger := log.New(out) | ||
cl := test.NewFakeClient(t, user1, identity1, user2, identity2) | ||
cl.MockList = func(ctx context.Context, list runtimeclient.ObjectList, opts ...runtimeclient.ListOption) error { | ||
if _, ok := list.(*userv1.IdentityList); ok { | ||
return fmt.Errorf("mock error") | ||
} | ||
return cl.Client.List(ctx, list, opts...) | ||
} | ||
|
||
// when | ||
err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) | ||
|
||
// then | ||
assert.EqualError(t, err, "unable to list identities: mock error") | ||
}) | ||
|
||
t.Run("cannot create user-identity mapping", func(t *testing.T) { | ||
// given | ||
out := new(bytes.Buffer) | ||
logger := log.New(out) | ||
cl := test.NewFakeClient(t, user1, identity1, user2, identity2) | ||
cl.MockCreate = func(ctx context.Context, obj runtimeclient.Object, opts ...runtimeclient.CreateOption) error { | ||
if _, ok := obj.(*userv1.UserIdentityMapping); ok { | ||
return fmt.Errorf("mock error") | ||
} | ||
return cl.Client.Create(ctx, obj, opts...) | ||
} | ||
|
||
// when | ||
err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) | ||
|
||
// then | ||
assert.EqualError(t, err, `unable to create identity mapping for username "user1" and identity "identity1": mock error`) | ||
}) | ||
}) | ||
} |
Oops, something went wrong.