Skip to content
Merged
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
89 changes: 89 additions & 0 deletions .github/workflows/deploy-jth-sandbox.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Deploy jth sandbox to AWS

on:
push:
branches:
- hyp3-jth-sandbox

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
deploy:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- environment: hyp3-jth-sandbox
template_bucket: cf-templates-bywc0durdnqy-us-west-2
image_tag: test
product_lifetime_in_days: 14
default_credits_per_user: 0
default_application_status: APPROVED
cost_profile: DEFAULT
opera_rtc_s1_end_date: Default
job_files: >-
job_spec/AUTORIFT.yml
job_spec/INSAR_GAMMA.yml
job_spec/RTC_GAMMA.yml
job_spec/INSAR_ISCE_BURST.yml
job_spec/INSAR_ISCE_MULTI_BURST.yml
job_spec/ARIA_S1_GUNW.yml
job_spec/OPERA_RTC_S1.yml
instance_types: r6id.xlarge,r6id.2xlarge,r6id.4xlarge,r6id.8xlarge,r6idn.xlarge,r6idn.2xlarge,r6idn.4xlarge,r6idn.8xlarge
default_max_vcpus: 640
expanded_max_vcpus: 640
required_surplus: 0
security_environment: EDC
ami_id: /ngap/amis/image_id_ecs_al2023_x86
distribution_url: ''

environment:
name: ${{ matrix.environment }}

steps:
- uses: actions/checkout@v4.2.2
with:
fetch-depth: 0

- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.V2_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.V2_AWS_SECRET_ACCESS_KEY }}
aws-session-token: ${{ secrets.V2_AWS_SESSION_TOKEN }}
aws-region: ${{ secrets.AWS_REGION }}

- uses: actions/setup-python@v5
with:
python-version: 3.13

- run: |
jq -n '{"Parameters": $ARGS.named}' \
--arg VpcId '${{ secrets.VPC_ID }}' \
--arg SubnetIds '${{ secrets.SUBNET_IDS }}' \
--arg SecretArn '${{ secrets.SECRET_ARN }}' \
--arg ImageTag '${{ matrix.IMAGE_TAG }}' \
--arg ProductLifetimeInDays '${{ matrix.product_lifetime_in_days }} ' \
--arg AuthPublicKey '${{ secrets.AUTH_PUBLIC_KEY }}' \
--arg DefaultCreditsPerUser '${{ matrix.default_credits_per_user }}' \
--arg DefaultApplicationStatus '${{ matrix.default_application_status }}' \
--arg OperaRtcS1EndDate '${{ matrix.opera_rtc_s1_end_date }}' \
--arg AmiId '${{ matrix.ami_id }}' \
--arg DefaultMaxvCpus '${{ matrix.default_max_vcpus }}' \
--arg ExpandedMaxvCpus '${{ matrix.expanded_max_vcpus }}' \
--arg MonthlyBudget '${{ secrets.MONTHLY_BUDGET }}' \
--arg RequiredSurplus '${{ matrix.required_surplus }}' \
--arg InstanceTypes '${{ matrix.instance_types }}' \
--arg BucketReadPrincipals '${{ secrets.BUCKET_READ_PRINCIPALS }}' \
--arg DistributionUrl '${{ matrix.distribution_url }}' \
> parameters.json
- uses: ./.github/actions/deploy-hyp3
with:
TEMPLATE_BUCKET: ${{ matrix.template_bucket }}
STACK_NAME: ${{ matrix.environment }}
API_NAME: ${{ matrix.environment }}
CLOUDFORMATION_ROLE_ARN: ${{ secrets.CLOUDFORMATION_ROLE_ARN }}
COST_PROFILE: ${{ matrix.cost_profile }}
JOB_FILES: ${{ matrix.job_files }}
SECURITY_ENVIRONMENT: ${{ matrix.security_environment }}
PARAMETER_FILE: parameters.json
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [10.12.0]

### Added
- Added a new `PATCH /jobs` endpoint that accepts up to 100 job IDs and updates those jobs with the given `name` value. The update is not transactional, so it's possible that only some of the jobs will be updated if an error occurs. See https://github.com/ASFHyP3/hyp3/issues/2972

## [10.11.18]

### Fixed
Expand Down
36 changes: 36 additions & 0 deletions apps/api/src/hyp3_api/api-spec/openapi-spec.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ paths:
schema:
$ref: "#/components/schemas/jobs_response"

patch:
description: |-
Update a list of up to 100 jobs. The update is not transactional,
so it's possible that only some of the jobs will be updated if an error occurs.
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/patch_jobs_body"
required: true
responses:
"200":
description: 200 response
content:
application/json:
schema:
type: object

get:
description: Get list of previously run jobs.
parameters:
Expand Down Expand Up @@ -199,6 +217,19 @@ components:
jobs:
$ref: "#/components/schemas/list_of_new_jobs"

patch_jobs_body:
description: List of jobs to update with a new name.
type: object
required:
- job_ids
- name
additionalProperties: false
properties:
job_ids:
$ref: "#/components/schemas/job_ids_list"
name:
$ref: "#/components/schemas/name_patch"

jobs_response:
description: List of submitted jobs.
type: object
Expand Down Expand Up @@ -263,6 +294,11 @@ components:
type: number
minimum: 0

job_ids_list:
type: array
items:
$ref: "#/components/schemas/job_id"

job_names_list:
type: array
items:
Expand Down
22 changes: 21 additions & 1 deletion apps/api/src/hyp3_api/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,28 @@ def get_job_by_id(job_id: str) -> dict:


def patch_job_by_id(body: dict, job_id: str, user: str) -> dict:
return _patch_job(job_id, body['name'], user)


def patch_jobs(body: dict, user: str) -> None:
job_ids = body['job_ids']
name = body['name']

if len(job_ids) == 0:
abort(problem_format(400, 'Must provide at least one job ID'))

# Max job IDs value is also documented in OpenAPI spec
max_job_ids = 100
if len(job_ids) > max_job_ids:
abort(problem_format(400, f'Cannot update more than {max_job_ids} jobs'))

for job_id in set(job_ids):
_patch_job(job_id, name, user)


def _patch_job(job_id: str, name: str, user: str) -> dict:
try:
job = dynamo.jobs.update_job_for_user(job_id, body['name'], user)
job = dynamo.jobs.update_job_for_user(job_id, name, user)
except UpdateJobForDifferentUserError as e:
abort(problem_format(403, str(e)))
except UpdateJobNotFoundError as e:
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/hyp3_api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ def jobs_post() -> Response:
return jsonify(handlers.post_jobs(request.get_json(), g.user))


@app.route('/jobs', methods=['PATCH'])
@openapi
def jobs_patch() -> Response:
handlers.patch_jobs(request.get_json(), g.user)
return jsonify({})


@app.route('/jobs', methods=['GET'])
@openapi
def jobs_get() -> Response:
Expand Down
4 changes: 3 additions & 1 deletion lib/dynamo/dynamo/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,9 @@ def update_job_for_user(job_id: str, name: str | None, user_id: str) -> dict:
raise UpdateJobNotFoundError(f'Job {job_id} does not exist')
else:
assert e.response['Item']['user_id']['S'] != user_id
raise UpdateJobForDifferentUserError("You cannot modify a different user's job")
raise UpdateJobForDifferentUserError(
f'You cannot modify job {job_id} because it belongs to a different user'
)
raise

return job
Expand Down
2 changes: 1 addition & 1 deletion tests/test_api/test_api_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


ENDPOINTS = {
JOBS_URI: {'GET', 'HEAD', 'OPTIONS', 'POST'},
JOBS_URI: {'GET', 'HEAD', 'OPTIONS', 'POST', 'PATCH'},
JOBS_URI + '/foo': {'GET', 'HEAD', 'OPTIONS', 'PATCH'},
USER_URI: {'GET', 'HEAD', 'OPTIONS', 'PATCH'},
}
Expand Down
10 changes: 8 additions & 2 deletions tests/test_api/test_patch_job_by_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,17 @@ def test_patch_job_different_user(client, tables):

response = client.patch(f'{JOBS_URI}/40183948-48a1-42d2-a96b-ce44fbba301b', json={'name': 'newname'})
assert response.status_code == HTTPStatus.FORBIDDEN
assert response.json['detail'] == "You cannot modify a different user's job"
assert (
response.json['detail']
== 'You cannot modify job 40183948-48a1-42d2-a96b-ce44fbba301b because it belongs to a different user'
)

response = client.patch(f'{JOBS_URI}/40183948-48a1-42d2-a96b-ce44fbba301b', json={'name': None})
assert response.status_code == HTTPStatus.FORBIDDEN
assert response.json['detail'] == "You cannot modify a different user's job"
assert (
response.json['detail']
== 'You cannot modify job 40183948-48a1-42d2-a96b-ce44fbba301b because it belongs to a different user'
)

assert tables.jobs_table.scan()['Items'] == [
{
Expand Down
Loading
Loading