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

Containerize and refactor #1

Merged
merged 14 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
84 changes: 84 additions & 0 deletions .github/workflows/publish-container-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

name: Docker Image Publish

on:
push:
branches: [ "main", "ol/containerize" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
# pull_request:
# branches: [ "main" ]

env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}


jobs:
build:

runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write

steps:
- name: Checkout
uses: actions/checkout@v4

# Add support for more platforms with QEMU (optional)
# https://github.com/docker/setup-qemu-action
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Version from git describe (https://git-scm.com/docs/git-describe)
- name: Get version
id: version
run: |
output=$(git describe --tags --always --dirty)
echo ::set-output name=git_version::$output

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}

- name: Build and push
uses: docker/build-push-action@v5
with:
context: ./publisher
push: ${{ github.event_name != 'pull_request' }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
file: ./publisher/Dockerfile
tags: |
${{ steps.meta.outputs.tags }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.git_version }}
platforms: linux/amd64,linux/arm/v7,linux/arm64
37 changes: 0 additions & 37 deletions .github/workflows/publisher.yml

This file was deleted.

5 changes: 5 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# CODEOWNERS file
# This file is used to define individuals or teams that are responsible for the code in a repository.
# Code owners are automatically requested for review when someone opens a pull request that modifies code that they own.

* @ottolote
73 changes: 18 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,30 @@
# GitHub to Confluence Publisher
# markdown2confluence

This repository contains a script that allows you to publish Markdown files from GitHub to Confluence. The script automates the process of converting Markdown files to Confluence markup and uploading them to your Confluence space.
Convert your Markdown files into Confluence pages with ease using the `markdown2confluence` script. It uploads all files from a specified Markdown directory to a Confluence space, maintaining the folder hierarchy as page structure.

Link to my article: https://dev.to/andygolubev/automate-publishing-markdown-files-from-github-to-confluence-with-github-to-confluence-publisher-tool-eh4
## Prerequisites

## Setup
Before you get started, locate the space ID and page ID in your Confluence URL by navigating to the desired space and page in your web browser.

### Create new domain in Confluence cloud
Create new cloud domain here https://my.atlassian.com/product
## Configuration

### Create new Space, a Parent page and API Token
You should create a Space and copy it's name from the URL (in my case: "Documentat". The full url is https://test-publisher.atlassian.net/wiki/spaces/Documentat/)
Create a new page and copy it's id. (in my case: "262359". the full url is https://test-publisher.atlassian.net/wiki/spaces/Documentat/pages/262359/Documentation+from+the+github)
Set the following environment variables in your environment before running the script:

Create new API key here https://id.atlassian.com/manage-profile/security/api-tokens
- `CONFLUENCE_URL`: The URL to your Confluence instance's API endpoint, e.g., `https://your-instance.atlassian.net/wiki/rest/api/`
- `CONFLUENCE_USERNAME`: Your Confluence username
- `CONFLUENCE_PASSWORD`: Your Confluence password or API token
- `CONFLUENCE_SPACE`: The ID of the space in Confluence where the pages will be created
- `CONFLUENCE_PARENT_PAGE_ID`: The ID of the parent page under which the new pages will be created
- `MARKDOWN_FOLDER`: The path to your folder containing markdown files INSIDE the container, e.g. /data

### Configure the publisher in publisher/config/config.yaml
## Example usage

Replace this values with your values

``` yaml

confluence_url: https://test-publisher.atlassian.net/wiki/rest/api/

confluence_space: Documentat
counfluence_parent_page_id: 262359
confluence_search_pattern: (this page is autogenerated)
github_folder_with_md_files: ./data
github_folder_with_image_files: ./data_images
To upload sample markdown files to Confluence, ensure you have a `.env` file containing necessary key=value pairs for configuration. Then, run the following Docker command:

```bash
# Run from the root of this repo
docker run --rm --env-file=.env -v $(pwd)/markdownsample:/data ghcr.io/innofactororg/markdown2confluence:0.1.0-alpha
```

I use "confluence_search_pattern:" to find autogenerated pages and delete it each run. I recommend to use some random value like "(this page is autogenerated FGWSDF)"


### Setup a pipeline (example is provided in the github workflows folder)

Create you secrets and setup your pipeline.
Confluence expect you e-mail as a login and a API Key as a password.


## How it works

Your confluence space is empty.

![space](/publisher_doc/Screenshot_01.png)

Then you run the publisher and it search for the pages with "confluence_search_pattern" from the config.
Then it deletes all the found pages.

local run
![local run](/publisher_doc/Screenshot_02.png)
or github actions
![github](/publisher_doc/Screenshot_03.png)

Then you can see that you parent page containes child pages with your content

![structure](/publisher_doc/Screenshot_04.png)

All the images were poster as attachements for the pages.

![attachements](/publisher_doc/Screenshot_05.png)

The pages that imitates the folder contains the "Children Display" widget.
This command will start a Docker container, which reads your `.env` file (containing the required environment variable settings), and synchronizes the contents of your local Markdown folder with your Confluence space.

![widget](/publisher_doc/Screenshot_06.png)
1 change: 1 addition & 0 deletions markdownsample/.confluenceignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/ignored-folder
1 change: 1 addition & 0 deletions markdownsample/ignored-folder/ignored-file.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Ignored markdown file
19 changes: 19 additions & 0 deletions publisher/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt /app/

RUN pip install --no-cache-dir -r requirements.txt

ENV CONFLUENCE_USERNAME=""
ENV CONFLUENCE_PASSWORD=""
ENV CONFLUENCE_URL="https://yourdomain.atlassian.net/wiki/rest/api/"
ENV CONFLUENCE_SPACE_ID="yourspace"
ENV CONFLUENCE_PARENT_PAGE_ID="65777"
ENV CONFLUENCE_SEARCH_PATTERN="(this page is autogenerated)"
ENV MARKDOWN_FOLDER="/data"

COPY . /app

CMD ["python", "/app/main.py"]
7 changes: 0 additions & 7 deletions publisher/config/config.yaml

This file was deleted.

46 changes: 46 additions & 0 deletions publisher/config/get_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os
import argparse


def get_config():
# Parse arguments with LOGIN and PASSWORD for Confluence
parser = argparse.ArgumentParser(
description='Publish markdown files to Confluence.')
parser.add_argument('--confluence-ignorefile', required=False,
help='File containing a list of filename patterns for ignored folders and files',
default=os.environ.get('CONFLUENCE_IGNOREFILE', ".confluenceignore"))
parser.add_argument('--confluence-password', required=False,
help='Confluence login password.',
default=os.environ.get('CONFLUENCE_PASSWORD'))
parser.add_argument('--confluence-search-pattern', required=False,
help='Confluence search pattern to find pages.',
default=os.environ.get('CONFLUENCE_SEARCH_PATTERN', "(this page is autogenerated)"))
parser.add_argument('--confluence-space-id', required=False,
help='Confluence space key',
default=os.environ.get('CONFLUENCE_SPACE_ID', "yourspace"))
parser.add_argument('--confluence-url', required=False,
help='Confluence URL',
default=os.environ.get('CONFLUENCE_URL', "https://yourdomain.atlassian.net/wiki/rest/api/"))
parser.add_argument('--confluence-username', required=False,
help='Confluence login username.',
default=os.environ.get('CONFLUENCE_USERNAME'))
parser.add_argument('--markdown-folder', required=False,
help='Target folder containing markdown files (other filetypes will be ignored).',
default=os.environ.get('MARKDOWN_FOLDER', "./data"))
parser.add_argument('--confluence-parent-page-id', required=False,
help='Confluence parent page ID',
default=os.environ.get('CONFLUENCE_PARENT_PAGE_ID', "65777"))

args = parser.parse_args()

return {
'confluence_ignorefile': args.confluence_ignorefile,
'confluence_parent_page_id': args.confluence_parent_page_id,
'confluence_password': args.confluence_password,
'confluence_search_pattern': args.confluence_search_pattern,
'confluence_space_id': args.confluence_space_id,
'confluence_url': args.confluence_url,
'confluence_username': args.confluence_username,
'markdown_folder': args.markdown_folder,
# Add other settings as needed
}
6 changes: 0 additions & 6 deletions publisher/config/getconfig.py

This file was deleted.

39 changes: 22 additions & 17 deletions publisher/main.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
import argparse
import time
import logging

from config.getconfig import getConfig
from pagesController import deletePages, searchPages
from pagesPublisher import publishFolder
from config.get_config import get_config
from pages_controller import delete_pages, search_pages
from pages_publisher import publish_folder

config = get_config()
logging.basicConfig(level=logging.INFO)


# Parse arguments with LOGIN and PASSWORD for Confluence
parser = argparse.ArgumentParser()
parser.add_argument('--login', help='Login with "" is mandatory', required=True)
parser.add_argument('--password', help='Password with "" is mandatory', required=True)
args = parser.parse_args()
inputArguments = vars(args)
def main():
# Load configuration
logging.debug(config)

# Extract the arguments for easier access
login = config["confluence_username"]
password = config["confluence_password"]

CONFIG = getConfig()
# Search and delete pages
pages = search_pages(login=login, password=password)
delete_pages(pages_id_list=pages, login=login, password=password)

logging.debug(CONFIG)
time.sleep(5) # Sleep for 5 seconds to allow the delete to fully complete

pages = searchPages(login=inputArguments['login'], password=inputArguments['password'])
deletePages(pagesIDList=pages, login=inputArguments['login'], password=inputArguments['password'])
# Publish the markdown files from the specified folder
publish_folder(
folder=config["markdown_folder"], login=login, password=password
)

publishFolder(folder = str(CONFIG["github_folder_with_md_files"]),
login=inputArguments['login'],
password=inputArguments['password'])

if __name__ == '__main__':
main()
Loading
Loading