Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:
- name: Run frontend tests
run: npm test
working-directory: ./frontend
env:
NODE_OPTIONS: "--max-old-space-size=4096"

backend-tests:
runs-on: ubuntu-latest
Expand Down
40 changes: 27 additions & 13 deletions api/PclusterApiHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@
USER_POOL_ID = os.getenv("USER_POOL_ID")
AUTH_PATH = os.getenv("AUTH_PATH")
API_BASE_URL = os.getenv("API_BASE_URL")
API_VERSION = os.getenv("API_VERSION", "3.1.0")
API_VERSION = sorted(os.getenv("API_VERSION", "3.1.0").split(","), key=lambda x: [-int(n) for n in x.split('.')])
DEFAULT_API_VERSION = API_VERSION[0]
API_USER_ROLE = os.getenv("API_USER_ROLE")
OIDC_PROVIDER = os.getenv("OIDC_PROVIDER")
CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
SECRET_ID = os.getenv("SECRET_ID")
SITE_URL = os.getenv("SITE_URL", API_BASE_URL)
SITE_URL = os.getenv("SITE_URL")
SCOPES_LIST = os.getenv("SCOPES_LIST")
REGION = os.getenv("AWS_DEFAULT_REGION")
TOKEN_URL = os.getenv("TOKEN_URL", f"{AUTH_PATH}/oauth2/token")
Expand All @@ -62,6 +63,14 @@
if not JWKS_URL:
JWKS_URL = os.getenv("JWKS_URL",
f"https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}/" ".well-known/jwks.json")
API_BASE_URL_MAPPING = {}

if API_BASE_URL:
for url in API_BASE_URL.split(","):
if url:
pair=url.split("=")
API_BASE_URL_MAPPING[pair[0]] = pair[1]



def jwt_decode(token, audience=None, access_token=None):
Expand Down Expand Up @@ -165,7 +174,7 @@ def authenticate(groups):

if (not groups):
return abort(403)

jwt_roles = set(decoded.get(USER_ROLES_CLAIM, []))
groups_granted = groups.intersection(jwt_roles)
if len(groups_granted) == 0:
Expand All @@ -191,7 +200,7 @@ def get_scopes_list():

def get_redirect_uri():
return f"{SITE_URL}/login"

# Local Endpoints


Expand Down Expand Up @@ -233,9 +242,9 @@ def ec2_action():
def get_cluster_config_text(cluster_name, region=None):
url = f"/v3/clusters/{cluster_name}"
if region:
info_resp = sigv4_request("GET", API_BASE_URL, url, params={"region": region})
info_resp = sigv4_request("GET", API_BASE_URL_MAPPING[_get_version(request)], url, params={"region": region})
else:
info_resp = sigv4_request("GET", API_BASE_URL, url)
info_resp = sigv4_request("GET", API_BASE_URL_MAPPING[_get_version(request)], url)
if info_resp.status_code != 200:
abort(info_resp.status_code)

Expand Down Expand Up @@ -365,7 +374,7 @@ def sacct():
user,
f"sacct {sacct_args} --json "
+ "| jq -c .jobs[0:120]\\|\\map\\({name,user,partition,state,job_id,exit_code\\}\\)",
)
)
if type(accounting) is tuple:
return accounting
else:
Expand Down Expand Up @@ -484,7 +493,7 @@ def get_dcv_session():


def get_custom_image_config():
image_info = sigv4_request("GET", API_BASE_URL, f"/v3/images/custom/{request.args.get('image_id')}").json()
image_info = sigv4_request("GET", API_BASE_URL_MAPPING[_get_version(request)], f"/v3/images/custom/{request.args.get('image_id')}").json()
configuration = requests.get(image_info["imageConfiguration"]["url"])
return configuration.text

Expand Down Expand Up @@ -596,9 +605,9 @@ def _get_identity_from_token(decoded, claims):
identity["username"] = decoded["username"]

for claim in claims:
if claim in decoded:
identity["attributes"][claim] = decoded[claim]
if claim in decoded:
identity["attributes"][claim] = decoded[claim]

return identity

def get_identity():
Expand Down Expand Up @@ -735,14 +744,19 @@ def _get_params(_request):
params.pop("path")
return params

def _get_version(v):
if v and str(v) in API_VERSION:
return str(v)
return DEFAULT_API_VERSION


pc = Blueprint('pc', __name__)

@pc.get('/', strict_slashes=False)
@authenticated({'admin'})
@validated(params=PCProxyArgs)
def pc_proxy_get():
response = sigv4_request(request.method, API_BASE_URL, request.args.get("path"), _get_params(request))
response = sigv4_request(request.method, API_BASE_URL_MAPPING[_get_version(request.args.get("version"))], request.args.get("path"), _get_params(request))
return response.json(), response.status_code

@pc.route('/', methods=['POST','PUT','PATCH','DELETE'], strict_slashes=False)
Expand All @@ -756,5 +770,5 @@ def pc_proxy():
except:
pass

response = sigv4_request(request.method, API_BASE_URL, request.args.get("path"), _get_params(request), body=body)
response = sigv4_request(request.method, API_BASE_URL_MAPPING[_get_version(request.args.get("version"))], request.args.get("path"), _get_params(request), body=body)
return response.json(), response.status_code
20 changes: 20 additions & 0 deletions frontend/locales/en/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,18 @@
"collapsedStepsLabel": "Step {{stepNumber}} of {{stepsCount}}",
"steps": "Steps"
},
"version": {
"label": "Cluster Version",
"title": "Version",
"placeholder": "Select your cluster version",
"description": "Select the AWS ParallelCluster version to use for this cluster.",
"help": {
"main": "Choose the version of AWS ParallelCluster to use for creating and managing your cluster."
},
"validation": {
"versionSelect": "You must select a version."
}
},
"cluster": {
"title": "Cluster",
"description": "Configure the settings that apply to all cluster resources.",
Expand Down Expand Up @@ -1430,6 +1442,14 @@
"href": "https://docs.aws.amazon.com/parallelcluster/latest/ug/support-policy.html"
}
},
"actions": {
"versionSelect": {
"selectedAriaLabel": "Selected version",
"versionText": "Version {{version}}",
"placeholder": "Select a version"
},
"refresh": "Refresh"
},
"list": {
"columns": {
"id": "ID",
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/__tests__/CreateCluster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const mockRequest = executeRequest as jest.Mock

describe('given a CreateCluster command and a cluster configuration', () => {
const clusterName = 'any-name'
const clusterVersion = 'some-version'
const clusterConfiguration = 'Imds:\n ImdsSupport: v2.0'
const mockRegion = 'some-region'
const mockSelectedRegion = 'some-region'
Expand Down Expand Up @@ -37,11 +38,12 @@ describe('given a CreateCluster command and a cluster configuration', () => {
clusterConfiguration,
mockRegion,
mockSelectedRegion,
clusterVersion,
)
expect(mockRequest).toHaveBeenCalledTimes(1)
expect(mockRequest).toHaveBeenCalledWith(
'post',
'api?path=/v3/clusters&region=some-region',
'api?path=/v3/clusters&region=some-region&version=some-version',
expectedBody,
expect.any(Object),
expect.any(Object),
Expand All @@ -55,6 +57,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
clusterConfiguration,
mockRegion,
mockSelectedRegion,
clusterVersion,
false,
mockSuccessCallback,
)
Expand All @@ -71,12 +74,13 @@ describe('given a CreateCluster command and a cluster configuration', () => {
clusterConfiguration,
mockRegion,
mockSelectedRegion,
clusterVersion,
mockDryRun,
)
expect(mockRequest).toHaveBeenCalledTimes(1)
expect(mockRequest).toHaveBeenCalledWith(
'post',
'api?path=/v3/clusters&dryrun=true&region=some-region',
'api?path=/v3/clusters&dryrun=true&region=some-region&version=some-version',
expect.any(Object),
expect.any(Object),
expect.any(Object),
Expand Down Expand Up @@ -106,6 +110,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
clusterConfiguration,
mockRegion,
mockSelectedRegion,
clusterVersion,
false,
undefined,
mockErrorCallback,
Expand All @@ -128,6 +133,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
'Imds:\n ImdsSupport: v2.0',
mockRegion,
mockSelectedRegion,
clusterVersion,
)

expect(mockRequest).toHaveBeenCalledWith(
Expand All @@ -154,6 +160,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
'Imds:\n ImdsSupport: v2.0\nTags:\n - Key: foo\n Value: bar',
mockRegion,
mockSelectedRegion,
clusterVersion,
)

expect(mockRequest).toHaveBeenCalledWith(
Expand All @@ -180,6 +187,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
"Imds:\n ImdsSupport: v2.0\nTags:\n - Key: parallelcluster-ui\n Value: 'true'",
mockRegion,
mockSelectedRegion,
clusterVersion,
)

expect(mockRequest).not.toHaveBeenCalledWith(
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/__tests__/GetVersion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('given a GetVersion command', () => {
describe('when the PC version can be retrieved successfully', () => {
beforeEach(() => {
const mockResponse = {
version: '3.5.0',
version: ['3.5.0', '3.6.0'],
}

mockGet.mockResolvedValueOnce({data: mockResponse})
Expand All @@ -39,13 +39,13 @@ describe('given a GetVersion command', () => {
it('should return the PC version', async () => {
const data = await GetVersion()

expect(data).toEqual<PCVersion>({full: '3.5.0'})
expect(data).toEqual<PCVersion>({full: ['3.5.0', '3.6.0']})
})

it('should store the PC version', async () => {
await GetVersion()

expect(setState).toHaveBeenCalledWith(['app', 'version'], {full: '3.5.0'})
expect(setState).toHaveBeenCalledWith(['app', 'version'], {full: ['3.5.0', '3.6.0']})
})
})

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/__tests__/ListClusters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const mockCluster1: ClusterInfoSummary = {
const mockCluster2: ClusterInfoSummary = {
clusterName: 'test-cluster-2',
clusterStatus: ClusterStatus.CreateComplete,
version: '3.8.0',
version: '3.9.0',
cloudformationStackArn: 'arn',
region: 'region',
cloudformationStackStatus: CloudFormationStackStatus.CreateComplete,
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/__tests__/useLoadingState.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('given a hook to load all the data necessary for the app to boot', () =
someKey: 'some-value',
},
app: {
version: {full: '3.5.0'},
version: {full: ['3.5.0', '3.7.0']},
appConfig: {
someKey: 'some-value',
},
Expand Down Expand Up @@ -109,7 +109,7 @@ describe('given a hook to load all the data necessary for the app to boot', () =
mockStore.getState.mockReturnValue({
identity: null,
app: {
version: {full: '3.5.0'},
wizard: {version: '3.5.0'},
appConfig: {
someKey: 'some-value',
},
Expand All @@ -131,7 +131,9 @@ describe('given a hook to load all the data necessary for the app to boot', () =
someKey: 'some-value',
},
app: {
version: {full: '3.5.0'},
wizard: {
version: '3.5.0',
},
appConfig: null,
},
})
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/feature-flags/useFeatureFlag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {featureFlagsProvider} from './featureFlagsProvider'
import {AvailableFeature} from './types'

export function useFeatureFlag(feature: AvailableFeature): boolean {
const version = useState(['app', 'version', 'full'])
const version = useState(['app', 'wizard', 'version'])
const region = useState(['aws', 'region'])
return isFeatureEnabled(version, region, feature)
}
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,15 @@ function CreateCluster(
clusterConfig: string,
region: string,
selectedRegion: string,
version: string,
dryrun = false,
successCallback?: Callback,
errorCallback?: Callback,
) {
var url = 'api?path=/v3/clusters'
url += dryrun ? '&dryrun=true' : ''
url += region ? `&region=${region}` : ''
url += version ? `&version=${version}` : ''
var body = {
clusterName: clusterName,
clusterConfiguration: mapAndApplyTags(clusterConfig),
Expand Down Expand Up @@ -159,12 +161,14 @@ function UpdateCluster(
clusterConfig: any,
dryrun = false,
forceUpdate: any,
version: any,
successCallback?: Callback,
errorCallback?: Callback,
) {
var url = `api?path=/v3/clusters/${clusterName}`
url += dryrun ? '&dryrun=true' : ''
url += forceUpdate ? '&forceUpdate=true' : ''
url += version ? `&version=${version}` : ''
var body = {clusterConfiguration: clusterConfig}
request('put', url, body)
.then((response: any) => {
Expand Down Expand Up @@ -455,8 +459,8 @@ async function BuildImage(imageId: string, imageConfig: string) {
return data
}

async function ListOfficialImages(region?: string) {
const url = `api?path=/v3/images/official${region ? `&region=${region}` : ''}`
async function ListOfficialImages(region?: string, version?: string) {
const url = `api?path=/v3/images/official${region ? `&region=${region}` : ''}${version ? `&version=${version}` : ''}`
try {
const {data} = await request('get', url)
return data?.images || []
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/old-pages/Clusters/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default function Actions() {
clusterStatus === ClusterStatus.DeleteInProgress ||
clusterStatus === ClusterStatus.UpdateInProgress ||
clusterStatus === ClusterStatus.CreateFailed ||
clusterVersion !== apiVersion
!apiVersion.includes(clusterVersion)
const isStartFleetDisabled = fleetStatus !== 'STOPPED'
const isStopFleetDisabled = fleetStatus !== 'RUNNING'
const isDeleteDisabled =
Expand All @@ -93,12 +93,13 @@ export default function Actions() {

const editConfiguration = React.useCallback(() => {
setState(['app', 'wizard', 'clusterName'], clusterName)
setState(['app', 'wizard', 'page'], 'cluster')
setState(['app', 'wizard', 'page'], 'version')
setState(['app', 'wizard', 'editing'], true)
setState(['app', 'wizard', 'version'], clusterVersion)

navigate('/configure')
loadTemplateFromCluster(clusterName)
}, [clusterName, navigate])
}, [clusterName, navigate, clusterVersion])

const deleteCluster = React.useCallback(() => {
console.log(`Deleting: ${clusterName}`)
Expand Down
Loading
Loading