Skip to content
Open
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
1 change: 1 addition & 0 deletions .openapi-generator-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ openapi-generator-config.json
# application_resources_api.py - No longer needed, new spec fixes the API design
# tag_filter.py - STILL NEEDED: New spec uses Dict[str, Any] which doesn't support string/int/bool values
instana_client/models/tag_filter.py
instana_client/models/service.py
280 changes: 280 additions & 0 deletions automation/publish_pypi.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
#!/bin/bash

# =============================================================================
# INSTANA PYTHON SDK PYPI PUBLISHING SCRIPT
# =============================================================================
# This script handles publishing the SDK to PyPI
# =============================================================================

set -e # Exit on any error

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above ANSI codes might not work in all terminals. How about some checks:

if [ -t 1 ] && command -v tput >/dev/null 2>&1; then
    RED=$(tput setaf 1)
    GREEN=$(tput setaf 2)
    YELLOW=$(tput setaf 3)
    BLUE=$(tput setaf 4)
    CYAN=$(tput setaf 6)
    NC=$(tput sgr0)
else
    RED=''
    GREEN=''
    YELLOW=''
    BLUE=''
    CYAN=''
    NC=''
fi

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
PYPI_USERNAME="${PYPI_USERNAME:-__token__}"
PYPI_PASSWORD="${PYPI_PASSWORD:-}"
PYPI_REPOSITORY="${PYPI_REPOSITORY:-pypi}"
DRY_RUN="${DRY_RUN:-false}"

# Function to print colored output
print_header() {
echo -e "\n${CYAN}========================================${NC}"
echo -e "${CYAN}$1${NC}"
echo -e "${CYAN}========================================${NC}\n"
}

print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}

print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}

print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}

print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}

# Function to show usage
show_usage() {
cat << EOF
Usage: $0 [OPTIONS]

Publish Instana Python SDK to PyPI

OPTIONS:
-u, --username USER PyPI username (default: __token__)
-p, --password PASS PyPI password/token (required)
-r, --repository REPO PyPI repository (default: pypi, use testpypi for testing)
-d, --dry-run Check packages without uploading
-h, --help Show this help message

EXAMPLES:
# Publish to PyPI with token
$0 --password YOUR_PYPI_TOKEN

# Publish to Test PyPI
$0 --password YOUR_TEST_TOKEN --repository testpypi

# Dry run to check packages
$0 --dry-run

# Publish with username/password (legacy)
$0 --username myuser --password mypassword

ENVIRONMENT VARIABLES:
PYPI_USERNAME PyPI username (default: __token__)
PYPI_PASSWORD PyPI password/token
PYPI_REPOSITORY PyPI repository (default: pypi)

NOTES:
- For PyPI API tokens, use username: __token__
- Get tokens at: https://pypi.org/manage/account/token/
- Test PyPI: https://test.pypi.org/manage/account/token/

EOF
}

# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-u|--username)
PYPI_USERNAME="$2"
shift 2
;;
-p|--password)
PYPI_PASSWORD="$2"
shift 2
;;
-r|--repository)
PYPI_REPOSITORY="$2"
shift 2
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
show_usage
exit 0
;;
*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done

# Change to project root
cd "$PROJECT_ROOT"

print_header "Instana Python SDK PyPI Publishing"
print_status "Project root: $PROJECT_ROOT"
print_status "Repository: $PYPI_REPOSITORY"
print_status "Username: $PYPI_USERNAME"
print_status "Dry run: $DRY_RUN"

# =============================================================================
# STEP 1: Validate prerequisites
# =============================================================================
print_header "Step 1: Validating prerequisites"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to check if python3 exists in the system when validating prerequisites.


detect_python() {
    for cmd in python3 python; do
        if command -v "$cmd" >/dev/null 2>&1; then
            PYTHON_CMD="$cmd"
            PYTHON_VERSION=$("$cmd" --version 2>&1 | awk '{print $2}')
            print_status "Using Python: $PYTHON_CMD ($PYTHON_VERSION)"
            return 0
        fi
    done
    print_error "Python not found. Please install Python 3.7+"
    exit 1
}

detect_python

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future commands in the script where python3 is referenced directly, you can change that to use PYTHON_CMD instead of python3

$PYTHON_CMD -m venv .venv-publish

# Check if dist directory exists
if [ ! -d "dist" ]; then
print_error "dist/ directory not found"
print_status "Run './automation/update_sdk.sh' first to build packages"
exit 1
fi

# Check if packages exist
PACKAGE_COUNT=$(ls -1 dist/*.whl dist/*.tar.gz 2>/dev/null | wc -l)
if [ "$PACKAGE_COUNT" -eq 0 ]; then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ls with multiple patterns can fail if one pattern has no matches.
Here is an alternative approach that counts packages more reliably.

WHEEL_COUNT=$(find dist -maxdepth 1 -name "*.whl" 2>/dev/null | wc -l)
TARBALL_COUNT=$(find dist -maxdepth 1 -name "*.tar.gz" 2>/dev/null | wc -l)
PACKAGE_COUNT=$((WHEEL_COUNT + TARBALL_COUNT))

if [ "$PACKAGE_COUNT" -eq 0 ]; then
    print_error "No distribution packages found in dist/"
    print_status "Run './automation/update_sdk.sh' first to build packages"
    exit 1
fi

print_success "Found $PACKAGE_COUNT distribution package(s) ($WHEEL_COUNT wheels, $TARBALL_COUNT tarballs)"

print_error "No distribution packages found in dist/"
print_status "Run './automation/update_sdk.sh' first to build packages"
exit 1
fi

print_success "Found $PACKAGE_COUNT distribution package(s)"
ls -lh dist/

# Get version from package
CURRENT_VERSION=$(grep "^VERSION = " setup.py | sed 's/VERSION = "\(.*\)"/\1/')
print_status "SDK version: $CURRENT_VERSION"

# =============================================================================
# STEP 2: Setup publishing environment
# =============================================================================
print_header "Step 2: Setting up publishing environment"

if [ "$DRY_RUN" = false ]; then
# Create virtual environment for publishing
print_status "Creating virtual environment..."
python3 -m venv .venv-publish
source .venv-publish/bin/activate

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above would be hard to troubleshoot in case virtual environment creation or activation failed. Better to add some extra checks, logging etc. Something like:

print_status "Creating virtual environment..."
if ! python3 -m venv .venv-publish; then
    print_error "Failed to create virtual environment"
    exit 1
fi

if ! source .venv-publish/bin/activate; then
    print_error "Failed to activate virtual environment"
    rm -rf .venv-publish
    exit 1
fi

# Install twine
print_status "Installing twine..."
pip install --upgrade twine
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to add some logs/checks in case something goes wrong when installing packages. Something like:

print_status "Installing twine..."
if ! pip install --upgrade twine; then
    print_error "Failed to install twine"
    exit 1
fi


print_success "Publishing environment ready"
else
print_status "DRY RUN: Would create virtual environment and install twine"
fi

# =============================================================================
# STEP 3: Check packages
# =============================================================================
print_header "Step 3: Checking packages"

if [ "$DRY_RUN" = false ]; then
source .venv-publish/bin/activate

print_status "Running twine check..."
if twine check dist/*; then
print_success "Package check passed"
else
print_error "Package check failed"
deactivate
rm -rf .venv-publish
exit 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If deactivate fails or virtual environment wasn't activated, cleanup may fail silently. You can make it more resilient by using trap instead of manual cleanup calls. Something like:

cleanup() {
    local exit_code=$?
    if [ -n "$VIRTUAL_ENV" ]; then
        deactivate 2>/dev/null || true
    fi
    if [ -d ".venv-publish" ]; then
        rm -rf .venv-publish
    fi
    return $exit_code
}

trap cleanup EXIT ERR

fi
else
print_status "DRY RUN: Would run twine check on packages"
fi

# =============================================================================
# STEP 4: Upload to PyPI
# =============================================================================
if [ "$DRY_RUN" = false ]; then
print_header "Step 4: Uploading to PyPI"

# Validate credentials
if [ -z "$PYPI_PASSWORD" ]; then
print_error "PyPI password/token is required"
print_status "Use --password option or set PYPI_PASSWORD environment variable"
deactivate
rm -rf .venv-publish
exit 1
fi

source .venv-publish/bin/activate

print_status "Uploading packages to $PYPI_REPOSITORY..."
print_status "This may take a moment..."

# Upload to PyPI
if [ "$PYPI_REPOSITORY" = "pypi" ]; then
twine upload dist/* -u "$PYPI_USERNAME" -p "$PYPI_PASSWORD"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a security vulnerability. Passing credentials via command-line arguments can expose them in shell history, and logs.

To prevent that, it is better to use env variables based approach that goes away after script execution.

export TWINE_USERNAME="$PYPI_USERNAME"
export TWINE_PASSWORD="$PYPI_PASSWORD"

if [ "$PYPI_REPOSITORY" = "pypi" ]; then
    twine upload dist/*
elif [ "$PYPI_REPOSITORY" = "testpypi" ]; then
    twine upload --repository testpypi dist/*
else
    twine upload --repository "$PYPI_REPOSITORY" dist/*
fi

unset TWINE_USERNAME TWINE_PASSWORD

PACKAGE_URL="https://pypi.org/project/instana-client/$CURRENT_VERSION/"
elif [ "$PYPI_REPOSITORY" = "testpypi" ]; then
twine upload --repository testpypi dist/* -u "$PYPI_USERNAME" -p "$PYPI_PASSWORD"
PACKAGE_URL="https://test.pypi.org/project/instana-client/$CURRENT_VERSION/"
else
twine upload --repository "$PYPI_REPOSITORY" dist/* -u "$PYPI_USERNAME" -p "$PYPI_PASSWORD"
PACKAGE_URL="Custom repository: $PYPI_REPOSITORY"
fi

print_success "Successfully uploaded to $PYPI_REPOSITORY"

deactivate
else
print_header "Step 4: Upload Preview (Dry Run)"
print_status "Would upload the following packages:"
ls -1 dist/
print_status "To repository: $PYPI_REPOSITORY"
print_status "With username: $PYPI_USERNAME"
fi

# =============================================================================
# STEP 5: Cleanup
# =============================================================================
print_header "Step 5: Cleanup"

if [ "$DRY_RUN" = false ]; then
rm -rf .venv-publish
print_success "Cleanup complete"
else
print_status "DRY RUN: Would cleanup virtual environment"
fi

# =============================================================================
# Summary
# =============================================================================
print_header "Publishing Summary"

if [ "$DRY_RUN" = false ]; then
echo -e "Publishing Complete!${NC}\n"
echo "Package: instana-client"
echo "Version: $CURRENT_VERSION"
echo "Repository: $PYPI_REPOSITORY"
echo ""
echo "Package URL: $PACKAGE_URL"
echo ""
echo "Next steps:"
echo "1. Verify package on PyPI"
echo "2. Test installation: pip install instana-client==$CURRENT_VERSION"
echo "3. Create git tag: git tag v$CURRENT_VERSION"
echo "4. Push tag: git push origin v$CURRENT_VERSION"
echo "5. Create GitHub release"
else
echo "This was a DRY RUN - no packages were uploaded"
echo "Run without --dry-run to publish to PyPI"
echo ""
echo "Packages ready for upload:"
ls -1 dist/
fi

print_success "Publishing process complete!"
Loading
Loading