From 8227721e321ee854f9d1610c86d925e25154bfd7 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Aug 2023 16:11:18 +0200 Subject: [PATCH 1/9] Deprecation: notification and feature flag for `build.image` config Define a weekly task to communicate our users about the deprecation of `build.image` using the deprecation plan we used for the configuration file v2 as well. - 3 brownout days - final removal date on October 2nd - weekly onsite/email notification on Wednesday at 11:15 CEST (around 3.5k projects affected) - allow to opt-out from these emails - feature flag for brownout days - build detail's page notification Related: * https://github.com/readthedocs/meta/discussions/48 * https://github.com/readthedocs/readthedocs.org/pull/10354 * https://github.com/readthedocs/readthedocs.org/issues/10587 --- readthedocs/builds/models.py | 14 ++ ...14_optout_email_build_image_deprecation.py | 30 ++++ readthedocs/core/models.py | 7 + readthedocs/doc_builder/director.py | 6 + readthedocs/doc_builder/exceptions.py | 5 + readthedocs/projects/models.py | 7 + readthedocs/projects/tasks/utils.py | 156 ++++++++++++++++++ readthedocs/settings/base.py | 5 + .../templates/builds/build_detail.html | 26 ++- .../deprecated_build_image_used_email.html | 1 + .../deprecated_build_image_used_email.txt | 31 ++++ 11 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 readthedocs/core/migrations/0014_optout_email_build_image_deprecation.py create mode 100644 readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html create mode 100644 readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 57166330510..80b138262cd 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -1104,6 +1104,20 @@ def deprecated_config_used(self): return int(self.config.get("version", "1")) != LATEST_CONFIGURATION_VERSION + def deprecated_build_image_used(self): + """ + Check whether this particular build is using the deprecated "build.image" config. + + Note we are using this to communicate deprecation of "build.image". + See https://github.com/readthedocs/meta/discussions/48 + """ + if not self.config: + # Don't notify users without a config file. + # We hope they will migrate to `build.os` in the process of adding a `.readthedocs.yaml` + return False + + return "image" in self.config.get("build", {}) + def reset(self): """ Reset the build so it can be re-used when re-trying. diff --git a/readthedocs/core/migrations/0014_optout_email_build_image_deprecation.py b/readthedocs/core/migrations/0014_optout_email_build_image_deprecation.py new file mode 100644 index 00000000000..5d2d94dcb9a --- /dev/null +++ b/readthedocs/core/migrations/0014_optout_email_build_image_deprecation.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.20 on 2023-08-01 13:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0013_add_optout_email_config_file_deprecation"), + ] + + operations = [ + migrations.AddField( + model_name="historicaluserprofile", + name="optout_email_build_image_deprecation", + field=models.BooleanField( + default=False, + null=True, + verbose_name="Opt-out from email about '\"build.image\" config key deprecation'", + ), + ), + migrations.AddField( + model_name="userprofile", + name="optout_email_build_image_deprecation", + field=models.BooleanField( + default=False, + null=True, + verbose_name="Opt-out from email about '\"build.image\" config key deprecation'", + ), + ), + ] diff --git a/readthedocs/core/models.py b/readthedocs/core/models.py index e476bd4f2f4..daf25cec286 100644 --- a/readthedocs/core/models.py +++ b/readthedocs/core/models.py @@ -51,6 +51,13 @@ class UserProfile(TimeStampedModel): default=False, null=True, ) + # NOTE: this is a temporary field that we can remove after October 16, 2023 + # See https://blog.readthedocs.com/build-image-config-deprecated/ + optout_email_build_image_deprecation = models.BooleanField( + _("Opt-out from email about '\"build.image\" config key deprecation'"), + default=False, + null=True, + ) # Model history history = ExtraHistoricalRecords() diff --git a/readthedocs/doc_builder/director.py b/readthedocs/doc_builder/director.py index e94b391b957..2d1370d225a 100644 --- a/readthedocs/doc_builder/director.py +++ b/readthedocs/doc_builder/director.py @@ -255,6 +255,12 @@ def checkout(self): ) and self.data.config.version not in ("2", 2): raise BuildUserError(BuildUserError.NO_CONFIG_FILE_DEPRECATED) + # Raise a build error if the project is using "build.image" on their config file + if self.data.project.has_feature( + Feature.BUILD_IMAGE_CONFIG_KEY_DEPRECATED + ) and "image" in self.data.config.source_config.get("build", {}): + raise BuildUserError(BuildUserError.BUILD_IMAGE_CONFIG_KEY_DEPRECATED) + if self.vcs_repository.supports_submodules: self.vcs_repository.update_submodules(self.data.config) diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index 11ea377ce9d..47dfad3fa75 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -66,6 +66,11 @@ class BuildUserError(BuildBaseException): "Add a configuration file to your project to make it build successfully. " "Read more at https://docs.readthedocs.io/en/stable/config-file/v2.html" ) + BUILD_IMAGE_CONFIG_KEY_DEPRECATED = gettext_noop( + 'The configuration key "build.image" is deprecated. ' + 'Please, use "build.os" instead to make it build successfully. ' + "Read more at https://docs.readthedocs.io/en/stable/config-file/v2.html" + ) class BuildUserSkip(BuildUserError): diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 834f55fa4b5..b2492b55d87 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1950,6 +1950,7 @@ def add_features(sender, **kwargs): GIT_CLONE_FETCH_CHECKOUT_PATTERN = "git_clone_fetch_checkout_pattern" HOSTING_INTEGRATIONS = "hosting_integrations" NO_CONFIG_FILE_DEPRECATED = "no_config_file" + BUILD_IMAGE_CONFIG_KEY_DEPRECATED = "build_image_config_key_deprecated" SCALE_IN_PROTECTION = "scale_in_prtection" FEATURES = ( @@ -2093,6 +2094,12 @@ def add_features(sender, **kwargs): NO_CONFIG_FILE_DEPRECATED, _("Build: Building without a configuration file is deprecated."), ), + ( + BUILD_IMAGE_CONFIG_KEY_DEPRECATED, + _( + 'Build: Building using "build.image" in the configuration file is deprecated.' + ), + ), ( SCALE_IN_PROTECTION, _("Build: Set scale-in protection before/after building."), diff --git a/readthedocs/projects/tasks/utils.py b/readthedocs/projects/tasks/utils.py index 09572c5d00f..d7b4d424517 100644 --- a/readthedocs/projects/tasks/utils.py +++ b/readthedocs/projects/tasks/utils.py @@ -323,6 +323,162 @@ def deprecated_config_file_used_notification(): ) +class DeprecatedBuildImageSiteNotification(SiteNotification): + failure_message = _( + 'Your project(s) "{{ project_slugs }}" are using the deprecated "build.image" ' + 'config on their ".readthedocs.yaml" file. ' + 'This config is deprecated in favor of "build.os" and will be removed on October 16, 2023. ' # noqa + 'Read our blog post to migrate to "build.os" ' # noqa + "and ensure your project continues building successfully." + ) + failure_level = WARNING_PERSISTENT + + +class DeprecatedBuildImageEmailNotification(Notification): + app_templates = "projects" + name = "deprecated_build_image_used" + subject = '[Action required] Update your ".readthedocs.yaml" file to use "build.os"' + level = REQUIREMENT + + def send(self): + """Method overwritten to remove on-site backend.""" + backend = EmailBackend(self.request) + backend.send(self) + + +@app.task(queue="web") +def deprecated_build_image_notification(): + """ + Send an email notification about using "build.image" to all maintainers of the project. + + This is a scheduled task to be executed on the webs. + Note the code uses `.iterator` and `.only` to avoid killing the db with this query. + Besdies, it excludes projects with enough spam score to be skipped. + """ + # Skip projects with a spam score bigger than this value. + # Currently, this gives us ~250k in total (from ~550k we have in our database) + spam_score = 300 + + projects = set() + start_datetime = datetime.datetime.now() + queryset = Project.objects.exclude(users__profile__banned=True) + if settings.ALLOW_PRIVATE_REPOS: + # Only send emails to active customers + queryset = queryset.filter( + organizations__stripe_subscription__status=SubscriptionStatus.active + ) + else: + # Take into account spam score on community + queryset = queryset.annotate(spam_score=Sum("spam_rules__value")).filter( + Q(spam_score__lt=spam_score) | Q(is_spam=False) + ) + queryset = queryset.only("slug", "default_version").order_by("id") + n_projects = queryset.count() + + for i, project in enumerate(queryset.iterator()): + if i % 500 == 0: + log.info( + 'Finding projects using "build.image" config key.', + progress=f"{i}/{n_projects}", + current_project_pk=project.pk, + current_project_slug=project.slug, + projects_found=len(projects), + time_elapsed=(datetime.datetime.now() - start_datetime).seconds, + ) + + # Only check for the default version because if the project is using tags + # they won't be able to update those and we will send them emails forever. + # We can update this query if we consider later. + version = ( + project.versions.filter(slug=project.default_version).only("id").first() + ) + if version: + # Use a fixed date here to avoid changing the date on each run + years_ago = timezone.datetime(2022, 8, 1) + build = ( + version.builds.filter(success=True, date__gt=years_ago) + .only("_config") + .order_by("-date") + .first() + ) + # TODO: uncomment this line before merging + # if build and build.deprecated_build_image_used(): + if build and "image" in build.config.get("build", {}): + projects.add(project.slug) + + # Store all the users we want to contact + users = set() + + n_projects = len(projects) + queryset = Project.objects.filter(slug__in=projects).order_by("id") + for i, project in enumerate(queryset.iterator()): + if i % 500 == 0: + log.info( + 'Querying all the users we want to contact about "build.image" deprecation.', + progress=f"{i}/{n_projects}", + current_project_pk=project.pk, + current_project_slug=project.slug, + users_found=len(users), + time_elapsed=(datetime.datetime.now() - start_datetime).seconds, + ) + + users.update(AdminPermission.owners(project).values_list("username", flat=True)) + + # Only send 1 email per user, + # even if that user has multiple projects using "build.image". + # The notification will mention all the projects. + queryset = User.objects.filter( + username__in=users, + profile__banned=False, + profile__optout_email_build_image_deprecation=False, + ).order_by("id") + + n_users = queryset.count() + for i, user in enumerate(queryset.iterator()): + if i % 500 == 0: + log.info( + 'Sending deprecated "build.image" config key notification to users.', + progress=f"{i}/{n_users}", + current_user_pk=user.pk, + current_user_username=user.username, + time_elapsed=(datetime.datetime.now() - start_datetime).seconds, + ) + + # All the projects for this user that are using "build.image". + # Use set() intersection in Python that's pretty quick since we only need the slugs. + # Otherwise we have to pass 82k slugs to the DB query, which makes it pretty slow. + user_projects = AdminPermission.projects(user, admin=True).values_list( + "slug", flat=True + ) + user_projects_slugs = list(set(user_projects) & projects) + user_projects = Project.objects.filter(slug__in=user_projects_slugs) + + # Create slug string for onsite notification + user_project_slugs = ", ".join(user_projects_slugs[:5]) + if len(user_projects) > 5: + user_project_slugs += " and others..." + + n_site = DeprecatedBuildImageSiteNotification( + user=user, + context_object=user, + extra_context={"project_slugs": user_project_slugs}, + success=False, + ) + n_site.send() + + n_email = DeprecatedBuildImageEmailNotification( + user=user, + context_object=user, + extra_context={"projects": user_projects}, + ) + n_email.send() + + log.info( + 'Finish sending deprecated "build.image" config key notifications.', + time_elapsed=(datetime.datetime.now() - start_datetime).seconds, + ) + + @app.task(queue="web") def set_builder_scale_in_protection(instance, protected_from_scale_in): """ diff --git a/readthedocs/settings/base.py b/readthedocs/settings/base.py index 604c88f2564..6cd34fc8aac 100644 --- a/readthedocs/settings/base.py +++ b/readthedocs/settings/base.py @@ -536,6 +536,11 @@ def TEMPLATES(self): 'schedule': crontab(day_of_week='wednesday', hour=11, minute=15), 'options': {'queue': 'web'}, }, + 'weekly-build-image-notification': { + 'task': 'readthedocs.projects.tasks.utils.deprecated_build_image_notification', + 'schedule': crontab(day_of_week='wednesday', hour=9, minute=15), + 'options': {'queue': 'web'}, + }, } # Sentry diff --git a/readthedocs/templates/builds/build_detail.html b/readthedocs/templates/builds/build_detail.html index 17bf9424074..5bc6428f140 100644 --- a/readthedocs/templates/builds/build_detail.html +++ b/readthedocs/templates/builds/build_detail.html @@ -163,17 +163,29 @@ {% endif %} {# This message is not dynamic and only appears when loading the page after the build has finished #} - {% if build.finished and build.deprecated_config_used %} -
+ {% if build.finished and build.deprecated_build_image_used %} +

- {% blocktrans trimmed with config_file_link="https://blog.readthedocs.com/migrate-configuration-v2/" %} + {% blocktrans trimmed with config_file_link="https://blog.readthedocs.com/build-image-config-deprecated/" %} Your builds will stop working soon!
- Configuration files will soon be required by projects, and will no longer be optional. - Read our blog post to create one + "build.image" config key is deprecated and it will be removed soon. + Read our blog post to know how to migrate to new key "build.os" and ensure your project continues building successfully. - {% endblocktrans %} + {% endblocktrans %}

-
+
+ {% endif %} + {% if build.finished and build.deprecated_config_used %} +
+

+ {% blocktrans trimmed with config_file_link="https://blog.readthedocs.com/migrate-configuration-v2/" %} + Your builds will stop working soon!
+ Configuration files will soon be required by projects, and will no longer be optional. + Read our blog post to create one + and ensure your project continues building successfully. + {% endblocktrans %} +

+
{% endif %} {% endif %} diff --git a/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html new file mode 100644 index 00000000000..deaf0b3fa8c --- /dev/null +++ b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html @@ -0,0 +1 @@ +{# TODO: copy the text from the TXT version once we agree on its content #} diff --git a/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt new file mode 100644 index 00000000000..495e6ac936a --- /dev/null +++ b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt @@ -0,0 +1,31 @@ +{% extends "core/email/common.txt" %} +{% block content %} +The Read the Docs build system is deprecating "build.image" config key on ".readthedocs.yaml" starting on October 16, 2023. +We are sending weekly notifications about this issue to all impacted users, +as well as temporary build failures (brownouts) as the date approaches for those who haven't migrated their projects. + +The timeline for this deprecation is as follows: + +* Monday, August 28, 2023: Do the first brownout (temporarily enforce this deprecation) for 12 hours: 00:01 PST to 11:59 PST (noon) +* Monday, September 18, 2023: Do a second brownout (temporarily enforce this deprecation) for 24 hours: 00:01 PST to 23:59 PST (midnight) +* Monday, October 2, 2023: Do a third and final brownout (temporarily enforce this deprecation) for 48 hours: 00:01 PST to October 3, 2023 23:59 PST (midnight) +* Monday, October 16, 2023: Fully remove support for building documentation using "build.image" on the configuration file + +We have identified that the following projects which you maintain, and were built in the last year, are impacted by this deprecation: + +{% for project in projects|slice:":15" %} +* {{ project.slug }} ({{ production_uri }}{{ project.get_absolute_url }}) +{% endfor %} +{% if projects.count > 15 %} +* ... and {{ projects.count|add:"-15" }} more projects. +{% endif %} + +Please use "build.os" on your configuration file to ensure that they continue building successfully and to stop receiving these notifications. +If you want to opt-out from these emails, you can edit your preferences in your account settings, at https://readthedocs.org/accounts/edit/. + +For more information on how to use "build.os", +read our blog post at https://blog.readthedocs.com/build-image-config-deprecated/ + +Get in touch with us via our support ({{ production_uri }}{% url 'support' %}) +and let us know if you are unable to use a configuration file for any reason. +{% endblock %} From b1810c3b0e70702da5fec8dd7960faefc9e69fad Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 1 Aug 2023 16:11:18 +0200 Subject: [PATCH 2/9] Deprecation: notification and feature flag for `build.image` config Define a weekly task to communicate our users about the deprecation of `build.image` using the deprecation plan we used for the configuration file v2 as well. - 3 brownout days - final removal date on October 2nd - weekly onsite/email notification on Wednesday at 11:15 CEST (around ~22k projects affected) - allow to opt-out from these emails - feature flag for brownout days - build detail's page notification Related: * https://github.com/readthedocs/meta/discussions/48 * https://github.com/readthedocs/readthedocs.org/pull/10354 * https://github.com/readthedocs/readthedocs.org/issues/10587 --- readthedocs/builds/models.py | 15 ++ readthedocs/core/forms.py | 5 +- ...14_optout_email_build_image_deprecation.py | 30 ++++ readthedocs/core/models.py | 7 + readthedocs/doc_builder/director.py | 10 ++ readthedocs/doc_builder/exceptions.py | 9 + readthedocs/projects/models.py | 7 + readthedocs/projects/tasks/utils.py | 156 ++++++++++++++++++ readthedocs/settings/base.py | 5 + .../templates/builds/build_detail.html | 26 ++- .../deprecated_build_image_used_email.html | 1 + .../deprecated_build_image_used_email.txt | 31 ++++ 12 files changed, 294 insertions(+), 8 deletions(-) create mode 100644 readthedocs/core/migrations/0014_optout_email_build_image_deprecation.py create mode 100644 readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html create mode 100644 readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 57166330510..872d80490ea 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -1104,6 +1104,21 @@ def deprecated_config_used(self): return int(self.config.get("version", "1")) != LATEST_CONFIGURATION_VERSION + def deprecated_build_image_used(self): + """ + Check whether this particular build is using the deprecated "build" config. + + Note we are using this to communicate deprecation of "build.image". + See https://github.com/readthedocs/meta/discussions/48 + """ + if not self.config: + # Don't notify users without a config file. + # We hope they will migrate to `build.os` in the process of adding a `.readthedocs.yaml` + return False + + build_config_key = self.config.get("build", {}) + return "image" in build_config_key or "os" not in build_config_key + def reset(self): """ Reset the build so it can be re-used when re-trying. diff --git a/readthedocs/core/forms.py b/readthedocs/core/forms.py index 32eaef7a471..9ea8dba507b 100644 --- a/readthedocs/core/forms.py +++ b/readthedocs/core/forms.py @@ -23,7 +23,10 @@ class Meta: model = UserProfile # Don't allow users edit someone else's user page profile_fields = ["first_name", "last_name", "homepage"] - optout_email_fields = ["optout_email_config_file_deprecation"] + optout_email_fields = [ + "optout_email_config_file_deprecation", + "optout_email_build_image_deprecation", + ] fields = ( *profile_fields, *optout_email_fields, diff --git a/readthedocs/core/migrations/0014_optout_email_build_image_deprecation.py b/readthedocs/core/migrations/0014_optout_email_build_image_deprecation.py new file mode 100644 index 00000000000..5d2d94dcb9a --- /dev/null +++ b/readthedocs/core/migrations/0014_optout_email_build_image_deprecation.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.20 on 2023-08-01 13:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0013_add_optout_email_config_file_deprecation"), + ] + + operations = [ + migrations.AddField( + model_name="historicaluserprofile", + name="optout_email_build_image_deprecation", + field=models.BooleanField( + default=False, + null=True, + verbose_name="Opt-out from email about '\"build.image\" config key deprecation'", + ), + ), + migrations.AddField( + model_name="userprofile", + name="optout_email_build_image_deprecation", + field=models.BooleanField( + default=False, + null=True, + verbose_name="Opt-out from email about '\"build.image\" config key deprecation'", + ), + ), + ] diff --git a/readthedocs/core/models.py b/readthedocs/core/models.py index e476bd4f2f4..daf25cec286 100644 --- a/readthedocs/core/models.py +++ b/readthedocs/core/models.py @@ -51,6 +51,13 @@ class UserProfile(TimeStampedModel): default=False, null=True, ) + # NOTE: this is a temporary field that we can remove after October 16, 2023 + # See https://blog.readthedocs.com/build-image-config-deprecated/ + optout_email_build_image_deprecation = models.BooleanField( + _("Opt-out from email about '\"build.image\" config key deprecation'"), + default=False, + null=True, + ) # Model history history = ExtraHistoricalRecords() diff --git a/readthedocs/doc_builder/director.py b/readthedocs/doc_builder/director.py index e94b391b957..3b9a385320d 100644 --- a/readthedocs/doc_builder/director.py +++ b/readthedocs/doc_builder/director.py @@ -255,6 +255,16 @@ def checkout(self): ) and self.data.config.version not in ("2", 2): raise BuildUserError(BuildUserError.NO_CONFIG_FILE_DEPRECATED) + # Raise a build error if the project is using "build.image" on their config file + if self.data.project.has_feature(Feature.BUILD_IMAGE_CONFIG_KEY_DEPRECATED): + build_config_key = self.data.config.source_config.get("build", {}) + if "image" in build_config_key: + raise BuildUserError(BuildUserError.BUILD_IMAGE_CONFIG_KEY_DEPRECATED) + + # TODO: move this validation to the Config object once we are settled here + if "image" not in build_config_key and "os" not in build_config_key: + raise BuildUserError(BuildUserError.BUILD_OS_REQUIRED) + if self.vcs_repository.supports_submodules: self.vcs_repository.update_submodules(self.data.config) diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index 11ea377ce9d..787b0c90ec4 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -66,6 +66,15 @@ class BuildUserError(BuildBaseException): "Add a configuration file to your project to make it build successfully. " "Read more at https://docs.readthedocs.io/en/stable/config-file/v2.html" ) + BUILD_IMAGE_CONFIG_KEY_DEPRECATED = gettext_noop( + 'The configuration key "build.image" is deprecated. ' + 'Please, use "build.os" instead to make it build successfully. ' + "Read more at https://docs.readthedocs.io/en/stable/config-file/v2.html" + ) + BUILD_OS_REQUIRED = gettext_noop( + 'The configuration key "build.os" is required to build your documentation. ' + "Read more at https://docs.readthedocs.io/en/stable/config-file/v2.html" + ) class BuildUserSkip(BuildUserError): diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 834f55fa4b5..b2492b55d87 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1950,6 +1950,7 @@ def add_features(sender, **kwargs): GIT_CLONE_FETCH_CHECKOUT_PATTERN = "git_clone_fetch_checkout_pattern" HOSTING_INTEGRATIONS = "hosting_integrations" NO_CONFIG_FILE_DEPRECATED = "no_config_file" + BUILD_IMAGE_CONFIG_KEY_DEPRECATED = "build_image_config_key_deprecated" SCALE_IN_PROTECTION = "scale_in_prtection" FEATURES = ( @@ -2093,6 +2094,12 @@ def add_features(sender, **kwargs): NO_CONFIG_FILE_DEPRECATED, _("Build: Building without a configuration file is deprecated."), ), + ( + BUILD_IMAGE_CONFIG_KEY_DEPRECATED, + _( + 'Build: Building using "build.image" in the configuration file is deprecated.' + ), + ), ( SCALE_IN_PROTECTION, _("Build: Set scale-in protection before/after building."), diff --git a/readthedocs/projects/tasks/utils.py b/readthedocs/projects/tasks/utils.py index 09572c5d00f..d7b4d424517 100644 --- a/readthedocs/projects/tasks/utils.py +++ b/readthedocs/projects/tasks/utils.py @@ -323,6 +323,162 @@ def deprecated_config_file_used_notification(): ) +class DeprecatedBuildImageSiteNotification(SiteNotification): + failure_message = _( + 'Your project(s) "{{ project_slugs }}" are using the deprecated "build.image" ' + 'config on their ".readthedocs.yaml" file. ' + 'This config is deprecated in favor of "build.os" and will be removed on October 16, 2023. ' # noqa + 'Read our blog post to migrate to "build.os" ' # noqa + "and ensure your project continues building successfully." + ) + failure_level = WARNING_PERSISTENT + + +class DeprecatedBuildImageEmailNotification(Notification): + app_templates = "projects" + name = "deprecated_build_image_used" + subject = '[Action required] Update your ".readthedocs.yaml" file to use "build.os"' + level = REQUIREMENT + + def send(self): + """Method overwritten to remove on-site backend.""" + backend = EmailBackend(self.request) + backend.send(self) + + +@app.task(queue="web") +def deprecated_build_image_notification(): + """ + Send an email notification about using "build.image" to all maintainers of the project. + + This is a scheduled task to be executed on the webs. + Note the code uses `.iterator` and `.only` to avoid killing the db with this query. + Besdies, it excludes projects with enough spam score to be skipped. + """ + # Skip projects with a spam score bigger than this value. + # Currently, this gives us ~250k in total (from ~550k we have in our database) + spam_score = 300 + + projects = set() + start_datetime = datetime.datetime.now() + queryset = Project.objects.exclude(users__profile__banned=True) + if settings.ALLOW_PRIVATE_REPOS: + # Only send emails to active customers + queryset = queryset.filter( + organizations__stripe_subscription__status=SubscriptionStatus.active + ) + else: + # Take into account spam score on community + queryset = queryset.annotate(spam_score=Sum("spam_rules__value")).filter( + Q(spam_score__lt=spam_score) | Q(is_spam=False) + ) + queryset = queryset.only("slug", "default_version").order_by("id") + n_projects = queryset.count() + + for i, project in enumerate(queryset.iterator()): + if i % 500 == 0: + log.info( + 'Finding projects using "build.image" config key.', + progress=f"{i}/{n_projects}", + current_project_pk=project.pk, + current_project_slug=project.slug, + projects_found=len(projects), + time_elapsed=(datetime.datetime.now() - start_datetime).seconds, + ) + + # Only check for the default version because if the project is using tags + # they won't be able to update those and we will send them emails forever. + # We can update this query if we consider later. + version = ( + project.versions.filter(slug=project.default_version).only("id").first() + ) + if version: + # Use a fixed date here to avoid changing the date on each run + years_ago = timezone.datetime(2022, 8, 1) + build = ( + version.builds.filter(success=True, date__gt=years_ago) + .only("_config") + .order_by("-date") + .first() + ) + # TODO: uncomment this line before merging + # if build and build.deprecated_build_image_used(): + if build and "image" in build.config.get("build", {}): + projects.add(project.slug) + + # Store all the users we want to contact + users = set() + + n_projects = len(projects) + queryset = Project.objects.filter(slug__in=projects).order_by("id") + for i, project in enumerate(queryset.iterator()): + if i % 500 == 0: + log.info( + 'Querying all the users we want to contact about "build.image" deprecation.', + progress=f"{i}/{n_projects}", + current_project_pk=project.pk, + current_project_slug=project.slug, + users_found=len(users), + time_elapsed=(datetime.datetime.now() - start_datetime).seconds, + ) + + users.update(AdminPermission.owners(project).values_list("username", flat=True)) + + # Only send 1 email per user, + # even if that user has multiple projects using "build.image". + # The notification will mention all the projects. + queryset = User.objects.filter( + username__in=users, + profile__banned=False, + profile__optout_email_build_image_deprecation=False, + ).order_by("id") + + n_users = queryset.count() + for i, user in enumerate(queryset.iterator()): + if i % 500 == 0: + log.info( + 'Sending deprecated "build.image" config key notification to users.', + progress=f"{i}/{n_users}", + current_user_pk=user.pk, + current_user_username=user.username, + time_elapsed=(datetime.datetime.now() - start_datetime).seconds, + ) + + # All the projects for this user that are using "build.image". + # Use set() intersection in Python that's pretty quick since we only need the slugs. + # Otherwise we have to pass 82k slugs to the DB query, which makes it pretty slow. + user_projects = AdminPermission.projects(user, admin=True).values_list( + "slug", flat=True + ) + user_projects_slugs = list(set(user_projects) & projects) + user_projects = Project.objects.filter(slug__in=user_projects_slugs) + + # Create slug string for onsite notification + user_project_slugs = ", ".join(user_projects_slugs[:5]) + if len(user_projects) > 5: + user_project_slugs += " and others..." + + n_site = DeprecatedBuildImageSiteNotification( + user=user, + context_object=user, + extra_context={"project_slugs": user_project_slugs}, + success=False, + ) + n_site.send() + + n_email = DeprecatedBuildImageEmailNotification( + user=user, + context_object=user, + extra_context={"projects": user_projects}, + ) + n_email.send() + + log.info( + 'Finish sending deprecated "build.image" config key notifications.', + time_elapsed=(datetime.datetime.now() - start_datetime).seconds, + ) + + @app.task(queue="web") def set_builder_scale_in_protection(instance, protected_from_scale_in): """ diff --git a/readthedocs/settings/base.py b/readthedocs/settings/base.py index 604c88f2564..6cd34fc8aac 100644 --- a/readthedocs/settings/base.py +++ b/readthedocs/settings/base.py @@ -536,6 +536,11 @@ def TEMPLATES(self): 'schedule': crontab(day_of_week='wednesday', hour=11, minute=15), 'options': {'queue': 'web'}, }, + 'weekly-build-image-notification': { + 'task': 'readthedocs.projects.tasks.utils.deprecated_build_image_notification', + 'schedule': crontab(day_of_week='wednesday', hour=9, minute=15), + 'options': {'queue': 'web'}, + }, } # Sentry diff --git a/readthedocs/templates/builds/build_detail.html b/readthedocs/templates/builds/build_detail.html index 17bf9424074..5bc6428f140 100644 --- a/readthedocs/templates/builds/build_detail.html +++ b/readthedocs/templates/builds/build_detail.html @@ -163,17 +163,29 @@ {% endif %} {# This message is not dynamic and only appears when loading the page after the build has finished #} - {% if build.finished and build.deprecated_config_used %} -
+ {% if build.finished and build.deprecated_build_image_used %} +

- {% blocktrans trimmed with config_file_link="https://blog.readthedocs.com/migrate-configuration-v2/" %} + {% blocktrans trimmed with config_file_link="https://blog.readthedocs.com/build-image-config-deprecated/" %} Your builds will stop working soon!
- Configuration files will soon be required by projects, and will no longer be optional. - Read our blog post to create one + "build.image" config key is deprecated and it will be removed soon. + Read our blog post to know how to migrate to new key "build.os" and ensure your project continues building successfully. - {% endblocktrans %} + {% endblocktrans %}

-
+
+ {% endif %} + {% if build.finished and build.deprecated_config_used %} +
+

+ {% blocktrans trimmed with config_file_link="https://blog.readthedocs.com/migrate-configuration-v2/" %} + Your builds will stop working soon!
+ Configuration files will soon be required by projects, and will no longer be optional. + Read our blog post to create one + and ensure your project continues building successfully. + {% endblocktrans %} +

+
{% endif %} {% endif %} diff --git a/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html new file mode 100644 index 00000000000..deaf0b3fa8c --- /dev/null +++ b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html @@ -0,0 +1 @@ +{# TODO: copy the text from the TXT version once we agree on its content #} diff --git a/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt new file mode 100644 index 00000000000..495e6ac936a --- /dev/null +++ b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt @@ -0,0 +1,31 @@ +{% extends "core/email/common.txt" %} +{% block content %} +The Read the Docs build system is deprecating "build.image" config key on ".readthedocs.yaml" starting on October 16, 2023. +We are sending weekly notifications about this issue to all impacted users, +as well as temporary build failures (brownouts) as the date approaches for those who haven't migrated their projects. + +The timeline for this deprecation is as follows: + +* Monday, August 28, 2023: Do the first brownout (temporarily enforce this deprecation) for 12 hours: 00:01 PST to 11:59 PST (noon) +* Monday, September 18, 2023: Do a second brownout (temporarily enforce this deprecation) for 24 hours: 00:01 PST to 23:59 PST (midnight) +* Monday, October 2, 2023: Do a third and final brownout (temporarily enforce this deprecation) for 48 hours: 00:01 PST to October 3, 2023 23:59 PST (midnight) +* Monday, October 16, 2023: Fully remove support for building documentation using "build.image" on the configuration file + +We have identified that the following projects which you maintain, and were built in the last year, are impacted by this deprecation: + +{% for project in projects|slice:":15" %} +* {{ project.slug }} ({{ production_uri }}{{ project.get_absolute_url }}) +{% endfor %} +{% if projects.count > 15 %} +* ... and {{ projects.count|add:"-15" }} more projects. +{% endif %} + +Please use "build.os" on your configuration file to ensure that they continue building successfully and to stop receiving these notifications. +If you want to opt-out from these emails, you can edit your preferences in your account settings, at https://readthedocs.org/accounts/edit/. + +For more information on how to use "build.os", +read our blog post at https://blog.readthedocs.com/build-image-config-deprecated/ + +Get in touch with us via our support ({{ production_uri }}{% url 'support' %}) +and let us know if you are unable to use a configuration file for any reason. +{% endblock %} From 0883154aa48dbd78a05d9dd6c077ad91d269612d Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 3 Aug 2023 13:55:59 +0200 Subject: [PATCH 3/9] Review and update logic --- readthedocs/builds/models.py | 4 ++-- readthedocs/projects/tasks/utils.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/readthedocs/builds/models.py b/readthedocs/builds/models.py index 872d80490ea..03d91ca9142 100644 --- a/readthedocs/builds/models.py +++ b/readthedocs/builds/models.py @@ -1106,7 +1106,7 @@ def deprecated_config_used(self): def deprecated_build_image_used(self): """ - Check whether this particular build is using the deprecated "build" config. + Check whether this particular build is using the deprecated "build.image" config. Note we are using this to communicate deprecation of "build.image". See https://github.com/readthedocs/meta/discussions/48 @@ -1117,7 +1117,7 @@ def deprecated_build_image_used(self): return False build_config_key = self.config.get("build", {}) - return "image" in build_config_key or "os" not in build_config_key + return "image" in build_config_key def reset(self): """ diff --git a/readthedocs/projects/tasks/utils.py b/readthedocs/projects/tasks/utils.py index d7b4d424517..1175ffb70c4 100644 --- a/readthedocs/projects/tasks/utils.py +++ b/readthedocs/projects/tasks/utils.py @@ -401,9 +401,7 @@ def deprecated_build_image_notification(): .order_by("-date") .first() ) - # TODO: uncomment this line before merging - # if build and build.deprecated_build_image_used(): - if build and "image" in build.config.get("build", {}): + if build and build.deprecated_build_image_used(): projects.add(project.slug) # Store all the users we want to contact From f6399df37d5420261f49d8f4c0159345d5905309 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 3 Aug 2023 14:24:01 +0200 Subject: [PATCH 4/9] Start emailing people with projects building from 3 years ago --- readthedocs/projects/tasks/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/projects/tasks/utils.py b/readthedocs/projects/tasks/utils.py index 1175ffb70c4..6864d499d99 100644 --- a/readthedocs/projects/tasks/utils.py +++ b/readthedocs/projects/tasks/utils.py @@ -394,7 +394,7 @@ def deprecated_build_image_notification(): ) if version: # Use a fixed date here to avoid changing the date on each run - years_ago = timezone.datetime(2022, 8, 1) + years_ago = timezone.datetime(2020, 8, 1) build = ( version.builds.filter(success=True, date__gt=years_ago) .only("_config") From 41150cc3a63abcc703cad3ae7eee42c4c6841c71 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Aug 2023 11:23:42 +0200 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Anthony Co-authored-by: Eric Holscher <25510+ericholscher@users.noreply.github.com> --- readthedocs/core/models.py | 2 +- readthedocs/doc_builder/exceptions.py | 6 +++--- readthedocs/projects/tasks/utils.py | 2 +- .../notifications/deprecated_build_image_used_email.txt | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/readthedocs/core/models.py b/readthedocs/core/models.py index daf25cec286..11efef7f34f 100644 --- a/readthedocs/core/models.py +++ b/readthedocs/core/models.py @@ -52,7 +52,7 @@ class UserProfile(TimeStampedModel): null=True, ) # NOTE: this is a temporary field that we can remove after October 16, 2023 - # See https://blog.readthedocs.com/build-image-config-deprecated/ + # See https://blog.readthedocs.com/use-build-os-config/ optout_email_build_image_deprecation = models.BooleanField( _("Opt-out from email about '\"build.image\" config key deprecation'"), default=False, diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index 787b0c90ec4..937eac7c846 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -68,12 +68,12 @@ class BuildUserError(BuildBaseException): ) BUILD_IMAGE_CONFIG_KEY_DEPRECATED = gettext_noop( 'The configuration key "build.image" is deprecated. ' - 'Please, use "build.os" instead to make it build successfully. ' - "Read more at https://docs.readthedocs.io/en/stable/config-file/v2.html" + 'Use "build.os" instead to continue building your project. ' + "Read more at https://docs.readthedocs.io/en/stable/config-file/v2.html#build-os" ) BUILD_OS_REQUIRED = gettext_noop( 'The configuration key "build.os" is required to build your documentation. ' - "Read more at https://docs.readthedocs.io/en/stable/config-file/v2.html" + "Read more at https://docs.readthedocs.io/en/stable/config-file/v2.html#build-os" ) diff --git a/readthedocs/projects/tasks/utils.py b/readthedocs/projects/tasks/utils.py index 6864d499d99..1175ffb70c4 100644 --- a/readthedocs/projects/tasks/utils.py +++ b/readthedocs/projects/tasks/utils.py @@ -394,7 +394,7 @@ def deprecated_build_image_notification(): ) if version: # Use a fixed date here to avoid changing the date on each run - years_ago = timezone.datetime(2020, 8, 1) + years_ago = timezone.datetime(2022, 8, 1) build = ( version.builds.filter(success=True, date__gt=years_ago) .only("_config") diff --git a/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt index 495e6ac936a..f68aa6fe3b1 100644 --- a/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt +++ b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.txt @@ -1,6 +1,6 @@ {% extends "core/email/common.txt" %} {% block content %} -The Read the Docs build system is deprecating "build.image" config key on ".readthedocs.yaml" starting on October 16, 2023. +The "build.image" config key on ".readthedocs.yaml" has been deprecated, and will be removed on October 16, 2023. We are sending weekly notifications about this issue to all impacted users, as well as temporary build failures (brownouts) as the date approaches for those who haven't migrated their projects. @@ -24,7 +24,7 @@ Please use "build.os" on your configuration file to ensure that they continue bu If you want to opt-out from these emails, you can edit your preferences in your account settings, at https://readthedocs.org/accounts/edit/. For more information on how to use "build.os", -read our blog post at https://blog.readthedocs.com/build-image-config-deprecated/ +read our blog post at https://blog.readthedocs.com/use-build-os-config/ Get in touch with us via our support ({{ production_uri }}{% url 'support' %}) and let us know if you are unable to use a configuration file for any reason. From 75921e404dc22b9db561ac08cc7082d4164f2bac Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Aug 2023 17:08:38 +0200 Subject: [PATCH 6/9] Add HTML version of the email --- .../deprecated_build_image_used_email.html | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html index deaf0b3fa8c..05c45695145 100644 --- a/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html +++ b/readthedocs/templates/projects/notifications/deprecated_build_image_used_email.html @@ -1 +1,35 @@ -{# TODO: copy the text from the TXT version once we agree on its content #} +{% extends "core/email/common.html" %} +{% block content %} +The build.image config key on .readthedocs.yaml has been deprecated, and will be removed on October 16, 2023. +We are sending weekly notifications about this issue to all impacted users, +as well as temporary build failures (brownouts) as the date approaches for those who haven't migrated their projects. + +The timeline for this deprecation is as follows: + +
    +
  • Monday, August 28, 2023: Do the first brownout (temporarily enforce this deprecation) for 12 hours: 00:01 PST to 11:59 PST (noon)
  • +
  • Monday, September 18, 2023: Do a second brownout (temporarily enforce this deprecation) for 24 hours: 00:01 PST to 23:59 PST (midnight)
  • +
  • Monday, October 2, 2023: Do a third and final brownout (temporarily enforce this deprecation) for 48 hours: 00:01 PST to October 3, 2023 23:59 PST (midnight)
  • +
  • Monday, October 16, 2023: Fully remove support for building documentation using "build.image" on the configuration file
  • +
+ +We have identified that the following projects which you maintain, and were built in the last year, are impacted by this deprecation: + +
    +{% for project in projects|slice:":15" %} +
  • {{ project.slug }}
  • +{% endfor %} +{% if projects.count > 15 %} +
  • ... and {{ projects.count|add:"-15" }} more projects.
  • +{% endif %} +
+ +Please use build.os on your configuration file to ensure that they continue building successfully and to stop receiving these notifications. +If you want to opt-out from these emails, you can edit your preferences in your account settings. + +For more information on how to use build.os, +read our blog post + +Get in touch with us via our support +and let us know if you are unable to use a configuration file for any reason. +{% endblock %} From 458913eab209217a17791b721e2eb5dfe4b33f1a Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Aug 2023 17:27:35 +0200 Subject: [PATCH 7/9] Codify brownout dates and remove the feature flag Follows the suggestion from https://github.com/readthedocs/blog/pull/233/files#r1283479184 --- readthedocs/doc_builder/director.py | 16 +++++++++++++++- readthedocs/projects/models.py | 7 ------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/readthedocs/doc_builder/director.py b/readthedocs/doc_builder/director.py index 3b9a385320d..1fa1879fb36 100644 --- a/readthedocs/doc_builder/director.py +++ b/readthedocs/doc_builder/director.py @@ -13,6 +13,7 @@ import structlog import yaml from django.conf import settings +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from readthedocs.builds.constants import EXTERNAL @@ -256,7 +257,20 @@ def checkout(self): raise BuildUserError(BuildUserError.NO_CONFIG_FILE_DEPRECATED) # Raise a build error if the project is using "build.image" on their config file - if self.data.project.has_feature(Feature.BUILD_IMAGE_CONFIG_KEY_DEPRECATED): + + now = timezone.now() + + # fmt: off + # These browndates matches https://blog.readthedocs.com/use-build-os-config/ + browndates = any([ + timezone.datetime(2023, 8, 28, 0, 0, 0) < now < timezone.datetime(2023, 8, 28, 12, 0, 0), # First, 12hs + timezone.datetime(2023, 9, 18, 0, 0, 0) < now < timezone.datetime(2023, 9, 19, 0, 0, 0), # Second, 24hs + timezone.datetime(2023, 10, 2, 0, 0, 0) < now < timezone.datetime(2023, 10, 4, 0, 0, 0), # Third, 48hs + timezone.datetime(2023, 10, 16, 0, 0, 0) < now, # Fully removal + ]) + # fmt: on + + if browndates: build_config_key = self.data.config.source_config.get("build", {}) if "image" in build_config_key: raise BuildUserError(BuildUserError.BUILD_IMAGE_CONFIG_KEY_DEPRECATED) diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index b2492b55d87..834f55fa4b5 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -1950,7 +1950,6 @@ def add_features(sender, **kwargs): GIT_CLONE_FETCH_CHECKOUT_PATTERN = "git_clone_fetch_checkout_pattern" HOSTING_INTEGRATIONS = "hosting_integrations" NO_CONFIG_FILE_DEPRECATED = "no_config_file" - BUILD_IMAGE_CONFIG_KEY_DEPRECATED = "build_image_config_key_deprecated" SCALE_IN_PROTECTION = "scale_in_prtection" FEATURES = ( @@ -2094,12 +2093,6 @@ def add_features(sender, **kwargs): NO_CONFIG_FILE_DEPRECATED, _("Build: Building without a configuration file is deprecated."), ), - ( - BUILD_IMAGE_CONFIG_KEY_DEPRECATED, - _( - 'Build: Building using "build.image" in the configuration file is deprecated.' - ), - ), ( SCALE_IN_PROTECTION, _("Build: Set scale-in protection before/after building."), From 4584cdfd34ef09cb1c83d691f9f956d2afe300e9 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Aug 2023 18:01:15 +0200 Subject: [PATCH 8/9] Use UTC datetimes to compare --- readthedocs/doc_builder/director.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readthedocs/doc_builder/director.py b/readthedocs/doc_builder/director.py index 1fa1879fb36..0bc4aeac859 100644 --- a/readthedocs/doc_builder/director.py +++ b/readthedocs/doc_builder/director.py @@ -263,10 +263,10 @@ def checkout(self): # fmt: off # These browndates matches https://blog.readthedocs.com/use-build-os-config/ browndates = any([ - timezone.datetime(2023, 8, 28, 0, 0, 0) < now < timezone.datetime(2023, 8, 28, 12, 0, 0), # First, 12hs - timezone.datetime(2023, 9, 18, 0, 0, 0) < now < timezone.datetime(2023, 9, 19, 0, 0, 0), # Second, 24hs - timezone.datetime(2023, 10, 2, 0, 0, 0) < now < timezone.datetime(2023, 10, 4, 0, 0, 0), # Third, 48hs - timezone.datetime(2023, 10, 16, 0, 0, 0) < now, # Fully removal + timezone.datetime(2023, 8, 28, 0, 0, 0, tzinfo=timezone.utc) < now < timezone.datetime(2023, 8, 28, 12, 0, 0, tzinfo=timezone.utc), # First, 12hs + timezone.datetime(2023, 9, 18, 0, 0, 0, tzinfo=timezone.utc) < now < timezone.datetime(2023, 9, 19, 0, 0, 0, tzinfo=timezone.utc), # Second, 24hs + timezone.datetime(2023, 10, 2, 0, 0, 0, tzinfo=timezone.utc) < now < timezone.datetime(2023, 10, 4, 0, 0, 0, tzinfo=timezone.utc), # Third, 48hs + timezone.datetime(2023, 10, 16, 0, 0, 0, tzinfo=timezone.utc) < now, # Fully removal ]) # fmt: on From 3d99b0bc5944777c32760f84f067fac7d0eb58d5 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 9 Aug 2023 10:32:47 +0200 Subject: [PATCH 9/9] Contact projects with a build in the last 3 years We will start with 3 years timeframe first and then lower it down to 1 year. --- readthedocs/projects/tasks/utils.py | 2 +- .../notifications/deprecated_config_file_used_email.html | 2 +- .../notifications/deprecated_config_file_used_email.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/readthedocs/projects/tasks/utils.py b/readthedocs/projects/tasks/utils.py index 1175ffb70c4..6864d499d99 100644 --- a/readthedocs/projects/tasks/utils.py +++ b/readthedocs/projects/tasks/utils.py @@ -394,7 +394,7 @@ def deprecated_build_image_notification(): ) if version: # Use a fixed date here to avoid changing the date on each run - years_ago = timezone.datetime(2022, 8, 1) + years_ago = timezone.datetime(2020, 8, 1) build = ( version.builds.filter(success=True, date__gt=years_ago) .only("_config") diff --git a/readthedocs/templates/projects/notifications/deprecated_config_file_used_email.html b/readthedocs/templates/projects/notifications/deprecated_config_file_used_email.html index 100c6c0b9ae..404ba87d5ee 100644 --- a/readthedocs/templates/projects/notifications/deprecated_config_file_used_email.html +++ b/readthedocs/templates/projects/notifications/deprecated_config_file_used_email.html @@ -13,7 +13,7 @@
  • Monday, September 25, 2023: Fully remove support for building documentation without configuration file v2.
  • -We have identified that the following projects which you maintain, and were built in the last year, are impacted by this deprecation: +We have identified that the following projects which you maintain, and were built in the last 3 years, are impacted by this deprecation:
      {% for project in projects|slice:":15" %} diff --git a/readthedocs/templates/projects/notifications/deprecated_config_file_used_email.txt b/readthedocs/templates/projects/notifications/deprecated_config_file_used_email.txt index 4790d1234d3..d069e49d0aa 100644 --- a/readthedocs/templates/projects/notifications/deprecated_config_file_used_email.txt +++ b/readthedocs/templates/projects/notifications/deprecated_config_file_used_email.txt @@ -11,7 +11,7 @@ The timeline for this deprecation is as follows: * Monday, September 4, 2023: Do a third and final brownout (temporarily enforce this deprecation) for 48 hours: 00:01 PST to September 5, 2023 23:59 PST (midnight) * Monday, September 25, 2023: Fully remove support for building documentation without configuration file v2. -We have identified that the following projects which you maintain, and were built in the last year, are impacted by this deprecation: +We have identified that the following projects which you maintain, and were built in the last 3 years, are impacted by this deprecation: {% for project in projects|slice:":15" %} * {{ project.slug }} ({{ production_uri }}{{ project.get_absolute_url }})