Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a table to keep product channel versions and APIs to update them #1465

Merged
merged 1 commit into from
Jun 11, 2024
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
54 changes: 53 additions & 1 deletion api/src/shipit_api/admin/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
rendered_hook_payload,
)
from shipit_api.common.config import HG_PREFIX, PROJECT_NAME, PULSE_ROUTE_REBUILD_PRODUCT_DETAILS, SCOPE_PREFIX
from shipit_api.common.models import DisabledProduct, Phase, Release, Signoff, XPIRelease
from shipit_api.common.models import DisabledProduct, Phase, Release, Signoff, Version, XPIRelease
from shipit_api.public.api import get_disabled_products, list_releases

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -375,3 +375,55 @@ def get_signoff_emails(phases):
additional_shipit_emails.update({signoff.completed_by for signoff in _phase.signoffs if signoff.completed_by is not None})

return list(additional_shipit_emails)


def get_product_channel_version(product, channel):
version = Version.query.filter_by(product_name=product, product_channel=channel).first()
if version:
return version.current_version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: All other functions here seem to return a tuple with (data, exitcode) should we follow the same pattern here?

else:
return {"error": f"No version found for {product} {channel}."}, 404


def update_product_channel_version(product, channel, body):
required_permission = f"{SCOPE_PREFIX}/update_product_channel_version/{product}"
if not current_user.has_permissions(required_permission):
user_permissions = ", ".join(current_user.get_permissions())
abort(401, f"required permission: {required_permission}, user permissions: {user_permissions}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: What's the difference between aborting and returning an error?
Also, seems like (data, exitcode) should be the pattern used?

version = Version.query.filter_by(product_name=product, product_channel=channel).first()
if version and version.current_version != body["version"]:
version.current_version = body["version"]
current_app.db.session.commit()
logger.info(f"Regenerating product details after updating {version.product_name} {version.product_channel} version to {version.current_version}")
_rebuild_product_details({})
notify_via_matrix(product, f"Updated {version.product_name} {version.product_channel} version to `{version.current_version}`.")
return {
"message": f"The version for {product} {channel} was updated successfully.",
"version": version.current_version,
"product": version.product_name,
"channel": version.product_channel,
}, 200
elif version and version.current_version == body["version"]:
return {"error": f"The {product} {channel} version is already {body['version']}!"}, 409
else:
return {"error": f"No version found for {product} {channel}."}, 404


def create_product_channel_version(product, channel, body):
required_permission = f"{SCOPE_PREFIX}/create_product_channel_version/{product}"
if not current_user.has_permissions(required_permission):
user_permissions = ", ".join(current_user.get_permissions())
abort(401, f"required permission: {required_permission}, user permissions: {user_permissions}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

existing_version = Version.query.filter_by(product_name=product, product_channel=channel).first()
if existing_version:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could improve readability here:

if Version.query.filter_by(product_name=product, product_channel=channel).first():
    return {"error": f"A {product} {channel} version already exists."}, 409
# code here without else

return {"error": f"A {product} {channel} version already exists."}, 409
else:
new_version = Version(product_name=product, product_channel=channel, current_version=body["version"])
current_app.db.session.add(new_version)
current_app.db.session.commit()
return {
"message": f"A {product} {channel} version was created successfully.",
"version": new_version.current_version,
"product": new_version.product_name,
"channel": new_version.product_channel,
}, 201
161 changes: 161 additions & 0 deletions api/src/shipit_api/admin/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,167 @@ paths:
"409":
description: Already submitted
content: {}
/versions/{product}/{channel}:
get:
summary: Get the current version for the given product channel
operationId: shipit_api.admin.api.get_product_channel_version
parameters:
- name: product
in: path
description: product name
required: true
schema:
type: string
- name: channel
in: path
description: product channel
required: true
schema:
type: string
responses:
"200":
description: The current version for the given product channel
content:
text/plain:
schema:
type: string
example: "127.0a1"
"404":
description: Product channel version not found
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: "No version found for firefox release."
post:
summary: Create a new product channel version
operationId: shipit_api.admin.api.create_product_channel_version
parameters:
- name: product
in: path
description: The product name for which the version is to be updated
required: true
schema:
type: string
- name: channel
in: path
description: product channel
required: true
schema:
type: string
requestBody:
description: Initial version for a new product channel
content:
application/json:
schema:
type: object
properties:
version:
type: string
example: "128.0a1"
required: true
responses:
"201":
description: Product channel version created successfully
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: "A firefox nightly version was created successfully."
version:
type: string
example: "128.0a1"
product:
type: string
example: "firefox"
channel:
type: string
example: "nightly"
"409":
description: Product channel version already exists
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: "A firefox nightly version already exists."
put:
summary: Update an existing product channel's version
operationId: shipit_api.admin.api.update_product_channel_version
parameters:
- name: product
in: path
description: The product name for which the version is to be updated
required: true
schema:
type: string
- name: channel
in: path
description: product channel
required: true
schema:
type: string
requestBody:
description: A valid version for the product channel
content:
application/json:
schema:
type: object
properties:
version:
type: string
example: "129.0a1"
required: true
responses:
"200":
description: Product version updated successfully
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: "The version for firefox nightly was updated successfully."
version:
type: string
example: "128.0a1"
product:
type: string
example: "firefox"
channel:
type: string
example: "nightly"
"409":
description: Product channel is already the given value
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: "The firefox nightly version is already 128.0a1!"
"404":
description: Product channel version not found
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: "No version found for firefox release."


components:
schemas:
ReleaseInput:
Expand Down
3 changes: 2 additions & 1 deletion api/src/shipit_api/admin/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@
AUTH0_AUTH_SCOPES = assign_ldap_groups_to_scopes()

# other scopes
AUTH0_AUTH_SCOPES.update({"rebuild_product_details": LDAP_GROUPS["firefox-signoff"], "update_release_status": []})
AUTH0_AUTH_SCOPES.update({"rebuild_product_details": LDAP_GROUPS["firefox-signoff"], "update_release_status": [], "create_product_channel_version/firefox": []})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another follow-up: We will need to grant releng scopes to seed the initial Thunderbird version once the Thunderbird bits are implemented.

Suggested change
AUTH0_AUTH_SCOPES.update({"rebuild_product_details": LDAP_GROUPS["firefox-signoff"], "update_release_status": [], "create_product_channel_version/firefox": []})
AUTH0_AUTH_SCOPES.update(
{
"rebuild_product_details": LDAP_GROUPS["firefox-signoff"],
"update_release_status": [],
"create_product_channel_version/firefox": [],
"create_product_channel_version/thunderbird": [],
}
)



# Github scopes
# The following scope gives permission to all github queries, inlcuding private repos
Expand Down
9 changes: 9 additions & 0 deletions api/src/shipit_api/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,12 @@ def json(self):
"completed": self.completed or "",
"phases": [p.json for p in self.phases],
}


class Version(db.Model):
__tablename__ = "shipit_api_versions"
id = sa.Column(sa.Integer, primary_key=True)
product_name = sa.Column(sa.String, unique=True, nullable=False)
product_channel = sa.Column(sa.String, nullable=False)
current_version = sa.Column(sa.String, nullable=False)
__table_args__ = (sa.UniqueConstraint("product_name", "product_channel", name="_product_name_channel_uc"),)