From 815973d403502ddf94bd437d9315dd306f82711e Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Wed, 22 Jan 2025 12:49:46 +0400 Subject: [PATCH 01/28] create juju ecosystem docs page --- templates/docs/juju-ecosystem-docs.html | 415 ++++++++++++++++++++++++ templates/partials/_navigation.html | 12 +- 2 files changed, 417 insertions(+), 10 deletions(-) create mode 100644 templates/docs/juju-ecosystem-docs.html diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html new file mode 100644 index 00000000..3ba5c985 --- /dev/null +++ b/templates/docs/juju-ecosystem-docs.html @@ -0,0 +1,415 @@ +{% extends "base.html" %} + +{% block content %} +
+ +
+ +
+
+
+
+
+
+
+
+

Juju ecosystem

+
+
+

Juju is an ecosystem of tools for deploying and managing applications on any cloud on any + infrastructure, Kubernetes or otherwise, using the Juju operator lifecycle manager and operators + called 'charms'.

+
+
+
+
+
+ + +
+
+

+
+
+
+ +
+

+

+ What is Juju? +

+
+
+ +
+ +
+

+

+ Install Juju +

+
+
+ +
+ +
+

+

+ Connect a cloud +

+
+

+ Amazon EC2    + Amazon EKS    + Amazon Equinix +

+

+ Google GCE    + Google GKE    + LXD

+

+ MAAS    + VMware    + Microsoft Azure +

+

+ Microsoft AKS    + OpenStack    + Oracle +

+
+
+
+
+ +
+

+

+ Use charmed applications +

+
+

+ Deploy    + Configure    + Integrate    + Scale +

+

+ Observe    + Migrate    + Upgrade +

+

+ Remove +

+
+
+
+
+

+
+
+
+
+

Get started with Juju

+
+
+ +
+

+
+
+
+

Browse charmed applications

+

on Charmhub, the curated marketplace for charms which can be readily integrated into any infrastructure.

+ Explore Charmhub +
+
+ + +
+

And many more...

+
+
+
+

+
+
+

Deploy and manage charmed applications

+
+
+
+
+
+ + interactively + +
+
+

+ with the Juju CLI +

+ +
+
+
+
+ + declaratively + +
+
+

+ with Terraform Juju +

+ +
+
+
+
+ + programatically + +
+
+

+ with Python Libjuju +

+ +
+
+
+
+ + enterprise style + +
+
+

+ with JAAS +

+ +
+
+
+

+
+
+

Build charmed applications

+
+
+
+
+
+ + with charmcraft flask + +
+
+

+ with Charmcraft/Flask +

+ +
+
+
+
+ + with charmcraft django + +
+
+

+ with Charmcraft/Django +

+ +
+
+
+
+ + with charmcaft fast api + +
+
+

+ with Charmcaft/FastAPI +

+ +
+
+
+
+ + with charmcraft go + +
+
+

+ with Charmcraft/Go +

+ +
+
+
+

+
+
+
+ + any application + +
+
+

Any application

+

+ Read the Ops docs +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/partials/_navigation.html b/templates/partials/_navigation.html index 3800bd30..e659ea56 100644 --- a/templates/partials/_navigation.html +++ b/templates/partials/_navigation.html @@ -43,16 +43,8 @@ -
  • Blog From 406dac219d46bf69844e8cf1cdebc954f95b61b6 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Thu, 23 Jan 2025 13:50:27 +0400 Subject: [PATCH 02/28] fix logo section --- templates/docs/juju-ecosystem-docs.html | 328 +++++++++++++++--------- 1 file changed, 202 insertions(+), 126 deletions(-) diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html index 3ba5c985..e2d796f0 100644 --- a/templates/docs/juju-ecosystem-docs.html +++ b/templates/docs/juju-ecosystem-docs.html @@ -9,16 +9,19 @@ Juju Docs
  • - Terraform Provider Juju Docs + Terraform Provider Juju Docs
  • Python Libjuju Docs
  • - JAAS Docs + JAAS + Docs
  • - Charmcraft Docs + Charmcraft + Docs
  • Ops Docs @@ -39,7 +42,8 @@

    Juju ecosystem

    -

    Juju is an ecosystem of tools for deploying and managing applications on any cloud on any +

    Juju is an ecosystem of tools for deploying and managing applications on any + cloud on any infrastructure, Kubernetes or otherwise, using the Juju operator lifecycle manager and operators called 'charms'.

    @@ -48,18 +52,10 @@

    Juju ecosystem

    - +
    -

    +
    +
    +
    - +
    - +
    -
    -

    +
    +
    +

    - Connect a cloud + Connect + a cloud

    - Amazon EC2    - Amazon EKS    - Amazon Equinix + Amazon + EC2    + Amazon + EKS    + Amazon + Equinix

    - Google GCE    - Google GKE    - LXD

    + Google + GCE    + Google + GKE    + LXD +

    - MAAS    - VMware    - Microsoft Azure + MAAS    + VMware    + Microsoft + Azure

    - Microsoft AKS    - OpenStack    - Oracle + Microsoft + AKS    + OpenStack    + Oracle

    @@ -144,36 +177,52 @@

    -
    -

    +
    +
    +

    - Use charmed applications + Use + charmed applications

    - Deploy    - Configure    - Integrate    - Scale + Deploy    + Configure    + Integrate    + Scale

    - Observe    - Migrate    - Upgrade + Observe    + Migrate    + Upgrade

    - Remove + Remove

    -

    +
    +
    +

    @@ -183,76 +232,77 @@

    Get started with Juju


    -
    -
    -
    +
    +
    +

    Browse charmed applications

    on Charmhub, the curated marketplace for charms which can be readily integrated into any infrastructure.

    - Explore Charmhub + + Explore Charmhub + +
    -
    - -
    +
    - MySQL + MySQL logo -
    -
    MySQL
    -
    -
  • -
  • +
    MySQL
    + +
    - MongoDB + Canonical logo -
    -
    MongoDB
    -
    -
  • -
  • +
    MongoDB
    + + +
    +
    +
    +
    +
  • -
  • +
    Prometheus
    + +
    - Grafana + Grafana logo -
    -
    Grafana
    -
    -
  • -
  • +
    Grafana
    + +
    - Loki + Loki logo -
    -
    Loki
    -
    -
  • - -
    +
    Loki
    + + +
    +
    +

    And many more...

    -

    +
    +
    +

    Deploy and manage charmed applications

    @@ -262,7 +312,8 @@

    Deploy and manage charmed applications

    @@ -271,14 +322,16 @@

    Deploy and manage charmed applications

    @@ -287,14 +340,16 @@

    Deploy and manage charmed applications

    @@ -303,14 +358,16 @@

    Deploy and manage charmed applications

    @@ -319,12 +376,15 @@

    Deploy and manage charmed applications

    -

    +
    +
    +

    Build charmed applications

    @@ -334,7 +394,8 @@

    Build charmed applications

    @@ -343,14 +404,16 @@

    Build charmed applications

    @@ -359,14 +422,17 @@

    Build charmed applications

    @@ -375,14 +441,17 @@

    Build charmed applications

    @@ -391,23 +460,30 @@

    Build charmed applications

    -

    +
    +
    +

    Any application

    -

    - Read the Ops docs +
    +
    +
    + Read the Ops docs
    From ea2f1b3e8350332ed9d8eaf26a39eaeef7273d20 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Thu, 23 Jan 2025 15:08:25 +0400 Subject: [PATCH 03/28] add footer --- templates/base.html | 5 ++- templates/docs/juju-ecosystem-docs.html | 44 +++++++++++++------ .../partials/_juju-ecosystem-docs-footer.html | 28 ++++++++++++ 3 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 templates/partials/_juju-ecosystem-docs-footer.html diff --git a/templates/base.html b/templates/base.html index 3d1b4426..be5477d7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -70,7 +70,10 @@ {% block content %}{% endblock %} - {% include "footer.html" %} + {% block footer %} + {% include "footer.html" %} + {% endblock footer %} + {% endblock body %}
    diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html index e2d796f0..c83c781d 100644 --- a/templates/docs/juju-ecosystem-docs.html +++ b/templates/docs/juju-ecosystem-docs.html @@ -1,6 +1,7 @@ {% extends "base.html" %} -{% block content %} +{% block body %} +{% include "partials/_navigation.html" %}
    -

    +
    +
    +
    @@ -253,21 +256,26 @@

    Browse charmed applications

    @@ -488,4 +502,6 @@

    Any application

    -{% endblock %} \ No newline at end of file + +{% include "partials/_juju-ecosystem-docs-footer.html" %} +{% endblock body %} \ No newline at end of file diff --git a/templates/partials/_juju-ecosystem-docs-footer.html b/templates/partials/_juju-ecosystem-docs-footer.html new file mode 100644 index 00000000..82cd45b8 --- /dev/null +++ b/templates/partials/_juju-ecosystem-docs-footer.html @@ -0,0 +1,28 @@ + From 7c958d944e543ecf76de2778e5c908349163d5e0 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Thu, 23 Jan 2025 15:19:21 +0400 Subject: [PATCH 04/28] add missing footer links --- templates/partials/_juju-ecosystem-docs-footer.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/partials/_juju-ecosystem-docs-footer.html b/templates/partials/_juju-ecosystem-docs-footer.html index 82cd45b8..1abfad4c 100644 --- a/templates/partials/_juju-ecosystem-docs-footer.html +++ b/templates/partials/_juju-ecosystem-docs-footer.html @@ -11,9 +11,9 @@
    Ask a question on Matrix
    - Open a GitHub issue for this page + Open a GitHub issue for this page
    - Edit this page on GitHub + Edit this page on GitHub

    From cd9fb1d1ba6f8ecdeda4759b7528b3a3dd4fd698 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Thu, 23 Jan 2025 16:08:46 +0400 Subject: [PATCH 05/28] render docs page --- templates/docs/document.html | 151 ----------------------------------- webapp/docs/views.py | 100 ++--------------------- 2 files changed, 6 insertions(+), 245 deletions(-) delete mode 100644 templates/docs/document.html diff --git a/templates/docs/document.html b/templates/docs/document.html deleted file mode 100644 index ddde49e8..00000000 --- a/templates/docs/document.html +++ /dev/null @@ -1,151 +0,0 @@ -{% extends "docs/base.html" %} - -{% block page_class %}docs{% endblock %} -{% block title %}{% if document %}{{ document.title }}{% else %}Docs{% endif %}{% endblock %} -{% block meta_title %}{% if document %}{{ document.title }}{% else %}Docs{% endif %}{% endblock %} - -{% set is_docs = True %} - -{% block body_class %}{# override default l-site with empty class #}{% endblock %} - -{% block content_docs %} -{% macro create_navigation(nav_items, expandable=False, expanded=False) %} -
      - {% for element in nav_items %} -
    • - {% if element.navlink_href %} - - {% else %} - - {% endif %} - - {% if expandable %} - {% if element.children %} - - {% endif %} - {{ create_navigation(element.children, expandable, element.is_active or element.has_active_child) }} - {% else %} - {% if element.children %} - {{ create_navigation(element.children, expandable) }} - {% endif %} - {% endif %} -
    • - {% endfor %} -
    -{% endmacro %} - -
    -
    - {% if versions | length > 1 %} - - - {% endif %} - - -
    -
    - -
    -
    -

    {{ document.title }}

    -
    -
    - -{% if document.headings_map is defined and document.headings_map|length > 0 %} -
    -
    - -
    -
    -{% endif %} - -
    -
    -
    - {{ document.body_html | safe | serve_assets }} -
    -
    -
    -

    - Last updated {{ document.updated }}. - Help improve this document in the forum - or - - File an issue - . -

    -
    -
    -
    -
    - -{% endblock %} - diff --git a/webapp/docs/views.py b/webapp/docs/views.py index a9238952..707ae60c 100644 --- a/webapp/docs/views.py +++ b/webapp/docs/views.py @@ -2,88 +2,19 @@ import talisker.requests -from canonicalwebteam.discourse import DiscourseAPI, DocParser, Docs from canonicalwebteam.search import build_search_view +from flask import render_template -DISCOURSE_API_KEY = getenv("DISCOURSE_API_KEY") -DISCOURSE_API_USERNAME = getenv("DISCOURSE_API_USERNAME") +RTD_DOCS_BASE_URL = "https://canonical-juju.readthedocs.io/en/latest/" def init_docs(app): session = talisker.requests.get_session() - main_docs = Docs( - parser=DocParser( - api=DiscourseAPI( - base_url="https://discourse.charmhub.io/", session=session - ), - index_topic_id=4513, - url_prefix="/docs", - ), - document_template="docs/document.html", - url_prefix="/docs", - blueprint_name="main_docs", - ) - main_docs.init_app(app) - - discourse_index_id = 1087 - - discourse_docs = Docs( - parser=DocParser( - api=DiscourseAPI( - base_url="https://discourse.charmhub.io/", - session=session, - api_key=DISCOURSE_API_KEY, - api_username=DISCOURSE_API_USERNAME, - get_topics_query_id=2, - ), - index_topic_id=discourse_index_id, - url_prefix="/docs/juju", - ), - document_template="docs/document.html", - url_prefix="/docs/juju", - ) - - discourse_docs.init_app(app) - - sdk_docs_id = 4449 - sdk_docs = Docs( - parser=DocParser( - api=DiscourseAPI( - base_url="https://discourse.charmhub.io/", - session=session, - api_key=DISCOURSE_API_KEY, - api_username=DISCOURSE_API_USERNAME, - get_topics_query_id=2, - ), - index_topic_id=sdk_docs_id, - url_prefix="/docs/sdk", - ), - document_template="docs/document.html", - url_prefix="/docs/sdk", - blueprint_name="sdk_docs", - ) - sdk_docs.init_app(app) - - cos_docs_id = 5132 - cos_docs = Docs( - parser=DocParser( - api=DiscourseAPI( - base_url="https://discourse.charmhub.io/", - session=session, - api_key=DISCOURSE_API_KEY, - api_username=DISCOURSE_API_USERNAME, - get_topics_query_id=2, - ), - index_topic_id=cos_docs_id, - url_prefix="/docs/cos", - ), - document_template="docs/document.html", - url_prefix="/docs/cos", - blueprint_name="cos_docs", - ) + def render_juju_ecosystem_docs_page(): + return render_template("docs/juju-ecosystem-docs.html") - cos_docs.init_app(app) + app.add_url_rule("/docs", "juju-ecosystem-docs", render_juju_ecosystem_docs_page) app.add_url_rule( "/docs/search", @@ -91,27 +22,8 @@ def init_docs(app): build_search_view( app=app, session=session, - site="juju.is/docs", + site="https://canonical-juju.readthedocs-hosted.com/en/latest", template_path="docs/search.html", ), ) - juju_dev_docs_id = 6669 - juju_dev_docs = Docs( - parser=DocParser( - api=DiscourseAPI( - base_url="https://discourse.charmhub.io/", - session=session, - api_key=DISCOURSE_API_KEY, - api_username=DISCOURSE_API_USERNAME, - get_topics_query_id=2, - ), - index_topic_id=juju_dev_docs_id, - url_prefix="/docs/dev", - ), - document_template="docs/document.html", - url_prefix="/docs/dev", - blueprint_name="juju_dev_docs", - ) - - juju_dev_docs.init_app(app) From 6c0f2ebc1b1bf02a5068448ef9fde7dd0cc15136 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Thu, 23 Jan 2025 16:20:12 +0400 Subject: [PATCH 06/28] fix python lint --- webapp/docs/views.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/webapp/docs/views.py b/webapp/docs/views.py index 707ae60c..7d0c00f1 100644 --- a/webapp/docs/views.py +++ b/webapp/docs/views.py @@ -1,5 +1,3 @@ -from os import getenv - import talisker.requests from canonicalwebteam.search import build_search_view @@ -11,10 +9,10 @@ def init_docs(app): session = talisker.requests.get_session() - def render_juju_ecosystem_docs_page(): + def render_juju_docs_page(): return render_template("docs/juju-ecosystem-docs.html") - app.add_url_rule("/docs", "juju-ecosystem-docs", render_juju_ecosystem_docs_page) + app.add_url_rule("/docs", "juju-ecosystem-docs", render_juju_docs_page) app.add_url_rule( "/docs/search", @@ -26,4 +24,3 @@ def render_juju_ecosystem_docs_page(): template_path="docs/search.html", ), ) - From 92b61b89898754b12202f99a419a1c6c5f8d0189 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Thu, 23 Jan 2025 19:20:53 +0400 Subject: [PATCH 07/28] add selected item in navigation --- templates/partials/_navigation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/partials/_navigation.html b/templates/partials/_navigation.html index e659ea56..8c930396 100644 --- a/templates/partials/_navigation.html +++ b/templates/partials/_navigation.html @@ -43,7 +43,7 @@ -
  • From 6db469e9087444cf7c63ae959271b87dbe75e153 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Fri, 24 Jan 2025 10:04:13 +0400 Subject: [PATCH 08/28] add shallow section --- templates/docs/juju-ecosystem-docs.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html index c83c781d..770fb85e 100644 --- a/templates/docs/juju-ecosystem-docs.html +++ b/templates/docs/juju-ecosystem-docs.html @@ -52,7 +52,7 @@

    Juju ecosystem

    - -

    -
    +
    @@ -225,7 +224,7 @@


    -
    +

    Get started with Juju

    From 3418d2090c0efd0d8408f4b06110e9162dc8a819 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Fri, 24 Jan 2025 13:36:22 +0400 Subject: [PATCH 09/28] remove shallow strip --- templates/docs/juju-ecosystem-docs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html index 770fb85e..adabf06a 100644 --- a/templates/docs/juju-ecosystem-docs.html +++ b/templates/docs/juju-ecosystem-docs.html @@ -224,7 +224,7 @@


    -
    +

    Get started with Juju

    From 590698c59bddd7c935854ff4e8143df1f7e7e847 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Tue, 28 Jan 2025 17:18:12 +0400 Subject: [PATCH 10/28] add juju.is redirects --- redirects.yaml | 535 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 535 insertions(+) diff --git a/redirects.yaml b/redirects.yaml index a74c1864..9f610b22 100644 --- a/redirects.yaml +++ b/redirects.yaml @@ -36,3 +36,538 @@ tutorials/how-to-work-with-resources-in-charmcraft/?: /docs/sdk/resources tutorials/how-to-work-with-bundles-in-charmcraft/?: /docs/sdk/manage-bundles tutorials/deploy-postgres-on-ubuntu-server/?: /docs/olm/how-to tutorials(.*?): /docs/sdk/tutorials + +docs/juju: https://canonical-juju.readthedocs-hosted.com/en/latest/# +docs/juju/action: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/action/ +docs/juju/agent: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/agent/ +docs/juju/agent-introspection: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/list-of-juju-introspect-macros/ +docs/juju/agent-introspection-juju-engine-report: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/list-of-juju-introspect-macros/juju_engine_report/ +docs/juju/agent-introspection-juju-goroutines: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/list-of-juju-introspect-macros/juju_goroutines/ +docs/juju/agent-introspection-juju-heap-profile: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/list-of-juju-introspect-macros/juju_heap_profile/ +docs/juju/agent-introspection-juju-machine-lock: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/list-of-juju-introspect-macros/juju_machine_lock/ +docs/juju/agent-introspection-juju-metrics: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/list-of-juju-introspect-macros/juju_metrics/ +docs/juju/agent-introspection-juju-start-unit: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/list-of-juju-introspect-macros/juju_start_unit/ +docs/juju/agent-introspection-juju-stop-unit: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/list-of-juju-introspect-macros/juju_stop_unit/ +docs/juju/agent-introspection-juju-unit-status: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/list-of-juju-introspect-macros/juju_unit_status/ +docs/juju/amazon-ec2: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-amazon-ec2-cloud-and-juju/#cloud-ec2 +docs/juju/amazon-eks: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-amazon-eks-cloud-and-juju/#cloud-kubernetes-eks +docs/juju/application: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/application/ +docs/juju/application-modelling: https://canonical-juju.readthedocs-hosted.com/en/latest/user/explanation/ +docs/juju/audit-log-exclude-methods: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/configuration/list-of-controller-configuration-keys/ +docs/juju/availability-zone: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/zone/ +docs/juju/base: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/machine/#machine-base +docs/juju/binding: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/space/ +docs/juju/bootstrapping: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju/juju-architecture/ +docs/juju/bundle: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/bundle/ +docs/juju/channel: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/channel/ +docs/juju/charm-environment-variables: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/juju/charm-resource: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-charm/ +docs/juju/charmed-operator: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/ +docs/juju/client: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/client/ +docs/juju/cloud: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/ +docs/juju/commands-available-on-a-juju-machine: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/resource-compute/list-of-commands-available-on-a-compute-resource-provisioned-by-juju/ +docs/juju/configuration: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/configuration/ +docs/juju/constraint: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/constraint/ +docs/juju/containeragent-binary: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/containeragent/ +docs/juju/controller: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/controller/ +docs/juju/credential: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/credential/ +docs/juju/cross-version-compatibility-in-juju: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju/juju-cross-version-compatibility/ +docs/juju/debug-bootstrapmachine-failures: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-your-deployment/troubleshoot-your-deployment/# +docs/juju/define-instance-tags: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/define-resource-tags-in-a-cloud/ +docs/juju/deployment: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju/juju-architecture/ +docs/juju/dynamic-storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/storage/#dynamic-storage +docs/juju/endpoint: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/application/#application-endpoint +docs/juju/environment-variables: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/juju-environment-variables/ +docs/juju/equinix-metal: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-equinix-metal-cloud-and-juju/#cloud-equinix +docs/juju/explanation: https://canonical-juju.readthedocs-hosted.com/en/latest/user/explanation/ +docs/juju/fan-container-networking: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/fan-container-networking/ +docs/juju/google-gce: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-google-gce-cloud-and-juju/ +docs/juju/google-gke: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-google-gke-cloud-and-juju/ +docs/juju/harden-your-deployment: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-your-deployment/harden-your-deployment/ +docs/juju/high-availability: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/high-availability/ +docs/juju/hook: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/juju/hook-tool: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook-command/ +docs/juju/how-to: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/ +docs/juju/install-and-manage-the-client: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-juju/ +docs/juju/juju-actions: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/actions +docs/juju/juju-add-cloud: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-cloud +docs/juju/juju-add-credential: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-credential +docs/juju/juju-add-k8s: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-k8s +docs/juju/juju-add-machine: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-machine +docs/juju/juju-add-model: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-model +docs/juju/juju-add-secret: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-secret +docs/juju/juju-add-secret-backend: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-secret-backend +docs/juju/juju-add-space: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-space +docs/juju/juju-add-ssh-key: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-ssh-key +docs/juju/juju-add-storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-storage +docs/juju/juju-add-unit: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-unit +docs/juju/juju-add-user: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/add-user +docs/juju/juju-agree: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/agree +docs/juju/juju-agreements: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/agreements +docs/juju/juju-attach-resource: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/attach-resource +docs/juju/juju-attach-storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/attach-storage +docs/juju/juju-autoload-credentials: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/autoload-credentials +docs/juju/juju-bind: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/bind +docs/juju/juju-bootstrap: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/bootstrap +docs/juju/juju-cancel-task: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/cancel-task +docs/juju/juju-change-user-password: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/change-user-password +docs/juju/juju-charm-resources: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/charm-resources +docs/juju/juju-cli-commands: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/ +docs/juju/juju-client: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/ +docs/juju/juju-clouds: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/clouds +docs/juju/juju-collect-metrics: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/collect-metrics +docs/juju/juju-config: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/config +docs/juju/juju-constraints: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/constraints +docs/juju/juju-consume: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/consume +docs/juju/juju-controller-config: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/controller-config +docs/juju/juju-controllers: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/controllers +docs/juju/juju-create-backup: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/create-backup +docs/juju/juju-create-storage-pool: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/create-storage-pool +docs/juju/juju-credentials: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/credentials +docs/juju/juju-dashboard: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/dashboard +docs/juju/juju-debug-code: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/debug-code +docs/juju/juju-debug-hook: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/debug-hook +docs/juju/juju-debug-hooks: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/debug-hooks +docs/juju/juju-debug-log: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/debug-log +docs/juju/juju-default-credential: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/default-credential +docs/juju/juju-default-region: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/default-region +docs/juju/juju-deploy: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/deploy +docs/juju/juju-destroy-controller: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/destroy-controller +docs/juju/juju-destroy-model: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/destroy-model +docs/juju/juju-detach-storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/detach-storage +docs/juju/juju-diff-bundle: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/diff-bundle +docs/juju/juju-disable-command: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/disable-command +docs/juju/juju-disable-user: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/disable-user +docs/juju/juju-disabled-commands: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/disabled-commands +docs/juju/juju-documentation: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/documentation +docs/juju/juju-download: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/download +docs/juju/juju-download-backup: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/download-backup +docs/juju/juju-enable-command: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/enable-command +docs/juju/juju-enable-destroy-controlle: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/enable-destroy-controlle +docs/juju/juju-enable-ha: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/enable-ha +docs/juju/juju-enable-user: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/enable-user +docs/juju/juju-exec: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/exec +docs/juju/juju-export-bundle: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/export-bundle +docs/juju/juju-expose: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/expose +docs/juju/juju-find: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/find +docs/juju/juju-find-offers: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/find-offers +docs/juju/juju-firewall-rules: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/firewall-rules +docs/juju/juju-grant: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/grant +docs/juju/juju-grant-cloud: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/grant-cloud +docs/juju/juju-grant-secret: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/grant-secret +docs/juju/juju-ha-space: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/configuration/list-of-controller-configuration-keys/ +docs/juju/juju-help: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/help +docs/juju/juju-help-tool: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/help-tool +docs/juju/juju-import-filesystem: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/import-filesystem +docs/juju/juju-import-ssh-key: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/import-ssh-key +docs/juju/juju-info: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/info +docs/juju/juju-integrate: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/integrate +docs/juju/juju-kill-controller: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/kill-controller +docs/juju/juju-list-actions: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-actions +docs/juju/juju-list-agreements: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-agreements +docs/juju/juju-list-charm-resources: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-charm-resources +docs/juju/juju-list-clouds: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-clouds +docs/juju/juju-list-controllers: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-controllers +docs/juju/juju-list-credentials: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-credentials +docs/juju/juju-list-disabled-commands: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-disabled-commands +docs/juju/juju-list-firewall-rules: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-firewall-rules +docs/juju/juju-list-machines: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-machines +docs/juju/juju-list-models: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-models +docs/juju/juju-list-offers: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-offers +docs/juju/juju-list-operations: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-operations +docs/juju/juju-list-payloads: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-payloads +docs/juju/juju-list-regions: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-regions +docs/juju/juju-list-resources: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-resources +docs/juju/juju-list-secret-backends: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-secret-backends +docs/juju/juju-list-secrets: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-secrets +docs/juju/juju-list-spaces: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-spaces +docs/juju/juju-list-ssh-keys: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-ssh-keys +docs/juju/juju-list-storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-storage +docs/juju/juju-list-storage-pools: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-storage-pools +docs/juju/juju-list-subnets: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-subnets +docs/juju/juju-list-users: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/list-users +docs/juju/juju-login: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/login +docs/juju/juju-logout: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/logout +docs/juju/juju-machines: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/machines +docs/juju/juju-metadata: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/plugin/list-of-known-juju-plugins/plugin-juju-metadata/ +docs/juju/juju-metrics: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/metrics +docs/juju/juju-mgmt-space: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/configuration/list-of-controller-configuration-keys/ +docs/juju/juju-migrate: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/migrate +docs/juju/juju-model-config: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/model-config +docs/juju/juju-model-constraints: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/model-constraints +docs/juju/juju-model-default: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/model-default +docs/juju/juju-model-defaults: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/model-defaults +docs/juju/juju-models: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/models +docs/juju/juju-move-to-space: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/move-to-space +docs/juju/juju-offer: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/offer +docs/juju/juju-offers: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/offers +docs/juju/juju-operations: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/operations +docs/juju/juju-payloads: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/payloads +docs/juju/juju-performance: https://canonical-juju.readthedocs-hosted.com/en/latest/user/explanation/performance-with-juju/ +docs/juju/juju-refresh: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/refresh +docs/juju/juju-regions: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/regions +docs/juju/juju-register: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/register +docs/juju/juju-relate: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/relate +docs/juju/juju-reload-spaces: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/reload-spaces +docs/juju/juju-remove-application: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-application +docs/juju/juju-remove-cloud: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-cloud +docs/juju/juju-remove-credential: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-credential +docs/juju/juju-remove-k8s: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-k8s +docs/juju/juju-remove-machine: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-machine +docs/juju/juju-remove-offer: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-offer +docs/juju/juju-remove-relation: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-relation +docs/juju/juju-remove-saas: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-saas +docs/juju/juju-remove-secret: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-secret +docs/juju/juju-remove-secret-backend: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-secret-backend +docs/juju/juju-remove-space: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-space +docs/juju/juju-remove-ssh-key: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-ssh-key +docs/juju/juju-remove-storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-storage +docs/juju/juju-remove-storage-pool: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-storage-pool +docs/juju/juju-remove-unit: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-unit +docs/juju/juju-remove-user: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/remove-user +docs/juju/juju-rename-space: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/rename-space +docs/juju/juju-resolve: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/resolve +docs/juju/juju-resolved: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/resolved +docs/juju/juju-resources: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/resources +docs/juju/juju-resume-relation: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/resume-relation +docs/juju/juju-retry-provisioning: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/retry-provisioning +docs/juju/juju-revoke: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/revoke +docs/juju/juju-revoke-cloud: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/revoke-cloud +docs/juju/juju-revoke-secret: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/revoke-secret +docs/juju/juju-run: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/run +docs/juju/juju-scale-application: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/scale-application +docs/juju/juju-scp: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/scp +docs/juju/juju-secret-backends: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/secret-backends +docs/juju/juju-secrets: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/secrets +docs/juju/juju-set-application-base: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/set-application-base +docs/juju/juju-set-constraints: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/set-constraints +docs/juju/juju-set-credential: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/set-credential +docs/juju/juju-set-default-credentials: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/set-default-credentials +docs/juju/juju-set-default-region: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/set-default-region +docs/juju/juju-set-firewall-rule: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/set-firewall-rule +docs/juju/juju-set-meter-status: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/set-meter-status +docs/juju/juju-set-model-constraints: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/set-model-constraints +docs/juju/juju-show-action: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-action +docs/juju/juju-show-application: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-application +docs/juju/juju-show-cloud: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-cloud +docs/juju/juju-show-controller: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-controller +docs/juju/juju-show-credential: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-credential +docs/juju/juju-show-credentials: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-credentials +docs/juju/juju-show-machine: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-machine +docs/juju/juju-show-model: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-model +docs/juju/juju-show-offer: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-offer +docs/juju/juju-show-operation: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-operation +docs/juju/juju-show-secret: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-secret +docs/juju/juju-show-secret-backend: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-secret-backend +docs/juju/juju-show-space: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-space +docs/juju/juju-show-status-log: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-status-log +docs/juju/juju-show-storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-storage +docs/juju/juju-show-task: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-task +docs/juju/juju-show-unit: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-unit +docs/juju/juju-show-user: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/show-user +docs/juju/juju-spaces: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/spaces +docs/juju/juju-ssh: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/ssh +docs/juju/juju-ssh-keys: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/ssh-keys +docs/juju/juju-stash: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/plugin/list-of-known-juju-plugins/plugin-juju-stash/ +docs/juju/juju-status: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/status +docs/juju/juju-storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/storage +docs/juju/juju-storage-pools: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/storage-pools +docs/juju/juju-subnets: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/subnets +docs/juju/juju-supported-clouds: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/ +docs/juju/juju-suspend-relation: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/suspend-relation +docs/juju/juju-switch: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/switch +docs/juju/juju-sync-agent-binary: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/sync-agent-binary +docs/juju/juju-trust: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/trust +docs/juju/juju-unexpose: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/unexpose +docs/juju/juju-unregister: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/unregister +docs/juju/juju-update-cloud: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/update-cloud +docs/juju/juju-update-credential: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/update-credential +docs/juju/juju-update-credentials: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/update-credentials +docs/juju/juju-update-k8s: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/update-k8s +docs/juju/juju-update-public-clouds: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/update-public-clouds +docs/juju/juju-update-secret: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/update-secret +docs/juju/juju-update-secret-backend: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/update-secret-backend +docs/juju/juju-update-storage-pool: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/update-storage-pool +docs/juju/juju-upgrade-controller: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/upgrade-controller +docs/juju/juju-upgrade-machine: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/upgrade-machine +docs/juju/juju-upgrade-model: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/upgrade-model +docs/juju/juju-users: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/users +docs/juju/juju-wait-for: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/wait-for +docs/juju/juju-wait-for-application: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/wait-for-application +docs/juju/juju-wait-for-machine: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/wait-for-machine +docs/juju/juju-wait-for-model: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/wait-for-model +docs/juju/juju-wait-for-unit: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/wait-for-unit +docs/juju/juju-whoami: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-cli/list-of-juju-cli-commands/whoami +docs/juju/jujuc-binary: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/jujuc/ +docs/juju/jujud-binary: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/jujud/ +docs/juju/kubernetes-clouds-and-juju: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/kubernetes-clouds-and-juju/ +docs/juju/kubernetes-in-juju: https://canonical-juju.readthedocs-hosted.com/en/latest/user/explanation/kubernetes-in-juju/ +docs/juju/leader: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/unit/#leader-unit +docs/juju/list-of-controller-configuration-keys: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/configuration/list-of-controller-configuration-keys/ +docs/juju/list-of-known-plugins: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/plugin/list-of-known-juju-plugins/#list-of-known-juju-plugins +docs/juju/list-of-model-configuration-keys: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/configuration/list-of-model-configuration-keys/ +docs/juju/log: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/log/ +docs/juju/lxd: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-lxd-cloud-and-juju/ +docs/juju/maas: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-maas-cloud-and-juju/ +docs/juju/machine: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/machine/ +docs/juju/manage-actions: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-actions/ +docs/juju/manage-applications: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-applications/ +docs/juju/manage-charm-resources: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-charm-resources/ +docs/juju/manage-charms-or-bundles: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-charms/ +docs/juju/manage-clouds: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-clouds/ +docs/juju/manage-controllers: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-controllers/ +docs/juju/manage-credentials: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-credentials/ +docs/juju/manage-logs: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-logs/ +docs/juju/manage-machines: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-machines/ +docs/juju/manage-metadata: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-metadata/ +docs/juju/manage-models: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-models/ +docs/juju/manage-offers: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-offers/ +docs/juju/manage-plugins: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-plugins/ +docs/juju/manage-relations: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-relations/ +docs/juju/manage-secret-backends: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-secret-backends/ +docs/juju/manage-secrets: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-secrets/ +docs/juju/manage-spaces: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-spaces/ +docs/juju/manage-ssh-keys: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-ssh-keys/ +docs/juju/manage-storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-storage/ +docs/juju/manage-storage-pools: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-storage-pools/ +docs/juju/manage-subnets: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-subnets/ +docs/juju/manage-the-juju-dashboard: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-the-juju-dashboard/ +docs/juju/manage-units: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-units/ +docs/juju/manage-users: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-users/ +docs/juju/manual: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-manual-cloud-and-juju/ +docs/juju/metric: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/ +docs/juju/microk8s: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-microk8s-cloud-and-juju/ +docs/juju/microsoft-aks: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-microsoft-aks-cloud-and-juju/ +docs/juju/microsoft-azure: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-microsoft-azure-cloud-and-juju/ +docs/juju/model: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/model/ +docs/juju/network-spaces: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/space/ +docs/juju/offer: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/offer/ +docs/juju/openstack: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-openstack-cloud-and-juju/ +docs/juju/operation: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/script/#script-operation +docs/juju/oracle-oci: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-oracle-oci-cloud-and-juju/ +docs/juju/placement-directive: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/placement-directive/ +docs/juju/plugin-flags: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/plugin/plugin-flags/ +docs/juju/plugins: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/plugin/ +docs/juju/python-libjuju-client: https://pythonlibjuju.readthedocs.io/en/latest/ +docs/juju/reference: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/ +docs/juju/relation: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/relation/ +docs/juju/removing-things: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/removing-things/ +docs/juju/roadmap: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju/juju-roadmap-and-releases/ +docs/juju/scaling: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/scaling/ +docs/juju/secret: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/secret/ +docs/juju/secret-backend: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/secret/#secret-backend +docs/juju/security-with-juju: https://canonical-juju.readthedocs-hosted.com/en/latest/user/explanation/performance-with-juju/ +docs/juju/set-up--tear-down-your-test-environment: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-your-deployment/set-up-your-deployment-environment/ +docs/juju/ssh-key: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/ssh-key/ +docs/juju/status: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/status/ +docs/juju/storage: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/storage/ +docs/juju/storage-constraint: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/storage/#storage-constraint-directive +docs/juju/storage-pool: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/storage/#storage-pool +docs/juju/storage-provider: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/storage/#storage-provider +docs/juju/storage-support: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/storage/#storage-support +docs/juju/subnet: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/subnet/ +docs/juju/supported-features: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/charmcraft-yaml-file/ +docs/juju/take-your-deployment-offline: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-your-deployment/take-your-deployment-offline/ +docs/juju/task: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/script/#script-task +docs/juju/telemetry-and-juju: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/telemetry/ +docs/juju/terraform-juju-client: https://canonical-terraform-provider-juju.readthedocs-hosted.com/en/latest/tutorial/ +docs/juju/the-juju-dashboard: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-dashboard/ +docs/juju/the-juju-web-cli: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju-web-cli/ +docs/juju/troubleshoot-your-deployment: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-your-deployment/troubleshoot-your-deployment/# +docs/juju/tutorial: https://canonical-juju.readthedocs-hosted.com/en/latest/user/tutorial/ +docs/juju/unit: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/unit/ +docs/juju/upgrade-your-juju-deployment: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-your-deployment/upgrade-your-deployment/ +docs/juju/upgrading: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/upgrading-things/ +docs/juju/user: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/user/ +docs/juju/user-permissions: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/user/#user-access-levels +docs/juju/vmware-vsphere: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/cloud/list-of-supported-clouds/the-vmware-vsphere-cloud-and-juju/ +docs/juju/worker: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/worker/ + +docs/sdk: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-charms/#build-a-charm +docs/sdk/12-factor-app-charm: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/charm-taxonomy/#factor-app-charm +docs/sdk/actions: https://ops.readthedocs.io/en/latest/howto/manage-actions.html +docs/sdk/actions-yaml: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/actions-yaml-file/ +docs/sdk/add-a-secret-to-a-charm: https://ops.readthedocs.io/en/latest/howto/manage-secrets.html +docs/sdk/add-docs-to-your-charmhub-page: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charms/#add-docs +docs/sdk/build-a-12-factor-app-charm: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-a-12-factor-app-charm/ +docs/sdk/build-and-own-a-charm-or-a-bundle: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charms/ +docs/sdk/bundle: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/bundle/ +docs/sdk/bundle.yaml: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/bundle-yaml-file/ +docs/sdk/change-step-behavior-in-a-charm: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-parts/ +docs/sdk/charm: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/ +docs/sdk/charm-documentation: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charms/#add-docs +docs/sdk/charm-lifecycle: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/sdk/charm-maturity: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/charm-maturity/ +docs/sdk/charm-maturity-stage-1: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/charm-maturity/ +docs/sdk/charm-maturity-stage-2: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/charm-maturity/ +docs/sdk/charm-relation-interfaces: https://ops.readthedocs.io/en/latest/explanation/charm-relation-interfaces.html +docs/sdk/charm-taxonomy: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/charm-taxonomy/ +docs/sdk/charmcraft: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/ +docs/sdk/charmcraft-analyse: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/analyse/ +docs/sdk/charmcraft-analyzers-and-linters: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/analyzers-and-linters/ +docs/sdk/charmcraft-build: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/build/ +docs/sdk/charmcraft-clean: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/clean/ +docs/sdk/charmcraft-cli-commands: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/ +docs/sdk/charmcraft-close: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/clean/ +docs/sdk/charmcraft-config: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charmcraft/ +docs/sdk/charmcraft-create-lib: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/close// +docs/sdk/charmcraft-deprecations: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/changelog/ +docs/sdk/charmcraft-expand-extensions: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/create-lib/ +docs/sdk/charmcraft-extension-django-framework: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/extensions/django-framework-extension/ +docs/sdk/charmcraft-extension-fastapi-framework: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/extensions/fastapi-framework-extension/ +docs/sdk/charmcraft-extension-flask-framework: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/extensions/flask-framework-extension/ +docs/sdk/charmcraft-extension-go-framework: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/extensions/go-framework-extension/ +docs/sdk/charmcraft-fetch-lib: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/expand-extensions/ +docs/sdk/charmcraft-fetch-libs: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/fetch-lib/ +docs/sdk/charmcraft-init: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/fetch-libs/ +docs/sdk/charmcraft-list-extensions: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/init/ +docs/sdk/charmcraft-list-lib: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/list-extensions/ +docs/sdk/charmcraft-login: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/list-lib/ +docs/sdk/charmcraft-logout: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/login/ +docs/sdk/charmcraft-names: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/logout/ +docs/sdk/charmcraft-pack: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/names/ +docs/sdk/charmcraft-prime: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/pack/ +docs/sdk/charmcraft-promote-bundle: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/prime/ +docs/sdk/charmcraft-publish-lib: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/pull/ +docs/sdk/charmcraft-pull: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/promote-bundle/ +docs/sdk/charmcraft-register: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/publish-lib/ +docs/sdk/charmcraft-register-bundle: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/register/ +docs/sdk/charmcraft-release: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/register-bundle/ +docs/sdk/charmcraft-remote-build: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/release/ +docs/sdk/charmcraft-resource-revisions: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/resources/ +docs/sdk/charmcraft-resources: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/remote-build/ +docs/sdk/charmcraft-revisions: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/resource-revisions/ +docs/sdk/charmcraft-set-resource-architectures: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/revisions/ +docs/sdk/charmcraft-stage: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/set-resource-architectures/ +docs/sdk/charmcraft-status: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/stage/ +docs/sdk/charmcraft-unregister: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/status/ +docs/sdk/charmcraft-upload: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/unregister/ +docs/sdk/charmcraft-upload-resource: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/upload/ +docs/sdk/charmcraft-version: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/upload-resource/ +docs/sdk/charmcraft-whoami: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/commands/version/ +docs/sdk/charmcraft-yaml: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/charmcraft-yaml-file/ +docs/sdk/charmed-operators-vs-kubernetes-operators: https://canonical-juju.readthedocs-hosted.com/en/latest/user/explanation/charms-vs-kubernetes-operators/ +docs/sdk/charmhub: https://charmhub.io/ +docs/sdk/config: https://ops.readthedocs.io/en/latest/howto/manage-configurations.html +docs/sdk/config-yaml: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/config-yaml-file/ +docs/sdk/contributing-md: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/contributing-md-file/ +docs/sdk/create-a-minimal-kubernetes-charm: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/create-a-minimal-kubernetes-charm.html +docs/sdk/create-a-track-for-your-charm: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-tracks/ +docs/sdk/create-an-icon-for-your-charm: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-icons/ +docs/sdk/create-and-publish-a-charm-library: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-libraries/ +docs/sdk/custom-event: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/sdk/debug-a-charm: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-charms/#how-to-manage-charms/#debug-a-charm +docs/sdk/deploy-a-charm: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-charms/#deploy-a-charm-bundle +docs/sdk/dev-setup: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-your-deployment/manage-your-test-environment/#set-things-up +docs/sdk/dispatch: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/dispatch-file/ +docs/sdk/document-your-charm-library: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/libname-py-file/#docstring +docs/sdk/event: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/sdk/events: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/sdk/explanation: https://ops.readthedocs.io/en/latest/explanation/index.html +docs/sdk/expose-the-version-of-the-application-behind-your-charm: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/expose-the-version-of-the-application-behind-your-charm.html +docs/sdk/expose-your-charms-operational-tasks-via-actions: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/expose-operational-tasks-via-actions.html +docs/sdk/extension: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/extensions/ +docs/sdk/find-and-use-a-charm-library: https://ops.readthedocs.io/en/latest/howto/manage-libraries.html#use-a-library +docs/sdk/from-zero-to-hero-write-your-first-kubernetes-charm: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/index.html +docs/sdk/get-logs-from-a-kubernetes-charm: https://canonical-juju.readthedocs-hosted.com/en/latest/user/howto/manage-charms/#how-to-manage-charms/#debug-a-charm +docs/sdk/get-started-with-charm-testing: https://ops.readthedocs.io/en/latest/howto/get-started-with-charm-testing.html +docs/sdk/handle-leadership: https://ops.readthedocs.io/en/latest/howto/manage-leadership-changes.html +docs/sdk/history: https://canonical-juju.readthedocs-hosted.com/en/latest/user/explanation/charming-history/ +docs/sdk/holistic-vs-delta-charms: https://ops.readthedocs.io/en/latest/explanation/holistic-vs-delta-charms.html +docs/sdk/how-and-when-to-defer-events: https://ops.readthedocs.io/en/latest/explanation/how-and-when-to-defer-events.html +docs/sdk/how-to: https://ops.readthedocs.io/en/latest/howto/index.html +docs/sdk/icon-svg: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/icon-svg-file/ +docs/sdk/implement-integrations-in-a-charm: https://ops.readthedocs.io/en/latest/howto/manage-relations.html +docs/sdk/include-extra-files-in-a-charm: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-parts/ +docs/sdk/install-charmcraft: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charmcraft/ +docs/sdk/instrument-your-charm-with-tracing-telemetry: https://charmhub.io/topics/charmed-tempo-ha/instrument-tracing +docs/sdk/integrate-your-charm-with-postgresql: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/integrate-your-charm-with-postgresql.html +docs/sdk/interact-with-pebble: https://ops.readthedocs.io/en/latest/howto/run-workloads-with-a-charm-kubernetes.html +docs/sdk/interface-tests: https://ops.readthedocs.io/en/latest/explanation/interface-tests.html +docs/sdk/jhack: https://github.com/canonical/jhack +docs/sdk/jhack-show-relation: https://github.com/canonical/jhack +docs/sdk/jhack-tail: https://github.com/canonical/jhack +docs/sdk/library: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/libname-py-file/ +docs/sdk/library-index: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/libname-py-file/#popular-libraries +docs/sdk/license: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/license-file/ +docs/sdk/list-of-events: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/sdk/list-of-files-in-the-charm-project: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/ +docs/sdk/logging: https://ops.readthedocs.io/en/latest/howto/manage-logs.html +docs/sdk/lxd-profile-yaml: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/lxd-profile-yaml-file/ +docs/sdk/make-your-charm-configurable: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/make-your-charm-configurable.html +docs/sdk/manage-bundles: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-bundles/ +docs/sdk/manage-charmcraft: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charmcraft/ +docs/sdk/manage-extensions: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-extensions/ +docs/sdk/manage-libraries: https://ops.readthedocs.io/en/latest/howto/manage-libraries.html +docs/sdk/manifest-yaml: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/manifest-yaml-file/ +docs/sdk/metadata-yaml: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/metadata-yaml-file/ +docs/sdk/naming: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/charm-development-best-practices/ +docs/sdk/observe-your-charm-with-cos-lite: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/observe-your-charm-with-cos-lite.html +docs/sdk/open-a-kubernetes-port-in-your-charm: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/open-a-kubernetes-port-in-your-charm.html +docs/sdk/ops: https://ops.readthedocs.io/en/latest/ +docs/sdk/pack-a-charm: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charms/#pack-a-charm +docs/sdk/pack-a-hooks-based-charm-with-charmcraft: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/build-guides/pack-a-hooks-based-charm-with-charmcraft/ +docs/sdk/pack-a-reactive-based-charm-with-charmcraft: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/build-guides/pack-a-reactive-charm-with-charmcraft/ +docs/sdk/pebble: https://ops.readthedocs.io/en/latest/reference/pebble.html +docs/sdk/preserve-your-charms-data: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/preserve-your-charms-data.html +docs/sdk/profile: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/profile/ +docs/sdk/promotion: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-revisions/#promote-a-charm-revision-to-a-better-risk-level +docs/sdk/publication: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charms/#publish-a-charm-on-charmhub +docs/sdk/publish-your-charm-on-charmhub: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/publish-your-charm-on-charmhub.html +docs/sdk/publishing: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charms/#publish-a-charm-on-charmhub +docs/sdk/pyproject-toml: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/pyproject-toml-file/ +docs/sdk/pytest-operator: https://ops.readthedocs.io/en/latest/explanation/testing.html +docs/sdk/readme-md: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/readme-md-file/ +docs/sdk/reasons-to-publish-your-charm-on-charmhub: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charms/#publish-a-charm-on-charmhub +docs/sdk/reference: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/ +docs/sdk/register-an-interface: https://ops.readthedocs.io/en/latest/howto/manage-interfaces.html#register-an-interface +docs/sdk/relation-events: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/sdk/remote-env-auth: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-the-current-charmhub-user/#log-in-to-charmhub +docs/sdk/requirements-dev-txt: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/requirements-dev-txt-file/ +docs/sdk/requirements-txt: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/requirements-txt-file/ +docs/sdk/resources: https://ops.readthedocs.io/en/latest/howto/manage-resources.html +docs/sdk/revision: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/revision/ +docs/sdk/rockcraft: https://documentation.ubuntu.com/rockcraft/en/latest/ +docs/sdk/rockcraft-extension-django-framework: https://documentation.ubuntu.com/rockcraft/en/latest/reference/extensions/django-framework/ +docs/sdk/rockcraft-extension-fastapi-framework: https://documentation.ubuntu.com/rockcraft/en/latest/reference/extensions/fastapi-framework/ +docs/sdk/rockcraft-extension-flask-framework: https://documentation.ubuntu.com/rockcraft/en/latest/reference/extensions/flask-framework/ +docs/sdk/scenario: https://ops.readthedocs.io/en/latest/reference/ops-testing.html +docs/sdk/scenario-context: https://ops.readthedocs.io/en/latest/reference/ops-testing.html +docs/sdk/scenario-event: https://ops.readthedocs.io/en/latest/reference/ops-testing.html +docs/sdk/scenario-state: https://ops.readthedocs.io/en/latest/reference/ops-testing.html +docs/sdk/secret-events: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/sdk/set-the-charm-version: https://ops.readthedocs.io/en/latest/howto/manage-the-charm-version.html +docs/sdk/set-the-workload-version: https://ops.readthedocs.io/en/latest/howto/manage-the-workload-version.html +docs/sdk/set-up-a-charm-project: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/howto/manage-charms/#initialise-a-charm +docs/sdk/set-up-your-development-environment: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/set-up-your-development-environment.html +docs/sdk/src-charm-py: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/src-charm-py-file/ +docs/sdk/status: https://ops.readthedocs.io/en/latest/reference/ops.html#ops.StatusBase +docs/sdk/storage: https://ops.readthedocs.io/en/latest/howto/manage-storage.html +docs/sdk/storage-events: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/sdk/stored-state-uses-limitations: https://ops.readthedocs.io/en/latest/explanation/storedstate-uses-limitations.html +docs/sdk/study-your-application: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/study-your-application.html +docs/sdk/styleguide: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/charm/charm-development-best-practices/ +docs/sdk/talking-to-a-workload-control-flow-from-a-to-z: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/hook/ +docs/sdk/testing: https://ops.readthedocs.io/en/latest/explanation/testing.html +docs/sdk/tests-integration-test-charm-py: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/tests-integration-test-charm-py-file/ +docs/sdk/tests-unit-test-charm-py: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/tests-unit-test-charm-py-file/ +docs/sdk/the-juju-execution-flow-for-a-charm: https://canonical-juju.readthedocs-hosted.com/en/latest/user/reference/juju/juju-architecture/#the-juju-execution-flow-for-a-charm +docs/sdk/tox-ini: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/tox-ini-file/ +docs/sdk/turn-a-hooks-based-charm-into-an-ops-charm: https://ops.readthedocs.io/en/latest/howto/turn-a-hooks-based-charm-into-an-ops-charm.html +docs/sdk/tutorials: https://ops.readthedocs.io/en/latest/tutorial/index.html +docs/sdk/use-storage-in-a-charm: https://ops.readthedocs.io/en/latest/howto/manage-storage.html +docs/sdk/workloads: https://ops.readthedocs.io/en/latest/howto/run-workloads-with-a-charm-machines.html +docs/sdk/write-a-functional-test-for-a-charm-with-scenario: https://ops.readthedocs.io/en/latest/howto/write-scenario-tests-for-a-charm.html +docs/sdk/write-a-scenario-test-for-a-charm-library: https://ops.readthedocs.io/en/latest/howto/manage-libraries.html#write-tests-for-a-library +docs/sdk/write-a-unit-test-for-a-charm: https://ops.readthedocs.io/en/latest/howto/write-unit-tests-for-a-charm.html +docs/sdk/write-integration-tests-for-a-charm: https://ops.readthedocs.io/en/latest/howto/write-integration-tests-for-a-charm.html +docs/sdk/write-integration-tests-for-your-charm: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/write-integration-tests-for-your-charm.html +docs/sdk/write-interface-tests: https://ops.readthedocs.io/en/latest/howto/manage-interfaces.html#write-tests-for-an-interface +docs/sdk/write-scenario-tests-for-your-charm: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/write-scenario-tests-for-your-charm.html +docs/sdk/write-unit-tests-for-your-charm: https://ops.readthedocs.io/en/latest/tutorial/from-zero-to-hero-write-your-first-kubernetes-charm/write-unit-tests-for-your-charm.html +docs/sdk/write-your-first-kubernetes-charm-for-a-django-app: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/tutorial/write-your-first-kubernetes-charm-for-a-django-app/ +docs/sdk/write-your-first-kubernetes-charm-for-a-fastapi-app: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/tutorial/write-your-first-kubernetes-charm-for-a-fastapi-app/ +docs/sdk/write-your-first-kubernetes-charm-for-a-flask-app: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/tutorial/flask/ +docs/sdk/write-your-first-kubernetes-charm-for-a-go-app: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/tutorial/write-your-first-kubernetes-charm-for-a-go-app/ +docs/sdk/write-your-first-machine-charm: https://ops.readthedocs.io/en/latest/tutorial/write-your-first-machine-charm.html +docs/sdk/yaml-anchors-and-aliases: https://canonical-charmcraft.readthedocs-hosted.com/en/stable/reference/files/charmcraft-yaml-file/ From 2f898f6f006943e7ba83ae8c1a37e99c5e653425 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Wed, 29 Jan 2025 08:42:49 +0400 Subject: [PATCH 11/28] change Observe link --- templates/docs/juju-ecosystem-docs.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html index adabf06a..eb525f7c 100644 --- a/templates/docs/juju-ecosystem-docs.html +++ b/templates/docs/juju-ecosystem-docs.html @@ -52,7 +52,7 @@

    Juju ecosystem

  • diff --git a/templates/docs/search.html b/templates/docs/search.html index 31b66260..fb09b4e8 100644 --- a/templates/docs/search.html +++ b/templates/docs/search.html @@ -1,8 +1,8 @@ {% extends "base.html" %} {% block meta %} - {{ super() }} - + {{ super() }} + {% endblock %} {% block title %}Documentation search results{% endblock %} @@ -12,78 +12,73 @@ {% block content %} -
    -
    - -
    -
    +
    +
    + +
    +
    -
    +{% if sorted_results %} +
    +
    +

    + We've found these results for your search: "{{ query }}" +

    +
    +
    +
    +{% else %} +
    +
    - {% if results and results.entries %} -

    We've found these results for your search "{{ query }}"

    - {% else %} -

    We haven't found any results for your search "{{ query }}".

    - {% endif %} +

    + We haven't found any results for your search: "{{ query }}". +

    -
    - - {% if results and results.entries %} - {% for item in results.entries %} -
    -
    -
    {{ item.htmlTitle | safe}}
    -

    - {{ item.htmlSnippet | safe }} -

    - {{ item.htmlFormattedUrl | safe }} +
    +

    Why not try widening your search?

    +

    You can do this by:

    +
      +
    • Adding alternative words or phrases
    • +
    • Using individual words instead of phrases
    • +
    • Trying a different spelling
    • +
    -
    - {% endfor %} -
    - {% if results.queries and results.queries.previousPage %} - - Previous - - {% endif %} - {% if results.queries and results.queries.nextPage %} - - Next - - {% endif %} -
    - {% else %} -
    -
    -
    -

    Why not try widening your search?

    -

    You can do this by:

    -
      -
    • Adding alternative words or phrases
    • -
    • Using individual words instead of phrases
    • -
    • Trying a different spelling
    • -
    -
    -
    -

    Still no luck?

    - -
    +
    +

    Still no luck?

    +
    -
    - {% endif %} + +
    +{% endif %} {% endblock content %} diff --git a/webapp/app.py b/webapp/app.py index 8812c449..6c2050b7 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -3,6 +3,7 @@ import requests import semver +import asyncio import talisker.requests from cachetools import TTLCache, cached from canonicalwebteam import image_template @@ -12,14 +13,18 @@ prepare_deleted, prepare_redirects, ) -from flask import render_template +from flask import render_template, request from flask_cors import cross_origin from webapp.blog.views import init_blog -from webapp.docs.views import init_docs from webapp.greenhouse import Greenhouse from webapp.handlers import set_handlers from webapp.template_utils import current_url_with_query, static_url +from webapp.docs.search import ( + search_all_docs, + process_and_sort_results, + DOMAIN_INFO, +) CACHE_TTL = 60 * 60 # 1 hour cache @@ -56,6 +61,34 @@ def get_in_touch(): return render_template("partials/_get-in-touch.html") +@app.route("/docs") +def docs(): + return render_template("docs/juju-ecosystem-docs.html") + + +@app.route("/docs/search", methods=["GET"]) +def search_docs(): + """Main search function that fetches and ranks documentation results.""" + query = request.args.get("q", "").strip() + if not query: + return render_template( + "docs/search.html", + query=query, + sorted_results=[], + domain_info=DOMAIN_INFO, + ) + + results = asyncio.run(search_all_docs(query)) + sorted_results = process_and_sort_results(results, query) + + return render_template( + "docs/search.html", + query=query, + sorted_results=sorted_results, + domain_info=DOMAIN_INFO, + ) + + @app.route("/latest.json") @cross_origin() @cached(cache=TTLCache(maxsize=128, ttl=CACHE_TTL)) @@ -114,7 +147,6 @@ def get_latest_versions(): app.add_url_rule("/", view_func=template_finder_view) app.add_url_rule("/", view_func=template_finder_view) -init_docs(app) init_blog(app, "/blog") diff --git a/webapp/docs/search.py b/webapp/docs/search.py new file mode 100644 index 00000000..5ec10429 --- /dev/null +++ b/webapp/docs/search.py @@ -0,0 +1,148 @@ +import asyncio +import aiohttp +from urllib.parse import urlparse +from sklearn.feature_extraction.text import TfidfVectorizer + +# ReadTheDocs projects and API endpoints +RTD_PROJECTS = { + "canonical-juju": ( + "https://canonical-juju.readthedocs-hosted.com/_/api/v3/search/" + ), + "canonical-terraform-provider-juju": ( + "https://canonical-terraform-provider-juju.readthedocs-hosted.com" + "/_/api/v3/search/" + ), + "pythonlibjuju": ("https://pythonlibjuju.readthedocs.io/_/api/v3/search/"), + "canonical-jaas-documentation": ( + "https://canonical-jaas-documentation.readthedocs-hosted.com" + "/_/api/v3/search/" + ), + "canonical-charmcraft": ( + "https://canonical-charmcraft.readthedocs-hosted.com/_/api/v3/search/" + ), + "ops": "https://ops.readthedocs.io/_/api/v3/search/", +} + +# Domain information mapping, title for chips, weight for relevance +DOMAIN_INFO = { + "canonical-juju.readthedocs-hosted.com": {"title": "Juju", "weight": 0.6}, + "canonical-terraform-provider-juju.readthedocs-hosted.com": { + "title": "Terraform Juju", + "weight": 0.5, + }, + "pythonlibjuju.readthedocs.io": {"title": "Python Libjuju", "weight": 0.4}, + "canonical-jaas-documentation.readthedocs-hosted.com": { + "title": "JAAS", + "weight": 0.3, + }, + "canonical-charmcraft.readthedocs-hosted.com": { + "title": "Charmcraft", + "weight": 0.2, + }, + "ops.readthedocs.io": {"title": "Ops", "weight": 0.1}, +} + + +async def fetch_search_results(session, project, url, query): + """Fetch search results asynchronously from ReadTheDocs API.""" + params = { + "q": f"project:{project} {query}", + "page_size": 10, # fetch the first 10 results from each domain + } + try: + async with session.get(url, params=params) as response: + return ( + await response.json() + if response.status == 200 + else {"results": []} + ) + except Exception: + return {"results": []} + + +async def search_all_docs(query): + """Run API searches concurrently across multiple domains.""" + async with aiohttp.ClientSession() as session: + tasks = [ + fetch_search_results(session, project, url, query) + for project, url in RTD_PROJECTS.items() + ] + results = await asyncio.gather(*tasks) + return [item for sublist in results for item in sublist["results"]] + + +def calculate_relevance(result, query): + """Calculate relevance using TF-IDF with scaled domain weighting.""" + title = result.get("title", "").lower() + content = " ".join( + block["content"] for block in result.get("blocks", []) + ).lower() + + parsed_domain = urlparse(result.get("domain", "")).hostname or result.get( + "domain", "" + ) + + search_results = [query, title, content] + vectorizer = TfidfVectorizer() + tfidf_matrix = vectorizer.fit_transform(search_results) + + query_vector = tfidf_matrix[0] + title_vector = tfidf_matrix[1] + content_vector = tfidf_matrix[2] + + title_score = (query_vector * title_vector.T).sum() * 1.5 + content_score = (query_vector * content_vector.T).sum() + + # Boost "How to..." articles + how_to_boost = 0.5 if title.startswith("how to") else 0 + + # Normalize domain weight + domain_weight = DOMAIN_INFO.get(parsed_domain, {}).get("weight", 1) + domain_multiplier = 1 + ( + domain_weight / 5 + ) # Convert weight into a multiplier + + # Final score with domain multiplier + return (title_score + content_score + how_to_boost) * domain_multiplier + + +def process_and_sort_results(results, query, max_length=200, limit=20): + """ + Merge, truncate, and sort search results based on relevance, + limiting to top 20 results. + """ + processed_results = [] + + for result in results: + parsed_domain = urlparse( + result.get("domain", "") + ).hostname or result.get("domain", "") + project_name = DOMAIN_INFO.get(parsed_domain, {}).get( + "title", parsed_domain + ) + + full_content = " ".join( + block["content"] for block in result.get("blocks", []) + ) + short_content = ( + full_content[:max_length] + "..." + if len(full_content) > max_length + else full_content + ) + + relevance_score = calculate_relevance(result, query) + + processed_results.append( + { + "title": result.get("title", "Untitled"), + "url": f"https://{parsed_domain}{result.get('path', '')}", + "domain": parsed_domain, + "project_name": project_name, + "short_content": short_content, + "relevance_score": relevance_score, + } + ) + + return sorted( + processed_results, key=lambda x: x["relevance_score"], reverse=True + )[:limit] diff --git a/webapp/docs/views.py b/webapp/docs/views.py deleted file mode 100644 index 7d0c00f1..00000000 --- a/webapp/docs/views.py +++ /dev/null @@ -1,26 +0,0 @@ -import talisker.requests - -from canonicalwebteam.search import build_search_view -from flask import render_template - -RTD_DOCS_BASE_URL = "https://canonical-juju.readthedocs.io/en/latest/" - - -def init_docs(app): - session = talisker.requests.get_session() - - def render_juju_docs_page(): - return render_template("docs/juju-ecosystem-docs.html") - - app.add_url_rule("/docs", "juju-ecosystem-docs", render_juju_docs_page) - - app.add_url_rule( - "/docs/search", - "docs-search", - build_search_view( - app=app, - session=session, - site="https://canonical-juju.readthedocs-hosted.com/en/latest", - template_path="docs/search.html", - ), - ) From cf7e75e13a6e7222297a05c6963844a7656379f3 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Thu, 30 Jan 2025 16:29:17 +0400 Subject: [PATCH 13/28] fix requirements.txt --- requirements.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index cdca54bd..074417ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,5 @@ requests==2.31.0 semver==3.0.2 cachetools==5.3.3 flask-cors==4.0.0 -asyncio -aiohttp -scikit-learn +aiohttp==3.11.11 +scikit-learn==1.6.1 From a03c73db2dff0761ffd059a7607d3e52a2ec4eeb Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Fri, 31 Jan 2025 11:04:56 +0400 Subject: [PATCH 14/28] fix hr on smaller screens --- templates/docs/juju-ecosystem-docs.html | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html index eb525f7c..dd020052 100644 --- a/templates/docs/juju-ecosystem-docs.html +++ b/templates/docs/juju-ecosystem-docs.html @@ -65,10 +65,10 @@

    Juju ecosystem

    +
    +
    +
    -
    -
    -
    @@ -79,12 +79,12 @@

    Juju ecosystem

    1. what is juju
    +
    +
    +
    -
    -
    -

    What is Juju?

    @@ -100,11 +100,11 @@

    alt="2. install juju" width="100%" height="100%">

    - -

    +
    +

    Install Juju @@ -122,11 +122,11 @@

    width="100%" height="100%">

    - -

    +
    +

    Connect a cloud @@ -184,11 +184,11 @@

    alt="4. use charmed applications" width="100%" height="100%">

    - -

    +
    +

    Use charmed applications From 9fafcbf92592e9050df360ac253b2ea422cf3515 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Fri, 31 Jan 2025 14:42:54 +0400 Subject: [PATCH 15/28] allow async events in flask --- requirements.txt | 1 + webapp/app.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 074417ff..cfa2102f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ cachetools==5.3.3 flask-cors==4.0.0 aiohttp==3.11.11 scikit-learn==1.6.1 +flask[async] diff --git a/webapp/app.py b/webapp/app.py index 6c2050b7..e6113853 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -67,7 +67,7 @@ def docs(): @app.route("/docs/search", methods=["GET"]) -def search_docs(): +async def search_docs(): """Main search function that fetches and ranks documentation results.""" query = request.args.get("q", "").strip() if not query: @@ -78,7 +78,7 @@ def search_docs(): domain_info=DOMAIN_INFO, ) - results = asyncio.run(search_all_docs(query)) + results = await search_all_docs(query) sorted_results = process_and_sort_results(results, query) return render_template( From 521bd9219aa07274cb604aefe14c5c3fb5442282 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Fri, 31 Jan 2025 14:44:57 +0400 Subject: [PATCH 16/28] fix python linting --- webapp/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp/app.py b/webapp/app.py index e6113853..1661c434 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -3,7 +3,6 @@ import requests import semver -import asyncio import talisker.requests from cachetools import TTLCache, cached from canonicalwebteam import image_template From 34aabc3a74b695b5fa09a2f47d12bbf5e11d2f8b Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Fri, 31 Jan 2025 17:30:13 +0400 Subject: [PATCH 17/28] fix async search event loop --- webapp/app.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/webapp/app.py b/webapp/app.py index 1661c434..216d455b 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -4,6 +4,7 @@ import requests import semver import talisker.requests +import asyncio from cachetools import TTLCache, cached from canonicalwebteam import image_template from canonicalwebteam.flask_base.app import FlaskBase @@ -66,7 +67,7 @@ def docs(): @app.route("/docs/search", methods=["GET"]) -async def search_docs(): +def search_docs(): """Main search function that fetches and ranks documentation results.""" query = request.args.get("q", "").strip() if not query: @@ -77,7 +78,16 @@ async def search_docs(): domain_info=DOMAIN_INFO, ) - results = await search_all_docs(query) + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + # schedule the async function and get the result + future = asyncio.ensure_future(search_all_docs(query)) + results = loop.run_until_complete(future) if not loop.is_running() else future.result() + sorted_results = process_and_sort_results(results, query) return render_template( From cb1ce33bc3c4418bef6624fcdafc5636167d7ce0 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Fri, 31 Jan 2025 17:31:49 +0400 Subject: [PATCH 18/28] fix python linting --- webapp/app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webapp/app.py b/webapp/app.py index 216d455b..f556ae51 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -86,7 +86,11 @@ def search_docs(): # schedule the async function and get the result future = asyncio.ensure_future(search_all_docs(query)) - results = loop.run_until_complete(future) if not loop.is_running() else future.result() + results = ( + loop.run_until_complete(future) + if not loop.is_running() + else future.result() + ) sorted_results = process_and_sort_results(results, query) From 83007530db8af342a91ab4f34650481f4debd06d Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Fri, 31 Jan 2025 18:00:57 +0400 Subject: [PATCH 19/28] replace async search with threadpool --- requirements.txt | 2 -- webapp/app.py | 16 +-------------- webapp/docs/search.py | 46 +++++++++++++++++++++++++------------------ 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/requirements.txt b/requirements.txt index cfa2102f..54a802c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,4 @@ requests==2.31.0 semver==3.0.2 cachetools==5.3.3 flask-cors==4.0.0 -aiohttp==3.11.11 scikit-learn==1.6.1 -flask[async] diff --git a/webapp/app.py b/webapp/app.py index f556ae51..4abbf549 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -4,7 +4,6 @@ import requests import semver import talisker.requests -import asyncio from cachetools import TTLCache, cached from canonicalwebteam import image_template from canonicalwebteam.flask_base.app import FlaskBase @@ -78,20 +77,7 @@ def search_docs(): domain_info=DOMAIN_INFO, ) - try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - # schedule the async function and get the result - future = asyncio.ensure_future(search_all_docs(query)) - results = ( - loop.run_until_complete(future) - if not loop.is_running() - else future.result() - ) - + results = search_all_docs(query) sorted_results = process_and_sort_results(results, query) return render_template( diff --git a/webapp/docs/search.py b/webapp/docs/search.py index 5ec10429..dfea2e14 100644 --- a/webapp/docs/search.py +++ b/webapp/docs/search.py @@ -1,6 +1,6 @@ -import asyncio -import aiohttp from urllib.parse import urlparse +import requests +import concurrent.futures from sklearn.feature_extraction.text import TfidfVectorizer # ReadTheDocs projects and API endpoints @@ -43,32 +43,40 @@ } -async def fetch_search_results(session, project, url, query): - """Fetch search results asynchronously from ReadTheDocs API.""" +def fetch_search_results(project, url, query): + """Fetch search results synchronously from ReadTheDocs API.""" params = { "q": f"project:{project} {query}", "page_size": 10, # fetch the first 10 results from each domain } try: - async with session.get(url, params=params) as response: - return ( - await response.json() - if response.status == 200 - else {"results": []} - ) - except Exception: + response = requests.get(url, params=params, timeout=5) + return ( + response.json() if response.status_code == 200 else {"results": []} + ) + except requests.exceptions.RequestException: return {"results": []} -async def search_all_docs(query): - """Run API searches concurrently across multiple domains.""" - async with aiohttp.ClientSession() as session: - tasks = [ - fetch_search_results(session, project, url, query) +def search_all_docs(query): + """Run API searches concurrently using ThreadPoolExecutor.""" + results = [] + + # use ThreadPoolExecutor to make requests in parallel + with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: + future_to_project = { + executor.submit(fetch_search_results, project, url, query): project for project, url in RTD_PROJECTS.items() - ] - results = await asyncio.gather(*tasks) - return [item for sublist in results for item in sublist["results"]] + } + + for future in concurrent.futures.as_completed(future_to_project): + try: + search_results = future.result() + results.extend(search_results.get("results", [])) + except Exception as e: + print(f"Error fetching search results: {e}") + + return results def calculate_relevance(result, query): From 3c4da40c084e659cd32cc367018b8ebf58e6a781 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Mon, 3 Feb 2025 09:44:43 +0400 Subject: [PATCH 20/28] move Juju tutorial CTA --- templates/docs/juju-ecosystem-docs.html | 31 +++++++------------------ 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html index dd020052..9655e56e 100644 --- a/templates/docs/juju-ecosystem-docs.html +++ b/templates/docs/juju-ecosystem-docs.html @@ -43,10 +43,14 @@

    Juju ecosystem

    -

    Juju is an ecosystem of tools for deploying and managing applications on any - cloud on any - infrastructure, Kubernetes or otherwise, using the Juju operator lifecycle manager and operators - called 'charms'.

    +

    Juju is an ecosystem of tools for deploying and managing applications on any + cloud on any infrastructure, Kubernetes or otherwise, using the Juju operator lifecycle manager + and operators called 'charms'. +

    + Try the Juju tutorial +
    @@ -69,7 +73,7 @@

    Juju ecosystem


    -
    +
    @@ -220,23 +224,6 @@

    -
    -
    -
    -
    -
    -
    -
    -

    Get started with Juju

    -
    -
    -

    From 352b4627dac1413f56df19b6f59d169608017922 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Mon, 3 Feb 2025 10:31:53 +0400 Subject: [PATCH 21/28] add logging for debugging --- webapp/docs/search.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/webapp/docs/search.py b/webapp/docs/search.py index dfea2e14..81e20146 100644 --- a/webapp/docs/search.py +++ b/webapp/docs/search.py @@ -2,6 +2,9 @@ import requests import concurrent.futures from sklearn.feature_extraction.text import TfidfVectorizer +import logging + +logging.basicConfig(level=logging.INFO) # ReadTheDocs projects and API endpoints RTD_PROJECTS = { @@ -44,17 +47,33 @@ def fetch_search_results(project, url, query): - """Fetch search results synchronously from ReadTheDocs API.""" + """ + Fetch search results synchronously from ReadTheDocs API + and log responses. + """ params = { "q": f"project:{project} {query}", "page_size": 10, # fetch the first 10 results from each domain } + + logging.info(f"Sending API Request to {url}") + logging.info(f"Query Parameters: {params}") + try: response = requests.get(url, params=params, timeout=5) - return ( - response.json() if response.status_code == 200 else {"results": []} - ) - except requests.exceptions.RequestException: + logging.info(f"Response Status: {response.status_code}") + logging.info(f"Response Body: {response.json()}") + + if response.status_code == 200: + return response.json() + else: + logging.warning(f"API request failed: {response.status_code}") + logging.warning(f"Response text: {response.text}") + + return {"results": []} + + except requests.exceptions.RequestException as e: + logging.error(f"Network error fetching {project}: {e}") return {"results": []} From ed672fe4bc8322e0354201b2ed52d869e94705b1 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Mon, 3 Feb 2025 16:06:40 +0400 Subject: [PATCH 22/28] clean up logging logic --- webapp/docs/search.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/webapp/docs/search.py b/webapp/docs/search.py index 81e20146..5a566cc8 100644 --- a/webapp/docs/search.py +++ b/webapp/docs/search.py @@ -2,9 +2,7 @@ import requests import concurrent.futures from sklearn.feature_extraction.text import TfidfVectorizer -import logging -logging.basicConfig(level=logging.INFO) # ReadTheDocs projects and API endpoints RTD_PROJECTS = { @@ -56,24 +54,14 @@ def fetch_search_results(project, url, query): "page_size": 10, # fetch the first 10 results from each domain } - logging.info(f"Sending API Request to {url}") - logging.info(f"Query Parameters: {params}") - try: response = requests.get(url, params=params, timeout=5) - logging.info(f"Response Status: {response.status_code}") - logging.info(f"Response Body: {response.json()}") - if response.status_code == 200: return response.json() else: - logging.warning(f"API request failed: {response.status_code}") - logging.warning(f"Response text: {response.text}") - return {"results": []} - except requests.exceptions.RequestException as e: - logging.error(f"Network error fetching {project}: {e}") + except requests.exceptions.RequestException: return {"results": []} @@ -99,7 +87,13 @@ def search_all_docs(query): def calculate_relevance(result, query): - """Calculate relevance using TF-IDF with scaled domain weighting.""" + """ + * This calculation is very sensitive + and is a V1 for the Juju ecosystem docs. + It will need modified in the future. + + Calculate relevance using TF-IDF with scaled domain weighting. + """ title = result.get("title", "").lower() content = " ".join( block["content"] for block in result.get("blocks", []) From 7d0ca9ee862a059583e3c57b8448beb8fd35e384 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Mon, 3 Feb 2025 16:48:04 +0400 Subject: [PATCH 23/28] add pagination and load more results button --- templates/docs/search.html | 61 +++++++++++++++++++++++++++++--------- webapp/docs/search.py | 6 ++-- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/templates/docs/search.html b/templates/docs/search.html index fb09b4e8..00735f99 100644 --- a/templates/docs/search.html +++ b/templates/docs/search.html @@ -35,22 +35,23 @@

    We've found these results for your search: "{{ query }}"

    +
    {% else %} @@ -80,5 +81,37 @@

    Still no luck?

    {% endif %} + + {% endblock content %} diff --git a/webapp/docs/search.py b/webapp/docs/search.py index 5a566cc8..fd1595ce 100644 --- a/webapp/docs/search.py +++ b/webapp/docs/search.py @@ -127,10 +127,10 @@ def calculate_relevance(result, query): return (title_score + content_score + how_to_boost) * domain_multiplier -def process_and_sort_results(results, query, max_length=200, limit=20): +def process_and_sort_results(results, query, max_length=200): """ Merge, truncate, and sort search results based on relevance, - limiting to top 20 results. + returning all results for frontend pagination. """ processed_results = [] @@ -166,4 +166,4 @@ def process_and_sort_results(results, query, max_length=200, limit=20): return sorted( processed_results, key=lambda x: x["relevance_score"], reverse=True - )[:limit] + ) From 4e5e1fb8ea2afd6add3eac656b677f4944369b2b Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Mon, 3 Feb 2025 18:34:29 +0400 Subject: [PATCH 24/28] add spacing to search results --- templates/docs/search.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/docs/search.html b/templates/docs/search.html index 00735f99..780892c2 100644 --- a/templates/docs/search.html +++ b/templates/docs/search.html @@ -36,8 +36,8 @@

      {% for result in sorted_results %} -
    • -
      +
    • +
      {{ result.title }}   From 275592e9d5e38448e06e09c2ca27be0460d35b50 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Tue, 4 Feb 2025 10:13:54 +0400 Subject: [PATCH 25/28] add redirect for empty search and fix container styling --- static/sass/styles.scss | 29 ++++++++++++++++++++++++- templates/docs/juju-ecosystem-docs.html | 12 +++++----- webapp/app.py | 9 ++------ 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/static/sass/styles.scss b/static/sass/styles.scss index 7d036266..c18600bb 100644 --- a/static/sass/styles.scss +++ b/static/sass/styles.scss @@ -329,4 +329,31 @@ html { strong { font-weight: bold !important; -} \ No newline at end of file +} + +.p-image-container, [class^=p-image-container--] { + aspect-ratio: auto !important; + height: auto !important; +} + +.p-image-container__image { + display: block; +} + +@media (620px <= width < 1036px) { + .any-application-container img { + object-fit: cover; + object-position: center; + width: 100%; + height: 230px; + } +} + +@media (width < 620px) { + .any-application-container img { + object-fit: cover; + object-position: center; + width: 100%; + height: 200px; + } +} diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html index 9655e56e..06697813 100644 --- a/templates/docs/juju-ecosystem-docs.html +++ b/templates/docs/juju-ecosystem-docs.html @@ -296,7 +296,7 @@

      -

      And many more...

      +

      And many more...

      @@ -472,10 +472,12 @@

      Build charmed applications

      Any application

      diff --git a/webapp/app.py b/webapp/app.py index 4abbf549..e3283ebd 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -12,7 +12,7 @@ prepare_deleted, prepare_redirects, ) -from flask import render_template, request +from flask import redirect, render_template, request, url_for from flask_cors import cross_origin from webapp.blog.views import init_blog @@ -70,12 +70,7 @@ def search_docs(): """Main search function that fetches and ranks documentation results.""" query = request.args.get("q", "").strip() if not query: - return render_template( - "docs/search.html", - query=query, - sorted_results=[], - domain_info=DOMAIN_INFO, - ) + return redirect(url_for("docs")) results = search_all_docs(query) sorted_results = process_and_sort_results(results, query) From 2cc2ab7163ce695480fc8c835608bdd8cc176840 Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Tue, 4 Feb 2025 11:33:18 +0400 Subject: [PATCH 26/28] combine api request for hosted docs --- webapp/docs/search.py | 93 ++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/webapp/docs/search.py b/webapp/docs/search.py index fd1595ce..edaea197 100644 --- a/webapp/docs/search.py +++ b/webapp/docs/search.py @@ -4,23 +4,17 @@ from sklearn.feature_extraction.text import TfidfVectorizer -# ReadTheDocs projects and API endpoints -RTD_PROJECTS = { - "canonical-juju": ( - "https://canonical-juju.readthedocs-hosted.com/_/api/v3/search/" - ), - "canonical-terraform-provider-juju": ( - "https://canonical-terraform-provider-juju.readthedocs-hosted.com" - "/_/api/v3/search/" - ), - "pythonlibjuju": ("https://pythonlibjuju.readthedocs.io/_/api/v3/search/"), - "canonical-jaas-documentation": ( - "https://canonical-jaas-documentation.readthedocs-hosted.com" - "/_/api/v3/search/" - ), - "canonical-charmcraft": ( - "https://canonical-charmcraft.readthedocs-hosted.com/_/api/v3/search/" - ), +# RTD API Endpoints +RTD_HOSTED_API = "https://readthedocs.com/api/v3/search/" +RTD_PROJECTS_HOSTED = [ + "canonical-juju", + "canonical-terraform-provider-juju", + "canonical-charmcraft", + "canonical-jaas-documentation", +] + +RTD_PROJECTS_IO = { + "pythonlibjuju": "https://pythonlibjuju.readthedocs.io/_/api/v3/search/", "ops": "https://ops.readthedocs.io/_/api/v3/search/", } @@ -44,44 +38,63 @@ } -def fetch_search_results(project, url, query): +def fetch_search_results(api_url, query, projects=None): """ - Fetch search results synchronously from ReadTheDocs API - and log responses. + Fetch search results from ReadTheDocs API. + - If `projects` is provided, constructs a query filtering + by multiple projects. + - Otherwise, assumes the API requires an explicit + `project:project-name` prefix. """ - params = { - "q": f"project:{project} {query}", - "page_size": 10, # fetch the first 10 results from each domain - } + if projects: + # Query multiple projects in one request + project_filters = " ".join( + [f"project:{project}" for project in projects] + ) + full_query = f"{project_filters} {query}" + else: + full_query = f"project:{query}" + + params = {"q": full_query, "page_size": 50} try: - response = requests.get(url, params=params, timeout=5) + response = requests.get(api_url, params=params, timeout=5) if response.status_code == 200: - return response.json() - else: - return {"results": []} + return response.json().get("results", []) + except requests.exceptions.RequestException as e: + print(f"Error fetching search results from {api_url}: {e}") - except requests.exceptions.RequestException: - return {"results": []} + return [] def search_all_docs(query): - """Run API searches concurrently using ThreadPoolExecutor.""" + """ + Fetch documentation search results: + - One request for all hosted projects (`readthedocs.com`). + - Two separate requests for `pythonlibjuju` and `ops`. + """ results = [] - # use ThreadPoolExecutor to make requests in parallel - with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: - future_to_project = { - executor.submit(fetch_search_results, project, url, query): project - for project, url in RTD_PROJECTS.items() + with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: + # Hosted docs (single request) + future_hosted = executor.submit( + fetch_search_results, RTD_HOSTED_API, query, RTD_PROJECTS_HOSTED + ) + + # Separate requests for pythonlibjuju and ops + future_io = { + project: executor.submit( + fetch_search_results, url, f"{project} {query}" + ) + for project, url in RTD_PROJECTS_IO.items() } - for future in concurrent.futures.as_completed(future_to_project): + results.extend(future_hosted.result()) + for project, future in future_io.items(): try: - search_results = future.result() - results.extend(search_results.get("results", [])) + results.extend(future.result()) except Exception as e: - print(f"Error fetching search results: {e}") + print(f"Error processing search results for {project}: {e}") return results From 8c5a72545f50e9589f406d45e3984445dcda863f Mon Sep 17 00:00:00 2001 From: Abbie Sims Date: Tue, 4 Feb 2025 16:24:51 +0400 Subject: [PATCH 27/28] fix formatting on mobile --- templates/docs/juju-ecosystem-docs.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/docs/juju-ecosystem-docs.html b/templates/docs/juju-ecosystem-docs.html index 06697813..ec4960bf 100644 --- a/templates/docs/juju-ecosystem-docs.html +++ b/templates/docs/juju-ecosystem-docs.html @@ -230,7 +230,7 @@