From f26faaef61e5ef48140bd2f84630c5d624041dad Mon Sep 17 00:00:00 2001 From: gerryRcom Date: Wed, 3 Jul 2024 09:18:02 +0100 Subject: [PATCH 01/36] oda spelling check on translations doc --- .custom_wordlist.txt | 26 +++++++++++++++++++ .sphinx/spellingcheck.yaml | 2 +- .../engineering-overview-translations.rst | 14 +++++----- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 5900b9d..5e36f78 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -52,6 +52,7 @@ changesets charmcraft Cmd CMDLINE +Cobol codebase Codehosting codeimport @@ -69,6 +70,7 @@ cryptographic cryptographically css Ctrl +customisable customize CVE Dalia @@ -86,6 +88,7 @@ defense defense deps dev +dia directDelivery DirectDelivery distro @@ -133,6 +136,7 @@ ftest fti functiondef gangotri +gettext github globals GPG @@ -251,15 +255,28 @@ pipx plaintext png po +POExport +POExportRequest +POFile +POFiles +POFileTranslator PolicyandProcess PolicyAndProcess PolicyForDocumentingCustomDistributions +POMsgID pooler portlets postgresql PostgreSQL PostGreSQL POSTs +POTs +POTExport +POTemplate +POTemplateSharingSubset +POTranslation +POTMsgSet +POTMsgSets ppa PPA PQM @@ -270,6 +287,7 @@ preprocessed prioritize prober proc +ProductSeries programmatically prometheus proname @@ -322,6 +340,8 @@ SolutionsLog sourcecode sourceforge sourcepackage +SourcePackage +SourcePackagename sourcetree soyuz specialized @@ -347,6 +367,7 @@ subdirectory subprocess subproject sudo +SuggestivePOTemplate summarized svg symlinked @@ -377,6 +398,10 @@ traceback tradeoff tradeoffs Transactional +TranslationGroup +TranslationImportQueue +TranslationMessage +TranslationTemplateItem traversers triaged triaging @@ -398,6 +423,7 @@ vbuilder virtualenv virtualenvs VPN +VPOTExport webdav webhook webservice diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index d3879fc..2947692 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/engineering-overview-translations/index.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html|!_build/explanation/javascript-integration-testing/index.html + - _build/**/*.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html|!_build/explanation/javascript-integration-testing/index.html pipeline: - pyspelling.filters.html: comments: false diff --git a/explanation/engineering-overview-translations.rst b/explanation/engineering-overview-translations.rst index 90dda9c..b4cc1f7 100644 --- a/explanation/engineering-overview-translations.rst +++ b/explanation/engineering-overview-translations.rst @@ -28,7 +28,7 @@ Launchpad: the *ubuntu side* and the *upstream side.* Where possible, the two sides are unified (in technical terms) and integrated (in collaboration terms). But you'll see a lot of cases where they are treated somewhat differently. Permissions can differ, -organizational structures differ, and some processes only exist on one +organisational structures differ, and some processes only exist on one side or the other. At the most fundamental level, the two sides are integrated through: @@ -153,7 +153,7 @@ or select a different translation message. A translation message can be *current* in a given PO file, or not. It's an emergent property of more complex shared data structures. So you can -view a PO file as a customizable “view” on the current translations of a +view a PO file as a customisable “view” on the current translations of a particular template into a given language. :: @@ -301,7 +301,7 @@ Soyuz uploads are different in that regard: all its custom logic is built into the gardener because the two developed hand in hand. Mainly for this reason, the gardener's approval logic is fiendishly complex. -Permissions and organization +Permissions and organisation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Message sharing @@ -335,7 +335,7 @@ In a nutshell: - A \`POTranslation\` holds the text of a translated string; \`TranslationMessage\` refers to it (once for every plural form in the language). -- A \`TranslationGroup\` is an organizational structure for managing +- A \`TranslationGroup\` is an organisational structure for managing translations. - A \`Translator\` is an entry in a \`TranslationGroup\` saying who is responsible for a particular \`Language`. @@ -355,7 +355,7 @@ multiple templates. We then call these templates *sharing templates.* And that means that a translation message to, say, Italian will be available in each of those templates' PO file for Italian. -This is where it gets complicated; please fasten your seatbelts and +This is where it gets complicated; please fasten your seat belts and extinguish smoking motherboards. A translation message can be in one of three sharing states: @@ -399,7 +399,7 @@ current translation message? Look for one with: - diverged to your template or, if no message matches, not diverged at all. -(On a sidenote, this is why “simple” translation statistics can be quite +(On a side note, this is why “simple” translation statistics can be quite hard to compute.) Which templates share? @@ -523,7 +523,7 @@ translation page. Its complexity also makes the SQL logs hard to follow. A large part of this query (in terms of SQL text) was involved in finding out what templates were eligible for taking suggestions from. This part was also completely repetitive, and it doesn't even need to be -immediately consistent, so we materialized it as a simple cache table +immediately consistent, so we materialised it as a simple cache table called \`SuggestivePOTemplate`. We refresh this cache all the time by clearing out the table and From fda0691919cd849ff4c6ee24e4dc1e3d5e6b1682 Mon Sep 17 00:00:00 2001 From: Jared Nielsen Date: Wed, 3 Jul 2024 11:32:15 -0400 Subject: [PATCH 02/36] Fix 'UI/CssSprites' link --- custom_conf.py | 1 - explanation/css.rst | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_conf.py b/custom_conf.py index 1d72a4c..e2d254d 100644 --- a/custom_conf.py +++ b/custom_conf.py @@ -137,7 +137,6 @@ 'PolicyAndProcess/Accessibility', # needs update 'Soyuz', # needs update 'Translations/Specs/UpstreamImportIntoUbuntu/FixingIsImported/setCurrentTranslation', # needs update - 'UI/CssSprites', # needs update 'attachment:codehosting.png', # needs update 'https://git.launchpad.net/launchpad-mojo-specs/tree/mojo-lp-git/services', # private 'https://wiki.canonical.com/InformationInfrastructure/OSA/LaunchpadProductionStatus', # private diff --git a/explanation/css.rst b/explanation/css.rst index c0e31e9..2e17e72 100644 --- a/explanation/css.rst +++ b/explanation/css.rst @@ -39,7 +39,7 @@ If you are dealing with sprites you may also have to run: make sprite_image -`More info on sprites `__ +:doc:`More info on sprites `. Fonts ----- From 017d19761d96d9c04a1ea61ac0e77bcf6a7b7cab Mon Sep 17 00:00:00 2001 From: Jared Nielsen Date: Wed, 3 Jul 2024 11:42:33 -0400 Subject: [PATCH 03/36] Fix 'Loggerhead' link --- custom_conf.py | 1 - explanation/code.rst | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/custom_conf.py b/custom_conf.py index e2d254d..c0c9709 100644 --- a/custom_conf.py +++ b/custom_conf.py @@ -129,7 +129,6 @@ '/Background', '/Concepts', # needs update '/HowToUseCodehostingLocally', # needs update - '/Loggerhead', # needs update 'Database/TableRenamePatch', # needs update 'Debugging#Profiling%20page%20requests', # needs update 'Debugging#Special%20URLs', # needs update diff --git a/explanation/code.rst b/explanation/code.rst index d625a4f..7a7caa0 100644 --- a/explanation/code.rst +++ b/explanation/code.rst @@ -21,7 +21,7 @@ system. The major sub-systems are: - Email processing - Code imports (from CVS, Subversion, git and Mercurial) - Branch source code browser (`cgit `__ - for Git; `loggerhead `__ for Bazaar) + for Git; :doc:`loggerhead <../how-to/land-update-for-loggerhead>` for Bazaar) - Source package recipes (`git-build-recipe`/`bzr-builder\` integration with `Soyuz `__) @@ -208,7 +208,7 @@ branch. - loggerhead itself - community project but with major contributions from Canonical -See `Loggerhead for Launchpad developers `__ for details on +See :doc:`Loggerhead for Launchpad developers <../how-to/land-update-for-loggerhead>` for details on how to land changes to Launchpad loggerhead. Source package recipes From 7649b104c9439dda5f938b2e0153e4d1c45f21b4 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 10:47:42 +0200 Subject: [PATCH 04/36] Remove javascript-integration-testing page from the exclusion list --- .sphinx/spellingcheck.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index d3879fc..101bcb0 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/engineering-overview-translations/index.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html|!_build/explanation/javascript-integration-testing/index.html + - _build/**/*.html|!_build/explanation/engineering-overview-translations/index.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html pipeline: - pyspelling.filters.html: comments: false From ce333446e7c7501629d3ceab239183aed64af319 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 10:48:48 +0200 Subject: [PATCH 05/36] Add correctly spelled words picked up by spell checker --- .custom_wordlist.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 5900b9d..24bfc53 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -8,6 +8,7 @@ api AppleApplications appmanifest appserver +AppServerLayer appservers artifact artifacts @@ -147,6 +148,7 @@ html http https https +iframe iharness IMailDelivery IMailer @@ -174,6 +176,7 @@ LandingChanges langpack LaunchpadAuthentication LaunchpadDatabaseRevision +LaunchpadObjectFactory LaunchpadPpa lazr libera @@ -418,6 +421,7 @@ YAGNI yaml yui yuilibrary +yuitest yy zcml ZFS From 5a2f090a2da9083b3c3b658592ec43595e78eb0e Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 10:50:08 +0200 Subject: [PATCH 06/36] Correct spelling errors, verified by external documentation, converted to en-gb and corrected formatting --- explanation/javascript-integration-testing.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/explanation/javascript-integration-testing.rst b/explanation/javascript-integration-testing.rst index fe65f67..df2b488 100644 --- a/explanation/javascript-integration-testing.rst +++ b/explanation/javascript-integration-testing.rst @@ -1,18 +1,18 @@ Integration Testing in JavaScript ================================= -Launchpad's JavaScript testing is built around YUI 3's yuitest library. -We use the GradedBrowserSupport chart to determine which browsers code should +Launchpad's JavaScript testing is built around YUI 3's ``yuitest`` library. +We use the Graded Browser Support chart to determine which browser's code should be regularly tested in. Every JavaScript component should be tested first and foremost using :doc:`unit testing `. -We have infrastructure to write tests centered on the integration +We have infrastructure to write tests centred on the integration between the JavaScript component and the app server (regular API or view/++model++ page api.) -These are still written using the \`yuitest\` library, but they are +These are still written using the ``yuitest`` library, but they are loaded and can access a "real" appserver (the one started by the AppServerLayer). @@ -32,7 +32,7 @@ Creating the tests - The ``.js`` file contains the tests using the standard ``yuitest`` library. - The ``.py`` file contains fixtures that will operate within the app server. They should create content through the standard LaunchpadObjectFactory that - will be accessed by the test through. The database is automatically reset + will be accessed by the test. The database is automatically reset after every test. Running the tests From 2318addb0ea19de7813b5f6b16efc43d21584659 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 16:06:24 +0200 Subject: [PATCH 07/36] Add words to exclusion list --- .custom_wordlist.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 5e36f78..f6caef0 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -212,6 +212,9 @@ mbox mboxMailer memcache milestoneoverlay +minified +minifies +minify minimize MockIo mockups @@ -220,6 +223,7 @@ mozilla natively né newsampledata +NPM OAuth OEM oid From da06505e8a3431d50a815d16ca4f89a5d66c7a41 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 16:06:52 +0200 Subject: [PATCH 08/36] Remove javascript-buildsystem from exclusion list --- .sphinx/spellingcheck.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index 2947692..b27420f 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html|!_build/explanation/javascript-integration-testing/index.html + - _build/**/*.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html pipeline: - pyspelling.filters.html: comments: false From acb384767214e3d432eafe062a2fb646f3c31938 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 16:07:25 +0200 Subject: [PATCH 09/36] Update mailing list URL, spelling error correction --- explanation/javascript-buildsystem.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/explanation/javascript-buildsystem.rst b/explanation/javascript-buildsystem.rst index c01fd39..8d7b025 100644 --- a/explanation/javascript-buildsystem.rst +++ b/explanation/javascript-buildsystem.rst @@ -34,8 +34,7 @@ Adding a third-party widget ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The current story for adding a third-party widget is to put it in -``lib/lp/contrib``. You can read the mailing list discussion ( -https://lists.launchpad.net/launchpad-dev/msg06474.html ) about the adoption of +``lib/lp/contrib``. You can read the `mailing list discussion`_ about the adoption of this location. For CSS, follow the rules above to modify the tools. If other assets are @@ -44,6 +43,8 @@ needed, you'll need to create a link in ``lib/lp/contrib`` so the assets can be found. See ``lib/canonical/launchpad/icing/yui3-gallery`` for an example. +.. _`mailing list discussion`: https://lists.launchpad.net/launchpad-dev/msg06474.html + New Combo loader Setup ---------------------- @@ -54,7 +55,7 @@ minified into a build directory ``build/js/``. Files are served out of the ``build/js`` directory based on the YUI combo loader config that is constructed in the ``lib/lp/app/templates/base-layout-macros.pt``. These are combined and -served out via the convoy wsgi application through Apache. +served out via the convoy WSGI application through Apache. Developing Javascript ~~~~~~~~~~~~~~~~~~~~~ @@ -85,7 +86,7 @@ include that module name in any YUI block. LPJS.use('modulename', function (Y)... The combo loader will serve your new module when you reload the page -ith that content on it. +with that content on it. Launchpad CSS ------------- From 73f12ca01f9cce4414702674cd24dc3d38e49304 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 10:47:42 +0200 Subject: [PATCH 10/36] Remove javascript-integration-testing page from the exclusion list --- .sphinx/spellingcheck.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index 2947692..101bcb0 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html|!_build/explanation/javascript-integration-testing/index.html + - _build/**/*.html|!_build/explanation/engineering-overview-translations/index.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html pipeline: - pyspelling.filters.html: comments: false From f1c66b1ce59f6af9a678f86f6b4fa637df91bcb3 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 10:48:48 +0200 Subject: [PATCH 11/36] Add correctly spelled words picked up by spell checker --- .custom_wordlist.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 5e36f78..3b1f0ea 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -8,6 +8,7 @@ api AppleApplications appmanifest appserver +AppServerLayer appservers artifact artifacts @@ -151,6 +152,7 @@ html http https https +iframe iharness IMailDelivery IMailer @@ -178,6 +180,7 @@ LandingChanges langpack LaunchpadAuthentication LaunchpadDatabaseRevision +LaunchpadObjectFactory LaunchpadPpa lazr libera @@ -444,6 +447,7 @@ YAGNI yaml yui yuilibrary +yuitest yy zcml ZFS From 54b74c252952c5de24c0e232bbbe560f9c4c416e Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 10:50:08 +0200 Subject: [PATCH 12/36] Correct spelling errors, verified by external documentation, converted to en-gb and corrected formatting --- explanation/javascript-integration-testing.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/explanation/javascript-integration-testing.rst b/explanation/javascript-integration-testing.rst index fe65f67..df2b488 100644 --- a/explanation/javascript-integration-testing.rst +++ b/explanation/javascript-integration-testing.rst @@ -1,18 +1,18 @@ Integration Testing in JavaScript ================================= -Launchpad's JavaScript testing is built around YUI 3's yuitest library. -We use the GradedBrowserSupport chart to determine which browsers code should +Launchpad's JavaScript testing is built around YUI 3's ``yuitest`` library. +We use the Graded Browser Support chart to determine which browser's code should be regularly tested in. Every JavaScript component should be tested first and foremost using :doc:`unit testing `. -We have infrastructure to write tests centered on the integration +We have infrastructure to write tests centred on the integration between the JavaScript component and the app server (regular API or view/++model++ page api.) -These are still written using the \`yuitest\` library, but they are +These are still written using the ``yuitest`` library, but they are loaded and can access a "real" appserver (the one started by the AppServerLayer). @@ -32,7 +32,7 @@ Creating the tests - The ``.js`` file contains the tests using the standard ``yuitest`` library. - The ``.py`` file contains fixtures that will operate within the app server. They should create content through the standard LaunchpadObjectFactory that - will be accessed by the test through. The database is automatically reset + will be accessed by the test. The database is automatically reset after every test. Running the tests From 93e5fb8d8356b70b52401c69e7884a1dea2e8b46 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Thu, 4 Jul 2024 18:44:24 +0200 Subject: [PATCH 13/36] Remove exclusion added via rebase --- .sphinx/spellingcheck.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index 101bcb0..3454a08 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/engineering-overview-translations/index.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html + - _build/**/*.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html pipeline: - pyspelling.filters.html: comments: false From f19310999278be18a3d92443a7b22cf1b0e7e441 Mon Sep 17 00:00:00 2001 From: gerryRcom Date: Thu, 4 Jul 2024 21:18:04 +0100 Subject: [PATCH 14/36] oda spelling check on testing doc --- .custom_wordlist.txt | 12 ++++++++++++ .sphinx/spellingcheck.yaml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 5e36f78..b94f04f 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -166,6 +166,7 @@ IPython irc IRCMeetings javascript +JavascriptUnitTesting Javadoc jenkaas jenkins @@ -211,6 +212,7 @@ maximize mbox mboxMailer memcache +MemCache milestoneoverlay minimize MockIo @@ -329,6 +331,8 @@ sendmail SendmailMailer seqscan setUp +setupDTCBrowser +setupRosettaExpertBrowser setuptools simplestreams Slony @@ -383,8 +387,12 @@ TeamEmail templating testability testbed +TestingJavaScript +TestingWebServices testMailer TestMailer +testr +testrepository testrunner testrunner's TestsFromChanges @@ -420,6 +428,7 @@ upstreams userbase VBScript vbuilder +ViewTests virtualenv virtualenvs VPN @@ -427,6 +436,7 @@ VPOTExport webdav webhook webservice +webservices webservice's whitespace wildcherry @@ -440,10 +450,12 @@ XHR xml XML-RPC xmlrpc +xvfb YAGNI yaml yui yuilibrary +yuixhr yy zcml ZFS diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index 2947692..0f68ba8 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/testing/index.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html|!_build/explanation/javascript-integration-testing/index.html + - _build/**/*.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html|!_build/explanation/javascript-integration-testing/index.html pipeline: - pyspelling.filters.html: comments: false From 053a96086a8e649f0b135aa6eeb942b858f7ba5b Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Fri, 5 Jul 2024 13:59:34 +0200 Subject: [PATCH 15/36] Add word to resolve conflict in pull request --- .custom_wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 3b1f0ea..316a117 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -448,6 +448,7 @@ yaml yui yuilibrary yuitest +yuixhr yy zcml ZFS From 1c6506b7e971fed802b3dfc85abc29bc0a075450 Mon Sep 17 00:00:00 2001 From: gerryRcom Date: Fri, 5 Jul 2024 20:06:05 +0100 Subject: [PATCH 16/36] oda spelling check on feature-flags doc --- .custom_wordlist.txt | 9 ++++++++- .sphinx/spellingcheck.yaml | 2 +- explanation/feature-flags.rst | 18 +++++++++--------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 8de2500..51226e0 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -137,6 +137,7 @@ ftest fti functiondef gangotri +getFeatureFlag gettext github globals @@ -181,6 +182,7 @@ LandingChanges langpack LaunchpadAuthentication LaunchpadDatabaseRevision +LaunchpadProductionStatus LaunchpadObjectFactory LaunchpadPpa lazr @@ -193,6 +195,8 @@ logpoints logrotate lookup lookups +LOSA +losas LOSAs lp Lp's @@ -230,6 +234,7 @@ OEM oid ok ols +omg OOPSes OpenID opstats @@ -285,9 +290,9 @@ POTMsgSets ppa PPA PQM -psql pre PreMergeReviews +prepending preprocessed prioritize prober @@ -297,6 +302,7 @@ programmatically prometheus proname proxied +psql px py pydoctor @@ -390,6 +396,7 @@ TeamEmail templating testability testbed +TestCase TestingJavaScript TestingWebServices testMailer diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index b7c35b7..ac66f8b 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/feature-flags/index.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html + - _build/**/*.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html|!_build/explanation/javascript-buildsystem/index.html pipeline: - pyspelling.filters.html: comments: false diff --git a/explanation/feature-flags.rst b/explanation/feature-flags.rst index 8eae069..a0ad8cb 100644 --- a/explanation/feature-flags.rst +++ b/explanation/feature-flags.rst @@ -3,7 +3,7 @@ Feature Flags .. include:: ../includes/important_not_revised.rst -**FeatureFlags allow Launchpad's configuration to be changed while it's +**Feature Flags allow Launchpad's configuration to be changed while it's running, and for particular features or behaviours to be exposed to only a subset of users or requests.** @@ -23,15 +23,15 @@ Scenarios - Dark launches (aka embargoes: land code first, turn it on later) - Closed betas -- Scram switches (eg "omg daily builds are killing us, make it stop") +- Scram switches (e.g. "omg daily builds are killing us, make it stop") - Soft/slow launch (let just a few users use it and see what happens) - Site-wide notification - Show an 'alpha', 'beta' or 'new!' badge next to a UI control, then later turn it off without a new rollout -- Show developer-oriented UI only to developers (eg the query count) +- Show developer-oriented UI only to developers (e.g. the query count) - Control page timeouts (or other resource limits) either per page id, or per user group -- Set resource limits (eg address space cap) for jobs. +- Set resource limits (e.g. address space cap) for jobs. Concepts -------- @@ -40,7 +40,7 @@ A **feature flag** has a string name, and has a dynamically-determined value within a particular context such as a web or api request. The value in that context depends on determining which **scopes** are relevant to the context, and what **rules** exist for that flag and -scopes. The rules are totally ordered and the highest-prority rule +scopes. The rules are totally ordered and the highest-priority rule determines the relevant value. Flags values are strings; or if no value is specified, \`None`. (If an @@ -128,7 +128,7 @@ Flags should be named as where each of the parts is a legal Python name (so use underscores to join words, not dashes.) -The **area** is the general area of Launchpad this relates to: eg +The **area** is the general area of Launchpad this relates to: e.g. 'code', 'librarian', ... The **feature** is the particular thing that's being controlled, such as @@ -218,7 +218,7 @@ Adding and documenting a new feature flag ----------------------------------------- If you introduce a new feature flag, as well as reading it from -whereever is useful, you should also: +wherever is useful, you should also: - Add a section in lib/lp/services/features/flags.py flag_info describing the flag, including documentation that will make sense to @@ -236,7 +236,7 @@ whereever is useful, you should also: ''), The last item in that list is descriptive, not prescriptive: it -*documents the code's default behavior* if no value is specified. The +*documents the code's default behaviour* if no value is specified. The flag's value will still read as None if no value is specified, and setting it to an empty value still returns the empty string. @@ -267,7 +267,7 @@ and/or SCRIPT_SCOPE_HANDLERS -depending on whether it applies to webapp requests, scripts, or both). +depending on whether it applies to web app requests, scripts, or both). Testing ------- From bdea1e1d11e88255eed19e335d840a278cefb134 Mon Sep 17 00:00:00 2001 From: gerryRcom Date: Wed, 10 Jul 2024 20:08:37 +0100 Subject: [PATCH 17/36] oda spelling check on ppa doc --- .custom_wordlist.txt | 32 ++++++++++++++++++++++++++++++++ .sphinx/spellingcheck.yaml | 2 +- explanation/launchpad-ppa.rst | 24 ++++++++++++------------ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 480a9b6..277333e 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -24,11 +24,15 @@ autocert autogenerated backend backends +backport +backports +backported backtrace Backtraces barfunc Becca BEM +Bionic's Blazingly bool boolean @@ -41,6 +45,7 @@ BugWatches bugzilla buildbot buildd +builddeb bzr bzr's centralized @@ -48,6 +53,7 @@ Centralized cfg cgroup cgroups +changelog changeset changesets charmcraft @@ -76,6 +82,7 @@ customize CVE Dalia Dalia's +Danilo DatabaseSchemaChangesProcess DatabaseSetup DatetimeUsageGuide @@ -84,11 +91,19 @@ dbpatches dbschema dbupgrade ddl +debchange +debcommit +DEBEMAIL +DEBFULLNAME +debhelper +debian DebuggingWithGdb +debversion defense defense deps dev +dh dia directDelivery DirectDelivery @@ -137,6 +152,9 @@ ftest fti functiondef gangotri +gina's +geoip +geoIP getFeatureFlag gettext github @@ -146,6 +164,7 @@ gunicorn gzip HackingLazrLibraries hba +hirsute's hostnames HSTS howto @@ -176,6 +195,7 @@ jenkins jinja js JScript +keyring kiko kompare LandingChanges @@ -187,6 +207,7 @@ LaunchpadObjectFactory LaunchpadPpa lazr libera +libgit lifecycle listdir LivePatching @@ -201,9 +222,12 @@ LOSAs lp Lp's lpbuildbot +lpbuildd lpci LPHowTo lpnet +lpreview +lpsetup LTS lxc lxd @@ -225,6 +249,7 @@ minified minifies minify minimize +mmm MockIo mockups mojo @@ -263,6 +288,7 @@ PatchSubmission pdb pentested performant +pgbouncer pgsql pgSQL pipx @@ -320,6 +346,7 @@ queuedDelivery QueuedDelivery quickref quickstart +rc rctp realfavicongenerator realizes @@ -348,6 +375,7 @@ setupDTCBrowser setupRosettaExpertBrowser setuptools simplestreams +slony Slony smtp smtpMailer @@ -439,6 +467,7 @@ unobvious untriaged untrusted upstreams +unsuffixed userbase VBScript vbuilder @@ -452,14 +481,17 @@ webhook webservice webservices webservice's +wgrant's whitespace wildcherry Wishlist WorkingWithReviews worktrees +wsgi WSGI www Xenial +Xenial's XHR xml XML-RPC diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index 5b07e82..a693b58 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/launchpad-ppa/index.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html + - _build/**/*.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html pipeline: - pyspelling.filters.html: comments: false diff --git a/explanation/launchpad-ppa.rst b/explanation/launchpad-ppa.rst index f51605b..3293078 100644 --- a/explanation/launchpad-ppa.rst +++ b/explanation/launchpad-ppa.rst @@ -48,7 +48,7 @@ Policy/procedure for updates: version number, so if debchange -i adds that for you, take it out again and increment the unsuffixed version number instead. 5. debcommit or bzr commit -6. Exercise personal judgment on whether your change merits a merge +6. Exercise personal judgement on whether your change merits a merge proposal, or is sufficiently trivial to just be committed directly. 7. If preparing a merge proposal, please ensure your branch for review contains a complete debian/changelog entry ready for release. @@ -94,13 +94,13 @@ mmm-archive-manager Backported or patched Ubuntu packages ------------------------------------- -postgresql-10, postgresql-common, postgresql-debversion, slony1-2 (trusty, xenial) +postgresql-10, postgresql-common, postgresql-debversion, slony1-2 (Trusty, Xenial) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Straight backports of PostgreSQL 10 and paraphernalia from bionic to -trusty. bionic's version is fine. +Straight backports of PostgreSQL 10 and paraphernalia from Bionic to +Trusty. Bionic's version is fine. -pgbouncer (trusty) +pgbouncer (Trusty) ~~~~~~~~~~~~~~~~~~ Trusty's pgbouncer with wgrant's ENABLE/DISABLE patch as required by @@ -108,9 +108,9 @@ full-update.py. For the benefit of launchpad-dependencies, the patched package additionally Provides pgbouncer-with-disconnect. The ENABLE/DISABLE patch is included upstream in pgbouncer 1.6, so -xenial's version is fine. +Xenial's version is fine. -libgit2, git (bionic) +libgit2, git (Bionic) ~~~~~~~~~~~~~~~~~~~~~ Various updates backported from focal for use on git.launchpad.net. @@ -125,7 +125,7 @@ The packaging is based on but modified to install Launchpad's convoy.wsgi. Ubuntu's modern packaging uses dh-python and supports Python 3. -debian-archive-keyring (xenial, bionic, focal) +debian-archive-keyring (Xenial, Bionic, Focal) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Straight backport for new Debian archive keys for gina's mirror. @@ -136,7 +136,7 @@ git-build-recipe `Daily builds of lp:git-build-recipe `__ -for buildds. +for builds. Distro series support --------------------- @@ -144,9 +144,9 @@ Distro series support Stable ~~~~~~ -- trusty (obsolete production LTS, still used by databases) -- xenial (current production LTS) -- bionic (next production LTS) +- Trusty (obsolete production LTS, still used by databases) +- Xenial (current production LTS) +- Bionic (next production LTS) In progress ~~~~~~~~~~~ From 141aa07f62d47e7b25581c113fe222679ca9135d Mon Sep 17 00:00:00 2001 From: gerryRcom Date: Wed, 10 Jul 2024 20:12:47 +0100 Subject: [PATCH 18/36] oda spelling check on ppa doc --- explanation/launchpad-ppa.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/explanation/launchpad-ppa.rst b/explanation/launchpad-ppa.rst index 3293078..a077fc0 100644 --- a/explanation/launchpad-ppa.rst +++ b/explanation/launchpad-ppa.rst @@ -113,7 +113,7 @@ Xenial's version is fine. libgit2, git (Bionic) ~~~~~~~~~~~~~~~~~~~~~ -Various updates backported from focal for use on git.launchpad.net. +Various updates backported from Focal for use on git.launchpad.net. convoy ~~~~~~ @@ -151,7 +151,7 @@ Stable In progress ~~~~~~~~~~~ -- focal (next, next production LTS?) +- Focal (next, next production LTS?) When the supported series change, remember to also update :doc:`../how-to/getting` and :doc:`../how-to/running`. From 124245b2b4b5699596e7039f09f6d1f3211b409f Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Mon, 15 Jul 2024 11:00:22 +0200 Subject: [PATCH 19/36] Remove Launchpad Mail page from exclusion list --- .sphinx/spellingcheck.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index a693b58..4dd8da0 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html|!_build/explanation/mail/index.html + - _build/**/*.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html pipeline: - pyspelling.filters.html: comments: false From 46170ead6fe34fde518fe8848e3d321b57506875 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Mon, 15 Jul 2024 11:02:57 +0200 Subject: [PATCH 20/36] Update formatting of URLs --- explanation/mail.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/explanation/mail.rst b/explanation/mail.rst index 44acbe3..ece5761 100644 --- a/explanation/mail.rst +++ b/explanation/mail.rst @@ -6,14 +6,14 @@ Launchpad Mail There are various kinds of emails in Launchpad: 1. Mailing lists (represented by Launchpad teams). A mailing list has an - address \`TEAM_NAME@lists.launchpad.net`, archives - (`https://lists.launchpad.net/TEAM_NAME`), and an administrative - interface (`https://lists.canonical.com/mailman/admin/TEAM_NAME`). + address ``TEAM_NAME@lists.launchpad.net``, archives + (``https://lists.launchpad.net/TEAM_NAME``), and an administrative + interface (``https://lists.canonical.com/mailman/admin/TEAM_NAME``). Launchpad uses `Mailman `__ to process these kinds of mails. 2. Emails sent from one user to another (that is, an email sent "by" Launchpad, but really sent by user Alice when Alice uses the - \`https://edge.launchpad.net/~barry/+contactuser\` form to contact + ``https://edge.launchpad.net/~barry/+contactuser`` form to contact user Barry. 3. Emails sent by Launchpad itself, such as emails sent to subscribers when a bug is changed. From e952eb0aa98fe33a20517b82640d88c2c6a8fc5f Mon Sep 17 00:00:00 2001 From: gerryRcom Date: Mon, 15 Jul 2024 20:17:36 +0100 Subject: [PATCH 21/36] oda spelling check on branches doc --- .custom_wordlist.txt | 2 ++ .sphinx/spellingcheck.yaml | 2 +- explanation/branches.rst | 10 +++++----- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 277333e..a14afa7 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -303,6 +303,7 @@ POFileTranslator PolicyandProcess PolicyAndProcess PolicyForDocumentingCustomDistributions +poller POMsgID pooler portlets @@ -485,6 +486,7 @@ wgrant's whitespace wildcherry Wishlist +WorkingWithDbDevel WorkingWithReviews worktrees wsgi diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index 4dd8da0..d8223f3 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/branches/index.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html + - _build/**/*.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html pipeline: - pyspelling.filters.html: comments: false diff --git a/explanation/branches.rst b/explanation/branches.rst index 9d1323a..360b19c 100644 --- a/explanation/branches.rst +++ b/explanation/branches.rst @@ -54,9 +54,9 @@ It is also possible to submit directly to the **db-devel** branch. Let's Try That in Words ----------------------- -Database changes can be destabilizing to other work, so we isolate them +Database changes can be destabilising to other work, so we isolate them out into a separate branch (**db-devel**). Then there are two arenas for -stabilizing changes for deployment: **stable** (which ends up on +stabilising changes for deployment: **stable** (which ends up on `qastaging `__ and is fed from the **master** branch), and **db-stable** (which ends up on `staging `__ and is fed from the @@ -79,7 +79,7 @@ In summary: with **db-devel**, sent as if it came from the Launchpad list. The Launchpad list will be informed of merge failures, and Launchpad developers will collectively be responsible for correcting them. - (***TODO: is this some internal list? Hmmm.***) + (***TODO: is this some internal list?***) - Staging runs **db-stable**; qastaging runs **stable**. We will deploy production DB schema changes from **db-stable**. (After a deployment, @@ -155,8 +155,8 @@ Now create and land a merge proposal. FAQ --- -Can I land a testfix before buildbot has finished a test run that has failed or will fail? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Can I land a test fix before buildbot has finished a test run that has failed or will fail? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Yes you can, and please do if appropriate, because this will mean that other developers will not encounter a broken tree at all. From 7708a5fa7b6ed6c0856fa2722f917228c9127eb0 Mon Sep 17 00:00:00 2001 From: Adriaan Van Niekerk <144734475+sfadriaan@users.noreply.github.com> Date: Wed, 17 Jul 2024 08:13:34 +0200 Subject: [PATCH 22/36] Spell check (URL traversal + Navigation Menus) (#87) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove Navigation Menu page from exclusion * Add words to be excluded from spell check * Correct spelling errors * Remove "url-traversal" from exclusion list * Update list of accepted words * Update formatting and correct errors --------- Co-authored-by: Jürgen Gmach --- .custom_wordlist.txt | 16 ++++++++++++++++ .sphinx/spellingcheck.yaml | 2 +- explanation/navigation-menus.rst | 6 +++--- explanation/url-traversal.rst | 6 +++--- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index a14afa7..88c9329 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -57,6 +57,7 @@ changelog changeset changesets charmcraft +CanonicalUrlData Cmd CMDLINE Cobol @@ -65,6 +66,8 @@ Codehosting codeimport codeimportscheduler CodeReviewChecklist +codereviewmessage +CodeReviewMessageView config configs ConfiguringWebApplications @@ -174,6 +177,10 @@ https https iframe iharness +IBranchMergeProposal +IBranchTarget +ICanonicalUrlData +ICodeReviewMessage IMailDelivery IMailer importances @@ -183,6 +190,7 @@ initialized integrations io ip +IPerson IPv IPython irc @@ -202,9 +210,11 @@ LandingChanges langpack LaunchpadAuthentication LaunchpadDatabaseRevision +LaunchpadFormView LaunchpadProductionStatus LaunchpadObjectFactory LaunchpadPpa +LaunchpadView lazr libera libgit @@ -255,6 +265,8 @@ mockups mojo mozilla natively +NavigationMenu +NavigationMenus né newsampledata NPM @@ -357,6 +369,7 @@ RESTful rocketfuel rollout rollouts +rootsite rosetta RPC rst @@ -401,6 +414,7 @@ SSO StagingServer standardized stdin +stepto steve stg StormMigrationGuide @@ -469,6 +483,8 @@ untriaged untrusted upstreams unsuffixed +url +urls userbase VBScript vbuilder diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index d8223f3..d21f119 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/url-traversal/index.html|!_build/explanation/navigation-menus/index.html|!_build/explanation/storm-migration-guide/index.html + - _build/**/*.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/storm-migration-guide/index.html pipeline: - pyspelling.filters.html: comments: false diff --git a/explanation/navigation-menus.rst b/explanation/navigation-menus.rst index 1bd2948..775fbc4 100644 --- a/explanation/navigation-menus.rst +++ b/explanation/navigation-menus.rst @@ -4,9 +4,9 @@ Navigation menus .. include:: ../includes/important_not_revised.rst When linking different views in Launchpad page templates it is recommend -to use the !NavigationMenu attached to each facet of that object. +to use the NavigationMenu attached to each facet of that object. -The !NavigationMenus are defined in the *browser* code. +The NavigationMenus are defined in the *browser* code. An object can have multiple *facets*. For example IPerson has a 'code', 'overview', 'translation' .. etc facets. @@ -37,7 +37,7 @@ An will only return the text without the anchor tag. -From withing a page template, you can use the following TAL expresion to +From withing a page template, you can use the following TAL expression to generate a link: :: diff --git a/explanation/url-traversal.rst b/explanation/url-traversal.rst index 18c6f34..f900c0a 100644 --- a/explanation/url-traversal.rst +++ b/explanation/url-traversal.rst @@ -42,7 +42,7 @@ This specifies: 2. attribute_to_parent defines the attribute of this interface that refers to the parent interface. Remember, we are starting from a leaf, and working back to the root URL. -3. We are adding comments/${id } to the path of the parent Interface. +3. We are adding comments/${id} to the path of the parent Interface. Where id is the id field of the instance. 4. rootsite is the subdomain this URL should be rooted at @@ -84,8 +84,8 @@ Next, you need to implement the factory: The function decorators helps reduce the ZCML needed for registration, they specify: -1. The interface that the adapter will provide: \`ICanonicalUrlData`. -2. The objects that the adapter works with: \`IBranchTarget`. +1. The interface that the adapter will provide: ``ICanonicalUrlData``. +2. The objects that the adapter works with: ``IBranchTarget``. Note that this is using the context of the view to get the ICanonicalUrlData. If it were only using the view, you'd get infinite From a539b047d012d5078b097041d9072937d2247704 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Wed, 17 Jul 2024 11:10:59 +0200 Subject: [PATCH 23/36] Remove "Security Policy" from exclusion list --- .sphinx/spellingcheck.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index d21f119..65f3211 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/code/index.html|!_build/explanation/security-policy/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/storm-migration-guide/index.html + - _build/**/*.html|!_build/explanation/code/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/storm-migration-guide/index.html pipeline: - pyspelling.filters.html: comments: false From 9eb17247c1100dc7c23dcb2a0275064ed1dc7a19 Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Wed, 17 Jul 2024 11:11:13 +0200 Subject: [PATCH 24/36] Add accepted words --- .custom_wordlist.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 88c9329..230bb34 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -4,6 +4,7 @@ adapter's AddingLaunchpadCelebrity analyze analyzer +AnyPerson api AppleApplications appmanifest @@ -15,6 +16,7 @@ artifacts aspirational AssertionsInLaunchpad attrgetter +AttributeError auditable auth authorization @@ -142,6 +144,7 @@ fastnodowntime favicons FK flavor +ForbiddenAttribute FooBar foofunc formatter @@ -380,6 +383,7 @@ SafariWebContent screencast ScreenCasts sdist +SecurityProxy segfaulted sendmail SendmailMailer @@ -479,6 +483,7 @@ unauthorized Uncomment unittest unobvious +unproxied untriaged untrusted upstreams From 06401ea4f554bd8eff483a03c5dea2508f942bdd Mon Sep 17 00:00:00 2001 From: Adriaan van Niekerk Date: Wed, 17 Jul 2024 11:13:05 +0200 Subject: [PATCH 25/36] Correct spelling errors --- explanation/security-policy.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/explanation/security-policy.rst b/explanation/security-policy.rst index a23472a..1906f92 100644 --- a/explanation/security-policy.rst +++ b/explanation/security-policy.rst @@ -7,7 +7,7 @@ Launchpad uses "permission" to control access to views, object attributes and object methods. Permission are granted based on the context object type (its interface) -by an ``IAuthorization`` adapters. Traditionally these adapters have +by an ``IAuthorization`` adaptors. Traditionally these adaptors have all been defined in the ``canonical.launchpad.security`` module, but they are being moved out in the ``security.py`` module of the specific application. @@ -76,7 +76,7 @@ permission is assigned to a given attribute, attempting to access it is **forbidden**. If there is a permission assigned to it, and the current user does not have that permission, attempting it is **unauthorized**. If the current user has the correct permission, then that attribute will -behave almost exactly the same as it would on an un-proxied object. The +behave almost exactly the same as it would on an unproxied object. The main difference is that any return values may be wrapped in a SecurityProxy as well. From 8500de5b96e4949b23d6c646c65272b9c8180424 Mon Sep 17 00:00:00 2001 From: Adriaan Van Niekerk <144734475+sfadriaan@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:05:04 +0200 Subject: [PATCH 26/36] Check Spelling (Database Performance page) (#91) * Remove database performance page from exclusion * Add accepted words * Correct spelling errors --- .custom_wordlist.txt | 28 ++++++++++++++++++++++++++++ .sphinx/spellingcheck.yaml | 2 +- explanation/database-performance.rst | 6 +++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 230bb34..f48b161 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -32,6 +32,8 @@ backported backtrace Backtraces barfunc +BatchNavigator +batchnavigator Becca BEM Bionic's @@ -90,6 +92,7 @@ Dalia's Danilo DatabaseSchemaChangesProcess DatabaseSetup +DateTimeJSONEncoder DatetimeUsageGuide DavidAllouche dbpatches @@ -104,9 +107,12 @@ debhelper debian DebuggingWithGdb debversion +DecoratedResultSet +DecoratedResultSets defense defense deps +Desc dev dh dia @@ -147,6 +153,7 @@ flavor ForbiddenAttribute FooBar foofunc +foos formatter formlib FreshLogs @@ -178,6 +185,7 @@ html http https https +indexable iframe iharness IBranchMergeProposal @@ -197,7 +205,9 @@ IPerson IPv IPython irc +IRangeFactory IRCMeetings +iter javascript JavascriptUnitTesting Javadoc @@ -206,6 +216,7 @@ jenkins jinja js JScript +JSONEncoder keyring kiko kompare @@ -222,6 +233,7 @@ lazr libera libgit lifecycle +ListRangeFactory listdir LivePatching LiveScript @@ -337,8 +349,13 @@ ppa PPA PQM pre +pre_iter_hook +prejoining +prejoins PreMergeReviews prepending +prepopulate +prepopulation preprocessed prioritize prober @@ -357,6 +374,7 @@ PyPI qa QAProcess qastaging +QueryCollector queueddelivery queuedDelivery QueuedDelivery @@ -369,6 +387,8 @@ realizes ReleaseCycles repo RESTful +resultset +ResultSets rocketfuel rollout rollouts @@ -392,6 +412,7 @@ setUp setupDTCBrowser setupRosettaExpertBrowser setuptools +simplejson simplestreams slony Slony @@ -410,6 +431,8 @@ soyuz specialized specializes sql +SQLBase +SQLObjectResultSets SRE SREs srv @@ -422,6 +445,8 @@ stepto steve stg StormMigrationGuide +StormRangeFactory +StormStatementRecorder stubmailer stubMailer StyleGuides @@ -430,6 +455,7 @@ subclassing subdirectory subprocess subproject +subselects sudo SuggestivePOTemplate summarized @@ -443,6 +469,7 @@ syntaxes systemd TableRenamePatch talisker +TAs TeamEmail templating testability @@ -498,6 +525,7 @@ virtualenv virtualenvs VPN VPOTExport +webapp webdav webhook webservice diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index 65f3211..0c491d9 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/code/index.html|!_build/explanation/database-performance/index.html|!_build/explanation/storm-migration-guide/index.html + - _build/**/*.html|!_build/explanation/code/index.html|!_build/explanation/storm-migration-guide/index.html pipeline: - pyspelling.filters.html: comments: false diff --git a/explanation/database-performance.rst b/explanation/database-performance.rst index af5c204..fb66b14 100644 --- a/explanation/database-performance.rst +++ b/explanation/database-performance.rst @@ -88,7 +88,7 @@ can be used to do this in combination with a Be sure to clear these caches with a Storm invalidation hook, to avoid test suite fallout. Objects are not reused between requests on the -appservers, so we're generally safe there. (Our storm and sqlbase +appservers, so we're generally safe there. (Our storm and SQLBase classes within the Launchpad tree have these hooks, so you only need to manually invalidate if you are using storm directly). @@ -137,7 +137,7 @@ one of these tools. - StormStatementRecorder, LP_DEBUG_SQL=1, LP_DEBUG_SQL_EXTRA=1, - QueryCollector. In extremis you can also turn on statement logging in + QueryCollector. In extremes you can also turn on statement logging in postgresql. [Note: please add more detail if you are reading this and have the time and knowledge.] - Raise an exception at a convenient point, to cause a real OOPS. @@ -146,7 +146,7 @@ Efficient batching of SQL result sets: StormRangeFactory -------------------------------------------------------- Batched result sets are rendered via the class -canonical.launchpad.webapp.bachting.BatchNavigator. (This class is a +canonical.launchpad.webapp.batching.BatchNavigator. (This class is a thin wrapper around lazr.batchnavigator.BatchNavigator.) BatchNavigator delegates the retrieval of batches from a result set to From ff237feec8ee9fd6530ccd0aa1f940939ddedee0 Mon Sep 17 00:00:00 2001 From: Adriaan Van Niekerk <144734475+sfadriaan@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:44:29 +0200 Subject: [PATCH 27/36] Check Spelling errors (Storm migration guide) (#92) * Remove Storm Migration Guide from exclusion list * Update code inline formatting and correct spelling errors * Add accepted words --- .custom_wordlist.txt | 15 +++ .sphinx/spellingcheck.yaml | 2 +- explanation/storm-migration-guide.rst | 184 ++++++++++++-------------- 3 files changed, 104 insertions(+), 97 deletions(-) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index f48b161..23f61b6 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -43,6 +43,7 @@ boolean breakpoint browserconfig BrowserNotificationMessages +BugSubscription bugtracker BugWatch BugWatches @@ -95,6 +96,7 @@ DatabaseSetup DateTimeJSONEncoder DatetimeUsageGuide DavidAllouche +DBEnums dbpatches dbschema dbupgrade @@ -154,6 +156,8 @@ ForbiddenAttribute FooBar foofunc foos +FooSet +ForeignKey formatter formlib FreshLogs @@ -198,6 +202,7 @@ importances InformationInfrastructure infos initialized +instantiation integrations io ip @@ -259,6 +264,7 @@ lxd LXD's macOS macquarie +MailingListSet Mantic ManualCdImageMirrorProber matchers @@ -285,6 +291,7 @@ NavigationMenus né newsampledata NPM +NULLs OAuth OEM oid @@ -431,7 +438,11 @@ soyuz specialized specializes sql +sqlbuilder +SQL's SQLBase +SQLObject +SQLObject's SQLObjectResultSets SRE SREs @@ -518,6 +529,10 @@ unsuffixed url urls userbase +UtcDateTimeCol +Validators +validators +validator VBScript vbuilder ViewTests diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index 0c491d9..2f301f3 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/code/index.html|!_build/explanation/storm-migration-guide/index.html + - _build/**/*.html|!_build/explanation/code/index.html pipeline: - pyspelling.filters.html: comments: false diff --git a/explanation/storm-migration-guide.rst b/explanation/storm-migration-guide.rst index cb2e5f0..e61863c 100644 --- a/explanation/storm-migration-guide.rst +++ b/explanation/storm-migration-guide.rst @@ -6,7 +6,7 @@ Storm Migration Guide This guide explains how certain SQLObject concepts map to equivalent Storm concepts. It expects a level of familiarity in how SQLObject works (or at least how it is used in Launchpad). It is not a full tutorial on -how to use Storm either – see https://storm.canonical.com/Tutorial for +how to use Storm either - see https://storm.canonical.com/Tutorial for that. Differences @@ -30,8 +30,8 @@ can be used to refer to objects in multiple databases (or to objects in the same database over different DB connections, as you might want to do in tests). -There are two main ways to access the main store. One is explicitely via -the \`IStoreSelector\` utility: +There are two main ways to access the main store. One is explicitly via +the ``IStoreSelector`` utility: :: @@ -48,7 +48,7 @@ Use the master flavor if you need to update the objects. Use the slave flavor to offload a search to a replica database and don't mind the search being made on data a few seconds out of date. Use the default flavor if you don't need to make changes, but need an up to date copy of -the database (eg. most views, as the object you are viewing might just +the database (e.g. most views, as the object you are viewing might just have been created) - Launchpad will choose an appropriate flavor. The other method is from an existing object: @@ -62,7 +62,7 @@ The other method is from an existing object: The second form is often more convenient, and is preferred if you don't need to make updates and want them to play nicely with objects from an -unknown store (eg. passed in via your method parameters). +unknown store (e.g. passed in via your method parameters). Utility methods and Stores ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -77,9 +77,7 @@ uses the master store: - If you are doing a POST (which means your overall operation may write) - If you are doing a GET, but have recently written (which means the - slaves may - -``not have your latest changes).`` + slaves may not have your latest changes). So the only times you'll run into trouble are if: @@ -87,9 +85,7 @@ So the only times you'll run into trouble are if: - a GET operation relies on data that was written to the database by another GET - a GET operation relies on data that was written to the database by - another browser - -``instance.`` + another browser instance. We plan to address these issues better once we're using Python 2.5 and its support for **with** statements / context management. @@ -152,9 +148,9 @@ takes the class and the primary key of the object as arguments: Querying Objects ~~~~~~~~~~~~~~~~ -The equivalent of SQLObject's \`select`, \`selectBy`, \`selectOne`, -\`selectOneBy`, \`selectFirst\` and \`selectFirstBy\` methods is -\`Store.find()`. It acts quite similar to the equivalent SQLObject +The equivalent of SQLObject's ``select``, ``selectBy``, ``selectOne``, +``selectOneBy``, ``selectFirst`` and ``selectFirstBy`` methods is +``Store.find()``. It acts quite similar to the equivalent SQLObject methods, and the following are equivalent: :: @@ -167,12 +163,12 @@ methods, and the following are equivalent: Note that the "`.q.`" bit is not required in the second example. The first two versions are preferred to direct SQL since they allow Storm to determine which tables are being used in the query automatically. As -with SQLObject, no query is issued when executing \`find()`: that is +with SQLObject, no query is issued when executing ``find()``: that is delayed until you try to access the result set. -The behaviour of \`selectOne\` and \`selectFirst\` are covered by the -\`one\` and \`first\` methods on the result set. You can chain them with -the \`find\` call if it is appropriate: +The behaviour of ``selectOne`` and ``selectFirst`` are covered by the +``one`` and ``first`` methods on the result set. You can chain them with +the ``find`` call if it is appropriate: :: @@ -199,9 +195,9 @@ Unlike SQLObject, the ordering is applied to the result set rather than creating another one. The method does return the result set though, to make it possible to chain the calls when constructing a result set. Similar to SQLObject, a table can specify the default ordering for -results with the \`__storm_order__\` class attribute. +results with the ``__storm_order__`` class attribute. -See the \`storm.store.ResultSet\` doc strings and the Storm tutorial for +See the ``storm.store.ResultSet`` doc strings and the Storm tutorial for more details on what is possible. Defining Tables @@ -210,12 +206,12 @@ Defining Tables Some of the primary differences between SQLObject and Storm database class definitions are: -- Subclass from \`lp.services.database.stormbase.StormBase\` instead of - \`lp.services.database.sqlbase.SQLBase`. (Subclassing - \`storm.base.Storm\` also works in most cases, but \`StormBase\` adds - a \`storm_invalidate\` hook for cached properties.) -- Use the \`__storm_table__\` attribute to set the table name instead - of \`_table`. +- Subclass from ``lp.services.database.stormbase.StormBase`` instead of + ``lp.services.database.sqlbase.SQLBase``. (Subclassing + ``storm.base.Storm`` also works in most cases, but ``StormBase`` adds + a ``storm_invalidate`` hook for cached properties.) +- Use the ``__storm_table__`` attribute to set the table name instead + of ``_table``. - The primary key must be defined explicitly. This will usually look like: @@ -229,35 +225,35 @@ class definitions are: id = Int(primary=True) - The class should have a constructor if appropriate (some classes like - \`BugSubscription\` may not need one). Note that the constructor + ``BugSubscription`` may not need one). Note that the constructor should not usually add the object to a store -- leave that for a - \`FooSet.new()\` method, or let it be inferred by a relation. - **BarryWarsaw: what if there is no \`FooSet\` or relation? See + ``FooSet.new()`` method, or let it be inferred by a relation. + **Barry Warsaw: what if there is no ``FooSet`` or relation? See question below.** - Default result set ordering should be set using the - \`__storm_order__\` property rather than \`_defaultOrder`. + ``__storm_order__`` property rather than ``_defaultOrder``. - Use the column definition classes are found in \`storm.properties`, - and do not use the \`Col\` suffix. In general, they will follow + and do not use the ``Col`` suffix. In general, they will follow Python's type naming conventions rather than SQL's (e.g. TimeDelta rather than Interval). -- There is no equivalent of \`alternateID=True`. The \`Store.find()\` - method provides equivalent functionality to the \`byColumnName\` +- There is no equivalent of ``alternateID=True``. The ``Store.find()`` + method provides equivalent functionality to the ``byColumnName`` methods generated by this argument. - To specify that a column can not contain NULLs, use - \`allow_none=False\` rather than \`notNull=True`. Note that if NULLs - are found in such columns, \`NoneError\` will be raised. -- If no \`default\` is specified for a column, the database default - will be used. So \`default=DEFAULT\` or similar can be removed. -- Be sure your table has a \`PRIMARY KEY\` constraint defined, - otherwise your \`id\` column will not get set automatically and you - will get an \`IntegrityError\` from PostgreSQL. + ``allow_none=False`` rather than ``notNull=True``. Note that if NULLs + are found in such columns, ``NoneError`` will be raised. +- If no ``default`` is specified for a column, the database default + will be used. So ``default=DEFAULT`` or similar can be removed. +- Be sure your table has a ``PRIMARY KEY`` constraint defined, + otherwise your ``id`` column will not get set automatically and you + will get an ``IntegrityError`` from PostgreSQL. Foreign Key References ^^^^^^^^^^^^^^^^^^^^^^ -The equivalent of SQLObject's \`ForeignKey\` class is \`Reference`. A -Storm \`Reference\` property creates a relationship between a local -column and a remote column. Unlike \`ForeignKey`, it does not implicitly +The equivalent of SQLObject's ``ForeignKey`` class is ``Reference``. A +Storm ``Reference`` property creates a relationship between a local +column and a remote column. Unlike ``ForeignKey``, it does not implicitly create the FK column. So the following definitions are equivalent: :: @@ -273,8 +269,8 @@ create the FK column. So the following definitions are equivalent: The columns can be passed directly to Reference(), or can be passed as strings that are looked up on first use. -The \`Reference\` class is also used to replace SQLObject's -\`SingleJoin\` class: +The ``Reference`` class is also used to replace SQLObject's +``SingleJoin`` class: :: @@ -288,8 +284,8 @@ The \`Reference\` class is also used to replace SQLObject's Reference Sets ^^^^^^^^^^^^^^ -The \`SQLMultipleJoin\` and \`SQLRelatedJoin\` classes are replaced by -Storm's \`ReferenceSet`: +The ``SQLMultipleJoin`` and ``SQLRelatedJoin`` classes are replaced by +Storm's ``ReferenceSet``: :: @@ -307,16 +303,16 @@ Storm's \`ReferenceSet`: order_by=Person.name) While the SQLObject properties return plain result sets, the Storm -properties return \`BoundReferenceSet\` objects. Some differences +properties return ``BoundReferenceSet`` objects. Some differences include: -- \`add(obj)\` and \`remove(obj)\` methods are provided for adding and +- ``add(obj)`` and ``remove(obj)`` methods are provided for adding and removing objects from the set. These are roughly equivalent to the - automatic \`addFoo()\` and \`removeFoo()\` methods that SQLObject + automatic ``addFoo()`` and ``removeFoo()`` methods that SQLObject generates. For reference sets that join through a third table, Storm will take care of inserting and deleting rows as needed. -- A \`find()\` method is provided for searching for objects within the - reference set. This behaves a lot like \`Store.find()\` without the +- A ``find()`` method is provided for searching for objects within the + reference set. This behaves a lot like ``Store.find()`` without the first argument. Property Setters / Validators @@ -324,20 +320,20 @@ Property Setters / Validators SQLObject provided two ways of controlling how variables were set: -1. magic \`_set_columnName()\` methods. +1. magic ``_set_columnName()`` methods. 2. the validator argument on column definitions. Storm does not support magic methods but does have validators (albeit in a simpler form than SQLObject). A validator is a function that takes -\`(object, attr_name, new_value)\` as arguments and returns the value +``(object, attr_name, new_value)`` as arguments and returns the value that should be set. This allows validation to be performed on the new value (by raising an exception on bad values), and transformation of the -value if appropriate (by returning something other than \`new_value`). +value if appropriate (by returning something other than ``new_value``). -A validator can be set for a column with the \`validator\` argument in +A validator can be set for a column with the ``validator`` argument in the column definition. -You may notice some uses of \`storm_validator\` in code using the +You may notice some uses of ``storm_validator`` in code using the compatibility layer. As the compatibility layer does not implement the either of the SQLObject validation APIs, this was done to allow use of Storm validators without completely rewriting the definitions. @@ -346,7 +342,7 @@ Prejoins ^^^^^^^^ Storm's equivalent of prejoins is tuple finds. To select all products -that are part of \`launchpad-project\` and their owners, we can do: +that are part of ``launchpad-project`` and their owners, we can do: :: @@ -389,14 +385,14 @@ This result set will return (product, owner, driver) tuples. Direct SQL Queries ~~~~~~~~~~~~~~~~~~ -To perform direct SQL queries, we previously used the \`cursor()\` -function from \`lp.services.database.sqlbase\` to get a cursor on the +To perform direct SQL queries, we previously used the ``cursor()`` +function from ``lp.services.database.sqlbase`` to get a cursor on the connection being used by SQLObject. These uses should be converted to -use \`Store.execute()`, which will make sure pending changes have been +use ``Store.execute()``, which will make sure pending changes have been flushed to the database first in order to stay consistent. -This method returns a result object with \`get_one\` and \`get_all\` -methods that act like a cursor's \`fetchone\` and \`fetchall\` methods. +This method returns a result object with ``get_one`` and ``get_all`` +methods that act like a cursor's ``fetchone`` and ``fetchall`` methods. It also supports iteration. :: @@ -412,18 +408,18 @@ A good order to migrate code is: 1. Convert column properties to use the Storm syntax. This should be a no-op change, and not affect external code. -2. Convert \`ForeignKey()\` definitions to an appropriate pair of - \`Int()\` and \`Reference()\` definitions. -3. Convert \`sync()`, \`syncUpdate()`, \`destroySelf()`, etc calls to +2. Convert ``ForeignKey()`` definitions to an appropriate pair of + ``Int()`` and ``Reference()`` definitions. +3. Convert ``sync()``, ``syncUpdate()``, ``destroySelf()``, etc calls to Storm equivalents. -4. Convert uses of \`Class.select*()\` to use \`find()`. Note that you +4. Convert uses of ``Class.select*()`` to use ``find()``. Note that you lose prejoins support here, so use tuple finds as appropriate. Change queries to use Storm expressions rather than sqlbuilder expressions. -5. Convert \`SQLMultipleJoin\` and \`SQLRelatedJoin\` to - \`ReferenceSet()`. As this changes the API of the class a bit, it +5. Convert ``SQLMultipleJoin`` and ``SQLRelatedJoin`` to + ``ReferenceSet()``. As this changes the API of the class a bit, it will probably require changes external to the class. 6. Change the class to derive from - \`lp.services.database.stormbase.StormBase\` instead of \`SQLBase`. + ``lp.services.database.stormbase.StormBase`` instead of ``SQLBase``. This list is roughly ordered based on the locality of changes and based on dependencies between changes. @@ -532,45 +528,41 @@ Questions 12-Aug-2008 -- Some of our ForeignKey columns had notNull=True but Storm's Reference - class - -``does not accept allow_none=False keyword argument.`` +- Some of our ForeignKey columns had ``notNull=True`` but Storm's Reference + class does not accept ``allow_none=False`` keyword argument. -- - - - Put the \`allow_none=False\` on the \`Int\` rather than on the - \`Reference`. + - Put the ``allow_none=False`` on the ``Int`` rather than on the + ``Reference``. .. raw:: html - How to actually convert a UtcDateTimeCol to a DateTime? For now, I'm - using + using a DateTime with ``tzinfo=pytz.timezone('UTC')`` keyword argument. + Also, does ``default=UTC_NOW`` still work? -| ``a DateTime with tzinfo=pytz.timezone('UTC') keyword arg.  Also, does`` -| ``default=UTC_NOW still work?`` + - Use ``default_factory=datetime.utcnow`` instead. -:literal:`bigjools: use `default_factory=datetime.utcnow` instead.` +.. raw:: html -- Can I still use EnumCol, or is there a better way to hook up with our + -``DBEnums?`` +- Can I still use EnumCol, or is there a better way to hook up with our DBEnums? -- + - Try ``lp.services.database.enumcol.DBEnum``. - - Try lp.services.database.enumcol.DBEnum. +.. raw:: html + + 03-Oct-2008 -- I'm still confused about the right way to add an object to a store. - If I'm - -| ``using native Storm APIs (as all new code should, right?) should I add a`` -| :literal:`Store.add() call my database object's `__init__()`?  That seems to be the` -| ``most straightforward translation of the SQLObject compatibility layer.  And`` -| ``if the answer is "yes", then how do I get the Store to use?  I could use`` -| :literal:`\`Store.of(someobj).add(self)` but `someobj` might not be in the right store.` -| :literal:`I could use the `getUtility()` trick, but it seems wrong that a database` -| :literal:`module should be importing an interface from `webapp`.` +- I'm still confused about the right way to add an object to a store. + If I'm using native Storm APIs (as all new code should, right?) should + I add a ``Store.add()`` call my database object's ``__init__()``? + That seems to be the most straightforward translation of the SQLObject compatibility layer. + And if the answer is "yes", then how do I get the Store to use? + I could use ``Store.of(someobj).add(self)`` but ``someobj`` might not be in the right store. + I could use the ``getUtility()`` trick, but it seems wrong that a database + module should be importing an interface from ``webapp``. From f238c1f4e2322d5ad31c9d86615108856c9f8dfc Mon Sep 17 00:00:00 2001 From: gerryRcom Date: Wed, 24 Jul 2024 06:01:27 +0100 Subject: [PATCH 28/36] oda spelling check on code doc (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * oda spelling check on code doc * oda spelling check on code doc * Update .custom_wordlist.txt --------- Co-authored-by: Jürgen Gmach --- .custom_wordlist.txt | 15 +++++++++++++++ .sphinx/spellingcheck.yaml | 2 +- explanation/code.rst | 12 ++++++------ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 23f61b6..3aab07e 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -43,6 +43,7 @@ boolean breakpoint browserconfig BrowserNotificationMessages +brz BugSubscription bugtracker BugWatch @@ -67,6 +68,7 @@ Cmd CMDLINE Cobol codebase +codehosting Codehosting codeimport codeimportscheduler @@ -83,9 +85,11 @@ crontab crontabs cryptographic cryptographically +CSCVS css Ctrl customisable +customisations customize CVE Dalia @@ -121,6 +125,7 @@ dia directDelivery DirectDelivery distro +distros Distro distroseries DistroSeries @@ -136,6 +141,7 @@ doctests docutils downstreams dtrt +dulwich EChangePolicy el else's @@ -168,6 +174,7 @@ fsyncs ftest fti functiondef +ganesha gangotri gina's geoip @@ -181,6 +188,7 @@ gunicorn gzip HackingLazrLibraries hba +hg hirsute's hostnames HSTS @@ -290,6 +298,7 @@ NavigationMenu NavigationMenus né newsampledata +nfs NPM NULLs OAuth @@ -354,6 +363,7 @@ POTMsgSet POTMsgSets ppa PPA +pqm PQM pre pre_iter_hook @@ -407,6 +417,7 @@ rsync rsyncing runtime SafariWebContent +scalability screencast ScreenCasts sdist @@ -419,6 +430,7 @@ setUp setupDTCBrowser setupRosettaExpertBrowser setuptools +SFTP simplejson simplestreams slony @@ -466,11 +478,13 @@ subclassing subdirectory subprocess subproject +subvertpy subselects sudo SuggestivePOTemplate summarized svg +svn symlinked symlinks synchronize @@ -536,6 +550,7 @@ validator VBScript vbuilder ViewTests +virt virtualenv virtualenvs VPN diff --git a/.sphinx/spellingcheck.yaml b/.sphinx/spellingcheck.yaml index 2f301f3..fc160bf 100644 --- a/.sphinx/spellingcheck.yaml +++ b/.sphinx/spellingcheck.yaml @@ -9,7 +9,7 @@ matrix: - .custom_wordlist.txt output: .sphinx/.wordlist.dic sources: - - _build/**/*.html|!_build/explanation/code/index.html + - _build/**/*.html pipeline: - pyspelling.filters.html: comments: false diff --git a/explanation/code.rst b/explanation/code.rst index 7a7caa0..79fafbc 100644 --- a/explanation/code.rst +++ b/explanation/code.rst @@ -13,7 +13,7 @@ system. The major sub-systems are: - The `git` and `bzr` / `brz` clients (neither of which is part of Launchpad, but their behaviours are important to us) -- Connectivity to Launchpad (git, git+ssh, and https for Git; sftp and +- Connectivity to Launchpad (git, git+ssh, and https for Git; SFTP and bzr+ssh for Bazaar) - Hosting infrastructure - The underlying object model @@ -29,7 +29,7 @@ Each of these subsystems also have multiple moving parts and some have other asynchronous jobs associated with them. The `codehosting overview diagram `__ -summarizes how some of these systems interact. +summarises how some of these systems interact. You can `run the codehosting system locally `__. @@ -86,14 +86,14 @@ Apache handles the HTTP routing using a number of mod-rewrite rules. '''Parts [and responsibilities] ''' -- HTTP apache configuration [shared with LOSAs] +- HTTP Apache configuration [shared with LOSAs] - branch location rewrite script (called by mod-rewrite rule) - ssh server - - authentication - - sftp implementation + - SFTP implementation - smart server launching - smart server @@ -182,7 +182,7 @@ into Git repositories in Launchpad. - - - cscvs for CVS (and legacy Subversion imports) + - CSCVS for CVS (and legacy Subversion imports) - bzr-svn and subvertpy for all new Subversion imports - bzr-git and dulwich for git - bzr-hg for mercurial imports @@ -192,7 +192,7 @@ Git repository source code browser (cgit) Launchpad uses `cgit `__ to provide a web view of the repository contents. We use an unmodified package of -\`cgit`; Launchpad's customizations are in +\`cgit`; Launchpad's customisations are in `turnip.pack.http `__. Bazaar branch source code browser (loggerhead) From d733640eefadd6ba1dc778e2d475a3c5fae01916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Gmach?= Date: Fri, 26 Jul 2024 14:55:52 +0200 Subject: [PATCH 29/36] Add reference to fetch service release spec --- reference/services/fetch-service.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/reference/services/fetch-service.rst b/reference/services/fetch-service.rst index 8a108e0..8fc1f02 100644 --- a/reference/services/fetch-service.rst +++ b/reference/services/fetch-service.rst @@ -99,6 +99,11 @@ Deployment We deploy the fetch service using the specs defined in `fetch service mojo specs `_. +In order to be able to evaluate new fetch service versions, we use different +Snap channels for qastaging and production, so we are able to +test new releases. This information is both defined in above mentioned mojo +specs, and in `ST118 fetch service release process `_. + Qastaging ~~~~~~~~~ For qastaging deployment, SSH into From 5b17edb6e789f1452e6a0477015e850c711e6ce5 Mon Sep 17 00:00:00 2001 From: Yuliy Date: Tue, 30 Jul 2024 08:09:50 +0100 Subject: [PATCH 30/36] Migrating `How to develop with Buildd` (#95) * Migrating `How to develop with Buildd` From https://dev.launchpad.net/Soyuz/HowToDevelopWithBuildd * Adding How to use Soyuz locally Need to add https://dev.launchpad.net/Soyuz/HowToUseSoyuzLocally since it is called by How to develop with Buildd --------- Co-authored-by: Yuliy Schwartzburg --- .custom_wordlist.txt | 111 ++++++++------ how-to/develop-with-buildd.rst | 270 +++++++++++++++++++++++++++++++++ how-to/getting-started.rst | 2 + how-to/use-soyuz-locally.rst | 140 +++++++++++++++++ 4 files changed, 473 insertions(+), 50 deletions(-) create mode 100644 how-to/develop-with-buildd.rst create mode 100644 how-to/use-soyuz-locally.rst diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 3aab07e..735acc0 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -27,8 +27,8 @@ autogenerated backend backends backport -backports backported +backports backtrace Backtraces barfunc @@ -49,13 +49,16 @@ bugtracker BugWatch BugWatches bugzilla +buildable buildbot buildd builddeb bzr bzr's -centralized +Canonical's +CanonicalUrlData Centralized +centralized cfg cgroup cgroups @@ -63,13 +66,12 @@ changelog changeset changesets charmcraft -CanonicalUrlData Cmd CMDLINE Cobol codebase -codehosting Codehosting +codehosting codeimport codeimportscheduler CodeReviewChecklist @@ -122,20 +124,20 @@ Desc dev dh dia -directDelivery DirectDelivery +directDelivery +Distro distro distros -Distro -distroseries DistroSeries +distroseries distutils DNS DocFileSuite -docstring Docstring -docstrings +docstring Docstrings +docstrings doctest doctests docutils @@ -158,11 +160,11 @@ fastnodowntime favicons FK flavor -ForbiddenAttribute FooBar foofunc foos FooSet +ForbiddenAttribute ForeignKey formatter formlib @@ -176,11 +178,11 @@ fti functiondef ganesha gangotri -gina's -geoip geoIP +geoip getFeatureFlag gettext +gina's github globals GPG @@ -191,24 +193,25 @@ hba hg hirsute's hostnames -HSTS howto +HSTS html http https https -indexable -iframe -iharness IBranchMergeProposal IBranchTarget ICanonicalUrlData ICodeReviewMessage +iframe +iharness IMailDelivery IMailer importances +indexable InformationInfrastructure infos +init initialized instantiation integrations @@ -217,13 +220,13 @@ ip IPerson IPv IPython -irc IRangeFactory +irc IRCMeetings iter +Javadoc javascript JavascriptUnitTesting -Javadoc jenkaas jenkins jinja @@ -238,16 +241,16 @@ langpack LaunchpadAuthentication LaunchpadDatabaseRevision LaunchpadFormView -LaunchpadProductionStatus LaunchpadObjectFactory LaunchpadPpa +LaunchpadProductionStatus LaunchpadView lazr libera libgit lifecycle -ListRangeFactory listdir +ListRangeFactory LivePatching LiveScript logpoints @@ -255,8 +258,8 @@ logrotate lookup lookups LOSA -losas LOSAs +losas lp Lp's lpbuildbot @@ -281,8 +284,8 @@ matic maximize mbox mboxMailer -memcache MemCache +memcache milestoneoverlay minified minifies @@ -296,12 +299,14 @@ mozilla natively NavigationMenu NavigationMenus -né newsampledata nfs NPM +NTP NULLs +né OAuth +OCI OEM oid ok @@ -321,19 +326,19 @@ os OSA OWASP OWASP's -pagespeed PageSpeed +pagespeed pagetest -pagetests PageTests +pagetests pamola PatchSubmission pdb pentested performant pgbouncer -pgsql pgSQL +pgsql pipx plaintext png @@ -343,30 +348,30 @@ POExportRequest POFile POFiles POFileTranslator -PolicyandProcess PolicyAndProcess +PolicyandProcess PolicyForDocumentingCustomDistributions poller POMsgID pooler portlets -postgresql -PostgreSQL PostGreSQL +PostgreSQL +postgresql POSTs -POTs -POTExport POTemplate POTemplateSharingSubset -POTranslation +POTExport POTMsgSet POTMsgSets -ppa +POTranslation +POTs PPA -pqm +ppa +PPAs PQM +pqm pre -pre_iter_hook prejoining prejoins PreMergeReviews @@ -374,6 +379,7 @@ prepending prepopulate prepopulation preprocessed +pre_iter_hook prioritize prober proc @@ -392,9 +398,9 @@ qa QAProcess qastaging QueryCollector -queueddelivery -queuedDelivery QueuedDelivery +queuedDelivery +queueddelivery quickref quickstart rc @@ -402,6 +408,7 @@ rctp realfavicongenerator realizes ReleaseCycles +repl repo RESTful resultset @@ -433,26 +440,26 @@ setuptools SFTP simplejson simplestreams -slony Slony +slony smtp -smtpMailer SMTPMailer +smtpMailer snapshotting SolutionsLog sourcecode sourceforge -sourcepackage SourcePackage +sourcepackage SourcePackagename sourcetree soyuz specialized specializes sql -sqlbuilder SQL's SQLBase +sqlbuilder SQLObject SQLObject's SQLObjectResultSets @@ -470,16 +477,16 @@ stg StormMigrationGuide StormRangeFactory StormStatementRecorder -stubmailer stubMailer +stubmailer StyleGuides stylesheet subclassing subdirectory subprocess subproject -subvertpy subselects +subvertpy sudo SuggestivePOTemplate summarized @@ -502,8 +509,8 @@ testbed TestCase TestingJavaScript TestingWebServices -testMailer TestMailer +testMailer testr testrepository testrunner @@ -527,40 +534,44 @@ traversers triaged triaging tsearch -ubuntu Ubuntu +ubuntu UCT UltimateVimPythonSetup unauthorized +uncheck Uncomment unittest unobvious unproxied +unsuffixed untriaged untrusted upstreams -unsuffixed url urls userbase UtcDateTimeCol +validator Validators validators -validator VBScript vbuilder ViewTests virt virtualenv virtualenvs +virtualized +VM +VMs VPN VPOTExport webapp webdav webhook webservice -webservices webservice's +webservices wgrant's whitespace wildcherry @@ -568,8 +579,8 @@ Wishlist WorkingWithDbDevel WorkingWithReviews worktrees -wsgi WSGI +wsgi www Xenial Xenial's @@ -587,7 +598,7 @@ yuixhr yy zcml ZFS -zope Zope +zope Zope's zz diff --git a/how-to/develop-with-buildd.rst b/how-to/develop-with-buildd.rst new file mode 100644 index 0000000..c5aae6e --- /dev/null +++ b/how-to/develop-with-buildd.rst @@ -0,0 +1,270 @@ +How to develop with Buildd +========================== + +LXD VM Support +-------------- + +This is now on stable and allows for management of VMs with the same LXD CLI. + +For now, we need to use the ``images:`` source for images, rather than the ``ubuntu:`` images. The default ubuntu images do not have the LXD agent preinstalled. Once they do, this gets a bit simpler. + +It is also slightly simpler to use the ``ubuntu`` user, as it is already available in the image and doesn't require as many hoops jumped to get ``uid``/``gid`` mapping to work. + +Create a LXD profile for VMs +---------------------------- + +This is a convenience helper profile for VMs that will add users and run ``cloud-init`` for installing the LXD VM agent. It is not required and you can pass the options on the ``lxc`` command. + +The password for the user can be generated using: + +.. code-block:: sh + + $ mkpasswd -m sha-512 + +``mkpasswd`` lives in the ``whois`` package. + +For now, we are using the LXD provided cloud images as it has the LXD agent and ``cloud-init`` preinstalled. This requires a smaller LXD profile, but needs some extra commands afterwards. + +To create this run: + +.. code-block:: sh + + $ lxc profile create vm + +and then: + +.. code-block:: sh + + $ lxc profile edit vm + +.. code-block:: yaml + + name: vm + config: + limits.cpu: "2" + limits.memory: 4GB + user.vendor-data: | + #cloud-config + package_update: true + ssh_pwauth: yes + packages: + - openssh-server + - byobu + - language-pack-en + users: + - name: "ubuntu" + passwd: "" + lock_passwd: false + groups: lxd + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + ssh-import-id: + description: "" + devices: + config: + source: cloud-init:config + type: disk + eth0: + name: eth0 + nictype: bridged + parent: lxdbr0 + type: nic + work: + path: + source: + type: disk + + +Start the LXD VM +---------------- + +Start a VM via downloading the images: cloud image + +``lxc launch images:ubuntu//cloud -p vm -p default --vm`` + +This will take a while to settle. You can monitor its progress with ``lxd console ``. + +Once it has complete cloud-init, you should then see an IP assigned in ``lxc list`` and be able to execute a bash shell with ``lxc exec bash``. + +Configure password and ssh +-------------------------- + +This should be done by the cloud-init config in the profile, but the package is not installed at the time that is run, so do it afterwards manually: + +.. code-block:: sh + + $ lxc exec sudo passwd ubuntu + $ lxc exec --user 1000 "/usr/bin/ssh-import-id" + + +This will not be required once we can use the ``ubuntu:`` image source in LXD. + +Launchpad Buildd +---------------- + +We'll need a clone of this and then build and install it for running. + +Branch +------ + +.. code-block:: sh + + $ sudo apt install git + $ git clone https://git.launchpad.net/launchpad-buildd + +Install dependencies +-------------------- + +.. code-block:: sh + + $ cd launchpad-buildd + $ sudo apt-add-repository ppa:launchpad/ubuntu/buildd-staging + $ sudo apt-add-repository ppa:launchpad/ubuntu/ppa + $ vi /etc/apt/sources.list.d/launchpad-ubuntu-ppa-bionic.list + $ sudo apt update + $ sudo apt build-dep launchpad-buildd fakeroot + $ sudo apt install -f + +Note: if ``fakeroot`` can't be found try: + +.. code-block:: sh + + $ sudo sed -Ei 's/^# deb-src /deb-src /' /etc/apt/sources.list + $ sudo apt-get update + $ sudo apt build-dep launchpad-buildd fakeroot + $ sudo apt install -f + +Make and install the package +---------------------------- + +.. code-block:: sh + + $ cd launchpad-buildd + $ make + $ cd .. + $ sudo dpkg -i ./python3-lpbuildd__all.deb ./launchpad-buildd__all.deb + +Run the buildd +-------------- + +Edit ``/etc/launchpad-buildd/default`` and change ``ntphost`` to something valid (``ntp.ubuntu.com`` should work) + +.. code-block:: sh + + $ sudo mkdir -p /var/run/launchpad-buildd + $ sudo chown ubuntu: /var/run/launchpad-buildd + $ cd launchpad-buildd + $ /usr/bin/python3 /usr/bin/twistd --no_save --pidfile /var/run/launchpad-buildd/default.pid --python /usr/lib/launchpad-buildd/buildd-slave.tac -n + +Making changes +-------------- + +The package is installed as a system deb, so to make changes you will need to rebuild and reinstall the package following the 'Make and install' section. + +Testing +------- + +You probably want the next section (:ref:`Configuring Launchpad `) at this point, but if you are doing any buildd development and need to test your changes without having to have the whole system running, you can use the XML-RPC interface to cause builds to happen. + +Getting a base image +-------------------- + +First, we need a base image to use for the builds. Usually this is pulled as part of a build, but if we don't have Launchpad involved, we need to set this up manually. + +.. code-block:: sh + + $ git clone https://git.launchpad.net/ubuntu-archive-tools + $ sudo apt install python3-launchpadlib python3-ubuntutools + $ ./manage-chroot -s bionic -a amd64 get + $ sha256sum livecd.ubuntu-base.rootfs.tar.gz/home/buildd/filecache-default + $ mv livecd.ubuntu-base.rootfs.tar.gz + $ sudo cp /home/buildd/filecache-default + $ sudo chown buildd: /home/buildd/filecache-default/ + +Running a build +--------------- + +You can try running a build via the XML-RPC interface. Start a Python/IPython repl and run. + +.. code-block:: python + + import xmlrpclib + proxy = xmlrpclib.ServerProxy("http://localhost:8221/rpc") + proxy.status() + +Assuming that works, a sample build can be created using (relying on the OCI capabilities being merged into launchpad-buildd): + +.. code-block:: python + + proxy.build('1-3', 'oci', '', {}, {'name': 'test-build', 'series': 'bionic', 'arch_tag': 'amd64', 'git_repository': 'https://github.com/tomwardill/test-docker-repo.git', 'archives': ['deb http://archive.ubuntu.com/ubuntu bionic main restricted', 'deb http://archive.ubuntu.com/ubuntu bionic-updates main restricted', 'deb http://archive.ubuntu.com/ubuntu bionic universe']}) + +.. _configuring-launchpad: + +Configuring Launchpad +--------------------- + +Change ``https://launchpad.test/ubuntu/+pubconf`` as admin from ``archive.launchpad.test`` to ``archive.ubuntu.com``. + +In ``launchpad/launchpad/configs/development/launchpad-lazr.conf`` change: + +1: ``git_browse_root`` from ``https://git.launchpad.test/`` to ``http://git.launchpad.test:9419/`` + +2: ``git_ssh_root`` from ``git+ssh://git.launchpad.test/`` to ``git+ssh://git.launchpad.test:9422/`` + +3: ``builder_proxy_host`` from ``snap-proxy.launchpad.test`` to ``none`` + +4: ``builder_proxy_port`` from ``3128`` to ``none`` + +In ``launchpad/launchpad/lib/lp/services/config/schema-lazr.conf`` under the ``[oci]`` tag add a pair of private and public keys in order to be able to add OCI credentials, valid example below: + +1: ``registry_secrets_private_key``: ``U6mw5MTwo+7F+t86ogCw+GXjcoOJfK1f9G/khlqhXc4=`` + +2: ``registry_secrets_public_key``: ``ijkzQTuYOIbAV9F5gF0loKNG/bU9kCCsCulYeoONXDI=`` + + + +Running soyuz and adding data +----------------------------- + +First, you'll need to run some extra bits in Launchpad: + +.. code-block:: sh + + $ utilities/start-dev-soyuz.sh + $ utilities/soyuz-sampledata-setup.py + $ make run + +Image Setup +----------- + +Consult the 'Launchpad Configuration' section of :doc:`use-soyuz-locally` to do the correct ``manage-chroot`` dance to register an image with launchpad. Without this, you will have no valid buildable architectures. + +User setup +---------- + +It's convenient to add your user to the correct groups, so you can interact with it, without being logged in as admin. + + 1. Log in as admin + 2. Go to https://launchpad.test/~launchpad-buildd-admins and add your user + 3. Go to https://launchpad.test/~ubuntu-team and add your user + +Registering the buildd +---------------------- + +The buildd that you have just installed needs registering with Launchpad so that builds can be dispatched to it. + + 1. Go to https://launchpad.test/builders + + 2. Press 'Register a new build machine' + + 3. Fill in the details. + + - The 'URL' is probably ``http://:8221``. + + - You can make the builder be either virtualized or non-virtualized, but each option requires some extra work. Make sure you understand what's needed in the case you choose. + + - Most production builders are virtualized, which means that there's machinery to automatically reset them to a clean VM image at the end of each build. To set this up, ``builddmaster.vm_resume_command`` in your config must be set to a command which ``buildd-manager`` can run to reset the builder. If the VM reset protocol is 1.1, then the resume command is expected to be synchronous: once it returns, the builder should be running. If the VM reset protocol is 2.0, then the resume command is expected to be asynchronous, and the builder management code is expected to change the builder's state from ``CLEANING`` to ``CLEAN`` using the webservice once the builder is running. + + - Non-virtualized builders are much simpler: ``launchpad-buildd`` is cleaned synchronously over XML-RPC at the end of each build, and that's it. If you use this, then you must be careful not to run any untrusted code on the builder (since a ``chroot`` or container escape could compromise the builder), and you'll need to uncheck "Require virtualized builders" on any PPAs, live file systems, recipes, etc. that you want to be allowed to build on this builder. + + 4. After 30 seconds or so, the status of the builder on the builders page should be 'Idle'. This page does not auto-update, so refresh! diff --git a/how-to/getting-started.rst b/how-to/getting-started.rst index f9eaa75..34e6a17 100644 --- a/how-to/getting-started.rst +++ b/how-to/getting-started.rst @@ -8,3 +8,5 @@ Getting started getting running database-setup + develop-with-buildd + use-soyuz-locally diff --git a/how-to/use-soyuz-locally.rst b/how-to/use-soyuz-locally.rst new file mode 100644 index 0000000..67a9bb0 --- /dev/null +++ b/how-to/use-soyuz-locally.rst @@ -0,0 +1,140 @@ +How to use Soyuz locally +======================== + +.. include:: ../includes/important_not_revised.rst + +You're going to run Soyuz in a branch you create for the purpose. To get the whole experience, you'll also be installing the builder-side ``launchpad-buildd`` package on your system. + +Initial setup +------------- + + * Run ``utilities/start-dev-soyuz.sh`` to ensure that some Soyuz-related services are running. Some of these may already be running, in which case you'll get some failures that are probably harmless. Note: these services eat lots of memory. + * Once you've set up your test database, run ``utilities/soyuz-sampledata-setup.py -e you@example.com`` (where ''you@example.com'' should be an email address you own and have a GPG key for). This prepares more suitable sample data in the ``launchpad_dev`` database, including recent Ubuntu series. If you get a "duplicate key" error, ``make schema`` and run again. + * `make run` (or if you also want to use codehosting, `make run_codehosting`—some services may fail to start up because you already started them, but it shouldn't be a problem). + * Open https://launchpad.test/~ppa-user/+archive/test-ppa in a browser to get to your pre-made testing PPA. Log in with your own email address and password ''test''. This user has your GPG key associated, has signed the Ubuntu Code of Conduct, and is a member ``ubuntu-team`` (conferring upload rights to the primary archive). + + +Extra PPA dependencies +^^^^^^^^^^^^^^^^^^^^^^ + +The testing PPA has an external dependency on Lucid. If that's not enough, or not what you want: + + * Log in as `admin@canonical.com:test` (I suggest using a different browser so you don't break up your ongoing session). + * Open https://launchpad.test/~ppa-user/+archive/test-ppa/+admin + * Edit external dependencies. They normally look like: + + .. code-block:: sh + + deb http://archive.ubuntu.com/ubuntu %(series)s main restricted universe multiverse + + +Set up a builder +---------------- + +Set up for development +^^^^^^^^^^^^^^^^^^^^^^ + +If you are intending to do any development on ``launchpad-buildd`` or similar, you possibly want :doc:`develop-with-buildd`. + +Installation +^^^^^^^^^^^^ + + * Create a new focal virtual-machine with ``kvm`` (recommended), or alternatively a focal ``lxc`` container. If using lxc, set ``lxc.aa_profile = unconfined`` in ``/var/lib/lxc/container-name/config`` which is required to disable ``AppArmor`` support. + +If you are running Launchpad in a container, you will more than likely want your VMs network bridged on ``lxcbr0``. + +In your builder VM/lxc: + +.. code-block:: sh + + $ sudo apt-add-repository ppa:launchpad/buildd-staging + $ sudo apt-get update + $ sudo apt-get install launchpad-buildd bzr-builder quilt binfmt-support qemu-user-static + +Alternatively, launchpad-buildd can be built from ``lp:launchpad-buildd`` with ``dpkg-buildpackage -b``. + + * Edit ``/etc/launchpad-buildd/default`` and make sure ``ntphost`` points to an existing NTP server. You can check the `NTP server pool `_ to find one near you. + +To run the builder by default, you should make sure that other hosts on the Internet cannot send requests to it! Then: + +.. code-block:: sh + + $ echo RUN_NETWORK_REQUESTS_AS_ROOT=yes > /etc/default/launchpad-buildd + +Launchpad Configuration +^^^^^^^^^^^^^^^^^^^^^^^ + +From your host system: + + * Get an Ubuntu ``buildd chroot`` from Launchpad, using ``manage-chroot`` from ``https://code.launchpad.net/+branch/ubuntu-archive-tools|lp:ubuntu-archive-tools``: + * ``manage-chroot -s precise -a i386 get`` + * ``LP_DISABLE_SSL_CERTIFICATE_VALIDATION=1 manage-chroot -l dev -s precise -a i386 -f chroot-ubuntu-precise-i386.tar.bz2 set`` + * Register a new builder with the URL pointed to ``http://YOUR-BUILDER-IP:8221/`` (https://launchpad.test/builders/+new) + +Shortly thereafter, the new builder should report a successful status of 'idle'. + +If you want to test just the builder without a Launchpad instance, then, instead of using ``manage-chroot -l dev set``, you can copy the ``chroot`` tarball to ``/home/buildd/filecache-default/``; the base name of the file should be its ``sha1sum``. You'll need to copy any other needed files (e.g. source packages) into the cache in the same way. You can then send XML-RPC instructions to the builder as below. + +Drive builder through RPC +------------------------- + +With librarian running, fire up a ``python3`` shell and: + +.. code-block:: python + + from xmlrpc.client import ServerProxy + proxy = ServerProxy('http://localhost:8221/rpc') + proxy.ensurepresent('d267a7b39544795f0e98d00c3cf7862045311464', 'http://launchpad.test:58080/93/chroot-ubuntu-lucid-i386.tar.bz2', '', '') + proxy.build('1-1', 'translation-templates', 'd267a7b39544795f0e98d00c3cf7862045311464', {}, + {'archives': ['deb http://archive.ubuntu.com/ubuntu/ lucid main'], 'branch_url': '/home/buildd/gimp-2.6.8'}) + proxy.status() + proxy.clean() # Clean up if it failed + +You may have to calculate a new ``sha1sum`` of the ``chroot`` file. + +Upload a source to the PPA +-------------------------- + + * Run ``scripts/process-upload.py /var/tmp/txpkgupload`` (creates hierarchy) + * Add to ``~/.dput.cf``: + + .. code-block:: yaml + + [lpdev] + fqdn = ppa.launchpad.test:2121 + method = ftp + incoming = %(lpdev)s + login = anonymous + + * Find a source package ``some_source`` with a changes file ``some_source.changes`` + * ``dput -u lpdev:~ppa-user/test-ppa/ubuntu some_source.changes`` + * ``scripts/process-upload.py /var/tmp/txpkgupload -C absolutely-anything -vvv # Accept the source upload.`` + * If this is your first time running soyuz locally, you'll also need to publish ubuntu: ``scripts/publish-distro.py -C`` + * Within five seconds of upload acceptance, the buildd should start building. Wait until it is complete (the build page will say "Uploading build"). + * ``scripts/process-upload.py -vvv --builds -C buildd /var/tmp/builddmaster # Process the build upload.`` + * ``scripts/process-accepted.py -vv --ppa ubuntu # Create publishings for the binaries.`` + * ``scripts/publish-distro.py -vv --ppa # Publish the source and binaries.`` + * Note that private archive builds will not be dispatched until their source is published. + +Build an OCI image +------------------ + + * Using Launchpad interface, create a new OCI project and a recipe for it. + * On the OCI Recipe page, click on "Request builds", and select which architectures should be built on the following screen. + * Once you have requested a build, you should run ``./cronscripts/process-job-source.py -v IOCIRecipeRequestBuildsJobSource`` to create builds for that build request. + * If you have builders idle, this should start the build. Make sure to have run ``utilities/start-dev-soyuz.sh``, and check builders status at ``/builders`` page. + * Once the build finishes, run ``./scripts/process-upload.py -M --builds /var/tmp/builddmaster/`` on Launchpad to make it collect the built layers and manifests. + * At this point, in each build page you should have the files listed. + * You can upload the built image to registry by running ``./cronscripts/process-job-source.py -v IOCIRegistryUploadJobSource`` in Launchpad. You can manage the push rules at OCI recipe's page, clicking at "Edit push rules" button. + +Dealing with the primary archive +-------------------------------- + + * ``dput lpdev:ubuntu some_source.changes`` + * ``scripts/process-upload.py -vvv /var/tmp/txpkgupload`` + * Watch the output -- the upload might end up in NEW. + * If it does, go to the queue and accept it. + * Your builder should now be busy. Once it finishes, the binaries might go into NEW. Accept them if required. + * ``scripts/process-accepted.py -vv ubuntu`` + * ``scripts/publish-distro.py -vv`` + * The first time, add ``-C`` to ensure a full publication of the archive. From c61134c9a221bbefa7b93bf32d619da108295a44 Mon Sep 17 00:00:00 2001 From: Yuliy Schwartzburg Date: Fri, 26 Jul 2024 09:03:38 +0100 Subject: [PATCH 31/36] Add fix for pynacl to "Setting up and running Launchpad" * doc: update running launchpad how-to with pynacl fix for arm64 --- how-to/running.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/how-to/running.rst b/how-to/running.rst index 672a617..8c9074b 100644 --- a/how-to/running.rst +++ b/how-to/running.rst @@ -265,6 +265,8 @@ Finally, build the database schema (this may take several minutes): $ make schema +If you encounter an error while building Python wheels, see :ref:`pynacl-fix`. + Running ======= @@ -417,6 +419,27 @@ the bridge interface on your computer): sudo ufw allow in on lxdbr0 sudo ufw route allow in on lxdbr0 +.. _pynacl-fix: + +Error building Python wheels +---------------------------- + +When running ``make schema`` on some machines, ``pynacl`` `fails to build `_, leading to ``ERROR: Failed building wheel for pynacl``. + +If you encounter this issue, try running the following: + +.. code-block:: shell-session + + $ sudo apt install --yes libsodium-dev + +Then add the following line to the ``Makefile`` under the ``PIP_ENV`` commands: + +.. code-block:: shell-session + + PIP_ENV += SODIUM_INSTALL=system + +Then run `make schema` again. + Email ----- From d011a2eb6fd35ba4ef36cda29ef23814b906a239 Mon Sep 17 00:00:00 2001 From: Yuliy Schwartzburg Date: Wed, 7 Aug 2024 12:49:00 +0200 Subject: [PATCH 32/36] Fix the link checker due to a reference to a Google doc in edit mode --- reference/services/fetch-service.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reference/services/fetch-service.rst b/reference/services/fetch-service.rst index 8fc1f02..a37e0b4 100644 --- a/reference/services/fetch-service.rst +++ b/reference/services/fetch-service.rst @@ -102,7 +102,7 @@ We deploy the fetch service using the specs defined in In order to be able to evaluate new fetch service versions, we use different Snap channels for qastaging and production, so we are able to test new releases. This information is both defined in above mentioned mojo -specs, and in `ST118 fetch service release process `_. +specs, and in `ST118 fetch service release process `_. Qastaging ~~~~~~~~~ From f8d476990bff60d55b37716125ccef5399195ceb Mon Sep 17 00:00:00 2001 From: Simone Pelosi Date: Fri, 9 Aug 2024 17:07:38 +0200 Subject: [PATCH 33/36] Add XML-RPC interaction with staging builders --- how-to/develop-with-buildd.rst | 125 ++++++++++++++++++++++++++++++--- 1 file changed, 117 insertions(+), 8 deletions(-) diff --git a/how-to/develop-with-buildd.rst b/how-to/develop-with-buildd.rst index c5aae6e..7a660e4 100644 --- a/how-to/develop-with-buildd.rst +++ b/how-to/develop-with-buildd.rst @@ -169,20 +169,42 @@ You probably want the next section (:ref:`Configuring Launchpad + +To download a ``chroot`` image proceed as follows: + +.. code-block:: sh + $ ./manage-chroot -s bionic -a amd64 get - $ sha256sum livecd.ubuntu-base.rootfs.tar.gz/home/buildd/filecache-default - $ mv livecd.ubuntu-base.rootfs.tar.gz - $ sudo cp /home/buildd/filecache-default - $ sudo chown buildd: /home/buildd/filecache-default/ + $ sha1sum livecd.ubuntu-base.rootfs.tar.gz + $ mv livecd.ubuntu-base.rootfs.tar.gz + +Now we should copy the downloaded image to the builder file cache to be picked up during the build phase if you are running your builder locally. + +.. code-block:: sh + + $ sudo cp /home/buildd/filecache-default + $ sudo chown buildd: /home/buildd/filecache-default/ -Running a build ---------------- +Running a build locally +----------------------- You can try running a build via the XML-RPC interface. Start a Python/IPython repl and run. @@ -193,10 +215,11 @@ You can try running a build via the XML-RPC interface. Start a Python/IPython re proxy.status() Assuming that works, a sample build can be created using (relying on the OCI capabilities being merged into launchpad-buildd): +Note that if we are using the ``lxd`` backend we should specify that in our build ``args`` by adding ``"image_type": "lxd"``. .. code-block:: python - proxy.build('1-3', 'oci', '', {}, {'name': 'test-build', 'series': 'bionic', 'arch_tag': 'amd64', 'git_repository': 'https://github.com/tomwardill/test-docker-repo.git', 'archives': ['deb http://archive.ubuntu.com/ubuntu bionic main restricted', 'deb http://archive.ubuntu.com/ubuntu bionic-updates main restricted', 'deb http://archive.ubuntu.com/ubuntu bionic universe']}) + proxy.build('1-3', 'oci', '', {}, {'name': 'test-build', 'series': 'bionic', 'arch_tag': 'amd64', 'git_repository': 'https://github.com/tomwardill/test-docker-repo.git', 'archives': ['deb http://archive.ubuntu.com/ubuntu bionic main restricted', 'deb http://archive.ubuntu.com/ubuntu bionic-updates main restricted', 'deb http://archive.ubuntu.com/ubuntu bionic universe']}) .. _configuring-launchpad: @@ -268,3 +291,89 @@ The buildd that you have just installed needs registering with Launchpad so that - Non-virtualized builders are much simpler: ``launchpad-buildd`` is cleaned synchronously over XML-RPC at the end of each build, and that's it. If you use this, then you must be careful not to run any untrusted code on the builder (since a ``chroot`` or container escape could compromise the builder), and you'll need to uncheck "Require virtualized builders" on any PPAs, live file systems, recipes, etc. that you want to be allowed to build on this builder. 4. After 30 seconds or so, the status of the builder on the builders page should be 'Idle'. This page does not auto-update, so refresh! + +Running a build on qastaging +---------------------------- + +We can use XML-RPC to interact also with qastaging/staging builders. +First of all, we should be able to ssh into our bastion and ssh into the ``launchpad-buildd-manager`` unit since it's the one that has the firewall rules to talk with builders. + +Then we should follow the same procedure to get the correct ``sha1sum`` of a backend image. + +.. code-block:: sh + + $ ./manage-chroot -s bionic -a amd64 -i lxd get + $ sha1sum livecd.ubuntu-base.lxd.tar.gz + +We want to call the ``manage-chroot`` command to download the LXD image from our database, in this way we can compute the ``sha1sum`` on it and +we can pass all the arguments that we need to call the ``ensurepresent`` function. The arguments that the function takes are the ``sha1sum`` hash +of the image we want, the URL from which we retrieved the image, username and password. +At this point we should select the builder that we want to interact with. We can navigate to ``https://qastaging.launchpad.net/builders/qastaging-bos03-amd64-001`` and get the builder location. +In this example ``http://qastaging-bos03-amd64-001.vbuilder.qastaging.bos03.scalingstack:8221`` + +.. code-block:: python + + import xmlrpclib + proxy = xmlrpclib.ServerProxy("http://qastaging-bos03-amd64-001.vbuilder.qastaging.bos03.scalingstack:8221/rpc") + proxy.status() + + # Inject the backend image we retrieved before. + proxy.ensurepresent("", "", "admin", "admin") + + # Start the build. + proxy.build('1-3', 'snap', '', {}, {'name': 'test-build', 'image_type': 'lxd', 'series': 'bionic', 'arch_tag': 'amd64', 'git_repository': 'https://github.com/tomwardill/test-docker-repo.git', 'archives': ['deb http://archive.ubuntu.com/ubuntu bionic main restricted', 'deb http://archive.ubuntu.com/ubuntu bionic-updates main restricted', 'deb http://archive.ubuntu.com/ubuntu bionic universe']}) + + # Clean the builder after a failure or a success. + proxy.clean() + +Proxy setup +----------- + +If our build needs to talk with the external world we will need to set up the proxy for our builders, with the word ``proxy`` we are referring here to both builder proxy and fetch service. +First of all, we need to pull the token to authenticate our ``launchpad-buildd-manager`` against the proxy. + +All these commands should be run on the ``launchpad-build-manager`` unit. + +.. code-block:: python + + admin_username = + admin_secret = + auth_string = f"{admin_username}:{admin_secret}".strip() + basic_token = base64.b64encode(auth_string.encode("ASCII")) + +We can retrieve ``builder_proxy_auth_api_admin_username`` and ``builder_proxy_auth_api_admin_secret`` values from the ``launchpad-buildd-manager`` configuration. +Once we have the basic token we can call the proxy, asking for a token: + +.. code-block:: sh + + $ curl -X POST http://builder-proxy-auth.staging.lp.internal:8080/tokens -H "Authorization: Basic " -H "Content-Type: application/json" -d '{"username": }' + +Now we have all the information that we need to populate ``args`` that we need in the ``build`` function to use the proxy. +These ``args`` are ``"proxy_url": "http://:@builder-proxy.staging.lp.internal:3128"`` and +``"revocation_endpoint": "http://builder-proxy-auth.staging.lp.internal:8080/tokens/"`` that we can assemble manually starting from +the information we retrieved before, ```` will be the name that we will pass to our build function (see the following code) and +the ```` will be the token we retrieved from the ``curl`` of the previous step. + +The modified ``build`` call will look like: + +.. code-block:: python + + # Start the build. + proxy.build( + 'app-name', + 'snap', + '', + {}, + {'name': 'app-name', + 'image_type': 'lxd', + 'series': 'bionic', + 'arch_tag': 'amd64', + 'git_repository': 'https://github.com/tomwardill/test-docker-repo.git', + 'archives': [ + 'deb http://archive.ubuntu.com/ubuntu bionic main restricted', + 'deb http://archive.ubuntu.com/ubuntu bionic-updates main restricted', + 'deb http://archive.ubuntu.com/ubuntu bionic universe' + ], + "proxy_url": "http://app-name:@builder-proxy.staging.lp.internal:3128", + "revocation_endpoint": "http://builder-proxy-auth.staging.lp.internal:8080/tokens/app-name" + }) From 317437262dd6d21bbb832e9603e4f84dbd4095b6 Mon Sep 17 00:00:00 2001 From: Jared Nielsen Date: Fri, 16 Aug 2024 15:02:25 -0400 Subject: [PATCH 34/36] add 'Soyuz' link (#103) --- custom_conf.py | 1 - explanation/code.rst | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_conf.py b/custom_conf.py index c0c9709..56b9d12 100644 --- a/custom_conf.py +++ b/custom_conf.py @@ -134,7 +134,6 @@ 'Debugging#Special%20URLs', # needs update 'JavascriptUnitTesting/MockIo', # needs update 'PolicyAndProcess/Accessibility', # needs update - 'Soyuz', # needs update 'Translations/Specs/UpstreamImportIntoUbuntu/FixingIsImported/setCurrentTranslation', # needs update 'attachment:codehosting.png', # needs update 'https://git.launchpad.net/launchpad-mojo-specs/tree/mojo-lp-git/services', # private diff --git a/explanation/code.rst b/explanation/code.rst index 79fafbc..7c59602 100644 --- a/explanation/code.rst +++ b/explanation/code.rst @@ -23,7 +23,7 @@ system. The major sub-systems are: - Branch source code browser (`cgit `__ for Git; :doc:`loggerhead <../how-to/land-update-for-loggerhead>` for Bazaar) - Source package recipes (`git-build-recipe`/`bzr-builder\` integration - with `Soyuz `__) + with :doc:`Soyuz ../how-to/use-soyuz-locally`) Each of these subsystems also have multiple moving parts and some have other asynchronous jobs associated with them. From 831d85eae1dff7793c3d1a94830467b85dcc2021 Mon Sep 17 00:00:00 2001 From: Tushar <30565750+tushar5526@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:46:34 +0530 Subject: [PATCH 35/36] Add documentation around how to port builders to a target ubuntu version (#101) * Add documentation around how to port builders to a target ubuntu version * Update porting-builders-to-newer-ubuntu-versions.rst * feat: update docs to mention pre-bake scripts * fix: fix spell check and link errors * fix: fix title underline * fix: remove duplicate targets * fix: resolve review comments * fix: remove whitespace * fix: remove whitespace * fix: fix broken link --- .custom_wordlist.txt | 1 + custom_conf.py | 1 + how-to/operating-launchpad.rst | 4 +- ...ting-builders-to-newer-ubuntu-versions.rst | 52 +++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 how-to/porting-builders-to-newer-ubuntu-versions.rst diff --git a/.custom_wordlist.txt b/.custom_wordlist.txt index 735acc0..d4a79e7 100644 --- a/.custom_wordlist.txt +++ b/.custom_wordlist.txt @@ -397,6 +397,7 @@ PyPI qa QAProcess qastaging +qemu QueryCollector QueuedDelivery queuedDelivery diff --git a/custom_conf.py b/custom_conf.py index 56b9d12..91957b7 100644 --- a/custom_conf.py +++ b/custom_conf.py @@ -145,6 +145,7 @@ 'attachment:TranslationsSchema.dia', # needs update r'https://github\.com/canonical/fetch-service*', # private r'https://github\.com/canonical/fetch-operator*', # private + 'https://git.launchpad.net/charm-launchpad-buildd-image-modifier/tree/files/scripts/setup-ppa-buildd', # private ] # Pages on which to ignore anchors diff --git a/how-to/operating-launchpad.rst b/how-to/operating-launchpad.rst index ea217c9..c7dca08 100644 --- a/how-to/operating-launchpad.rst +++ b/how-to/operating-launchpad.rst @@ -13,4 +13,6 @@ Operating Launchpad deploying-configuration-changes land-update-for-loggerhead transfer-project-ownership - create-bot-account \ No newline at end of file + create-bot-account + porting-builders-to-newer-ubuntu-versions + diff --git a/how-to/porting-builders-to-newer-ubuntu-versions.rst b/how-to/porting-builders-to-newer-ubuntu-versions.rst new file mode 100644 index 0000000..4a23bf9 --- /dev/null +++ b/how-to/porting-builders-to-newer-ubuntu-versions.rst @@ -0,0 +1,52 @@ +Porting builders to newer Ubuntu versions +========================================= + +QA Migration & Deployment +------------------------- + +There are following steps to porting builders to newer Ubuntu versions. + +- Porting `launchpad-buildd `_ and its dependencies to work on the target Ubuntu version. You can follow `lp-buildd docs `_ to develop and publish on buildd-staging PPA. + - Apart from the deb dependencies defined in `debian/control `_ in `launchpad-buildd `_, you would also need to make sure that deb packages of target ubuntu version are available for ``bzr-builder``, ``git-recipe-builder`` and ``quilt``. + - These dependencies are defined in `charm-launchpad-buildd-image-modifier `__ + +- Update the ``gss_series`` variable in `launchpad-mojo-specs `__. Run ``mojo run`` to deploy the config changes. + - PS: We use ``vbuilder`` branch for build farm mojo specs. + - You don't have to update the builder config to target Ubuntu version at this step. We first have to build an image and then update the builder configs. + +- Next step is to rebuild images. Currently `launchpad-mojo-specs `__ `(vbuilder branch)` uses 2 charms to rebuild images & sync images. You can either trigger a rebuild by following: `testing-on-qastaging `_ or use the ``sync-images`` action. + - `charm-glance-simplestreams-sync `_ provides a `sync-images` action that downloads the configured base images and calls a hook to run the image modifier charm. + - `charm-launchpad-buildd-image-modifier `__ has scripts that creates a qemu COW VM image for builders with all the needed dependencies and configuration. + +.. code-block:: sh + + juju actions --help + juju list-actions + juju run-action --verbose sync-images + + + +- Update the ``builder config`` to use target Ubuntu version in `launchpad-mojo-specs `_. Use ``mojo run`` to deploy the config changes. + +- You can either wait for builders to reset and pick the new image or reset them using `ubuntu archive tools `_ + +.. code-block:: sh + + ./manage-builders -l qastaging --disabled -a riscv64 --reset + +Notes & Helpful links +--------------------- + +- With Ubuntu Noble, ``lxd`` is no longer a part of the base image and is pre-baked. Refer this `commit `_ that pre-bakes ``lxd`` if not available. `launchpad-buildd `_ uses ``lxd`` to run builds. + +- `Setting up a user on QA staging `_ + +- `Debugging build farms `_ + +- Charms can also be manually upgraded for a unit via: + +.. code-block:: sh + + juju upgrade-charm + + From 766dc568b06e49afbb831c25a6163be31ab5064a Mon Sep 17 00:00:00 2001 From: Jared Nielsen Date: Thu, 5 Sep 2024 03:09:19 -0400 Subject: [PATCH 36/36] Add codehosting.png, fix broken link (#104) * add codehosting.png, fix broken link * delete linkcheck_ignore item --- custom_conf.py | 1 - explanation/code.rst | 2 +- images/codehosting.png | Bin 0 -> 61819 bytes 3 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 images/codehosting.png diff --git a/custom_conf.py b/custom_conf.py index 91957b7..e026b44 100644 --- a/custom_conf.py +++ b/custom_conf.py @@ -135,7 +135,6 @@ 'JavascriptUnitTesting/MockIo', # needs update 'PolicyAndProcess/Accessibility', # needs update 'Translations/Specs/UpstreamImportIntoUbuntu/FixingIsImported/setCurrentTranslation', # needs update - 'attachment:codehosting.png', # needs update 'https://git.launchpad.net/launchpad-mojo-specs/tree/mojo-lp-git/services', # private 'https://wiki.canonical.com/InformationInfrastructure/OSA/LaunchpadProductionStatus', # private 'https://wiki.canonical.com/Launchpad/PolicyandProcess/ProductionChange', # private diff --git a/explanation/code.rst b/explanation/code.rst index 7c59602..4ef6890 100644 --- a/explanation/code.rst +++ b/explanation/code.rst @@ -28,7 +28,7 @@ system. The major sub-systems are: Each of these subsystems also have multiple moving parts and some have other asynchronous jobs associated with them. -The `codehosting overview diagram `__ +The `codehosting overview diagram :attachment:../images/codehosting.png` summarises how some of these systems interact. You can `run the codehosting system diff --git a/images/codehosting.png b/images/codehosting.png new file mode 100644 index 0000000000000000000000000000000000000000..0cbffb5dac3877a981223f8f4782a06e14aa3936 GIT binary patch literal 61819 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 literal 0 HcmV?d00001