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