From cc8cd47fefc4441ac636379623034cbe29202680 Mon Sep 17 00:00:00 2001 From: ealemayehu Date: Mon, 8 Mar 2021 11:02:48 -0500 Subject: [PATCH 01/10] test: Implemented integration tests for service catalog workspaces (#350) * Integration test for update api of workspace-types * test: Integration test for update api of workspace-types * Remove erroneously added update method Instead of adding an update in WorkspaceTypes collection the correct way of doing the update is to invoke the update in the singular WorkspaceType * test: implemented additional workspace-types apis Only remaining API is delete, which is pending on work that needs to be done at the parent resource level. * Applied code review feedback Ensured for the positive test cases we have workspaces in the databases. * test: Int tests for workspace-types-configuration Also includes test for delete-workspace , and typo fixes. * Applied code review feedback * Implemented remaning workspace-type apis 1. Delete for configurations 2. Get for workspace-type-candidates 3. Get for config-vars * Removed overrides that throw errors * Empty commit to trigger PR check * Applied codereview feedback * Removed erroneously added file * test: Implemented workspace service-catalog tests * Fixed lint error * Refactored method into common file * Created a template for the default test product * Added validation logic for default product ID This is to ensure the product is based on the cloud formation template specified in main/integration-tests/support/service-catalog/default-integration-test-product.yml * Auto create default product Currently, we are manually creating the service catalog product and specifying its IDs in the stage file. Made changes to auto create the product in each relevant test suite. * Hard delete service catalog environments A soft delete is done when removing service catalog environments. For integration testing, we need to do a hard removal of the soft deleted service catalog environments. * Made changes per Yanyu's PR feedback Co-authored-by: Eyor Alemayehu Co-authored-by: Yanyu Zheng Co-authored-by: Hatim Khan --- .../cidr-workspace-service-catalog.test.js | 94 ++++++++++ .../connections/create-url-connection.test.js | 100 +++++++++++ .../connections/get-connections.test.js | 96 ++++++++++ .../send-ssh-public-key-connection.test.js | 108 +++++++++++ .../windows-rdp-info-connection.test.js | 100 +++++++++++ .../create-workspace-service-catalog.test.js | 169 ++++++++++++++++++ .../delete-workspace-service-catalog.test.js | 152 ++++++++++++++++ .../get-workspace-service-catalog.test.js | 158 ++++++++++++++++ .../get-workspace-service-catalogs.test.js | 89 +++++++++ .../start-workspace-service-catalog.test.js | 111 ++++++++++++ .../stop-workspace-service-catalog.test.js | 111 ++++++++++++ .../approve-workspace-type.test.js | 43 ++--- .../config-vars/get-config-vars.test.js | 24 +-- .../create-configuration.test.js | 28 ++- .../delete-configuration.test.js | 24 +-- .../configurations/get-configuration.test.js | 30 ++-- .../configurations/get-configurations.test.js | 34 ++-- .../update-configuration.test.js | 52 +++--- .../create-workspace-type.test.js | 31 +++- .../delete-workspace-type.test.js | 28 +-- .../get-workspace-type.test.js | 50 +++--- .../get-workspace-types.test.js | 36 ++-- .../revoke-workspace-type.test.js | 43 ++--- .../update-workspace-type.test.js | 28 ++- .../config/settings/example.yml | 10 -- main/integration-tests/support/aws/config.js | 1 + .../integration-tests/support/aws/services.js | 2 + .../support/aws/services/s3.js | 25 +++ .../support/aws/services/service-catalog.js | 86 +++++++++ ...create-workspace-type-and-configuration.js | 22 +++ .../default-integration-test-product.js | 73 ++++++++ .../delete-workspace-service-catalog.js | 57 ++++++ main/integration-tests/support/resources.js | 2 + .../connections/connection.js | 53 ++++++ .../connections/connections.js | 39 ++++ .../workspace-service-catalog.js | 65 +++++++ .../workspace-service-catalogs.js | 52 ++++++ .../workspace-types/workspace-types.js | 2 - .../default-integration-test-product.yml | 12 ++ 39 files changed, 2014 insertions(+), 226 deletions(-) create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/cidr-workspace-service-catalog.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/create-url-connection.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/get-connections.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/send-ssh-public-key-connection.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/windows-rdp-info-connection.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/create-workspace-service-catalog.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/delete-workspace-service-catalog.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/get-workspace-service-catalog.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/get-workspace-service-catalogs.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/start-workspace-service-catalog.test.js create mode 100644 main/integration-tests/__test__/api-tests/workspace-service-catalogs/stop-workspace-service-catalog.test.js create mode 100644 main/integration-tests/support/aws/services/service-catalog.js create mode 100644 main/integration-tests/support/complex/create-workspace-type-and-configuration.js create mode 100644 main/integration-tests/support/complex/default-integration-test-product.js create mode 100644 main/integration-tests/support/complex/delete-workspace-service-catalog.js create mode 100644 main/integration-tests/support/resources/workspace-service-catalogs/connections/connection.js create mode 100644 main/integration-tests/support/resources/workspace-service-catalogs/connections/connections.js create mode 100644 main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js create mode 100644 main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalogs.js create mode 100644 main/integration-tests/support/service-catalog/default-integration-test-product.yml diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/cidr-workspace-service-catalog.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/cidr-workspace-service-catalog.test.js new file mode 100644 index 0000000000..9e738637b3 --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/cidr-workspace-service-catalog.test.js @@ -0,0 +1,94 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../support/setup'); +const errorCode = require('../../../support/utils/error-code'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../support/complex/default-integration-test-product'); + +describe('Cidr workspace-service-catalog scenarios', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Cidr workspace-service-catalog', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + await adminSession.resources.users.deactivateUser(adminSession2.user); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + const cidrs = [{ fromPort: 10, toPort: 20, protocol: 'http', cidrBlocks: ['0.0.0.0/32'] }]; + + await expect( + adminSession2.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).cidr(cidrs), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + const cidrs = [{ fromPort: 10, toPort: 20, protocol: 'http', cidrBlocks: ['0.0.0.0/32'] }]; + + await expect( + anonymousSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).cidr(cidrs), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/create-url-connection.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/create-url-connection.test.js new file mode 100644 index 0000000000..239b35ba73 --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/create-url-connection.test.js @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../../support/setup'); +const errorCode = require('../../../../support/utils/error-code'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../../support/complex/default-integration-test-product'); + +describe('Create URL scenarios', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Create URL', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + const connectionId = setup.gen.string({ prefix: 'workspace-service-catalog-connection-test' }); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await adminSession2.resources.users.deactivateUser(adminSession2.user); + + await expect( + adminSession2.resources.workspaceServiceCatalogs + .workspaceServiceCatalog(response.id) + .connections() + .connection(connectionId) + .createUrl(), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + const connectionId = setup.gen.string({ prefix: 'workspace-service-catalog-connection-test' }); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + anonymousSession.resources.workspaceServiceCatalogs + .workspaceServiceCatalog(response.id) + .connections() + .connection(connectionId) + .createUrl(), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/get-connections.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/get-connections.test.js new file mode 100644 index 0000000000..9e3eea7088 --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/get-connections.test.js @@ -0,0 +1,96 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../../support/setup'); +const errorCode = require('../../../../support/utils/error-code'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../../support/complex/default-integration-test-product'); + +describe('Get connections scenario', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Get connections', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await adminSession2.resources.users.deactivateUser(adminSession2.user); + + await expect( + adminSession2.resources.workspaceServiceCatalogs + .workspaceServiceCatalog(response.id) + .connections() + .get(), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + anonymousSession.resources.workspaceServiceCatalogs + .workspaceServiceCatalog(response.id) + .connections() + .get(), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/send-ssh-public-key-connection.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/send-ssh-public-key-connection.test.js new file mode 100644 index 0000000000..d5e5bcd0ca --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/send-ssh-public-key-connection.test.js @@ -0,0 +1,108 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../../support/setup'); +const errorCode = require('../../../../support/utils/error-code'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../../support/complex/default-integration-test-product'); + +describe('Send SSH public key scenarios', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Send SSH public key', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + const connectionId = setup.gen.string({ prefix: 'workspace-service-catalog-connection-test' }); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await adminSession2.resources.users.deactivateUser(adminSession2.user); + + const sshInfo = { + keyPairId: 'xxxx-id', + }; + + await expect( + adminSession2.resources.workspaceServiceCatalogs + .workspaceServiceCatalog(response.id) + .connections() + .connection(connectionId) + .sendSshPublicKey(sshInfo), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + const connectionId = setup.gen.string({ prefix: 'workspace-service-catalog-connection-test' }); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + const sshInfo = { + keyPairId: 'xxxx-id', + }; + + await expect( + anonymousSession.resources.workspaceServiceCatalogs + .workspaceServiceCatalog(response.id) + .connections() + .connection(connectionId) + .sendSshPublicKey(sshInfo), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/windows-rdp-info-connection.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/windows-rdp-info-connection.test.js new file mode 100644 index 0000000000..a889a97a85 --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/connections/windows-rdp-info-connection.test.js @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../../support/setup'); +const errorCode = require('../../../../support/utils/error-code'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../../support/complex/default-integration-test-product'); + +describe('Get Windows password for RDP scenario', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Get Windows password for RDP scenario', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + const connectionId = setup.gen.string({ prefix: 'workspace-service-catalog-connection-test' }); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await adminSession2.resources.users.deactivateUser(adminSession2.user); + + await expect( + adminSession2.resources.workspaceServiceCatalogs + .workspaceServiceCatalog(response.id) + .connections() + .connection(connectionId) + .windowsRdpInfo(), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + const connectionId = setup.gen.string({ prefix: 'workspace-service-catalog-connection-test' }); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + anonymousSession.resources.workspaceServiceCatalogs + .workspaceServiceCatalog(response.id) + .connections() + .connection(connectionId) + .windowsRdpInfo(), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/create-workspace-service-catalog.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/create-workspace-service-catalog.test.js new file mode 100644 index 0000000000..9da88ac508 --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/create-workspace-service-catalog.test.js @@ -0,0 +1,169 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../support/setup'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../support/complex/default-integration-test-product'); +const errorCode = require('../../../support/utils/error-code'); + +describe('Create workspace-service-catalog scenarios', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Create workspace-service-catalog', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + await adminSession2.resources.users.deactivateUser(adminSession2.user); + + await expect( + adminSession2.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + await expect( + anonymousSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + + it('should fail if user role is not allowed', async () => { + const researcherSession = await setup.createResearcherSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + await expect( + researcherSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + invalid: 'data', + }), + ).rejects.toMatchObject({ + code: errorCode.http.code.badRequest, + }); + }); + + it('should fail if input is not valid', async () => { + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + await expect( + adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + invalid: 'data', + }), + ).rejects.toMatchObject({ + code: errorCode.http.code.badRequest, + }); + }); + + it('should create the service catalog workspace if admin', async () => { + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + await expect( + adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }), + ).resolves.toMatchObject({ + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + }); + + it('should create if user role is allowed', async () => { + const researcherSession = await setup.createResearcherSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ['researcher'], + ); + + await expect( + researcherSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }), + ).resolves.toMatchObject({ + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/delete-workspace-service-catalog.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/delete-workspace-service-catalog.test.js new file mode 100644 index 0000000000..73102aa35a --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/delete-workspace-service-catalog.test.js @@ -0,0 +1,152 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../support/setup'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../support/complex/default-integration-test-product'); +const errorCode = require('../../../support/utils/error-code'); + +describe('Delete workspace-service-catalog scenarios', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Delete workspace-service-catalog', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + await adminSession.resources.users.deactivateUser(adminSession2.user); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + adminSession2.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).delete(), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + anonymousSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).delete(), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + + it('should fail if user is not owner of workspace', async () => { + const researcherSession = await setup.createResearcherSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + researcherSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).delete(), + ).rejects.toMatchObject({ + code: errorCode.http.code.forbidden, + }); + }); + + it('should delete if user is admin', async () => { + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + adminSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).delete(), + ).resolves.toEqual({}); + }); + + it('should delete if user is owner of workspace', async () => { + const researcherSession = await setup.createResearcherSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ['researcher'], + ); + + const response = await researcherSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + researcherSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).delete(), + ).resolves.toEqual({}); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/get-workspace-service-catalog.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/get-workspace-service-catalog.test.js new file mode 100644 index 0000000000..3e404d9de1 --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/get-workspace-service-catalog.test.js @@ -0,0 +1,158 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../support/setup'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../support/complex/default-integration-test-product'); +const errorCode = require('../../../support/utils/error-code'); + +describe('Get workspace-service-catalog scenarios', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Get workspace-service-catalog', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + await adminSession.resources.users.deactivateUser(adminSession2.user); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + adminSession2.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).get(), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + anonymousSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).get(), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + + it('should fail if user role is not owner', async () => { + const researcherSession = await setup.createResearcherSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + researcherSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).get(), + ).rejects.toMatchObject({ + code: errorCode.http.code.forbidden, + }); + }); + + it('should pass if user role is owner', async () => { + const researcherSession = await setup.createResearcherSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ['researcher'], + ); + + const response = await researcherSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + researcherSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).get(), + ).resolves.toMatchObject({ + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + }); + + it('should pass if user is admin', async () => { + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + adminSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).get(), + ).resolves.toMatchObject({ + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/get-workspace-service-catalogs.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/get-workspace-service-catalogs.test.js new file mode 100644 index 0000000000..29bd70ffad --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/get-workspace-service-catalogs.test.js @@ -0,0 +1,89 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../support/setup'); +const errorCode = require('../../../support/utils/error-code'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../support/complex/default-integration-test-product'); + +describe('Get workspace-service-catalogs scenarios', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Get workspace-service-catalogs', () => { + it('should fail to get workspaces if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + + await adminSession.resources.users.deactivateUser(adminSession2.user); + + await expect(adminSession2.resources.workspaceServiceCatalogs.get()).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail to get workspaces if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + + await expect(anonymousSession.resources.workspaceServiceCatalogs.get()).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + + it('should succeed to get workspaces if user is admin', async () => { + await expect(adminSession.resources.workspaceServiceCatalogs.get()).resolves.toBeInstanceOf(Array); + }); + + it('should succeed to get workspaces of user if user is not admin', async () => { + const researcherSession = await setup.createResearcherSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ['researcher'], + ); + + await researcherSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect(researcherSession.resources.workspaceServiceCatalogs.get()).resolves.toEqual( + expect.arrayContaining([ + expect.objectContaining({ envTypeId: workspaceTypeId, envTypeConfigId: configurationId }), + ]), + ); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/start-workspace-service-catalog.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/start-workspace-service-catalog.test.js new file mode 100644 index 0000000000..8c4db60076 --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/start-workspace-service-catalog.test.js @@ -0,0 +1,111 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../support/setup'); +const errorCode = require('../../../support/utils/error-code'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../support/complex/default-integration-test-product'); + +describe('Start workspace-service-catalog scenarios', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Start workspace-service-catalog', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + await adminSession.resources.users.deactivateUser(adminSession2.user); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + adminSession2.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).start(), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + anonymousSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).start(), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + + it('should fail if user is not owner of workspace', async () => { + const researcherSession = await setup.createResearcherSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + researcherSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).start(), + ).rejects.toMatchObject({ + code: errorCode.http.code.forbidden, + }); + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-service-catalogs/stop-workspace-service-catalog.test.js b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/stop-workspace-service-catalog.test.js new file mode 100644 index 0000000000..9273cebbd0 --- /dev/null +++ b/main/integration-tests/__test__/api-tests/workspace-service-catalogs/stop-workspace-service-catalog.test.js @@ -0,0 +1,111 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { runSetup } = require('../../../support/setup'); +const errorCode = require('../../../support/utils/error-code'); + +const { + createWorkspaceTypeAndConfiguration, +} = require('../../../support/complex/create-workspace-type-and-configuration'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, +} = require('../../../support/complex/default-integration-test-product'); + +describe('Stop workspace-service-catalog scenarios', () => { + let setup; + let adminSession; + let productInfo; + + beforeAll(async () => { + setup = await runSetup(); + adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); + }); + + afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); + await setup.cleanup(); + }); + + describe('Stop workspace-service-catalog', () => { + it('should fail if user is inactive', async () => { + const adminSession2 = await setup.createAdminSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + await adminSession.resources.users.deactivateUser(adminSession2.user); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + adminSession2.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).stop(), + ).rejects.toMatchObject({ + code: errorCode.http.code.unauthorized, + }); + }); + + it('should fail if user is anonymous', async () => { + const anonymousSession = await setup.createAnonymousSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + anonymousSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).stop(), + ).rejects.toMatchObject({ + code: errorCode.http.code.badImplementation, + }); + }); + }); + + it('should fail if user is not owner of workspace', async () => { + const researcherSession = await setup.createResearcherSession(); + const workspaceName = setup.gen.string({ prefix: 'workspace-service-catalog-test' }); + const { workspaceTypeId, configurationId } = await createWorkspaceTypeAndConfiguration( + productInfo, + adminSession, + setup, + ); + + const response = await adminSession.resources.workspaceServiceCatalogs.create({ + name: workspaceName, + envTypeId: workspaceTypeId, + envTypeConfigId: configurationId, + }); + + await expect( + researcherSession.resources.workspaceServiceCatalogs.workspaceServiceCatalog(response.id).stop(), + ).rejects.toMatchObject({ + code: errorCode.http.code.forbidden, + }); + }); +}); diff --git a/main/integration-tests/__test__/api-tests/workspace-types/approve-workspace-type.test.js b/main/integration-tests/__test__/api-tests/workspace-types/approve-workspace-type.test.js index 6c4f0ccc88..55a42f5f9a 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/approve-workspace-type.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/approve-workspace-type.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../support/complex/default-integration-test-product'); const errorCode = require('../../../support/utils/error-code'); describe('Approve workspace-type scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,10 +42,9 @@ describe('Approve workspace-type scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); const approveBody = { rev: 0, @@ -56,10 +63,9 @@ describe('Approve workspace-type scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); const approveBody = { rev: 0, @@ -76,10 +82,9 @@ describe('Approve workspace-type scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); const approveBody = { rev: 0, @@ -95,10 +100,9 @@ describe('Approve workspace-type scenarios', () => { it('should fail if input is not valid', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); const approveBody = { invalid: 0, @@ -114,10 +118,9 @@ describe('Approve workspace-type scenarios', () => { it('should approve if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); const approveBody = { rev: 0, diff --git a/main/integration-tests/__test__/api-tests/workspace-types/config-vars/get-config-vars.test.js b/main/integration-tests/__test__/api-tests/workspace-types/config-vars/get-config-vars.test.js index 4db0695c4c..ed2633e406 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/config-vars/get-config-vars.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/config-vars/get-config-vars.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../../support/complex/default-integration-test-product'); const errorCode = require('../../../../support/utils/error-code'); describe('Get config-vars scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,9 +42,7 @@ describe('Get config-vars scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await adminSession.resources.users.deactivateUser(adminSession2.user); @@ -54,9 +60,7 @@ describe('Get config-vars scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await expect( anonymousSession.resources.workspaceTypes @@ -72,9 +76,7 @@ describe('Get config-vars scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await expect( researcherSession.resources.workspaceTypes @@ -87,9 +89,7 @@ describe('Get config-vars scenarios', () => { it('should return non-empty array if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await expect( adminSession.resources.workspaceTypes diff --git a/main/integration-tests/__test__/api-tests/workspace-types/configurations/create-configuration.test.js b/main/integration-tests/__test__/api-tests/workspace-types/configurations/create-configuration.test.js index fdb403513f..48ccccf372 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/configurations/create-configuration.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/configurations/create-configuration.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../../support/complex/default-integration-test-product'); const errorCode = require('../../../../support/utils/error-code'); describe('Create configuration scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,9 +42,7 @@ describe('Create configuration scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await adminSession.resources.users.deactivateUser(adminSession2.user); @@ -58,9 +64,7 @@ describe('Create configuration scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -80,9 +84,7 @@ describe('Create configuration scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -101,9 +103,7 @@ describe('Create configuration scenarios', () => { it('should fail if input is not valid', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -123,9 +123,7 @@ describe('Create configuration scenarios', () => { it('should create if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); diff --git a/main/integration-tests/__test__/api-tests/workspace-types/configurations/delete-configuration.test.js b/main/integration-tests/__test__/api-tests/workspace-types/configurations/delete-configuration.test.js index 18e540395f..9b271f7bd3 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/configurations/delete-configuration.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/configurations/delete-configuration.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../../support/complex/default-integration-test-product'); const errorCode = require('../../../../support/utils/error-code'); describe('Delete workspace-type scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,9 +42,7 @@ describe('Delete workspace-type scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -73,9 +79,7 @@ describe('Delete workspace-type scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -109,9 +113,7 @@ describe('Delete workspace-type scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -144,9 +146,7 @@ describe('Delete workspace-type scenarios', () => { it('should delete when user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); diff --git a/main/integration-tests/__test__/api-tests/workspace-types/configurations/get-configuration.test.js b/main/integration-tests/__test__/api-tests/workspace-types/configurations/get-configuration.test.js index d31a4d6609..fa7dd74b6b 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/configurations/get-configuration.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/configurations/get-configuration.test.js @@ -15,17 +15,25 @@ const { runSetup } = require('../../../../support/setup'); const errorCode = require('../../../../support/utils/error-code'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../../support/complex/default-integration-test-product'); describe('Get configuration scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,9 +42,7 @@ describe('Get configuration scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -65,9 +71,7 @@ describe('Get configuration scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -92,10 +96,9 @@ describe('Get configuration scenarios', () => { it("should fail if user's role does not have access", async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -123,10 +126,9 @@ describe('Get configuration scenarios', () => { it("should get a configuration if user's role has access", async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); diff --git a/main/integration-tests/__test__/api-tests/workspace-types/configurations/get-configurations.test.js b/main/integration-tests/__test__/api-tests/workspace-types/configurations/get-configurations.test.js index e541772609..d42bc13bdd 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/configurations/get-configurations.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/configurations/get-configurations.test.js @@ -15,17 +15,25 @@ const { runSetup } = require('../../../../support/setup'); const errorCode = require('../../../../support/utils/error-code'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../../support/complex/default-integration-test-product'); describe('Get configurations scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,9 +42,7 @@ describe('Get configurations scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await adminSession.resources.users.deactivateUser(adminSession2.user); @@ -55,9 +61,7 @@ describe('Get configurations scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await expect( anonymousSession.resources.workspaceTypes @@ -72,9 +76,7 @@ describe('Get configurations scenarios', () => { it('should return all configurations if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -96,10 +98,9 @@ describe('Get configurations scenarios', () => { it('should not return configurations if a user does not have access', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -124,10 +125,9 @@ describe('Get configurations scenarios', () => { it('should return configurations when a user has access', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); diff --git a/main/integration-tests/__test__/api-tests/workspace-types/configurations/update-configuration.test.js b/main/integration-tests/__test__/api-tests/workspace-types/configurations/update-configuration.test.js index d18b51a659..a6eca23359 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/configurations/update-configuration.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/configurations/update-configuration.test.js @@ -15,17 +15,25 @@ const { runSetup } = require('../../../../support/setup'); const errorCode = require('../../../../support/utils/error-code'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../../support/complex/default-integration-test-product'); describe('Update configuration scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,9 +42,7 @@ describe('Update configuration scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -70,12 +76,16 @@ describe('Update configuration scenarios', () => { it('should fail if user is anonymous', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); + const updateBody = { + id: configurationId, + name: configurationId, + desc: setup.gen.description(), + }; + await adminSession.resources.workspaceTypes .workspaceType(workspaceTypeId) .configurations() @@ -83,12 +93,6 @@ describe('Update configuration scenarios', () => { id: configurationId, }); - const updateBody = { - id: configurationId, - name: configurationId, - desc: setup.gen.description(), - }; - const anonymousSession = await setup.createAnonymousSession(); await expect( @@ -105,9 +109,7 @@ describe('Update configuration scenarios', () => { it('should fail if user is not admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -140,9 +142,7 @@ describe('Update configuration scenarios', () => { it('should fail if input is not valid', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); @@ -174,12 +174,16 @@ describe('Update configuration scenarios', () => { it('should update if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const configurationId = setup.gen.string({ prefix: 'configuration-test' }); + const updateBody = { + id: configurationId, + name: configurationId, + desc: setup.gen.description(), + }; + await adminSession.resources.workspaceTypes .workspaceType(workspaceTypeId) .configurations() @@ -187,12 +191,6 @@ describe('Update configuration scenarios', () => { id: configurationId, }); - const updateBody = { - id: configurationId, - name: configurationId, - desc: setup.gen.description(), - }; - await expect( adminSession.resources.workspaceTypes .workspaceType(workspaceTypeId) diff --git a/main/integration-tests/__test__/api-tests/workspace-types/create-workspace-type.test.js b/main/integration-tests/__test__/api-tests/workspace-types/create-workspace-type.test.js index 3acf066bc5..88ba2b8802 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/create-workspace-type.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/create-workspace-type.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../support/complex/default-integration-test-product'); const errorCode = require('../../../support/utils/error-code'); describe('Create workspace-type scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -36,7 +44,9 @@ describe('Create workspace-type scenarios', () => { await adminSession2.resources.users.deactivateUser(adminSession2.user); - await expect(adminSession2.resources.workspaceTypes.create({ id: workspaceTypeId })).rejects.toMatchObject({ + await expect( + adminSession2.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)), + ).rejects.toMatchObject({ code: errorCode.http.code.unauthorized, }); }); @@ -45,7 +55,9 @@ describe('Create workspace-type scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await expect(researcherSession.resources.workspaceTypes.create({ id: workspaceTypeId })).rejects.toMatchObject({ + await expect( + researcherSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)), + ).rejects.toMatchObject({ code: errorCode.http.code.forbidden, }); }); @@ -54,7 +66,9 @@ describe('Create workspace-type scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await expect(anonymousSession.resources.workspaceTypes.create({ id: workspaceTypeId })).rejects.toMatchObject({ + await expect( + anonymousSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)), + ).rejects.toMatchObject({ code: errorCode.http.code.badImplementation, }); }); @@ -63,7 +77,9 @@ describe('Create workspace-type scenarios', () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); await expect( - adminSession.resources.workspaceTypes.create({ id: workspaceTypeId, invalid: 'data' }), + adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, invalid: 'data' }, productInfo), + ), ).rejects.toMatchObject({ code: errorCode.http.code.badRequest, }); @@ -72,10 +88,9 @@ describe('Create workspace-type scenarios', () => { it('should create if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await expect(adminSession.resources.workspaceTypes.create({ id: workspaceTypeId })).resolves.toHaveProperty( - 'id', - workspaceTypeId, - ); + await expect( + adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)), + ).resolves.toHaveProperty('id', workspaceTypeId); }); }); }); diff --git a/main/integration-tests/__test__/api-tests/workspace-types/delete-workspace-type.test.js b/main/integration-tests/__test__/api-tests/workspace-types/delete-workspace-type.test.js index 909defce33..afc08e63a1 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/delete-workspace-type.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/delete-workspace-type.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../support/complex/default-integration-test-product'); const errorCode = require('../../../support/utils/error-code'); describe('Delete workspace-type scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,9 +42,7 @@ describe('Delete workspace-type scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await adminSession2.resources.users.deactivateUser(adminSession2.user); @@ -56,9 +62,7 @@ describe('Delete workspace-type scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await expect( researcherSession.resources.workspaceTypes.workspaceType(workspaceTypeId).delete(), @@ -73,17 +77,15 @@ describe('Delete workspace-type scenarios', () => { }); it('should fail if user is anonymous', async () => { - const anonymousSession = await setup.createResearcherSession(); + const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await expect( anonymousSession.resources.workspaceTypes.workspaceType(workspaceTypeId).delete(), ).rejects.toMatchObject({ - code: errorCode.http.code.forbidden, + code: errorCode.http.code.badImplementation, }); await expect(adminSession.resources.workspaceTypes.workspaceType(workspaceTypeId).get()).resolves.toHaveProperty( @@ -95,9 +97,7 @@ describe('Delete workspace-type scenarios', () => { it('should delete if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); await expect( adminSession.resources.workspaceTypes.workspaceType(workspaceTypeId).delete(), diff --git a/main/integration-tests/__test__/api-tests/workspace-types/get-workspace-type.test.js b/main/integration-tests/__test__/api-tests/workspace-types/get-workspace-type.test.js index 27e2d207ea..9f42634f74 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/get-workspace-type.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/get-workspace-type.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../support/complex/default-integration-test-product'); const errorCode = require('../../../support/utils/error-code'); describe('Get workspace-type scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,10 +42,9 @@ describe('Get workspace-type scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); await adminSession.resources.users.deactivateUser(researcherSession.user); @@ -52,10 +59,9 @@ describe('Get workspace-type scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); await expect( researcherSession.resources.workspaceTypes.workspaceType(workspaceTypeId).get(), @@ -68,10 +74,9 @@ describe('Get workspace-type scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); await expect( anonymousSession.resources.workspaceTypes.workspaceType(workspaceTypeId).get(), @@ -84,10 +89,9 @@ describe('Get workspace-type scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); await expect( anonymousSession.resources.workspaceTypes.workspaceType(workspaceTypeId).get(), @@ -100,10 +104,9 @@ describe('Get workspace-type scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); await expect( researcherSession.resources.workspaceTypes.workspaceType(workspaceTypeId).get(), @@ -114,10 +117,9 @@ describe('Get workspace-type scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); await expect(adminSession2.resources.workspaceTypes.workspaceType(workspaceTypeId).get()).resolves.toHaveProperty( 'id', diff --git a/main/integration-tests/__test__/api-tests/workspace-types/get-workspace-types.test.js b/main/integration-tests/__test__/api-tests/workspace-types/get-workspace-types.test.js index ba90d93888..24d955231c 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/get-workspace-types.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/get-workspace-types.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../support/complex/default-integration-test-product'); const errorCode = require('../../../support/utils/error-code'); describe('Get workspace-types scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -68,10 +76,9 @@ describe('Get workspace-types scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); await expect(researcherSession.resources.workspaceTypes.getApproved()).resolves.not.toHaveLength(0); }); @@ -80,10 +87,9 @@ describe('Get workspace-types scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); await expect(researcherSession.resources.workspaceTypes.getNotApproved()).resolves.toHaveLength(0); }); @@ -91,10 +97,9 @@ describe('Get workspace-types scenarios', () => { it('should return approved workspaces if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); await expect(adminSession.resources.workspaceTypes.getApproved()).resolves.not.toHaveLength(0); }); @@ -102,10 +107,9 @@ describe('Get workspace-types scenarios', () => { it('should return not-approved workspaces if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'not-approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'not-approved' }, productInfo), + ); await expect(adminSession.resources.workspaceTypes.getNotApproved()).resolves.not.toHaveLength(0); }); diff --git a/main/integration-tests/__test__/api-tests/workspace-types/revoke-workspace-type.test.js b/main/integration-tests/__test__/api-tests/workspace-types/revoke-workspace-type.test.js index 35f6193db8..16ae07805e 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/revoke-workspace-type.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/revoke-workspace-type.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../support/complex/default-integration-test-product'); const errorCode = require('../../../support/utils/error-code'); describe('Revoke workspace-type scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,10 +42,9 @@ describe('Revoke workspace-type scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); const revokeBody = { rev: 0, @@ -56,10 +63,9 @@ describe('Revoke workspace-type scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); const revokeBody = { rev: 0, @@ -76,10 +82,9 @@ describe('Revoke workspace-type scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); const revokeBody = { rev: 0, @@ -95,10 +100,9 @@ describe('Revoke workspace-type scenarios', () => { it('should fail if input is not valid', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); const revokeBody = { invalid: 0, @@ -114,10 +118,9 @@ describe('Revoke workspace-type scenarios', () => { it('should revoke if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - status: 'approved', - }); + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); const revokeBody = { rev: 0, diff --git a/main/integration-tests/__test__/api-tests/workspace-types/update-workspace-type.test.js b/main/integration-tests/__test__/api-tests/workspace-types/update-workspace-type.test.js index 70f0d67532..047f6ed369 100644 --- a/main/integration-tests/__test__/api-tests/workspace-types/update-workspace-type.test.js +++ b/main/integration-tests/__test__/api-tests/workspace-types/update-workspace-type.test.js @@ -14,18 +14,26 @@ */ const { runSetup } = require('../../../support/setup'); +const { + createDefaultServiceCatalogProduct, + deleteDefaultServiceCatalogProduct, + addProductInfo, +} = require('../../../support/complex/default-integration-test-product'); const errorCode = require('../../../support/utils/error-code'); describe('Update workspace-type scenarios', () => { let setup; let adminSession; + let productInfo; beforeAll(async () => { setup = await runSetup(); adminSession = await setup.defaultAdminSession(); + productInfo = await createDefaultServiceCatalogProduct(setup); }); afterAll(async () => { + await deleteDefaultServiceCatalogProduct(setup, productInfo); await setup.cleanup(); }); @@ -34,9 +42,7 @@ describe('Update workspace-type scenarios', () => { const adminSession2 = await setup.createAdminSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const updateBody = { id: workspaceTypeId, @@ -57,9 +63,7 @@ describe('Update workspace-type scenarios', () => { const researcherSession = await setup.createResearcherSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const updateBody = { id: workspaceTypeId, @@ -78,9 +82,7 @@ describe('Update workspace-type scenarios', () => { const anonymousSession = await setup.createAnonymousSession(); const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const updateBody = { id: workspaceTypeId, @@ -98,9 +100,7 @@ describe('Update workspace-type scenarios', () => { it('should fail if input is not valid', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const updateBody = { id: workspaceTypeId, @@ -118,9 +118,7 @@ describe('Update workspace-type scenarios', () => { it('should update if user is admin', async () => { const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); - await adminSession.resources.workspaceTypes.create({ - id: workspaceTypeId, - }); + await adminSession.resources.workspaceTypes.create(addProductInfo({ id: workspaceTypeId }, productInfo)); const updateBody = { id: workspaceTypeId, diff --git a/main/integration-tests/config/settings/example.yml b/main/integration-tests/config/settings/example.yml index e81da02d2e..9a274c8281 100644 --- a/main/integration-tests/config/settings/example.yml +++ b/main/integration-tests/config/settings/example.yml @@ -37,13 +37,3 @@ isLocal: false # Set this to the API endpoint if different than the following localApiEndpoint: http://localhost:4000 - -# In the AWS Console, go to Service Catalog > Products and select the ID of a product -# to set as the default product ID. -defaultProductId: "" - -# Launch an instance of the above product by going to Service Catalog > Products. -# Make sure to stop the instance so that it won't incur significant cost. Specify the -# provisioning artifact ID of the launched product below by obtaining it from Service -# Catalog > Provisioned products. -defaultProvisioningArtifactId: "" diff --git a/main/integration-tests/support/aws/config.js b/main/integration-tests/support/aws/config.js index ed3130faf7..38960768c7 100644 --- a/main/integration-tests/support/aws/config.js +++ b/main/integration-tests/support/aws/config.js @@ -51,6 +51,7 @@ const config = async ({ settings }) => { return { awsRegionShortName, namespace, + globalNamespace, backendStackName: `${envName}-${awsRegionShortName}-${solutionName}-backend`, dbPrefix: namespace, studyDataBucketName: `${globalNamespace}-studydata`, diff --git a/main/integration-tests/support/aws/services.js b/main/integration-tests/support/aws/services.js index c7e36f6324..2bf62504f8 100644 --- a/main/integration-tests/support/aws/services.js +++ b/main/integration-tests/support/aws/services.js @@ -19,6 +19,7 @@ const CloudFormation = require('./services/cloudformation.js'); const ParameterStore = require('./services/parameter-store.js'); const DynamoDb = require('./services/dynamodb'); const S3 = require('./services/s3'); +const ServiceCatalog = require('./services/service-catalog.js'); /** * The function assumes the specified role and constructs an instance of the specified AWS client SDK with the @@ -57,6 +58,7 @@ async function getServices({ aws }) { parameterStore: async (options = {}, roleInfo = {}) => getInstance(ParameterStore, { aws }, options, roleInfo), dynamoDb: async (options = {}, roleInfo = {}) => getInstance(DynamoDb, { aws }, options, roleInfo), s3: async (options = {}, roleInfo = {}) => getInstance(S3, { aws }, options, roleInfo), + serviceCatalog: async (options = {}, roleInfo = {}) => getInstance(ServiceCatalog, { aws }, options, roleInfo), }; return services; diff --git a/main/integration-tests/support/aws/services/s3.js b/main/integration-tests/support/aws/services/s3.js index bed4e6f6fe..45fea3cd8b 100644 --- a/main/integration-tests/support/aws/services/s3.js +++ b/main/integration-tests/support/aws/services/s3.js @@ -16,6 +16,7 @@ */ const _ = require('lodash'); +const fs = require('fs'); const { run } = require('../../utils/utils'); @@ -50,6 +51,30 @@ class S3 { return result; } + async uploadFile(bucket, key, localFilePath) { + const content = fs.readFileSync(localFilePath); + + await this.sdk.upload({ Bucket: bucket, Key: key, Body: content }).promise(); + } + + async deleteObject(s3Location) { + const { s3BucketName, s3Key } = this.parseS3Details(s3Location); + await this.sdk.deleteObject({ Bucket: s3BucketName, Key: s3Key }).promise(); + } + + parseS3Details(s3Location) { + const s3Prefix = 's3://'; + if (!_.startsWith(s3Location, s3Prefix)) { + throw new Error('Incorrect s3Location. Expecting s3Location to be in s3://bucketname/s3key format'); + } + const s3Path = s3Location.substring(s3Prefix.length, s3Location.length); + const idxOfFirstSlash = s3Path.indexOf('/'); + const s3BucketName = s3Path.substring(0, idxOfFirstSlash < 0 ? s3Path.length : idxOfFirstSlash); + const s3Key = s3Path.substring(idxOfFirstSlash + 1, idxOfFirstSlash < 0 ? idxOfFirstSlash : s3Path.length); + + return { s3BucketName, s3Key }; + } + /** * Deletes everything inside the folder and the folder itself. The folder should be the full path. * An example of a folder is 'studies/Organization/anon-user-get-files-org-study-test-1613828306167-xxxx' diff --git a/main/integration-tests/support/aws/services/service-catalog.js b/main/integration-tests/support/aws/services/service-catalog.js new file mode 100644 index 0000000000..b0179c1738 --- /dev/null +++ b/main/integration-tests/support/aws/services/service-catalog.js @@ -0,0 +1,86 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-await-in-loop */ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +class ServiceCatalog { + constructor({ aws, sdk }) { + this.aws = aws; + this.sdk = sdk; + } + + async getProductName(productId) { + const response = await this.sdk.describeProduct({ Id: productId }).promise(); + + return response.ProductViewSummary.Name; + } + + async createProduct(productName, description, templateUrl) { + const response = await this.sdk + .createProduct({ + Name: productName, + Owner: '_integration-test_', + ProductType: 'CLOUD_FORMATION_TEMPLATE', + Description: description, + ProvisioningArtifactParameters: { + DisableTemplateValidation: true, + Info: { LoadTemplateFromURL: templateUrl }, + Type: 'CLOUD_FORMATION_TEMPLATE', + Name: productName, + Description: description, + }, + }) + .promise(); + + return { + productId: response.ProductViewDetail.ProductViewSummary.ProductId, + provisioningArtifactId: response.ProvisioningArtifactDetail.Id, + }; + } + + async associateProductWithPortfolio(productId, portfolioId) { + await this.sdk.associateProductWithPortfolio({ ProductId: productId, PortfolioId: portfolioId }).promise(); + } + + async disassociateProductFromPortfolio(productId, portfolioId) { + await this.sdk.disassociateProductFromPortfolio({ ProductId: productId, PortfolioId: portfolioId }).promise(); + } + + async createConstraint(productId, portfolioId, type, roleName) { + const response = await this.sdk + .createConstraint({ + ProductId: productId, + PortfolioId: portfolioId, + Type: type, + Parameters: JSON.stringify({ LocalRoleName: roleName }), + }) + .promise(); + + return response.ConstraintDetail.ConstraintId; + } + + async deleteConstraint(constraintId) { + await this.sdk.deleteConstraint({ Id: constraintId }).promise(); + } + + async deleteProduct(productId) { + await this.sdk.deleteProduct({ Id: productId }).promise(); + } +} + +// The aws javascript sdk client name +ServiceCatalog.clientName = 'ServiceCatalog'; + +module.exports = ServiceCatalog; diff --git a/main/integration-tests/support/complex/create-workspace-type-and-configuration.js b/main/integration-tests/support/complex/create-workspace-type-and-configuration.js new file mode 100644 index 0000000000..5ff7a69745 --- /dev/null +++ b/main/integration-tests/support/complex/create-workspace-type-and-configuration.js @@ -0,0 +1,22 @@ +const { addProductInfo } = require('./default-integration-test-product'); + +/** + * A function that performs the complex task of creating a workspace type + * and configuration. + */ +async function createWorkspaceTypeAndConfiguration(productInfo, adminSession, setup, allowRoleIds = ['admin']) { + const workspaceTypeId = setup.gen.string({ prefix: 'workspace-test' }); + const configurationId = setup.gen.string({ prefix: 'configuration-test' }); + + await adminSession.resources.workspaceTypes.create( + addProductInfo({ id: workspaceTypeId, status: 'approved' }, productInfo), + ); + await adminSession.resources.workspaceTypes + .workspaceType(workspaceTypeId) + .configurations() + .create({ id: configurationId, allowRoleIds }); + + return { workspaceTypeId, configurationId }; +} + +module.exports = { createWorkspaceTypeAndConfiguration }; diff --git a/main/integration-tests/support/complex/default-integration-test-product.js b/main/integration-tests/support/complex/default-integration-test-product.js new file mode 100644 index 0000000000..ea3589dc98 --- /dev/null +++ b/main/integration-tests/support/complex/default-integration-test-product.js @@ -0,0 +1,73 @@ +/** + * Creates the default service catalog product. + */ +async function createDefaultServiceCatalogProduct(setup) { + const s3 = await setup.aws.services.s3(); + const globalNamespace = setup.aws.settings.get('globalNamespace'); + const bucket = `${globalNamespace}-external-templates`; + const templateId = 'default-integration-test-product'; + const template = `./support/service-catalog/${templateId}.yml`; + const key = `${templateId}-${Date.now()}.yml`; + const templateUrl = `https://${bucket}.s3.amazonaws.com/${key}`; + + await s3.uploadFile(bucket, key, template); + + const serviceCatalog = await setup.aws.services.serviceCatalog(); + const portfolioId = await getPortfolioId(setup); + + const productInfo = await serviceCatalog.createProduct( + 'Integration Test', + 'Product for Integration Test', + templateUrl, + ); + + await serviceCatalog.associateProductWithPortfolio(productInfo.productId, portfolioId); + + const namespace = setup.aws.settings.get('namespace'); + const roleName = `${namespace}-LaunchConstraint`; + const constraintId = await serviceCatalog.createConstraint(productInfo.productId, portfolioId, 'LAUNCH', roleName); + + const templateS3Path = `s3://${bucket}/${key}`; + + return { ...productInfo, portfolioId, constraintId, templateS3Path }; +} + +/** + * Creates the default service catalog product. + */ +async function deleteDefaultServiceCatalogProduct(setup, productInfo) { + const serviceCatalog = await setup.aws.services.serviceCatalog(); + + await serviceCatalog.deleteConstraint(productInfo.constraintId); + await serviceCatalog.disassociateProductFromPortfolio(productInfo.productId, productInfo.portfolioId); + await serviceCatalog.deleteProduct(productInfo.productId); + + const s3 = await setup.aws.services.s3(); + + await s3.deleteObject(productInfo.templateS3Path); +} + +/** + * Adds product information to a Workspace Type + */ +function addProductInfo(workspaceType, productInfo) { + return { + ...workspaceType, + product: { productId: productInfo.productId }, + provisioningArtifact: { id: productInfo.provisioningArtifactId }, + }; +} + +async function getPortfolioId(setup) { + const db = await setup.aws.services.dynamoDb(); + const deploymentItem = await db.tables.deploymentStore + .getter() + .key({ type: 'default-sc-portfolio', id: 'default-SC-portfolio-1' }) + .projection(['value']) + .get(); + const deploymentValue = JSON.parse(deploymentItem.value); + + return deploymentValue.portfolioId; +} + +module.exports = { createDefaultServiceCatalogProduct, deleteDefaultServiceCatalogProduct, addProductInfo }; diff --git a/main/integration-tests/support/complex/delete-workspace-service-catalog.js b/main/integration-tests/support/complex/delete-workspace-service-catalog.js new file mode 100644 index 0000000000..2a15ffc831 --- /dev/null +++ b/main/integration-tests/support/complex/delete-workspace-service-catalog.js @@ -0,0 +1,57 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-console */ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const { sleep } = require('@aws-ee/base-services/lib/helpers/utils'); +const _ = require('lodash'); + +/** + * We need a delete method to remove service catalog workspaces + * because the deletion done by the environment-sc service is a + * soft delete. That is the record of the delete is not removed + * from the database. + */ +async function deleteWorkspaceServiceCatalog({ aws, id = '' }) { + const db = await aws.services.dynamoDb(); + let tryCount = 0; + + while (tryCount < 15) { + tryCount += 1; + + const item = await db.tables.environmentsSc + .getter() + .key({ id }) + .projection(['status']) + .get(); + + if (_.isEmpty(item)) { + return; + } + + if (item.status === 'FAILED' || item.status === 'TERMINATED' || tryCount === 10) { + await db.tables.environmentsSc + .deleter() + .key({ id }) + .delete(); + + return; + } + + await sleep(1000); + } +} + +module.exports = { deleteWorkspaceServiceCatalog }; diff --git a/main/integration-tests/support/resources.js b/main/integration-tests/support/resources.js index edc5fbc835..80b10fd69a 100644 --- a/main/integration-tests/support/resources.js +++ b/main/integration-tests/support/resources.js @@ -27,6 +27,7 @@ const WorkspaceTypeCandidates = require('./resources/workspace-type-candidates/w const StepTemplates = require('./resources/step-templates/step-templates'); const KeyPairs = require('./resources/key-pairs/key-pairs'); const WorkflowTemplates = require('./resources/workflow-templates/workflow-templates'); +const WorkspaceServiceCatalogs = require('./resources/workspace-service-catalogs/workspace-service-catalogs'); // Returns the top level resource operations helpers. You should not use this directly in your tests. // These top level resource operation helpers are available via client sessions. @@ -46,6 +47,7 @@ async function getResources({ clientSession }) { stepTemplates: new StepTemplates({ clientSession }), keyPairs: new KeyPairs({ clientSession }), workflowTemplates: new WorkflowTemplates({ clientSession }), + workspaceServiceCatalogs: new WorkspaceServiceCatalogs({ clientSession }), }; return resources; diff --git a/main/integration-tests/support/resources/workspace-service-catalogs/connections/connection.js b/main/integration-tests/support/resources/workspace-service-catalogs/connections/connection.js new file mode 100644 index 0000000000..17e80c1757 --- /dev/null +++ b/main/integration-tests/support/resources/workspace-service-catalogs/connections/connection.js @@ -0,0 +1,53 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const _ = require('lodash'); + +const Resource = require('../../base/resource'); + +class Connection extends Resource { + constructor({ clientSession, id, parent }) { + super({ + clientSession, + type: 'connection', + id, + parent, + }); + + if (_.isEmpty(parent)) throw Error('A parent resource was not provided to resource type [connection]'); + } + + async createUrl() { + const api = `${this.api}/url`; + + return this.doCall(async () => this.axiosClient.post(api, {}, {})); + } + + async windowsRdpInfo() { + const api = `${this.api}/windows-rdp-info`; + + return this.doCall(async () => this.axiosClient.get(api, {}, {})); + } + + async sendSshPublicKey(body) { + const api = `${this.api}/send-ssh-public-key`; + + return this.doCall(async () => this.axiosClient.post(api, body, {})); + } + + // ************************ Helpers methods ************************ +} + +module.exports = Connection; diff --git a/main/integration-tests/support/resources/workspace-service-catalogs/connections/connections.js b/main/integration-tests/support/resources/workspace-service-catalogs/connections/connections.js new file mode 100644 index 0000000000..02b656e660 --- /dev/null +++ b/main/integration-tests/support/resources/workspace-service-catalogs/connections/connections.js @@ -0,0 +1,39 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const CollectionResource = require('../../base/collection-resource'); +const Connection = require('./connection'); + +class Connections extends CollectionResource { + constructor({ clientSession, parent }) { + super({ + clientSession, + type: 'connections', + childType: 'connection', + }); + + this.api = `${parent.api}/connections`; + } + + // Because Connections is a collection resource type, it is assumed that accessing the resource helper of the + // child resource is done by calling connection(id). + connection(id) { + return new Connection({ clientSession: this.clientSession, id, parent: this }); + } + + // ************************ Helpers methods ************************ +} + +module.exports = Connections; diff --git a/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js b/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js new file mode 100644 index 0000000000..015da94f17 --- /dev/null +++ b/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js @@ -0,0 +1,65 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const _ = require('lodash'); + +const Resource = require('../base/resource'); +const Connections = require('./connections/connections'); +const { deleteWorkspaceServiceCatalog } = require('../../complex/delete-workspace-service-catalog.js'); + +class WorkspaceServiceCatalog extends Resource { + constructor({ clientSession, id, parent }) { + super({ + clientSession, + type: 'workspaceServiceCatalog', + id, + parent, + }); + + if (_.isEmpty(parent)) + throw Error('A parent resource was not provided to resource type [workspace-service-catalog]'); + } + + connections() { + return new Connections({ clientSession: this.clientSession, parent: this }); + } + + async stop() { + const api = `${this.api}/stop`; + + return this.doCall(async () => this.axiosClient.put(api, {}, {})); + } + + async start() { + const api = `${this.api}/start`; + + return this.doCall(async () => this.axiosClient.put(api, {}, {})); + } + + async cidr(body) { + const api = `${this.api}/cidr`; + + return this.doCall(async () => this.axiosClient.put(api, body, {})); + } + + async cleanup() { + await super.cleanup(); + await deleteWorkspaceServiceCatalog({ aws: this.setup.aws, id: this.id }); + } + + // ************************ Helpers methods ************************ +} + +module.exports = WorkspaceServiceCatalog; diff --git a/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalogs.js b/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalogs.js new file mode 100644 index 0000000000..5c8d672ea4 --- /dev/null +++ b/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalogs.js @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +const CollectionResource = require('../base/collection-resource'); +const WorkspaceServiceCatalog = require('./workspace-service-catalog'); + +class WorkspaceServiceCatalogs extends CollectionResource { + constructor({ clientSession }) { + super({ + clientSession, + type: 'workspaceServiceCatalogs', + childType: 'workspaceServiceCatalog', + }); + + this.api = '/api/workspaces/service-catalog'; + } + + // Because Workspace is a collection resource type, it is assumed that accessing the resource helper of the + // child resource is done by calling workspaceType(id). For example, the full access pattern to get hold of the + // resource helper of the child resource is: session.resources.workspaceServiceCatalogs.workspaceServiceCatalog() + workspaceServiceCatalog(id) { + return new WorkspaceServiceCatalog({ clientSession: this.clientSession, id, parent: this }); + } + + // When creating a child resource, this method provides default values. This method is used by the + // CollectionResource class when we use get() method on this resource operations helper. + defaults(workspaceServiceCatalog = {}) { + const name = workspaceServiceCatalog.name || this.setup.gen.string({ prefix: 'workspace-service-catalog' }); + return { + name, + description: this.setup.gen.description(), + projectId: this.settings.get('projectId'), + ...workspaceServiceCatalog, + }; + } + + // ************************ Helpers methods ************************ +} + +module.exports = WorkspaceServiceCatalogs; diff --git a/main/integration-tests/support/resources/workspace-types/workspace-types.js b/main/integration-tests/support/resources/workspace-types/workspace-types.js index 574334b2c8..c9defd7960 100644 --- a/main/integration-tests/support/resources/workspace-types/workspace-types.js +++ b/main/integration-tests/support/resources/workspace-types/workspace-types.js @@ -46,8 +46,6 @@ class WorkspaceTypes extends CollectionResource { name: id, desc: this.setup.gen.description(), status: 'not-approved', - product: { productId: this.settings.get('defaultProductId') }, - provisioningArtifact: { id: this.settings.get('defaultProvisioningArtifactId') }, ...workspaceType, }; } diff --git a/main/integration-tests/support/service-catalog/default-integration-test-product.yml b/main/integration-tests/support/service-catalog/default-integration-test-product.yml new file mode 100644 index 0000000000..a2d55b3908 --- /dev/null +++ b/main/integration-tests/support/service-catalog/default-integration-test-product.yml @@ -0,0 +1,12 @@ +Description: "Default integration test product template" + +Resources: + # We create a resource that does not have an adverse impact if created in + # large numbers. The resource we create is a Secret Manager entry. The + # footprint of such resource is small and up to 40,000 entries can be + # created. + DefaultIntTestScProduct: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: "integration-test-resource" From f19ff7e9ab8b2cff9564cdda630e4c78158fdcce Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Mon, 8 Mar 2021 18:34:11 -0500 Subject: [PATCH 02/10] fix: emr workspace image. Lock jupyterlab to version 2.2.6 (#372) --- .../machine-images/config/infra/provisioners/provision-hail.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main/solution/machine-images/config/infra/provisioners/provision-hail.sh b/main/solution/machine-images/config/infra/provisioners/provision-hail.sh index 35edf6ece4..31a55caece 100644 --- a/main/solution/machine-images/config/infra/provisioners/provision-hail.sh +++ b/main/solution/machine-images/config/infra/provisioners/provision-hail.sh @@ -40,6 +40,8 @@ PACKAGES="humanize==1.0.0 sudo python3 -m pip install $PACKAGES +sudo python3 -m pip install -Iv jupyterlab==2.2.6 + export HASH="current" ./hail_build.sh -v $HASH From 7a7e32cc013ab9532ca4854866b87a9a92c076c3 Mon Sep 17 00:00:00 2001 From: ealemayehu Date: Thu, 11 Mar 2021 13:32:13 -0500 Subject: [PATCH 03/10] test: Added delays to POST/PUT/DELETE APIs to reduce flakey tests (#369) * test: Retry jest configuration for flakey tests * Update pnpm lock file * test: Added 1-second delay to deflake tests * Boosted sleep for service catalog to prevent flakes Also added missing awaits. * test: Applied feedback from Jeet Co-authored-by: Eyor Alemayehu --- main/integration-tests/package.json | 2 +- .../default-integration-test-product.js | 12 ++ .../resources/base/collection-resource.js | 18 ++- .../support/resources/base/resource.js | 16 ++- .../connections/connection.js | 9 +- .../workspace-service-catalog.js | 13 +- .../workspace-types/workspace-type.js | 10 +- pnpm-lock.yaml | 124 ++++++++---------- 8 files changed, 125 insertions(+), 79 deletions(-) diff --git a/main/integration-tests/package.json b/main/integration-tests/package.json index 661135ba48..c24dace4ff 100644 --- a/main/integration-tests/package.json +++ b/main/integration-tests/package.json @@ -6,7 +6,7 @@ "author": "Amazon Web Services", "license": "Apache-2.0", "scripts": { - "intTest": "NODE_ENV=test jest --config jest.config.js --passWithNoTests --runInBand --verbose", + "intTest": "NODE_ENV=test jest --config jest.config.js --passWithNoTests", "intTestWatch": "NODE_ENV=test jest --config jest.config.js --passWithNoTests --watchAll", "lint": "pnpm run lint:eslint && pnpm run lint:prettier", "lint:eslint": "eslint --ignore-path .gitignore . ", diff --git a/main/integration-tests/support/complex/default-integration-test-product.js b/main/integration-tests/support/complex/default-integration-test-product.js index ea3589dc98..3c685bfe14 100644 --- a/main/integration-tests/support/complex/default-integration-test-product.js +++ b/main/integration-tests/support/complex/default-integration-test-product.js @@ -1,3 +1,5 @@ +const { sleep } = require('@aws-ee/base-services/lib/helpers/utils'); + /** * Creates the default service catalog product. */ @@ -21,14 +23,24 @@ async function createDefaultServiceCatalogProduct(setup) { templateUrl, ); + // Delay to prevent eventual consistency issues from affecting the next call. + await sleep(1000); + await serviceCatalog.associateProductWithPortfolio(productInfo.productId, portfolioId); const namespace = setup.aws.settings.get('namespace'); const roleName = `${namespace}-LaunchConstraint`; + + // Delay to prevent eventual consistency issues from affecting the next call. + await sleep(1000); + const constraintId = await serviceCatalog.createConstraint(productInfo.productId, portfolioId, 'LAUNCH', roleName); const templateS3Path = `s3://${bucket}/${key}`; + // Large delay needed to ensure the above service catalog changes are fully persisted. + await sleep(30000); + return { ...productInfo, portfolioId, constraintId, templateS3Path }; } diff --git a/main/integration-tests/support/resources/base/collection-resource.js b/main/integration-tests/support/resources/base/collection-resource.js index f884b2d6ed..44de59e653 100644 --- a/main/integration-tests/support/resources/base/collection-resource.js +++ b/main/integration-tests/support/resources/base/collection-resource.js @@ -16,6 +16,7 @@ const _ = require('lodash'); +const { sleep } = require('@aws-ee/base-services/lib/helpers/utils'); const { transform } = require('../../utils/axios-error'); /** @@ -65,6 +66,7 @@ class CollectionResource { // We add a cleanup task to the cleanup queue for the session this.clientSession.addCleanupTask({ id: taskId, task: async () => resourceNode.cleanup(resource) }); + await sleep(this.deflakeDelay()); return resource; } catch (error) { throw transform(error); @@ -72,7 +74,10 @@ class CollectionResource { } async update(body = {}, params = {}, { api = this.api } = {}) { - return this.doCall(async () => this.axiosClient.put(api, body, { params })); + const response = await this.doCall(async () => this.axiosClient.put(api, body, { params })); + + await sleep(this.deflakeDelay()); + return response; } // Because this is a collection resource, the GET method returns an array of the instance child resources @@ -83,7 +88,10 @@ class CollectionResource { // In general, most of SWB APIs on the server side should not support the ability to delete a collection // resource. However, it might be desireable that we test against this. Therefore, this method exists. async delete(body = {}, params = {}, { api = this.api } = {}) { - return this.doCall(async () => this.axiosClient.delete(api, body, { params })); + const response = await this.doCall(async () => this.axiosClient.delete(api, body, { params })); + + await sleep(this.deflakeDelay()); + return response; } // We wrap the call to axios so that we can capture the boom code and payload attributes passed from the @@ -96,6 +104,12 @@ class CollectionResource { throw transform(error); } } + + // Specifies the delay duration in milliseconds needed to minimize the usage of stale data due to eventual + // consistency. Duration can be altered by overriding function in sub-class. + async deflakeDelay() { + return 1000; + } } module.exports = CollectionResource; diff --git a/main/integration-tests/support/resources/base/resource.js b/main/integration-tests/support/resources/base/resource.js index 72ebb3b646..b1a922a0ce 100644 --- a/main/integration-tests/support/resources/base/resource.js +++ b/main/integration-tests/support/resources/base/resource.js @@ -15,6 +15,7 @@ */ const _ = require('lodash'); +const { sleep } = require('@aws-ee/base-services/lib/helpers/utils'); const { transform } = require('../../utils/axios-error'); @@ -58,6 +59,8 @@ class Resource { // We add a cleanup task to the cleanup queue for the session this.clientSession.addCleanupTask({ id: taskId, task: async () => this.cleanup(resource) }); + await sleep(this.deflakeDelay()); + return resource; } catch (error) { throw transform(error); @@ -69,7 +72,10 @@ class Resource { } async update(body = {}, params = {}, { api = this.api } = {}) { - return this.doCall(async () => this.axiosClient.put(api, body, { params })); + const response = await this.doCall(async () => this.axiosClient.put(api, body, { params })); + + await sleep(this.deflakeDelay()); + return response; } async delete(params = {}, { api = this.api } = {}) { @@ -80,6 +86,8 @@ class Resource { // task for this resource (if one existed) const taskId = `${this.type}-${this.id}`; this.clientSession.removeCleanupTask(taskId); + + await sleep(this.deflakeDelay()); return response; }); } @@ -114,6 +122,12 @@ class Resource { await this.delete(); } } + + // Specifies the delay duration in milliseconds needed to minimize the usage of stale data due to eventual + // consistency. Duration can be altered by overriding function in sub-class. + async deflakeDelay() { + return 1000; + } } module.exports = Resource; diff --git a/main/integration-tests/support/resources/workspace-service-catalogs/connections/connection.js b/main/integration-tests/support/resources/workspace-service-catalogs/connections/connection.js index 17e80c1757..74f2e93dca 100644 --- a/main/integration-tests/support/resources/workspace-service-catalogs/connections/connection.js +++ b/main/integration-tests/support/resources/workspace-service-catalogs/connections/connection.js @@ -14,6 +14,7 @@ */ const _ = require('lodash'); +const { sleep } = require('@aws-ee/base-services/lib/helpers/utils'); const Resource = require('../../base/resource'); @@ -31,8 +32,10 @@ class Connection extends Resource { async createUrl() { const api = `${this.api}/url`; + const response = await this.doCall(async () => this.axiosClient.post(api, {}, {})); - return this.doCall(async () => this.axiosClient.post(api, {}, {})); + await sleep(this.deflakeDelay()); + return response; } async windowsRdpInfo() { @@ -43,8 +46,10 @@ class Connection extends Resource { async sendSshPublicKey(body) { const api = `${this.api}/send-ssh-public-key`; + const response = await this.doCall(async () => this.axiosClient.post(api, body, {})); - return this.doCall(async () => this.axiosClient.post(api, body, {})); + await sleep(this.deflakeDelay()); + return response; } // ************************ Helpers methods ************************ diff --git a/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js b/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js index 015da94f17..5f8ce95763 100644 --- a/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js +++ b/main/integration-tests/support/resources/workspace-service-catalogs/workspace-service-catalog.js @@ -14,6 +14,7 @@ */ const _ = require('lodash'); +const { sleep } = require('@aws-ee/base-services/lib/helpers/utils'); const Resource = require('../base/resource'); const Connections = require('./connections/connections'); @@ -38,20 +39,26 @@ class WorkspaceServiceCatalog extends Resource { async stop() { const api = `${this.api}/stop`; + const response = await this.doCall(async () => this.axiosClient.put(api, {}, {})); - return this.doCall(async () => this.axiosClient.put(api, {}, {})); + await sleep(this.deflakeDelay()); + return response; } async start() { const api = `${this.api}/start`; + const response = await this.doCall(async () => this.axiosClient.put(api, {}, {})); - return this.doCall(async () => this.axiosClient.put(api, {}, {})); + await sleep(this.deflakeDelay()); + return response; } async cidr(body) { const api = `${this.api}/cidr`; + const response = await this.doCall(async () => this.axiosClient.put(api, body, {})); - return this.doCall(async () => this.axiosClient.put(api, body, {})); + await sleep(this.deflakeDelay()); + return response; } async cleanup() { diff --git a/main/integration-tests/support/resources/workspace-types/workspace-type.js b/main/integration-tests/support/resources/workspace-types/workspace-type.js index 9606640d32..60899afc5c 100644 --- a/main/integration-tests/support/resources/workspace-types/workspace-type.js +++ b/main/integration-tests/support/resources/workspace-types/workspace-type.js @@ -15,6 +15,8 @@ const _ = require('lodash'); +const { sleep } = require('@aws-ee/base-services/lib/helpers/utils'); + const Resource = require('../base/resource'); const Configurations = require('./configurations/configurations'); const ConfigVars = require('./config-vars/config-vars'); @@ -41,14 +43,18 @@ class WorkspaceType extends Resource { async approve(body) { const api = `${this.api}/approve`; + const response = await this.doCall(async () => this.axiosClient.put(api, body, {})); - return this.doCall(async () => this.axiosClient.put(api, body, {})); + await sleep(this.deflakeDelay()); + return response; } async revoke(body) { const api = `${this.api}/revoke`; + const response = await this.doCall(async () => this.axiosClient.put(api, body, {})); - return this.doCall(async () => this.axiosClient.put(api, body, {})); + await sleep(this.deflakeDelay()); + return response; } // ************************ Helpers methods ************************ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56e3bf87a7..a566717efa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -111,7 +111,7 @@ importers: serverless: 1.67.3 serverless-deployment-bucket: 1.1.1 typescript: 3.8.3 - webpack: 4.41.2_webpack@4.41.2 + webpack: 4.41.2 specifiers: '@auth0/auth0-spa-js': ^1.2.3 '@aws-ee/base-services': 'workspace:*' @@ -751,7 +751,7 @@ importers: serverless: 1.67.3 serverless-deployment-bucket: 1.1.1 typescript: 3.8.3 - webpack: 4.41.2_webpack@4.41.2 + webpack: 4.41.2 specifiers: '@aws-ee/base-serverless-settings-helper': 'workspace:*' '@aws-ee/base-serverless-ui-tools': 'workspace:*' @@ -942,7 +942,7 @@ importers: serverless: 1.67.3 serverless-deployment-bucket: 1.1.1 typescript: 3.8.3 - webpack: 4.41.2_webpack@4.41.2 + webpack: 4.41.2 specifiers: '@aws-ee/base-ui': 'workspace:*' '@babel/cli': ^7.8.4 @@ -2034,7 +2034,7 @@ importers: serverless-s3-sync: 1.12.0 serverless-webpack: 5.3.1_webpack@4.42.1 source-map-support: 0.5.16 - webpack: 4.42.1_webpack@4.42.1 + webpack: 4.42.1 webpack-cli: 3.3.11_webpack@4.42.1 webpack-node-externals: 1.7.2 optionalDependencies: @@ -2186,7 +2186,7 @@ importers: serverless-s3-sync: 1.12.0 serverless-webpack: 5.3.4_webpack@4.42.1 source-map-support: 0.5.16 - webpack: 4.42.1_webpack@4.42.1 + webpack: 4.42.1 specifiers: '@aws-ee/base-post-deployment': 'workspace:*' '@aws-ee/base-raas-post-deployment': 'workspace:*' @@ -2335,7 +2335,7 @@ importers: toastr: ^2.1.4 typeface-lato: 0.0.75 uuid: ^3.4.0 -lockfileVersion: 5.1 +lockfileVersion: 5.2 packages: /2-thenable/1.0.0: dependencies: @@ -2427,7 +2427,7 @@ packages: debug: 4.3.1 gensync: 1.0.0-beta.2 json5: 2.1.3 - lodash: 4.17.20 + lodash: 4.17.21 resolve: 1.19.0 semver: 5.7.1 source-map: 0.5.7 @@ -4508,7 +4508,7 @@ packages: '@babel/types': 7.12.7 debug: 4.3.1 globals: 11.12.0 - lodash: 4.17.20 + lodash: 4.17.21 dev: true resolution: integrity: sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw== @@ -5003,7 +5003,7 @@ packages: jest-haste-map: 24.9.0 jest-message-util: 24.9.0 jest-regex-util: 24.9.0 - jest-resolve: 24.9.0_jest-resolve@24.9.0 + jest-resolve: 24.9.0 jest-resolve-dependencies: 24.9.0 jest-runner: 24.9.0 jest-runtime: 24.9.0 @@ -5039,7 +5039,7 @@ packages: jest-haste-map: 26.6.2 jest-message-util: 26.6.2 jest-regex-util: 26.0.0 - jest-resolve: 26.6.2_jest-resolve@26.6.2 + jest-resolve: 26.6.2 jest-resolve-dependencies: 26.6.3 jest-runner: 26.6.3 jest-runtime: 26.6.3 @@ -5127,7 +5127,7 @@ packages: istanbul-lib-source-maps: 3.0.6 istanbul-reports: 2.2.7 jest-haste-map: 24.9.0 - jest-resolve: 24.9.0_jest-resolve@24.9.0 + jest-resolve: 24.9.0 jest-runtime: 24.9.0 jest-util: 24.9.0 jest-worker: 24.9.0 @@ -5158,7 +5158,7 @@ packages: istanbul-lib-source-maps: 4.0.0 istanbul-reports: 3.0.2 jest-haste-map: 26.6.2 - jest-resolve: 26.6.2_jest-resolve@26.6.2 + jest-resolve: 26.6.2 jest-util: 26.6.2 jest-worker: 26.6.2 slash: 3.0.0 @@ -7574,7 +7574,7 @@ packages: mkdirp: 0.5.5 pify: 4.0.1 schema-utils: 2.6.5 - webpack: 4.41.2_webpack@4.41.2 + webpack: 4.41.2 dev: true engines: node: '>= 6.9' @@ -7591,7 +7591,7 @@ packages: mkdirp: 0.5.5 pify: 4.0.1 schema-utils: 2.6.5 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 dev: true engines: node: '>= 6.9' @@ -7608,7 +7608,7 @@ packages: mkdirp: 0.5.5 pify: 4.0.1 schema-utils: 2.6.5 - webpack: 4.42.1_webpack@4.42.1 + webpack: 4.42.1 dev: true engines: node: '>= 6.9' @@ -9156,7 +9156,7 @@ packages: p-limit: 2.3.0 schema-utils: 1.0.0 serialize-javascript: 2.1.2 - webpack: 4.42.1_webpack@4.42.1 + webpack: 4.42.1 webpack-log: 2.0.0 dev: true engines: @@ -9435,7 +9435,7 @@ packages: postcss-modules-values: 3.0.0 postcss-value-parser: 4.1.0 schema-utils: 2.7.1 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 dev: true engines: node: '>= 8.9.0' @@ -9691,7 +9691,7 @@ packages: is-ci: 2.0.0 is-installed-globally: 0.3.2 lazy-ass: 1.6.0 - listr: 0.14.3_listr@0.14.3 + listr: 0.14.3 lodash: 4.17.20 log-symbols: 3.0.0 minimist: 1.2.5 @@ -11051,7 +11051,7 @@ packages: loader-utils: 1.4.0 object-hash: 2.0.3 schema-utils: 2.7.1 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 deprecated: This loader has been deprecated. Please use eslint-webpack-plugin dev: true engines: @@ -11908,7 +11908,7 @@ packages: dependencies: loader-utils: 1.4.0 schema-utils: 2.7.1 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 dev: true engines: node: '>= 8.9.0' @@ -13135,7 +13135,7 @@ packages: pretty-error: 2.1.2 tapable: 1.1.3 util.promisify: 1.0.0 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 dev: true engines: node: '>=6.9' @@ -14410,7 +14410,7 @@ packages: jest-get-type: 24.9.0 jest-jasmine2: 24.9.0 jest-regex-util: 24.9.0 - jest-resolve: 24.9.0_jest-resolve@24.9.0 + jest-resolve: 24.9.0 jest-util: 24.9.0 jest-validate: 24.9.0 micromatch: 3.1.10 @@ -14436,7 +14436,7 @@ packages: jest-get-type: 26.3.0 jest-jasmine2: 26.6.3 jest-regex-util: 26.0.0 - jest-resolve: 26.6.2_jest-resolve@26.6.2 + jest-resolve: 26.6.2 jest-util: 26.6.2 jest-validate: 26.6.2 micromatch: 4.0.2 @@ -14780,7 +14780,7 @@ packages: integrity: sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== /jest-pnp-resolver/1.2.2_jest-resolve@24.9.0: dependencies: - jest-resolve: 24.9.0_jest-resolve@24.9.0 + jest-resolve: 24.9.0 dev: true engines: node: '>=6' @@ -14793,7 +14793,7 @@ packages: integrity: sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== /jest-pnp-resolver/1.2.2_jest-resolve@26.6.2: dependencies: - jest-resolve: 26.6.2_jest-resolve@26.6.2 + jest-resolve: 26.6.2 dev: true engines: node: '>=6' @@ -14836,7 +14836,7 @@ packages: node: '>= 10.14.2' resolution: integrity: sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== - /jest-resolve/24.9.0_jest-resolve@24.9.0: + /jest-resolve/24.9.0: dependencies: '@jest/types': 24.9.0 browser-resolve: 1.11.3 @@ -14846,11 +14846,9 @@ packages: dev: true engines: node: '>= 6' - peerDependencies: - jest-resolve: '*' resolution: integrity: sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ== - /jest-resolve/26.6.2_jest-resolve@26.6.2: + /jest-resolve/26.6.2: dependencies: '@jest/types': 26.6.2 chalk: 4.1.0 @@ -14863,8 +14861,6 @@ packages: dev: true engines: node: '>= 10.14.2' - peerDependencies: - jest-resolve: '*' resolution: integrity: sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== /jest-runner/24.9.0: @@ -14882,7 +14878,7 @@ packages: jest-jasmine2: 24.9.0 jest-leak-detector: 24.9.0 jest-message-util: 24.9.0 - jest-resolve: 24.9.0_jest-resolve@24.9.0 + jest-resolve: 24.9.0 jest-runtime: 24.9.0 jest-util: 24.9.0 jest-worker: 24.9.0 @@ -14909,7 +14905,7 @@ packages: jest-haste-map: 26.6.2 jest-leak-detector: 26.6.2 jest-message-util: 26.6.2 - jest-resolve: 26.6.2_jest-resolve@26.6.2 + jest-resolve: 26.6.2 jest-runtime: 26.6.3 jest-util: 26.6.2 jest-worker: 26.6.2 @@ -14937,7 +14933,7 @@ packages: jest-message-util: 24.9.0 jest-mock: 24.9.0 jest-regex-util: 24.9.0 - jest-resolve: 24.9.0_jest-resolve@24.9.0 + jest-resolve: 24.9.0 jest-snapshot: 24.9.0 jest-util: 24.9.0 jest-validate: 24.9.0 @@ -14973,7 +14969,7 @@ packages: jest-message-util: 26.6.2 jest-mock: 26.6.2 jest-regex-util: 26.0.0 - jest-resolve: 26.6.2_jest-resolve@26.6.2 + jest-resolve: 26.6.2 jest-snapshot: 26.6.2 jest-util: 26.6.2 jest-validate: 26.6.2 @@ -15011,7 +15007,7 @@ packages: jest-get-type: 24.9.0 jest-matcher-utils: 24.9.0 jest-message-util: 24.9.0 - jest-resolve: 24.9.0_jest-resolve@24.9.0 + jest-resolve: 24.9.0 mkdirp: 0.5.5 natural-compare: 1.4.0 pretty-format: 24.9.0 @@ -15035,7 +15031,7 @@ packages: jest-haste-map: 26.6.2 jest-matcher-utils: 26.6.2 jest-message-util: 26.6.2 - jest-resolve: 26.6.2_jest-resolve@26.6.2 + jest-resolve: 26.6.2 natural-compare: 1.4.0 pretty-format: 26.6.2 semver: 7.3.4 @@ -15770,7 +15766,7 @@ packages: elegant-spinner: 1.0.1 figures: 1.7.0 indent-string: 3.2.0 - listr: 0.14.3_listr@0.14.3 + listr: 0.14.3 log-symbols: 1.0.2 log-update: 2.3.0 strip-ansi: 3.0.1 @@ -15792,7 +15788,7 @@ packages: node: '>=4' resolution: integrity: sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw== - /listr/0.14.3_listr@0.14.3: + /listr/0.14.3: dependencies: '@samverschueren/stream-to-observable': 0.3.1_rxjs@6.5.5 is-observable: 1.1.0 @@ -15806,8 +15802,6 @@ packages: dev: true engines: node: '>=6' - peerDependencies: - listr: '*' resolution: integrity: sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA== /load-json-file/2.0.0: @@ -16431,7 +16425,7 @@ packages: loader-utils: 1.4.0 normalize-url: 1.9.1 schema-utils: 1.0.0 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 webpack-sources: 1.4.3 dev: true engines: @@ -17353,7 +17347,7 @@ packages: dependencies: cssnano: 4.1.10 last-call-webpack-plugin: 3.0.0 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 dev: true peerDependencies: webpack: ^4.0.0 @@ -19471,7 +19465,7 @@ packages: identity-obj-proxy: 3.0.0 jest: 24.9.0 jest-environment-jsdom-fourteen: 1.0.1 - jest-resolve: 24.9.0_jest-resolve@24.9.0 + jest-resolve: 24.9.0 jest-watch-typeahead: 0.4.2 mini-css-extract-plugin: 0.9.0_webpack@4.42.0 optimize-css-assets-webpack-plugin: 5.0.3_webpack@4.42.0 @@ -19491,7 +19485,7 @@ packages: terser-webpack-plugin: 2.3.8_webpack@4.42.0 ts-pnp: 1.1.6 url-loader: 2.3.0_file-loader@4.3.0+webpack@4.42.0 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 webpack-dev-server: 3.11.0_webpack@4.42.0 webpack-manifest-plugin: 2.2.0_webpack@4.42.0 workbox-webpack-plugin: 4.3.1_webpack@4.42.0 @@ -19989,7 +19983,7 @@ packages: integrity: sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== /request-promise-core/1.1.4_request@2.88.2: dependencies: - lodash: 4.17.20 + lodash: 4.17.21 request: 2.88.2 dev: true engines: @@ -20383,7 +20377,7 @@ packages: neo-async: 2.6.2 schema-utils: 2.7.1 semver: 6.3.0 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 dev: true engines: node: '>= 8.9.0' @@ -20718,7 +20712,7 @@ packages: lodash: 4.17.15 semver: 5.7.1 ts-node: 3.3.0 - webpack: 4.42.1_webpack@4.42.1 + webpack: 4.42.1 dev: true peerDependencies: webpack: '>= 3.0.0 < 6' @@ -20734,7 +20728,7 @@ packages: lodash: 4.17.20 semver: 6.3.0 ts-node: 8.10.2 - webpack: 4.42.1_webpack@4.42.1 + webpack: 4.42.1 dev: true peerDependencies: webpack: '>= 3.0.0 < 6' @@ -21866,7 +21860,7 @@ packages: serialize-javascript: 2.1.2 source-map: 0.6.1 terser: 4.6.11 - webpack: 4.41.2_webpack@4.41.2 + webpack: 4.41.2 webpack-sources: 1.4.3 worker-farm: 1.7.0 dev: true @@ -21885,7 +21879,7 @@ packages: serialize-javascript: 2.1.2 source-map: 0.6.1 terser: 4.6.11 - webpack: 4.42.1_webpack@4.42.1 + webpack: 4.42.1 webpack-sources: 1.4.3 worker-farm: 1.7.0 dev: true @@ -21904,7 +21898,7 @@ packages: serialize-javascript: 4.0.0 source-map: 0.6.1 terser: 4.8.0 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 webpack-sources: 1.4.3 worker-farm: 1.7.0 dev: true @@ -21924,7 +21918,7 @@ packages: serialize-javascript: 4.0.0 source-map: 0.6.1 terser: 4.8.0 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 webpack-sources: 1.4.3 dev: true engines: @@ -22602,7 +22596,7 @@ packages: loader-utils: 1.4.0 mime: 2.4.6 schema-utils: 2.7.1 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 dev: true engines: node: '>= 8.9.0' @@ -22919,7 +22913,7 @@ packages: loader-utils: 1.2.3 supports-color: 6.1.0 v8-compile-cache: 2.0.3 - webpack: 4.42.1_webpack@4.42.1 + webpack: 4.42.1 yargs: 13.2.4 dev: true engines: @@ -22935,7 +22929,7 @@ packages: mime: 2.4.6 mkdirp: 0.5.5 range-parser: 1.2.1 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 webpack-log: 2.0.0 dev: true engines: @@ -22975,7 +22969,7 @@ packages: strip-ansi: 3.0.1 supports-color: 6.1.0 url: 0.11.0 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 webpack-dev-middleware: 3.7.2_webpack@4.42.0 webpack-log: 2.0.0 ws: 6.2.1 @@ -23007,7 +23001,7 @@ packages: lodash: 4.17.20 object.entries: 1.1.3 tapable: 1.1.3 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 dev: true engines: node: '>=6.11.5' @@ -23026,7 +23020,7 @@ packages: dev: true resolution: integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - /webpack/4.41.2_webpack@4.41.2: + /webpack/4.41.2: dependencies: '@webassemblyjs/ast': 1.8.5 '@webassemblyjs/helper-module-context': 1.8.5 @@ -23055,11 +23049,9 @@ packages: engines: node: '>=6.11.5' hasBin: true - peerDependencies: - webpack: '*' resolution: integrity: sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A== - /webpack/4.42.0_webpack@4.42.0: + /webpack/4.42.0: dependencies: '@webassemblyjs/ast': 1.8.5 '@webassemblyjs/helper-module-context': 1.8.5 @@ -23088,11 +23080,9 @@ packages: engines: node: '>=6.11.5' hasBin: true - peerDependencies: - webpack: '*' resolution: integrity: sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w== - /webpack/4.42.1_webpack@4.42.1: + /webpack/4.42.1: dependencies: '@webassemblyjs/ast': 1.9.0 '@webassemblyjs/helper-module-context': 1.9.0 @@ -23121,8 +23111,6 @@ packages: engines: node: '>=6.11.5' hasBin: true - peerDependencies: - webpack: '*' resolution: integrity: sha512-SGfYMigqEfdGchGhFFJ9KyRpQKnipvEvjc1TwrXEPCM6H5Wywu10ka8o3KGrMzSMxMQKt8aCHUFh5DaQ9UmyRg== /websocket-driver/0.6.5: @@ -23369,7 +23357,7 @@ packages: dependencies: '@babel/runtime': 7.12.5 json-stable-stringify: 1.0.1 - webpack: 4.42.0_webpack@4.42.0 + webpack: 4.42.0 workbox-build: 4.3.1 dev: true engines: From 2414685c969ccb4440e2252411dc37d1253d1b49 Mon Sep 17 00:00:00 2001 From: ealemayehu Date: Fri, 12 Mar 2021 10:07:18 -0500 Subject: [PATCH 04/10] test: Delays to fix flakes caused when getting ID tokens (#387) * test: Retry jest configuration for flakey tests * Update pnpm lock file * test: Added 1-second delay to deflake tests * Boosted sleep for service catalog to prevent flakes Also added missing awaits. * test: Applied feedback from Jeet * test: Delays to fix flakes when getting ID tokens * test: Update default deflake delay to 2 seconds * Removed extraneous new line This is so that there won't be a change in the diff. Co-authored-by: Eyor Alemayehu --- .../support/resources/base/collection-resource.js | 2 +- main/integration-tests/support/resources/base/resource.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main/integration-tests/support/resources/base/collection-resource.js b/main/integration-tests/support/resources/base/collection-resource.js index 44de59e653..09439d49cc 100644 --- a/main/integration-tests/support/resources/base/collection-resource.js +++ b/main/integration-tests/support/resources/base/collection-resource.js @@ -108,7 +108,7 @@ class CollectionResource { // Specifies the delay duration in milliseconds needed to minimize the usage of stale data due to eventual // consistency. Duration can be altered by overriding function in sub-class. async deflakeDelay() { - return 1000; + return 2000; } } diff --git a/main/integration-tests/support/resources/base/resource.js b/main/integration-tests/support/resources/base/resource.js index b1a922a0ce..52d7185e62 100644 --- a/main/integration-tests/support/resources/base/resource.js +++ b/main/integration-tests/support/resources/base/resource.js @@ -126,7 +126,7 @@ class Resource { // Specifies the delay duration in milliseconds needed to minimize the usage of stale data due to eventual // consistency. Duration can be altered by overriding function in sub-class. async deflakeDelay() { - return 1000; + return 2000; } } From 6c317e8bbb698960733b1cc00c6462f00eedbf03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Mar 2021 10:31:27 -0500 Subject: [PATCH 05/10] chore(deps): bump websocket-extensions from 0.1.3 to 0.1.4 in /docs (#320) Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4. - [Release notes](https://github.com/faye/websocket-extensions-node/releases) - [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md) - [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Robert Smayda Co-authored-by: Jeet <68876606+jn1119@users.noreply.github.com> From b1a20aafac11d7bfb071f7876c6a998eab9e7352 Mon Sep 17 00:00:00 2001 From: Jeet <68876606+jn1119@users.noreply.github.com> Date: Fri, 12 Mar 2021 09:45:54 -0700 Subject: [PATCH 06/10] docs: Updated CHANGELOG for 2.0.3 release (#388) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92d0f4779f..e971792432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [2.0.3] - 2021-03-12 +- chore(deps): bump websocket-extensions from 0.1.3 to 0.1.4 +- test: fix flaky integ tests +- fix: emr workspace image. Lock jupyterlab to version 2.2.6 +- test: Implemented integration tests for service catalog workspaces +- feat: verbose integ test log + ## [2.0.2] - 2021-03-03 ### Added From b799b5bfb1305c07b04742725697c6b0e5f47bdc Mon Sep 17 00:00:00 2001 From: Jeet <68876606+jn1119@users.noreply.github.com> Date: Fri, 12 Mar 2021 10:46:59 -0700 Subject: [PATCH 07/10] Merge BYOB branch into develop (#385) * feat: bring your own bucket - registration part (#218) feat: bring your own bucket - registration part * feat: role allocations * feat: role allocations * feat: study access strategy extension point implementation * chore: rename EnvResourceUsageService to ResourceUsageService * feat: adding workflow logic for reachability check * feat: adding auth check and using updateStatus() * feat: code cleanup * feat: Change mount scripts for EC2 linux and windows to make mounting work for BYOB * Added feature branches to github workflow yml files * feat: getting dsAccounts for bulk reachability * feat: filesytem role service * fix: method name fix * feat: creating new reachability service * fix: code cleanup * feat: changes per code review * feat: adding lambda to check reachability per 5min * feat: filesystem role service * feat: add daily cron job for reachability check * feat: file system service test * feat: filesystem role service * feat: changes per code review * fix: code cleanup * fix: addressing the PR comments for file system role service * feat: adding unit tests * fix: code cleanup * fix: API body retrieval * feat: declaring workflow steps in plugins * fix: code cleanup after debug reachability * fix: permission revision on backend cfn * fix: providing required settings for cron lambda * feat: filesytem role service * feat: filesystem role service * feat: file system service test * feat: filesystem role service * fix: addressing the PR comments for file system role service * fix: changes per code review * feat: changes per review * fix: add guard for bulk check reachability * fix: add todo comment * feat: byob workflow changes * fix: kms_arn was giving an error and make kms_arn optinal * feat: byob populating iam policy and s3 mount info * feat: permission propagation * chor: environment mount serivce is deprecated * feat: updating devDependencies in lock * feat: adding stackCreated boolean update * fix: code review changes * feat: more unit tests * fix: linting * fix: linting * feat: generate cfn template and signed url * fix: unit tests * fix: remove unneeded declarations in unit tests * chor: remove unused method 'updateStudyIds' for now * feat: Added code for add/remove permissions for internal studies which use bucket policy * feat: add more unit tests to env resource service * feat: Added code for add/remove permissions for internal studies which use bucket policy * chor: remove unused method per PR review * chor: added more unit tests per PR review * feat: byob ui * feat: make reachability return the updated entity info * feat: byob data source listing ui * feat: byob data source listing UI * feat: byob data sources listing UI * feat: change text from 'check' to 'test' * fix: typo * chor: remove getStudyStore from the root store * fix: typo * feat: query cfn * fix: linting for now * feat: stack info retrieval * fix: reachability bugs * feat: byob stack detection logic * feat: byob registration ui * feat: studies list page update (#245) * Show appropriate permission level for external study * Update study card according to status * Update for status reachable Co-authored-by: zheyanyu * feat: byob registration UI * fix: a bug in the reachability service * feat: byob registration UI * chor: Updated pnpm-lock file after merge from master * feat: byob registration UI * fix: unit test fix for ds account service * chore: pnpm lock update * fix: UI make sure the store is ready before rendering * feat: byob registration ui - email template * Feat byob error handling unit tests filesystem (#247) * feat: adding idempotency tests * feat: unit tests to check allocateRole idempotency * feat: adding asserts for both attempts * feat: corrections per code review * feat: adding unit tests for deprovisionRole * feat: additional tests for provisionRole() * feat: byob update account info UI * fix: open data studies were not showing * fix: an existing issue in master, related to study.access * fix: cleanup and fix the issue of registering a new account * fix: address a few issues with reachability * chore: better messaging in the UI * feat: prevent overlappoing folders * fix: deal with null value in contact info in the UI * feat: only show label for studies that are not reachable * feat: reachability logic update * test: Unit test for env-provisioning-plugin, s3-arn, resource-usage-service (#250) Co-authored-by: zheyanyu * test:Feat new unit tests (#252) * test: adding dependencies in unit test * test: adding unit tests for appRoleSvc * test: added unit tests for app role methods * test: adding env config vars svc tests * test: added tests for isOverlapping * test: changes per code review * feat: env res service idempotency & async cleanup (#253) * fix: DS lists are set with a shorter tickPeriod (#254) * fix: Fixed cross-region S3 bucket access for windows * Feat byob fixes (#257) * feat: fixing byob code after e2e testing * feat: changes per code review * feat: added unit test for lambda * fix: check for empty JSON objects * feat: adding checks in lambda tests * fix: code cleanup * docs: Add openAPI definition (#256) * Add openAPI definition * Update PR template checlist Co-authored-by: zheyanyu * refactor: Remove create auth provider endpoint (#260) Co-authored-by: zheyanyu * feat: documentation updates for BYOB (#265) * feat: documentation updates for BYOB * feat: BYOB documentation (added new files) * feat: BYOB documentation, adding Note to upload files section * feat: BYOB documentation, fix to Introduction page, My Studies section Co-authored-by: Stephen Younge * docs: Add openAPI definition for User, Account, Index, Project, Compute and CfnTemplate management * docs: corrected API documentation for some APIs * docs: Update UpdateUser API with correct parameters * docs: Add user and authN APIs in api docs (#280) Co-authored-by: zheyanyu * docs: Added Open Api Docs for step-template-controller, workflow-controller, workflow-template-controller, and key-pair-controller (#276) * feat: Remove APIs for built-in workspaces and clarify documentation on the enableBuiltInWorkspaces flag * docs: Update UpdateUser API with correct parameters * docs: Correct UpdateCurrentUserInfo schema * chore: add schema checking to the DataSource APIs (#351) * fix: Fix bug in reachability handler * fix: Allow study admins to update study and its permissions * fix: change the default expiration of internally generated JWT tokens to one hour * chore: update input validation (#352) * fix: Restrict wildcards '*', '?' in study path to prevent authorization bypass (#364) * chore: add clarity to tests and make schema more permissible (#363) * Fix resources arn in create study integration test (#366) * fix: Restrict wildcards '*', '?' in study path to prevent authorization bypass * fix: Fix resources arn in create study integration test * fix: Add character and length restrictions on bucket names (#367) * fix: Restrict wildcards '*', '?' in study path to prevent authorization bypass * fix: Fix resources arn in create study integration test * fix: Make bucket names further restrictive * fix: Update pattern and maxLength on bucket name in request schema for RegisterDSbucket API * test: data source resources integration tests (#368) * tetst: data sources integration tests * test: integration test for updating ds accounts * test: integration test for registering data source buckets * test: integration test registering data source buckets * test: integration tests registering data source buckets * test: integration tests registering a study * test: intergration tests for registering studies * test: integration tests for listing registered studies * test: integration tests for cfn template generation for registered accounts * fix: address the PR suggestions Co-authored-by: Hatim Khan * feat: adding study API test cases for BYOB (#370) * feat: adding study API test cases for BYOB * feat: placing execute call in expect() * feat: execute in expect call - study perm * docs: byob changes * docs:well architected and byob corrections * Fix update permissions API for BYOB studies (#373) 1. Stop fetching CIDR info when making changes to environment with BYOB studies 2. Change lambda timeout to 30 seconds (from default 6 seconds) * test: api integration tests for workflows - part 1 of 2 (#378) * test: api integration tests for workflow drafts * test: api testing for workflow draft publishing * test: api integration tests for workflow versions listing * test: integration tests for workflow version get * test: added a few comments to the workflow vrersions get tests * test: address PR comments Co-authored-by: Hatim Khan * Feat misc test additions (#376) * feat: adding integ tests for misc resources * feat: adding costs and external CFN tests * fix: code cleanup * fix: fix comments * feat: adding test cases for guests * fix: Removed tests for creating auth providers since API was removed (#379) * fix: remove the POST workflow status query endpoint (#381) * fix: remove the POST workflow status query endpoint * fix: remove the reference to the workflow listing of status Co-authored-by: Hatim Khan * test: Add integ Tests for Budget API (#377) * refactor: update HTTP methods for create and update budget * test: add integ tests for Budgets API Co-authored-by: zheyanyu * docs: Integ test documentation (#382) * docs: README update for integration tests * docs: openapi update for get workflow assignments Co-authored-by: zheyanyu * test: api integration tests for workflows - part 2 of 2 (#383) * test: api integration tests for workflow drafts * test: api testing for workflow draft publishing * test: api integration tests for workflow versions listing * test: integration tests for workflow version get * test: added a few comments to the workflow vrersions get tests * test: address PR comments * test: integration tests for the workflow instances and the trigger * test: integration tests for the workflow instance and assignment * test: integration api for assignments * fix: address the PR comments Co-authored-by: Hatim Khan * Feat: Additional auth tests for study APIs (#384) * feat: additional auth tests for study APIs * feat: adding researcher test cases for study APIs * fix: removing duplicate scenario * docs: Updated CHANGELOG for 2.0.3 release * docs: Updated CHANGELOG for BYOB release in 2.1.0 Co-authored-by: Hatim Khan Co-authored-by: Hatim Khan Co-authored-by: SanketD92 Co-authored-by: Yanyu Zheng Co-authored-by: zheyanyu Co-authored-by: Stephen Younge <64421461+stephen-younge@users.noreply.github.com> Co-authored-by: Stephen Younge Co-authored-by: Tim Nguyen Co-authored-by: Robert Smayda Co-authored-by: Yogesh Sharma Co-authored-by: shyogesh-sw <79225266+shyogesh-sw@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 3 +- .github/workflows/unit-test-code-analysis.yml | 1 + CHANGELOG.md | 8 + .../packages/base-raas-ui/src/helpers/api.js | 63 +- .../base-raas-ui/src/helpers/errors.js | 6 +- .../src/models/constants/aws-regions.js | 49 + .../src/models/constants/bucket.js | 33 + .../models/data-sources/DataSourceAccount.js | 238 ++ .../data-sources/DataSourceAccountStore.js | 96 + .../data-sources/DataSourceAccountsStore.js | 188 + .../data-sources/DataSourceStackInfoStore.js | 59 + .../models/data-sources/DataSourceStudy.js | 137 + .../data-sources/DataSourceStudyStore.js | 66 + .../src/models/data-sources/StackInfo.js | 61 + .../register/RegisterStudyWizard.js | 207 + .../register/operations/PrepareCfn.js | 50 + .../register/operations/RegisterAccount.js | 51 + .../register/operations/RegisterBucket.js | 51 + .../register/operations/RegisterStudy.js | 43 + .../src/models/forms/RegisterStudyForm.js | 128 + .../forms/UpdateRegisteredAccountForm.js | 43 + .../src/models/helpers/Operation.js | 77 + .../src/models/operations/Operation.js | 119 + .../src/models/operations/Operations.js | 142 + .../src/models/studies/StudiesStore.js | 16 + .../base-raas-ui/src/models/studies/Study.js | 49 + .../src/models/studies/StudyPermissions.js | 25 +- .../models/studies/StudyPermissionsStore.js | 21 +- .../data-sources/DataSourceAccountCard.js | 227 ++ .../data-sources/DataSourceAccountCfn.js | 80 + .../data-sources/DataSourceAccountInfo.js | 119 + .../data-sources/DataSourceAccountsList.js | 133 + .../data-sources/DataSourceStudiesList.js | 120 + .../parts/data-sources/DataSourceStudyRow.js | 322 ++ .../data-sources/parts/AccountCfnPanel.js | 317 ++ .../parts/AccountConnectionPanel.js | 101 + .../parts/AccountStatusMessage.js | 184 + .../parts/StudyConnectionPanel.js | 101 + .../data-sources/parts/StudyStatusMessage.js | 152 + .../parts/data-sources/register/CfnStep.js | 108 + .../parts/data-sources/register/InputStep.js | 412 ++ .../data-sources/register/RegisterStudy.js | 112 + .../parts/data-sources/register/StartStep.js | 151 + .../parts/data-sources/register/SubmitStep.js | 231 ++ .../src/parts/studies/StudiesTab.js | 12 +- .../src/parts/studies/StudyFilesTable.js | 4 +- .../parts/studies/StudyPermissionsTable.js | 14 +- .../src/parts/studies/StudyRow.js | 35 +- .../src/plugins/app-context-items-plugin.js | 6 +- .../src/plugins/menu-items-plugin.js | 9 + .../base-raas-ui/src/plugins/routes-plugin.js | 4 + .../ec2-windows-instance.cfn.yml | 26 + .../lib/controllers/budgets-controller.js | 8 +- .../lib/controllers/costs-controller.js | 3 + .../lib/controllers/data-source-controller.js | 146 + .../lib/controllers/study-controller.js | 63 +- .../lib/plugins/routes-plugin.js | 2 + .../lib/plugins/services-plugin.js | 23 + .../budgets/__tests__/budgets-service.test.js | 88 + .../data-source-account-service.test.js | 390 ++ .../data-source-bucket-service.test.js | 249 ++ .../data-source-reachability-service.test.js | 335 ++ .../data-source-registration-service.test.js | 144 + .../environment-resource-service.test.js | 806 ++++ .../legacy/environment-resource-service.js | 553 +++ .../application-role-methods.test.js | 326 ++ .../application-role-service.test.js | 362 ++ .../environment-resource-service.test.js | 212 + .../__tests__/filesystem-role-service.test.js | 855 ++++ .../roles-only/application-role-service.js | 289 ++ .../environment-resource-service.js | 255 ++ .../roles-only/filesystem-role-service.js | 557 +++ .../roles-only/helpers/composite-keys.js | 55 + .../entities/application-role-methods.js | 327 ++ .../entities/filesystem-role-methods.js | 200 + .../data-source-account-service.js | 395 ++ .../data-source/data-source-bucket-service.js | 172 + .../data-source-reachability-service.js | 326 ++ .../data-source-registration-service.js | 234 ++ .../helpers/app-stack-cfn-resource.js | 93 + .../lib/data-source/helpers/composite-keys.js | 39 + .../entities/data-source-account-methods.js | 72 + .../entities/data-source-bucket-methods.js | 63 + .../environment-mount-service.test.js | 109 +- .../environment/environment-authz-service.js | 2 + .../environment/environment-mount-service.js | 171 +- .../environment-config-vars-service.test.js | 302 ++ .../environment-sc-connection-service.test.js | 20 + .../__tests__/environment-sc-service.test.js | 221 ++ .../environment-config-vars-service.js | 84 +- .../service-catalog/environment-sc-service.js | 235 +- .../lib/helpers/__tests__/s3-arn.test.js | 86 + .../lib/helpers/cfn-template.js | 62 + .../lib/helpers/composite-key.js | 39 + .../lib/helpers/iam/study-policy.js | 227 ++ .../base-raas-services/lib/helpers/s3-arn.js | 99 + .../base-raas-services/lib/helpers/utils.js | 47 + .../__tests__/env-provisioning-plugin.test.js | 498 +++ .../lib/plugins/env-provisioning-plugin.js | 203 +- .../lib/plugins/legacy-strategy-plugin.js | 91 + .../lib/plugins/roles-only-strategy-plugin.js | 141 + .../lib/schema/attempt-reach-data-source.json | 22 + .../lib/schema/create-budget.json | 19 +- .../lib/schema/create-study-permissions.json | 32 + .../lib/schema/create-study.json | 16 +- .../lib/schema/create-user.json | 3 +- .../lib/schema/get-study-permissions.json | 14 + .../schema/register-data-source-account.json | 58 + .../schema/register-data-source-bucket.json | 65 + .../lib/schema/register-study.json | 58 + .../lib/schema/ssh-connection-info-sc.json | 10 +- .../lib/schema/update-aws-accounts.json | 3 +- .../schema/update-data-source-account.json | 32 + .../schema/update-ds-account-stack-info.json | 22 + .../lib/schema/update-environment-sc.json | 5 +- .../lib/schema/update-indexes.json | 3 +- .../lib/schema/update-project.json | 3 +- .../lib/schema/update-study-permissions.json | 4 +- .../lib/schema/update-study.json | 34 +- .../lib/schema/update-user-roles.json | 13 +- .../lib/schema/update-user.json | 3 +- .../__tests__/storage-gateway-service.test.js | 8 +- .../storage-gateway-service.js | 13 +- .../__tests__/study-operation-service.test.js | 381 ++ .../study-permission-service.test.js | 1207 ++---- .../lib/study/__tests__/study-service.test.js | 1018 +++-- .../update-permissions-request.test.js | 167 + .../entities/__tests__/study-methods.test.js | 238 ++ .../study-permissions-methods.test.js | 59 + .../study/helpers/entities/study-methods.js | 204 + .../entities/study-permissions-methods.js | 81 + .../entities/user-permissions-methods.js | 52 + .../helpers/update-permissions-request.js | 89 + .../lib/study/study-operation-service.js | 290 ++ .../lib/study/study-permission-service.js | 743 ++-- .../lib/study/study-service.js | 545 ++- .../__test__/resource-usage-service.test.js | 179 + .../lib/usage/helpers/composite-keys.js | 14 + .../lib/usage/resource-usage-service.js | 143 + .../packages/base-raas-services/package.json | 19 +- .../lib/plugins/workflow-steps-plugin.js | 6 + .../bulk-reachability-check.js | 45 + .../bulk-reachability-check.yml | 8 + .../ds-account-status-change.js | 48 + .../ds-account-status-change.yml | 8 + .../lib/plugins/workflows-plugin.js | 4 + .../lib/workflows/bulk-reachability-check.yml | 17 + .../workflows/ds-account-status-change.yml | 17 + .../workflows/wf-ds-account-status-change.yml | 17 + .../lib/authentication-provider-controller.js | 10 - .../packages/base-ui/src/models/BaseStore.js | 15 +- .../packages/base-ui/src/models/Cleaner.js | 6 +- .../base-ui/src/parts/helpers/ErrorBox.js | 4 +- .../base-ui/src/parts/helpers/UserLabels.js | 2 +- .../src/parts/helpers/fields/DropDown.js | 2 +- .../parts/helpers/fields/SelectionButtons.js | 96 + .../lib/controllers/workflow-controller.js | 17 - .../base-workflow-ui/src/helpers/api.js | 5 - .../lib/schema/update-wf-assignment.json | 3 +- .../lib/workflow/workflow-instance-service.js | 20 - .../services/lib/__mocks__/db-service.js | 8 + .../packages/services/lib/iam/iam-service.js | 10 + .../plugin-registry-service.js | 10 + .../services/lib/schema/create-user.json | 3 +- .../services/lib/schema/update-user.json | 3 +- .../lib/plugins/workflow-steps-plugin.js | 4 + .../pre-environment-provisioning.js | 70 + .../pre-environment-provisioning.yml | 28 + .../workflows/provision-environment-sc.yml | 3 + .../__tests__/env-type-config-service.test.js | 77 +- .../__tests__/env-type-service.test.js | 210 + .../schema/create-env-type.js | 9 +- .../create-or-update-env-type-config.js | 10 + .../schema/update-env-type.js | 6 +- .../s3-synchronizer/src/s3-default-mounts.go | 8 +- .../s3-synchronizer/src/s3-download.go | 19 +- .../packages/s3-synchronizer/src/s3-mounts.go | 3 +- .../s3-synchronizer/src/s3-synchronizer.go | 32 +- .../src/s3-synchronizer_test.go | 22 +- docs/docs/deployment/post_deployment/logs.md | 43 + docs/docs/deployment/summary.md | 6 +- docs/docs/introduction.md | 7 +- .../sidebar/common/studies/data_sources.md | 82 + .../sidebar/common/studies/introduction.md | 18 +- .../sidebar/common/studies/studies_page.md | 30 + .../sidebar/common/studies/uploading_files.md | 16 - .../workspaces/create_workspace_study.md | 9 +- docs/sidebars.js | 13 +- main/integration-tests/README.md | 96 +- .../create-auth-provider-config.test.js | 97 - .../api-tests/budgets/create-budget.test.js | 68 + .../api-tests/budgets/get-budget.test.js | 65 + .../api-tests/budgets/update-budget.test.js | 69 + .../api-tests/costs/get-costs.test.js | 73 + .../data-sources/cfn-ds-account.test.js | 87 + .../data-sources/get-ds-accounts.test.js | 74 + .../data-sources/get-ds-studies.test.js | 135 + .../data-sources/reach-ds-account.test.js | 89 + .../data-sources/register-ds-account.test.js | 84 + .../data-sources/register-ds-bucket.test.js | 120 + .../data-sources/register-ds-study.test.js | 166 + .../data-sources/update-ds-account.test.js | 110 + .../__test__/api-tests/ip/get-ip.test.js | 64 + .../api-tests/studies/create-study.test.js | 123 +- .../api-tests/studies/get-studies.test.js | 201 + .../api-tests/studies/get-study-files.test.js | 212 + .../studies/get-study-permissions.test.js | 255 +- .../studies/get-study-upload-requests.test.js | 252 +- .../api-tests/studies/get-study.test.js | 177 +- .../studies/update-study-permissions.test.js | 408 +- .../api-tests/studies/update-study.test.js | 350 +- .../get-external-cfn-template.test.js | 86 + .../user-roles/get-user-roles.test.js | 111 + .../create-workflow-draft.test.js | 78 + .../delete-workflow-draft.test.js | 139 + .../get-workflow-drafts.test.js | 77 + .../publish-workflow-draft.test.js | 111 + .../update-workflow-draft.test.js | 138 + .../get-workflow-assignments.test.js | 77 + .../workflows/get-workflow-instances.test.js | 193 + .../workflows/get-workflow-versions.test.js | 179 + .../api-tests/workflows/get-workflows.test.js | 90 + .../workflows/trigger-workflow.test.js | 107 + .../config/settings/example.yml | 1 + main/integration-tests/support/aws/config.js | 1 + .../integration-tests/support/aws/services.js | 2 + .../support/aws/services/step-functions.js | 36 + main/integration-tests/support/aws/tables.js | 2 + .../complex/delete-workflow-instance.js | 74 + .../complex/delete-workflow-version.js | 125 + .../support/complex/unregister-account.js | 64 + .../support/complex/unregister-bucket.js | 84 + main/integration-tests/support/resources.js | 14 + .../authentication-provider-configs.js | 4 - .../support/resources/budgets/budget.js | 40 + .../support/resources/budgets/budgets.js | 66 + .../support/resources/costs/costs.js | 35 + .../support/resources/data-sources/account.js | 59 + .../resources/data-sources/accounts.js | 66 + .../support/resources/data-sources/bucket.js | 47 + .../support/resources/data-sources/buckets.js | 68 + .../support/resources/data-sources/studies.js | 59 + .../support/resources/data-sources/study.js | 44 + .../support/resources/ip/ip.js | 31 + .../support/resources/templates/template.js | 35 + .../support/resources/templates/templates.js | 38 + .../resources/user-roles/user-roles.js | 31 + .../workflows/workflow-assignments.js | 31 + .../resources/workflows/workflow-draft.js | 36 + .../resources/workflows/workflow-drafts.js | 98 + .../resources/workflows/workflow-instance.js | 49 + .../resources/workflows/workflow-instances.js | 72 + .../resources/workflows/workflow-version.js | 79 + .../resources/workflows/workflow-versions.js | 78 + .../support/resources/workflows/workflows.js | 51 + .../support/utils/generators.js | 2 + main/integration-tests/support/utils/utils.js | 4 +- .../backend/config/infra/cloudformation.yml | 112 + .../backend/config/infra/functions.yml | 35 + .../backend/config/settings/.defaults.yml | 15 +- main/solution/backend/serverless.yml | 7 + .../api-handler/plugins/plugin-registry.js | 3 + .../data-source-reachability-daily/handler.js | 36 + .../plugins/plugin-registry.js | 58 + .../__tests__/handler.test.js | 243 ++ .../data-source-reachability/handler.js | 65 + .../plugins/plugin-registry.js | 58 + .../env-status-poll-handler/handler.js | 15 + .../plugins/plugin-registry.js | 3 + .../config/environment-files/bin/mount_s3.sh | 41 +- .../config/settings/.defaults.yml | 3 + openapi.yaml | 3515 +++++++++++++++++ pnpm-lock.yaml | 565 +-- 273 files changed, 30888 insertions(+), 2440 deletions(-) create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/constants/aws-regions.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/constants/bucket.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccount.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccountStore.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccountsStore.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStackInfoStore.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStudy.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStudyStore.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/StackInfo.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/RegisterStudyWizard.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/PrepareCfn.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterAccount.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterBucket.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterStudy.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/forms/RegisterStudyForm.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/forms/UpdateRegisteredAccountForm.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/helpers/Operation.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/operations/Operation.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/models/operations/Operations.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountCard.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountCfn.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountInfo.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountsList.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceStudiesList.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceStudyRow.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/parts/AccountCfnPanel.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/parts/AccountConnectionPanel.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/parts/AccountStatusMessage.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/parts/StudyConnectionPanel.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/parts/StudyStatusMessage.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/register/CfnStep.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/register/InputStep.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/register/RegisterStudy.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/register/StartStep.js create mode 100644 addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/register/SubmitStep.js create mode 100644 addons/addon-base-raas/packages/base-raas-rest-api/lib/controllers/data-source-controller.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/__tests__/data-source-account-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/__tests__/data-source-bucket-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/__tests__/data-source-reachability-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/__tests__/data-source-registration-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/__tests__/environment-resource-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/legacy/environment-resource-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/__tests__/application-role-methods.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/__tests__/application-role-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/__tests__/environment-resource-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/__tests__/filesystem-role-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/application-role-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/environment-resource-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/filesystem-role-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/helpers/composite-keys.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/helpers/entities/application-role-methods.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/access-strategy/roles-only/helpers/entities/filesystem-role-methods.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/data-source-account-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/data-source-bucket-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/data-source-reachability-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/data-source-registration-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/helpers/app-stack-cfn-resource.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/helpers/composite-keys.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/helpers/entities/data-source-account-methods.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/data-source/helpers/entities/data-source-bucket-methods.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/environment/service-catalog/__tests__/environment-config-vars-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/helpers/__tests__/s3-arn.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/helpers/cfn-template.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/helpers/composite-key.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/helpers/iam/study-policy.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/helpers/s3-arn.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/helpers/utils.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/plugins/__tests__/env-provisioning-plugin.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/plugins/legacy-strategy-plugin.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/plugins/roles-only-strategy-plugin.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/schema/attempt-reach-data-source.json create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/schema/create-study-permissions.json create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/schema/get-study-permissions.json create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/schema/register-data-source-account.json create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/schema/register-data-source-bucket.json create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/schema/register-study.json create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/schema/update-data-source-account.json create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/schema/update-ds-account-stack-info.json create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/study/__tests__/study-operation-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/study/helpers/__tests__/update-permissions-request.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/study/helpers/entities/__tests__/study-methods.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/study/helpers/entities/__tests__/study-permissions-methods.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/study/helpers/entities/study-methods.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/study/helpers/entities/study-permissions-methods.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/study/helpers/entities/user-permissions-methods.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/study/helpers/update-permissions-request.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/study/study-operation-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/usage/__test__/resource-usage-service.test.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/usage/helpers/composite-keys.js create mode 100644 addons/addon-base-raas/packages/base-raas-services/lib/usage/resource-usage-service.js create mode 100644 addons/addon-base-raas/packages/base-raas-workflow-steps/lib/steps/bulk-reachability-check/bulk-reachability-check.js create mode 100644 addons/addon-base-raas/packages/base-raas-workflow-steps/lib/steps/bulk-reachability-check/bulk-reachability-check.yml create mode 100644 addons/addon-base-raas/packages/base-raas-workflow-steps/lib/steps/ds-account-status-change/ds-account-status-change.js create mode 100644 addons/addon-base-raas/packages/base-raas-workflow-steps/lib/steps/ds-account-status-change/ds-account-status-change.yml create mode 100644 addons/addon-base-raas/packages/base-raas-workflows/lib/workflows/bulk-reachability-check.yml create mode 100644 addons/addon-base-raas/packages/base-raas-workflows/lib/workflows/ds-account-status-change.yml create mode 100644 addons/addon-base-raas/packages/base-raas-workflows/lib/workflows/wf-ds-account-status-change.yml create mode 100644 addons/addon-base-ui/packages/base-ui/src/parts/helpers/fields/SelectionButtons.js create mode 100644 addons/addon-environment-sc-api/packages/environment-sc-workflow-steps/lib/steps/pre-environment-provisioning/pre-environment-provisioning.js create mode 100644 addons/addon-environment-sc-api/packages/environment-sc-workflow-steps/lib/steps/pre-environment-provisioning/pre-environment-provisioning.yml create mode 100644 docs/docs/deployment/post_deployment/logs.md create mode 100644 docs/docs/user_guide/sidebar/common/studies/data_sources.md create mode 100644 docs/docs/user_guide/sidebar/common/studies/studies_page.md delete mode 100644 docs/docs/user_guide/sidebar/common/studies/uploading_files.md delete mode 100644 main/integration-tests/__test__/api-tests/authentication/create-auth-provider-config.test.js create mode 100644 main/integration-tests/__test__/api-tests/budgets/create-budget.test.js create mode 100644 main/integration-tests/__test__/api-tests/budgets/get-budget.test.js create mode 100644 main/integration-tests/__test__/api-tests/budgets/update-budget.test.js create mode 100644 main/integration-tests/__test__/api-tests/costs/get-costs.test.js create mode 100644 main/integration-tests/__test__/api-tests/data-sources/cfn-ds-account.test.js create mode 100644 main/integration-tests/__test__/api-tests/data-sources/get-ds-accounts.test.js create mode 100644 main/integration-tests/__test__/api-tests/data-sources/get-ds-studies.test.js create mode 100644 main/integration-tests/__test__/api-tests/data-sources/reach-ds-account.test.js create mode 100644 main/integration-tests/__test__/api-tests/data-sources/register-ds-account.test.js create mode 100644 main/integration-tests/__test__/api-tests/data-sources/register-ds-bucket.test.js create mode 100644 main/integration-tests/__test__/api-tests/data-sources/register-ds-study.test.js create mode 100644 main/integration-tests/__test__/api-tests/data-sources/update-ds-account.test.js create mode 100644 main/integration-tests/__test__/api-tests/ip/get-ip.test.js create mode 100644 main/integration-tests/__test__/api-tests/template/get-external-cfn-template.test.js create mode 100644 main/integration-tests/__test__/api-tests/user-roles/get-user-roles.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflow-drafts/create-workflow-draft.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflow-drafts/delete-workflow-draft.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflow-drafts/get-workflow-drafts.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflow-drafts/publish-workflow-draft.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflow-drafts/update-workflow-draft.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflows/get-workflow-assignments.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflows/get-workflow-instances.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflows/get-workflow-versions.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflows/get-workflows.test.js create mode 100644 main/integration-tests/__test__/api-tests/workflows/trigger-workflow.test.js create mode 100644 main/integration-tests/support/aws/services/step-functions.js create mode 100644 main/integration-tests/support/complex/delete-workflow-instance.js create mode 100644 main/integration-tests/support/complex/delete-workflow-version.js create mode 100644 main/integration-tests/support/complex/unregister-account.js create mode 100644 main/integration-tests/support/complex/unregister-bucket.js create mode 100644 main/integration-tests/support/resources/budgets/budget.js create mode 100644 main/integration-tests/support/resources/budgets/budgets.js create mode 100644 main/integration-tests/support/resources/costs/costs.js create mode 100644 main/integration-tests/support/resources/data-sources/account.js create mode 100644 main/integration-tests/support/resources/data-sources/accounts.js create mode 100644 main/integration-tests/support/resources/data-sources/bucket.js create mode 100644 main/integration-tests/support/resources/data-sources/buckets.js create mode 100644 main/integration-tests/support/resources/data-sources/studies.js create mode 100644 main/integration-tests/support/resources/data-sources/study.js create mode 100644 main/integration-tests/support/resources/ip/ip.js create mode 100644 main/integration-tests/support/resources/templates/template.js create mode 100644 main/integration-tests/support/resources/templates/templates.js create mode 100644 main/integration-tests/support/resources/user-roles/user-roles.js create mode 100644 main/integration-tests/support/resources/workflows/workflow-assignments.js create mode 100644 main/integration-tests/support/resources/workflows/workflow-draft.js create mode 100644 main/integration-tests/support/resources/workflows/workflow-drafts.js create mode 100644 main/integration-tests/support/resources/workflows/workflow-instance.js create mode 100644 main/integration-tests/support/resources/workflows/workflow-instances.js create mode 100644 main/integration-tests/support/resources/workflows/workflow-version.js create mode 100644 main/integration-tests/support/resources/workflows/workflow-versions.js create mode 100644 main/integration-tests/support/resources/workflows/workflows.js create mode 100644 main/solution/backend/src/lambdas/data-source-reachability-daily/handler.js create mode 100644 main/solution/backend/src/lambdas/data-source-reachability-daily/plugins/plugin-registry.js create mode 100644 main/solution/backend/src/lambdas/data-source-reachability/__tests__/handler.test.js create mode 100644 main/solution/backend/src/lambdas/data-source-reachability/handler.js create mode 100644 main/solution/backend/src/lambdas/data-source-reachability/plugins/plugin-registry.js create mode 100644 openapi.yaml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2bdeca0e8c..6b0cc97fcb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,10 +9,11 @@ Checklist: - [ ] Have you successfully deployed to an AWS account with your changes? - [ ] Have you written new tests for your core changes, as applicable? - [ ] Have you successfully tested with your changes locally? +- [ ] Have you updated openapi.yaml if you made updates to API definition (including add, delete or update parameter and request data schema)? - [ ] If you had to run manual tests, have you considered automating those tests by adding them to [end-to-end tests](../main/end-to-end-tests/README.md)? AS review ticket id: -By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. +By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. \ No newline at end of file diff --git a/.github/workflows/unit-test-code-analysis.yml b/.github/workflows/unit-test-code-analysis.yml index 1510797c63..8285311cc4 100644 --- a/.github/workflows/unit-test-code-analysis.yml +++ b/.github/workflows/unit-test-code-analysis.yml @@ -8,6 +8,7 @@ on: pull_request: branches: - develop + - 'feat-*' jobs: static-code-analysis-and-unit-test: name: Unit Tests & Code Analysis diff --git a/CHANGELOG.md b/CHANGELOG.md index e971792432..6625dfde59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## [2.1.0] - 2021-03-12 + +### Added +- feat: Added Bring Your Own Bucket(BYOB) functionality +- feat: Added integration testing for all APIs +- feat: Added OpenAPI documentation +- feat: Removed unused APIs- listWorkflowInstancesByStatus and createAuthenticationProviderConfig + ## [2.0.3] - 2021-03-12 - chore(deps): bump websocket-extensions from 0.1.3 to 0.1.4 - test: fix flaky integ tests diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/helpers/api.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/helpers/api.js index 71229b8aa2..10a163799d 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/helpers/api.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/helpers/api.js @@ -37,11 +37,11 @@ function getAwsAccountBudget(accountUUID) { } function createAwsAccountBudget(budgetConfiguration) { - return httpApiPut(`api/budgets/aws-account`, { data: budgetConfiguration }); + return httpApiPost(`api/budgets/aws-account`, { data: budgetConfiguration }); } function updateAwsAccountBudget(budgetConfiguration) { - return httpApiPost(`api/budgets/aws-account`, { data: budgetConfiguration }); + return httpApiPut(`api/budgets/aws-account`, { data: budgetConfiguration }); } function addUsers(users) { @@ -288,7 +288,55 @@ function getWindowsRpInfo(envId, connectionId) { return httpApiGet(`api/workspaces/service-catalog/${envId}/connections/${connectionId}/windows-rdp-info`); } -// API Functions Insertion Point (do not change this text, it is being used by hygen cli) +function getDataSourceAccounts() { + return httpApiGet(`api/data-sources/accounts/`); +} + +function getDataSourceStudies(accountId) { + return httpApiGet(`api/data-sources/accounts/${accountId}/studies`); +} + +function checkAccountReachability(accountId) { + return httpApiPost('api/data-sources/accounts/ops/reachability', { + data: { id: accountId, type: 'dsAccount' }, + }); +} + +function checkStudyReachability(studyId) { + return httpApiPost('api/data-sources/accounts/ops/reachability', { + data: { id: studyId, type: 'study' }, + }); +} + +function registerAccount(account) { + return httpApiPost('api/data-sources/accounts', { + data: account, + }); +} + +function registerBucket(accountId, bucket) { + return httpApiPost(`api/data-sources/accounts/${accountId}/buckets`, { + data: bucket, + }); +} + +function registerStudy(accountId, bucketName, study) { + return httpApiPost(`api/data-sources/accounts/${accountId}/buckets/${bucketName}/studies`, { + data: study, + }); +} + +function generateAccountCfnTemplate(accountId) { + return httpApiPost(`api/data-sources/accounts/${accountId}/cfn`, { + data: {}, + }); +} + +function updateRegisteredAccount(accountId, data) { + return httpApiPut(`api/data-sources/accounts/${accountId}`, { + data, + }); +} export { addIndex, @@ -351,4 +399,13 @@ export { updateScEnvironmentCidrs, sendSshKey, getWindowsRpInfo, + getDataSourceAccounts, + getDataSourceStudies, + checkStudyReachability, + checkAccountReachability, + registerAccount, + registerBucket, + registerStudy, + generateAccountCfnTemplate, + updateRegisteredAccount, }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/helpers/errors.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/helpers/errors.js index 37c17c8abc..0de062c4b1 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/helpers/errors.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/helpers/errors.js @@ -62,4 +62,8 @@ const isForbidden = error => { return _.get(error, 'code') === 'forbidden'; }; -export { boom, isNotFound, isTokenExpired, isForbidden }; +const isAlreadyExists = error => { + return _.get(error, 'code') === 'alreadyExists'; +}; + +export { boom, isNotFound, isTokenExpired, isForbidden, isAlreadyExists }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/constants/aws-regions.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/constants/aws-regions.js new file mode 100644 index 0000000000..337efd7639 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/constants/aws-regions.js @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +/* eslint-disable import/prefer-default-export */ +import _ from 'lodash'; + +const regionNames = [ + 'us-east-1', + 'us-east-2', + 'us-west-1', + 'us-west-2', + 'af-south-1', + 'ap-east-1', + 'ap-south-1', + 'ap-northeast-1', + 'ap-northeast-2', + 'ap-northeast-3', + 'ap-southeast-1', + 'ap-southeast-2', + 'ca-central-1', + 'eu-central-1', + 'eu-north-1', + 'eu-south-1', + 'eu-west-1', + 'eu-west-2', + 'eu-west-3', + 'me-south-1', + 'sa-east-1', +]; + +const regionOptions = _.map(regionNames, name => ({ + key: name, + value: name, + text: name, +})); + +export { regionNames, regionOptions }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/constants/bucket.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/constants/bucket.js new file mode 100644 index 0000000000..1e246348a4 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/constants/bucket.js @@ -0,0 +1,33 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +/* eslint-disable import/prefer-default-export */ + +const encryptionOptions = [ + { + text: 'AWS Key Management Service key (SSE-KMS)', + value: 'kms', + }, + { + text: 'Amazon S3 key (SSE-S3)', + value: 's3', + }, + { + text: 'Disabled', + value: 'none', + }, +]; + +export { encryptionOptions }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccount.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccount.js new file mode 100644 index 0000000000..db3f6a288c --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccount.js @@ -0,0 +1,238 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import _ from 'lodash'; +import { values } from 'mobx'; +import { types } from 'mobx-state-tree'; + +import { consolidateToMap } from '@aws-ee/base-ui/dist/helpers/utils'; + +import { DataSourceStudy } from './DataSourceStudy'; +import { StackInfo } from './StackInfo'; + +const states = { + pending: { + id: 'pending', + display: 'Pending', + color: 'orange', + }, + error: { + id: 'error', + display: 'Unavailable', + color: 'red', + }, + reachable: { + id: 'reachable', + display: 'Available', + color: 'green', + }, +}; + +// ================================================================== +// DataSourceAccount +// ================================================================== +const DataSourceAccount = types + .model('DataSourceAccount', { + id: '', + rev: types.maybe(types.number), + name: '', + createdAt: '', + createdBy: '', + updatedAt: '', + updatedBy: '', + stackCreated: false, + mainRegion: '', + qualifier: '', + contactInfo: types.optional(types.maybeNull(types.string), ''), + stack: '', + status: '', + statusMsg: '', + statusAt: '', + description: types.optional(types.maybeNull(types.string), ''), + type: '', // managed vs unmanaged + templateIdExpected: '', + templateIdFound: '', + stackId: '', + buckets: types.array(types.frozen()), + studies: types.map(DataSourceStudy), + stackInfo: types.optional(StackInfo, {}), + }) + .actions(self => ({ + setDataSourceAccount(raw = {}) { + _.forEach(raw, (value, key) => { + if (value === 'studies') return; // we don't want to update the studies + if (value === 'stackInfo') return; // we don't want to update the stack info + self[key] = value; + }); + + // We want to take care of thee statusMsg because it might come as undefined + if (_.isUndefined(raw.statusMsg)) self.statusMsg = ''; + }, + + setStudies(studies) { + consolidateToMap(self.studies, studies, (existing, newItem) => { + existing.setStudy(newItem); + }); + }, + + setStudy(study) { + self.studies.set(study.id, study); + + return self.studies.get(study.id); + }, + + setBucket(bucket) { + // Because buckets are frozen, we need to deep clone first + const buckets = _.cloneDeep(self.buckets); + buckets.push(bucket); + self.buckets = buckets; + + return bucket; + }, + + setStackInfo(stackInfo) { + self.stackInfo.setStackInfo(stackInfo); + }, + })) + + // eslint-disable-next-line no-unused-vars + .views(self => ({ + get studiesList() { + return _.orderBy(values(self.studies), ['id'], ['asc']); + }, + + getStudy(studyId) { + return self.studies.get(studyId); + }, + + get state() { + return states[self.status] || states.reachable; + }, + + get pendingState() { + return self.status === 'pending'; + }, + + get errorState() { + return self.status === 'error'; + }, + + get reachableState() { + return self.status === 'reachable'; + }, + + get statusMessageInfo() { + const msg = self.statusMsg; + const info = { + prefix: '', + color: 'grey', + message: msg, + }; + + if (_.isEmpty(msg)) return info; + + if (_.startsWith(msg, 'WARN|||')) { + info.prefix = 'WARN'; + info.message = _.nth(_.split(msg, '|||'), 1); + info.color = 'orange'; + } else if (_.startsWith(msg, 'ERR|||')) { + info.prefix = 'ERR'; + info.message = _.nth(_.split(msg, '|||'), 1); + info.color = 'red'; + } + + return info; + }, + + get stackOutDated() { + return !_.isEmpty(self.stackId) && self.stackCreated && self.templateIdExpected !== self.templateIdFound; + }, + + get incorrectStackNameProvisioned() { + return _.isEmpty(self.stackId) && self.stackCreated; + }, + + getBucket(name) { + return _.find(self.buckets, bucket => bucket.name === name); + }, + + get bucketNames() { + return _.map(self.buckets, bucket => bucket.name); + }, + + getStudiesForBucket(name) { + return _.filter(values(self.studies), study => study.bucket === name); + }, + + get emailCommonSection() { + const names = self.bucketNames; + const lines = ['Dear Admin,', '', 'We are requesting access to the following bucket(s) and studies:']; + _.forEach(names, name => { + lines.push(`\nBucket name: ${name}`); + const studies = self.getStudiesForBucket(name); + _.forEach(studies, study => { + lines.push(` - folder: ${study.folder}`); + lines.push(` access: ${study.friendlyAccessType}`); + }); + }); + + lines.push(''); + lines.push( + 'For your convenience, you can follow these steps to configure the account for the requested access:\n', + ); + + return lines; + }, + + get updateStackEmailTemplate() { + const { id, mainRegion, stackInfo = {} } = self; + const { cfnConsoleUrl, updateStackUrl, urlExpiry } = stackInfo; + const lines = _.slice(self.emailCommonSection); + + lines.push( + `1 - Log in to the aws console using the correct account. Please ensure that you are using the correct account # ${id} and region ${mainRegion}\n`, + ); + lines.push(`2 - Go to the AWS CloudFormation console ${cfnConsoleUrl}\n`); + lines.push(` You need to visit the AWS CloudFormation console page before you can follow the next link\n`); + lines.push(`3 - Click on the following link\n`); + lines.push(` ${updateStackUrl}\n`); + lines.push( + ' The link takes you to the CloudFormation console where you can review the stack information and provision it.\n', + ); + lines.push(` Note: the link expires at ${new Date(urlExpiry).toISOString()}`); + lines.push(`\n\nRegards,\nService Workbench admin`); + return lines.join('\n'); + }, + + get createStackEmailTemplate() { + const { id, mainRegion, stackInfo = {} } = self; + const { createStackUrl, urlExpiry } = stackInfo; + const lines = _.slice(self.emailCommonSection); + + lines.push( + `1 - Log in to the aws console using the correct account. Please ensure that you are using the correct account # ${id} and region ${mainRegion}\n`, + ); + lines.push(`2 - Click on the following link\n`); + lines.push(` ${createStackUrl}\n`); + lines.push( + ' The link takes you to the CloudFormation console where you can review the stack information and provision it.\n', + ); + lines.push(` Note: the link expires at ${new Date(urlExpiry).toISOString()}`); + lines.push(`\n\nRegards,\nService Workbench admin`); + return lines.join('\n'); + }, + })); + +export { DataSourceAccount }; // eslint-disable-line import/prefer-default-export diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccountStore.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccountStore.js new file mode 100644 index 0000000000..4bbb4ac11f --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccountStore.js @@ -0,0 +1,96 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { getParent, types } from 'mobx-state-tree'; +import { BaseStore } from '@aws-ee/base-ui/dist/models/BaseStore'; + +import { getDataSourceStudies } from '../../helpers/api'; +import { DataSourceStudyStore } from './DataSourceStudyStore'; +import { DataSourceStackInfoStore } from './DataSourceStackInfoStore'; + +// ================================================================== +// DataSourceAccountStore +// ================================================================== +const DataSourceAccountStore = BaseStore.named('DataSourceAccountStore') + .props({ + accountId: '', + studyStores: types.map(DataSourceStudyStore), + stackInfoStore: types.maybe(DataSourceStackInfoStore), + tickPeriod: 60 * 1000, // 1 minute + }) + + .actions(self => { + // save the base implementation of cleanup + const superCleanup = self.cleanup; + + return { + async doLoad() { + const studies = await getDataSourceStudies(self.accountId); + const account = self.account; + account.setStudies(studies); + }, + + getStudyStore(studyId) { + let entry = self.studyStores.get(studyId); + if (!entry) { + // Lazily create the store + self.studyStores.set(studyId, DataSourceStudyStore.create({ accountId: self.accountId, studyId })); + entry = self.studyStores.get(studyId); + } + + return entry; + }, + + getStackInfoStore() { + let entry = self.stackInfoStore; + if (!entry) { + // Lazily create the store + self.stackInfoStore = DataSourceStackInfoStore.create({ accountId: self.accountId }); + entry = self.stackInfoStore; + } + + return entry; + }, + + cleanup: () => { + self.accountId = ''; + self.studyStores.clear(); + self.stackInfoStore.clear(); + superCleanup(); + }, + }; + }) + + .views(self => ({ + get account() { + const parent = getParent(self, 2); + const a = parent.getAccount(self.accountId); + return a; + }, + + get studiesTotal() { + const account = self.account || { studies: {} }; + return account.studies.size; + }, + + getStudy(studyId) { + return self.account.getStudy(studyId); + }, + })); + +// Note: Do NOT register this in the global context, if you want to gain access to an instance +// use dataSourceAccountsStore.getDataSourceAccountStore() +// eslint-disable-next-line import/prefer-default-export +export { DataSourceAccountStore }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccountsStore.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccountsStore.js new file mode 100644 index 0000000000..f635cec850 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceAccountsStore.js @@ -0,0 +1,188 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import _ from 'lodash'; +import React from 'react'; +import { Header } from 'semantic-ui-react'; +import { values } from 'mobx'; +import { types } from 'mobx-state-tree'; +import { consolidateToMap } from '@aws-ee/base-ui/dist/helpers/utils'; +import { BaseStore } from '@aws-ee/base-ui/dist/models/BaseStore'; + +import { + getDataSourceAccounts, + checkStudyReachability, + checkAccountReachability, + registerAccount, + registerBucket, + registerStudy, + updateRegisteredAccount, +} from '../../helpers/api'; +import { DataSourceAccount } from './DataSourceAccount'; +import { DataSourceAccountStore } from './DataSourceAccountStore'; + +// ================================================================== +// DataSourceAccountsStore +// ================================================================== +const DataSourceAccountsStore = BaseStore.named('DataSourceAccountsStore') + .props({ + accounts: types.map(DataSourceAccount), + accountStores: types.map(DataSourceAccountStore), + tickPeriod: 3 * 60 * 1000, // 3 minutes + }) + + .actions(self => { + // save the base implementation of cleanup + const superCleanup = self.cleanup; + + return { + async doLoad() { + const accounts = await getDataSourceAccounts(); + self.runInAction(() => { + consolidateToMap(self.accounts, accounts, (existing, newItem) => { + existing.setDataSourceAccount(newItem); + }); + }); + }, + + addAccount(raw) { + const id = raw.id; + const previous = self.accounts.get(id); + + if (!previous) { + self.accounts.set(raw.id, raw); + } else { + previous.setDataSourceAccount(raw); + } + }, + + getAccountStore(accountId) { + let entry = self.accountStores.get(accountId); + if (!entry) { + // Lazily create the store + self.accountStores.set(accountId, DataSourceAccountStore.create({ accountId })); + entry = self.accountStores.get(accountId); + } + + return entry; + }, + + async updateAccount(account) { + const updatedAccount = await updateRegisteredAccount(account.id, _.omit(account, ['id'])); + const existingAccount = self.getAccount(account.id); + + // If we get null values for the props, we need to change them to empty string + if (_.isEmpty(updatedAccount.contactInfo)) { + updatedAccount.contactInfo = ''; + } + + if (_.isEmpty(updatedAccount.description)) { + updatedAccount.description = ''; + } + + if (_.isEmpty(updatedAccount.name)) { + updatedAccount.name = ''; + } + + existingAccount.setDataSourceAccount(updatedAccount); + }, + + async registerAccount(account) { + const newAccount = await registerAccount(account); + self.addAccount(newAccount); + + return self.getAccount(account.id); + }, + + async registerBucket(accountId, bucket = {}) { + const normalizedBucket = { ...bucket, awsPartition: 'aws', access: 'roles' }; + const account = self.getAccount(accountId); + if (_.isEmpty(account)) throw new Error(`Account #${accountId} is not loaded yet`); + + const newBucket = await registerBucket(accountId, normalizedBucket); + + return account.setBucket(newBucket); + }, + + async registerStudy(accountId, bucketName, study = {}) { + const account = self.getAccount(accountId); + if (_.isEmpty(account)) throw new Error(`Account #${accountId} is not loaded yet`); + + const newStudy = await registerStudy(accountId, bucketName, study); + + return account.setStudy(newStudy); + }, + + async checkAccountReachability(accountId) { + const accountEntity = await checkAccountReachability(accountId); + const account = self.getAccount(accountId); + if (account) account.setDataSourceAccount(accountEntity); + }, + + async checkStudyReachability(studyId) { + const studyEntity = await checkStudyReachability(studyId); + const account = self.getAccount(studyEntity.accountId); + const study = account.getStudy(studyId); + if (study) study.setStudy(studyEntity); + }, + + cleanup: () => { + self.accounts.clear(); + self.accountStores.clear(); + superCleanup(); + }, + }; + }) + + .views(self => ({ + get empty() { + return self.accounts.size === 0; + }, + + get total() { + return self.accounts.size; + }, + + get list() { + return _.orderBy(values(self.accounts), ['createdAt', 'name'], ['desc', 'asc']); + }, + + getAccount(id) { + return self.accounts.get(id); + }, + + get dropdownOptions() { + const result = _.map(values(self.accounts), account => ({ + key: account.id, + value: account.id, + text: account.id, + content: ( +
+ ), + })); + + return result; + }, + })); + +function registerContextItems(appContext) { + appContext.dataSourceAccountsStore = DataSourceAccountsStore.create({}, appContext); +} + +export { DataSourceAccountsStore, registerContextItems }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStackInfoStore.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStackInfoStore.js new file mode 100644 index 0000000000..947dcdd22b --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStackInfoStore.js @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { getParent } from 'mobx-state-tree'; +import { BaseStore } from '@aws-ee/base-ui/dist/models/BaseStore'; + +import { generateAccountCfnTemplate } from '../../helpers/api'; + +// ================================================================== +// DataSourceStackInfoStore +// ================================================================== +const DataSourceStackInfoStore = BaseStore.named('DataSourceStackInfoStore') + .props({ + accountId: '', + tickPeriod: 2 * 60 * 1000, // 2 minutes + }) + + .actions(self => { + // save the base implementation of cleanup + const superCleanup = self.cleanup; + + return { + async doLoad() { + const account = self.account; + const stackInfo = await generateAccountCfnTemplate(self.accountId); + account.setStackInfo(stackInfo); + }, + + cleanup: () => { + self.accountId = ''; + superCleanup(); + }, + }; + }) + + .views(self => ({ + get account() { + const parent = getParent(self); + const a = parent.account; + return a; + }, + })); + +// Note: Do NOT register this in the global context, if you want to gain access to an instance +// use dataSourceAccountStore.getDataSourceStackInfoStore() +// eslint-disable-next-line import/prefer-default-export +export { DataSourceStackInfoStore }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStudy.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStudy.js new file mode 100644 index 0000000000..acc6f02342 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStudy.js @@ -0,0 +1,137 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import _ from 'lodash'; +import { types } from 'mobx-state-tree'; + +const states = { + pending: { + id: 'pending', + display: 'Pending', + color: 'orange', + }, + error: { + id: 'error', + display: 'Unavailable', + color: 'red', + }, + reachable: { + id: 'reachable', + display: 'Available', + color: 'green', + }, +}; + +// ================================================================== +// DataSourceStudy +// ================================================================== +const DataSourceStudy = types + .model('DataSourceStudy', { + id: '', + rev: types.maybe(types.number), + name: '', + folder: '', + accountId: '', + awsPartition: 'aws', + bucket: '', + accessType: '', + bucketAccess: '', + qualifier: '', + appRoleArn: '', + category: '', + region: '', + kmsScope: '', + kmsArn: '', + status: '', + statusMsg: '', + statusAt: '', + createdAt: '', + createdBy: '', + updatedAt: '', + updatedBy: '', + permissions: types.maybe(types.frozen()), + }) + .actions(self => ({ + setStudy(raw = {}) { + _.forEach(raw, (value, key) => { + if (value === 'permissions') return; // we don't want to update the permissions + if (_.isArray(value)) { + self[key].replace(value); + } else { + self[key] = value; + } + }); + // We want to take care of thee statusMsg because it might come as undefined + if (_.isUndefined(raw.statusMsg)) self.statusMsg = ''; + }, + + setPermissions(permissions = {}) { + self.permissions = permissions; + }, + })) + + // eslint-disable-next-line no-unused-vars + .views(self => ({ + get friendlyAccessType() { + if (self.accessType === 'readonly') return 'Read Only'; + if (self.accessType === 'writeonly') return 'Write Only'; + return 'Read & Write'; + }, + + get myStudies() { + return self.category === 'My Studies'; + }, + + get state() { + return states[self.status] || states.reachable; + }, + + get pendingState() { + return self.status === 'pending'; + }, + + get errorState() { + return self.status === 'error'; + }, + + get reachableState() { + return self.status === 'reachable'; + }, + + get statusMessageInfo() { + const msg = self.statusMsg; + const info = { + prefix: '', + color: 'grey', + message: msg, + }; + + if (_.isEmpty(msg)) return info; + + if (_.startsWith(msg, 'WARN|||')) { + info.prefix = 'WARN'; + info.message = _.nth(_.split(msg, '|||'), 1); + info.color = 'orange'; + } else if (_.startsWith(msg, 'ERR|||')) { + info.prefix = 'ERR'; + info.message = _.nth(_.split(msg, '|||'), 1); + info.color = 'red'; + } + + return info; + }, + })); + +export { DataSourceStudy }; // eslint-disable-line import/prefer-default-export diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStudyStore.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStudyStore.js new file mode 100644 index 0000000000..7fb42e09b8 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/DataSourceStudyStore.js @@ -0,0 +1,66 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import _ from 'lodash'; +import { getParent } from 'mobx-state-tree'; +import { BaseStore } from '@aws-ee/base-ui/dist/models/BaseStore'; + +import { getStudyPermissions } from '../../helpers/api'; + +// ================================================================== +// DataSourceStudyStore +// ================================================================== +const DataSourceStudyStore = BaseStore.named('DataSourceStudyStore') + .props({ + accountId: '', + studyId: '', + tickPeriod: 1 * 60 * 1000, // 1 minute + }) + + .actions(self => { + // save the base implementation of cleanup + const superCleanup = self.cleanup; + + return { + async doLoad() { + const study = self.study; + const permissions = await getStudyPermissions(self.studyId); + if (_.isUndefined(study)) return; + study.setPermissions(permissions); + }, + + cleanup: () => { + self.accountId = ''; + self.studyId = ''; + superCleanup(); + }, + }; + }) + + .views(self => ({ + get account() { + const parent = getParent(self, 2); + const a = parent.account; + return a; + }, + get study() { + return self.account.getStudy(self.studyId); + }, + })); + +// Note: Do NOT register this in the global context, if you want to gain access to an instance +// use dataSourceAccountsStore.getDataSourceStudyStore() +// eslint-disable-next-line import/prefer-default-export +export { DataSourceStudyStore }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/StackInfo.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/StackInfo.js new file mode 100644 index 0000000000..78d648ebbf --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/StackInfo.js @@ -0,0 +1,61 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import _ from 'lodash'; +import { types } from 'mobx-state-tree'; + +// ================================================================== +// StackInfo +// ================================================================== +const StackInfo = types + .model('StackInfo', { + id: '', + name: '', + region: '', + accountId: '', + stackId: '', + template: types.optional(types.frozen(), {}), + signedUrl: '', + createStackUrl: '', + updateStackUrl: '', + cfnConsoleUrl: '', + urlExpiry: 0, + }) + .actions(self => ({ + setStackInfo(raw = {}) { + _.forEach(raw, (value, key) => { + self[key] = value; + }); + }, + })) + + // eslint-disable-next-line no-unused-vars + .views(self => ({ + get formattedTemplate() { + return JSON.stringify(self.template, null, 2); + }, + + get hasUpdateStackUrl() { + return !_.isEmpty(self.updateStackUrl); + }, + + get expired() { + const now = Date.now(); + + return self.urlExpiry < now + 1000 * 60; // lets buffer 1 minute + }, + })); + +export { StackInfo }; // eslint-disable-line import/prefer-default-export diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/RegisterStudyWizard.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/RegisterStudyWizard.js new file mode 100644 index 0000000000..57e9d470ce --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/RegisterStudyWizard.js @@ -0,0 +1,207 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import _ from 'lodash'; +import { types, getEnv } from 'mobx-state-tree'; + +import Operations from '../../operations/Operations'; +import RegisterAccountOperation from './operations/RegisterAccount'; +import RegisterBucketOperation from './operations/RegisterBucket'; +import RegisterStudyOperation from './operations/RegisterStudy'; +import PrepareCfnOperation from './operations/PrepareCfn'; + +// ================================================================== +// RegisterStudyWizard +// ================================================================== +const RegisterStudyWizard = types + .model('RegisterStudyWizard', { + step: '', + accountId: '', + }) + + .volatile(_self => ({ + operations: undefined, + })) + + .actions(() => ({ + // I had issues using runInAction from mobx + // the issue is discussed here https://github.com/mobxjs/mobx-state-tree/issues/915 + runInAction(fn) { + return fn(); + }, + })) + + .actions(self => ({ + afterCreate: () => { + self.step = 'start'; + self.operations = new Operations(); + }, + + submit: async (formData = {}) => { + const providedAccount = formData.account || {}; + const providedBucket = formData.bucket || {}; + const studies = formData.studies || []; + const ops = self.operations; + const accountsStore = self.accountsStore; + const existingAccount = self.getAccount(providedAccount.id); + const existingBucket = existingAccount ? existingAccount.getBucket(providedBucket.name) : undefined; + + self.accountId = providedAccount.id; + ops.clear(); + + if (_.isEmpty(existingAccount)) { + ops.add(new RegisterAccountOperation({ account: providedAccount, accountsStore })); + } + + if (_.isEmpty(existingBucket)) { + ops.add(new RegisterBucketOperation({ accountId: providedAccount.id, bucket: providedBucket, accountsStore })); + } + + _.forEach(studies, providedStudy => { + const study = { ...providedStudy }; + // lets determine the kmsScope + const sse = providedBucket.sse; + const kmsArn = study.kmsArn; + if (!_.isEmpty(kmsArn)) study.kmsScope = 'study'; + else if (sse === 'kms') study.kmsScope = 'bucket'; + else study.kmsScope = 'none'; + + // make sure adminUsers is an array, this is because in the form drop down if the study is my studies, then + // we ask for a single value, which will not return an array + if (!_.isArray(study.adminUsers)) { + study.adminUsers = [study.adminUsers]; + } + + ops.add( + new RegisterStudyOperation({ + accountId: providedAccount.id, + bucket: providedBucket, + study: removeEmpty(study), + accountsStore, + }), + ); + }); + + ops.add(new PrepareCfnOperation({ accountId: providedAccount.id, accountsStore })); + + self.step = 'submit'; + await ops.run(); + }, + + retry: async () => { + self.step = 'submit'; + await self.operations.rerun(); + }, + + reset: () => { + self.cleanup(); + }, + + advanceToNextStep: () => { + if (self.step === 'start') { + self.step = 'input'; + } else if (self.step === 'submit') { + self.step = 'cfn'; + } + }, + + cleanup: () => { + self.step = 'start'; + if (self.operations) self.operations.clear(); + self.accountId = ''; + }, + })) + + // eslint-disable-next-line no-unused-vars + .views(self => ({ + get isStartStep() { + return self.step === 'start'; + }, + + get isInputStep() { + return self.step === 'input'; + }, + + get isSubmitStep() { + return self.step === 'submit'; + }, + + get isCfnStep() { + return self.step === 'cfn'; + }, + + get dropdownAccountOptions() { + const accountsStore = getEnv(self).dataSourceAccountsStore; + + return accountsStore.dropdownOptions; + }, + + get processedAccount() { + if (_.isEmpty(self.accountId)) return {}; + + return self.getAccount(self.accountId); + }, + + get accountsStore() { + return getEnv(self).dataSourceAccountsStore; + }, + + getAccount(id) { + return self.accountsStore.getAccount(id); + }, + + getBucket({ accountId, bucketName }) { + const account = self.getAccount(accountId); + if (_.isEmpty(account)) return undefined; + + return _.find(account.buckets, bucket => bucket.name === bucketName); + }, + + getBucketRegion({ accountId, bucketName }) { + const bucket = self.getBucket({ accountId, bucketName }); + if (_.isEmpty(bucket)) return undefined; + + return bucket.region; + }, + + getDropdownBucketOptions(accountId) { + const account = self.getAccount(accountId); + if (_.isEmpty(account)) return []; + + return _.map(account.buckets, bucket => ({ + key: bucket.name, + value: bucket.name, + text: bucket.name, + })); + }, + })); + +// Given an object returns a new object where all empty/undefined properties are removed +function removeEmpty(obj) { + const result = {}; + _.forEach(_.keys(obj), key => { + if (!_.isEmpty(obj[key])) { + result[key] = obj[key]; + } + }); + + return result; +} + +function registerContextItems(appContext) { + appContext.registerStudyWizard = RegisterStudyWizard.create({}, appContext); +} + +export { RegisterStudyWizard, registerContextItems }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/PrepareCfn.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/PrepareCfn.js new file mode 100644 index 0000000000..648e1b96a4 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/PrepareCfn.js @@ -0,0 +1,50 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { decorate, action } from 'mobx'; + +import { delay } from '@aws-ee/base-ui/dist/helpers/utils'; + +import { generateAccountCfnTemplate } from '../../../../helpers/api'; +import Operation from '../../../operations/Operation'; + +class PrepareCfnOperation extends Operation { + constructor({ accountId, accountsStore }) { + super(); + this.accountId = accountId; + this.name = `Preparing the latest CloudFormation for account #${accountId}`; + this.accountsStore = accountsStore; + } + + async doRun() { + const accountsStore = this.accountsStore; + const stackInfo = await generateAccountCfnTemplate(this.accountId); + + await delay(0.5); // We don't have strong read when we load the accounts, therefore we have this delay in place + await accountsStore.load(); + + const account = accountsStore.getAccount(this.accountId); + account.setStackInfo(stackInfo); + + this.setMessage(`Successfully prepared CloudFormation for account #${this.accountId}`); + } +} + +// see https://medium.com/@mweststrate/mobx-4-better-simpler-faster-smaller-c1fbc08008da +decorate(PrepareCfnOperation, { + doRun: action, +}); + +export default PrepareCfnOperation; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterAccount.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterAccount.js new file mode 100644 index 0000000000..7bb9a0c462 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterAccount.js @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { decorate, action } from 'mobx'; + +import { isAlreadyExists } from '../../../../helpers/errors'; +import Operation from '../../../operations/Operation'; + +class RegisterAccountOperation extends Operation { + constructor({ account = {}, accountsStore }) { + super(); + const { id } = account; + this.account = account; + this.name = `Registering account #${id}`; + this.accountsStore = accountsStore; + } + + async doRun() { + const { id } = this.account; + this.setMessage(`Registering AWS account #${id}`); + try { + await this.accountsStore.registerAccount(this.account); + this.setMessage(`Successfully registered account #${id}`); + } catch (error) { + // Check if the error is about existing account, if so, then skip it + if (!isAlreadyExists(error)) throw error; + + this.markSkipped(); + this.setMessage(`Skipping account registration, the account is already registered`); + } + } +} + +// see https://medium.com/@mweststrate/mobx-4-better-simpler-faster-smaller-c1fbc08008da +decorate(RegisterAccountOperation, { + doRun: action, +}); + +export default RegisterAccountOperation; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterBucket.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterBucket.js new file mode 100644 index 0000000000..1515e77be1 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterBucket.js @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { decorate, action } from 'mobx'; + +import { isAlreadyExists } from '../../../../helpers/errors'; +import Operation from '../../../operations/Operation'; + +class RegisterBucketOperation extends Operation { + constructor({ accountId, bucket = {}, accountsStore }) { + super(); + this.accountId = accountId; + this.bucket = bucket; + this.name = `Registering bucket ${bucket.name}`; + this.accountsStore = accountsStore; + } + + async doRun() { + const { name } = this.bucket; + this.setMessage(`Registering bucket${name}`); + try { + await this.accountsStore.registerBucket(this.accountId, this.bucket); + this.setMessage(`Successfully registered bucket ${name}`); + } catch (error) { + // Check if the error is about existing bucket, if so, then skip it + if (!isAlreadyExists(error)) throw error; + + this.markSkipped(); + this.setMessage(`Skipping bucket registration, the bucket is already registered`); + } + } +} + +// see https://medium.com/@mweststrate/mobx-4-better-simpler-faster-smaller-c1fbc08008da +decorate(RegisterBucketOperation, { + doRun: action, +}); + +export default RegisterBucketOperation; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterStudy.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterStudy.js new file mode 100644 index 0000000000..9b4542a2cc --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/data-sources/register/operations/RegisterStudy.js @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { decorate, action } from 'mobx'; + +import Operation from '../../../operations/Operation'; + +class RegisterStudyOperation extends Operation { + constructor({ accountId, bucket = {}, study = {}, accountsStore }) { + super(); + this.accountId = accountId; + this.bucket = bucket; + this.study = study; + this.name = `Registering study ${study.name || study.id}`; + this.accountsStore = accountsStore; + } + + async doRun() { + const study = this.study; + this.setMessage(`Registering study ${study.name || study.id}`); + await this.accountsStore.registerStudy(this.accountId, this.bucket.name, this.study); + this.setMessage(`Successfully registered study ${study.name || study.id}`); + } +} + +// see https://medium.com/@mweststrate/mobx-4-better-simpler-faster-smaller-c1fbc08008da +decorate(RegisterStudyOperation, { + doRun: action, +}); + +export default RegisterStudyOperation; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/forms/RegisterStudyForm.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/forms/RegisterStudyForm.js new file mode 100644 index 0000000000..60c13e074c --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/forms/RegisterStudyForm.js @@ -0,0 +1,128 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { createFormSeparatedFormat } from '../../helpers/form'; + +function getRegisterStudyForm() { + const fields = [ + 'account.id', + 'account.name', + 'account.description', + 'account.contactInfo', + 'account.mainRegion', + 'bucket.name', + 'bucket.region', + 'bucket.sse', + 'bucket.kmsArn', + 'studies', + 'studies[].id', + 'studies[].name', + 'studies[].folder', + 'studies[].kmsArn', + 'studies[].category', + 'studies[].accessType', + 'studies[].projectId', + 'studies[].description', + 'studies[].adminUsers', + ]; + + const labels = { + 'account.id': 'AWS Account ID', + 'account.name': 'Account Name', + 'account.contactInfo': 'Contact Information', + 'account.mainRegion': 'Region', + 'bucket.name': 'Bucket Name', + 'bucket.region': 'Bucket Region', + 'bucket.sse': 'Bucket Default Encryption', + 'bucket.kmsArn': 'KMS Arn', + 'studies': 'Studies', + 'studies[].id': 'Study Id', + 'studies[].name': 'Study Name', + 'studies[].folder': 'Study Folder', + 'studies[].kmsArn': 'Study KMS Arn', + 'studies[].category': 'Type', + 'studies[].accessType': 'Access', + 'studies[].projectId': 'Project', + 'studies[].description': 'Description', + 'studies[].adminUsers': 'Admin', + }; + + const placeholders = { + 'account.id': 'Type the AWS account id', + 'account.name': 'Give a name to this account. This is for UI display purposes only', + 'account.mainRegion': 'Pick a region', + 'account.contactInfo': + '(Optional) Type the contact information for the admins of this account. This information is purely for your convenience and it does not have any impact on the registration process.', + 'bucket.name': 'The name of the bucket', + 'bucket.region': 'Pick the bucket region', + 'bucket.sse': 'Bucket encryption', + 'bucket.kmsArn': 'KMS Arn (alias arn is not supported)', + 'studies[].id': 'A unique id for the study', + 'studies[].name': 'A name for the study', + 'studies[].folder': 'The study path in the bucket', + 'studies[].kmsArn': 'Only provide the kms arn if it is different for this study', + 'studies[].projectId': 'The project to associate with the study', + }; + + const extra = { + 'account.id': { + explain: 'The AWS account id that owns the bucket that contains the studies', + }, + 'account.mainRegion': { + explain: 'Pick a region that you intend to deploy the CloudFormation stack in', + }, + 'studies[].category': { + yesLabel: 'My Study', + noLabel: 'Organization Study', + yesValue: 'My Studies', + noValue: 'Organization', + }, + + 'studies[].accessType': { + yesLabel: 'Read Only', + noLabel: 'Read & Write', + yesValue: 'readonly', + noValue: 'readwrite', + }, + }; + + const rules = { + 'account.id': 'required|min:12|max:12|regex:/^[0-9]+$/', + 'account.name': 'required|max:300', + 'account.mainRegion': 'required', + 'bucket.name': 'required', + 'bucket.region': 'required', + 'bucket.sse': 'required', + 'bucket.kmsArn': 'required', + 'studies': 'required', + 'studies[].id': 'required|string|between:1,100|regex:/^[A-Za-z0-9-_]+$/', + 'studies[].name': 'string|max:2048', + 'studies[].folder': 'required|min:1|max:1000', + 'studies[].kmsArn': 'string|max:90', + 'studies[].category': 'required', + 'studies[].adminUsers': 'required', + }; + + const values = { + bucket: { + sse: 'kms', + }, + }; + + return createFormSeparatedFormat({ fields, labels, placeholders, extra, rules, values }); +} + +// eslint-disable-next-line import/prefer-default-export +export { getRegisterStudyForm }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/forms/UpdateRegisteredAccountForm.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/forms/UpdateRegisteredAccountForm.js new file mode 100644 index 0000000000..eb38cf1bd2 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/forms/UpdateRegisteredAccountForm.js @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { createForm } from '../../helpers/form'; + +function getAccountForm(account) { + const fields = { + name: { + label: 'Account Name', + placeholder: 'Give a name to this account. This is for UI display purposes only', + rules: 'required|max:300', + value: account.name, + }, + contactInfo: { + label: 'Contact Information', + placeholder: + '(Optional) Type the contact information for the admins of this account. This information is purely for your convenience and it does not have any impact on the registration process.', + rules: 'max:2048', + value: account.contactInfo, + }, + description: { + label: 'Description', + placeholder: '(Optional) A description for the account', + rules: 'max:2048', + value: account.description, + }, + }; + return createForm(fields); +} + +export { getAccountForm }; // eslint-disable-line import/prefer-default-export diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/helpers/Operation.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/helpers/Operation.js new file mode 100644 index 0000000000..531db1ebb1 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/helpers/Operation.js @@ -0,0 +1,77 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import { types } from 'mobx-state-tree'; +import { toErr, Err } from '@aws-ee/base-ui/dist/models/Err'; + +// ================================================================== +// Operation +// ================================================================== +const Operation = types + .model('Operation', { + id: '', + state: 'initial', // initial, processing, completed + error: types.maybe(Err), + }) + + .actions(() => ({ + // I had issues using runInAction from mobx + // the issue is discussed here https://github.com/mobxjs/mobx-state-tree/issues/915 + runInAction(fn) { + return fn(); + }, + })) + + .actions(self => ({ + async run(fn) { + self.state = 'processing'; + try { + await fn(); + self.runInAction(() => { + self.error = undefined; + }); + } catch (error) { + self.runInAction(() => { + self.error = toErr(error); + }); + throw error; + } finally { + self.runInAction(() => { + self.state = 'completed'; + }); + } + }, + })) + + // eslint-disable-next-line no-unused-vars + .views(self => ({ + get initial() { + return self.state === 'initial'; + }, + get processing() { + return self.state === 'processing'; + }, + get completed() { + return self.state === 'completed'; + }, + get hasError() { + return !!self.error; + }, + get errorMessage() { + return self.error ? self.error.message || 'unknown error' : ''; + }, + })); + +export { Operation }; // eslint-disable-line import/prefer-default-export diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/operations/Operation.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/operations/Operation.js new file mode 100644 index 0000000000..0cab0971d1 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/operations/Operation.js @@ -0,0 +1,119 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import _ from 'lodash'; +import { decorate, observable, action, computed } from 'mobx'; + +let counter = 0; + +/** + * A generic class that represents an operation. This class is not meant to be instantiated directly, instead you want + * to extend this class and provide a method named 'doRun' + */ +class Operation { + constructor() { + this.status = 'notStarted'; + this.error = ''; + this.privateSkipped = false; + counter += 1; + this.id = `${Date.now()}-${counter}`; + } + + async run(payload) { + try { + this.privateSkipped = false; + this.clearError(); + this.clearMessage(); + this.markRunning(); + await this.doRun(payload); + this.markFinished(); + } catch (error) { + this.setError(error); + this.markFinished(); + } + } + + markRunning() { + this.status = 'running'; + } + + markFinished() { + this.status = 'finished'; + } + + markSkipped() { + this.markFinished(); + this.privateSkipped = true; + } + + clearError() { + this.error = ''; + } + + setError(error) { + if (_.isString(error)) this.error = error; + else this.error = error.message; + } + + setMessage(message = '') { + this.message = message; + } + + clearMessage() { + this.message = ''; + } + + get running() { + return this.status === 'running'; + } + + get hasError() { + return this.error !== ''; + } + + get skipped() { + return !this.failure && this.status === 'finished' && this.privateSkipped; + } + + get success() { + return this.status === 'finished' && !this.hasError && !this.privateSkipped; + } + + get failure() { + return this.status === 'finished' && this.hasError; + } +} + +// see https://medium.com/@mweststrate/mobx-4-better-simpler-faster-smaller-c1fbc08008da +decorate(Operation, { + id: observable, + status: observable, + message: observable, + error: observable, + running: computed, + hasError: computed, + success: computed, + failure: computed, + skipped: computed, + markRunning: action, + markFinished: action, + markSkipped: action, + clearError: action, + setError: action, + clearMessage: action, + setMessage: action, +}); + +export default Operation; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/operations/Operations.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/operations/Operations.js new file mode 100644 index 0000000000..e214bfa5c4 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/operations/Operations.js @@ -0,0 +1,142 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-continue */ +/* eslint-disable no-restricted-syntax */ +import _ from 'lodash'; +import { decorate, observable, action, computed } from 'mobx'; + +class Operations { + constructor() { + this.ops = []; + this.status = 'notStarted'; // this is the overall status for all operations + this.payload = {}; + } + + add(op) { + this.ops.push(op); + } + + async run(payload) { + if (this.status === 'running') return; + this.payload = payload; + this.markRunning(); + for (const op of this.ops) { + if (op.success) continue; + if (op.running) continue; + if (op.skipped) continue; + await op.run(this.payload); + } + this.markFinished(); + } + + async rerun() { + if (this.status === 'running') return; + this.markNotStarted(); + + this.run(this.payload); + } + + markNotStarted() { + this.status = 'notStarted'; + } + + markRunning() { + this.status = 'running'; + } + + markFinished() { + this.status = 'finished'; + } + + clear() { + this.ops.clear(); + this.status = 'notStarted'; + this.payload = {}; + } + + get running() { + return this.status === 'running'; + } + + get notStarted() { + return this.status === 'notStarted'; + } + + get started() { + return !this.notStarted; + } + + get success() { + if (this.status !== 'finished') return false; + if (this.empty) return true; + + let result = true; + // eslint-disable-next-line consistent-return + _.forEach(this.ops, op => { + if (op.failure) { + result = false; + return false; // to stop the forEach loop since we got the answer we want + } + }); + + return result; + } + + // If we have one or more operations that failed + get failure() { + if (this.status !== 'finished') return false; + return !this.success; + } + + // True if all operations failed (not even skipped) + get allFailed() { + if (this.status !== 'finished') return false; + if (this.empty) return false; + + let result = true; + // eslint-disable-next-line consistent-return + _.forEach(this.ops, op => { + if (op.success || op.skipped) { + result = false; + return false; // to stop the forEach loop since we got the answer we want + } + }); + + return result; + } + + get empty() { + return this.ops.length === 0; + } +} + +// see https://medium.com/@mweststrate/mobx-4-better-simpler-faster-smaller-c1fbc08008da +decorate(Operations, { + ops: observable, + status: observable, + running: computed, + success: computed, + failure: computed, + allFailed: computed, + notStarted: computed, + markRunning: action, + markFinished: action, + markNotStarted: action, + clear: action, +}); + +export default Operations; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudiesStore.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudiesStore.js index 05c69766ee..0a04c0ee7b 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudiesStore.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudiesStore.js @@ -85,6 +85,7 @@ const StudiesStore = BaseStore.named('StudiesStore') cleanup: () => { self.studies.clear(); + self.studyStores.clear(); superCleanup(); }, }; @@ -122,6 +123,21 @@ function registerContextItems(appContext) { [categories.organization.id]: StudiesStore.create({ category: categories.organization.name }), [categories.openData.id]: StudiesStore.create({ category: categories.openData.name }), }; + + appContext.cleanupMap = { + // This method is going to be automatically called when the logout is invoked + cleanup: () => { + _.forEach(appContext.studiesStoresMap, obj => { + if (_.isFunction(obj.cleanup)) { + try { + obj.cleanup(); + } catch (error) { + console.error(error); + } + } + }); + }, + }; } export { registerContextItems }; diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/Study.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/Study.js index d51e7f858d..42307573d0 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/Study.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/Study.js @@ -15,10 +15,43 @@ import { types, applySnapshot } from 'mobx-state-tree'; +import _ from 'lodash'; import { StudyFilesStore } from './StudyFilesStore'; import { StudyPermissionsStore } from './StudyPermissionsStore'; import { categories } from './categories'; +// 'pending', 'error', 'default' +const states = { + pending: { + key: 'pending', + display: 'PENDING', + color: 'orange', + tip: + 'This study is in the process of being configured. Once the configuration is completed by the Service Workbench admins, the study status will become available.', + canChangePermission: false, + canSelect: false, + spinner: true, + }, + error: { + key: 'error', + display: 'UNAVAILABLE', + color: 'red', + tip: 'There seems to be an issue accessing the study files. Please contact Service Workbench admins.', + canChangePermission: false, + canSelect: false, + spinner: false, + }, + reachable: { + key: 'default', + display: 'AVAILABLE', + color: 'green', + tip: 'The study is available and ready for use.', + canChangePermission: true, + canSelect: true, + spinner: false, + }, +}; + // ================================================================== // Study // ================================================================== @@ -27,8 +60,10 @@ const Study = types id: types.identifier, rev: types.maybe(types.number), name: '', + status: types.maybe(types.string), category: '', projectId: '', + accessType: types.maybe(types.string), access: types.optional(types.array(types.string), []), resources: types.optional(types.array(types.model({ arn: types.string })), []), description: types.maybeNull(types.string), @@ -73,6 +108,20 @@ const Study = types return self.category === categories.organization.name; // TODO the backend should really send an id and not a name }, + get state() { + if (self.status) { + return _.cloneDeep(states[self.status]); + } + return _.cloneDeep(states.reachable); + }, + + get userTypes() { + if (self.accessType === 'readonly') { + return ['admin', 'readonly']; + } + return ['admin', 'readwrite', 'readonly']; + }, + get canUpload() { return self.access.includes('admin') || self.access.includes('readwrite'); }, diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissions.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissions.js index 81e7c25515..4f2ca70a8d 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissions.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissions.js @@ -22,17 +22,30 @@ import { types } from 'mobx-state-tree'; const StudyPermissions = types .model('StudyPermissions', { id: types.identifier, - adminUsers: types.optional(types.array(types.string), []), - readonlyUsers: types.optional(types.array(types.string), []), - readwriteUsers: types.optional(types.array(types.string), []), + adminUsers: types.array(types.string), + readonlyUsers: types.array(types.string), + readwriteUsers: types.array(types.string), + writeonlyUsers: types.array(types.string), createdAt: '', createdBy: '', updatedAt: '', updatedBy: '', }) - .views(_self => ({ - get userTypes() { - return ['admin', 'readwrite', 'readonly']; + .actions(self => ({ + setStudyPermissions(raw = {}) { + self.adminUsers.replace(raw.adminUsers || []); + self.readonlyUsers.replace(raw.readonlyUsers || []); + self.readwriteUsers.replace(raw.readwriteUsers || []); + self.writeonlyUsers.replace(raw.writeonlyUsers || []); + self.createdAt = raw.createdAt; + self.createdBy = raw.createdBy; + self.updatedAt = raw.updatedAt; + self.updatedBy = raw.updatedBy; + }, + })) + .views(self => ({ + isStudyAdmin(uid) { + return self.adminUsers.some(adminUid => adminUid === uid); }, })); diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissionsStore.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissionsStore.js index 1876ae2539..0414a2d42b 100644 --- a/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissionsStore.js +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/models/studies/StudyPermissionsStore.js @@ -15,7 +15,7 @@ /* eslint-disable import/prefer-default-export */ import _ from 'lodash'; -import { types } from 'mobx-state-tree'; +import { getParent, types } from 'mobx-state-tree'; import { BaseStore } from '@aws-ee/base-ui/dist/models/BaseStore'; import { getStudyPermissions, updateStudyPermissions } from '../../helpers/api'; @@ -38,21 +38,28 @@ const StudyPermissionsStore = BaseStore.named('StudyPermissionsStore') return { doLoad: async () => { const newPermissions = await getStudyPermissions(self.studyId); - if (!self.studyPermissions || !_.isEqual(self.studyPermissions, newPermissions)) { - self.runInAction(() => { - self.studyPermissions = newPermissions; - }); - } + self.runInAction(() => { + if (!self.studyPermissions) { + self.studyPermissions = StudyPermissions.create({ + id: self.studyId, + ...newPermissions, + }); + } else { + self.studyPermissions.setStudyPermissions(newPermissions); + } + }); }, cleanup: () => { + self.studyPermissions = undefined; superCleanup(); }, update: async selectedUserIds => { const updateRequest = { usersToAdd: [], usersToRemove: [] }; - self.studyPermissions.userTypes.forEach(type => { + const parent = getParent(self, 1); + parent.userTypes.forEach(type => { const userToRequestFormat = uid => ({ uid, permissionLevel: type }); // Set selected users as "usersToAdd" (API is idempotent) diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountCard.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountCard.js new file mode 100644 index 0000000000..c0a2cb97af --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountCard.js @@ -0,0 +1,227 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +/* eslint-disable max-classes-per-file */ +import _ from 'lodash'; +import React from 'react'; +import { decorate, computed, observable, action, runInAction } from 'mobx'; +import { observer, inject, Observer } from 'mobx-react'; +import { withRouter } from 'react-router-dom'; +import { Header, Tab, Label, Menu, Button, Message } from 'semantic-ui-react'; +import TimeAgo from 'react-timeago'; + +import { niceNumber, swallowError } from '@aws-ee/base-ui/dist/helpers/utils'; +import { isStoreError, isStoreNew, isStoreLoading } from '@aws-ee/base-ui/dist/models/BaseStore'; + +import By from '../helpers/By'; +import DataSourceStudiesList from './DataSourceStudiesList'; +import DataSourceAccountCfn from './DataSourceAccountCfn'; +import DataSourceAccountInfo from './DataSourceAccountInfo'; +import { Operation } from '../../models/helpers/Operation'; +import AccountConnectionPanel from './parts/AccountConnectionPanel'; +import AccountStatusMessage from './parts/AccountStatusMessage'; + +// This component is used with the TabPane to replace the default Segment wrapper since +// we don't want to display the border. +// eslint-disable-next-line react/prefer-stateless-function +class TabPaneWrapper extends React.Component { + render() { + return <>{this.props.children}; + } +} + +// expected props +// - account (via prop) +// - dataSourceAccountsStore (via injection) +class DataSourceAccountCard extends React.Component { + constructor(props) { + super(props); + runInAction(() => { + this.expanded = false; + this.connectionPanel = { + show: false, + operation: Operation.create({}), + }; + }); + } + + get account() { + return this.props.account; + } + + get accountsStore() { + return this.props.dataSourceAccountsStore; + } + + getAccountStore() { + const accountsStore = this.accountsStore; + const account = this.account || {}; + return accountsStore.getAccountStore(account.id); + } + + handleCheckConnection = () => { + this.connectionPanel.show = true; + + const account = this.account; + const accountsStore = this.accountsStore; + const operation = this.connectionPanel.operation; + const doWork = async () => { + await accountsStore.checkAccountReachability(account.id); + }; + + swallowError(operation.run(doWork)); + }; + + handleDismissPanel = () => { + this.connectionPanel.show = false; + }; + + render() { + const account = this.account; + const operation = this.connectionPanel.operation; + const showPanel = this.connectionPanel.show; + const reachable = account.reachableState; + const hasMsg = !_.isEmpty(account.statusMessageInfo.message); + const showMsg = !showPanel && (!reachable || (reachable && hasMsg)); + + return ( +
+ + {this.renderTitle(account)} + {this.renderStatus(account)} + {showMsg && } + {showPanel && ( + + )} + {this.renderStackMismatch(account)} + {this.renderTabs()} +
+ ); + } + + renderTabs() { + const getMenuItemLabel = () => { + const store = this.getAccountStore(); + const emptySpan = null; + if (!store) return emptySpan; + if (isStoreError(store)) return emptySpan; + if (isStoreNew(store)) return emptySpan; + if (isStoreLoading(store)) return emptySpan; + return ; + }; + + const account = this.account; + const panes = [ + { + menuItem: Studies {getMenuItemLabel()}, + render: () => ( + + {() => } + + ), + }, + { + menuItem: 'CloudFormation', + render: () => ( + + {() => } + + ), + }, + { + menuItem: 'Account Information', + render: () => ( + + {() => } + + ), + }, + ]; + + return ; + } + + renderTitle(account) { + return ( +
+ {account.name} + + + Registered + — + + + Status checked + — + + AWS Account # {account.id} + +
+ ); + } + + renderStatus(account) { + const { state } = account; + return ( + + ); + } + + renderStackMismatch(account) { + const stackOutDated = account.stackOutDated; + const incorrectStackNameProvisioned = account.incorrectStackNameProvisioned; + + if (!stackOutDated && !incorrectStackNameProvisioned) return null; + + if (incorrectStackNameProvisioned) { + return ( + + Incorrect stack name +

+ It seems that the correct CloudFormation stack was deployed to AWS account {account.id} but with an + incorrect stack name. Please ensure that you have the latest CloudFormation template deployed with the stack + name {account.stack} in the account. If you just updated the stack you can run the connection test again. +

+
+ ); + } + + return ( + + Stack is outdated +

+ It seems that the CloudFormation stack {account.stack} deployed to AWS account {account.id} is outdated + and does not contain the latest changes made. Please use the latest CloudFormation template to update the + stack. If you just updated the stack you can run the connection test again. +

+
+ ); + } +} + +// see https://medium.com/@mweststrate/mobx-4-better-simpler-faster-smaller-c1fbc08008da +decorate(DataSourceAccountCard, { + accountsStore: computed, + account: computed, + handleCheckConnection: action, + handleDismissPanel: action, + connectionPanel: observable, +}); + +export default inject('dataSourceAccountsStore')(withRouter(observer(DataSourceAccountCard))); diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountCfn.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountCfn.js new file mode 100644 index 0000000000..5404d26131 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountCfn.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { decorate, computed } from 'mobx'; +import { observer, inject } from 'mobx-react'; +import { withRouter } from 'react-router-dom'; + +import { swallowError } from '@aws-ee/base-ui/dist/helpers/utils'; +import { isStoreReady, isStoreLoading, isStoreError, stopHeartbeat } from '@aws-ee/base-ui/dist/models/BaseStore'; +import ErrorBox from '@aws-ee/base-ui/dist/parts/helpers/ErrorBox'; +import ProgressPlaceHolder from '@aws-ee/base-ui/dist/parts/helpers/BasicProgressPlaceholder'; + +import AccountCfnPanel from './parts/AccountCfnPanel'; + +// expected props +// - account (via prop) +// - dataSourceAccountsStore (via injection) +class DataSourceAccountCfn extends React.Component { + componentDidMount() { + const store = this.getStackInfoStore(); + if (!isStoreReady(store)) { + swallowError(store.load()); + } + store.startHeartbeat(); + } + + componentWillUnmount() { + const store = this.getStackInfoStore(); + stopHeartbeat(store); + } + + get account() { + return this.props.account; + } + + get accountsStore() { + return this.props.dataSourceAccountsStore; + } + + getStackInfoStore() { + const accountsStore = this.accountsStore; + const account = this.account || {}; + const accountStore = accountsStore.getAccountStore(account.id); + + return accountStore.getStackInfoStore(); + } + + render() { + const store = this.getStackInfoStore(); + let content = null; + + if (isStoreError(store)) { + content = ; + } else if (isStoreLoading(store)) { + content = ; + } else if (isStoreReady(store)) { + content = this.renderMain(); + } else { + content = null; + } + + return content; + } + + renderMain() { + const account = this.account; + + return ( +
+ +
+ ); + } +} + +// see https://medium.com/@mweststrate/mobx-4-better-simpler-faster-smaller-c1fbc08008da +decorate(DataSourceAccountCfn, { + accountsStore: computed, + account: computed, +}); + +export default inject('dataSourceAccountsStore')(withRouter(observer(DataSourceAccountCfn))); diff --git a/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountInfo.js b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountInfo.js new file mode 100644 index 0000000000..0cbb3e1d81 --- /dev/null +++ b/addons/addon-base-raas-ui/packages/base-raas-ui/src/parts/data-sources/DataSourceAccountInfo.js @@ -0,0 +1,119 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ + +import _ from 'lodash'; +import React from 'react'; +import { decorate, computed, runInAction, observable, action } from 'mobx'; +import { observer, inject } from 'mobx-react'; +import { withRouter } from 'react-router-dom'; +import { Button } from 'semantic-ui-react'; + +import { displaySuccess, displayError } from '@aws-ee/base-ui/dist/helpers/notification'; +import Form from '@aws-ee/base-ui/dist/parts/helpers/fields/Form'; +import Input from '@aws-ee/base-ui/dist/parts/helpers/fields/Input'; +import TextArea from '@aws-ee/base-ui/dist/parts/helpers/fields/TextArea'; + +import { getAccountForm } from '../../models/forms/UpdateRegisteredAccountForm'; + +// expected props +// - account (via prop) +// - dataSourceAccountsStore (via injection) +class DataSourceAccountInfo extends React.Component { + constructor(props) { + super(props); + runInAction(() => { + this.form = getAccountForm(props.account); + }); + } + + get account() { + return this.props.account; + } + + get accountsStore() { + return this.props.dataSourceAccountsStore; + } + + getFields(names, container) { + const form = container || this.form; + return _.map(names, name => form.$(name)); + } + + handleCancel = () => { + this.form.reset(); + }; + + handleSave = async form => { + const account = this.account; + const accountsStore = this.accountsStore; + const formData = form.values(); + + const data = { ...formData, id: account.id, rev: account.rev }; + try { + await accountsStore.updateAccount(data); + runInAction(() => { + this.form = getAccountForm(data); + }); + displaySuccess('Account information updated successfully'); + } catch (error) { + displayError(error); + } + }; + + render() { + const form = this.form; + const isDirty = form.isDirty; + + return ( +
+
+ {({ processing, /* onSubmit, */ onCancel }) => ( + <> + +