#649 [frontend][python][cli] support temporary projects
Closed 5 years ago by praiskup. Opened 5 years ago by praiskup.
Unknown source temporary-projects  into  master

file modified
+8
@@ -354,6 +354,7 @@

              persistent=args.persistent,

              auto_prune=ON_OFF_MAP[args.auto_prune],

              use_bootstrap_container=ON_OFF_MAP[args.use_bootstrap_container],

+             delete_after_days=args.delete_after_days,

          )

          print("New project was successfully created.")

  
@@ -374,6 +375,7 @@

              auto_prune=ON_OFF_MAP[args.auto_prune],

              use_bootstrap_container=ON_OFF_MAP[args.use_bootstrap_container],

              chroots=args.chroots,

+             delete_after_days=args.delete_after_days,

          )

  

      @requires_api_auth
@@ -776,6 +778,8 @@

                                 This option can only be specified by a COPR admin.")

      parser_create.add_argument("--use-bootstrap", choices=["on", "off"], dest="use_bootstrap_container",

                                 help="If mock bootstrap container is used to initialize the buildroot.")

+     parser_create.add_argument("--delete-after-days", default=None, metavar='DAYS',

+                                help="Delete the project after the specfied period of time")

      parser_create.set_defaults(func="action_create")

  

      # create the parser for the "modify_project" command
@@ -801,6 +805,10 @@

                                 This option can only be specified by a COPR admin.")

      parser_modify.add_argument("--use-bootstrap", choices=["on", "off"], dest="use_bootstrap_container",

                                 help="If mock bootstrap container is used to initialize the buildroot.")

+     parser_modify.add_argument("--delete-after-days", default=None, metavar='DAYS',

+                                help=("Delete the project after the specfied "

+                                      "period of time, empty or -1 disables, "

+                                      "(default is \"don't change\")"))

      parser_modify.set_defaults(func="action_modify_project")

  

      # create the parser for the "delete" command

file modified
+2 -1
@@ -373,7 +373,8 @@

          "instructions": "instruction string", "chroots": ["f20", "f21"],

          "additional_repos": ["repo1", "repo2"],

          "unlisted_on_hp": None, "devel_mode": None, "enable_net": False,

-         "use_bootstrap_container": None

+         "use_bootstrap_container": None,

+         "delete_after_days": None,

      }

      assert stdout == "New project was successfully created.\n"

  

@@ -138,6 +138,15 @@

  :ref:`custom_source_method`.

  

  

+ Temporary projects

+ ------------------

+ 

+ If you want have your copr project deleted automatically after some time

+ (because it is some CI/CD project, some testing stuff, etc.) you can set the

+ "delete after days" option in web UI or on command-line:

+ ``copr-cli create your-project ... --delete-after-days 10``

+ 

+ 

  GitHub Webhooks

  ---------------

  

@@ -1,3 +1,4 @@

  #!/usr/bin/sh

  

  runuser -c '/usr/share/copr/coprs_frontend/manage.py vacuum_graphs' - copr-fe

+ runuser -c '/usr/share/copr/coprs_frontend/manage.py clean_expired_project' - copr-fe

@@ -0,0 +1,21 @@

+ """

+ temporary project

+ 

+ Revision ID: b828274ddebf

+ Revises: b64659389c54

+ Create Date: 2019-04-05 11:55:08.004627

+ """

+ 

+ import sqlalchemy as sa

+ from alembic import op

+ 

+ revision = 'b828274ddebf'

+ down_revision = 'b8a8a1345ed9'

+ 

+ def upgrade():

+     op.add_column('copr', sa.Column('delete_after', sa.DateTime(), nullable=True))

+     op.create_index(op.f('ix_copr_delete_after'), 'copr', ['delete_after'], unique=False)

+ 

+ def downgrade():

+     op.drop_index(op.f('ix_copr_delete_after'), table_name='copr')

+     op.drop_column('copr', 'delete_after')

@@ -0,0 +1,15 @@

+ from flask_script import Command

+ from coprs import db_session_scope

+ from coprs.logic.coprs_logic import CoprsLogic

+ 

+ 

+ class CleanExpiredProjectsCommand(Command):

+     """

+     Clean all the expired temporary projects.  This command is meant to be

+     executed by cron.

+     """

+ 

+     # pylint: disable=method-hidden

+     def run(self):

+         with db_session_scope():

+             CoprsLogic.delete_expired_projects()

@@ -230,7 +230,7 @@

  class CoprFormFactory(object):

  

      @staticmethod

-     def create_form_cls(mock_chroots=None, user=None, group=None):

+     def create_form_cls(mock_chroots=None, user=None, group=None, copr=None):

          class F(FlaskForm):

              # also use id here, to be able to find out whether user

              # is updating a copr if so, we don't want to shout
@@ -263,6 +263,14 @@

  

              instructions = wtforms.TextAreaField("Instructions")

  

+             delete_after_days = wtforms.IntegerField(

+                 "Delete after days",

+                 validators=[

+                     wtforms.validators.Optional(),

+                     wtforms.validators.NumberRange(min=0, max=60),

+                 ],

+                 render_kw={'disabled': bool(copr and copr.persistent)})

+ 

              repos = wtforms.TextAreaField(

                  "External Repositories",

                  validators=[UrlRepoListValidator()],
@@ -275,16 +283,61 @@

                      UrlSrpmListValidator()],

                  filters=[StringListFilter()])

  

-             disable_createrepo = wtforms.BooleanField(default=False, false_values=FALSE_VALUES)

-             unlisted_on_hp = wtforms.BooleanField("Do not display this project on home page", default=False, false_values=FALSE_VALUES)

-             persistent = wtforms.BooleanField(default=False, false_values=FALSE_VALUES)

-             auto_prune = wtforms.BooleanField("If backend auto-prunning script should be run for this project", default=True, false_values=FALSE_VALUES)

-             use_bootstrap_container = wtforms.BooleanField("Enable use_bootstrap_container mock's feature (experimental)", default=False, false_values=FALSE_VALUES)

-             follow_fedora_branching = wtforms.BooleanField("If newly branched chroots should be automatically enabled and populated.", default=True, false_values=FALSE_VALUES)

+             disable_createrepo = wtforms.BooleanField(default=False,

+                     label="Create repositories manually",

+                     description="""When Fedora is branched from rawhide, the

+                     respective chroots for the new branch are automatically

+                     created for you (as soon as they are available) as rawhide

+                     chroot forks.""",

+                     false_values=FALSE_VALUES)

+ 

+             unlisted_on_hp = wtforms.BooleanField(

+                     "Project will not be listed on home page",

+                     default=False,

+                     false_values=FALSE_VALUES)

+ 

+             persistent = wtforms.BooleanField(

+                     "Protect project and its builds against deletion",

+                     description="""Project's builds and the project itself

+                     cannot be deleted by any means.  This option is set once and

+                     for all (this option can not be changed after project is

+                     created).""",

+                     render_kw={'disabled': bool(copr)},

+                     default=False, false_values=FALSE_VALUES)

+ 

+             auto_prune = wtforms.BooleanField(

+                     "Old builds will be deleted automatically",

+                     default=True, false_values=FALSE_VALUES,

+                     description="""Build will be deleted only if there is a

+                     newer build (with respect to package version) and it is

+                     older than 14 days""")

+ 

+             use_bootstrap_container = wtforms.BooleanField(

+                     "Enable mock's use_bootstrap_container experimental feature",

+                     description="""This will make the build slower but it has an

+                     advantage that the dnf _from_ the given chroot will be used

+                     to setup the chroot (otherwise host system dnf and rpm is

+                     used)""",

+                     default=False,

+                     false_values=FALSE_VALUES)

+ 

+             follow_fedora_branching = wtforms.BooleanField(

+                     "Follow Fedora branching",

+                     description="""When Fedora is branched from rawhide, the

+                     respective chroots for the new branch are automatically

+                     created for you (as soon as they are available) as rawhide

+                     chroot forks.""",

+                     default=True,

+                     false_values=FALSE_VALUES)

  

              # Deprecated, use `enable_net` instead

-             build_enable_net = wtforms.BooleanField(default=False, false_values=FALSE_VALUES)

-             enable_net = wtforms.BooleanField(default=False, false_values=FALSE_VALUES)

+             build_enable_net = wtforms.BooleanField(

+                     "Enable internet access during builds",

+                     default=False, false_values=FALSE_VALUES)

+ 

+             enable_net = wtforms.BooleanField(

+                     "Enable internet access during builds",

+                     default=False, false_values=FALSE_VALUES)

  

              @property

              def selected_chroots(self):
@@ -301,6 +354,12 @@

                  if not self.validate_mock_chroots_not_empty():

                      self.errors["chroots"] = ["At least one chroot must be selected"]

                      return False

+ 

+                 if self.persistent.data and self.delete_after_days.data:

+                     self.delete_after_days.errors.append(

+                         "'delete after' can not be combined with persistent")

+                     return False

+ 

                  return True

  

              def validate_mock_chroots_not_empty(self):
@@ -984,6 +1043,11 @@

      auto_prune = wtforms.BooleanField(validators=[wtforms.validators.Optional()], false_values=FALSE_VALUES)

      use_bootstrap_container = wtforms.BooleanField(validators=[wtforms.validators.Optional()], false_values=FALSE_VALUES)

      follow_fedora_branching = wtforms.BooleanField(validators=[wtforms.validators.Optional()], false_values=FALSE_VALUES)

+     follow_fedora_branching = wtforms.BooleanField(default=True, false_values=FALSE_VALUES)

+     delete_after_days = wtforms.IntegerField(

+         validators=[wtforms.validators.Optional(),

+                     wtforms.validators.NumberRange(min=-1, max=60)],

+         filters=[(lambda x : -1 if x is None else x)])

  

      # Deprecated, use `enable_net` instead

      build_enable_net = wtforms.BooleanField(validators=[wtforms.validators.Optional()], false_values=FALSE_VALUES)

@@ -370,6 +370,19 @@

              raise exceptions.InsufficientRightsException(

                  "Only owners may delete their projects.")

  

+     @classmethod

+     def delete_expired_projects(cls):

+         query = (

+             models.Copr.query

+             .filter(models.Copr.delete_after.isnot(None))

+             .filter(models.Copr.delete_after < datetime.datetime.now())

+             .filter(models.Copr.deleted.isnot(True))

+         )

+         for copr in query.all():

+             print("deleting project '{}'".format(copr.full_name))

+             CoprsLogic.delete_unsafe(copr.user, copr)

+ 

+ 

  

  class CoprPermissionsLogic(object):

      @classmethod

@@ -256,6 +256,9 @@

      scm_repo_url = db.Column(db.Text)

      scm_api_type = db.Column(db.Text)

  

+     # temporary project if non-null

+     delete_after = db.Column(db.DateTime, index=True, nullable=True)

+ 

      __mapper_args__ = {

          "order_by": created_on.desc()

      }
@@ -470,6 +473,30 @@

      def new_webhook_secret(self):

          self.webhook_secret = str(uuid.uuid4())

  

+     @property

+     def delete_after_days(self):

+         if self.delete_after is None:

+             return None

+ 

+         delta = self.delete_after - datetime.datetime.now()

+         return delta.days if delta.days > 0 else 0

+ 

+     @delete_after_days.setter

+     def delete_after_days(self, days):

+         if days is None or days == -1:

+             self.delete_after = None

+             return

+ 

+         delete_after = datetime.datetime.now() + datetime.timedelta(days=days+1)

+         delete_after = delete_after.replace(hour=0, minute=0, second=0, microsecond=0)

+         self.delete_after = delete_after

+ 

+     @property

+     def delete_after_msg(self):

+         if self.delete_after_days == 0:

+             return "will be deleted ASAP"

+         return "will be deleted after {} days".format(self.delete_after_days)

+ 

  

  class CoprPermission(db.Model, helpers.Serializer):

      """

@@ -34,3 +34,7 @@

      width: 5px;

      display: inline-block;

  }

+ 

+ input.short-input-field {

+     width: 6em;

+ }

@@ -1,4 +1,4 @@

- {% macro render_field(field, label=None, class='', info=None) %}

+ {% macro render_field(field, label=None, class=None, info=None) %}

    {% if not kwargs['hidden'] %}

  

      <div class="form-group {% if field.errors %} has-error {% endif %} ">
@@ -6,7 +6,7 @@

          {{ label or field.label }}:

        </label>

        <div class="col-sm-10">

-         {{ field(class="form-control", **kwargs)|safe }}

+         {{ field(class="form-control" + (" " + class if class else ""), **kwargs)|safe }}

          <ul class="list-unstyled">

          {% if info %}

            {% for line in (info if info is not string else [info]) %}
@@ -30,6 +30,34 @@

    {% endif %}

  {% endmacro %}

  

+ {% macro render_checkbox_field(title, items) %}

+ <div class="form-group">

+   <label class="col-sm-2 control-label" for="textInput-markup">

+     {{ title }}

+   </label>

+   <div class="col-sm-10">

+     {% for tuple in items %}

+       {% set checkbox = tuple[0] %}

+       {% set show     = tuple[1] if tuple|length > 1 else True %}

+       {% if show %}

+     <div class="checkbox">

+       <label>

+         {{ checkbox }}

+         {{ checkbox.label.text }}

+         {% if checkbox.description %}

+         <br>

+         <small class="text-muted pficon pficon-info">

+           {{ checkbox.description }}

+         </small>

+         {% endif %}

+       </label>

+     </div>

+       {% endif %}

+     {% endfor %}

+   </div>

+ </div>

+ {% endmacro %}

+ 

  {% macro render_pypi_python_versions_field(field) %}

    <div class="form-group {% if field.errors %}has-error{% endif %}">

      <label class="col-sm-2 control-label">

@@ -1,4 +1,4 @@

- {% from "_helpers.html" import render_field, render_form_errors, copr_details_href, copr_url %}

+ {% from "_helpers.html" import render_field, render_checkbox_field, render_form_errors, copr_details_href, copr_url %}

  

  {% macro copr_form(form, view, copr = None, username = None, group = None) %}

    {# if using for updating, we need to pass name to url_for, but otherwise we need to pass nothing #}
@@ -21,7 +21,7 @@

  

    <div class="panel panel-default">

      <div class="panel-heading">

-       <h3 class="panel-title">1. Project information</h3>

+       <h3 class="panel-title">{{ counter('instructions') }}. Project information</h3>

      </div>

      <div class="panel-body">

  
@@ -44,13 +44,12 @@

  

      {{ render_field(form.homepage, label='Homepage', placeholder='Optional - project homepage') }}

      {{ render_field(form.contact, label='Contact', placeholder='Optional - email address or contact url', info="Use e-mail address or a link to your issue tracker. This information will be shown publicly.") }}

- 

- 

      </div>

    </div>

+ 

    <div class="panel panel-default">

      <div class="panel-heading">

-       <h3 class="panel-title">2. Build options</h3>

+       <h3 class="panel-title">{{ counter('instructions')}}. Build options</h3>

      </div>

      <div class="panel-body">

  
@@ -110,72 +109,14 @@

        </div>

      </div>

  

- 

-     <div class="form-group">

-       <label class="col-sm-2 control-label" for="textInput-markup">

-         Other Options

-       </label>

-       <div class="col-sm-10">

-         <div class="checkbox">

-           {{ form.disable_createrepo }} Create repositories manually

-           <br>

-           <small class="text-muted pficon pficon-info">

-             Repository meta data is normally refreshed after each build. If you want to do this manually, check the option above.

-           </small>

-         </div>

-         <div class="checkbox">

-           {{ form.build_enable_net }} Enable internet access during builds

-         </div>

-         {% if g.user.admin %}

-         <div class="checkbox">

-           {{ form.auto_prune }} Old builds will be deleted automatically

-           <small class="text-muted pficon pficon-info">

-             Build will be deleted only if there is a newer build (with respect to package version) and it is older than 14 days

-           </small>

-         </div>

-         {% endif %}

-         <div class="checkbox">

-           {{ form.unlisted_on_hp }} Project will not be listed on home page

-         </div>

-         {% if g.user.admin and not copr %} {# the following setting is available only for admins on creating a new project #}

-         <div class="checkbox">

-           {{ form.persistent }} Protect project and its builds against deletion

-           <br>

-           <small class="text-muted pficon pficon-info">

-             Project's builds and the project itself cannot be deleted by any means. This option is set once and for all.

-           </small>

-         </div>

-         {% endif %}

-         {% if g.user.admin and copr %} {# let an admin see the status of the 'Persistent builds' field but do not let him change the preset value #}

-         <div class="checkbox">

-           {{ form.persistent(disabled=True) }} Protect project and its builds against deletion

-           <br/><small class="text-muted pficon pficon-info">

-             This option cannot be altered after a project has been created.

-           </small>

-         </div>

-         {% endif %}

-         <div class="checkbox">

-           {{ form.use_bootstrap_container }} Enable mock's use_bootstrap_container experimental feature

-           <small class="text-muted pficon pficon-info">

-             This will make the build slower but it has an advantage that the dnf _from_ the given chroot will be used to setup the chroot (otherwise host system dnf and rpm is used)

-           </small>

-         </div>

-         <div class="checkbox">

-           {{ form.follow_fedora_branching }} Follow Fedora branching

-           <small class="text-muted pficon pficon-info">

-             When Fedora is branched from rawhide, the respective chroots for the new branch are automatically created for you (as soon as they are available) as rawhide chroot forks.

-           </small>

-         </div>

-       </div>

-     </div>

- 

      </div>

    </div>

  

+ 

    {% if copr is none %}{# we're creating the copr, so display initial builds area #}

    <div class="panel panel-default">

      <div class="panel-heading">

-       <h3 class="panel-title">3. (Optional) initial builds </h3>

+         <h3 class="panel-title">{{ counter('instructions') }}. (Optional) initial builds </h3>

      </div>

      <div class="panel-body">

  
@@ -184,6 +125,32 @@

    </div>

    {% endif %}

  

+ 

+   <div class="panel panel-default">

+     <div class="panel-heading">

+       <h3 class="panel-title">{{ counter('instructions') }}. Other options</h3>

+     </div>

+     <div class="panel-body">

+ 

+     {{ render_checkbox_field(

+         "Booleans", [

+         [form.disable_createrepo],

+         [form.build_enable_net],

+         [form.auto_prune, g.user.admin],

+         [form.unlisted_on_hp],

+         [form.persistent, g.user.admin],

+         [form.use_bootstrap_container],

+         [form.follow_fedora_branching],

+     ])}}

+ 

+     {{ render_field(form.delete_after_days,

+             class="short-input-field",

+             placeholder='Optional',

+             info='Delete the project after the specfied period of time (empty = disabled)') }}

+ 

+     </div>

+   </div>

+ 

      <input class="btn btn-primary" type="submit" value="{% if copr %}Update{% else %}Create{% endif %}">

    </form>

  {% endmacro %}

@@ -1,7 +1,7 @@

  {% extends "layout.html" %}

  {% block title %}Add a Project{% endblock %}

  {% block header %}Add a new Project{% endblock %}

- {% from "coprs/_coprs_forms.html" import copr_form %}

+ {% from "coprs/_coprs_forms.html" import copr_form with context %}

  

  {% block breadcrumbs %}

  <ol class="breadcrumb">

@@ -29,6 +29,9 @@

  <!-- PROJECT NAME -->

  <div style="margin-bottom:22px;margin-top:22px">

    <h1 class="project-name" style="display:inline;">{{ copr_title(copr) }}</h1>

+   {% if copr.delete_after %}

+   <small><i>temporary project: {{ copr.delete_after_msg }}</i></small>

+   {% endif %}

    {% if copr.forked_from %}

    <small><i>( forked from {{ copr_title(copr.forked_from) }})</i></small>

    {% endif %}

@@ -1,5 +1,5 @@

  {% extends "coprs/detail/settings.html" %}

- {% from "coprs/_coprs_forms.html" import copr_form %}

+ {% from "coprs/_coprs_forms.html" import copr_form with context %}

  

  {% set selected_monitor_tab = "edit" %}

  {%block settings_breadcrumb %}Project Details{% endblock %}

@@ -1,7 +1,7 @@

  {% extends "layout.html" %}

  {% block title %}Add a new Group Project{% endblock %}

  {% block header %}Add a new Group Project{% endblock %}

- {% from "coprs/_coprs_forms.html" import copr_form %}

+ {% from "coprs/_coprs_forms.html" import copr_form with context %}

  

  {% block breadcrumbs %}

  <ol class="breadcrumb">

@@ -15,9 +15,14 @@

        {% for copr in coprs %}

          <!--copr-project-->

          <a href="{{ copr_details_href(copr) }}" class="list-group-item">

-           <h3 class="list-group-item-heading">

-             {{ copr_name(copr) }}

+           <div>

+           <h3 class="list-group-item-heading" style="display: inline;">

+               {{ copr_name(copr) }}

            </h3>

+           {% if copr.delete_after %}

+           <small> (temporary project, {{ copr.delete_after_msg }})</small>

+           {% endif %}

+           </div>

            <span class="list-group-item-text">

              {{ copr.description|markdown|remove_anchor|default('Description not filled in by author. Very likely personal repository for testing purpose, which you should not use.', true) }}

              <ul class="list-inline text-muted">

@@ -125,6 +125,7 @@

              homepage=form.homepage.data,

              contact=form.contact.data,

              disable_createrepo=form.disable_createrepo.data,

+             delete_after_days=form.delete_after_days.data,

          )

          db.session.commit()

      except (DuplicateException,

@@ -153,55 +153,21 @@

  @coprs_ns.route("/g/<group_name>/new/", methods=["POST"])

  @login_required

  def copr_new(username=None, group_name=None):

-     if group_name:

-         return process_group_copr_new(group_name)

-     return process_copr_new(username)

- 

- 

- def process_group_copr_new(group_name):

-     group = ComplexLogic.get_group_by_name_safe(group_name)

-     form = forms.CoprFormFactory.create_form_cls(group=group)()

- 

-     if form.validate_on_submit():

-         try:

-             copr = coprs_logic.CoprsLogic.add(

-                 flask.g.user,

-                 name=form.name.data,

-                 homepage=form.homepage.data,

-                 contact=form.contact.data,

-                 repos=form.repos.data.replace("\n", " "),

-                 selected_chroots=form.selected_chroots,

-                 description=form.description.data,

-                 instructions=form.instructions.data,

-                 disable_createrepo=form.disable_createrepo.data,

-                 build_enable_net=form.build_enable_net.data,

-                 unlisted_on_hp=form.unlisted_on_hp.data,

-                 group=group,

-                 persistent=form.persistent.data,

-                 auto_prune=(form.auto_prune.data if flask.g.user.admin else True),

-                 use_bootstrap_container=form.use_bootstrap_container.data,

-                 follow_fedora_branching=form.follow_fedora_branching.data,

-             )

-         except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e:

-             flask.flash(str(e), "error")

-             return flask.render_template("coprs/group_add.html", form=form, group=group)

- 

-         db.session.add(copr)

-         db.session.commit()

-         after_the_project_creation(copr, form)

- 

-         return flask.redirect(url_for_copr_details(copr))

-     else:

-         return flask.render_template("coprs/group_add.html", form=form, group=group)

+     return process_copr_new(group_name)

  

  

- def process_copr_new(username):

+ def process_copr_new(group_name=None):

      """

-     Receive information from the user on how to create its new copr

+     Receive information from the user (and group) on how to create its new copr

      and create it accordingly.

      """

+     group = None

+     redirect = "coprs/add.html"

+     if group_name:

+         group = ComplexLogic.get_group_by_name_safe(group_name)

+         redirect = "coprs/group_add.html"

  

-     form = forms.CoprFormFactory.create_form_cls()()

+     form = forms.CoprFormFactory.create_form_cls(group=group)()

      if form.validate_on_submit():

          try:

              copr = coprs_logic.CoprsLogic.add(
@@ -216,21 +182,23 @@

                  disable_createrepo=form.disable_createrepo.data,

                  build_enable_net=form.build_enable_net.data,

                  unlisted_on_hp=form.unlisted_on_hp.data,

+                 group=group,

                  persistent=form.persistent.data,

                  auto_prune=(form.auto_prune.data if flask.g.user.admin else True),

                  use_bootstrap_container=form.use_bootstrap_container.data,

                  follow_fedora_branching=form.follow_fedora_branching.data,

+                 delete_after_days=form.delete_after_days.data,

              )

          except (exceptions.DuplicateException, exceptions.NonAdminCannotCreatePersistentProject) as e:

              flask.flash(str(e), "error")

-             return flask.render_template("coprs/add.html", form=form)

+             return flask.render_template(redirect, form=form)

  

          db.session.commit()

          after_the_project_creation(copr, form)

  

          return flask.redirect(url_for_copr_details(copr))

      else:

-         return flask.render_template("coprs/add.html", form=form)

+         return flask.render_template(redirect, form=form)

  

  

  def after_the_project_creation(copr, form):
@@ -453,7 +421,7 @@

  def render_copr_edit(copr, form, view):

      if not form:

          form = forms.CoprFormFactory.create_form_cls(

-             copr.mock_chroots)(obj=copr)

+             copr.mock_chroots, copr=copr)(obj=copr)

      return flask.render_template(

          "coprs/detail/settings/edit.html",

          copr=copr, form=form, view=view)
@@ -485,6 +453,7 @@

      copr.unlisted_on_hp = form.unlisted_on_hp.data

      copr.use_bootstrap_container = form.use_bootstrap_container.data

      copr.follow_fedora_branching = form.follow_fedora_branching.data

+     copr.delete_after_days = form.delete_after_days.data

      if flask.g.user.admin:

          copr.auto_prune = form.auto_prune.data

      else:

@@ -40,6 +40,7 @@

      "vacuum_graphs": "RemoveGraphsDataCommand",

      "notify_outdated_chroots": "NotifyOutdatedChrootsCommand",

      "delete_outdated_chroots": "DeleteOutdatedChrootsCommand",

+     "clean_expired_projects": "CleanExpiredProjectsCommand",

  }

  

  if os.getuid() == 0:

@@ -1,4 +1,5 @@

  import json

+ import datetime

  

  from flask_whooshee import Whooshee

  
@@ -80,3 +81,22 @@

          assert data["projectname"] == name

  

  

+     def test_delete_expired_coprs(self, f_users, f_mock_chroots, f_coprs, f_db):

+         query = self.db.session.query(models.Copr)

+ 

+         # nothing is deleted at the beginning

+         assert len([c for c in query.all() if c.deleted]) == 0

+ 

+         # one is to be deleted in the future

+         self.c1.delete_after_days = 2

+         # one is already to be deleted

+         self.c2.delete_after = datetime.datetime.now() - datetime.timedelta(days=1)

+ 

+         # and one is not to be temporary at all (c3)

+ 

+         CoprsLogic.delete_expired_projects()

+         self.db.session.commit()

+ 

+         query = self.db.session.query(models.Copr)

+         assert len(query.all()) == 3 # we only set deleted=true

+         assert len([c for c in query.all() if c.deleted]) == 1

@@ -59,7 +59,8 @@

  

      def add(self, ownername, projectname, chroots, description=None, instructions=None, homepage=None,

              contact=None, additional_repos=None, unlisted_on_hp=False, enable_net=True, persistent=False,

-             auto_prune=True, use_bootstrap_container=False, devel_mode=False):

+             auto_prune=True, use_bootstrap_container=False, devel_mode=False,

+             delete_after_days=None):

          """

          Create a project

  
@@ -77,6 +78,7 @@

          :param bool auto_prune: if backend auto-deletion script should be run for the project

          :param bool use_bootstrap_container: if mock bootstrap container is used to initialize the buildroot

          :param bool devel_mode: if createrepo should run automatically

+         :param int delete_after_days: delete the project after the specfied period of time

          :return: Munch

          """

          endpoint = "/project/add/{ownername}"
@@ -97,6 +99,7 @@

              "auto_prune": auto_prune,

              "use_bootstrap_container": use_bootstrap_container,

              "devel_mode": devel_mode,

+             "delete_after_days": delete_after_days,

          }

          request = Request(endpoint, api_base_url=self.api_base_url, method=POST,

                            params=params, data=data, auth=self.auth)
@@ -105,7 +108,8 @@

  

      def edit(self, ownername, projectname, chroots=None, description=None, instructions=None, homepage=None,

               contact=None, additional_repos=None, unlisted_on_hp=None, enable_net=None,

-              auto_prune=None, use_bootstrap_container=None, devel_mode=None):

+              auto_prune=None, use_bootstrap_container=None, devel_mode=None,

+              delete_after_days=None):

          """

          Edit a project

  
@@ -122,6 +126,7 @@

          :param bool auto_prune: if backend auto-deletion script should be run for the project

          :param bool use_bootstrap_container: if mock bootstrap container is used to initialize the buildroot

          :param bool devel_mode: if createrepo should run automatically

+         :param int delete_after_days: delete the project after the specfied period of time

          :return: Munch

          """

          endpoint = "/project/edit/{ownername}/{projectname}"
@@ -141,6 +146,7 @@

              "auto_prune": auto_prune,

              "use_bootstrap_container": use_bootstrap_container,

              "devel_mode": devel_mode,

+             "delete_after_days": delete_after_days,

          }

          request = Request(endpoint, api_base_url=self.api_base_url, method=POST,

                            params=params, data=data, auth=self.auth)

rebased onto 48a3c3dc053ce8e654354261e8f08167526e9ebc

5 years ago

Can you change this to: disable=method-hidden
For me - as pure human - this is more readable :)

The label says Delete after days; I'll remove the - days entirely.

rebased onto ca69f35fb74c68053749c7c259b171debaf1856c

5 years ago

Btw., this PR doesn't have tag=review yet; I'll update it once I have some testing in place. And I'll also upload the screenshots (hope that it works in pagure, somehow).

rebased onto 8d9ef7e95fc47967dd283b2ef91a3e0336cd7f6c

5 years ago

After chat with Mirek I moved the new "input" field to the bottom of the page, and created a new section with non-build related options. See the screenshots:
https://praiskup.fedorapeople.org/pull-requets/copr/649/

Metadata Update from @praiskup:
- Pull-request tagged with: review

5 years ago
$ copr create --help
...
  --delete-after-days DAYS
                        Delete the project after the specfied period of time

$ copr modify --help
...
  --delete-after-days DAYS
                        Delete the project after the specfied period of time,
                        empty or -1 disables, (default is "don't change")

rebased onto 1a948ea9224808976cb54bb74340cbce522ed801

5 years ago

rebased onto 983b65dd46cdc3d44dd870e260edae02ccfb0485

5 years ago

Can you change this to: disable=method-hidden
For me - as pure human - this is more readable :)

Fixed.

Maybe "number of days" ?

I changed this, see the screenshots.

rebased onto b7c0cb9b072207264a1f665b05654d7cfa18513d

5 years ago

@msuchy, rebased (updated down_revision for private tables change).

rebased onto d7615a6

5 years ago

pagure strikes, committed manually as
diff 073b6d6..3e7c629

Pull-Request has been closed by praiskup

5 years ago
Metadata
Changes Summary 22
+8 -0
file changed
cli/copr_cli/main.py
+2 -1
file changed
cli/tests/test_cli.py
+9 -0
file changed
doc/user_documentation.rst
+1 -0
file changed
frontend/conf/cron.daily/copr-frontend
+21
file added
frontend/coprs_frontend/alembic/schema/versions/b828274ddebf_temporary_project.py
+15
file added
frontend/coprs_frontend/commands/clean_expired_projects.py
+73 -9
file changed
frontend/coprs_frontend/coprs/forms.py
+13 -0
file changed
frontend/coprs_frontend/coprs/logic/coprs_logic.py
+27 -0
file changed
frontend/coprs_frontend/coprs/models.py
+4 -0
file changed
frontend/coprs_frontend/coprs/static/css/custom-styles.css
+30 -2
file changed
frontend/coprs_frontend/coprs/templates/_helpers.html
+32 -65
file changed
frontend/coprs_frontend/coprs/templates/coprs/_coprs_forms.html
+1 -1
file changed
frontend/coprs_frontend/coprs/templates/coprs/add.html
+3 -0
file changed
frontend/coprs_frontend/coprs/templates/coprs/detail.html
+1 -1
file changed
frontend/coprs_frontend/coprs/templates/coprs/detail/settings/edit.html
+1 -1
file changed
frontend/coprs_frontend/coprs/templates/coprs/group_add.html
+7 -2
file changed
frontend/coprs_frontend/coprs/templates/coprs/show.html
+1 -0
file changed
frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py
+15 -46
file changed
frontend/coprs_frontend/coprs/views/coprs_ns/coprs_general.py
+1 -0
file changed
frontend/coprs_frontend/manage.py
+20 -0
file changed
frontend/coprs_frontend/tests/test_logic/test_coprs_logic.py
+8 -2
file changed
python/copr/v3/proxies/project.py