Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Community merge conflicts #113

Merged
merged 100 commits into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from 67 commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
5414045
fix: create admin api key
yanksyoon Mar 16, 2023
33421a3
fix: revert to discourse from charmhub
yanksyoon Mar 16, 2023
a13becc
fix: typo
yanksyoon Mar 16, 2023
50f6381
fix: user & api key creation flow
yanksyoon Mar 17, 2023
9da601f
fix: upgrade discourse charm
yanksyoon Mar 19, 2023
e8bbc16
add function that retrieves the file contents
jdkandersson Mar 21, 2023
af7fbc7
add tests for retrieving content of a file
jdkandersson Mar 21, 2023
7f1e328
fix linting issues
jdkandersson Mar 21, 2023
d2ef605
remove unnecessary None alternative
jdkandersson Mar 21, 2023
40fdaf3
make repository client available to reconcile
jdkandersson Mar 21, 2023
fb6c853
add content from default branch to content change
jdkandersson Mar 21, 2023
115089b
fix: remove unlist parameter(user level api failure)
yanksyoon Mar 21, 2023
4af70d7
fix: update assert to pass returned content
yanksyoon Mar 21, 2023
5b1c9cc
fix: set hostname for successful redirect
yanksyoon Mar 21, 2023
944a45d
fix: static test dependency update to bandit w/ toml
yanksyoon Mar 21, 2023
8317844
fix: lint fixes
yanksyoon Mar 21, 2023
cf35ca6
test: wait for idle for longer
yanksyoon Mar 21, 2023
ed501be
test: give enough timeout for asset compile
yanksyoon Mar 21, 2023
b340f36
fix: deploy and wait for idle before getting status
yanksyoon Mar 21, 2023
cf43316
debug
yanksyoon Mar 21, 2023
0b868e8
fix: parse raw content
yanksyoon Mar 21, 2023
d850a2a
fix: lint fixes
yanksyoon Mar 21, 2023
2f4501d
fix: wait for idle not active status
yanksyoon Mar 22, 2023
88c7010
fix: content in paragraph tag
yanksyoon Mar 22, 2023
83db749
fix: backwards compatibility fix for e2e
yanksyoon Mar 22, 2023
eb65c39
fix: infinte wait fix
yanksyoon Mar 22, 2023
7f7d40f
add merge conflict module
jdkandersson Mar 22, 2023
2815790
add tests for content module
jdkandersson Mar 22, 2023
0a13c92
fix: set config after relations
yanksyoon Mar 22, 2023
4eafb6f
fix: reorder deploy flow
yanksyoon Mar 22, 2023
a9cbcf5
fix: wait for ip
yanksyoon Mar 22, 2023
b0e6ebe
fix: revert
yanksyoon Mar 22, 2023
368855f
chore: revert back to main operator workflow
yanksyoon Mar 22, 2023
45cec2b
chore: pin juju version & move to dev-requirements.txt
yanksyoon Mar 22, 2023
12c794e
chore: remove pre_run
yanksyoon Mar 22, 2023
43e3c65
fix linting issues
jdkandersson Mar 23, 2023
e7e4e9b
add module that checks actions
jdkandersson Mar 23, 2023
63faa56
refactor
jdkandersson Mar 23, 2023
acc8f7b
add check for any content conflicts
jdkandersson Mar 23, 2023
35d0157
add reconcile test with merge conflicts
jdkandersson Mar 23, 2023
a3cf054
add content merging
jdkandersson Mar 23, 2023
bc564a7
separate action tests into multiple modules
jdkandersson Mar 23, 2023
2f9fe75
fix linting issues
jdkandersson Mar 23, 2023
2e77eff
chore: add timeouts to requests
yanksyoon Mar 23, 2023
fddb8a0
test: separate out response parsing
yanksyoon Mar 23, 2023
3376403
chore: revert content assertions
yanksyoon Mar 23, 2023
c7ec6a7
fix: doc imperative mood fix
yanksyoon Mar 23, 2023
4d76466
test: fixturize admin API headers
yanksyoon Mar 23, 2023
5d29487
test: consistency fixes
yanksyoon Mar 23, 2023
41afb91
chore: remove unnecessary fstring
yanksyoon Mar 23, 2023
f33a012
fix: fixture reference
yanksyoon Mar 24, 2023
22398c6
merge fix/ci
jdkandersson Mar 24, 2023
06bbb10
add github token to e2e tests
jdkandersson Mar 24, 2023
fc9f657
move requirements to requirements.txt files
jdkandersson Mar 24, 2023
2148cef
add support for the case where the file was not found on the branch
jdkandersson Mar 24, 2023
651a160
add tests for no base
jdkandersson Mar 24, 2023
323c76a
add support for retrieving the file from a particular branch
jdkandersson Mar 24, 2023
5af706c
add base branch input
jdkandersson Mar 24, 2023
86b92d3
add tests for that file is retrieved from the correct branch
jdkandersson Mar 24, 2023
3afc02c
fix linting issues
jdkandersson Mar 24, 2023
233fc11
to trigger CI
jdkandersson Mar 24, 2023
c8a5793
merge main
jdkandersson Mar 24, 2023
15d0734
fix linting issues
jdkandersson Mar 28, 2023
c893551
fix wording issues
jdkandersson Mar 28, 2023
b5cd499
add integration test for merge conflict
jdkandersson Mar 28, 2023
dee0079
update docs
jdkandersson Mar 28, 2023
7224454
fix security issue
jdkandersson Mar 28, 2023
d9db83a
rename conflict so that it doesn't get caught by reconcile mark
jdkandersson Mar 29, 2023
4d4389f
aggregate clients into a tuple
jdkandersson Mar 29, 2023
3bd73f8
fix linting issues
jdkandersson Mar 29, 2023
4cdc71f
update attributes
jdkandersson Mar 29, 2023
c52b540
change to use constants
jdkandersson Mar 29, 2023
dc9507c
correct docs
jdkandersson Mar 30, 2023
e57a981
add more granular exception handling
jdkandersson Mar 30, 2023
de5774a
fix linting issue
jdkandersson Mar 30, 2023
5474bab
add update e2e test
jdkandersson Mar 30, 2023
4e0af57
remove spaces
jdkandersson Mar 30, 2023
e5aa0ac
change to append extra content
jdkandersson Mar 30, 2023
adbde17
show errors with deleting branch
jdkandersson Mar 30, 2023
b41441d
move the update content before branch creation
jdkandersson Mar 30, 2023
52c4c59
fix incorrect repo name
jdkandersson Mar 30, 2023
589f897
correct expected result
jdkandersson Mar 30, 2023
e1f2879
add branch argument where it was missing
jdkandersson Mar 30, 2023
b9f59c8
reenable all tests
jdkandersson Mar 30, 2023
39fb77a
change logic to be more maintainable
jdkandersson Mar 31, 2023
11ea738
move writing GitGHub output to separate module
jdkandersson Mar 31, 2023
eec8dd2
add docs for checking for conflicts
jdkandersson Mar 31, 2023
7ad1e78
correct grammar
jdkandersson Mar 31, 2023
6902a91
remove version pin for pytest-operator
jdkandersson Mar 31, 2023
277c536
remove unnecessary new lines
jdkandersson Mar 31, 2023
779a9cc
change to split the how and execution logic
jdkandersson Mar 31, 2023
8fc9d68
fix linting issue
jdkandersson Mar 31, 2023
06a44b4
switch back to Python 3.10 for now for test execution
jdkandersson Mar 31, 2023
a6a6b62
correct docs and log cleanup failures
jdkandersson Apr 3, 2023
a64bad2
remove unnecessary single quotes
jdkandersson Apr 4, 2023
4dd0908
add back exception that was removed
jdkandersson Apr 4, 2023
139d8f3
fix type
jdkandersson Apr 4, 2023
be4da3a
clarify variable name
jdkandersson Apr 4, 2023
a48401e
remove unnecessary del
jdkandersson Apr 4, 2023
c76d5f2
remove unnecessary comment
jdkandersson Apr 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/e2e_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
discourse_host: discourse.charmhub.io
discourse_api_username: ${{ secrets.DISCOURSE_API_USERNAME }}
discourse_api_key: ${{ secrets.DISCOURSE_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
dry_run: true
- name: Show pages
run: echo '${{ steps.e2e-test-draft.outputs.urls_with_actions }}'
Expand All @@ -71,6 +72,7 @@ jobs:
discourse_host: discourse.charmhub.io
discourse_api_username: ${{ secrets.DISCOURSE_API_USERNAME }}
discourse_api_key: ${{ secrets.DISCOURSE_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Show pages
run: echo '${{ steps.e2e-test-create.outputs.urls_with_actions }}'
- name: Check create
Expand All @@ -90,6 +92,7 @@ jobs:
discourse_host: discourse.charmhub.io
discourse_api_username: ${{ secrets.DISCOURSE_API_USERNAME }}
discourse_api_key: ${{ secrets.DISCOURSE_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Show pages
run: echo '${{ steps.e2e-test-delete-topics.outputs.urls_with_actions }}'
- name: Check delete topics disabled
Expand All @@ -104,6 +107,7 @@ jobs:
discourse_host: discourse.charmhub.io
discourse_api_username: ${{ secrets.DISCOURSE_API_USERNAME }}
discourse_api_key: ${{ secrets.DISCOURSE_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Show pages
run: echo '${{ steps.e2e-test-delete.outputs.urls_with_actions }}'
- name: Check delete topics enabled
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ jobs:
uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main
secrets: inherit
with:
modules: '["discourse", "reconcile", "migrate"]'
modules: '["discourse", "reconcile", "reconcile_conflict", "migrate"]'
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ jobs:
- name: Check for any code improvements
run: |
PYTHON_FILES=$(find . -name '*.py')
pyupgrade --py310-plus $PYTHON_FILES
pyupgrade --py311-plus $PYTHON_FILES
refurb $PYTHON_FILES
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@ charmhub.
github_token: ${{ secrets.GITHUB_TOKEN }}
```

a branch name with `upload-charm-docs/migrate` will be created and a pull
request named `[upload-charm-docs] Migrate charm docs` will be created
towards the working branch the workflow was triggered with.
In order to ensure that the branches can be created successfully, please
a branch name with `upload-charm-docs/migrate` will be created and a pull
request named `[upload-charm-docs] Migrate charm docs` will be created
towards the working branch the workflow was triggered with.
jdkandersson marked this conversation as resolved.
Show resolved Hide resolved
In order to ensure that the branches can be created successfully, please
make sure that there are no existing branches clashing with the name above.
Please note that `dry_run` parameter has no effect on migrate mode.

Expand Down
6 changes: 6 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ inputs:
Required if running in migration mode.
required: false
type: string
base_branch:
arturo-seijas marked this conversation as resolved.
Show resolved Hide resolved
description: |
The name of the branch that contains the documentation after it has been uploaded to
discourse. Defaults to the default branch of the repository.
required: false
type: string
outputs:
urls_with_actions:
description: |
Expand Down
4 changes: 4 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
factory_boy>=3.2,<3.3
ops>=2.1,<2.2
jdkandersson marked this conversation as resolved.
Show resolved Hide resolved
pytest-asyncio>=0.21,<0.22
pytest-operator>=0.26,<0.27
pytest>=7.2,<7.3
juju==3.0.4
2 changes: 2 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def _parse_env_vars() -> types_.UserInputs:
delete_topics = os.getenv("INPUT_DELETE_TOPICS") == "true"
dry_run = os.getenv("INPUT_DRY_RUN") == "true"
github_access_token = os.getenv("INPUT_GITHUB_TOKEN")
base_branch = os.getenv("INPUT_BASE_BRANCH")

return types_.UserInputs(
discourse=types_.UserInputsDiscourse(
Expand All @@ -47,6 +48,7 @@ def _parse_env_vars() -> types_.UserInputs:
delete_pages=delete_topics,
dry_run=dry_run,
github_access_token=github_access_token,
base_branch=base_branch,
)


Expand Down
14 changes: 7 additions & 7 deletions prepare_check_cleanup/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
from github.Repository import Repository

from prepare_check_cleanup import exit_
from src.constants import (
DOCUMENTATION_FOLDER_NAME,
DOCUMENTATION_INDEX_FILENAME,
NAVIGATION_TABLE_START,
)
from src.discourse import create_discourse
from src.exceptions import DiscourseError
from src.index import DOCUMENTATION_FOLDER_NAME, DOCUMENTATION_INDEX_FILENAME
from src.pull_request import (
ACTIONS_PULL_REQUEST_TITLE,
DEFAULT_BRANCH_NAME,
create_repository_client,
)
from src.reconcile import NAVIGATION_TABLE_START
from src.pull_request import DEFAULT_BRANCH_NAME
from src.repository import ACTIONS_PULL_REQUEST_TITLE, create_repository_client


class Action(str, Enum):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ show_missing = true
[tool.pytest.ini_options]
minversion = "6.0"
log_cli_level = "INFO"
markers = ["reconcile", "migrate", "discourse"]
markers = ["reconcile", "reconcile_conflict", "migrate", "discourse"]

# Formatting tools configuration
[tool.black]
Expand Down
53 changes: 39 additions & 14 deletions src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@

"""Library for uploading docs to charmhub."""

from itertools import tee
from pathlib import Path

from .action import DRY_RUN_NAVLINK_LINK, FAIL_NAVLINK_LINK
from .action import run_all as run_all_actions
from .check import conflicts as check_conflicts
from .constants import DOCUMENTATION_FOLDER_NAME
from .discourse import Discourse
from .docs_directory import has_docs_directory
from .docs_directory import read as read_docs_directory
from .exceptions import InputError
from .index import DOCUMENTATION_FOLDER_NAME, contents_from_page
from .index import contents_from_page
from .index import get as get_index
from .metadata import get as get_metadata
from .migration import run as migrate_contents
from .navigation_table import from_page as navigation_table_from_page
from .pull_request import RepositoryClient, create_pull_request, create_repository_client
from .pull_request import create_pull_request
from .reconcile import run as run_reconcile
from .repository import Client as RepositoryClient
from .repository import create_repository_client
from .types_ import ActionResult, Metadata, UserInputs

GETTING_STARTED = (
Expand All @@ -30,35 +35,55 @@ def _run_reconcile(
base_path: Path,
metadata: Metadata,
discourse: Discourse,
dry_run: bool,
delete_pages: bool,
repository: RepositoryClient,
user_inputs: UserInputs,
) -> dict[str, str]:
"""Upload the documentation to charmhub.

Args:
base_path: The base path of the repository.
metadata: Information about the charm.
discourse: A client to the documentation server.
dry_run: If enabled, only log the action that would be taken.
delete_pages: Whether to delete pages that are no longer needed.
repository: Repository client for managing both local and remote git repositories.
user_inputs: Configurable inputs for running upload-charm-docs.

Returns:
All the URLs that had an action with the result of that action.

Raises:
InputError: if there are any problems with executing any of the actions.

"""
index = get_index(metadata=metadata, base_path=base_path, server_client=discourse)
path_infos = read_docs_directory(docs_path=base_path / DOCUMENTATION_FOLDER_NAME)
server_content = (
index.server.content if index.server is not None and index.server.content else ""
)
table_rows = navigation_table_from_page(page=server_content, discourse=discourse)
actions = run_reconcile(path_infos=path_infos, table_rows=table_rows, discourse=discourse)
actions = run_reconcile(
path_infos=path_infos,
table_rows=table_rows,
discourse=discourse,
repository=repository,
base_path=base_path,
user_inputs=user_inputs,
)

# tee creates a copy of the iterator which is needed as check_conflicts consumes the iterator
# it is passed
actions, check_actions = tee(actions, 2)
jdkandersson marked this conversation as resolved.
Show resolved Hide resolved
problems = tuple(check_conflicts(actions=check_actions))
if problems:
raise InputError(
"One or more of the required actions could not be executed, see the log for details"
)

reports = run_all_actions(
actions=actions,
index=index,
discourse=discourse,
dry_run=dry_run,
delete_pages=delete_pages,
dry_run=user_inputs.dry_run,
delete_pages=user_inputs.delete_pages,
)
return {
str(report.location): report.result
Expand Down Expand Up @@ -121,10 +146,10 @@ def run(base_path: Path, discourse: Discourse, user_inputs: UserInputs) -> dict[
"""
metadata = get_metadata(base_path)
has_docs_dir = has_docs_directory(base_path=base_path)
repository = create_repository_client(
access_token=user_inputs.github_access_token, base_path=base_path
)
if metadata.docs and not has_docs_dir:
repository = create_repository_client(
access_token=user_inputs.github_access_token, base_path=base_path
)
return _run_migrate(
base_path=base_path,
metadata=metadata,
Expand All @@ -136,7 +161,7 @@ def run(base_path: Path, discourse: Discourse, user_inputs: UserInputs) -> dict[
base_path=base_path,
metadata=metadata,
discourse=discourse,
dry_run=user_inputs.dry_run,
delete_pages=user_inputs.delete_pages,
repository=repository,
user_inputs=user_inputs,
)
raise InputError(GETTING_STARTED)
60 changes: 37 additions & 23 deletions src/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@

"""Module for taking the required actions to match the server state with the local state."""

import difflib
import logging
import typing

from . import exceptions, reconcile, types_
from .content import diff as content_diff
from .content import merge as content_merge
from .discourse import Discourse

DRY_RUN_NAVLINK_LINK = "<not created due to dry run>"
DRY_RUN_REASON = "dry run"
BASE_MISSING_REASON = "no base for the content to be automatically merged"
FAIL_NAVLINK_LINK = "<not created due to error>"
NOT_DELETE_REASON = "delete_topics is false"

Expand All @@ -29,23 +31,19 @@ def _absolute_url(url: types_.Url | None, discourse: Discourse) -> types_.Url |
return discourse.absolute_url(url=url) if url is not None else None


def _log_content_change(old: str, new: str) -> None:
"""Log the difference between the old and new content, if any.
def _log_content_change(base: str, new: str) -> None:
"""Log the difference between the base and new content, if any.

Args:
old: The previous content.
base: The previous content.
new: The current content.
"""
old = f"{old}\n" if not old.endswith("\n") else old
old = f"{base}\n" if not base.endswith("\n") else base
new = f"{new}\n" if not new.endswith("\n") else new
if new != old:
logging.info(
"content change:\n%s",
"".join(
difflib.Differ().compare(
old.splitlines(keepends=True), new.splitlines(keepends=True)
)
),
content_diff(old, new),
)


Expand Down Expand Up @@ -140,39 +138,55 @@ def _update(
raise exceptions.ActionError(
f"internal error, content change for page is None, {action=!r}"
)
if (
action.navlink_change.new.link is not None
and action.content_change is not None
and action.content_change.new is None
):
raise exceptions.ActionError(f"internal error, new content for page is None, {action=!r}")

if (
action.content_change is not None
and action.content_change.old is not None
and action.content_change.new is not None
):
_log_content_change(old=action.content_change.old, new=action.content_change.new)
_log_content_change(
base=action.content_change.base or action.content_change.old,
new=action.content_change.new,
)

if (
# All of these are needed to ensure types are as expected
if ( # pylint: disable=too-many-boolean-expressions
not dry_run
and action.navlink_change.new.link is not None
and action.content_change is not None
and action.content_change.base is not None
and action.content_change.new is not None
and action.content_change.new != action.content_change.old
):
try:
discourse.update_topic(
url=action.navlink_change.new.link, content=action.content_change.new
merged_content = content_merge(
base=action.content_change.base,
theirs=action.content_change.old,
ours=action.content_change.new,
)
discourse.update_topic(url=action.navlink_change.new.link, content=merged_content)
result = types_.ActionResult.SUCCESS
reason = None
except exceptions.DiscourseError as exc:
result = types_.ActionResult.FAIL
reason = str(exc)
except exceptions.ContentError as exc:
jdkandersson marked this conversation as resolved.
Show resolved Hide resolved
result = types_.ActionResult.FAIL
reason = str(exc)
else:
result = types_.ActionResult.SKIP if dry_run else types_.ActionResult.SUCCESS
reason = DRY_RUN_REASON if dry_run else None
if dry_run:
jdkandersson marked this conversation as resolved.
Show resolved Hide resolved
result = types_.ActionResult.SKIP
reason = DRY_RUN_REASON
elif (
action.content_change is not None
jdkandersson marked this conversation as resolved.
Show resolved Hide resolved
and action.content_change.base is None
and action.content_change.new != action.content_change.old
jdkandersson marked this conversation as resolved.
Show resolved Hide resolved
):
result = types_.ActionResult.FAIL
reason = BASE_MISSING_REASON
else:
result = types_.ActionResult.SUCCESS
reason = None

url = _absolute_url(action.navlink_change.new.link, discourse=discourse)
table_row = types_.TableRow(
Expand Down Expand Up @@ -343,7 +357,7 @@ def _run_index(
case types_.UpdateIndexAction:
try:
assert isinstance(action, types_.UpdateIndexAction) # nosec
_log_content_change(old=action.content_change.old, new=action.content_change.new)
_log_content_change(base=action.content_change.old, new=action.content_change.new)
discourse.update_topic(url=action.url, content=action.content_change.new)
report = types_.ActionReport(
table_row=None,
Expand Down
Loading