update-go-version.yaml 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. name: Update Go Version in "go.mod"
  2. on:
  3. schedule:
  4. - cron: '0 14 * * 1' # Run every Monday at 2 PM UTC
  5. workflow_dispatch: # Allow manual triggering
  6. concurrency:
  7. group: golang-ver-bump
  8. cancel-in-progress: true
  9. jobs:
  10. check-and-update-go:
  11. name: Check and Update Go Version
  12. runs-on: ubuntu-latest
  13. permissions:
  14. pull-requests: write
  15. contents: write
  16. steps:
  17. - uses: actions/checkout@v6
  18. with:
  19. repository: 'opencost/opencost'
  20. ref: 'develop'
  21. fetch-depth: 0
  22. path: ./opencost
  23. - name: Setup Go
  24. uses: actions/setup-go@v6
  25. with:
  26. go-version: 'stable'
  27. - name: Check for existing open PRs
  28. id: check-existing-prs
  29. run: |
  30. # Check if there are any open PRs with the go-version-update label
  31. EXISTING_PRS=$(gh pr list --label "go-version-update" --state open --json number,title,url)
  32. if [ "$(echo "$EXISTING_PRS" | jq 'length')" -gt 0 ]; then
  33. echo "Found existing open PR(s) with go-version-update label:"
  34. echo "$EXISTING_PRS" | jq -r '.[] | " - #\(.number): \(.title) (\(.url))"'
  35. echo "skip_update=true" >> $GITHUB_OUTPUT
  36. else
  37. echo "No existing open PRs found, proceeding with update check"
  38. echo "skip_update=false" >> $GITHUB_OUTPUT
  39. fi
  40. env:
  41. GH_TOKEN: ${{ secrets.GITHUB_TOKEN}}
  42. GH_REPO: ${{ github.repository }}
  43. - name: Get current Go version from go.mod
  44. id: current-version
  45. working-directory: ./opencost
  46. if: steps.check-existing-prs.outputs.skip_update == 'false'
  47. run: |
  48. CURRENT_VERSION=$(grep '^go ' go.mod | awk '{print $2}')
  49. echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
  50. echo "Current Go version: $CURRENT_VERSION"
  51. - name: Get latest Go version
  52. id: latest-version
  53. working-directory: ./opencost
  54. if: steps.check-existing-prs.outputs.skip_update == 'false'
  55. run: |
  56. # Get the latest Go version from golang.org/dl
  57. LATEST_VERSION=$(curl -sS https://go.dev/VERSION?m=text | grep -o 'go[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1)
  58. echo "latest_version=$LATEST_VERSION" >> $GITHUB_OUTPUT
  59. echo "Latest Go version: $LATEST_VERSION"
  60. - name: Compare versions
  61. id: version-check
  62. working-directory: ./opencost
  63. if: steps.check-existing-prs.outputs.skip_update == 'false'
  64. run: |
  65. CURRENT_VERSION="${{ steps.current-version.outputs.current_version }}"
  66. LATEST_VERSION="${{ steps.latest-version.outputs.latest_version }}"
  67. # Remove 'go' prefix for comparison
  68. CURRENT_NUM=$(echo "$CURRENT_VERSION" | sed 's/^go//')
  69. LATEST_NUM=$(echo "$LATEST_VERSION" | sed 's/^go//')
  70. echo "Raw current version: $CURRENT_VERSION"
  71. echo "Raw latest version: $LATEST_VERSION"
  72. echo "Current version number: $CURRENT_NUM"
  73. echo "Latest version number: $LATEST_NUM"
  74. # Validate that both versions are non-empty and match an expected pattern (e.g., 1.22.1)
  75. VERSION_REGEX='^[0-9]+\.[0-9]+(\.[0-9]+)?$'
  76. if ! echo "$CURRENT_NUM" | grep -Eq "$VERSION_REGEX"; then
  77. echo "Error: CURRENT_VERSION '$CURRENT_VERSION' (normalized: '$CURRENT_NUM') is invalid or empty."
  78. exit 1
  79. fi
  80. if ! echo "$LATEST_NUM" | grep -Eq "$VERSION_REGEX"; then
  81. echo "Error: LATEST_VERSION '$LATEST_VERSION' (normalized: '$LATEST_NUM') is invalid or empty."
  82. exit 1
  83. fi
  84. if [ "$CURRENT_NUM" = "$LATEST_NUM" ]; then
  85. echo "Go version is up to date"
  86. echo "update_needed=false" >> $GITHUB_OUTPUT
  87. else
  88. # Use version-aware sorting to determine which version is greater
  89. HIGHEST_VERSION=$(printf '%s\n' "$CURRENT_NUM" "$LATEST_NUM" | sort -V | tail -n1)
  90. if [ "$HIGHEST_VERSION" = "$LATEST_NUM" ]; then
  91. echo "Newer Go version available: $LATEST_VERSION (current: $CURRENT_VERSION)"
  92. echo "update_needed=true" >> $GITHUB_OUTPUT
  93. echo "new_version=$LATEST_VERSION" >> $GITHUB_OUTPUT
  94. else
  95. echo "Current Go version ($CURRENT_VERSION) is newer than or equal to latest reported version ($LATEST_VERSION); no update needed."
  96. echo "update_needed=false" >> $GITHUB_OUTPUT
  97. fi
  98. fi
  99. - name: Update go.mod with new Go version
  100. if: steps.check-existing-prs.outputs.skip_update == 'false' && steps.version-check.outputs.update_needed == 'true'
  101. working-directory: ./opencost
  102. run: |
  103. NEW_VERSION="$(echo '${{ steps.version-check.outputs.new_version }}' | tr -d 'go')"
  104. echo "Updating all go.mod files under $(pwd) to use Go version: $NEW_VERSION"
  105. # Recursively update every go.mod we find under the working directory.
  106. # (Skip vendor directories to avoid accidental edits of vendored content.)
  107. while IFS= read -r go_mod_file; do
  108. echo "Updating: $go_mod_file"
  109. sed -i -E "s/^go[[:space:]].*/go ${NEW_VERSION}/" "$go_mod_file"
  110. done < <(find . -name go.mod -type f -not -path "*/vendor/*")
  111. - name: Run go mod tidy
  112. if: steps.check-existing-prs.outputs.skip_update == 'false' && steps.version-check.outputs.update_needed == 'true'
  113. working-directory: ./opencost
  114. run: |
  115. echo "Running go mod tidy for every module found under $(pwd)"
  116. # Tidy once per module directory (dirname of each go.mod we find).
  117. # Skip vendor directories to avoid editing vendored content.
  118. mapfile -t tidy_dirs < <(find . -name go.mod -type f -not -path "*/vendor/*" -exec dirname {} \; | sort -u)
  119. for d in "${tidy_dirs[@]}"; do
  120. echo "Tidying: $d"
  121. (cd "$d" && go mod tidy)
  122. done
  123. - name: Check for changes
  124. id: changes
  125. if: steps.check-existing-prs.outputs.skip_update == 'false' && steps.version-check.outputs.update_needed == 'true'
  126. working-directory: ./opencost
  127. run: |
  128. if [ -z "$(git status --porcelain)" ]; then
  129. echo "No changes detected, skipping PR creation"
  130. echo "skip_pr=true" >> $GITHUB_OUTPUT
  131. else
  132. echo "Changes detected, will create PR"
  133. echo "skip_pr=false" >> $GITHUB_OUTPUT
  134. # Generate a unique branch name with timestamp
  135. branch_name="update-go-version-$(date +%Y%m%d-%H%M%S)"
  136. echo "branch_name=$branch_name" >> $GITHUB_OUTPUT
  137. fi
  138. - name: Create Pull Request
  139. if: steps.check-existing-prs.outputs.skip_update == 'false' && steps.version-check.outputs.update_needed == 'true' && steps.changes.outputs.skip_pr == 'false'
  140. id: create-pr
  141. uses: peter-evans/create-pull-request@v8
  142. with:
  143. path: ./opencost
  144. token: ${{ secrets.GITHUB_TOKEN }}
  145. branch: ${{ steps.changes.outputs.branch_name }}
  146. title: 'chore: update Go version to ${{ steps.version-check.outputs.new_version }}'
  147. commit-message: 'chore: update Go version to ${{ steps.version-check.outputs.new_version }}'
  148. delete-branch: true
  149. add-paths: |
  150. go.mod
  151. go.sum
  152. **/go.mod
  153. **/go.sum
  154. body: |
  155. Automated PR to update Go version from ${{ steps.current-version.outputs.current_version }} to ${{ steps.version-check.outputs.new_version }}.
  156. This PR was automatically generated after detecting a newer Go version is available.
  157. base: develop
  158. labels: automated,go-version-update
  159. - name: Skip message - no update needed
  160. if: steps.check-existing-prs.outputs.skip_update == 'false' && steps.version-check.outputs.update_needed == 'false'
  161. run: |
  162. echo "No Go version update needed. Current version is up to date."
  163. - name: Skip message - existing PR
  164. if: steps.check-existing-prs.outputs.skip_update == 'true'
  165. run: |
  166. echo "Skipping Go version update because an existing PR with go-version-update label is already open."