Skip to content

Instantly share code, notes, and snippets.

@fintechestenberg
Created March 17, 2025 10:42
Show Gist options
  • Save fintechestenberg/65628c3b03eaa468d5442278869b33af to your computer and use it in GitHub Desktop.
Save fintechestenberg/65628c3b03eaa468d5442278869b33af to your computer and use it in GitHub Desktop.
github-actions-sha-notifier

GitHub Actions Checker

Overview

GitHub Actions Checker is a Go-based tool that scans a list of GitHub Actions with SHA version tags, checks for newer versions, and optionally sends notifications to Slack.

Features

  • Reads a list of GitHub Actions from a file.
  • Queries the GitHub API to check for newer versions based on repository tags.
  • Prints available updates to the console.
  • Supports authentication via a GitHub token to avoid API rate limiting.
  • Optionally sends results to a Slack webhook if configured.

Usage

Basic Usage

Run the program with an input file containing a list of GitHub Actions:

github_actions_checker actions.txt

The actions.txt file should have entries in the format:

tj-actions/changed-files@4a3b2c1d
actions/checkout@1234567abcdef

Authentication

To avoid rate limiting, set a GitHub token as an environment variable:

export GITHUB_TOKEN=your_personal_access_token

Slack Notifications

To send results to Slack, set the webhook URL as an environment variable:

export SLACK_WEBHOOK_URL=https://hooks.slack.com/services/your/webhook/url

If a newer version is found, the tool will send a message to the configured Slack channel.

package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"regexp"
"sort"
"strings"
)
type Tag struct {
Name string `json:"name"`
Commit struct {
SHA string `json:"sha"`
} `json:"commit"`
}
type RepoInfo struct {
Owner string
Repo string
SHA string
}
func main() {
if len(os.Args) < 2 {
log.Fatal("Usage: github_actions_checker <input_file>")
}
inputFile := os.Args[1]
actions, err := readInputFile(inputFile)
if err != nil {
log.Fatalf("Failed to read input file: %v", err)
}
var results []string
for _, action := range actions {
latestTag, latestSHA, err := findLatestTag(action)
if err != nil {
log.Printf("Error checking %s/%s: %v", action.Owner, action.Repo, err)
continue
}
if latestTag != "" && latestSHA != action.SHA {
result := fmt.Sprintf("%s/%s: Newer version available: %s (%s) (current: %s)", action.Owner, action.Repo, latestTag, latestSHA, action.SHA)
fmt.Println(result)
results = append(results, result)
}
}
// Send results to Slack if webhook is set
slackWebhook := os.Getenv("SLACK_WEBHOOK_URL")
if slackWebhook != "" && len(results) > 0 {
sendToSlack(slackWebhook, results)
}
}
func readInputFile(filename string) ([]RepoInfo, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var actions []RepoInfo
scanner := bufio.NewScanner(file)
re := regexp.MustCompile(`([^/]+)/([^@]+)@([a-fA-F0-9]+)`) // Matches user/repo@SHA
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
matches := re.FindStringSubmatch(line)
if len(matches) == 4 {
actions = append(actions, RepoInfo{
Owner: matches[1],
Repo: matches[2],
SHA: matches[3],
})
}
}
return actions, scanner.Err()
}
func findLatestTag(action RepoInfo) (string, string, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/tags", action.Owner, action.Repo)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", "", err
}
// Add GitHub token if available
token := os.Getenv("GITHUB_TOKEN")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
req.Header.Set("Accept", "application/vnd.github.v3+json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", "", fmt.Errorf("GitHub API returned %d", resp.StatusCode)
}
var tags []Tag
if err := json.NewDecoder(resp.Body).Decode(&tags); err != nil {
return "", "", err
}
if len(tags) == 0 {
return "", "", nil
}
sort.Slice(tags, func(i, j int) bool { return tags[i].Name > tags[j].Name }) // Sort descending
return tags[0].Name, tags[0].Commit.SHA, nil
}
func sendToSlack(webhookURL string, messages []string) {
payload := map[string]string{
"text": strings.Join(messages, "\n"),
}
jsonPayload, err := json.Marshal(payload)
if err != nil {
log.Printf("Failed to create Slack payload: %v", err)
return
}
req, err := http.NewRequest("POST", webhookURL, bytes.NewBuffer(jsonPayload))
if err != nil {
log.Printf("Failed to create Slack request: %v", err)
return
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Printf("Failed to send Slack message: %v", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Printf("Slack API returned %d", resp.StatusCode)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment