From 710489f2d4cdbebf0fa8c9b9f8e2d8e38e749cad Mon Sep 17 00:00:00 2001 From: Yusheng Li Date: Wed, 20 Sep 2023 17:14:41 +0800 Subject: [PATCH] chore(changelog): use reusable changelog workflow (#11549) This avoids code duplication and makes improvements on changelog generation/format easier. --- .github/workflows/changelog-requirement.yml | 32 +++ .github/workflows/changelog-validation.yml | 17 ++ .github/workflows/changelog.yaml | 47 ---- CHANGELOG/Makefile | 3 - CHANGELOG/README.md | 88 ------ CHANGELOG/changelog | 292 -------------------- CHANGELOG/changelog-md-template.lua | 63 ----- CHANGELOG/changelog-template.yaml | 2 - CHANGELOG/schema.json | 67 ----- 9 files changed, 49 insertions(+), 562 deletions(-) create mode 100644 .github/workflows/changelog-requirement.yml create mode 100644 .github/workflows/changelog-validation.yml delete mode 100644 .github/workflows/changelog.yaml delete mode 100644 CHANGELOG/Makefile delete mode 100644 CHANGELOG/README.md delete mode 100755 CHANGELOG/changelog delete mode 100644 CHANGELOG/changelog-md-template.lua delete mode 100644 CHANGELOG/schema.json diff --git a/.github/workflows/changelog-requirement.yml b/.github/workflows/changelog-requirement.yml new file mode 100644 index 000000000000..7bbc02a32a38 --- /dev/null +++ b/.github/workflows/changelog-requirement.yml @@ -0,0 +1,32 @@ +name: Changelog Requirement + +on: + pull_request: + types: [ opened, synchronize, labeled, unlabeled ] + paths: + - 'kong/**' + - '**.rockspec' + - '.requirements' + +jobs: + require-changelog: + if: ${{ !contains(github.event.*.labels.*.name, 'skip-changelog') }} + name: Requires changelog + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: computes changed files + id: changelog-check + uses: tj-actions/changed-files@2f7246cb26e8bb6709b6cbfc1fec7febfe82e96a # v37 + with: + files: 'CHANGELOG/unreleased/**/*.yaml' + + - name: asserts changelog added + run: > + if [ "${{ steps.changelog-check.outputs.added_files_count }}" = "0" ]; then + echo "Should contain at least one changelog file in CHANGELOG/unreleased/*/ directory" + exit 1 + fi diff --git a/.github/workflows/changelog-validation.yml b/.github/workflows/changelog-validation.yml new file mode 100644 index 000000000000..7590fca8928d --- /dev/null +++ b/.github/workflows/changelog-validation.yml @@ -0,0 +1,17 @@ +name: Changelog Validation + +on: + pull_request: + types: [ opened, synchronize ] + +jobs: + validate-changelog: + name: Validate changelog + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate changelogs + uses: Kong/gateway-changelog@main + with: + files: CHANGELOG/unreleased/*/*.yaml diff --git a/.github/workflows/changelog.yaml b/.github/workflows/changelog.yaml deleted file mode 100644 index ce2b55eb6fa2..000000000000 --- a/.github/workflows/changelog.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: Changelog - -on: - pull_request: - types: [ "opened", "synchronize", "labeled", "unlabeled" ] - paths: - - 'kong/**' - - '**.rockspec' - - '.requirements' - -jobs: - require-changelog: - name: Is changelog required? - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Retrives changed files in CHANGELOG/unreleased/**/*.yaml - id: changelog-check - uses: tj-actions/changed-files@2f7246cb26e8bb6709b6cbfc1fec7febfe82e96a # v37 - with: - files: 'CHANGELOG/unreleased/**/*.yaml' - - - name: Requires a changelog file if 'skip-changelog' label is not added - if: ${{ !contains(github.event.*.labels.*.name, 'skip-changelog') }} - run: > - if [ "${{ steps.changelog-check.outputs.added_files_count }}" = "0" ]; then - echo "PR should contain a changelog file" - exit 1 - fi - - validate-changelog: - name: Validate changelog - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Validate changelogs - uses: thiagodnf/yaml-schema-checker@58b96413951ebe86a396275c48620b8435439694 # v0.0.10 - with: - jsonSchemaFile: CHANGELOG/schema.json - yamlFiles: | - CHANGELOG/unreleased/*/*.yaml diff --git a/CHANGELOG/Makefile b/CHANGELOG/Makefile deleted file mode 100644 index a71e38b41106..000000000000 --- a/CHANGELOG/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -install_dependencies: - luarocks install penlight --local - luarocks install lyaml --local diff --git a/CHANGELOG/README.md b/CHANGELOG/README.md deleted file mode 100644 index ead8a94074c7..000000000000 --- a/CHANGELOG/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# CHANGELOG - -The CHANGELOG directory is used for individual changelog file practice. -The `kong/CHANGELOG.md` now is deprecated. - - -## How to add a changelog file for your PR? - -1/ Copy the `changelog-template.yaml` file and rename with your PR number or a short message as the filename. For example, `11279.yaml`, `introduce-a-new-changelog-system.yaml`. (Prefer using PR number as it's already unique and wouldn't introduce conflict) - -2/ Fill out the changelog template. - - -The description of the changelog file field, please follow the `schema.json` for more details. - -- message: Message of the changelog -- type: Changelog type. (`feature`, `bugfix`, `dependency`, `deprecation`, `breaking_change`) -- scope: Changelog scope. (`Core`, `Plugin`, `PDK`, `Admin API`, `Performance`, `Configuration`, `Clustering`) -- prs: List of associated GitHub PRs -- issues: List of associated GitHub issues -- jiras: List of associated Jira tickets for internal track - -Sample 1 -```yaml -message: Introduce the request id as core feature. -type: feat -scope: Core -prs: - - 11308 -``` - -Sample 2 -```yaml -message: Fix response body gets repeated when `kong.response.get_raw_body()` is called multiple times in a request lifecycle. -type: bugfix -scope: PDK -prs: - - 11424 -jiras: - - "FTI-5296" -``` - - -## changelog command - -The `changelog` command tool provides `preview`, and `release` commands. - -### Prerequisites - -You can skip this part if you're at Kong Bazel virtual env. - -Install luajit - -Install luarocks libraries - -``` -luarocks install penlight --local -luarocks install lyaml --local -``` - -### Usage - -```shell -$ ./changelog -h - -Usage: changelog [options] - -Commands: - release release a release note based on the files in the CHANGELOG/unreleased directory. - preview preview a release note based on the files in the CHANGELOG/unreleased directory. - -Options: - -h, --help display help for command - -Examples: - changelog preview 1.0.0 - changelog release 1.0.0 -``` - -**Preview a release note** -```shell -./changelog preview 1.0.0 -``` - -**Release a release note** -```shell -./changelog release 1.0.0 -``` diff --git a/CHANGELOG/changelog b/CHANGELOG/changelog deleted file mode 100755 index 87e93e2c46d8..000000000000 --- a/CHANGELOG/changelog +++ /dev/null @@ -1,292 +0,0 @@ -#!/usr/bin/env luajit - -local pl_template = require "pl.template" -local pl_tablex = require "pl.tablex" -local pl_file = require "pl.file" -local pl_dir = require "pl.dir" -local pl_path = require "pl.path" -local pl_stringx = require "pl.stringx" -local lyaml = require "lyaml" -local pl_app = require 'pl.lapp' - -local CHANGELOG_PATH -- absolute path of CHANGELOG directory -do - local base_path = os.getenv("PWD") - local command = debug.getinfo(1, "S").source:sub(2) - local last_idx = pl_stringx.rfind(command, "/") - if last_idx then - base_path = pl_path.join(base_path, string.sub(command, 1, last_idx - 1)) - end - CHANGELOG_PATH = base_path -end -local UNRELEASED = "unreleased" -local REPOS = { - kong = "Kong/kong", -} -local JIRA_BASE_URL = "https://konghq.atlassian.net/browse/" -local GITHUB_REFERENCE = { - pr = "https://github.com/%s/pull/%d", - issue = "https://github.com/%s/issues/%d" -} -local SCOPE_PRIORITY = { -- smallest on top - Performance = 10, - Configuration = 20, - Core = 30, - PDK = 40, - Plugin = 50, - ["Admin API"] = 60, - Clustering = 70, - Default = 100, -- default priority -} - -setmetatable(SCOPE_PRIORITY, { - __index = function() - return rawget(SCOPE_PRIORITY, "Default") - 1 - end -}) - -local function table_keys(t) - if type(t) ~= "table" then - return t - end - local keys = {} - for k, _ in pairs(t) do - table.insert(keys, k) - end - return keys -end - -local function parse_github_ref(system, reference_type, references) - if references == nil or references == lyaml.null then - return nil - end - local parsed_references = {} - for i, ref in ipairs(references or {}) do - local repo = REPOS[system] - local ref_no = tonumber(ref) -- treat ref as number string first - local name = "#" .. ref - if not ref_no then -- ref is not a number string - local parts = pl_stringx.split(ref, ":") - repo = parts[1] - ref_no = parts[2] - name = pl_stringx.replace(tostring(ref), ":", " #") - end - parsed_references[i] = { - id = ref_no, - name = name, - link = string.format(GITHUB_REFERENCE[reference_type], repo, ref_no), - } - end - return parsed_references -end - - -local function parse_jiras(jiras) - local jira_items = {} - for i, jira in ipairs(jiras or {}) do - jiras[i] = { - id = jira, - link = JIRA_BASE_URL .. jira - } - end - return jira_items -end - - -local function is_yaml(filename) - return pl_stringx.endswith(filename, ".yaml") or - pl_stringx.endswith(filename, ".yml") -end - -local function is_empty_table(t) - return next(t) == nil -end - -local function compile_template(data, template) - local compile_env = { - _escape = ">", - _brackets = "{}", - _debug = true, - pairs = pairs, - ipairs = ipairs, - tostring = tostring, - is_empty_table = is_empty_table, - } - - compile_env = pl_tablex.merge(compile_env, data, true) -- union - local content, err = pl_template.substitute(template, compile_env) - if not content then - return nil, "failed to compile template: " .. err - end - - return content -end - -local function absolute_path(...) - local path = CHANGELOG_PATH - for _, p in ipairs({...}) do - path = pl_path.join(path, p) - end - return path -end - -local function collect_files(folder) - local files - if pl_path.exists(folder) then - files = assert(pl_dir.getfiles(folder)) - if files then - table.sort(files) - end - end - local sorted_files = {} - for _, filename in ipairs(files or {}) do - if is_yaml(filename) then - table.insert(sorted_files, filename) - end - end - - return sorted_files -end - - -local function collect_folder(system, folder) - local data = { - features = {}, - bugfixes = {}, - breaking_changes = {}, - dependencies = {}, - deprecations = {}, - } - - local map = { - feature = "features", - bugfix = "bugfixes", - breaking_change = "breaking_changes", - dependency = "dependencies", - deprecation = "deprecations", - } - - local files = collect_files(folder) - for _, filename in ipairs(files) do - local content = assert(pl_file.read(filename)) - local entry = assert(lyaml.load(content)) - - entry.prs = parse_github_ref(system, "pr", entry.prs) or {} - entry.issues = parse_github_ref(system, "issue", entry.issues) or {} - entry.jiras = parse_jiras(entry.jiras) or {} - - if entry.scope == nil or entry.scope == lyaml.null then - entry.scope = "Default" - end - - local key = map[entry.type] - if not data[key][entry.scope] then - data[key][entry.scope] = {} - end - table.insert(data[key][entry.scope], entry) - end - - for _, scopes in pairs(data) do - local scope_names = table_keys(scopes) - table.sort(scope_names, function(a, b) return SCOPE_PRIORITY[a] < SCOPE_PRIORITY[b] end) - scopes.sorted_scopes = scope_names - end - - return data -end - -local function collect_unreleased() - local data = {} - - data.kong = collect_folder("kong", absolute_path(UNRELEASED, "kong")) - - return data -end - - -local function generate_content(data) - local template_path = absolute_path("changelog-md-template.lua") - local content = assert(pl_file.read(template_path)) - local changelog_template = assert(loadstring(content))() - return compile_template(data, changelog_template) -end - - --- command: release --- release a release note -local function release(version) - -- mkdir unreleased path if not exists - if not pl_path.exists(absolute_path(UNRELEASED)) then - assert(pl_dir.makepath(absolute_path(UNRELEASED))) - end - - local data = collect_unreleased() - data.version = version - local content = assert(generate_content(data)) - local target_path = absolute_path(version) - if pl_path.exists(target_path) then - error("directory exists, please manually remove. " .. version) - end - os.execute("mv " .. UNRELEASED .. " " .. target_path) - local filename = pl_path.join(target_path, "changelog.md") - assert(pl_file.write(filename, content)) - assert(pl_dir.makepath(UNRELEASED)) - - print("Successfully generated release note.") -end - - --- command: preview --- preview the release note -local function preview(version) - local data = collect_unreleased() - data.version = version - local content = assert(generate_content(data)) - print(content) -end - - -local cmds = { - release = function(args) - local version = args[1] - if not version then - error("Missing version") - end - release(version) - end, - preview = function(args) - local version = args[1] - if not version then - error("Missing version") - end - preview(version) - end, -} - - -local args = pl_app [[ -Usage: changelog [options] - -Commands: - release release a release note based on the files in the CHANGELOG/unreleased directory. - preview preview a release note based on the files in the CHANGELOG/unreleased directory. - -Options: - -h, --help display help for command - -Examples: - changelog preview 1.0.0 - changelog release 1.0.0 -]] - -local cmd_name = table.remove(args, 1) -if not cmd_name then - pl_app.quit() -end - -local cmd_fn = cmds[cmd_name] -if not cmds[cmd_name] then - pl_app.quit("Invalid command: " .. cmd_name, true) -end - -cmd_fn(args) diff --git a/CHANGELOG/changelog-md-template.lua b/CHANGELOG/changelog-md-template.lua deleted file mode 100644 index b631139c2657..000000000000 --- a/CHANGELOG/changelog-md-template.lua +++ /dev/null @@ -1,63 +0,0 @@ -return [[ -> local function render_changelog_entry(entry) -- ${entry.message} -> if #(entry.prs or {}) > 0 then -> for _, pr in ipairs(entry.prs or {}) do - [${pr.name}](${pr.link}) -> end -> end -> if entry.jiras then -> for _, jira in ipairs(entry.jiras or {}) do - [${jira.id}](${jira.link}) -> end -> end -> if #(entry.issues or {}) > 0 then -(issue: -> for _, issue in ipairs(entry.issues or {}) do - [${issue.name}](${issue.link}) -> end -) -> end -> end -> -> local function render_changelog_entries(entries) -> for _, entry in ipairs(entries or {}) do -> render_changelog_entry(entry) -> end -> end -> -> local function render_changelog_section(section_name, t) -> if #t.sorted_scopes > 0 then -### ${section_name} - -> end -> for _, scope_name in ipairs(t.sorted_scopes or {}) do -> if not (#t.sorted_scopes == 1 and scope_name == "Default") then -- do not print the scope_name if only one scope and it's Default scope -#### ${scope_name} - -> end -> render_changelog_entries(t[scope_name]) -> end -> end -> -> -> -# ${version} - -## Kong - -> render_changelog_section("Breaking Changes", kong.breaking_changes) - - -> render_changelog_section("Deprecations", kong.deprecations) - - -> render_changelog_section("Dependencies", kong.dependencies) - - -> render_changelog_section("Features", kong.features) - - -> render_changelog_section("Fixes", kong.bugfixes) - -]] diff --git a/CHANGELOG/changelog-template.yaml b/CHANGELOG/changelog-template.yaml index f2594e2911b5..20cfee703592 100644 --- a/CHANGELOG/changelog-template.yaml +++ b/CHANGELOG/changelog-template.yaml @@ -1,5 +1,3 @@ message: type: prs: -jiras: -issues: diff --git a/CHANGELOG/schema.json b/CHANGELOG/schema.json deleted file mode 100644 index 22476c94fc90..000000000000 --- a/CHANGELOG/schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Message of the changelog", - "minLength": 1, - "maxLength": 1000 - }, - "type": { - "type": "string", - "description": "Changelog type", - "enum": [ - "feature", - "bugfix", - "dependency", - "deprecation", - "breaking_change", - "performance" - ] - }, - "scope": { - "type": "string", - "description": "Changelog scope", - "enum": [ - "Core", - "Plugin", - "PDK", - "Admin API", - "Performance", - "Configuration", - "Clustering" - ] - }, - "prs": { - "type": "array", - "description": "List of associated GitHub PRs", - "items": { - "pattern": "^(\\d+|\\w+\/\\w+:\\d+)$", - "type": ["integer", "string"], - "examples": ["1", "torvalds/linux:1"] - } - }, - "issues": { - "type": "array", - "description": "List of associated GitHub issues", - "items": { - "pattern": "^(\\d+|\\w+\/\\w+:\\d+)$", - "type": ["integer", "string"], - "examples": ["1", "torvalds/linux:1"] - } - }, - "jiras": { - "type": "array", - "description": "List of associated Jira tickets for internal tracking.", - "items": { - "type": "string", - "pattern": "^[A-Z]+-[0-9]+$" - } - } - }, - "required": [ - "message", - "type" - ] -}