Skip to content

Commit

Permalink
feat: introduce Cloudian GET User
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgb committed Jan 18, 2025
1 parent aae9cb8 commit ca9735e
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 20 deletions.
6 changes: 3 additions & 3 deletions internal/controller/group/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
}

observedGroup, err := c.cloudianService.GetGroup(ctx, externalName)
if errors.Is(err, cloudian.ErrNotFound) {
return managed.ExternalObservation{ResourceExists: false}, nil
}
if err != nil {
if errors.Is(err, cloudian.ErrNotFound) {
return managed.ExternalObservation{ResourceExists: false}, nil
}
return managed.ExternalObservation{}, errors.Wrap(err, errGetGroup)
}

Expand Down
27 changes: 11 additions & 16 deletions internal/controller/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection"
"github.com/crossplane/crossplane-runtime/pkg/controller"
"github.com/crossplane/crossplane-runtime/pkg/event"
Expand All @@ -48,7 +49,6 @@ const (
errNewClient = "cannot create new Service"
errCreateUser = "cannot create User"
errDeleteUser = "cannot delete User"
errListUsers = "cannot list Users"
errGetUser = "cannot get User"
)

Expand Down Expand Up @@ -157,40 +157,35 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
return managed.ExternalObservation{}, nil
}

// TODO: GET User instead of listing users for group
users, err := c.cloudianService.ListUsers(ctx, group, nil)
_, err := c.cloudianService.GetUser(ctx, cloudian.User{
GroupID: group,
UserID: externalName})
if errors.Is(err, cloudian.ErrNotFound) {
return managed.ExternalObservation{ResourceExists: false}, nil
}
if err != nil {
return managed.ExternalObservation{}, errors.Wrap(err, errListUsers)
return managed.ExternalObservation{}, errors.Wrap(err, errGetUser)
}

upToDate := isUpToDate(meta.GetExternalName(mg), users)
cr.SetConditions(xpv1.Available())

return managed.ExternalObservation{
// Return false when the external resource does not exist. This lets
// the managed resource reconciler know that it needs to call Create to
// (re)create the resource, or that it has successfully been deleted.
ResourceExists: upToDate,
ResourceExists: true,

// Return false when the external resource exists, but it not up to date
// with the desired managed resource state. This lets the managed
// resource reconciler know that it needs to call Update.
ResourceUpToDate: upToDate,
ResourceUpToDate: true,

// Return any details that may be required to connect to the external
// resource. These will be stored as the connection secret.
ConnectionDetails: managed.ConnectionDetails{},
}, nil
}

func isUpToDate(userId string, users []cloudian.User) bool {
for _, user := range users {
if user.UserID == userId {
return true
}
}
return false
}

func (c *external) Create(ctx context.Context, mg resource.Managed) (managed.ExternalCreation, error) {
cr, ok := mg.(*v1alpha1.User)
if !ok {
Expand Down
27 changes: 26 additions & 1 deletion internal/sdk/cloudian/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,31 @@ func (client Client) CreateUser(ctx context.Context, user User) error {
}
}

// GetUser gets a user. Returns an error even in the case of a user not found.
// This error can then be checked against ErrNotFound: errors.Is(err, ErrNotFound)
func (client Client) GetUser(ctx context.Context, user User) (*User, error) {
// FIXME: Introduce UserId struct and enrich User
resp, err := client.newRequest(ctx).
SetQueryParams(map[string]string{
"groupId": user.GroupID,
"userId": user.UserID,
}).
Get("/user")
if err != nil {
return nil, err
}

switch resp.StatusCode() {
case 200:
return &user, nil
case 204:
// Cloudian-API returns 204 if the user does not exist
return nil, ErrNotFound
default:
return nil, fmt.Errorf("error: GET user unexpected status code: %d", resp.StatusCode())
}
}

// CreateUserCredentials creates a new set of credentials for a user.
func (client Client) CreateUserCredentials(ctx context.Context, user User) (*SecurityInfo, error) {
var securityInfo SecurityInfo
Expand Down Expand Up @@ -376,7 +401,7 @@ func (client Client) GetGroup(ctx context.Context, groupID string) (*Group, erro
// Cloudian-API returns 204 if the group does not exist
return nil, ErrNotFound
default:
return nil, fmt.Errorf("GET unexpected status. Failure: %w", err)
return nil, fmt.Errorf("error: GET group unexpected status code: %d", resp.StatusCode())
}
}

Expand Down
28 changes: 28 additions & 0 deletions internal/sdk/cloudian/sdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,31 @@ func mockBy(handler http.HandlerFunc) (*Client, *httptest.Server) {
mockServer := httptest.NewServer(handler)
return NewClient(mockServer.URL, ""), mockServer
}

func TestClient_GetUser(t *testing.T) {
tests := []struct {
name string
user User
status int
wantErr error
}{
{name: "Exists", user: User{UserID: strconv.Itoa(http.StatusOK)}},
{name: "Not found", user: User{UserID: strconv.Itoa(http.StatusNoContent)}, wantErr: ErrNotFound},
}

client, testServer := mockBy(func(w http.ResponseWriter, r *http.Request) {
userId := r.URL.Query().Get("userId")
statusCode, _ := strconv.Atoi(userId)
w.WriteHeader(statusCode)
})
defer testServer.Close()

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := client.GetUser(context.Background(), tt.user)
if !errors.Is(err, tt.wantErr) {
t.Errorf("GetUser() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

0 comments on commit ca9735e

Please sign in to comment.