18 | )
19 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Configure/Create.types.ts:
--------------------------------------------------------------------------------
1 | type ErrorLevel = 'INFO' | 'WARNING' | 'ERROR'
2 |
3 | export type ConfigError = {
4 | id: string
5 | type: string
6 | level: ErrorLevel
7 | message: string
8 | }
9 |
10 | export type UpdateError = {
11 | parameter: string
12 | currentValue: string
13 | requestedValue: string
14 | message: string
15 | }
16 |
17 | export type CreateErrors = {
18 | message: string
19 | validationMessages?: ConfigError[]
20 | configurationValidationErrors?: ConfigError[]
21 | updateValidationErrors?: UpdateError[]
22 | }
23 |
--------------------------------------------------------------------------------
/awslambda/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | # with the License. A copy of the License is located at
5 | #
6 | # http://aws.amazon.com/apache2.0/
7 | #
8 | # or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | # limitations under the License.
11 |
--------------------------------------------------------------------------------
/frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/logger/ILogger.ts:
--------------------------------------------------------------------------------
1 | export interface ILogger {
2 | info(message: string, extra?: Record, source?: string): void
3 |
4 | warning(
5 | message: string,
6 | extra?: Record,
7 | source?: string,
8 | ): void
9 |
10 | debug(message: string, extra?: Record, source?: string): void
11 |
12 | error(
13 | message: Error | string,
14 | extra?: Record,
15 | source?: string,
16 | ): void
17 |
18 | critical(
19 | message: Error | string,
20 | extra?: Record,
21 | source?: string,
22 | ): void
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/src/components/useClusterPoll.tsx:
--------------------------------------------------------------------------------
1 | import {useEffect, useState} from 'react'
2 | import {DescribeCluster} from '../model'
3 |
4 | export const useClusterPoll = (clusterName: string, startOnRender: boolean) => {
5 | const [start, setStart] = useState(startOnRender)
6 |
7 | useEffect(() => {
8 | if (!start) return
9 | const timerId = setInterval(
10 | () => clusterName && DescribeCluster(clusterName),
11 | 5000,
12 | )
13 | return () => clearInterval(timerId)
14 | }, [start, clusterName])
15 |
16 | return {
17 | start: () => setStart(true),
18 | stop: () => setStart(false),
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/scripts/cognito-tools/common.sh:
--------------------------------------------------------------------------------
1 | RED='\033[0;31m'
2 | NC='\033[0m'
3 |
4 | check_region() {
5 | if [ -z $REGION ]; then
6 | echo -e "${RED}Region is not set. Exiting${NC}" 1>&2
7 | exit 1
8 | fi
9 | }
10 |
11 | get_user_pool_id() {
12 | aws cognito-idp list-user-pools --region "$REGION" --max-results 1 --query 'UserPools[0].Id' --output text
13 | }
14 |
15 | check_user_pool_id() {
16 | if [ -z $USER_POOL_ID ]; then
17 | echo -e "${RED}Cognito user pool id is NOT set, using first user pool returned by the query${NC}" 1>&2
18 | USER_POOL_ID=`get_user_pool_id`
19 | echo -e "${RED}Chosen Cognito user pool id is ${USER_POOL_ID}${NC}" 1>&2
20 | fi
21 | }
22 |
--------------------------------------------------------------------------------
/api/tests/security/test_secret_generator.py:
--------------------------------------------------------------------------------
1 | from api.security.fingerprint import CognitoFingerprintGenerator
2 |
3 |
4 | def test_cognito_fingerprint_generator():
5 | """
6 | With fixed values
7 | and a CognitoFingerprintGenerator
8 | it should produce the same fingerprint everytime
9 | """
10 | client_id, client_secret, user_pool_id = 'client-id', 'client-secret', 'pool-id'
11 | expected_fingerprint = '88056a6f3236e82b05de9bbb01a979b2877563c0c971148bc20aaa9aad8b3b85'
12 |
13 | gen = CognitoFingerprintGenerator(client_id, client_secret, user_pool_id)
14 | fingerprint = gen.fingerprint()
15 |
16 | assert fingerprint == expected_fingerprint
17 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 |
2 | changelog:
3 | exclude:
4 | labels:
5 | - DON'T MERGE
6 | - duplicate
7 | - invalid
8 | - question
9 | - wontfix
10 |
11 | categories:
12 | - title: Features
13 | labels:
14 | - release:feature
15 |
16 | - title: Changes
17 | labels:
18 | - release:improvement
19 |
20 | - title: Bugfixes
21 | labels:
22 | - release:bugfix
23 |
24 | - title: Deprecated
25 | labels:
26 | - release:deprecated
27 |
28 | - title: Breaking Changes
29 | labels:
30 | - release:breaking-change
31 |
32 | - title: Known issues
33 | labels:
34 | - bug
--------------------------------------------------------------------------------
/api/security/fingerprint.py:
--------------------------------------------------------------------------------
1 | from hashlib import pbkdf2_hmac
2 | from abc import ABC
3 |
4 |
5 | class IFingerprintGenerator(ABC):
6 |
7 | def fingerprint(self):
8 | pass
9 |
10 | SALT = 'cognito-fingerprint-salt'.encode()
11 |
12 | class CognitoFingerprintGenerator(IFingerprintGenerator):
13 |
14 | def __init__(self, client_id, client_secret, user_pool_id):
15 | self.client_id = client_id
16 | self.client_secret = client_secret
17 | self.user_pool_id = user_pool_id
18 |
19 | def fingerprint(self):
20 | to_encrypt = self.client_id + self.client_secret + self.user_pool_id
21 | return pbkdf2_hmac('sha256', to_encrypt.encode(), SALT, 500_000).hex()
--------------------------------------------------------------------------------
/e2e/fixtures/wizard.template.yaml:
--------------------------------------------------------------------------------
1 | HeadNode:
2 | InstanceType: t2.micro
3 | Networking:
4 | SubnetId: subnet-006e97a9837b1710d
5 | LocalStorage:
6 | RootVolume:
7 | VolumeType: gp3
8 | Scheduling:
9 | Scheduler: slurm
10 | SlurmQueues:
11 | - Name: queue0
12 | ComputeResources:
13 | - Name: queue0-compute-resource-0
14 | InstanceType: c5n.large
15 | MinCount: 0
16 | MaxCount: 4
17 | Networking:
18 | SubnetIds:
19 | - subnet-006e97a9837b1710d
20 | ComputeSettings:
21 | LocalStorage:
22 | RootVolume:
23 | VolumeType: gp3
24 | Region: ca-central-1
25 | Image:
26 | Os: alinux2
27 |
28 |
--------------------------------------------------------------------------------
/decisions/20221025-public-documentation-release.md:
--------------------------------------------------------------------------------
1 | # Public documentation release
2 |
3 | - Status: accepted
4 | - Date: 2022-10-24
5 |
6 | ## Context
7 | The [public documentation](https://pcluster.cloud/) is updated every time a new pull request is made. This is not always the desired behavior, especially when an amend is made to the documentation for unreleased ParallelCluster or ParallelCluster Manager features.
8 |
9 | ## Decision
10 | Change the documentation workflow's trigger to run only on releases.
11 |
12 | ## Consequences
13 | The documentation is updated only when ParallelCluster Mananager is released to customers. The downside is quick fixes to the documentation require more time to be released.
14 |
--------------------------------------------------------------------------------
/frontend/src/__tests__/console.test.tsx:
--------------------------------------------------------------------------------
1 | import {consoleDomain} from '../store'
2 |
3 | describe('Given a function to determine the console endpoint', () => {
4 | describe('when the current region is the US government one', () => {
5 | it('should point to the specific domain', () => {
6 | const domain = consoleDomain('us-gov')
7 | expect(domain).toBe('https://console.amazonaws-us-gov.com')
8 | })
9 | })
10 | describe('when the current region is NOT the US government one', () => {
11 | it('should point to the regional domain', () => {
12 | const domain = consoleDomain('eu-central-1')
13 | expect(domain).toBe('https://eu-central-1.console.aws.amazon.com')
14 | })
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/e2e/configs/environment.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | export const ENVIRONMENT_CONFIG = {
12 | URL: process.env.E2E_TEST_URL || 'https://pcui-demo.nuraghe.team'
13 | }
--------------------------------------------------------------------------------
/infrastructure/common.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
5 | # with the License. A copy of the License is located at
6 | #
7 | # http://aws.amazon.com/apache2.0/
8 | #
9 | # or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
10 | # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
11 | # limitations under the License.
12 |
13 | error(){
14 | echo "An error has occurred while releasing the infrastructure files."
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/e2e/configs/login.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | export const LOGIN_CONFIG = {
12 | username: process.env.E2E_TEST1_EMAIL || '',
13 | password: process.env.E2E_TEST1_PASSWORD || ''
14 | }
--------------------------------------------------------------------------------
/frontend/src/app-config/types.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | export interface AppConfig {
12 | authUrl: string
13 | clientId: string
14 | scopes: string
15 | redirectUri: string
16 | }
17 |
--------------------------------------------------------------------------------
/api/tests/security/csrf/conftest.py:
--------------------------------------------------------------------------------
1 | import hashlib
2 |
3 | import pytest
4 | from itsdangerous import URLSafeTimedSerializer
5 |
6 | from api.tests.security.csrf.test_csrf import MOCK_URANDOM_VALUE, SECRET_KEY, SALT
7 |
8 |
9 | @pytest.fixture
10 | def mock_csrf_token_value():
11 | _mock_csrf_token_value = hashlib.sha256(MOCK_URANDOM_VALUE).hexdigest()
12 | return _mock_csrf_token_value
13 |
14 |
15 | @pytest.fixture
16 | def mock_csrf_token_string(mock_csrf_token_value):
17 | return URLSafeTimedSerializer(SECRET_KEY, SALT, signer_kwargs={'digest_method': hashlib.sha256}).dumps(mock_csrf_token_value)
18 |
19 |
20 | @pytest.fixture(scope='function')
21 | def mock_parse_csrf(mocker):
22 | return mocker.patch('api.security.csrf.csrf.parse_csrf_token')
23 |
--------------------------------------------------------------------------------
/frontend/src/logger/LoggerProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, {useContext} from 'react'
2 | import {ILogger} from './ILogger'
3 | import {executeRequest} from '../http/executeRequest'
4 | import {ConsoleLogger} from './ConsoleLogger'
5 | import {Logger} from './RemoteLogger'
6 |
7 | export const logger: ILogger =
8 | process.env.NODE_ENV !== 'production'
9 | ? new ConsoleLogger()
10 | : new Logger(executeRequest)
11 |
12 | const LoggerContext = React.createContext(logger)
13 |
14 | export function useLogger(): ILogger {
15 | return useContext(LoggerContext)
16 | }
17 |
18 | export function LoggerProvider(props: any) {
19 | return (
20 |
21 | {props.children}
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/auth/types.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | export type UserRole = 'admin'
12 |
13 | export interface UserIdentity {
14 | attributes: {email: string}
15 | user_roles: UserRole[]
16 | username: string
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/http/csrf.ts:
--------------------------------------------------------------------------------
1 | import {RequestParams} from './executeRequest'
2 |
3 | export const requestWithCSRF = async (
4 | internalRequest: (...params: RequestParams) => Promise,
5 | ...params: RequestParams
6 | ) => {
7 | const [method, url, body, headers, appConfig] = params
8 |
9 | if (method === 'get') {
10 | return internalRequest(...params)
11 | }
12 | const {data} = (await internalRequest(
13 | 'get',
14 | '/csrf',
15 | null,
16 | {},
17 | appConfig,
18 | )) as {
19 | data: {csrf_token: string}
20 | }
21 | const tokenHeader = {
22 | 'X-CSRF-Token': data.csrf_token,
23 | }
24 | return internalRequest(
25 | method,
26 | url,
27 | body,
28 | {...headers, ...tokenHeader},
29 | appConfig,
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Configure/Queues/queues.validators.tsx:
--------------------------------------------------------------------------------
1 | export const QUEUE_NAME_MAX_LENGTH = 25
2 | export const queueNameErrorsMapping = {
3 | empty: 'wizard.queues.validation.emptyName',
4 | max_length: 'wizard.queues.validation.maxLengthName',
5 | forbidden_chars: 'wizard.queues.validation.forbiddenCharsName',
6 | }
7 | type QueueNameErrorKind = keyof typeof queueNameErrorsMapping
8 |
9 | export function validateQueueName(
10 | name: string,
11 | ): [true] | [false, QueueNameErrorKind] {
12 | if (!name) {
13 | return [false, 'empty']
14 | }
15 | if (name.length > QUEUE_NAME_MAX_LENGTH) {
16 | return [false, 'max_length']
17 | }
18 | if (!/^([a-z][a-z0-9-]+)$/.test(name)) {
19 | return [false, 'forbidden_chars']
20 | }
21 | return [true]
22 | }
23 |
--------------------------------------------------------------------------------
/decisions/20230327-disable-fetch-on-focus.md:
--------------------------------------------------------------------------------
1 | # Disable fetch on focus
2 |
3 | - Status: accepted
4 | - Tags: frontend, data, react-query
5 |
6 | ## Context
7 | [`react-query`](https://github.com/tanstack/query) by default re-fetches active queries on window focus. This causes the UI to activate its loading animations, if there's any. This is a good behaviour for a PWA or other specific type of data visualizion, but it's less relevant for PCUI.
8 |
9 | ## Decision
10 | We [disabled](https://github.com/aws/aws-parallelcluster-ui/pull/126/commits/1e60ffaf2532b87353e283139b5d790622b1fd5d) this default behaviour, and will activate on a per-query basis in case of need.
11 |
12 | ## Consequences
13 | Data needs to intentionally refreshed either by developers or by users via an appropriate Refresh button.
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2016",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "incremental": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "sourceMap": true,
21 | "allowJs": true,
22 | "typeRoots": ["./node_modules/@types", "src/types"]
23 | },
24 | "include": [
25 | "next-env.d.ts",
26 | "**/*.ts",
27 | "**/*.tsx"
28 | ],
29 | "exclude": [
30 | "node_modules"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/resources/files/sacct/slurmdbd.conf.erb:
--------------------------------------------------------------------------------
1 | ArchiveEvents=yes
2 | ArchiveJobs=yes
3 | ArchiveResvs=yes
4 | ArchiveSteps=no
5 | ArchiveSuspend=no
6 | ArchiveTXN=no
7 | ArchiveUsage=no
8 | AuthType=auth/munge
9 | DbdHost=<%= @slurm_dbd_host %>
10 | DbdPort=6819
11 | DebugLevel=info
12 | PurgeEventAfter=1month
13 | PurgeJobAfter=12month
14 | PurgeResvAfter=1month
15 | PurgeStepAfter=1month
16 | PurgeSuspendAfter=1month
17 | PurgeTXNAfter=12month
18 | PurgeUsageAfter=24month
19 | SlurmUser=slurm
20 | LogFile=/var/log/slurmdbd.log
21 | PidFile=/var/run/slurmdbd.pid
22 | StorageType=accounting_storage/mysql
23 | StorageUser=<%= @slurm_db_user %>
24 | StoragePass=<%= @slurm_db_password %>
25 | StorageHost=<%= node['slurm_accounting']['rds_endpoint'] %>
26 | StoragePort=<%= node['slurm_accounting']['rds_port'] %>
27 |
--------------------------------------------------------------------------------
/decisions/20230125-pcui-versioning-strategy.md:
--------------------------------------------------------------------------------
1 | # PCUI Versioning strategy
2 |
3 | - Status: superseded by [20230222-make-revision-required](20230222-make-revision-required.md)
4 | - Deciders: Nuraghe Team
5 | - Tags: versioning, pcui
6 |
7 | ## Context
8 | We want to avoid confusion for customers using PC UI and Parallel Cluster.
9 | So we need to keep PC UI and PC versioning schemas separated.
10 | While PC keeps their semver-like schema, PC UI switches do a year.month[.revision] schema.
11 |
12 | ## Decision
13 | Using the format YYYY.MM[.REVISION] for PC UI versions, where
14 |
15 | - YYYY is the full year in which PC UI gets released
16 | - MM is the two digit number for the month in which PC UI gets released
17 | - REVISION is an optional value to allow separation of patches between the same major version
18 |
19 |
--------------------------------------------------------------------------------
/decisions/README.md:
--------------------------------------------------------------------------------
1 | # Architecture Decision Records
2 |
3 | ## Development
4 |
5 | If not already done, install Log4brains:
6 |
7 | ```bash
8 | npm install -g log4brains
9 | ```
10 |
11 | To preview the knowledge base locally, run:
12 |
13 | ```bash
14 | log4brains preview
15 | ```
16 |
17 | In preview mode, the Hot Reload feature is enabled: any change you make to a markdown file is applied live in the UI.
18 |
19 | To create a new ADR interactively, run:
20 |
21 | ```bash
22 | log4brains adr new
23 | ```
24 |
25 | ## More information
26 |
27 | - [Log4brains documentation](https://github.com/thomvaill/log4brains/tree/master#readme)
28 | - [What is an ADR and why should you use them](https://github.com/thomvaill/log4brains/tree/master#-what-is-an-adr-and-why-should-you-use-them)
29 | - [ADR GitHub organization](https://adr.github.io/)
30 |
--------------------------------------------------------------------------------
/frontend/scripts/git-secrets-command.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # The following script checks if git-secrets is installed on a local machine.
4 | # If not, it prints an error message pointing to the official awslabs git-secrets repository.
5 | # If yes, it executes the 'git-secrets' command with the argument(s) passed to the script.
6 | #
7 | # Usage:
8 | # get-secrets-command.sh [COMMAND]
9 | #
10 | # Examples:
11 | # $ get-secrets-command.sh '--register-aws > /dev/null'
12 | # $ get-secrets-command.sh '--pre_commit_hook -- "$@"'
13 | #
14 |
15 | if [ -z "${CI}" ] || [ "${CI}" != true ]; then
16 | if ! command -v git-secrets >/dev/null 2>&1; then
17 | echo "git-secrets is not installed. Please visit https://github.com/awslabs/git-secrets#installing-git-secrets"
18 | exit 1
19 | fi
20 |
21 | _command="git-secrets $@"
22 | eval "$_command"
23 | fi
24 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/Costs/index.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import {useParams} from 'react-router-dom'
13 | import {CostData} from './CostData'
14 |
15 | export function Costs() {
16 | const {clusterName} = useParams()
17 |
18 | return
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/types/users.tsx:
--------------------------------------------------------------------------------
1 | export type UserAttributes = {
2 | email: string
3 | phone_number: string
4 | sub: string
5 | }
6 |
7 | export type UserGroup = {
8 | GroupName: string
9 | Description: string
10 | UserPoolId: string
11 | Precedence: number
12 | CreationDate: Date
13 | LastModifiedDate: Date
14 | }
15 |
16 | export enum UserStatus {
17 | Confirmed = 'CONFIRMED',
18 | Unconfirmed = 'UNCONFIRMED',
19 | ExternalProvider = 'EXTERNAL_PROVIDER',
20 | Archived = 'ARCHIVED',
21 | Unknown = 'UNKNOWN',
22 | ResetRequired = 'RESET_REQUIRED',
23 | ForceChangePassword = 'FORCE_CHANGE_PASSWORD',
24 | }
25 |
26 | export type User = {
27 | Attributes: UserAttributes
28 | Groups: UserGroup[]
29 | Username: string
30 | UserStatus: UserStatus
31 | Enabled: boolean
32 | UserCreateDate: Date
33 | UserLastModifiedDate: Date
34 | }
35 |
--------------------------------------------------------------------------------
/decisions/20221129-adopt-releaselabels-to-automate-changelog-generation.md:
--------------------------------------------------------------------------------
1 | # Adopt release:*labels to automate Changelog generation
2 |
3 | - Status: accepted
4 | - Deciders: Marco Basile
5 | - Tags: release changelog labels
6 |
7 | ## Context
8 | In the superseded ADR, it had been decided to automatically generate the changelog with a simple release-include/exclude labeling system. In order to generate a more useful changelog for our users, we committed to improve on this mechanism
9 |
10 | ## Decision
11 | We are adopting the following labels for our PRs:
12 |
13 | - release:breaking-change
14 | - release:bugfix
15 | - release:improvement
16 | - release:feature
17 | - release:deprecated
18 |
19 | ## Links
20 | - Supersedes [20221107-adopt-github-labels-to-allow-automating-release-changelog-creation](20221107-adopt-github-labels-to-allow-automating-release-changelog-creation.md)
21 |
--------------------------------------------------------------------------------
/frontend/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next'
2 | import {initReactI18next} from 'react-i18next'
3 | import enStrings from '../../locales/en/strings.json'
4 |
5 | const resources = {
6 | en: {
7 | translation: enStrings,
8 | },
9 | }
10 |
11 | i18n.use(initReactI18next).init({
12 | resources,
13 | lng: 'en', // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
14 | // you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
15 | // if you're using a language detector, do not define the lng option
16 | interpolation: {
17 | escapeValue: false, // react already safes from xss
18 | },
19 | react: {
20 | transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'code', 'p'],
21 | },
22 | })
23 |
24 | export default i18n
25 |
--------------------------------------------------------------------------------
/api/exception/exceptions.py:
--------------------------------------------------------------------------------
1 | from werkzeug.exceptions import Forbidden
2 |
3 |
4 | class CSRFError(Forbidden):
5 | """Raise if the client sends invalid CSRF data with the request.
6 | Generates a 403 Forbidden response with the failure reason by default.
7 | Customize the response by registering a handler with
8 | :meth:`flask.Flask.errorhandler`.
9 | """
10 |
11 | description = "CSRF validation failed."
12 |
13 | def __init__(self, description):
14 | self.description = description
15 |
16 | class RefreshTokenError(Exception):
17 | ERROR_FMT = 'Refresh token error: {description}'
18 | description = 'Refresh token flow failed'
19 |
20 | def __init__(self, description=None):
21 | if description:
22 | self.description = self.ERROR_FMT.format(description=description)
23 |
24 | def __str__(self):
25 | return self.description
--------------------------------------------------------------------------------
/frontend/src/components/InfoLink.tsx:
--------------------------------------------------------------------------------
1 | import {Link} from '@cloudscape-design/components'
2 | import {ReactElement, useCallback} from 'react'
3 | import {useTranslation} from 'react-i18next'
4 | import {useHelpPanel} from './help-panel/HelpPanel'
5 |
6 | type InfoLinkProps = {
7 | helpPanel: ReactElement
8 | ariaLabel?: string
9 | }
10 |
11 | function InfoLink({ariaLabel, helpPanel}: InfoLinkProps) {
12 | const {updateHelpPanel} = useHelpPanel()
13 | const {t} = useTranslation()
14 |
15 | const setHelpPanel = useCallback(() => {
16 | updateHelpPanel({element: helpPanel, open: true})
17 | }, [updateHelpPanel, helpPanel])
18 |
19 | return (
20 |
25 | {t('infoLink.label')}
26 |
27 | )
28 | }
29 |
30 | export default InfoLink
31 |
--------------------------------------------------------------------------------
/decisions/20220708-use-michael-nygard-format-for-adr.md:
--------------------------------------------------------------------------------
1 | # Use Michael Nygard format for ADR
2 |
3 | - Status: accepted
4 | - Tags: dev-tools, doc
5 |
6 | ## Context
7 | Adopting a format for ADR which is too verbose might prevent people from writing them, as they might consider the activity too time consuming.
8 |
9 | ## Decision
10 | We decided to adopt the format proposed by Michael Nygard, as it is brief enough to be written rapidly and conveys enough information for making the adoption of ADR valuable.
11 |
12 | ## Consequences
13 | - People might be more keen to write ADRs as the format is simpler
14 | - We might risk to loose the driver for some decisions and the options evaluated
15 |
16 | ## Links
17 | - [ADR template by Michael Nygard in Markdown](https://github.com/joelparkerhenderson/architecture-decision-record/blob/main/templates/decision-record-template-by-michael-nygard/index.md)
--------------------------------------------------------------------------------
/decisions/20221027-support-multiple-versions-of-pc-api.md:
--------------------------------------------------------------------------------
1 | # Support multiple versions of PC API
2 |
3 | - Status: accepted
4 | - Date: 2022-10-27
5 | - Tags: feature-flags, pc-api
6 |
7 | ## Context
8 | Both the UI and the generated YAML need to change according to version of the PC api the customer is using. There is no central point where all the features we support are listed and it is getting hard to keep track of all the moving parts whenever a new version of the PC api is released
9 |
10 | ## Decision
11 | Use feature flags to toggle on and off features. Generate the list of active flags based on the current version of the PC api.
12 |
13 | ## Consequences
14 | - Upside: We have a single point where every feature is listed, and a single map to manage which feature maps to which version.
15 | - Downside: We still have to maintain different UIs and it may get very hard to test every combination of features in the long run.
--------------------------------------------------------------------------------
/PROJECT_GUIDELINES.md:
--------------------------------------------------------------------------------
1 | # Project Guidelines
2 |
3 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
4 |
5 | - You have added tests, even if just the happy path, when adding or refactoring code
6 | - You used types in your TypeScript code so that we can leverage static analysis to maintain quality
7 | - You have internationalized any user-facing text or copy, by using [`react-i18next`](https://react.i18next.com/) library ([useTranslation hook](https://react.i18next.com/latest/usetranslation-hook) and/or [Trans component](https://react.i18next.com/latest/trans-component)) so that we can translate to other languages in the future, see an example [here](https://github.com/aws/aws-parallelcluster-ui/commit/a6f1e2aa46b245b5bf7500a04b83195477a5cfa5)
8 | - You have followed the PR Quality Checklist available in our [Pull Request Template](.github/pull_request_template.md)
9 |
10 |
--------------------------------------------------------------------------------
/decisions/template.md:
--------------------------------------------------------------------------------
1 | # [short title of solved problem and solution]
2 |
3 | - Status: [draft | proposed | rejected | accepted | deprecated | … | superseded by [xxx](yyyymmdd-xxx.md)]
4 | - Deciders: [list everyone involved in the decision]
5 | - Date: [YYYY-MM-DD when the decision was last updated]
6 | - Tags: [space and/or comma separated list of tags]
7 |
8 | ## Context
9 | What is the issue that we're seeing that is motivating this decision or change?
10 |
11 | ## Decision
12 | What is the change that we're proposing and/or doing?
13 |
14 | ## Consequences
15 | What becomes easier or more difficult to do because of this change?
16 |
17 | ## Links
18 | - [Link type](link to adr)
19 | - …
20 |
--------------------------------------------------------------------------------
/decisions/20230222-make-revision-required.md:
--------------------------------------------------------------------------------
1 | # Make revision required
2 |
3 | - Status: accepted
4 | - Deciders: Nuraghe Team
5 | - Tags: versioning, pcui
6 |
7 | ## Context
8 | Currently, the versioning schema adopted takes inspiration from [CalVer](https://calver.org/) and it is composed of Year, Month and Revision number.
9 |
10 | As of today, Revision number is optional, and it is not automatically added by the release workflow.
11 |
12 | ## Decision
13 | We are making the Revision required, thus the new schema is now YYYY.MM.REVISION
14 |
15 | ## Consequences
16 | It is now possible to automatically release patches, without manual interventions. Revision number is taken from the GitHub ref name, as implemented with [#55](https://github.com/aws/aws-parallelcluster-ui/pull/55)
17 |
18 | ## Links
19 | - [PR](https://github.com/aws/aws-parallelcluster-ui/pull/55)
20 | - Supersedes [20230125-pcui-versioning-strategy](20230125-pcui-versioning-strategy.md)
21 |
--------------------------------------------------------------------------------
/infrastructure/bucket_configuration.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
5 | # with the License. A copy of the License is located at
6 | #
7 | # http://aws.amazon.com/apache2.0/
8 | #
9 | # or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
10 | # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
11 | # limitations under the License.
12 |
13 | REGULAR_BUCKET=parallelcluster-ui-release-artifacts-us-east-1
14 | ALTERNATIVE_BUCKET=parallelcluster-ui-release-artifacts-eu-west-1
15 | REGULAR_REGION_FOR_TEMPLATES="us-east-1"
16 | ALTERNATIVE_REGION_FOR_TEMPLATES="eu-west-1"
17 |
18 | BUCKETS=("$REGULAR_BUCKET" "$ALTERNATIVE_BUCKET")
19 | REGIONS=("$REGULAR_REGION_FOR_TEMPLATES" "$ALTERNATIVE_REGION_FOR_TEMPLATES")
--------------------------------------------------------------------------------
/decisions/20220708-use-log4brains-to-manage-the-adrs.md:
--------------------------------------------------------------------------------
1 | # Use Log4brains to manage the ADRs
2 |
3 | - Status: accepted
4 | - Date: 2022-07-07
5 | - Tags: dev-tools, doc
6 |
7 | ## Context and Problem Statement
8 |
9 | We want to record architectural decisions made in this project.
10 | Which tool(s) should we use to manage these records?
11 |
12 | ## Considered Options
13 |
14 | - [Log4brains](https://github.com/thomvaill/log4brains): architecture knowledge base (command-line + static site generator)
15 | - [ADR Tools](https://github.com/npryce/adr-tools): command-line to create ADRs
16 | - [ADR Tools Python](https://bitbucket.org/tinkerer_/adr-tools-python/src/master/): command-line to create ADRs
17 | - [adr-viewer](https://github.com/mrwilson/adr-viewer): static site generator
18 | - [adr-log](https://adr.github.io/adr-log/): command-line to create a TOC of ADRs
19 |
20 | ## Decision Outcome
21 |
22 | Chosen option: "Log4brains", because it includes the features of all the other tools, and even more.
23 |
--------------------------------------------------------------------------------
/infrastructure/release_infrastructure.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
6 | # with the License. A copy of the License is located at
7 | #
8 | # http://aws.amazon.com/apache2.0/
9 | #
10 | # or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
11 | # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
15 |
16 | source ${SCRIPT_DIR}/common.sh
17 | source ${SCRIPT_DIR}/bucket_configuration.sh
18 | trap 'error' ERR
19 |
20 | echo "Uploading the main templates"
21 | "${SCRIPT_DIR}"/upload.sh "$SCRIPT_DIR"
22 | echo "Uploading accounting template"
23 | "${SCRIPT_DIR}"/slurm-accounting/upload.sh "$SCRIPT_DIR"
--------------------------------------------------------------------------------
/frontend/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | const reportWebVitals = (onPerfEntry: any) => {
12 | if (onPerfEntry && onPerfEntry instanceof Function) {
13 | import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
14 | getCLS(onPerfEntry)
15 | getFID(onPerfEntry)
16 | getFCP(onPerfEntry)
17 | getLCP(onPerfEntry)
18 | getTTFB(onPerfEntry)
19 | })
20 | }
21 | }
22 |
23 | export default reportWebVitals
24 |
--------------------------------------------------------------------------------
/frontend/src/navigation/useLocationChangeLog.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import {useEffect} from 'react'
12 | import {useLocation} from 'react-router-dom'
13 | import {useLogger} from '../logger/LoggerProvider'
14 |
15 | export function useLocationChangeLog() {
16 | const logger = useLogger()
17 | const location = useLocation()
18 |
19 | useEffect(() => {
20 | logger.info('Location changed', {
21 | to: location.pathname,
22 | })
23 | }, [location, logger])
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/Costs/costs.types.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | export type CostMonitoringStatus = boolean
13 |
14 | export interface CostMonitoringStatusResponse {
15 | active: CostMonitoringStatus
16 | }
17 |
18 | export interface CostMonitoringData {
19 | period: {
20 | start: string
21 | end: string
22 | }
23 | amount: number
24 | unit: string
25 | }
26 |
27 | export interface CostMonitoringDataResponse {
28 | costs: CostMonitoringData[]
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/Costs/valueFormatter.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | export function toShortDollarAmount(value: number) {
13 | const absValue = Math.abs(value)
14 |
15 | if (absValue >= 1e9) {
16 | return (value / 1e9).toFixed(1) + 'G'
17 | }
18 |
19 | if (absValue >= 1e6) {
20 | return (value / 1e6).toFixed(1) + 'M'
21 | }
22 |
23 | if (absValue >= 1e3) {
24 | return (value / 1e3).toFixed(1) + 'K'
25 | }
26 |
27 | return value.toFixed(2)
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/public/img/ec2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/components/__tests__/Status.test.tsx:
--------------------------------------------------------------------------------
1 | import {formatStatus} from '../Status'
2 |
3 | describe('Given a resource status', () => {
4 | describe('when the status has a dash', () => {
5 | it('should be removed', () => {
6 | expect(formatStatus('shutting-down')).toBe('Shutting down')
7 | })
8 | })
9 |
10 | describe('when the status has an underscore', () => {
11 | it('should be removed', () => {
12 | expect(formatStatus('shutting_down')).toBe('Shutting down')
13 | })
14 | })
15 |
16 | describe('when the status is lowercase', () => {
17 | it('should be capitalized', () => {
18 | expect(formatStatus('created')).toBe('Created')
19 | })
20 | })
21 |
22 | describe('when the status is uppercase', () => {
23 | it('should be capitalized', () => {
24 | expect(formatStatus('CREATE_COMPLETE')).toBe('Create complete')
25 | })
26 | })
27 |
28 | describe('when given an undefined string', () => {
29 | it('should return an empty string', () => {
30 | expect(formatStatus()).toBe('')
31 | })
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/frontend/src/navigation/useWizardSectionChangeLog.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import {useEffect} from 'react'
12 | import {useLogger} from '../logger/LoggerProvider'
13 | import {useState} from '../store'
14 |
15 | export function useWizardSectionChangeLog() {
16 | const page = useState(['app', 'wizard', 'page'])
17 | const logger = useLogger()
18 |
19 | useEffect(() => {
20 | if (!page) return
21 |
22 | logger.info('Wizard section changed:', {
23 | to: page,
24 | })
25 | }, [logger, page])
26 | }
27 |
--------------------------------------------------------------------------------
/decisions/20230324-avoid-overriding-customer-ssm-sessionmanagerrunshell-removing-ssmsessionprofile-cfnyaml.md:
--------------------------------------------------------------------------------
1 | # Avoid overriding customer SSM-SessionManagerRunShell removing SSMSessionProfile-cfn.yaml
2 |
3 | - Status: accepted
4 | - Deciders: Nuraghe team
5 | - Date: 2023-03-24
6 | - Tags: SessionManager, SSM, Shell
7 |
8 | ## Context
9 | Customer SSM-SessionManagerRunShell gets overridden by PCUI Cloudformation template, possibly causing loss of
10 | customer's SSM shell preferences for the region in cui PCUI is deployed.
11 |
12 | ## Decision
13 | Remove the cloudformation substack SSMSessionProfile-cfn.yaml.
14 | With this change in place, when customers use the Shell button to connect to an SSM enabled cluster headnode,
15 | they will end up being logged in as the default SSM user (which is `ssm-user`) instead of the "default" ParallelCluster user.
16 | Nothing else will change for them.
17 |
18 | ## Links
19 | - [SSMSessionProfile-cfn.yaml](https://github.com/aws/aws-parallelcluster-ui/blob/1a6260ad60cd6bf5160e9b607e2dea85af9428e7/infrastructure/SSMSessionProfile-cfn.yaml)
20 |
--------------------------------------------------------------------------------
/.github/workflows/publish-adrs.yml:
--------------------------------------------------------------------------------
1 | name: Publish ADRs
2 | on:
3 | push:
4 | branches:
5 | - main
6 | permissions:
7 | contents: write
8 | pages: write
9 |
10 | jobs:
11 | build-and-publish:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v2.3.4
16 | with:
17 | persist-credentials: false # required by JamesIves/github-pages-deploy-action
18 | fetch-depth: 0 # required by Log4brains to work correctly (needs the whole Git history)
19 | - name: Install Node
20 | uses: actions/setup-node@v1
21 | with:
22 | node-version: "14"
23 | - name: Install and Build Log4brains
24 | run: |
25 | npm install -g log4brains
26 | log4brains build --basePath /${GITHUB_REPOSITORY#*/}/log4brains
27 | - name: Deploy
28 | uses: JamesIves/github-pages-deploy-action@3.7.1
29 | with:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 | BRANCH: gh-pages
32 | FOLDER: .log4brains/out
33 | TARGET_FOLDER: log4brains
34 |
--------------------------------------------------------------------------------
/decisions/20220713-use-github-actions-for-pipelines.md:
--------------------------------------------------------------------------------
1 | # Use Github Actions for pipelines
2 |
3 | - Status: accepted
4 | - Deciders: Mattia Franchetto, Alessandro Menduni, Marco Basile
5 | - Date: 7/12/2022
6 |
7 | ## Context
8 | PCluster Manager doesn't have a pipeline to run tests of different kinds (unit, integration and so on) in a continuous integration fashion, and optionally deliver the tested changes automatically.
9 |
10 | ## Decision
11 | Pipelines will be built on top of Github Actions because it's fast, simple to configure using YAML files, immediately available and free for open source projects.
12 | A solution like AWS CodeBuild has been discarded for the moment for its complex setup, but may be used in the future.
13 |
14 | ## Consequences
15 | The pipeline will perform tests on every pull request: if we fail to configure it properly the code won't get merged until the pipeline is restored with manual actions (like restarting the job or tweak the configuration), but this is a common scenario regardless of the tool being used.
16 |
17 | ## Links
18 | - [Actions](https://docs.github.com/en/actions)
19 |
--------------------------------------------------------------------------------
/frontend/src/shared/extendCollectionsOptions.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import {UseCollectionOptions} from '@cloudscape-design/collection-hooks'
13 |
14 | const DEFAULT_USE_COLLECTION_OPTIONS = {
15 | pageSize: 20,
16 | }
17 |
18 | export function extendCollectionsOptions(
19 | options: UseCollectionOptions = {},
20 | ): UseCollectionOptions {
21 | return {
22 | ...options,
23 | pagination: {
24 | pageSize: DEFAULT_USE_COLLECTION_OPTIONS.pageSize,
25 | ...options.pagination,
26 | },
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/scripts/cognito-tools/export_cognito_users.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 |
4 | function print_help() {
5 | echo "Usage $0 --region REGION [--user-pool-id USER_POOL_ID]"
6 | }
7 |
8 | while [[ $# -gt 0 ]]
9 | do
10 | key="$1"
11 |
12 | case $key in
13 | -h)
14 | print_help >&2
15 | exit 1
16 | ;;
17 |
18 | --user-pool-id)
19 | USER_POOL_ID="$2"
20 | shift
21 | shift
22 | ;;
23 |
24 | --region)
25 | REGION=$2
26 | shift
27 | shift
28 | ;;
29 |
30 | *) # unknown option
31 | print_help >&2
32 | exit 1
33 | ;;
34 | esac
35 | done
36 |
37 |
38 | . common.sh
39 |
40 | check_region
41 | check_user_pool_id
42 |
43 | aws cognito-idp list-users --region "$REGION" --user-pool-id $USER_POOL_ID --query "Users[].{email: Attributes[?Name == 'email'].Value | [0]}" --output text | while read USERNAME
44 | do
45 | USER_GROUPS=$(aws cognito-idp admin-list-groups-for-user --username "$USERNAME" --user-pool-id "$USER_POOL_ID" --region "$REGION" --query 'Groups[*].GroupName' --output text | tr -s '\t' ',')
46 | echo "$USERNAME|$USER_GROUPS"
47 | done
48 |
--------------------------------------------------------------------------------
/e2e/test-utils/login.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import { Page } from "@playwright/test";
13 | import { ENVIRONMENT_CONFIG } from "../configs/environment";
14 | import { LOGIN_CONFIG } from "../configs/login";
15 |
16 | export async function visitAndLogin(page: Page) {
17 | await page.goto(ENVIRONMENT_CONFIG.URL);
18 | await page.getByRole('textbox', { name: 'name@host.com' }).fill(LOGIN_CONFIG.username);
19 | await page.getByRole('textbox', { name: 'Password' }).fill(LOGIN_CONFIG.password);
20 | await page.getByRole('button', { name: 'submit' }).click();
21 | }
--------------------------------------------------------------------------------
/frontend/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 | import {Html, Head, Main, NextScript} from 'next/document'
11 |
12 | export default function Document() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/decisions/20230310-scope-down-e2e-tests.md:
--------------------------------------------------------------------------------
1 | # Scope down e2e tests
2 |
3 | - Status: accepted
4 | - Deciders: Nuraghe team
5 | - Date: 2023-03-10
6 | - Tags: e2e, tests
7 |
8 | ## Context
9 | Typically, e2e tests consist in simulating user interactions and making sure all the components of the system under test behave as expected. This involves reducing the number of mocks to the minimum (e.g. - only expensive third party APIs).
10 |
11 | In the case of PCUI, this would involve a lot of infrastructure being moved (e.g. creating a cluster) and it would add many dependencies to every test (ParallelCluster, the underlying AWS infrastructure, CloudFormation and so on). It would make the tests very costly, with little to no actual improvement in the confidence that PCUI is working as expected.
12 |
13 | Last but not least, PC provides a powerful dry-run feature that allows PCUI to quickly verify most of its features.
14 |
15 | ## Decision
16 | In PCUI, e2e tests will avoid involving costly tests such as an actual cluster creation
17 |
18 | ## Consequences
19 | - Cheaper, much quicker tests
20 | - More or less the same level of confidence in the stability of PCUI
--------------------------------------------------------------------------------
/frontend/src/components/InputErrors.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | import {SpaceBetween} from '@cloudscape-design/components'
12 |
13 | // UI Elements
14 | export default function InputErrors({errors}: any) {
15 | return (
16 | errors && (
17 |
26 | )
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/src/components/date/DateView.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | import React from 'react'
12 | import AbsoluteTimestamp, {TimeZone} from './AbsoluteTimestamp'
13 |
14 | type DateViewProps = {
15 | date: string
16 | locales?: string | string[]
17 | timeZone?: TimeZone
18 | }
19 |
20 | export default function DateView({
21 | date,
22 | locales = 'en-Us',
23 | timeZone = TimeZone.Local,
24 | }: DateViewProps) {
25 | const timestamp = Date.parse(date)
26 | return (
27 |
28 | {timestamp}
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Configure/__tests__/itemToOption.test.ts:
--------------------------------------------------------------------------------
1 | import {describe} from '@jest/globals'
2 | import {itemToOption} from '../Cluster'
3 |
4 | describe('Given a selection item', () => {
5 | describe('when the item is a string', () => {
6 | it('should return a valid SelectProps.Option', () => {
7 | const item = 'option-value'
8 | const expectedOption = {label: 'option-value', value: 'option-value'}
9 |
10 | const option = itemToOption(item)
11 |
12 | expect(option).toEqual(expectedOption)
13 | })
14 | })
15 |
16 | describe('when the item is a tuple of 2 strings', () => {
17 | it('should return a valid SelectProps.Option', () => {
18 | const item = ['option-value', 'option-label'] as [string, string]
19 | const expectedOption = {label: 'option-label', value: 'option-value'}
20 |
21 | const option = itemToOption(item)
22 |
23 | expect(option).toEqual(expectedOption)
24 | })
25 | })
26 |
27 | describe('when the item is null', () => {
28 | const item = null
29 |
30 | it('should return null', () => {
31 | const option = itemToOption(item)
32 | expect(option).toBeNull()
33 | })
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/api/security/csrf/__init__.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, Blueprint, current_app, jsonify, request
2 |
3 | from api.security.csrf.constants import CSRF_SECRET_KEY, SALT, CSRF_COOKIE_NAME
4 | from api.security.csrf.csrf import generate_csrf_token, set_csrf_cookie
5 |
6 | from api.security.fingerprint import IFingerprintGenerator
7 |
8 | csrf_blueprint = Blueprint('csrf', __name__)
9 |
10 |
11 | @csrf_blueprint.get('/csrf')
12 | def get_and_set_csrf_token():
13 | csrf_secret_key = current_app.config.get(CSRF_SECRET_KEY)
14 | csrf_token = generate_csrf_token(csrf_secret_key, SALT)
15 | resp = jsonify(csrf_token=csrf_token)
16 | set_csrf_cookie(resp, csrf_token)
17 | return resp
18 |
19 |
20 | class CSRF(object):
21 |
22 | def __init__(self, app: Flask = None, fingerprint_generator=None):
23 | if app is not None and fingerprint_generator is not None:
24 | self.init_app(app, fingerprint_generator)
25 |
26 | def init_app(self, app, fingerprint_generator: IFingerprintGenerator):
27 | csrf_secret_key = fingerprint_generator.fingerprint()
28 |
29 | app.config['CSRF_SECRET_KEY'] = csrf_secret_key
30 | app.register_blueprint(csrf_blueprint)
31 |
--------------------------------------------------------------------------------
/api/tests/test_push_log.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def trim_log(caplog):
4 | return caplog.text.replace('\n', '')
5 |
6 |
7 | def test_push_log_controller_with_valid_json_no_extra(client, caplog, mock_disable_auth, mock_csrf_needed):
8 | request_body = { 'logs': [{'message': 'sample-message', 'level': 'info'}] }
9 | expected_log = "INFO pcluster-manager:logger.py:24 {'message': 'sample-message'}"
10 |
11 | caplog.clear()
12 | response = client.post('/logs', json=request_body)
13 |
14 | assert response.status_code == 200
15 | assert expected_log in trim_log(caplog)
16 |
17 |
18 | def test_push_log_controller_with_valid_json_with_extra(client, caplog, mock_disable_auth, mock_csrf_needed):
19 | request_body = { 'logs': [{'message': 'sample-message', 'level': 'error',
20 | 'extra': {'extra_1': 'value_1', 'extra_2': 'value_2'}}]}
21 | expected_log = "ERROR pcluster-manager:logger.py:30 {'extra_1': 'value_1', 'extra_2': 'value_2', 'message': 'sample-message'}"
22 |
23 | caplog.clear()
24 | response = client.post('/logs', json=request_body)
25 |
26 | assert response.status_code == 200
27 | assert expected_log in trim_log(caplog)
28 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/Costs/composeTimeRange.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | function clearTimeData(date: Date) {
13 | date.setHours(0)
14 | date.setMinutes(0)
15 | date.setSeconds(0)
16 | date.setMilliseconds(0)
17 | }
18 |
19 | export function composeTimeRange(today = new Date()) {
20 | const twelveMonthsAgo = new Date(today)
21 |
22 | clearTimeData(today)
23 | clearTimeData(twelveMonthsAgo)
24 |
25 | const currentYear = today.getFullYear()
26 |
27 | twelveMonthsAgo.setFullYear(currentYear - 1)
28 |
29 | return {
30 | fromDate: twelveMonthsAgo.toISOString(),
31 | toDate: today.toISOString(),
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/src/components/EmptyState.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | // UI Elements
13 | import {Box} from '@cloudscape-design/components'
14 |
15 | interface Props {
16 | title: string
17 | subtitle: string
18 | action?: React.ReactElement
19 | }
20 |
21 | export default function EmptyState({title, subtitle, action}: Props) {
22 | return (
23 |
24 |
25 | {title}
26 |
27 |
28 | {subtitle}
29 |
30 | {action}
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/public/img/error_pages_illustration.svg:
--------------------------------------------------------------------------------
1 |
26 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Logs/withNodeType.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import {Instance, NodeType} from '../../types/instances'
13 | import {LogStreamView} from '../../types/logs'
14 |
15 | function toNodeType(
16 | headNode: Instance | null,
17 | instanceId: string,
18 | ): NodeType | null {
19 | if (!headNode) return null
20 |
21 | return headNode.instanceId === instanceId
22 | ? NodeType.HeadNode
23 | : NodeType.ComputeNode
24 | }
25 |
26 | export function withNodeType(
27 | headNode: Instance | null,
28 | logStream: LogStreamView,
29 | ) {
30 | return {
31 | ...logStream,
32 | nodeType: toNodeType(headNode, logStream.instanceId),
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/e2e/specs/noMatch.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import { expect, test } from '@playwright/test';
12 | import { visitAndLogin } from '../test-utils/login';
13 | import { ENVIRONMENT_CONFIG } from "../configs/environment";
14 |
15 | test.describe('Given an endpoint where AWS ParallelCluster UI is deployed', () => {
16 | test.describe('when a user is logged in, and navigates to an unmatched route', () => {
17 | test('an error page should be displayed', async ({ page }) => {
18 | await visitAndLogin(page)
19 | await page.goto(`${ENVIRONMENT_CONFIG.URL}/noMatch`)
20 | await expect(page.getByRole('heading', { name: 'Page not found' })).toBeVisible()
21 | });
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/e2e/test-utils/clusters.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import { Page } from "@playwright/test";
13 |
14 | const DEFAULT_CLUSTER_TO_SELECT = 'DO-NOT-DELETE'
15 |
16 | export async function selectCluster(page: Page, clusterName: string = DEFAULT_CLUSTER_TO_SELECT) {
17 | await page.getByRole('row', { name: clusterName })
18 | .getByRole('radio')
19 | .click();
20 | }
21 |
22 | export enum ClusterAction {
23 | VIEW_LOGS = 'View logs'
24 | }
25 |
26 | export async function selectClusterAction(page: Page, action: ClusterAction) {
27 | await page.getByRole('button', { name: 'Actions', exact: true }).click();
28 | await page.getByRole('menuitem', { name: action, exact: true }).click();
29 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | AWS ParallelCluster UI
2 | ================================
3 | This project is a front-end for [AWS ParallelCluster](https://github.com/aws/aws-parallelcluster)
4 |
5 | Quickly and easily create HPC cluster in AWS using AWS ParallelCluster UI. This UI uses the AWS ParallelCluster 3.x API to Create, Update and Delete Clusters as well as access, view logs, and build Amazon Machine Images (AMI's).
6 |
7 | ## Install
8 | See [Official documentation](https://docs.aws.amazon.com/parallelcluster/latest/ug/install-pcui-v3.html) to install ParallelCluster UI.
9 | ## Development
10 |
11 | See [Development guide](DEVELOPMENT.md) to setup a local environment.
12 |
13 | ## Security
14 |
15 | See [Security Issue Notifications](CONTRIBUTING.md#security-issue-notifications) for more information.
16 |
17 | ## Contributing
18 |
19 | Please refer to our [Contributing Guidelines](CONTRIBUTING.md) before reporting bugs or feature requests.
20 |
21 | Please refer to our [Project Guidelines](PROJECT_GUIDELINES.md) before diving into the code.
22 |
23 | ## License
24 |
25 | This project is licensed under the Apache-2.0 License.
26 |
27 | [](https://aws.github.io/aws-parallelcluster-ui/log4brains/)
28 |
--------------------------------------------------------------------------------
/frontend/src/app-config/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import {AxiosInstance} from 'axios'
12 | import {AppConfig} from './types'
13 |
14 | interface RawAppConfig {
15 | auth_url: string
16 | client_id: string
17 | scopes: string
18 | redirect_uri: string
19 | }
20 |
21 | function mapAppConfig(data: RawAppConfig): AppConfig {
22 | return {
23 | authUrl: data.auth_url,
24 | clientId: data.client_id,
25 | redirectUri: data.redirect_uri,
26 | scopes: data.scopes,
27 | }
28 | }
29 |
30 | export async function getAppConfig(
31 | axiosInstance: AxiosInstance,
32 | ): Promise {
33 | const {data} = await axiosInstance.get('manager/get_app_config')
34 | return data ? mapAppConfig(data) : {}
35 | }
36 |
--------------------------------------------------------------------------------
/infrastructure/slurm-accounting/upload.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
6 | # with the License. A copy of the License is located at
7 | #
8 | # http://aws.amazon.com/apache2.0/
9 | #
10 | # or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
11 | # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | source $1/common.sh
15 | source $1/bucket_configuration.sh
16 | trap 'error' ERR
17 |
18 | ACCOUNTING_SCRIPT_DIR="$1/slurm-accounting"
19 |
20 | if [ ! -d "$ACCOUNTING_SCRIPT_DIR" ] || [ ! -r "$ACCOUNTING_SCRIPT_DIR" ];
21 | then
22 | echo "ACCOUNTING_SCRIPT_DIR=$ACCOUNTING_SCRIPT_DIR must be a readable directory"
23 | exit 1;
24 | fi
25 |
26 | FILES=(accounting-cluster-template.yaml)
27 |
28 | for INDEX in "${!BUCKETS[@]}"
29 | do
30 | echo Uploading to: "${BUCKETS[INDEX]}"
31 | for FILE in "${FILES[@]}"
32 | do
33 | aws s3 cp "${ACCOUNTING_SCRIPT_DIR}/${FILE}" "s3://${BUCKETS[INDEX]}/slurm-accounting/${FILE}"
34 | done
35 | done
36 |
--------------------------------------------------------------------------------
/e2e/specs/wizard.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import { test } from '@playwright/test';
12 | import { visitAndLogin } from '../test-utils/login';
13 | import { fillWizard } from '../test-utils/wizard';
14 |
15 | test.describe('Given an endpoint where AWS ParallelCluster UI is deployed', () => {
16 | test('a user should be able to login, navigate till the end of the cluster creation wizard, and perform a dry-run successfully', async ({ page }) => {
17 | await visitAndLogin(page)
18 |
19 | await page.getByRole('button', { name: 'Create cluster' }).first().click();
20 | await page.getByRole('menuitem', { name: 'Step by step' }).click();
21 |
22 | await fillWizard(page, {vpc: /vpc-.*/, region: 'eu-west-1'})
23 | });
24 | })
25 |
--------------------------------------------------------------------------------
/frontend/src/components/Loading.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | import Spinner from '@cloudscape-design/components/spinner'
12 | import {useTranslation} from 'react-i18next'
13 |
14 | export default function Loading(props: any) {
15 | const {t} = useTranslation()
16 | const defaultText = t('components.Loading.text')
17 | return (
18 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/api/logging/logger.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from sys import exc_info
3 |
4 | class DefaultLogger(object):
5 | def __init__(self, is_running_local):
6 | self.logger = logging.getLogger("pcluster-manager")
7 | if is_running_local:
8 | handler = logging.StreamHandler()
9 | handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
10 | self.logger.addHandler(handler)
11 | self.logger.setLevel(logging.DEBUG)
12 | else:
13 | self.logger.setLevel(logging.INFO)
14 |
15 | def _log_output(self, msg, extra):
16 | _extra = {} if extra is None else extra
17 | _extra["message"] = msg
18 | return _extra
19 |
20 | def debug(self, msg, extra=None):
21 | self.logger.debug(self._log_output(msg, extra))
22 |
23 | def info(self, msg, extra=None):
24 | self.logger.info(self._log_output(msg, extra))
25 |
26 | def warning(self, msg, extra=None):
27 | self.logger.warning(self._log_output(msg, extra))
28 |
29 | def error(self, msg, extra=None):
30 | self.logger.error(self._log_output(msg, extra), exc_info=self.__is_exception_caught())
31 |
32 | def __is_exception_caught(self):
33 | return exc_info() != (None, None, None)
34 |
35 | def critical(self, msg, extra=None):
36 | self.logger.critical(self._log_output(msg, extra), exc_info=True)
37 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Configure/Queues/__tests__/multiAZ.test.tsx:
--------------------------------------------------------------------------------
1 | import {areMultiAZSelected} from '../Queues'
2 |
3 | describe('Given a list of subnets', () => {
4 | describe('when zero subnets are selected', () => {
5 | it('should keep EFA and Placement groups enabled', () => {
6 | const {multiAZ, canUseEFA, canUsePlacementGroup} = areMultiAZSelected([])
7 | expect(multiAZ).toBe(false)
8 | expect(canUseEFA).toBe(true)
9 | expect(canUsePlacementGroup).toBe(true)
10 | })
11 | })
12 |
13 | describe('when a single subnet is selected', () => {
14 | it('should keep EFA and Placement groups enabled', () => {
15 | const {multiAZ, canUseEFA, canUsePlacementGroup} = areMultiAZSelected([
16 | 'subnet-a',
17 | ])
18 | expect(multiAZ).toBe(false)
19 | expect(canUseEFA).toBe(true)
20 | expect(canUsePlacementGroup).toBe(true)
21 | })
22 | })
23 |
24 | describe('when more than one subnet is selected', () => {
25 | it('should keep EFA and Placement groups disabled', () => {
26 | const {multiAZ, canUseEFA, canUsePlacementGroup} = areMultiAZSelected([
27 | 'subnet-a',
28 | 'subnet-b',
29 | ])
30 | expect(multiAZ).toBe(true)
31 | expect(canUseEFA).toBe(false)
32 | expect(canUsePlacementGroup).toBe(false)
33 | })
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/decisions/20221206-frontend-log-instrumentation.md:
--------------------------------------------------------------------------------
1 | # Frontend log instrumentation
2 |
3 | - Status: accepted
4 | - Deciders: Nuraghe team
5 | - Tags: logs, frontend
6 |
7 | ## Context
8 | Right now we only have a remote logger implementation that pushes logs to the backend.
9 | We want to also log stacktraces but without source-maps it would be hard for those stacktraces
10 | to be actually useful/readable.
11 |
12 | ## Decision
13 | Since our logger implementation provides an `extra` parameter for additional info to add to the log entry, we will:
14 |
15 | - make an effort to log clear and effective messages that are actually useful when read by a developer/technician
16 | - continuously look for ways to improve our existing logging calls when needed
17 | - optionally leverage the `extra` parameter that to provide contextual information about where the log entry is being logged from
18 |
19 | ## Consequences
20 | We get the ability to have effective logs available for inspection in case it's needed.
21 |
22 | ## Useful Links
23 | - [Owasp Logging](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html)
24 | - [9 best practices for logging](https://www.atatus.com/blog/9-best-practice-for-application-logging-that-you-must-know/#9-logging-and-monitoring-best-practices)
25 | - [13 best practices for logging](https://www.dataset.com/blog/the-10-commandments-of-logging/)
--------------------------------------------------------------------------------
/api/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | import api.security
4 | from app import run
5 | import app as _app
6 |
7 | @pytest.fixture(autouse=True)
8 | def mock_cognito_variables(mocker):
9 | mocker.patch.object(_app, 'CLIENT_ID', 'client-id')
10 | mocker.patch.object(_app, 'USER_POOL_ID', 'user-pool')
11 | mocker.patch.object(_app, 'CLIENT_SECRET', 'client-secret')
12 |
13 | @pytest.fixture()
14 | def app():
15 | app = run()
16 | app.config.update({
17 | "TESTING": True,
18 | })
19 |
20 | yield app
21 |
22 | @pytest.fixture()
23 | def client(app):
24 | return app.test_client()
25 |
26 |
27 | @pytest.fixture()
28 | def runner(app):
29 | return app.test_cli_runner()
30 |
31 | @pytest.fixture()
32 | def dev_app(monkeypatch):
33 | monkeypatch.setenv("ENV", "dev")
34 |
35 | app = run()
36 | app.config.update({
37 | "TESTING": True,
38 | })
39 |
40 |
41 |
42 | yield app
43 |
44 |
45 | @pytest.fixture()
46 | def dev_client(dev_app):
47 | return dev_app.test_client()
48 |
49 |
50 | @pytest.fixture(scope='function')
51 | def mock_csrf_needed(mocker, app):
52 | mock_csrf_enabled = mocker.patch.object(api.security.csrf.csrf, 'is_csrf_enabled')
53 | mock_csrf_enabled.return_value = False
54 |
55 | @pytest.fixture
56 | def mock_disable_auth(mocker):
57 | mocker.patch.object(api.utils, 'DISABLE_AUTH', True)
--------------------------------------------------------------------------------
/api/logging/http_info.py:
--------------------------------------------------------------------------------
1 | from typing import Union
2 |
3 | from flask import Request, Response
4 |
5 |
6 | def log_request_body_and_headers(_logger, request: Request):
7 | details = __get_http_info(request)
8 | details['path'] = request.path
9 | if request.args:
10 | details['params'] = request.args
11 |
12 | if 'serverless.event' in request.environ:
13 | env = request.environ.get('serverless.event')
14 | if 'requestContext' in env and 'requestId' in env.get('requestContext'):
15 | details['apigw-request-id'] = env.get('requestContext').get('requestId')
16 |
17 | _logger.info(details)
18 |
19 |
20 | def log_response_body_and_headers(_logger, response: Response):
21 | details = __get_http_info(response)
22 | _logger.info(details)
23 |
24 |
25 | def __get_http_info(r: Union[Request,Response]) -> dict:
26 | headers = __filter_headers(r.headers)
27 | details = {'headers': headers}
28 |
29 | try:
30 | body = r.json
31 | if body:
32 | details['body'] = body
33 | except:
34 | pass
35 |
36 | return details
37 |
38 |
39 | def __filter_headers(headers: dict):
40 | """ utility function to remove sensitive information from request headers """
41 | _headers = dict(headers)
42 | _headers.pop('Cookie', None)
43 | _headers.pop('X-CSRF-Token', None)
44 | return _headers
45 |
--------------------------------------------------------------------------------
/decisions/20221027-export-pcluster-manager-logs-from-cloudwatch.md:
--------------------------------------------------------------------------------
1 | # Export PCluster Manager logs from CloudWatch
2 |
3 | - Status: accepted
4 | - Deciders: Nuraghe team
5 | - Date: 2022-10-27
6 | - Tags: cloudwatch, logging, s3
7 |
8 | ## Context
9 | Customers need a way to easily export their PCluster Manager deployment logs in order to provide the support team
10 | with the info needed to get help. Right now there is no way from the PCM UI to be guided in performing the
11 | necessary actions to download log files produced by the application.
12 | Since a complete automation and a 1-click experience are not feasible right now with what we have, we cannot provide
13 | customers with an immediate way to get logs and some manual process would still be necessary,
14 | (pre-signed URL for S3 are only possible for single objects and not collection of objects), so right now we decided to
15 | skip the automation part entirely.
16 |
17 | ## Decision
18 | Guide the users with documentation links and/or instructions on how to perform an [export to s3 action](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/S3ExportTasks.html)
19 | letting them know what is needed and how to download log archives.
20 |
21 | ## Consequences
22 | Helping customers solve their issues will become easier and more manageable,
23 | although part of the manual process may be error-prone for users less acquainted with AWS console.
24 |
--------------------------------------------------------------------------------
/frontend/src/feature-flags/useFeatureFlag.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import {useState} from '../store'
12 | import {featureFlagsProvider} from './featureFlagsProvider'
13 | import {AvailableFeature} from './types'
14 |
15 | export function useFeatureFlag(feature: AvailableFeature): boolean {
16 | const version = useState(['app', 'wizard', 'version'])
17 | const region = useState(['aws', 'region'])
18 | return isFeatureEnabled(version, region, feature)
19 | }
20 |
21 | export function isFeatureEnabled(
22 | version: string,
23 | region: string,
24 | feature: AvailableFeature,
25 | ): boolean {
26 | const features = new Set(featureFlagsProvider(version, region))
27 | const enabled = features.has(feature)
28 | if (!enabled) {
29 | console.log(`FEATURE FLAG: Feature ${feature} is disabled`)
30 | }
31 | return enabled
32 | }
33 |
--------------------------------------------------------------------------------
/e2e/specs/logs.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import { test } from '@playwright/test';
12 | import { ClusterAction, selectCluster, selectClusterAction } from '../test-utils/clusters';
13 | import { visitAndLogin } from '../test-utils/login';
14 | import { visitClusterLogsPage } from '../test-utils/logs';
15 |
16 | test.describe('environment: @demo', () => {
17 | test.describe('Given an endpoint where AWS ParallelCluster UI is deployed', () => {
18 | test.describe('when the user selects a cluster', () => {
19 | test('the user can navigate to the cluster logs page', async ({ page }) => {
20 | await visitAndLogin(page)
21 |
22 | await selectCluster(page)
23 |
24 | await selectClusterAction(page, ClusterAction.VIEW_LOGS)
25 |
26 | await visitClusterLogsPage(page)
27 | });
28 | })
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/frontend/src/http/httpLogs.ts:
--------------------------------------------------------------------------------
1 | import {AxiosError, AxiosInstance} from 'axios'
2 | import {ILogger} from '../logger/ILogger'
3 |
4 | export const enableHttpLogs = (
5 | axiosInstance: AxiosInstance,
6 | logger: ILogger,
7 | ) => {
8 | const requestInterceptor = axiosInstance.interceptors.request.use(config => {
9 | if (!isLogEndpoint(config.url)) {
10 | logger.info('HTTP request started', {
11 | url: config.url,
12 | })
13 | }
14 | return config
15 | })
16 | const responseInterceptor = axiosInstance.interceptors.response.use(
17 | response => {
18 | if (!isLogEndpoint(response.config?.url)) {
19 | logger.info('HTTP response received', {
20 | url: response.config?.url,
21 | statusCode: response.status,
22 | })
23 | }
24 | return response
25 | },
26 | (error: AxiosError) => {
27 | if (!isLogEndpoint(error.config?.url)) {
28 | logger.error('HTTP response received', {
29 | url: error.config?.url,
30 | statusCode: error.response?.status,
31 | })
32 | }
33 | throw error
34 | },
35 | )
36 | return () => {
37 | axiosInstance.interceptors.request.eject(requestInterceptor)
38 | axiosInstance.interceptors.response.eject(responseInterceptor)
39 | }
40 | }
41 |
42 | const isLogEndpoint = (url: string | undefined) =>
43 | url && url.indexOf('/logs') > -1
44 |
--------------------------------------------------------------------------------
/api/tests/test_revoke_cognito_refresh_token.py:
--------------------------------------------------------------------------------
1 | from unittest.mock import call, ANY
2 |
3 | import pytest
4 |
5 | from api.PclusterApiHandler import revoke_cognito_refresh_token
6 | from api.logging.logger import DefaultLogger
7 |
8 |
9 | @pytest.fixture
10 | def mock_requests(mocker):
11 | return mocker.patch('api.PclusterApiHandler.requests')
12 |
13 | @pytest.fixture
14 | def mock_logger(mocker):
15 | return mocker.patch('api.PclusterApiHandler.logger', DefaultLogger(is_running_local=False))
16 |
17 | class MockResponse:
18 | def __init__(self, status_code):
19 | self.status_code = status_code
20 |
21 | def test_revoke_cognito_refresh_token_success(mock_requests):
22 | mock_requests.post.return_value = MockResponse(200)
23 |
24 | revoke_cognito_refresh_token('refresh-token')
25 |
26 | mock_requests.post.has_calls(call(ANY, {'token': 'refresh-token'}, ANY, {'Content-Type': 'application/x-www-form-urlencoded'}))
27 |
28 |
29 |
30 |
31 | def test_revoke_cognito_refresh_token_failing(mock_requests, mock_logger, caplog):
32 | mock_requests.post.return_value = MockResponse(400)
33 |
34 | revoke_cognito_refresh_token('refresh-token')
35 |
36 | mock_requests.post.has_calls(call(ANY, {'token': 'refresh-token'}, ANY, {'Content-Type': 'application/x-www-form-urlencoded'}))
37 | assert caplog.text.strip() == "WARNING pcluster-manager:logger.py:27 {'message': 'Unable to revoke cognito refresh token'}"
--------------------------------------------------------------------------------
/e2e/specs/images.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import { expect, test } from '@playwright/test';
12 | import { visitAndLogin } from '../test-utils/login';
13 |
14 | test.describe('Given an endpoint where AWS ParallelCluster UI is deployed', () => {
15 | test.describe('when the user navigates to the Images page', () => {
16 | test('the user can switch between Official and Custom images sections', async ({ page }) => {
17 | await visitAndLogin(page)
18 |
19 | await page.getByRole('link', { name: 'Images' }).click();
20 |
21 | await page.getByRole('tab', { name: /Official/i }).click();
22 | await expect(page.getByRole('heading', { name: /Official images/ })).toBeVisible()
23 |
24 | await page.getByRole('tab', { name: /Custom/i }).click();
25 | await expect(page.getByRole('heading', { name: /Custom images/ })).toBeVisible()
26 | });
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/frontend/src/logger/ConsoleLogger.ts:
--------------------------------------------------------------------------------
1 | import {ILogger} from './ILogger'
2 |
3 | export class ConsoleLogger implements ILogger {
4 | critical(
5 | message: Error | string,
6 | extra?: Record,
7 | source?: string,
8 | ): void {
9 | console.error(this.formatMessage(message, extra, source))
10 | }
11 |
12 | debug(
13 | message: string,
14 | extra?: Record,
15 | source?: string,
16 | ): void {
17 | console.debug(this.formatMessage(message, extra, source))
18 | }
19 |
20 | error(
21 | message: Error | string,
22 | extra?: Record,
23 | source?: string,
24 | ): void {
25 | console.error(this.formatMessage(message, extra, source))
26 | }
27 |
28 | info(
29 | message: string,
30 | extra?: Record,
31 | source?: string,
32 | ): void {
33 | console.info(this.formatMessage(message, extra, source))
34 | }
35 |
36 | warning(
37 | message: string,
38 | extra?: Record,
39 | source?: string,
40 | ): void {
41 | console.warn(this.formatMessage(message, extra, source))
42 | }
43 |
44 | private formatMessage(
45 | message: Error | string,
46 | extra: Record | undefined,
47 | source?: string,
48 | ) {
49 | if (!extra) extra = {}
50 |
51 | extra['source'] ||= source
52 | return `${message}, extra: ${JSON.stringify(extra)}`
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/frontend/src/feature-flags/types.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | // We must track here the features that may be under feature flagging in the wizard.
12 | export type AvailableFeature =
13 | | 'ubuntu1804'
14 | | 'fsx_ontap'
15 | | 'fsx_openzsf'
16 | | 'lustre_persistent2'
17 | | 'multiuser_cluster'
18 | | 'memory_based_scheduling'
19 | | 'slurm_accounting'
20 | | 'slurm_queue_update_strategy'
21 | | 'queues_multiple_instance_types'
22 | | 'multi_az'
23 | | 'on_node_updated'
24 | | 'dynamic_fs_mount'
25 | | 'ebs_deletion_policy'
26 | | 'efs_deletion_policy'
27 | | 'lustre_deletion_policy'
28 | | 'imds_support'
29 | | 'rhel8'
30 | | 'cost_monitoring'
31 | | 'new_resources_limits'
32 | | 'ubuntu2204'
33 | | 'login_nodes'
34 | | 'amazon_file_cache'
35 | | 'job_exclusive_allocation'
36 | | 'memory_based_scheduling_with_multiple_instance_types'
37 | | 'rhel9'
38 |
--------------------------------------------------------------------------------
/frontend/src/types/instances.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | export enum InstanceState {
13 | Pending = 'pending',
14 | Running = 'running',
15 | ShuttingDown = 'shutting-down',
16 | Terminated = 'terminated',
17 | Stopping = 'stopping',
18 | Stopped = 'stopped',
19 | }
20 |
21 | export enum NodeType {
22 | HeadNode = 'HeadNode',
23 | ComputeNode = 'ComputeNode',
24 | }
25 |
26 | export type EC2Instance = {
27 | instanceId: string
28 | instanceType: string
29 | launchTime: string
30 | privateIpAddress: string // only primary?
31 | publicIpAddress: string
32 | state: InstanceState
33 | }
34 |
35 | export type Instance = {
36 | instanceId: string
37 | instanceType: string
38 | launchTime: string
39 | nodeType: NodeType
40 | privateIpAddress: string // only primary?
41 | publicIpAddress?: string
42 | queueName?: string
43 | state: InstanceState
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/e2e_tests.yml:
--------------------------------------------------------------------------------
1 | name: e2e
2 |
3 | on:
4 | workflow_run:
5 | workflows: ["Deploy to Demo"]
6 | types:
7 | - completed
8 |
9 | permissions:
10 | id-token: write
11 | contents: read
12 |
13 | jobs:
14 | e2e-tests:
15 | timeout-minutes: 60
16 | runs-on: ubuntu-latest
17 | if: ${{ github.event.workflow_run.conclusion == 'success' }}
18 | steps:
19 | - uses: actions/checkout@v3
20 | - uses: actions/setup-node@v3
21 | with:
22 | node-version: 16
23 | - name: Configure AWS Credentials
24 | uses: aws-actions/configure-aws-credentials@v1
25 | with:
26 | aws-region: eu-west-1
27 | role-to-assume: ${{ secrets.ACTION_E2E_TESTS_ROLE }}
28 |
29 | - name: Retrieve test user email and password
30 | uses: aws-actions/aws-secretsmanager-get-secrets@v1
31 | with:
32 | secret-ids: |
33 | e2e/test1
34 | parse-json-secrets: true
35 |
36 | - name: Install dependencies
37 | run: npm ci
38 | working-directory: e2e
39 | - name: Install Playwright Browsers
40 | run: npx playwright install --with-deps
41 | working-directory: e2e
42 | - name: Run Playwright tests
43 | run: npm run e2e:test
44 | working-directory: e2e
45 | - uses: actions/upload-artifact@v3
46 | if: always()
47 | with:
48 | name: test-results
49 | path: e2e/test-results/
50 | retention-days: 30
--------------------------------------------------------------------------------
/frontend/src/old-pages/Configure/Queues/__tests__/validateQueueName.test.ts:
--------------------------------------------------------------------------------
1 | import {validateQueueName} from '../queues.validators'
2 |
3 | describe('Given a queue name', () => {
4 | describe("when it's blank", () => {
5 | it('should fail the validation', () => {
6 | expect(validateQueueName('')).toEqual([false, 'empty'])
7 | })
8 | })
9 |
10 | describe("when it's more than 25 chars", () => {
11 | it('should fail the validation', () => {
12 | expect(validateQueueName(new Array(27).join('a'))).toEqual([
13 | false,
14 | 'max_length',
15 | ])
16 | })
17 | })
18 |
19 | describe("when it's less than or equal to 25 chars", () => {
20 | it('should be validated', () => {
21 | expect(validateQueueName(new Array(25).join('a'))).toEqual([true])
22 | })
23 | })
24 |
25 | describe('when one or more bad characters are given', () => {
26 | it('should fail the validation', () => {
27 | expect(validateQueueName('_')).toEqual([false, 'forbidden_chars'])
28 | expect(validateQueueName('=')).toEqual([false, 'forbidden_chars'])
29 | expect(validateQueueName('A')).toEqual([false, 'forbidden_chars'])
30 | expect(validateQueueName('#')).toEqual([false, 'forbidden_chars'])
31 | })
32 | })
33 |
34 | describe('when no bad characters are given', () => {
35 | it('should be validated', () => {
36 | expect(validateQueueName('queue-0')).toEqual([true])
37 | })
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Configure/Queues/queues.types.ts:
--------------------------------------------------------------------------------
1 | export type Queue = {
2 | Name: string
3 | AllocationStrategy: AllocationStrategy
4 | ComputeResources: MultiInstanceComputeResource[]
5 | Networking?: {
6 | SubnetIds: string[]
7 | }
8 | }
9 |
10 | export type QueueValidationErrors = Record<
11 | number,
12 | 'instance_type_unique' | 'instance_types_empty'
13 | >
14 |
15 | export type ComputeResource = {
16 | Name: string
17 | MinCount: number
18 | MaxCount: number
19 | }
20 |
21 | export type SingleInstanceComputeResource = ComputeResource & {
22 | InstanceType: string
23 | }
24 |
25 | export type AllocationStrategy = 'lowest-price' | 'capacity-optimized'
26 |
27 | export type ComputeResourceInstance = {InstanceType: string}
28 |
29 | export type CapacityReservationTarget = {
30 | CapacityReservationId?: string
31 | CapacityReservationResourceGroupArn?: string
32 | }
33 |
34 | export type MultiInstanceComputeResource = ComputeResource & {
35 | Instances?: ComputeResourceInstance[]
36 | CapacityReservationTarget?: CapacityReservationTarget
37 | }
38 |
39 | export type Tag = {
40 | Key: string
41 | Value: string
42 | }
43 |
44 | export type Subnet = {
45 | SubnetId: string
46 | AvailabilityZone: string
47 | AvailabilityZoneId: string
48 | VpcId: string
49 | Tags?: Tag[]
50 | }
51 |
52 | export type ClusterResourcesLimits = {
53 | maxQueues: number
54 | maxCRPerQueue: number
55 | maxCRPerCluster: number
56 | }
57 |
--------------------------------------------------------------------------------
/infrastructure/upload.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
6 | # with the License. A copy of the License is located at
7 | #
8 | # http://aws.amazon.com/apache2.0/
9 | #
10 | # or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
11 | # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | SCRIPT_DIR=$1
15 |
16 | source ${SCRIPT_DIR}/common.sh
17 | source ${SCRIPT_DIR}/bucket_configuration.sh
18 | trap 'error' ERR
19 |
20 |
21 | if [ -z "$SCRIPT_DIR" ];
22 | then
23 | echo "SCRIPT_DIR=$SCRIPT_DIR must be initialized and not empty"
24 | exit 1;
25 | fi
26 |
27 | FILES=(parallelcluster-ui-cognito.yaml parallelcluster-ui.yaml)
28 |
29 | for INDEX in "${!BUCKETS[@]}"
30 | do
31 | echo Uploading to: "${BUCKETS[INDEX]}"
32 | #FIXME For other partitions we should also parametrize the partition in the URL
33 | TEMPLATE_URL="https:\/\/${BUCKETS[INDEX]}\.s3\.${REGIONS[INDEX]}\.amazonaws\.com"
34 | sed -i "s/PLACEHOLDER/${TEMPLATE_URL}/g" "${SCRIPT_DIR}/parallelcluster-ui.yaml"
35 | for FILE in "${FILES[@]}"
36 | do
37 | aws s3 cp "${SCRIPT_DIR}/${FILE}" "s3://${BUCKETS[INDEX]}/${FILE}"
38 | done
39 | done
40 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Users/__tests__/userValidate.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import {validateUser} from '../AddUserModal'
12 |
13 | describe('Given a function to validate a user email', () => {
14 | describe('when the email is not in a correct format', () => {
15 | const username = 'test-email'
16 | it('should fail the validation', () => {
17 | expect(validateUser(username)).toEqual(false)
18 | })
19 | })
20 |
21 | describe('when the email is missing a top-level domain', () => {
22 | const username = 'test-email@domain'
23 | it('should fail the validation', () => {
24 | expect(validateUser(username)).toEqual(false)
25 | })
26 | })
27 |
28 | describe('when the email is in a correct format', () => {
29 | const username = 'test-email@domain.com'
30 | it('should be validated', () => {
31 | expect(validateUser(username)).toEqual(true)
32 | })
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/frontend/public/img/od.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/__tests__/Filesystems.test.ts:
--------------------------------------------------------------------------------
1 | import {mock, MockProxy} from 'jest-mock-extended'
2 | import {EC2Instance} from '../../../types/instances'
3 | import {Storages} from '../../Configure/Storage.types'
4 | import {buildFilesystemLink} from '../Filesystems'
5 |
6 | describe('given a function to build the link to the filesystem in the AWS console', () => {
7 | let mockHeadNode: MockProxy
8 | const mockFileSystem = mock({MountDir: 'some-mount-dir'})
9 | const mockRegion = 'some-region'
10 |
11 | describe('when the headnode configuration is available', () => {
12 | beforeEach(() => {
13 | mockHeadNode = mock({instanceId: 'some-instance-id'})
14 | })
15 |
16 | it('should return the link to the filsystem', () => {
17 | expect(
18 | buildFilesystemLink(mockRegion, mockHeadNode, mockFileSystem),
19 | ).toBe(
20 | 'https://some-region.console.aws.amazon.com/systems-manager/managed-instances/some-instance-id/file-system?region=some-region&osplatform=Linux#%7B%22path%22%3A%22some-mount-dir%22%7D',
21 | )
22 | })
23 | })
24 |
25 | describe('when the headnode configuration is not available', () => {
26 | beforeEach(() => {
27 | mockHeadNode = undefined
28 | })
29 |
30 | it('should return null', () => {
31 | expect(
32 | buildFilesystemLink(mockRegion, mockHeadNode, mockFileSystem),
33 | ).toBe(null)
34 | })
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/api/validation/__init__.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 |
3 | from flask import request, Request
4 | from marshmallow import Schema, ValidationError
5 |
6 | from api.validation.schemas import EC2Action
7 |
8 |
9 | def __validate_request(_request: Request, *, body_schema: Schema = None, params_schema: Schema = None, cookies_schema: Schema = None, raise_on_missing_body = True):
10 | errors = {}
11 | if body_schema:
12 | try:
13 | errors.update(body_schema.validate(_request.json))
14 | except:
15 | if raise_on_missing_body:
16 | raise ValueError('Expected json body')
17 |
18 | if params_schema:
19 | errors.update(params_schema.validate(_request.args))
20 |
21 | if cookies_schema:
22 | errors.update(cookies_schema.validate(_request.cookies))
23 |
24 | return errors
25 |
26 |
27 | def validated(*, body: Schema = None, params: Schema = None, cookies: Schema = None, raise_on_missing_body = True):
28 | def wrapper(func):
29 | @wraps(func)
30 | def decorated(*pargs, **kwargs):
31 | errors = __validate_request(request, body_schema=body, params_schema=params, cookies_schema=cookies, raise_on_missing_body=raise_on_missing_body)
32 | if errors:
33 | raise ValidationError(f'Input validation failed for requested resource {request.path}', data=errors)
34 | return func(*pargs, **kwargs)
35 |
36 | return decorated
37 |
38 | return wrapper
39 |
--------------------------------------------------------------------------------
/frontend/public/img/pcluster.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/e2e/test-utils/logs.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import { expect, Page } from "@playwright/test";
13 |
14 | const NON_ZERO_MESSAGES_COUNT = /Messages.*\([1-9][0-9]*\+\)/
15 |
16 | export async function visitClusterLogsPage(page: Page) {
17 | await expect(page.getByRole('heading', { name: 'Cluster DO-NOT-DELETE logs', exact: true })).toBeVisible()
18 | await expect(page.getByRole('heading', { name: 'Log streams' })).toBeVisible()
19 | await expect(page.getByRole('heading', { name: 'Messages' })).toBeVisible()
20 |
21 | const logStreamsFilter = page.getByPlaceholder('Filter log streams')
22 | await logStreamsFilter.click();
23 | await logStreamsFilter.fill('clusterstatusmgtd');
24 | await logStreamsFilter.press('Enter');
25 |
26 | await page.getByRole('row', { name: 'clusterstatusmgtd' })
27 | .getByRole('radio')
28 | .click();
29 |
30 | await expect(page.getByRole('heading', { name: NON_ZERO_MESSAGES_COUNT })).toBeVisible()
31 | }
--------------------------------------------------------------------------------
/frontend/src/http/executeRequest.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import {AppConfig} from '../app-config/types'
3 | import {handleNotAuthorizedErrors} from '../auth/handleNotAuthorizedErrors'
4 | import identityFn from 'lodash/identity'
5 | import {requestWithCSRF} from './csrf'
6 |
7 | export const axiosInstance = axios.create({
8 | baseURL: getHost(),
9 | })
10 |
11 | function getHost() {
12 | if (process.env.NODE_ENV !== 'production') return 'http://localhost:5001/'
13 | return '/pcui'
14 | }
15 |
16 | export type HTTPMethod = 'get' | 'put' | 'post' | 'patch' | 'delete'
17 |
18 | export type RequestParams = [
19 | method: HTTPMethod,
20 | url: string,
21 | body?: any,
22 | headers?: Record,
23 | appConfig?: AppConfig,
24 | ]
25 |
26 | export function internalExecuteRequest(...params: RequestParams) {
27 | const [method, url, body, headers, appConfig] = params
28 | const requestFunc = axiosInstance[method]
29 |
30 | const headersToSend = {'Content-Type': 'application/json', ...headers}
31 | const handle401and403 = appConfig
32 | ? handleNotAuthorizedErrors(appConfig)
33 | : identityFn>
34 |
35 | const promise =
36 | method === 'get' || method === 'delete'
37 | ? requestFunc(url, {headers: headersToSend})
38 | : requestFunc(url, body, {headers: headersToSend})
39 |
40 | return handle401and403(promise)
41 | }
42 |
43 | export const executeRequest = (...params: RequestParams) =>
44 | requestWithCSRF(internalExecuteRequest, ...params)
45 |
--------------------------------------------------------------------------------
/api/pcm_globals.py:
--------------------------------------------------------------------------------
1 | from contextvars import ContextVar
2 |
3 | from flask import g, Response
4 | from flask.scaffold import Scaffold
5 | from werkzeug.local import LocalProxy
6 |
7 | from api.logging.logger import DefaultLogger
8 |
9 | _logger_ctxvar = ContextVar('pcm_logger')
10 |
11 | logger = LocalProxy(_logger_ctxvar)
12 |
13 | def set_auth_cookies_in_context(cookies: dict):
14 | g.auth_cookies = cookies
15 |
16 | def get_auth_cookies():
17 | if 'auth_cookies' not in g:
18 | g.auth_cookies = {}
19 |
20 | return g.auth_cookies
21 |
22 | auth_cookies = LocalProxy(get_auth_cookies)
23 |
24 | def add_auth_cookies(response: Response):
25 | for name, value in auth_cookies.items():
26 | response.set_cookie(name, value, httponly=True, secure=True, samesite='Lax')
27 | return response
28 |
29 | class PCMGlobals(object):
30 | def __init__(self, app: Scaffold = None, running_local=False):
31 | self.running_local = running_local
32 | if app is not None:
33 | self.init_app(app)
34 |
35 | def init_app(self, app: Scaffold):
36 | _logger = self.__create_logger()
37 |
38 | def set_global_logger_before_func():
39 | _logger_ctxvar.set(_logger)
40 |
41 | app.before_request(set_global_logger_before_func)
42 |
43 | # required for setting auth cookies in case of a token refresh
44 | app.after_request(add_auth_cookies)
45 |
46 |
47 | def __create_logger(self):
48 | return DefaultLogger(self.running_local)
49 |
--------------------------------------------------------------------------------
/infrastructure/custom-domain/custom-domain.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: 2010-09-09
2 | Description: AWS ParallelCluster UI - Custom Domain
3 |
4 | Parameters:
5 | CustomDomainName:
6 | Description: Custom domain name.
7 | Type: String
8 | AllowedPattern: ^(\*\.)?(((?!-)[A-Za-z0-9-]{0,62}[A-Za-z0-9])\.)+((?!-)[A-Za-z0-9-]{1,62}[A-Za-z0-9])$
9 | MinLength: 1
10 | MaxLength: 253
11 |
12 | HostedZoneId:
13 | Description: HostedZoneId
14 | Type: AWS::Route53::HostedZone::Id
15 |
16 | Metadata:
17 | AWS::CloudFormation::Interface:
18 | ParameterGroups:
19 | - Label:
20 | default: Domain
21 | Parameters:
22 | - CustomDomainName
23 | - Label:
24 | default: Networking
25 | Parameters:
26 | - HostedZoneId
27 |
28 | Resources:
29 | CustomDomainCertificate:
30 | Type: AWS::CertificateManager::Certificate
31 | Properties:
32 | DomainName: !Ref CustomDomainName
33 | DomainValidationOptions:
34 | - DomainName: !Ref CustomDomainName
35 | HostedZoneId: !Ref HostedZoneId
36 | KeyAlgorithm: RSA_2048
37 | SubjectAlternativeNames:
38 | - !Sub "*.${CustomDomainName}"
39 | ValidationMethod: DNS
40 |
41 | Outputs:
42 | CustomDomainName:
43 | Value: !Ref CustomDomainName
44 | Description: Custom domain name.
45 | CustomDomainCertificate:
46 | Value: !Ref CustomDomainCertificate
47 | Description: ACM certificate to certify the custom domain name and its subdomains.
48 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: PCUI is malfunctioning
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Before submitting a ticket, please search through the following resources:**
11 | - [Official documentation](https://docs.aws.amazon.com/parallelcluster/latest/ug/pcui-using-v3.html)
12 | - [Issues](https://github.com/aws/aws-parallelcluster-ui/issues)
13 |
14 | ## Description
15 | Briefly describe the issue
16 |
17 | ## Steps to reproduce the issue
18 | List the steps you took to reproduce the issue
19 |
20 | ## Expected behaviour
21 | Describe what you expected to happen
22 |
23 | ## Actual behaviour
24 | Describe what happened instead. Please include, if relevant, any of the following:
25 | - error messages you see
26 | - screenshots
27 |
28 | ## Required info
29 | In order to help us determine the root cause of the issue, please provide the following information:
30 | - Region where ParallelCluster UI is installed
31 | - Version of ParallelCluster UI and ParallelCluster (follow [this guide](https://docs.aws.amazon.com/parallelcluster/parallelcluster/latest/ug/install-pcui-v3.html) to see what's installed)
32 | - Logs
33 |
34 | ## Additional info
35 | The following information is not required but helpful:
36 | - OS: [e.g. MacOS]
37 | - Browser [e.g. chrome, safari]
38 |
39 | ## If having problems with cluster creation or update
40 | YAML file generated by the ParallelCluster UI
41 |
42 | ## If having problems with custom image creation
43 | YAML file of the custom image
44 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/Costs/__tests__/composeTimeRange.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import {composeTimeRange} from '../composeTimeRange'
13 | import tzmock from 'timezone-mock'
14 |
15 | describe('given a function to generate a time range for the last 12 months', () => {
16 | beforeEach(() => {
17 | tzmock.register('UTC')
18 | })
19 |
20 | afterEach(() => {
21 | tzmock.unregister()
22 | })
23 |
24 | describe('given a date', () => {
25 | let mockDate: Date
26 |
27 | beforeEach(() => {
28 | mockDate = new Date('2023-04-21T12:11:15Z')
29 | })
30 |
31 | it('returns the given date in ISO string', () => {
32 | expect(composeTimeRange(mockDate).toDate).toBe('2023-04-21T00:00:00.000Z')
33 | })
34 |
35 | it('returns the date of 12 months earlier in ISO string', () => {
36 | expect(composeTimeRange(mockDate).fromDate).toBe(
37 | '2022-04-21T00:00:00.000Z',
38 | )
39 | })
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/e2e/specs/wizard.template.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import { FileChooser, test } from '@playwright/test';
12 | import { visitAndLogin } from '../test-utils/login';
13 | import { fillWizard } from '../test-utils/wizard';
14 |
15 | const TEMPLATE_PATH = './fixtures/wizard.template.yaml'
16 |
17 | test.describe('environment: @demo', () => {
18 | test.describe('given a cluster configuration template created with single instance type', () => {
19 | test.describe('when the file is imported as a template', () => {
20 | test('user can perform a dry-run successfully', async ({ page }) => {
21 | await visitAndLogin(page)
22 |
23 | await page.getByRole('button', { name: 'Create cluster' }).first().click();
24 |
25 | page.on("filechooser", (fileChooser: FileChooser) => {
26 | fileChooser.setFiles([TEMPLATE_PATH]);
27 | })
28 | await page.getByRole('menuitem', { name: 'With a template' }).click();
29 |
30 | await fillWizard(page)
31 | });
32 | });
33 | });
34 | });
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/Costs/__tests__/valueFormatter.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import {toShortDollarAmount} from '../valueFormatter'
13 |
14 | describe('toShortDollarAmount', () => {
15 | test('returns correct value for input >= 1e9', () => {
16 | const result = toShortDollarAmount(1500000000)
17 | expect(result).toEqual('1.5G')
18 | })
19 |
20 | test('returns correct value for input >= 1e6', () => {
21 | const result = toShortDollarAmount(2000000)
22 | expect(result).toEqual('2.0M')
23 | })
24 |
25 | test('returns correct value for input >= 1e3', () => {
26 | const result = toShortDollarAmount(5000)
27 | expect(result).toEqual('5.0K')
28 | })
29 |
30 | test('returns correct value for input < 1e3', () => {
31 | const result = toShortDollarAmount(123.456)
32 | expect(result).toEqual('123.46')
33 | })
34 |
35 | test('returns correct value for negative input', () => {
36 | const result = toShortDollarAmount(-1234567890)
37 | expect(result).toEqual('-1.2G')
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/frontend/jest.config.js:
--------------------------------------------------------------------------------
1 | const merge = require('lodash/merge')
2 | const nextJest = require('next/jest')
3 | const awsuiPreset = require('@cloudscape-design/jest-preset/jest-preset')
4 |
5 | const createJestConfig = nextJest({
6 | // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
7 | dir: './',
8 | })
9 |
10 | // Add any custom config to be passed to Jest
11 | const customJestConfig = {
12 | // Add more setup options before each test is run
13 | // setupFilesAfterEnv: ['/jest.setup.js'],
14 | // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
15 | moduleDirectories: ['node_modules', '/'],
16 | testEnvironment: 'jest-environment-jsdom',
17 | }
18 |
19 | async function mergePolarisPreset() {
20 | const nextConfig = await createJestConfig(customJestConfig)()
21 | const mergedConfig = merge({}, nextConfig, awsuiPreset)
22 |
23 | return mergedConfig
24 | }
25 |
26 | // necessary to circumvent Jest exception handlers to test exception throwing
27 | // this is due to the impossibility to run code before Jest starts, since Jest performs its
28 | // override/patching of the `process` variable
29 | // see https://johann.pardanaud.com/blog/how-to-assert-unhandled-rejection-and-uncaught-exception-with-jest/
30 | // this is not the right place for setup code or custom configuration code
31 | // do NOT add code here
32 | process._original = (function (_original) {
33 | return function () {
34 | return _original
35 | }
36 | })(process)
37 |
38 | module.exports = mergePolarisPreset
39 |
--------------------------------------------------------------------------------
/frontend/src/auth/handleNotAuthorizedErrors.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import {AxiosError} from 'axios'
12 | import {AppConfig} from '../app-config/types'
13 | import {generateRandomId} from '../util'
14 |
15 | export const handleNotAuthorizedErrors =
16 | ({authUrl, clientId, scopes, redirectUri}: AppConfig) =>
17 | async (requestPromise: Promise) => {
18 | return requestPromise.catch(error => {
19 | switch ((error as AxiosError).response?.status) {
20 | case 401:
21 | case 403:
22 | redirectToAuthServer(authUrl, clientId, scopes, redirectUri)
23 | return Promise.reject(error)
24 | }
25 | return Promise.reject(error)
26 | })
27 | }
28 |
29 | function redirectToAuthServer(
30 | authUrl: string,
31 | clientId: string,
32 | scopes: string,
33 | redirectUri: string,
34 | ) {
35 | const url = `${authUrl}?response_type=code&client_id=${clientId}&scope=${encodeURIComponent(
36 | scopes,
37 | )}&redirect_uri=${redirectUri}&state=${generateRandomId()}`
38 | window.location.replace(url)
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Configure/Storage/__tests__/validateStorageName.test.ts:
--------------------------------------------------------------------------------
1 | import {validateStorageName} from '../storage.validators'
2 |
3 | describe('Given a storage name', () => {
4 | describe("when it's more than 30 chars", () => {
5 | it('should fail the validation', () => {
6 | expect(validateStorageName(new Array(32).join('a'))).toEqual([
7 | false,
8 | 'max_length',
9 | ])
10 | })
11 | })
12 |
13 | describe("when it's less than or equal to 30 chars", () => {
14 | it('should be validated', () => {
15 | expect(validateStorageName(new Array(30).join('a'))).toEqual([true])
16 | })
17 | })
18 |
19 | describe("when it's blank", () => {
20 | it('should fail the validation', () => {
21 | expect(validateStorageName('')).toEqual([false, 'empty'])
22 | })
23 | })
24 |
25 | describe("when the name is 'default'", () => {
26 | it('should fail the validation', () => {
27 | expect(validateStorageName('default')).toEqual([
28 | false,
29 | 'forbidden_keyword',
30 | ])
31 | })
32 | })
33 |
34 | describe('when one or more bad characters are given', () => {
35 | it('should fail the validation', () => {
36 | expect(validateStorageName(';')).toEqual([false, 'forbidden_chars'])
37 | expect(validateStorageName('``')).toEqual([false, 'forbidden_chars'])
38 | expect(validateStorageName('#')).toEqual([false, 'forbidden_chars'])
39 | })
40 | })
41 |
42 | describe('when no bad characters are given', () => {
43 | it('should be validated', () => {
44 | expect(validateStorageName('efs_4')).toEqual([true])
45 | })
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/.github/workflows/pull_request.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Pull Request
3 |
4 | on:
5 | #By default, a workflow only runs when a pull_request event's activity type is opened, synchronize, or reopened.
6 | pull_request:
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | frontend-tests:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Checkout repo
17 | uses: actions/checkout@v3
18 |
19 | - uses: actions/setup-node@v3
20 | name: Setup Node version
21 | with:
22 | node-version-file: frontend/.nvmrc
23 | cache: 'npm'
24 | cache-dependency-path: frontend/package-lock.json
25 |
26 | - name: Install dependencies
27 | run: npm ci # https://docs.npmjs.com/cli/v8/commands/npm-ci
28 | working-directory: ./frontend
29 |
30 | - name: Run linter
31 | run: npm run lint
32 | working-directory: ./frontend
33 |
34 | - name: Run type checks
35 | run: npm run ts-validate
36 | working-directory: ./frontend
37 |
38 | - name: Run frontend tests
39 | run: npm test
40 | working-directory: ./frontend
41 |
42 | backend-tests:
43 | runs-on: ubuntu-latest
44 |
45 | steps:
46 | - name: Checkout repo
47 | uses: actions/checkout@v3
48 |
49 | - name: Setup Python version 3.8
50 | uses: actions/setup-python@v4
51 | with:
52 | python-version: '3.8'
53 | cache: 'pip'
54 |
55 | - name: Install python dependencies
56 | run: if [ -f requirements.txt ]; then pip3 install -r requirements.txt ; fi
57 |
58 | - name: Run backend tests
59 | run: pytest
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/util.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 | // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'js-y... Remove this comment to see the full error message
11 | import jsyaml from 'js-yaml'
12 |
13 | import {getState, setState} from '../../store'
14 |
15 | export async function selectCluster(
16 | clusterName: string,
17 | DescribeCluster: (name: string, callback?: () => void) => Promise,
18 | GetConfiguration: (name: string, callback: (value: any) => void) => void,
19 | ) {
20 | const oldClusterName = getState(['app', 'clusters', 'selected'])
21 | let config_path = ['clusters', 'index', clusterName, 'config']
22 | if (oldClusterName !== clusterName) {
23 | setState(['app', 'clusters', 'selected'], clusterName)
24 | }
25 |
26 | try {
27 | await DescribeCluster(clusterName)
28 | GetConfiguration(clusterName, (configuration: any) => {
29 | setState(['clusters', 'index', clusterName, 'configYaml'], configuration)
30 | setState(config_path, jsyaml.load(configuration))
31 | })
32 | } catch (_) {
33 | // NOOP
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/e2e/specs/wizard.fromcluster.spec.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import { test } from '@playwright/test';
12 | import { visitAndLogin } from '../test-utils/login';
13 | import { fillWizard } from '../test-utils/wizard';
14 |
15 | const CLUSTER_TO_COPY_FROM = 'DO-NOT-DELETE'
16 |
17 | test.describe('environment: @demo', () => {
18 | test.describe('given an already existing cluster', () => {
19 | test.describe('when the cluster is picked as source to start the creation wizard', () => {
20 | test('user can perform a dry-run successfully', async ({ page }) => {
21 | await visitAndLogin(page)
22 |
23 | await page.getByRole('button', { name: 'Create cluster' }).first().click();
24 | await page.getByRole('menuitem', { name: 'With an existing cluster' }).click();
25 |
26 | await page.getByRole('button', { name: 'Select a cluster' }).click();
27 | await page.getByRole('option', { name: CLUSTER_TO_COPY_FROM }).click();
28 | await page.getByRole('dialog').getByRole('button', { name: 'Create' }).click();
29 |
30 | await fillWizard(page)
31 | });
32 | })
33 | })
34 | })
--------------------------------------------------------------------------------
/frontend/src/feature-flags/__tests__/useFeatureFlag.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
3 | // with the License. A copy of the License is located at
4 | //
5 | // http://aws.amazon.com/apache2.0/
6 | //
7 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
8 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
9 | // limitations under the License.
10 |
11 | import {renderHook} from '@testing-library/react'
12 | import {useFeatureFlag} from '../useFeatureFlag'
13 |
14 | const mockUseState = jest.fn()
15 |
16 | jest.mock('../../store', () => ({
17 | ...(jest.requireActual('../../store') as any),
18 | useState: (...args: unknown[]) => mockUseState(...args),
19 | }))
20 |
21 | describe('given a hook to test whether a feature flag is active', () => {
22 | describe('when the feature is active', () => {
23 | beforeEach(() => {
24 | mockUseState.mockReturnValue('3.2.0')
25 | })
26 |
27 | it('should return true', () => {
28 | const {result} = renderHook(() => useFeatureFlag('fsx_ontap'))
29 | expect(result.current).toBe(true)
30 | })
31 | })
32 |
33 | describe('when the feature is not active', () => {
34 | beforeEach(() => {
35 | mockUseState.mockReturnValue('3.1.0')
36 | })
37 |
38 | it('should return false', () => {
39 | const {result} = renderHook(() => useFeatureFlag('fsx_ontap'))
40 | expect(result.current).toBe(false)
41 | })
42 | })
43 | })
44 |
--------------------------------------------------------------------------------
/frontend/src/types/stackevents.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | import {CloudFormationResourceStatus} from './base'
12 |
13 | export type StackEvent = {
14 | // The unique ID name of the instance of the stack.
15 | stackId: string
16 | // The unique ID of this event.
17 | eventId: string
18 | // The name associated with a stack.
19 | stackName: string
20 | // The logical name of the resource specified in the template.
21 | logicalResourceId: string
22 | // The name or unique identifier associated with the physical instance of the resource.
23 | physicalResourceId: string
24 | // Type of resource.
25 | resourceType: string
26 | // Time the status was updated.
27 | timestamp: string
28 | // Current status of the resource.
29 | resourceStatus: CloudFormationResourceStatus
30 | // Success/failure message associated with the resource.
31 | resourceStatusReason?: string
32 | // BLOB of the properties used to create the resource.
33 | resourceProperties?: string
34 | // The token passed to the operation that generated this event.
35 | clientRequestToken?: string
36 | }
37 |
38 | export type StackEvents = StackEvent[]
39 |
--------------------------------------------------------------------------------
/e2e/test-utils/users.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import { Page } from "@playwright/test";
13 |
14 | export function generateUserEmail() {
15 | return `user${Math.random().toString(20).substring(2, 8)}@${Math.random().toString(20).substring(2, 6)}.com`
16 | }
17 |
18 | export async function addUser(page: Page, email: string) {
19 | await page.getByRole('button', { name: 'Add user' }).first().click();
20 | await page.getByPlaceholder('email@domain.com').fill(email);
21 | await page.getByRole('button', { name: 'Add user' }).nth(1).click()
22 | }
23 |
24 | export async function deleteUser(page: Page, email: string) {
25 | selectUser(page, email)
26 | await page.getByRole('button', { name: 'Remove' }).first().click();
27 | await page.getByRole('button', { name: 'Delete' }).first().click();
28 | }
29 |
30 | export async function selectUser(page: Page, email: string) {
31 | await page.getByRole('row', { name: email }).getByRole('radio').click();
32 | }
33 |
34 | export async function findUser(page: Page, email: string) {
35 | await page.getByPlaceholder('Find users').click();
36 | await page.getByPlaceholder('Find users').fill(email);
37 | }
38 |
--------------------------------------------------------------------------------
/frontend/src/components/date/__tests__/DateView.test.tsx:
--------------------------------------------------------------------------------
1 | import {render, RenderResult, waitFor} from '@testing-library/react'
2 | import {TimeZone} from '../AbsoluteTimestamp'
3 | import DateView from '../DateView'
4 | import tzmock from 'timezone-mock'
5 |
6 | describe('Given a DateView component', () => {
7 | tzmock.register('UTC')
8 | let renderResult: RenderResult
9 |
10 | describe('when only a string date is provided', () => {
11 | const dateString = '2022-09-16T14:03:35.000Z'
12 | const expectedResult = 'September 16, 2022 at 14:03 (UTC)'
13 |
14 | beforeEach(async () => {
15 | renderResult = await waitFor(() => render())
16 | })
17 |
18 | it('should render the date in the expected absolute format with default locales and timezone', async () => {
19 | expect(renderResult.getByText(expectedResult)).toBeTruthy()
20 | })
21 | })
22 |
23 | describe('when a date string, a locale and a timezone is provided', () => {
24 | const dateString = '2022-09-16T14:03:35.000Z'
25 | const locale = 'it-IT'
26 | const timeZone = TimeZone.UTC
27 |
28 | beforeEach(async () => {
29 | renderResult = await waitFor(() =>
30 | render(
31 | ,
32 | ),
33 | )
34 | })
35 |
36 | it('should render the date in the expeted absolute format with provided locales and timezone', async () => {
37 | const renderedDateString = renderResult.getByTitle(dateString).textContent
38 |
39 | expect(renderedDateString).toContain('16 settembre 2022')
40 | expect(renderedDateString).toContain('14:03')
41 | expect(renderedDateString).toContain('(UTC)')
42 | })
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/CreateButtonDropdown/__tests__/CreateButtonDropdown.test.tsx:
--------------------------------------------------------------------------------
1 | import wrapper from '@cloudscape-design/components/test-utils/dom'
2 | import {Store} from '@reduxjs/toolkit'
3 | import {render, RenderResult} from '@testing-library/react'
4 | import i18n from 'i18next'
5 | import {mock} from 'jest-mock-extended'
6 | import {I18nextProvider, initReactI18next} from 'react-i18next'
7 | import {Provider} from 'react-redux'
8 | import {BrowserRouter} from 'react-router-dom'
9 | import {CreateButtonDropdown} from '../CreateButtonDropdown'
10 |
11 | i18n.use(initReactI18next).init({
12 | resources: {},
13 | lng: 'en',
14 | })
15 |
16 | const mockStore = mock()
17 |
18 | const MockProviders = (props: any) => (
19 |
20 |
21 | {props.children}
22 |
23 |
24 | )
25 |
26 | describe('given a dropdown button to create a cluster', () => {
27 | let screen: RenderResult
28 | let mockOpenWizard: jest.Mock
29 |
30 | beforeEach(() => {
31 | mockOpenWizard = jest.fn()
32 |
33 | screen = render(
34 |
35 |
36 | ,
37 | )
38 | })
39 |
40 | describe('when user selects the option to create a cluster using the wizard', () => {
41 | beforeEach(() => {
42 | const buttonDropdown = wrapper(screen.container).findButtonDropdown()!
43 | buttonDropdown.openDropdown()
44 | buttonDropdown.findItemById('wizard')?.click()
45 | })
46 |
47 | it('should open the cluster creation wizard', () => {
48 | expect(mockOpenWizard).toHaveBeenCalledTimes(1)
49 | })
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Clusters/__tests__/util.test.ts:
--------------------------------------------------------------------------------
1 | import {selectCluster} from '../util'
2 |
3 | describe('Given a function to select the current cluster and a cluster name', () => {
4 | const clusterName = 'some-cluster-name'
5 | let mockDescribeCluster: jest.Mock
6 | let mockGetConfiguration: jest.Mock
7 |
8 | beforeEach(() => {
9 | mockDescribeCluster = jest.fn()
10 | mockGetConfiguration = jest.fn()
11 | })
12 |
13 | describe('when user selects a cluster by name', () => {
14 | it('should describe the cluster', async () => {
15 | await selectCluster(
16 | clusterName,
17 | mockDescribeCluster,
18 | mockGetConfiguration,
19 | )
20 | expect(mockDescribeCluster).toHaveBeenCalledTimes(1)
21 | expect(mockDescribeCluster).toHaveBeenCalledWith(clusterName)
22 | })
23 |
24 | it('should get the cluster configuration', async () => {
25 | await selectCluster(
26 | clusterName,
27 | mockDescribeCluster,
28 | mockGetConfiguration,
29 | )
30 | expect(mockGetConfiguration).toHaveBeenCalledTimes(1)
31 | expect(mockGetConfiguration).toHaveBeenCalledWith(
32 | clusterName,
33 | expect.any(Function),
34 | )
35 | })
36 |
37 | describe('when describing the cluster fails', () => {
38 | beforeEach(async () => {
39 | mockDescribeCluster = jest.fn(() => Promise.reject('any-error'))
40 | await selectCluster(
41 | clusterName,
42 | mockDescribeCluster,
43 | mockGetConfiguration,
44 | )
45 | })
46 |
47 | it('should not get the cluster configuration', () => {
48 | expect(mockGetConfiguration).toHaveBeenCalledTimes(0)
49 | })
50 | })
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Configure/Storage/buildStorageEntries.ts:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import {canCreateStorage} from '../Storage'
13 | import {Storages, UIStorageSettings, StorageType} from '../Storage.types'
14 |
15 | export function buildStorageEntries(
16 | storages: Storages,
17 | uiStorageSettings: UIStorageSettings,
18 | selectedStorageTypes: StorageType[],
19 | ): [Storages, UIStorageSettings] {
20 | const storageEntries: Storages = []
21 | const uiSettingsEntries: UIStorageSettings = []
22 |
23 | const firstIndex = storages.length
24 |
25 | selectedStorageTypes.forEach((storageType: StorageType, index: number) => {
26 | const storageIndex = firstIndex + index
27 | const useExisting = !canCreateStorage(
28 | storageType,
29 | storages,
30 | uiStorageSettings,
31 | )
32 |
33 | const storageEntry: Storages[0] = {
34 | Name: `${storageType}${storageIndex}`,
35 | StorageType: storageType,
36 | MountDir: '/shared',
37 | }
38 | const uiSettingsEntry = {
39 | useExisting,
40 | }
41 |
42 | storageEntries.push(storageEntry)
43 | uiSettingsEntries.push(uiSettingsEntry)
44 | })
45 |
46 | return [storageEntries, uiSettingsEntries]
47 | }
48 |
--------------------------------------------------------------------------------
/api/tests/validation/test_api_custom_validators.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from marshmallow import ValidationError
3 |
4 | from api.validation.validators import size_not_exceeding, is_safe_path
5 |
6 |
7 | def test_size_not_exceeding():
8 | max_size = 300
9 | test_str_not_exceeding = 'a' * (max_size - 2) # save 2 chars for double quotes
10 |
11 | size_not_exceeding(test_str_not_exceeding, max_size)
12 |
13 | def test_size_not_exceeding_failing():
14 | max_size = 300
15 | test_str_not_exceeding = 'a' * max_size # will produce "aaa...", max_size + 2
16 |
17 | with pytest.raises(ValidationError):
18 | size_not_exceeding(test_str_not_exceeding, max_size)
19 |
20 |
21 | @pytest.mark.parametrize(
22 | "path, expected_result", [
23 | pytest.param(
24 | "/whatever_api_version/whatever_api_resource",
25 | True,
26 | id="safe api path absolute"
27 | ),
28 | pytest.param(
29 | "whatever_api_version/whatever_api_resource",
30 | True,
31 | id="safe api path relative"
32 | ),
33 | pytest.param(
34 | "/../whatever",
35 | False,
36 | id="unsafe path traversal 1"
37 | ),
38 | pytest.param(
39 | "./../whatever",
40 | False,
41 | id="unsafe path traversal 2"
42 | ),
43 | pytest.param(
44 | "/whatever/../whatever",
45 | False,
46 | id="unsafe path traversal 3"
47 | ),
48 | pytest.param(
49 | "whatever/../whatever",
50 | False,
51 | id="unsafe path traversal 4"
52 | ),
53 | ])
54 | def test_is_safe_path(path: str, expected_result: bool):
55 | assert is_safe_path(path) == expected_result
--------------------------------------------------------------------------------
/frontend/public/img/queue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/components/FileChooser.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | import * as React from 'react'
12 |
13 | // UI Elements
14 | import {Button} from '@cloudscape-design/components'
15 |
16 | // State
17 | import {useTranslation} from 'react-i18next'
18 |
19 | function FileUploadButton(props: any) {
20 | const {t} = useTranslation()
21 | const hiddenFileInput = React.useRef(null)
22 | const handleClick = (event: any) => {
23 | // @ts-expect-error TS(2531) FIXME: Object is possibly 'null'.
24 | hiddenFileInput.current.click()
25 | }
26 | const handleChange = (event: any) => {
27 | var file = event.target.files[0]
28 | var reader = new FileReader()
29 | reader.onload = function (e) {
30 | // @ts-expect-error TS(2531) FIXME: Object is possibly 'null'.
31 | props.handleData(e.target.result)
32 | }
33 | reader.readAsText(file)
34 | }
35 | return (
36 |
37 |
40 |
46 |
47 | )
48 | }
49 |
50 | export {FileUploadButton as default}
51 |
--------------------------------------------------------------------------------
/scripts/cognito-tools/README.md:
--------------------------------------------------------------------------------
1 | # Cognito Tools
2 |
3 | ## Features
4 | - user export with groups
5 | - user import with groups
6 |
7 | ## Requirements
8 | - AWS credentials in the form of ENV vars
9 | - AWS_ACCESS_KEY_ID
10 | - AWS_SECRET_ACCESS_KEY
11 | - AWS_SESSION_TOKEN
12 | Other form of AWS credentials could be used, the underline executable is the [AWSCli](https://docs.aws.amazon.com/cli/index.html)
13 | - AWS Cli installed
14 |
15 | # How to
16 |
17 | Get AWS credentials for the target account in which you want to operate.
18 | Paste those credentials in a terminal as usual.
19 |
20 | Then, to export users you need to get at least the region in which the Cognito user pool is.
21 | If you don't specify the user pool, the script will just grab the first user pool id available
22 | for the account in the specified region.
23 |
24 | ## Export users with groups
25 | To export users and groups you need
26 | - the region for the user pool
27 | - the user pool id (optional, if not specified the script will grab the first one returned by the API)
28 |
29 | So you can run either this
30 | ```bash
31 | ./export_cognito_users.sh --region eu-west-1 --pool-id eu-west-1_X0gPxTtR8
32 | ```
33 |
34 |
35 | ## Import users with groups
36 | To import users with their respective groups, you need
37 | - the region for the user pool
38 | - the path to the export file
39 | - the user pool id (optional, if not specified the script will grab the first one returned by the API)
40 | - the temporary password to set for each user (optional, defaults to `P@ssw0rd`)
41 | - whether you want to send the email alerting users of the account creation or not
42 |
43 | Assuming you exported the users and groups to a file called `export.txt`, you can run
44 | ```bash
45 | ./import_cognito_users.sh --region eu-west-1 --users-export-file export.txt
46 | ```
--------------------------------------------------------------------------------
/api/logging/__init__.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, request
2 |
3 | from api.logging.http_info import log_request_body_and_headers, log_response_body_and_headers
4 |
5 | VALID_LOG_LEVELS = {'debug', 'info', 'warning', 'error', 'critical'}
6 |
7 | def parse_log_entry(_logger, entry):
8 | """
9 | Parse a log entry expected from PCM frontend and logs
10 | every single entry with the correct log level
11 | returns
12 | log level,
13 | message,
14 | extra dict (if present)
15 | """
16 | level, message, extra = entry.get('level'), entry.get('message'), entry.get('extra')
17 |
18 | lowercase_level = level.lower()
19 | if lowercase_level not in VALID_LOG_LEVELS:
20 | raise ValueError('Level param must be a valid log level')
21 |
22 | return lowercase_level, message, extra
23 |
24 |
25 | def push_log_entry(_logger, level, message, extra):
26 | """ Logs a single log entry at the specified level """
27 | logging_fun = getattr(_logger, level, None)
28 | logging_fun(message, extra=extra)
29 |
30 |
31 | class RequestResponseLogging:
32 | def __init__(self, logger, app: Flask = None, urls_deny_list=['/logs']):
33 | self.logger = logger
34 | self.urls_deny_list = urls_deny_list
35 | if app:
36 | self.init_app(app)
37 |
38 | def init_app(self, app):
39 |
40 | def log_request():
41 | if request.path not in self.urls_deny_list:
42 | log_request_body_and_headers(self.logger, request)
43 |
44 | def log_response(response = None):
45 | if request.path not in self.urls_deny_list:
46 | log_response_body_and_headers(self.logger, response)
47 | return response
48 |
49 | app.before_request(log_request)
50 | app.after_request(log_response)
51 |
--------------------------------------------------------------------------------
/frontend/src/old-pages/Configure/Queues/SubnetMultiSelect.tsx:
--------------------------------------------------------------------------------
1 | import {Multiselect, MultiselectProps} from '@cloudscape-design/components'
2 | import {NonCancelableEventHandler} from '@cloudscape-design/components/internal/events'
3 | import React from 'react'
4 | import {useMemo} from 'react'
5 | import {useTranslation} from 'react-i18next'
6 | import {useState} from '../../../store'
7 | import {subnetName} from '../util'
8 | import {Subnet} from './queues.types'
9 |
10 | type SubnetMultiSelectProps = {
11 | value: string[]
12 | onChange: NonCancelableEventHandler
13 | }
14 |
15 | function SubnetMultiSelect({value, onChange}: SubnetMultiSelectProps) {
16 | const {t} = useTranslation()
17 | const vpc = useState(['app', 'wizard', 'vpc'])
18 | const subnets = useState(['aws', 'subnets'])
19 | const filteredSubnets: Subnet[] = useMemo(
20 | () =>
21 | subnets &&
22 | subnets.filter((s: Subnet) => {
23 | return vpc ? s.VpcId === vpc : true
24 | }),
25 | [subnets, vpc],
26 | )
27 |
28 | const subnetOptions = useMemo(() => {
29 | return filteredSubnets.map((subnet: Subnet) => {
30 | return {
31 | value: subnet.SubnetId,
32 | label: subnet.SubnetId,
33 | description:
34 | subnet.AvailabilityZone +
35 | ` - ${subnet.AvailabilityZoneId}` +
36 | (subnetName(subnet) ? ` (${subnetName(subnet)})` : ''),
37 | }
38 | })
39 | }, [filteredSubnets])
40 |
41 | return (
42 | {
44 | return value.includes(option.value)
45 | })}
46 | onChange={onChange}
47 | placeholder={t('wizard.queues.subnet.placeholder')}
48 | options={subnetOptions}
49 | />
50 | )
51 | }
52 |
53 | export {SubnetMultiSelect}
54 |
--------------------------------------------------------------------------------
/scripts/tail-logs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
6 | # with the License. A copy of the License is located at
7 | #
8 | # http://aws.amazon.com/apache2.0/
9 | #
10 | # or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
11 | # OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
12 | # limitations under the License.
13 |
14 | # This script is used to update the infrastructure of a given PCM environment.
15 | # An environment is composed of a list of variables with the entrypoints of the environment
16 | # and a CloudFormation request file where the stack update can be customized,
17 | # for example by changing the parameters provided to the previous version of the stack
18 | #
19 | # Usage: ./scripts/tail.sh [ENVIRONMENT]
20 | # Example: ./scripts/tail.sh demo
21 |
22 | CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)"
23 |
24 | INFRASTRUCTURE_DIR="$CURRENT_DIR/../infrastructure"
25 |
26 | source "$CURRENT_DIR/common.sh"
27 |
28 | ENVIRONMENT=$1
29 |
30 | [[ -z $ENVIRONMENT ]] && fail "Missing required argument: ENVIRONMENT"
31 |
32 | info "Selected environment: $ENVIRONMENT"
33 |
34 | source "$INFRASTRUCTURE_DIR/environments/$ENVIRONMENT-variables.sh"
35 |
36 | info "Retrieving log group"
37 | LOG_GROUP=$(aws cloudformation describe-stack-resources \
38 | --region "$REGION" \
39 | --stack-name "$STACK_NAME" \
40 | --logical-resource-id ParallelClusterUILambdaLogGroup \
41 | --output text \
42 | --query 'StackResources[0].PhysicalResourceId')
43 |
44 | aws logs tail $LOG_GROUP --region $REGION --follow --format short
--------------------------------------------------------------------------------
/frontend/src/components/NoMatch.tsx:
--------------------------------------------------------------------------------
1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
4 | // with the License. A copy of the License is located at
5 | //
6 | // http://aws.amazon.com/apache2.0/
7 | //
8 | // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
9 | // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
10 | // limitations under the License.
11 |
12 | import {
13 | Container,
14 | ContentLayout,
15 | Link,
16 | TextContent,
17 | } from '@cloudscape-design/components'
18 | import React from 'react'
19 | import errorPage from './../../public/img/error_pages_illustration.svg'
20 | import Image from 'next/image'
21 | import {useTranslation} from 'react-i18next'
22 | import Layout from '../old-pages/Layout'
23 | import {DefaultHelpPanel} from './help-panel/DefaultHelpPanel'
24 | import {useHelpPanel} from './help-panel/HelpPanel'
25 |
26 | export function NoMatch() {
27 | const {t} = useTranslation()
28 | useHelpPanel()
29 |
30 | return (
31 |
32 | >}>
33 |
34 |
35 |
36 |