class Landing(db.Model): """DEPRECATED This model has been replaced by landoapi.models.transplant.Transplant and has been kept around as part of data migration. In the future when migration has been completed this may been cleaned up and dropped from the database with another migration. """ __tablename__ = "landings" id = db.Column(db.Integer, primary_key=True) request_id = db.Column(db.Integer, unique=True) revision_id = db.Column(db.Integer) diff_id = db.Column(db.Integer) active_diff_id = db.Column(db.Integer) status = db.Column(db.Enum(LandingStatus), nullable=False, default=LandingStatus.aborted) error = db.Column(db.Text(), default='') result = db.Column(db.Text(), default='') requester_email = db.Column(db.String(254)) tree = db.Column(db.String(128)) created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=db.func.now()) updated_at = db.Column(db.DateTime(timezone=True), nullable=False, default=db.func.now(), onupdate=db.func.now())
class Base(db.Model): """An abstract base model that provides common methods and columns. """ __abstract__ = True id = db.Column(db.Integer, primary_key=True) created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=db.func.now()) updated_at = db.Column( db.DateTime(timezone=True), nullable=False, default=db.func.now(), onupdate=db.func.now(), ) @declared_attr def __tablename__(self): """Return a snake-case version of the class name as the table name. To override __tablename__, define this attribute as needed on your model. """ return table_name_re.sub(r"_\1", self.__name__).lower() def __repr__(self): """Return a human-readable representation of the instance. For example, `<Transplant: 1235>`. """ return f"<{self.__class__.__name__}: {self.id}>"
class Transplant(db.Model): """Represents a request to Autoland Transplant.""" __tablename__ = "transplants" # Internal request ID. id = db.Column(db.Integer, primary_key=True) # Autoland Transplant request ID. request_id = db.Column(db.Integer, unique=True) status = db.Column(db.Enum(TransplantStatus), nullable=False, default=TransplantStatus.aborted) created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=db.func.now()) updated_at = db.Column( db.DateTime(timezone=True), nullable=False, default=db.func.now(), onupdate=db.func.now(), ) # JSON object mapping string revision id of the form "<int>" (used because # json keys may not be integers) to integer diff id. This is used to # record the diff id used with each revision and make searching for # Transplants that match a set of revisions easy (such as those # in a stack). # e.g. # { # "1001": 1221, # "1002": 1246, # "1003": 1412 # } revision_to_diff_id = db.Column(JSONB, nullable=False) # JSON array of string revision ids of the form "<int>" (used to match # the string type of revision_to_diff_id keys) listing the order # of the revisions in the request from most ancestral to most # descendant. # e.g. # ["1001", "1002", "1003"] revision_order = db.Column(JSONB, nullable=False) # Text describing errors when not landed. error = db.Column(db.Text(), default="") # Revision (sha) of the head of the push. result = db.Column(db.Text(), default="") # LDAP email of the user who requested transplant. requester_email = db.Column(db.String(254)) # URL of the repository revisions are to land to. repository_url = db.Column(db.Text(), default="") # Treestatus tree name the revisions are to land to. tree = db.Column(db.String(128)) def __repr__(self): return "<Transplant: %s>" % self.id def update_from_transplant(self, landed, error="", result=""): """Set the status from pingback request.""" self.error = error self.result = result if not landed: self.status = (TransplantStatus.failed if error else TransplantStatus.submitted) else: self.status = TransplantStatus.landed @property def landing_path(self): return [(int(r), self.revision_to_diff_id[r]) for r in self.revision_order] @property def head_revision(self): """Human-readable representation of the branch head's Phabricator revision ID. """ assert ( self.revision_order ), "head_revision should never be called without setting self.revision_order!" return "D" + self.revision_order[-1] @classmethod def revisions_query(cls, revisions): revisions = [str(int(r)) for r in revisions] return cls.query.filter( cls.revision_to_diff_id.has_any(array(revisions))) def serialize(self): """Return a JSON compatible dictionary.""" return { "id": self.id, "request_id": self.request_id, "status": self.status.value, "landing_path": [{ "revision_id": "D{}".format(r), "diff_id": self.revision_to_diff_id[r] } for r in self.revision_order], "details": (self.error or self.result if self.status in (TransplantStatus.failed, TransplantStatus.aborted) else self.result or self.error), "requester_email": self.requester_email, "tree": self.tree, "repository_url": self.repository_url, "created_at": (self.created_at.astimezone(datetime.timezone.utc).isoformat()), "updated_at": (self.updated_at.astimezone(datetime.timezone.utc).isoformat()), }
class Transplant(db.Model): """Represents a request to Autoland Transplant.""" __tablename__ = "transplants" # Internal request ID. id = db.Column(db.Integer, primary_key=True) # Autoland Transplant request ID. request_id = db.Column(db.Integer, unique=True) status = db.Column(db.Enum(TransplantStatus), nullable=False, default=TransplantStatus.aborted) created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=db.func.now()) updated_at = db.Column(db.DateTime(timezone=True), nullable=False, default=db.func.now(), onupdate=db.func.now()) # JSON object mapping string revision id of the form "<int>" (used because # json keys may not be integers) to integer diff id. This is used to # record the diff id used with each revision and make searching for # Transplants that match a set of revisions easy (such as those # in a stack). # e.g. # { # "1001": 1221, # "1002": 1246, # "1003": 1412 # } revision_to_diff_id = db.Column(JSONB, nullable=False) # JSON array of string revision ids of the form "<int>" (used to match # the string type of revision_to_diff_id keys) listing the order # of the revisions in the request from most ancestral to most # descendant. # e.g. # ["1001", "1002", "1003"] revision_order = db.Column(JSONB, nullable=False) # Text describing errors when not landed. error = db.Column(db.Text(), default='') # Revision (sha) of the head of the push. result = db.Column(db.Text(), default='') # LDAP email of the user who requested transplant. requester_email = db.Column(db.String(254)) # URL of the repository revisions are to land to. repository_url = db.Column(db.Text(), default='') # Treestatus tree name the revisions are to land to. tree = db.Column(db.String(128)) def __repr__(self): return '<Transplant: %s>' % self.id def update_from_transplant(self, landed, error='', result=''): """Set the status from pingback request.""" self.error = error self.result = result if not landed: self.status = (TransplantStatus.failed if error else TransplantStatus.submitted) else: self.status = TransplantStatus.landed @property def landing_path(self): return [(int(r), self.revision_to_diff_id[r]) for r in self.revision_order] @classmethod def revisions_query(cls, revisions): revisions = [str(int(r)) for r in revisions] return cls.query.filter( cls.revision_to_diff_id.has_any(array(revisions))) @classmethod def is_revision_submitted(cls, revision_id): """Check if revision is successfully submitted. Args: revision_id: The integer id of the revision. Returns: Transplant object or False if not submitted. """ transplants = cls.revisions_query( [revision_id]).filter_by(status=TransplantStatus.submitted).all() if not transplants: return False return transplants[0] @classmethod def legacy_latest_landed(cls, revision_id): """DEPRECATED Return the latest Landing that is landed, or None. Args: revision_id: The integer id of the revision. Returns: Latest transplant object with status landed, or None if none exist. """ return cls.revisions_query( [revision_id]).filter_by(status=TransplantStatus.landed).order_by( cls.updated_at.desc()).first() def serialize(self): """Return a JSON compatible dictionary.""" return { 'id': self.id, 'request_id': self.request_id, 'status': self.status.value, 'landing_path': [ { 'revision_id': 'D{}'.format(r), 'diff_id': self.revision_to_diff_id[r], } for r in self.revision_order ], 'details': ( self.error or self.result if self.status in ( TransplantStatus.failed, TransplantStatus.aborted ) else self.result or self.error ), 'requester_email': self.requester_email, 'tree': self.tree, 'repository_url': self.repository_url, 'created_at': ( self.created_at.astimezone(datetime.timezone.utc).isoformat() ), 'updated_at': ( self.updated_at.astimezone(datetime.timezone.utc).isoformat() ), } # yapf: disable def legacy_serialize(self): """DEPRECATED Serialize to JSON compatible dictionary.""" revision_id = None diff_id = None if self.revision_order is not None: revision_id = self.revision_order[-1] if (revision_id is not None and self.revision_to_diff_id is not None): diff_id = self.revision_to_diff_id.get(revision_id) return { 'id': self.id, 'revision_id': 'D{}'.format(revision_id), 'request_id': self.request_id, 'diff_id': diff_id, 'active_diff_id': diff_id, 'status': self.status.value, 'error_msg': self.error, 'result': self.result, 'requester_email': self.requester_email, 'tree': self.tree, 'tree_url': self.repository_url or '', 'created_at': ( self.created_at.astimezone(datetime.timezone.utc).isoformat() ), 'updated_at': ( self.updated_at.astimezone(datetime.timezone.utc).isoformat() ), } # yapf: disable
class Landing(db.Model): """Represents the landing process in Autoland. Landing is communicating with Autoland via TransplantClient. Landing is communicating with Phabricator via PhabricatorClient. Landing object might be saved to database without creation of the actual landing in Autoland. It is done before landing request to construct required "pingback URL" and save related Patch objects. To update the Landing status Transplant is calling provided pingback URL. Active Diff Id is stored on creation if it is different than diff_id. Attributes: id: Primary Key request_id: Id of the request in Autoland revision_id: Phabricator id of the revision to be landed diff_id: Phabricator id of the diff to be landed active_diff_id: Phabricator id of the diff active at the moment of landing status: Status of the landing. Modified by `update` API error: Text describing the error if not landed result: Revision (sha) of push requester_email: The email address of the requester of the landing. tree: The treestatus tree name the revision is to land to. created_at: DateTime of the creation updated_at: DateTime of the last save """ __tablename__ = "landings" id = db.Column(db.Integer, primary_key=True) request_id = db.Column(db.Integer, unique=True) revision_id = db.Column(db.Integer) diff_id = db.Column(db.Integer) active_diff_id = db.Column(db.Integer) status = db.Column(db.Enum(LandingStatus), nullable=False, default=LandingStatus.aborted) error = db.Column(db.Text(), default='') result = db.Column(db.Text(), default='') requester_email = db.Column(db.String(254)) tree = db.Column(db.String(128)) created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=db.func.now()) updated_at = db.Column(db.DateTime(timezone=True), nullable=False, default=db.func.now(), onupdate=db.func.now()) @classmethod def is_revision_submitted(cls, revision_id): """Check if revision is successfully submitted. Args: revision_id: The integer id of the revision. Returns: Landed Revision object or False if not submitted. """ landings = cls.query.filter( cls.revision_id == revision_id, cls.status == LandingStatus.submitted).all() if not landings: return False return landings[0] @classmethod def latest_landed(cls, revision_id): """Return the latest Landing that is landed, or None. Args: revision_id: The integer id of the revision. Returns: Latest landing object with status landed, or None if none exist. """ return cls.query.filter_by(revision_id=revision_id, status=LandingStatus.landed).order_by( cls.updated_at.desc()).first() def __repr__(self): return '<Landing: %s>' % self.id def serialize(self): """Serialize to JSON compatible dictionary.""" return { 'id': self.id, 'revision_id': 'D{}'.format(self.revision_id), 'request_id': self.request_id, 'diff_id': self.diff_id, 'active_diff_id': self.active_diff_id, 'status': self.status.value, 'error_msg': self.error, 'result': self.result, 'requester_email': self.requester_email, 'tree': self.tree, 'created_at': ( self.created_at.astimezone(datetime.timezone.utc).isoformat() ), 'updated_at': ( self.updated_at.astimezone(datetime.timezone.utc).isoformat() ), } # yapf: disable def update_from_transplant(self, landed, error='', result=''): """Set the status from pingback request.""" self.error = error self.result = result if not landed: self.status = (LandingStatus.failed if error else LandingStatus.submitted) else: self.status = LandingStatus.landed