]> git.ipfire.org Git - pbs.git/commitdiff
distros: Add scaffolding for releases
authorMichael Tremer <michael.tremer@ipfire.org>
Wed, 14 Jun 2023 10:05:21 +0000 (10:05 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Wed, 14 Jun 2023 10:05:21 +0000 (10:05 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/buildservice/distribution.py
src/database.sql
src/templates/distros/releases/index.html [new file with mode: 0644]
src/templates/distros/releases/modules/list.html [new file with mode: 0644]
src/templates/distros/releases/show.html [new file with mode: 0644]
src/web/__init__.py
src/web/distributions.py

index 8e74e127410a566aaf0db7daee7ba46d2678b561..b3ad549b7cea56d4be565ea4a7ed559d2c058087 100644 (file)
@@ -233,6 +233,17 @@ dist_templates_distros_modules_DATA = \
 
 templates_distros_modulesdir = $(templates_distrosdir)/modules
 
+dist_templates_distros_releases_DATA = \
+       src/templates/distros/releases/index.html \
+       src/templates/distros/releases/show.html
+
+templates_distros_releasesdir = $(templates_distrosdir)/releases
+
+dist_templates_distros_releases_modules_DATA = \
+       src/templates/distros/releases/modules/list.html
+
+templates_distros_releases_modulesdir = $(templates_distros_releasesdir)/modules
+
 dist_templates_monitorings_DATA = \
        src/templates/monitorings/delete.html \
        src/templates/monitorings/edit.html \
index 2b0063468edb0c5389b24b6be4284c8457c7482c..10fa341080a90756fc4ecced5f331e1863068764 100644 (file)
@@ -3,6 +3,7 @@
 import logging
 
 from . import base
+from . import misc
 from .decorators import *
 
 # Setup logging
@@ -85,6 +86,13 @@ class Distributions(base.Object):
                        """, name, distro_id, version_id,
                )
 
+       @lazy_property
+       def releases(self):
+               """
+                       Releases
+               """
+               return Releases(self.backend)
+
 
 class Distribution(base.DataObject):
        table = "distributions"
@@ -346,6 +354,8 @@ class Distribution(base.DataObject):
 
                return builds
 
+       # Sources
+
        @lazy_property
        def sources(self):
                sources = self.backend.sources._get_sources("""
@@ -363,3 +373,249 @@ class Distribution(base.DataObject):
                )
 
                return list(sources)
+
+       # Releases
+
+       def get_releases(self, limit=None, offset=None):
+               releases = self.backend.distros.releases._get_releases("""
+                       SELECT
+                               *
+                       FROM
+                               releases
+                       WHERE
+                               distro_id = %s
+                       AND
+                               deleted_at IS NULL
+                       ORDER BY
+                               created_at DESC
+                       LIMIT
+                               %s
+                       OFFSET
+                               %s
+                       """, self.id, limit, offset,
+
+                       # Populate cache
+                       distro=self,
+               )
+
+               return list(releases)
+
+       def get_release(self, slug):
+               return self.backend.distros.releases._get_release("""
+                       SELECT
+                               *
+                       FROM
+                               releases
+                       WHERE
+                               distro_id = %s
+                       AND
+                               slug = %s
+                       AND
+                               deleted_at IS NULL
+                       """, self.id, slug,
+               )
+
+
+class Releases(base.Object):
+       def _get_releases(self, query, *args, **kwargs):
+               return self.db.fetch_many(Release, query, *args, **kwargs)
+
+       def _get_release(self, query, *args, **kwargs):
+               return self.db.fetch_one(Release, query, *args, **kwargs)
+
+       def get_by_id(self, id):
+               return self._get_release("""
+                       SELECT
+                               *
+                       FROM
+                               releases
+                       WHERE
+                               id = %s
+                       """, id,
+               )
+
+       def create(self, distro, repo, name, user, prerelease=False):
+               """
+                       Creates a new release
+               """
+               # Create a slug
+               slug = misc.normalize(name)
+
+               release = self._get_release("""
+                       INSERT INTO
+                               releases
+                       (
+                               distro_id,
+                               repo_id,
+                               name,
+                               slug,
+                               created_by,
+                               prerelease
+                       )
+                       VALUES
+                       (
+                               %s, %s, %s, %s, %s, %s
+                       )
+                       RETURNING *
+                       """, distro, repo, name, slug, user, prerelease,
+
+                       # Populate cache
+                       distro=distro, repo=repo,
+               )
+
+               # XXX create image jobs
+
+               return release
+
+       # Images
+
+       @lazy_property
+       def images(self):
+               return Images(self.backend)
+
+
+class Release(base.DataObject):
+       table = "releases"
+
+       def __repr__(self):
+               return "<%s %s>" % (self.__class__.__name__, self.name)
+
+       def __str__(self):
+               return self.name
+
+       # Distro
+
+       @lazy_property
+       def distro(self):
+               return self.backend.distros.get_by_id(self.data.distro_id)
+
+       # Repo
+
+       @lazy_property
+       def repo(self):
+               return self.backend.repos.get_by_id(self.data.repo_id)
+
+       # Name
+
+       @property
+       def name(self):
+               return self.data.name
+
+       # Slug
+
+       @property
+       def slug(self):
+               return self.data.slug
+
+       # Created At
+
+       @property
+       def created_at(self):
+               return self.data.created_at
+
+       # Created By
+
+       @lazy_property
+       def created_by(self):
+               return self.backend.users.get_by_id(self.data.created_by)
+
+       # Deleted At
+
+       @property
+       def deleted_at(self):
+               return self.data.deleted_at
+
+       # Deleted By
+
+       @lazy_property
+       def deleted_by(self):
+               return self.backend.users.get_by_id(self.data.deleted_by)
+
+       # Pre-Release
+
+       @property
+       def prerelease(self):
+               return self.data.prerelease
+
+       # Announcement
+
+       def get_announcement(self):
+               return self.data.announcement
+
+       def set_announcement(self, text):
+               self._set_attribute("announcement", text)
+
+       announcement = property(get_announcement, set_announcement)
+
+       # URL
+
+       @property
+       def url(self):
+               return "/distros/%s/releases/%s" % (self.distro.slug, self.slug)
+
+       # Release
+
+       def is_released(self):
+               if self.released_at:
+                       return True
+
+               return False
+
+       @property
+       def released_at(self):
+               return self.data.released_at
+
+       async def release(self, when=None):
+               """
+                       Called to prepare releasing the release
+               """
+               if when:
+                       self._set_attribute("released_at", when)
+               else:
+                       self._set_attribute_now("released_at")
+
+               # XXX TODO
+
+       # Images
+
+       @lazy_property
+       def images(self):
+               images = self.backend.distros.releases.images._get_images("""
+                       SELECT
+                               *
+                       FROM
+                               release_images
+                       WHERE
+                               release_id = %s
+                       AND
+                               deleted_at IS NULL
+                       """, self.id,
+
+                       # Populate cache
+                       release=self,
+               )
+
+               # Return grouped by architecture
+               return misc.group(images, lambda image: image.arch)
+
+
+class Images(base.Object):
+       def _get_images(self, query, *args, **kwargs):
+               return self.db.fetch_many(Image, query, *args, **kwargs)
+
+       def _get_image(self, query, *args, **kwargs):
+               return self.db.fetch_one(Image, query, *args, **kwargs)
+
+
+class Image(base.DataObject):
+       table = "release_images"
+
+       @lazy_property
+       def release(self):
+               return self.backend.distros.releases.get_by_id(self.data.release_id)
+
+       # Arch
+
+       @property
+       def arch(self):
+               return self.data.arch
index 74b764622692535364561e846e57ae9deeed0870..b12465d87fb773730fd7d61614d008e53dd331a8 100644 (file)
@@ -704,6 +704,40 @@ CREATE VIEW public.relation_sizes AS
   ORDER BY (pg_relation_size((c.oid)::regclass)) DESC;
 
 
+--
+-- Name: release_images; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.release_images (
+    id integer NOT NULL,
+    release_id integer NOT NULL,
+    type_id integer NOT NULL,
+    arch text NOT NULL,
+    filename text NOT NULL,
+    path text
+);
+
+
+--
+-- Name: release_images_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.release_images_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: release_images_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.release_images_id_seq OWNED BY public.release_images.id;
+
+
 --
 -- Name: release_monitoring_releases; Type: TABLE; Schema: public; Owner: -
 --
@@ -760,6 +794,46 @@ CREATE SEQUENCE public.release_monitorings_id_seq
 ALTER SEQUENCE public.release_monitorings_id_seq OWNED BY public.release_monitorings.id;
 
 
+--
+-- Name: releases; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.releases (
+    id integer NOT NULL,
+    distro_id integer NOT NULL,
+    repo_id integer NOT NULL,
+    name text NOT NULL,
+    slug text NOT NULL,
+    created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+    created_by integer NOT NULL,
+    deleted_at timestamp without time zone,
+    deleted_by integer,
+    released_at timestamp without time zone,
+    prerelease boolean DEFAULT false NOT NULL,
+    announcement text
+);
+
+
+--
+-- Name: releases_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.releases_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: releases_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.releases_id_seq OWNED BY public.releases.id;
+
+
 --
 -- Name: repo_builds; Type: TABLE; Schema: public; Owner: -
 --
@@ -1168,6 +1242,13 @@ ALTER TABLE ONLY public.mirrors ALTER COLUMN id SET DEFAULT nextval('public.mirr
 ALTER TABLE ONLY public.packages ALTER COLUMN id SET DEFAULT nextval('public.packages_id_seq'::regclass);
 
 
+--
+-- Name: release_images id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.release_images ALTER COLUMN id SET DEFAULT nextval('public.release_images_id_seq'::regclass);
+
+
 --
 -- Name: release_monitoring_releases id; Type: DEFAULT; Schema: public; Owner: -
 --
@@ -1182,6 +1263,13 @@ ALTER TABLE ONLY public.release_monitoring_releases ALTER COLUMN id SET DEFAULT
 ALTER TABLE ONLY public.release_monitorings ALTER COLUMN id SET DEFAULT nextval('public.release_monitorings_id_seq'::regclass);
 
 
+--
+-- Name: releases id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases ALTER COLUMN id SET DEFAULT nextval('public.releases_id_seq'::regclass);
+
+
 --
 -- Name: repositories id; Type: DEFAULT; Schema: public; Owner: -
 --
@@ -1349,6 +1437,14 @@ ALTER TABLE ONLY public.packages
     ADD CONSTRAINT packages_pkey PRIMARY KEY (id);
 
 
+--
+-- Name: release_images release_images_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.release_images
+    ADD CONSTRAINT release_images_pkey PRIMARY KEY (id);
+
+
 --
 -- Name: release_monitoring_releases release_monitoring_releases_pkey; Type: CONSTRAINT; Schema: public; Owner: -
 --
@@ -1365,6 +1461,14 @@ ALTER TABLE ONLY public.release_monitorings
     ADD CONSTRAINT release_monitorings_pkey PRIMARY KEY (id);
 
 
+--
+-- Name: releases releases_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+    ADD CONSTRAINT releases_pkey PRIMARY KEY (id);
+
+
 --
 -- Name: repositories repositories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
 --
@@ -1710,6 +1814,20 @@ CREATE UNIQUE INDEX release_monitoring_releases_unique ON public.release_monitor
 CREATE UNIQUE INDEX release_monitorings_unique ON public.release_monitorings USING btree (distro_id, name) WHERE (deleted_at IS NULL);
 
 
+--
+-- Name: releases_distro_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX releases_distro_id ON public.releases USING btree (distro_id) WHERE (deleted_at IS NULL);
+
+
+--
+-- Name: releases_unique; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE UNIQUE INDEX releases_unique ON public.releases USING btree (slug) WHERE (deleted_at IS NULL);
+
+
 --
 -- Name: repo_builds_build_id; Type: INDEX; Schema: public; Owner: -
 --
@@ -2137,6 +2255,22 @@ ALTER TABLE ONLY public.packages
     ADD CONSTRAINT packages_distro_id FOREIGN KEY (distro_id) REFERENCES public.distributions(id);
 
 
+--
+-- Name: release_images release_images_release_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.release_images
+    ADD CONSTRAINT release_images_release_id FOREIGN KEY (release_id) REFERENCES public.releases(id);
+
+
+--
+-- Name: release_images release_images_type_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.release_images
+    ADD CONSTRAINT release_images_type_id FOREIGN KEY (type_id) REFERENCES public.images_types(id);
+
+
 --
 -- Name: release_monitoring_releases release_monitoring_releases_build_id; Type: FK CONSTRAINT; Schema: public; Owner: -
 --
@@ -2185,6 +2319,38 @@ ALTER TABLE ONLY public.release_monitorings
     ADD CONSTRAINT release_monitorings_distro_id FOREIGN KEY (distro_id) REFERENCES public.distributions(id);
 
 
+--
+-- Name: releases releases_created_by; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+    ADD CONSTRAINT releases_created_by FOREIGN KEY (created_by) REFERENCES public.users(id);
+
+
+--
+-- Name: releases releases_deleted_by; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+    ADD CONSTRAINT releases_deleted_by FOREIGN KEY (deleted_by) REFERENCES public.users(id);
+
+
+--
+-- Name: releases releases_distro_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+    ADD CONSTRAINT releases_distro_id FOREIGN KEY (distro_id) REFERENCES public.distributions(id);
+
+
+--
+-- Name: releases releases_repo_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.releases
+    ADD CONSTRAINT releases_repo_id FOREIGN KEY (repo_id) REFERENCES public.repositories(id);
+
+
 --
 -- Name: repo_builds repo_builds_added_by; Type: FK CONSTRAINT; Schema: public; Owner: -
 --
diff --git a/src/templates/distros/releases/index.html b/src/templates/distros/releases/index.html
new file mode 100644 (file)
index 0000000..1519c3d
--- /dev/null
@@ -0,0 +1,36 @@
+{% extends "../../base.html" %}
+
+{% block title %}{{ distro }} - {{ _("Releases") }}{% end block %}
+
+{% block body %}
+       <section class="hero is-light">
+               <div class="hero-body">
+                       <div class="container">
+                               <nav class="breadcrumb" aria-label="breadcrumbs">
+                                       <ul>
+                                               <li>
+                                                       <a href="/distros">{{ _("Distributions") }}</a>
+                                               </li>
+                                               <li>
+                                                       <a href="/distros/{{ distro.slug }}">
+                                                               {{ distro }}
+                                                       </a>
+                                               </li>
+                                               <li class="is-active">
+                                                       <a href="#" aria-current="page">{{ _("Releases") }}</a>
+                                               </li>
+                                       </ul>
+                               </nav>
+
+                               <h1 class="title">{{ _("Releases") }}</h1>
+                               <h4 class="subtitle is-4">{{ distro }}</h4>
+                       </div>
+               </div>
+       </section>
+
+       <section class="section">
+               <div class="container">
+                       {% module ReleasesList(releases) %}
+               </div>
+       </section>
+{% end block %}
diff --git a/src/templates/distros/releases/modules/list.html b/src/templates/distros/releases/modules/list.html
new file mode 100644 (file)
index 0000000..297d532
--- /dev/null
@@ -0,0 +1,34 @@
+<div class="block">
+       <nav class="panel is-link">
+               {% for release in releases %}
+                       <a class="panel-block is-block -between p-4" href="{{ release.url }}">
+                               <h5 class="title is-5 mb-2">
+                                       {{ release }}
+                               </h5>
+
+                               <div class="level is-mobile">
+                                       <div class="level-left">
+                                               <div class="level-item">
+                                                       {% if release.prerelease %}
+                                                               <span class="tag is-danger">{{ _("Development Release") }}</span>
+                                                       {% else %}
+                                                               <span class="tag is-success">{{ _("Stable Release") }}</span>
+                                                       {% end %}
+                                               </div>
+
+                                               {# Release Date #}
+                                               <div class="level-item">
+                                                       {% if release.is_released() %}
+                                                               {{ locale.format_date(release.released_at) }}
+                                                       {% else %}
+                                                               <span class="has-text-warning">
+                                                                       {{ _("Not released, yet") }}
+                                                               </span>
+                                                       {% end %}
+                                               </div>
+                                       </div>
+                               </div>
+                       </a>
+               {% end %}
+       </nav>
+</div>
diff --git a/src/templates/distros/releases/show.html b/src/templates/distros/releases/show.html
new file mode 100644 (file)
index 0000000..bae0a9d
--- /dev/null
@@ -0,0 +1,50 @@
+{% extends "../../base.html" %}
+
+{% block title %}{{ distro }} - {{ release }}{% end block %}
+
+{% block body %}
+       <section class="hero is-light">
+               <div class="hero-body">
+                       <div class="container">
+                               <nav class="breadcrumb" aria-label="breadcrumbs">
+                                       <ul>
+                                               <li>
+                                                       <a href="/distros">{{ _("Distributions") }}</a>
+                                               </li>
+                                               <li>
+                                                       <a href="/distros/{{ distro.slug }}">
+                                                               {{ distro }}
+                                                       </a>
+                                               </li>
+                                               <li>
+                                                       <a href="/distros/{{ distro.slug }}/releases">
+                                                               {{ _("Releases") }}
+                                                       </a>
+                                               </li>
+                                               <li class="is-active">
+                                                       <a href="#" aria-current="page">{{ release }}</a>
+                                               </li>
+                                       </ul>
+                               </nav>
+
+                               <h1 class="title">{{ release }}</h1>
+                               <h4 class="subtitle is-4">
+                                       {% if release.prerelease %}
+                                               <span class="tag is-danger">{{ _("Development Release") }}</span>
+                                       {% else %}
+                                               <span class="tag is-success">{{ _("Stable Release") }}</span>
+                                       {% end %}
+                               </h4>
+                       </div>
+               </div>
+       </section>
+
+       {# Announcement #}
+       {% if release.announcement %}
+               <section class="section">
+                       <div class="container">
+                               {% module Text(release.announcement) %}
+                       </div>
+               </section>
+       {% end %}
+{% end block %}
index 47461d882a93bf377660913e528179fef8b375a2..d8a868c91420b78fe7d286eb607b6a82c3b47eb8 100644 (file)
@@ -78,6 +78,9 @@ class Application(tornado.web.Application):
                                "PackageInfo"        : packages.InfoModule,
                                "PackageDependencies": packages.DependenciesModule,
 
+                               # Releases
+                               "ReleasesList"       : distributions.ReleasesListModule,
+
                                # Repositories
                                "ReposList"          : repos.ListModule,
 
@@ -218,6 +221,10 @@ class Application(tornado.web.Application):
                        (r"/distros/([A-Za-z0-9\-\.]+)/monitorings/([\w\-_]+)/delete", monitorings.DeleteHandler),
                        (r"/distros/([A-Za-z0-9\-\.]+)/monitorings/([\w\-_]+)/edit", monitorings.EditHandler),
 
+                       # Distro Releases
+                       (r"/distros/([A-Za-z0-9\-\.]+)/releases", distributions.ReleasesIndexHandler),
+                       (r"/distros/([A-Za-z0-9\-\.]+)/releases/([\w\-_]+)", distributions.ReleasesShowHandler),
+
                        # Mirrors
                        (r"/mirrors",                                    mirrors.IndexHandler),
                        (r"/mirrors/create",             mirrors.CreateHandler),
index 566c9c77f5fb656a7904f013537600408db10b09..5e9604c5bacbde501c09f7627be1b8abee16a50f 100644 (file)
@@ -55,6 +55,43 @@ class EditHandler(base.BaseHandler):
                self.redirect("/distros/%s" % distro.slug)
 
 
+class ReleasesIndexHandler(base.BaseHandler):
+       def get(self, distro_slug):
+               distro = self.backend.distros.get_by_slug(distro_slug)
+               if not distro:
+                       raise tornado.web.HTTPError(404, "Could not find distro: %s" % distro_slug)
+
+               # Pagination
+               limit  = self.get_argument_int("limit", 20)
+               offset = self.get_argument_int("offset", None)
+
+               # Fetch releases
+               releases = distro.get_releases(limit=limit, offset=offset)
+
+               self.render("distros/releases/index.html", distro=distro, releases=releases)
+
+
+class ReleasesShowHandler(base.BaseHandler):
+       def get(self, distro_slug, release_slug):
+               distro = self.backend.distros.get_by_slug(distro_slug)
+               if not distro:
+                       raise tornado.web.HTTPError(404, "Could not find distro: %s" % distro_slug)
+
+               # Fetch the release
+               release = distro.get_release(release_slug)
+               if not release:
+                       raise tornado.web.HTTPError(404, "Could not find release %s" % release_slug)
+
+               # XXX check permissions
+
+               self.render("distros/releases/show.html", distro=distro, release=release)
+
+
 class ListModule(ui_modules.UIModule):
        def render(self, distros):
                return self.render_string("distros/modules/list.html", distros=distros)
+
+
+class ReleasesListModule(ui_modules.UIModule):
+       def render(self, releases):
+               return self.render_string("distros/releases/modules/list.html", releases=releases)