|
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) |
|
} |
|
} |