diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5d2118be0..383b1536b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,8 +9,6 @@ updates: directory: "/" schedule: interval: "daily" - ignore: - - dependency-name: "openapi-spec-validator" labels: - "bumpless" - package-ecosystem: "github-actions" diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 1790266fe..0120a6b72 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -13,6 +13,6 @@ on: jobs: call-changelog-check-workflow: - uses: ASFHyP3/actions/.github/workflows/reusable-changelog-check.yml@v0.8.3 + uses: ASFHyP3/actions/.github/workflows/reusable-changelog-check.yml@v0.9.0 secrets: USER_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/create-jira-issue.yml b/.github/workflows/create-jira-issue.yml index 3a138e945..e87e1c240 100644 --- a/.github/workflows/create-jira-issue.yml +++ b/.github/workflows/create-jira-issue.yml @@ -6,7 +6,7 @@ on: jobs: call-create-jira-issue-workflow: - uses: ASFHyP3/actions/.github/workflows/reusable-create-jira-issue.yml@v0.8.3 + uses: ASFHyP3/actions/.github/workflows/reusable-create-jira-issue.yml@v0.9.0 secrets: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} diff --git a/.github/workflows/deploy-daac.yml b/.github/workflows/deploy-daac.yml index f09989c4f..691866a1b 100644 --- a/.github/workflows/deploy-daac.yml +++ b/.github/workflows/deploy-daac.yml @@ -102,6 +102,6 @@ jobs: call-bump-version-workflow: if: github.ref == 'refs/heads/main' needs: deploy - uses: ASFHyP3/actions/.github/workflows/reusable-bump-version.yml@v0.8.3 + uses: ASFHyP3/actions/.github/workflows/reusable-bump-version.yml@v0.9.0 secrets: USER_TOKEN: ${{ secrets.TOOLS_BOT_PAK }} diff --git a/.github/workflows/labeled-pr.yml b/.github/workflows/labeled-pr.yml index 103ae29a7..64b1b10b5 100644 --- a/.github/workflows/labeled-pr.yml +++ b/.github/workflows/labeled-pr.yml @@ -12,4 +12,4 @@ on: jobs: call-labeled-pr-check-workflow: - uses: ASFHyP3/actions/.github/workflows/reusable-labeled-pr-check.yml@v0.8.3 + uses: ASFHyP3/actions/.github/workflows/reusable-labeled-pr-check.yml@v0.9.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7525b5e30..2ec7cbe35 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: jobs: call-release-workflow: - uses: ASFHyP3/actions/.github/workflows/reusable-release.yml@v0.8.3 + uses: ASFHyP3/actions/.github/workflows/reusable-release.yml@v0.9.0 with: release_prefix: HyP3 secrets: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index f52cb7ab7..638931054 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -87,4 +87,4 @@ jobs: snyk iac test --severity-threshold=high call-secrets-analysis-workflow: - uses: ASFHyP3/actions/.github/workflows/reusable-secrets-analysis.yml@v0.8.3 + uses: ASFHyP3/actions/.github/workflows/reusable-secrets-analysis.yml@v0.9.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index ea6ed3a36..5145b32cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ 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). +## [5.0.1] +### Fixed +- Upgrade the `openapi-core`, `openapi-spec-validator`, and `jsonschema` packages to their latest versions. This is now possible thanks to the pre-release of [openapi-core v0.19.0a1](https://github.com/python-openapi/openapi-core/releases/tag/0.19.0a1), which fixes . Resolves . ## [5.0.0] ### Removed diff --git a/apps/api/src/hyp3_api/api-spec/job_parameters.yml.j2 b/apps/api/src/hyp3_api/api-spec/job_parameters.yml.j2 index 694008afb..349af55e8 100644 --- a/apps/api/src/hyp3_api/api-spec/job_parameters.yml.j2 +++ b/apps/api/src/hyp3_api/api-spec/job_parameters.yml.j2 @@ -53,7 +53,7 @@ components: enum: - {{ job_type }} name: - $ref: "./openapi-spec.yml#components/schemas/name" + $ref: "./openapi-spec.yml#/components/schemas/name" job_parameters: $ref: "#/components/schemas/{{ job_type }}Parameters" diff --git a/apps/api/src/hyp3_api/api-spec/openapi-spec.yml.j2 b/apps/api/src/hyp3_api/api-spec/openapi-spec.yml.j2 index 82fb9b1e0..26135d69c 100644 --- a/apps/api/src/hyp3_api/api-spec/openapi-spec.yml.j2 +++ b/apps/api/src/hyp3_api/api-spec/openapi-spec.yml.j2 @@ -7,6 +7,13 @@ info: security: - EarthDataLogin: [] +{% if security_environment == 'EDC' %} +# See https://github.com/ASFHyP3/hyp3/pull/2009 for context. +servers: + - url: / + - url: /api +{% endif %} + paths: /jobs: @@ -53,7 +60,7 @@ paths: - name: job_type in: query schema: - $ref: "./job_parameters.yml#components/schemas/job_type" + $ref: "./job_parameters.yml#/components/schemas/job_type" - name: start_token in: query schema: @@ -138,7 +145,7 @@ components: quota: $ref: "#/components/schemas/quota" job_names: - $ref: "#components/schemas/job_names_list" + $ref: "#/components/schemas/job_names_list" quota: description: Containes the limit of jobs per month and the amount remaining for a user. @@ -168,7 +175,7 @@ components: minItems: 1 maxItems: 200 items: - $ref: "./job_parameters.yml#components/schemas/new_job" + $ref: "./job_parameters.yml#/components/schemas/new_job" list_of_jobs: type: array @@ -254,9 +261,9 @@ components: user_id: $ref: "#/components/schemas/user_id" job_type: - $ref: "./job_parameters.yml#components/schemas/job_type" + $ref: "./job_parameters.yml#/components/schemas/job_type" job_parameters: - $ref: "./job_parameters.yml#components/schemas/job_parameters" + $ref: "./job_parameters.yml#/components/schemas/job_parameters" request_time: $ref: "#/components/schemas/datetime" status_code: diff --git a/apps/api/src/hyp3_api/routes.py b/apps/api/src/hyp3_api/routes.py index 829b51f08..3fdb01204 100644 --- a/apps/api/src/hyp3_api/routes.py +++ b/apps/api/src/hyp3_api/routes.py @@ -7,17 +7,16 @@ import yaml from flask import abort, g, jsonify, make_response, redirect, render_template, request from flask_cors import CORS +from openapi_core import OpenAPI +from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator from openapi_core.contrib.flask.handlers import FlaskOpenAPIErrorsHandler -from openapi_core.contrib.flask.views import FlaskOpenAPIView -from openapi_core.spec.shortcuts import create_spec -from openapi_core.validation.response.datatypes import ResponseValidationResult from hyp3_api import app, auth, handlers from hyp3_api.openapi import get_spec_yaml api_spec_file = Path(__file__).parent / 'api-spec' / 'openapi-spec.yml' api_spec_dict = get_spec_yaml(api_spec_file) -api_spec = create_spec(api_spec_dict) +api_spec = OpenAPI.from_dict(api_spec_dict) CORS(app, origins=r'https?://([-\w]+\.)*asf\.alaska\.edu', supports_credentials=True) AUTHENTICATED_ROUTES = ['/jobs', '/user'] @@ -68,7 +67,7 @@ def render_ui(): @app.errorhandler(404) -def error404(e): +def error404(_): return handlers.problem_format(404, 'The requested URL was not found on the server.' ' If you entered the URL manually please check your spelling and try again.') @@ -93,41 +92,39 @@ def default(self, o): json.JSONEncoder.default(self, o) -class NonValidator: - def __init__(self, spec): - pass - - def validate(self, res): - return ResponseValidationResult() - - class ErrorHandler(FlaskOpenAPIErrorsHandler): def __init__(self): super().__init__() - @classmethod - def handle(cls, errors): - response = super().handle(errors) + def __call__(self, errors): + response = super().__call__(errors) error = response.json['errors'][0] return handlers.problem_format(error['status'], error['title']) -class Jobs(FlaskOpenAPIView): - def __init__(self, spec): - super().__init__(spec) - self.response_validator = NonValidator - self.openapi_errors_handler = ErrorHandler +app.json_encoder = CustomEncoder + +openapi = FlaskOpenAPIViewDecorator( + api_spec, + response_cls=None, + errors_handler_cls=ErrorHandler, +) + - def post(self): - return jsonify(handlers.post_jobs(request.get_json(), g.user)) +@app.route('/jobs', methods=['POST']) +@openapi +def jobs_post(): + return jsonify(handlers.post_jobs(request.get_json(), g.user)) - def get(self, job_id): - if job_id is not None: - return jsonify(handlers.get_job_by_id(job_id)) - parameters = request.openapi.parameters.query - start = parameters.get('start') - end = parameters.get('end') - return jsonify(handlers.get_jobs( + +@app.route('/jobs', methods=['GET']) +@openapi +def jobs_get(): + parameters = request.openapi.parameters.query + start = parameters.get('start') + end = parameters.get('end') + return jsonify( + handlers.get_jobs( parameters.get('user_id') or g.user, start.isoformat(timespec='seconds') if start else None, end.isoformat(timespec='seconds') if end else None, @@ -135,25 +132,17 @@ def get(self, job_id): parameters.get('name'), parameters.get('job_type'), parameters.get('start_token'), - )) - - -class User(FlaskOpenAPIView): - def __init__(self, spec): - super().__init__(spec) - self.response_validator = NonValidator - self.openapi_errors_handler = ErrorHandler + ) + ) - def get(self): - return jsonify(handlers.get_user(g.user)) +@app.route('/jobs/', methods=['GET']) +@openapi +def jobs_get_by_job_id(job_id): + return jsonify(handlers.get_job_by_id(job_id)) -app.json_encoder = CustomEncoder - -jobs_view = Jobs.as_view('jobs', api_spec) -app.add_url_rule('/jobs', view_func=jobs_view, methods=['GET'], defaults={'job_id': None}) -app.add_url_rule('/jobs', view_func=jobs_view, methods=['POST']) -app.add_url_rule('/jobs/', view_func=jobs_view, methods=['GET']) -user_view = User.as_view('user', api_spec) -app.add_url_rule('/user', view_func=user_view) +@app.route('/user', methods=['GET']) +@openapi +def user_get(): + return jsonify(handlers.get_user(g.user)) diff --git a/requirements-all.txt b/requirements-all.txt index ee8e10148..d74823d95 100644 --- a/requirements-all.txt +++ b/requirements-all.txt @@ -5,16 +5,16 @@ -r requirements-apps-start-execution-worker.txt -r requirements-apps-disable-private-dns.txt -r requirements-apps-update-db.txt -boto3==1.34.9 +boto3==1.34.13 jinja2==3.1.2 moto[dynamodb]==4.2.12 -pytest==7.4.3 +pytest==7.4.4 PyYAML==6.0.1 responses==0.24.1 flake8==6.1.0 flake8-import-order==0.18.2 flake8-blind-except==0.2.1 flake8-builtins==2.2.0 -setuptools==69.0.2 -openapi-spec-validator==0.4.0 +setuptools==69.0.3 +openapi-spec-validator==0.7.1 cfn-lint==0.83.7 diff --git a/requirements-apps-api.txt b/requirements-apps-api.txt index 8df57fcfe..e9d245da2 100644 --- a/requirements-apps-api.txt +++ b/requirements-apps-api.txt @@ -1,7 +1,8 @@ flask==2.2.5 Flask-Cors==4.0.0 -jsonschema==4.17.3 -openapi-core==0.14.5 +jsonschema==4.20.0 +# TODO convert this pin back to the normal format after the next PyPI release +openapi-core @ git+https://github.com/python-openapi/openapi-core@0.19.0a1 prance==23.6.21.0 PyJWT==2.8.0 requests==2.31.0 diff --git a/requirements-apps-disable-private-dns.txt b/requirements-apps-disable-private-dns.txt index 9de482fcb..7275e3bc4 100644 --- a/requirements-apps-disable-private-dns.txt +++ b/requirements-apps-disable-private-dns.txt @@ -1 +1 @@ -boto3==1.34.9 +boto3==1.34.13 diff --git a/requirements-apps-start-execution-manager.txt b/requirements-apps-start-execution-manager.txt index aac993e5a..e29a199f4 100644 --- a/requirements-apps-start-execution-manager.txt +++ b/requirements-apps-start-execution-manager.txt @@ -1,3 +1,3 @@ -boto3==1.34.9 +boto3==1.34.13 ./lib/dynamo/ ./lib/lambda_logging/ diff --git a/requirements-apps-start-execution-worker.txt b/requirements-apps-start-execution-worker.txt index d14d817d3..2388fd873 100644 --- a/requirements-apps-start-execution-worker.txt +++ b/requirements-apps-start-execution-worker.txt @@ -1,2 +1,2 @@ -boto3==1.34.9 +boto3==1.34.13 ./lib/lambda_logging/