Skip to content

Commit

Permalink
Implement organizations API.
Browse files Browse the repository at this point in the history
Adds the APIs for managing the organization:
- Get/List/Update organizations
- List/Create/Delete organization invitations
- List/Delete organization members
- Add/Remove organization member role assignments

(API docs: https://www.elastic.co/guide/en/cloud/current/Organizations.html)
  • Loading branch information
gigerdo committed Sep 3, 2024
1 parent 6d20496 commit 5f5f60d
Show file tree
Hide file tree
Showing 21 changed files with 1,774 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .changelog/1.21.0/477.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
category: enhancement
title: Add the ESS-only organizations API.
description: |
Adds support for the organizations API (https://www.elastic.co/guide/en/cloud/current/Organizations.html). This API is currently unavailable in self-hosted ECE and can only be used against the Elastic cloud service.
61 changes: 61 additions & 0 deletions pkg/api/organizationapi/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 organizationapi

import (
"errors"
"github.com/elastic/cloud-sdk-go/pkg/api"
"github.com/elastic/cloud-sdk-go/pkg/api/apierror"
"github.com/elastic/cloud-sdk-go/pkg/client/organizations"
"github.com/elastic/cloud-sdk-go/pkg/models"
"github.com/elastic/cloud-sdk-go/pkg/multierror"
)

type GetParams struct {
*api.API

OrganizationID string
}

func (params GetParams) Validate() error {
var merr = multierror.NewPrefixed("invalid user params")
if params.API == nil {
merr = merr.Append(apierror.ErrMissingAPI)
}
if params.OrganizationID == "" {
merr = merr.Append(errors.New("OrganizationID is not specified and is required for this operation"))
}
return merr.ErrorOrNil()
}

func Get(params GetParams) (*models.Organization, error) {
if err := params.Validate(); err != nil {
return nil, err
}

response, err := params.V1API.Organizations.GetOrganization(
organizations.NewGetOrganizationParams().
WithOrganizationID(params.OrganizationID),
params.AuthWriter,
)
if err != nil {
return nil, apierror.Wrap(err)
}

return response.Payload, nil
}
103 changes: 103 additions & 0 deletions pkg/api/organizationapi/get_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 organizationapi

import (
"github.com/elastic/cloud-sdk-go/pkg/api"
"github.com/elastic/cloud-sdk-go/pkg/api/mock"
"github.com/elastic/cloud-sdk-go/pkg/models"
"github.com/elastic/cloud-sdk-go/pkg/util/ec"
"github.com/stretchr/testify/assert"
"testing"
)

func TestGetOrganization(t *testing.T) {
tests := []struct {
name string
params GetParams
want *models.Organization
err string
}{
{
name: "fails due to parameter validation",
err: "invalid user params: 2 errors occurred:\n\t* OrganizationID is not specified and is required for this operation\n\t* api reference is required for the operation\n\n",
},
{
name: "handles successful response",
params: GetParams{
API: api.NewMock(
mock.New200ResponseAssertion(
&mock.RequestAssertion{
Header: api.DefaultReadMockHeaders,
Method: "GET",
Host: api.DefaultMockHost,
Path: "/api/v1/organizations/testorg",
},
mock.NewStringBody(`
{
"billing_contacts" : [
"contact-a", "contact-b"
],
"default_disk_usage_alerts_enabled" : true,
"id" : "testorg",
"name" : "testorganization",
"notifications_allowed_email_domains" : [
"mail@test"
],
"operational_contacts" : [
"op@test"
]
}`)),
),
OrganizationID: "testorg",
},
want: &models.Organization{
ID: ec.String("testorg"),
Name: ec.String("testorganization"),
BillingContacts: []string{"contact-a", "contact-b"},
DefaultDiskUsageAlertsEnabled: ec.Bool(true),
NotificationsAllowedEmailDomains: []string{"mail@test"},
OperationalContacts: []string{"op@test"},
},
},
{
name: "handles failure response",
params: GetParams{
API: api.NewMock(
mock.NewErrorResponse(404, mock.APIError{
Code: "organization.not_found",
Message: "organization not found",
}),
),
OrganizationID: "testorg",
},
err: "api error: 1 error occurred:\n\t* organization.not_found: organization not found\n\n",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := Get(test.params)
if err != nil && !assert.EqualError(t, err, test.err) {
t.Error(err)
}
if !assert.Equal(t, test.want, got) {
t.Error(err)
}
})
}
}
78 changes: 78 additions & 0 deletions pkg/api/organizationapi/invitation_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 organizationapi

import (
"errors"
"github.com/elastic/cloud-sdk-go/pkg/api"
"github.com/elastic/cloud-sdk-go/pkg/api/apierror"
"github.com/elastic/cloud-sdk-go/pkg/client/organizations"
"github.com/elastic/cloud-sdk-go/pkg/models"
"github.com/elastic/cloud-sdk-go/pkg/multierror"
)

type CreateInvitationParams struct {
*api.API

OrganizationID string

// The email addresses to invite to the organization
Emails []string

// The expiration time for the invitation, for example 24h, 7d. Defaults to 72h.
ExpiresIn string

// Roles to assign to the newly invited user
RoleAssignments *models.RoleAssignments
}

func (params CreateInvitationParams) Validate() error {
var merr = multierror.NewPrefixed("invalid user params")
if params.API == nil {
merr = merr.Append(apierror.ErrMissingAPI)
}
if params.OrganizationID == "" {
merr = merr.Append(errors.New("OrganizationID is not specified and is required for this operation"))
}
if len(params.Emails) == 0 {
merr = merr.Append(errors.New("Emails must specify at least one email address"))
}
return merr.ErrorOrNil()
}

func CreateInvitation(params CreateInvitationParams) (*models.OrganizationInvitations, error) {
if err := params.Validate(); err != nil {
return nil, err
}

response, err := params.V1API.Organizations.CreateOrganizationInvitations(
organizations.NewCreateOrganizationInvitationsParams().
WithOrganizationID(params.OrganizationID).
WithBody(&models.OrganizationInvitationRequest{
Emails: params.Emails,
ExpiresIn: params.ExpiresIn,
RoleAssignments: params.RoleAssignments,
}),
params.AuthWriter,
)
if err != nil {
return nil, apierror.Wrap(err)
}

return response.Payload, nil
}
Loading

0 comments on commit 5f5f60d

Please sign in to comment.