~sircmpwn/builds.sr.ht

builds.sr.ht/api/graph/resolver.go -rw-r--r-- 2.4 KiB
0467110eDrew DeVault qemu: upgrade to 7.0.0 8 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package graph

import (
	"context"
	"database/sql"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"

	"github.com/99designs/gqlgen/graphql"

	"git.sr.ht/~sircmpwn/builds.sr.ht/api/graph/model"
)

type Resolver struct{}

func FetchLogs(ctx context.Context, url string) (*model.Log, error) {
	log := &model.Log{FullURL: url}

	// If the user hasn't requested the log body, stop here
	if graphql.GetFieldContext(ctx) != nil {
		found := false
		for _, field := range graphql.CollectFieldsCtx(ctx, nil) {
			if field.Name == "last128KiB" {
				found = true
				break
			}
		}
		if !found {
			return log, nil
		}
	}

	// TODO: It might be possible/desirable to set up an API with the runners
	// we can use to fetch logs in bulk, perhaps gzipped, and set up a loader
	// for it.
	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Range", "bytes=-131072") // Last 128 KiB
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case http.StatusOK:
	case http.StatusPartialContent:
		// OK
		break
	case http.StatusNotFound:
		return nil, nil
	default:
		return nil, fmt.Errorf("Unexpected response from build runner: %s", resp.Status)
	}
	limit := io.LimitReader(resp.Body, 131072)
	b, err := ioutil.ReadAll(limit)
	if err != nil {
		return nil, err
	}
	log.Last128KiB = string(b)

	return log, nil
}

// Starts a job group. Does not authenticate the user.
func StartJobGroupUnsafe(ctx context.Context, tx *sql.Tx, id int) error {
	var manifests []struct {
		ID       int
		Manifest *Manifest
	}

	rows, err := tx.QueryContext(ctx, `
		UPDATE job SET status = 'queued'
		WHERE job_group_id = $1
		RETURNING id, manifest;
	`, id)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		var (
			id       int
			manifest string
		)
		if err := rows.Scan(&id, &manifest); err != nil {
			return err
		}

		man, err := LoadManifest(manifest)
		if err != nil {
			// Invalid manifests shouldn't make it to the database
			panic(err)
		}

		manifests = append(manifests, struct {
			ID       int
			Manifest *Manifest
		}{
			ID:       id,
			Manifest: man,
		})
	}

	if err := rows.Err(); err != nil {
		return err
	}

	err = tx.Commit()
	if err != nil {
		return err
	}

	for _, job := range manifests {
		if err := SubmitJob(ctx, job.ID, job.Manifest); err != nil {
			return fmt.Errorf("Failed to submit some jobs: %e", err)
		}
	}

	return nil
}