Live video on the AT Protocol
1package main
2
3import (
4 "encoding/json"
5 "flag"
6 "fmt"
7 "math/rand"
8 "net/http"
9 "os"
10 "sort"
11 "strings"
12
13 "github.com/go-git/go-git/v5"
14 "github.com/go-git/go-git/v5/plumbing"
15 "github.com/go-git/go-git/v5/plumbing/object"
16 "github.com/go-git/go-git/v5/plumbing/storer"
17 "github.com/google/uuid"
18)
19
20func main() {
21 err := makeGit()
22 if err != nil {
23 panic(err)
24 }
25}
26
27var tmpl = `package main
28
29var Version = "%s"
30var BuildTime = "%d"
31var UUID = "%s"
32`
33
34var tmplJS = `
35export const version = "%s";
36export const buildTime = "%d";
37export const uuid = "%s";
38`
39
40func gitlabURL() string {
41 CI_API_V4_URL := os.Getenv("CI_API_V4_URL") //nolint:all
42 CIProjectID := os.Getenv("CI_PROJECT_ID")
43 CI_API_V4_URL = strings.Replace(CI_API_V4_URL, "https://git.stream.place", "https://git-cloudflare.stream.place", 1)
44 return fmt.Sprintf("%s/projects/%s", CI_API_V4_URL, CIProjectID)
45}
46
47func gitlab(suffix string, dest any) {
48 u := fmt.Sprintf("%s%s", gitlabURL(), suffix)
49
50 req, err := http.Get(u)
51 if err != nil {
52 panic(err)
53 }
54 if err := json.NewDecoder(req.Body).Decode(dest); err != nil {
55 panic(err)
56 }
57}
58
59func gitlabList(suffix string) []map[string]any {
60 var result []map[string]any
61 gitlab(suffix, &result)
62 return result
63}
64
65func gitlabDict(suffix string) map[string]any {
66 var result map[string]any
67 gitlab(suffix, &result)
68 return result
69}
70
71func makeGit() error {
72 output := flag.String("o", "", "file to output to")
73 version := flag.Bool("v", false, "just print version")
74 env := flag.Bool("env", false, "print a bunch of useful environment variables")
75 doBranch := flag.Bool("branch", false, "print branch")
76 doRelease := flag.Bool("release", false, "print release json file")
77 javascript := flag.Bool("js", false, "print code in javascript format")
78
79 flag.Parse()
80 r, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
81 if err != nil {
82 return err
83 }
84
85 // ... retrieving the HEAD reference
86 ref, err := r.Head()
87 if err != nil {
88 return err
89 }
90 c, err := r.CommitObject(ref.Hash())
91 if err != nil {
92 return err
93 }
94
95 ts := c.Author.When.Unix()
96 rander := rand.New(rand.NewSource(ts))
97 u, err := uuid.NewV7FromReader(rander)
98 if err != nil {
99 return err
100 }
101 g, err := PlainOpen(".")
102 if err != nil {
103 return err
104 }
105 desc, err := g.Describe(ref)
106 if err != nil {
107 return err
108 }
109 var out string
110 if *version {
111 out = desc
112 } else if *doBranch {
113 out = branch()
114 } else if *env {
115 StreamplaceBranch := branch()
116 outMap := map[string]string{}
117 outMap["STREAMPLACE_BRANCH"] = StreamplaceBranch
118 outMap["STREAMPLACE_VERSION"] = desc
119 outMap["STREAMPLACE_BRANCH"] = StreamplaceBranch
120 for _, arch := range []string{"amd64", "arm64"} {
121 k := fmt.Sprintf("STREAMPLACE_URL_LINUX_%s", strings.ToUpper(arch))
122 v := fmt.Sprintf("%s/packages/generic/%s/%s/streamplace-%s-linux-%s.tar.gz", gitlabURL(), StreamplaceBranch, desc, desc, arch)
123 outMap[k] = v
124 macK := fmt.Sprintf("STREAMPLACE_URL_DARWIN_%s", strings.ToUpper(arch))
125 macV := fmt.Sprintf("%s/packages/generic/%s/%s/streamplace-%s-darwin-%s.zip", gitlabURL(), StreamplaceBranch, desc, desc, arch)
126 outMap[macK] = macV
127 }
128 outMap["STREAMPLACE_DESKTOP_URL_WINDOWS_AMD64"] = fmt.Sprintf("%s/packages/generic/%s/%s/streamplace-desktop-%s-windows-amd64.exe", gitlabURL(), StreamplaceBranch, desc, desc)
129 for k, v := range outMap {
130 out = out + fmt.Sprintf("%s=%s\n", k, v)
131 }
132 } else if *doRelease {
133 outMap := map[string]any{}
134 outMap["name"] = desc
135 outMap["tag-name"] = desc
136 pkgs := gitlabList(fmt.Sprintf("/packages?order_by=created_at&sort=desc&package_name=%s", branch()))
137 id := pkgs[0]["id"].(float64)
138 pkgFiles := gitlabList(fmt.Sprintf("/packages/%d/package_files", int(id)))
139 outFiles := []string{}
140 sort.Slice(pkgFiles, func(i, j int) bool {
141 s1 := pkgFiles[i]["file_name"].(string)
142 s2 := pkgFiles[j]["file_name"].(string)
143 return s1 < s2
144 })
145 for _, file := range pkgFiles {
146 fileJSON := map[string]string{
147 "name": file["file_name"].(string),
148 "url": fmt.Sprintf("%s/packages/generic/%s/%s/%s", gitlabURL(), branch(), desc, file["file_name"].(string)),
149 }
150 bs, err := json.Marshal(fileJSON)
151 if err != nil {
152 return err
153 }
154 outFiles = append(outFiles, string(bs))
155 }
156 outMap["assets-link"] = outFiles
157 changelog := gitlabDict(fmt.Sprintf("/repository/changelog?version=%s", desc))
158 outMap["description"] = changelog["notes"]
159 bs, err := json.MarshalIndent(outMap, "", " ")
160 if err != nil {
161 return err
162 }
163 out = string(bs)
164 } else if *javascript {
165 out = fmt.Sprintf(tmplJS, desc, ts, u)
166 } else {
167 out = fmt.Sprintf(tmpl, desc, ts, u)
168 }
169
170 if *output != "" {
171 if err := os.WriteFile(*output, []byte(out), 0644); err != nil {
172 return err
173 }
174 } else {
175 fmt.Print(out)
176 }
177 return nil
178}
179
180func branch() string {
181 CICommitTag := os.Getenv("CI_COMMIT_TAG")
182 CICommitBranch := os.Getenv("CI_COMMIT_BRANCH")
183 if CICommitTag != "" {
184 return "latest"
185 } else if CICommitBranch != "" {
186 return strings.ReplaceAll(CICommitBranch, "/", "-")
187 } else {
188 panic("CI_COMMIT_TAG and CI_COMMIT_BRANCH undefined, can't get branch")
189 }
190}
191
192// Git struct wrapps Repository class from go-git to add a tag map used to perform queries when describing.
193type Git struct {
194 TagsMap map[plumbing.Hash]*plumbing.Reference
195 *git.Repository
196}
197
198// PlainOpen opens a git repository from the given path. It detects if the
199// repository is bare or a normal one. If the path doesn't contain a valid
200// repository ErrRepositoryNotExists is returned
201func PlainOpen(path string) (*Git, error) {
202 r, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{DetectDotGit: true})
203 return &Git{
204 make(map[plumbing.Hash]*plumbing.Reference),
205 r,
206 }, err
207}
208
209func (g *Git) getTagMap() error {
210 tags, err := g.Tags()
211 if err != nil {
212 return err
213 }
214
215 err = tags.ForEach(func(t *plumbing.Reference) error {
216 h, err := g.ResolveRevision(plumbing.Revision(t.Name()))
217 if err != nil {
218 return err
219 }
220 g.TagsMap[*h] = t
221 return nil
222 })
223
224 return err
225}
226
227// Describe the reference as 'git describe --tags' will do
228func (g *Git) Describe(reference *plumbing.Reference) (string, error) {
229
230 // Fetch the reference log
231 cIter, err := g.Log(&git.LogOptions{
232 // From: reference.Hash(),
233 Order: git.LogOrderCommitterTime,
234 })
235 if err != nil {
236 return "", err
237 }
238
239 // Build the tag map
240 err = g.getTagMap()
241 if err != nil {
242 return "", err
243 }
244
245 // Search the tag
246 var tag *plumbing.Reference
247 var count int
248 err = cIter.ForEach(func(c *object.Commit) error {
249 t, ok := g.TagsMap[c.Hash]
250 if ok {
251 tag = t
252 return storer.ErrStop
253 }
254 count++
255 return nil
256 })
257 if err != nil {
258 return "", err
259 }
260 head, err := g.Head()
261 if err != nil {
262 return "", err
263 }
264 if count == 0 && os.Getenv("CI_COMMIT_TAG") != "" {
265 return fmt.Sprint(tag.Name().Short()), nil
266 } else {
267 return fmt.Sprintf("%s-%s",
268 tag.Name().Short(),
269 head.Hash().String()[0:8],
270 ), nil
271 }
272}