diff --git a/.github/workflows/build-and-snapshot.yml b/.github/workflows/build-and-snapshot.yml index d029662..e5e6e7b 100644 --- a/.github/workflows/build-and-snapshot.yml +++ b/.github/workflows/build-and-snapshot.yml @@ -25,19 +25,7 @@ jobs: with: python-version: "3.11" - - name: Check if Python tests exist - id: check-tests - run: | - if [ -f "test/requirements.txt" ] && [ -f "test/test.sh" ]; then - echo "tests_exist=true" >> $GITHUB_OUTPUT - echo "โœ… Python test suite found" - else - echo "tests_exist=false" >> $GITHUB_OUTPUT - echo "โš ๏ธ Python test suite not found - skipping tests" - fi - - name: Setup Python test environment - if: steps.check-tests.outputs.tests_exist == 'true' run: | cd test python -m venv venv @@ -46,21 +34,11 @@ jobs: python -m pip install -r requirements.txt - name: Run Python linting - if: steps.check-tests.outputs.tests_exist == 'true' run: | cd test source venv/bin/activate ../scripts/lint-python.sh ci -# - name: Run Python tests -# if: steps.check-tests.outputs.tests_exist == 'true' -# run: | -# cd testing -# source venv/bin/activate -# echo "๐Ÿงช Running Python tests..." -# pytest -v --tb=short -# echo "โœ… Python tests completed!" - build: name: Build and Test Go Plugin runs-on: ${{ matrix.os }} @@ -106,6 +84,19 @@ jobs: if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && (needs.lint-and-test-python.result == 'success' || needs.lint-and-test-python.result == 'skipped') steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install Python dependencies for plugin repo generation + run: | + python -m pip install --upgrade pip + pip install PyYAML + - name: Download all artifacts uses: actions/download-artifact@v4 with: @@ -116,6 +107,22 @@ jobs: mkdir -p dist mv dist/*/* dist/ || true + - name: Generate plugin repository YAML for snapshot + env: + GITHUB_REF_NAME: snapshot + run: | + echo "๐Ÿ“ Generating plugin repository YAML file..." + python3 -m venv venv + source venv/bin/activate + python3 -m pip install --upgrade pip + pip install PyYAML requests + python3 .github/workflows/generate_plugin_repo.py + echo "โœ… Plugin repository YAML generated" + + - name: Generate timestamp + id: timestamp + run: echo "timestamp=$(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_OUTPUT + - uses: thomashampson/delete-older-releases@main with: keep_latest: 0 @@ -125,24 +132,50 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Delete and regenerate tag snapshot + run: | + echo "Deleting existing snapshot tag..." + git tag -d snapshot || true + git push origin :snapshot || true + echo "Regenerating snapshot tag..." + git tag snapshot + git push origin snapshot --force + - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: - files: dist/* + files: | + dist/* + plugin-repo-entry.yml + plugin-repo-summary.txt prerelease: false - release: false + draft: false tag_name: snapshot body: | This is a snapshot release of the cf-cli-java-plugin. It includes the latest changes and is not intended for production use. Please test it and provide feedback. - ## Build Status - - โœ… Go Plugin: Built and tested on Linux, macOS, and Windows - - โœ… Python Tests: Linting and test suite validation completed + **Build Timestamp**: ${{ steps.timestamp.outputs.timestamp }} + + ## Installation + + Download the current snapshot release and install manually: + + ```sh + # on Mac arm64 + cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/download/snapshot/cf-cli-java-plugin-macos-arm64 + # on Windows x86 + cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/download/snapshot/cf-cli-java-plugin-windows-amd64 + # on Linux x86 + cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/download/snapshot/cf-cli-java-plugin-linux-amd64 + ``` + + **Note:** On Linux and macOS, if you get a permission error, run `chmod +x [cf-cli-java-plugin]` on the plugin binary. + On Windows, the plugin will refuse to install unless the binary has the `.exe` file extension. - ## Changes - This snapshot includes the latest commits from the repository. + You can verify that the plugin is successfully installed by looking for `java` in the output of `cf plugins`. + name: Snapshot Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/generate_plugin_repo.py b/.github/workflows/generate_plugin_repo.py new file mode 100644 index 0000000..055156d --- /dev/null +++ b/.github/workflows/generate_plugin_repo.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Generate plugin repository YAML file for CF CLI plugin repository. +This script creates the YAML file in the required format for the CF CLI plugin repository. +""" + +import os +import sys +import yaml +import hashlib +import requests +from datetime import datetime +from pathlib import Path + +def calculate_sha1(file_path): + """Calculate SHA1 checksum of a file.""" + sha1_hash = hashlib.sha1() + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + sha1_hash.update(chunk) + return sha1_hash.hexdigest() + +def get_version_from_tag(): + """Get version from git tag or environment variable.""" + version = os.environ.get('GITHUB_REF_NAME', '') + if version.startswith('v'): + version = version[1:] # Remove 'v' prefix + return version or "dev" + +def generate_plugin_repo_yaml(): + """Generate the plugin repository YAML file.""" + version = get_version_from_tag() + repo_url = "https://github.com/SAP/cf-cli-java-plugin" + + # Define the binary platforms and their corresponding file extensions + platforms = { + "linux64": "cf-cli-java-plugin-linux-amd64", + "osx": "cf-cli-java-plugin-macos-arm64", + "win64": "cf-cli-java-plugin-windows-amd64" + } + + binaries = [] + dist_dir = Path("dist") + + for platform, filename in platforms.items(): + file_path = dist_dir / filename + if file_path.exists(): + checksum = calculate_sha1(file_path) + binary_info = { + "checksum": checksum, + "platform": platform, + "url": f"{repo_url}/releases/download/v{version}/{filename}" + } + binaries.append(binary_info) + print(f"Added {platform}: {filename} (checksum: {checksum})") + else: + print(f"Warning: Binary not found for {platform}: {filename}") + + if not binaries: + print("Error: No binaries found in dist/ directory") + sys.exit(1) + + # Create the plugin repository entry + plugin_entry = { + "authors": [{ + "contact": "johannes.bechberger@sap.com", + "homepage": "https://github.com/SAP", + "name": "Johannes Bechberger" + }], + "binaries": binaries, + "company": "SAP", + "created": "2024-01-01T00:00:00Z", # Initial creation date + "description": "Plugin for profiling Java applications and getting heap and thread-dumps", + "homepage": repo_url, + "name": "java", + "updated": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), + "version": version + } + + # Write the YAML file + output_file = Path("plugin-repo-entry.yml") + with open(output_file, 'w') as f: + yaml.dump(plugin_entry, f, default_flow_style=False, sort_keys=False) + + print(f"Generated plugin repository YAML file: {output_file}") + print(f"Version: {version}") + print(f"Binaries: {len(binaries)} platforms") + + # Also create a human-readable summary + summary_file = Path("plugin-repo-summary.txt") + with open(summary_file, 'w') as f: + f.write(f"CF CLI Java Plugin Repository Entry\n") + f.write(f"====================================\n\n") + f.write(f"Version: {version}\n") + f.write(f"Updated: {plugin_entry['updated']}\n") + f.write(f"Binaries: {len(binaries)} platforms\n\n") + f.write("Platform checksums:\n") + for binary in binaries: + f.write(f" {binary['platform']}: {binary['checksum']}\n") + f.write(f"\nRepository URL: {repo_url}\n") + f.write(f"Release URL: {repo_url}/releases/tag/v{version}\n") + + print(f"Generated summary file: {summary_file}") + +if __name__ == "__main__": + generate_plugin_repo_yaml() \ No newline at end of file diff --git a/.github/workflows/plugin-repo.yml b/.github/workflows/plugin-repo.yml new file mode 100644 index 0000000..fb87aef --- /dev/null +++ b/.github/workflows/plugin-repo.yml @@ -0,0 +1,56 @@ +name: Generate Plugin Repository Entry + +on: + release: + types: [published] + +jobs: + generate-plugin-repo: + name: Generate Plugin Repository YAML + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install PyYAML requests + + - name: Download release assets + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir -p dist + # Download all release assets + gh release download ${{ github.event.release.tag_name }} -D dist/ + + - name: Generate plugin repository YAML + env: + GITHUB_REF_NAME: ${{ github.event.release.tag_name }} + run: python3 .github/workflows/generate_plugin_repo.py + + - name: Upload plugin repository files to release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload ${{ github.event.release.tag_name }} plugin-repo-entry.yml plugin-repo-summary.txt + + - name: Create PR to plugin repository (optional) + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Plugin repository entry generated!" + echo "To submit to CF CLI plugin repository:" + echo "1. Fork https://github.com/cloudfoundry-incubator/cli-plugin-repo" + echo "2. Add the contents of plugin-repo-entry.yml to repo-index.yml" + echo "3. Create a pull request" + echo "" + echo "Entry content:" + cat plugin-repo-entry.yml \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e31b9b4..29094a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,6 +2,15 @@ name: Manual Release on: workflow_dispatch: # Allows manual triggering + inputs: + version: + description: 'Release version (e.g., 3.0.4), you must have a changelog ready for this version (e.h. 3.0.4-snapshot-20231001)' + required: true + type: string + +permissions: + contents: write + actions: read jobs: release: @@ -26,21 +35,188 @@ jobs: with: python-version: "3.x" + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install PyYAML + - name: Install dependencies run: go mod tidy -e || true - name: Lint Go files - run: ./scripts/lint-go.sh check + run: ./scripts/lint-go.sh ci - name: Run tests - run: ./scripts/lint-go.sh test + run: ./scripts/lint-go.sh ci - name: Build binary run: python3 .github/workflows/build.py + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: binaries-${{ matrix.os }} + path: dist/ + + create-release: + name: Create GitHub Release with Plugin Repository Entry + needs: release + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Install Python dependencies + run: | + python3 -m venv venv + source venv/bin/activate + python3 -m pip install --upgrade pip + pip install PyYAML requests + + - name: Parse version and update Go source + run: | + # Parse version input (e.g., "4.1.0" -> major=4, minor=1, build=0) + VERSION="${{ github.event.inputs.version }}" + IFS='.' read -r MAJOR MINOR BUILD <<< "$VERSION" + + echo "Updating version to $MAJOR.$MINOR.$BUILD" + python3 .github/workflows/update_version.py "$MAJOR" "$MINOR" "$BUILD" + + # Configure git + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + # Check if there are changes + if git diff --quiet; then + echo "No version changes detected" + else + echo "Committing version update" + git add cf_cli_java_plugin.go README.md + git commit -m "Set version to v$VERSION" + git push + fi + + - name: Create and push tag + run: | + VERSION="${{ github.event.inputs.version }}" + echo "Creating tag $VERSION" + git tag "$VERSION" + git push origin "$VERSION" + + - name: Download all build artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: Combine all artifacts + run: | + mkdir -p dist + find artifacts/ -type f -exec cp {} dist/ \; + ls -la dist/ + + - name: Generate plugin repository YAML + env: + GITHUB_REF_NAME: ${{ github.event.inputs.version }} + run: | + source venv/bin/activate + echo "๐Ÿ“ Generating plugin repository YAML file for version ${{ github.event.inputs.version }}..." + python3 .github/workflows/generate_plugin_repo.py + echo "โœ… Plugin repository YAML generated" + echo "" + echo "Generated files:" + ls -la plugin-repo-* + echo "" + echo "Plugin repository entry preview:" + head -20 plugin-repo-entry.yml + + - name: Generate timestamp + id: timestamp + run: echo "timestamp=$(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_OUTPUT + - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: - files: dist/* + tag_name: ${{ github.event.inputs.version }} + name: ${{ github.event.inputs.version }} + files: | + dist/* + plugin-repo-entry.yml + plugin-repo-summary.txt + body: | + ## CF CLI Java Plugin ${{ github.event.inputs.version }} + + Plugin for profiling Java applications and getting heap and thread-dumps. + + ## Changes + + $(cat release_changelog.txt || echo "No changelog available") + + ## Installation + + ### Installation via CF Community Repository + + Make sure you have the CF Community plugin repository configured or add it via: + ```bash + cf add-plugin-repo CF-Community http://plugins.cloudfoundry.org + ``` + + Trigger installation of the plugin via: + ```bash + cf install-plugin -r CF-Community "java" + ``` + + ### Manual Installation + + Download this specific release (${{ github.event.inputs.version }}) and install manually: + + ```bash + # on Mac arm64 + cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/download/${{ github.event.inputs.version }}/cf-cli-java-plugin-macos-arm64 + # on Windows x86 + cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/download/${{ github.event.inputs.version }}/cf-cli-java-plugin-windows-amd64 + # on Linux x86 + cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/download/${{ github.event.inputs.version }}/cf-cli-java-plugin-linux-amd64 + ``` + + Or download the latest release: + + ```bash + # on Mac arm64 + cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/latest/download/cf-cli-java-plugin-macos-arm64 + # on Windows x86 + cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/latest/download/cf-cli-java-plugin-windows-amd64 + # on Linux x86 + cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/latest/download/cf-cli-java-plugin-linux-amd64 + ``` + + **Note:** On Linux and macOS, if you get a permission error, run `chmod +x [cf-cli-java-plugin]` on the plugin binary. + On Windows, the plugin will refuse to install unless the binary has the `.exe` file extension. + + You can verify that the plugin is successfully installed by looking for `java` in the output of `cf plugins`. + + **Build Timestamp**: ${{ steps.timestamp.outputs.timestamp }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create Summary Comment + run: | + echo "## ๐Ÿš€ Release ${{ github.event.inputs.version }} Created Successfully!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Files Generated:" >> $GITHUB_STEP_SUMMARY + echo "- Release binaries for all platforms" >> $GITHUB_STEP_SUMMARY + echo "- \`plugin-repo-entry.yml\` - CF CLI plugin repository entry" >> $GITHUB_STEP_SUMMARY + echo "- \`plugin-repo-summary.txt\` - Human-readable summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Next Steps:" >> $GITHUB_STEP_SUMMARY + echo "1. Download \`plugin-repo-entry.yml\` from the release" >> $GITHUB_STEP_SUMMARY + echo "2. Submit to CF CLI plugin repository" >> $GITHUB_STEP_SUMMARY + echo "3. Update documentation if needed" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/update_version.py b/.github/workflows/update_version.py new file mode 100644 index 0000000..ced03c2 --- /dev/null +++ b/.github/workflows/update_version.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +""" +Update version in cf_cli_java_plugin.go for releases. +This script updates the PluginMetadata version in the Go source code and processes the changelog. +""" + +import sys +import re +from pathlib import Path + +def update_version_in_go_file(file_path, major, minor, build): + """Update the version in the Go plugin metadata.""" + with open(file_path, 'r') as f: + content = f.read() + + # Pattern to match the Version struct in PluginMetadata + pattern = r'(Version: plugin\.VersionType\s*{\s*Major:\s*)\d+(\s*,\s*Minor:\s*)\d+(\s*,\s*Build:\s*)\d+(\s*,\s*})' + + replacement = rf'\g<1>{major}\g<2>{minor}\g<3>{build}\g<4>' + + new_content = re.sub(pattern, replacement, content) + + if new_content == content: + print(f"Warning: Version pattern not found or not updated in {file_path}") + return False + + with open(file_path, 'w') as f: + f.write(new_content) + + print(f"โœ… Updated version to {major}.{minor}.{build} in {file_path}") + return True + +def process_readme_changelog(readme_path, version): + """Process the README changelog section for the release.""" + with open(readme_path, 'r') as f: + content = f.read() + + # Look for the snapshot section + snapshot_pattern = rf'## {re.escape(version)}-snapshot\s*\n' + match = re.search(snapshot_pattern, content) + + if not match: + print(f"Error: README.md does not contain a '## {version}-snapshot' section") + return False, None + + # Find the content of the snapshot section + start_pos = match.end() + + # Find the next ## section or end of file + next_section_pattern = r'\n## ' + next_match = re.search(next_section_pattern, content[start_pos:]) + + if next_match: + end_pos = start_pos + next_match.start() + section_content = content[start_pos:end_pos].strip() + else: + section_content = content[start_pos:].strip() + + # Remove the "-snapshot" from the header + new_header = f"## {version}" + updated_content = re.sub(snapshot_pattern, new_header + '\n\n', content) + + # Write the updated README + with open(readme_path, 'w') as f: + f.write(updated_content) + + print(f"โœ… Updated README.md: converted '## {version}-snapshot' to '## {version}'") + return True, section_content + +def get_base_version(version): + """Return the base version (e.g., 4.0.0 from 4.0.0-rc2)""" + return version.split('-')[0] + +def is_rc_version(version_str): + """Return True if the version string ends with -rc or -rcN.""" + return bool(re.match(r"^\d+\.\d+\.\d+-rc(\d+)?$", version_str)) + +def main(): + if len(sys.argv) != 4: + print("Usage: update_version.py ") + print("Example: update_version.py 4 1 0") + sys.exit(1) + + try: + major = int(sys.argv[1]) + minor = int(sys.argv[2]) + build = int(sys.argv[3]) + except ValueError: + print("Error: Version numbers must be integers") + sys.exit(1) + + version = f"{major}.{minor}.{build}" + version_arg = f"{major}.{minor}.{build}" if (major + minor + build) != 0 else sys.argv[1] + # Accept any -rc suffix, e.g. 4.0.0-rc, 4.0.0-rc1, 4.0.0-rc2 + if is_rc_version(sys.argv[1]): + base_version = get_base_version(sys.argv[1]) + go_file = Path("cf_cli_java_plugin.go") + readme_file = Path("README.md") + changelog_file = Path("release_changelog.txt") + if not readme_file.exists(): + print(f"Error: {readme_file} not found") + sys.exit(1) + with open(readme_file, 'r') as f: + content = f.read() + # Find the section for the base version + base_pattern = rf'## {re.escape(base_version)}\s*\n' + match = re.search(base_pattern, content) + if not match: + print(f"Error: README.md does not contain a '## {base_version}' section for RC release") + sys.exit(1) + start_pos = match.end() + next_match = re.search(r'\n## ', content[start_pos:]) + if next_match: + end_pos = start_pos + next_match.start() + section_content = content[start_pos:end_pos].strip() + else: + section_content = content[start_pos:].strip() + with open(changelog_file, 'w') as f: + f.write(section_content) + print(f"โœ… RC release: Changelog for {base_version} saved to {changelog_file}") + sys.exit(0) + + go_file = Path("cf_cli_java_plugin.go") + readme_file = Path("README.md") + changelog_file = Path("release_changelog.txt") + + # Update Go version + success = update_version_in_go_file(go_file, major, minor, build) + if not success: + sys.exit(1) + + # Process README changelog + success, changelog_content = process_readme_changelog(readme_file, version) + if not success: + sys.exit(1) + + # Write changelog content to a file for the workflow to use + with open(changelog_file, 'w') as f: + f.write(changelog_content) + + print(f"โœ… Version updated successfully to {version}") + print(f"โœ… Changelog content saved to {changelog_file}") + +if __name__ == "__main__": + main()