1package util
2
3import (
4	"errors"
5	"fmt"
6	"io"
7	"net/http"
8	"os"
9	"os/exec"
10	"path/filepath"
11	"runtime"
12	"time"
13	"context"
14
15	"github.com/google/go-github/github"
16)
17
18// Adapted from github.com/apex/apex
19
20// UpdateAvailable contains the full repository information for the latest release of Semaphore
21var UpdateAvailable *github.RepositoryRelease
22
23// DoUpgrade checks for an update, and if available downloads the binary and installs it
24func DoUpgrade(version string) error {
25	fmt.Printf("current release is v%s\n", version)
26
27	if err := CheckUpdate(version); err != nil || UpdateAvailable == nil {
28		return err
29	}
30
31	asset := findAsset(UpdateAvailable)
32	if asset == nil {
33		return errors.New("cannot find binary for your system")
34	}
35
36	// create tmp file
37	tmpPath := filepath.Join(os.TempDir(), "semaphore-upgrade")
38	f, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0755) //nolint: gas
39	if err != nil {
40		return err
41	}
42
43	// download binary
44	fmt.Printf("downloading %s\n", *asset.BrowserDownloadURL)
45	res, err := http.Get(*asset.BrowserDownloadURL)
46	if err != nil {
47		return err
48	}
49
50	defer res.Body.Close() //nolint: errcheck
51
52	// copy it down
53	_, err = io.Copy(f, res.Body)
54	if err != nil {
55		return err
56	}
57
58	// replace it
59	cmdPath := FindSemaphore()
60	if len(cmdPath) == 0 {
61		return errors.New("Cannot find semaphore binary")
62	}
63
64	fmt.Printf("replacing %s\n", cmdPath)
65	err = os.Rename(tmpPath, cmdPath)
66	if err != nil {
67		return err
68	}
69
70	fmt.Println("visit https://github.com/ansible-semaphore/semaphore/releases for the changelog")
71	go func() {
72		time.Sleep(time.Second * 3)
73		os.Exit(0)
74	}()
75
76	return nil
77}
78
79// FindSemaphore looks in the PATH for the semaphore variable
80// if not found it will attempt to find the absolute path of the first
81// os argument, the semaphore command, and return it
82func FindSemaphore() string {
83	cmdPath, _ := exec.LookPath("semaphore") //nolint: gas
84
85	if len(cmdPath) == 0 {
86		cmdPath, _ = filepath.Abs(os.Args[0]) // nolint: gas
87	}
88
89	return cmdPath
90}
91
92// findAsset returns the binary for this platform.
93func findAsset(release *github.RepositoryRelease) *github.ReleaseAsset {
94	for _, asset := range release.Assets {
95		if *asset.Name == fmt.Sprintf("semaphore_%s_%s", runtime.GOOS, runtime.GOARCH) {
96			return &asset
97		}
98	}
99
100	return nil
101}
102
103// CheckUpdate uses the github client to check for new tags in the semaphore repo
104func CheckUpdate(version string) error {
105	// fetch releases
106	gh := github.NewClient(nil)
107	releases, _, err := gh.Repositories.ListReleases(context.TODO(), "ansible-semaphore", "semaphore", nil)
108	if err != nil {
109		return err
110	}
111
112	UpdateAvailable = nil
113	if (*releases[0].TagName)[1:] != version {
114		UpdateAvailable = releases[0]
115	}
116
117	return nil
118}
119