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")
42 CI_PROJECT_ID := 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, CI_PROJECT_ID)
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 STREAMPLACE_BRANCH := branch()
116 outMap := map[string]string{}
117 outMap["STREAMPLACE_BRANCH"] = STREAMPLACE_BRANCH
118 outMap["STREAMPLACE_VERSION"] = desc
119 outMap["STREAMPLACE_BRANCH"] = STREAMPLACE_BRANCH
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(), STREAMPLACE_BRANCH, 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(), STREAMPLACE_BRANCH, 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(), STREAMPLACE_BRANCH, 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 os.WriteFile(*output, []byte(out), 0644)
172 } else {
173 fmt.Print(out)
174 }
175 return nil
176}
177
178func branch() string {
179 CI_COMMIT_TAG := os.Getenv("CI_COMMIT_TAG")
180 CI_COMMIT_BRANCH := os.Getenv("CI_COMMIT_BRANCH")
181 if CI_COMMIT_TAG != "" {
182 return "latest"
183 } else if CI_COMMIT_BRANCH != "" {
184 return strings.Replace(CI_COMMIT_BRANCH, "/", "-", -1)
185 } else {
186 panic("CI_COMMIT_TAG and CI_COMMIT_BRANCH undefined, can't get branch")
187 }
188}
189
190// Git struct wrapps Repository class from go-git to add a tag map used to perform queries when describing.
191type Git struct {
192 TagsMap map[plumbing.Hash]*plumbing.Reference
193 *git.Repository
194}
195
196// PlainOpen opens a git repository from the given path. It detects if the
197// repository is bare or a normal one. If the path doesn't contain a valid
198// repository ErrRepositoryNotExists is returned
199func PlainOpen(path string) (*Git, error) {
200 r, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{DetectDotGit: true})
201 return &Git{
202 make(map[plumbing.Hash]*plumbing.Reference),
203 r,
204 }, err
205}
206
207func (g *Git) getTagMap() error {
208 tags, err := g.Tags()
209 if err != nil {
210 return err
211 }
212
213 err = tags.ForEach(func(t *plumbing.Reference) error {
214 h, err := g.ResolveRevision(plumbing.Revision(t.Name()))
215 if err != nil {
216 return err
217 }
218 g.TagsMap[*h] = t
219 return nil
220 })
221
222 return err
223}
224
225// Describe the reference as 'git describe --tags' will do
226func (g *Git) Describe(reference *plumbing.Reference) (string, error) {
227
228 // Fetch the reference log
229 cIter, err := g.Log(&git.LogOptions{
230 // From: reference.Hash(),
231 Order: git.LogOrderCommitterTime,
232 })
233 if err != nil {
234 return "", err
235 }
236
237 // Build the tag map
238 err = g.getTagMap()
239 if err != nil {
240 return "", err
241 }
242
243 // Search the tag
244 var tag *plumbing.Reference
245 var count int
246 err = cIter.ForEach(func(c *object.Commit) error {
247 t, ok := g.TagsMap[c.Hash]
248 if ok {
249 tag = t
250 return storer.ErrStop
251 }
252 count++
253 return nil
254 })
255 if err != nil {
256 return "", err
257 }
258 head, err := g.Head()
259 if err != nil {
260 return "", err
261 }
262 if count == 0 && os.Getenv("CI_COMMIT_TAG") != "" {
263 return fmt.Sprint(tag.Name().Short()), nil
264 } else {
265 return fmt.Sprintf("%s-%s",
266 tag.Name().Short(),
267 head.Hash().String()[0:8],
268 ), nil
269 }
270}