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

These are experiments around enchancing shipit to accomplish things like the merge duty workflow #1454

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
96 changes: 96 additions & 0 deletions api/src/shipit_api/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import slugid
import sqlalchemy as sa
import sqlalchemy.orm
from sqlalchemy.dialects.postgresql import ENUM

from backend_common.db import db
from shipit_api.common.config import ALLOW_PHASE_SKIPPING, SIGNOFFS
Expand Down Expand Up @@ -264,3 +265,98 @@ def json(self):
"completed": self.completed or "",
"phases": [p.json for p in self.phases],
}


class Approval(db.Model):
__tablename__ = "shipit_api_workflow_approvals"
id = sa.Column(sa.Integer, primary_key=True)
uid = sa.Column(sa.String, nullable=False, unique=True)
name = sa.Column(sa.String, nullable=False)
description = sa.Column(sa.Text)
permissions = sa.Column(sa.String, nullable=False)
completed = sa.Column(sa.DateTime)
completed_by = sa.Column(sa.String)
signed = sa.Column(sa.Boolean, default=False)
step_id = sa.Column(sa.Integer, sa.ForeignKey("shipit_api_workflow_steps.id"))
step = sqlalchemy.orm.relationship("Step", back_populates="approvals")

def __init__(self, uid, name, description, permissions):
self.uid = uid
self.name = name
self.description = description
self.permissions = permissions

@property
def json(self):
return dict(
uid=self.uid,
name=self.name,
description=self.description,
permissions=self.permissions,
completed=self.completed or "",
completed_by=self.completed_by or "",
signed=self.signed,
)


class Step(db.Model):
__tablename__ = "shipit_api_workflow_steps"
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String, nullable=False)
submitted = sa.Column(sa.Boolean, nullable=False, default=False)
task_id = sa.Column(sa.String, nullable=False)
task = sa.Column(sa.JSON, nullable=False)
context = sa.Column(sa.JSON, nullable=False)
created = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
completed = sa.Column(sa.DateTime)
completed_by = sa.Column(sa.String)
workflow_id = sa.Column(sa.Integer, sa.ForeignKey("shipit_api_workflows.id"))
workflow = sqlalchemy.orm.relationship("Workflow", back_populates="steps")
approvals = sqlalchemy.orm.relationship("Approval", order_by=Approval.id, back_populates="step")


class Workflow(db.Model):
__tablename__ = "shipit_api_workflows"
id = sa.Column(sa.Integer, primary_key=True)
attributes = sa.Column(sa.JSON, nullable=False)
name = sa.Column(sa.String(80), nullable=False, unique=True)
status = sa.Column(ENUM("scheduled", "completed", "aborted", name="shipit_api_workflow_status"), nullable=False)
created = sa.Column(sa.DateTime, nullable=False, default=datetime.datetime.utcnow)
completed = sa.Column(sa.DateTime)
steps = sa.orm.relationship("Step", order_by=Step.id, back_populates="workflow")

def __init__(self, attributes, status):
self.name = self.construct_name(attributes)
self.attributes = attributes
self.status = status

def construct_name(self, attributes):
raise NotImplementedError("Subclasses must implement this method to construct the workflow's name")

def step_approvals(self, step):
raise NotImplementedError("Subclasses must implement this method to get approvals for a workflow step")

@property
def allow_step_skipping(self):
# This only affects the frontend, *the API doesn't enforce step skipping.*
return False

@property
def json(self):
return {
"name": self.name,
"status": self.status,
"attributes": self.attributes,
"created": self.created.isoformat(),
"completed": self.completed.isoformat() if self.completed else "",
"steps": [p.json for p in self.steps],
"allow_step_skipping": self.allow_step_skipping,
}


class Merge(Workflow):
def step_approvals(self, step):
return []

def construct_name(self, attributes):
return f"merges-{attributes['version']}"
Copy link
Member Author

Choose a reason for hiding this comment

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

I can sort of imagine a workflow called "merges-125" but I am speculating (a lot)

14 changes: 14 additions & 0 deletions api/workflows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
merges:
authorized-ldap-groups:
- releng
- shipit_relman
steps:
- close-beta # closes beta using the tree status API.
- beta-to-release # merges beta to release, bumps release, tags beta, sends a notification, etc.
- central-to-beta # merges central to beta, bumps beta, tags central, sends a notification, etc.
- open-beta # sets beta the beta tree to "approval required" using the tree status API.
- bump-central # bumps the nightly version and sends a notification.
# - bump-esr-VERSION # not sure what to do about this one yet, but we can probably read HGMO and figure out the ESR bump steps? That data is here https://searchfox.org/mozilla-central/rev/38377227b8f96fda8f418db614e6a8aa67d01c31/taskcluster/config.yml#615-627
# - update-wiki # I don't know if this is feasible any more :/ see Bug 1414278: https://bugzilla.mozilla.org/show_bug.cgi?id=1414278 can the wiki read dynamic data?
- update-shipit # This part is about updating the nightly version and release dates in shipit. To accomplish this we need to modify the app so updating these doesn't require a deployment. Maybe we can store them in a table and create APIs to manage the data.
Copy link
Member Author

Choose a reason for hiding this comment

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

update ShipIt/rebuild product details could be 1 step.

- rebuild-product-details # sends a message to the product details pulse topic to pick up the updated shipit data.
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ services:
- ./api/src/cli_common:/app/src/cli_common
- ./api/src/shipit_api:/app/src/shipit_api
- "./api/products.yml:/app/products.yml"
- "./api/products.yml:/app/workflows.yml"
environment:
- HOST=0.0.0.0
- PORT=8016
Expand Down