Files
ServerKit/backend/app/models/image_update.py
T
Juan DenisandClaude Opus 4.8 7dc0bffb96 feat(docker): image-digest update detection
- ImageUpdateCheck model + ImageUpdateService: compare an app's local
  RepoDigest against the registry's current index digest (via
  `docker buildx imagetools inspect`) for the same tag, and flag
  update_available when they differ. Shells out to docker like the rest of
  the Docker layer; degrades gracefully when the image is local-only or the
  registry is unreachable.
- API: admin POST /api/v1/image-updates/applications/<id>/check and
  GET /api/v1/image-updates/applications/<id> for the latest result.
- Application.to_dict gains a lightweight image_update badge (mirrors the
  existing image_scan badge).
- 7 tests: digest diff/match/unknown, no-image, badge, endpoint, auth gate.

One-click apply (pull + recreate) to follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:22:18 -04:00

36 lines
1.5 KiB
Python

from datetime import datetime
from app import db
class ImageUpdateCheck(db.Model):
"""Result of comparing an application's running image digest against the
current digest for the same tag in its registry."""
__tablename__ = 'image_update_checks'
id = db.Column(db.Integer, primary_key=True)
application_id = db.Column(db.Integer, db.ForeignKey('applications.id'), nullable=False, index=True)
image_ref = db.Column(db.String(500), nullable=False)
current_digest = db.Column(db.String(128)) # sha256:... running locally
latest_digest = db.Column(db.String(128)) # sha256:... currently in the registry
update_available = db.Column(db.Boolean, default=False, nullable=False)
status = db.Column(db.String(20), default='pending') # pending, completed, failed
error_message = db.Column(db.Text)
checked_at = db.Column(db.DateTime, default=datetime.utcnow)
application = db.relationship('Application', backref=db.backref(
'image_update_checks', lazy='dynamic',
order_by='ImageUpdateCheck.checked_at.desc()'))
def to_dict(self):
return {
'id': self.id,
'application_id': self.application_id,
'image_ref': self.image_ref,
'current_digest': self.current_digest,
'latest_digest': self.latest_digest,
'update_available': self.update_available,
'status': self.status,
'error_message': self.error_message,
'checked_at': self.checked_at.isoformat() if self.checked_at else None,
}