File: poller.go

package info (click to toggle)
gitlab-agent 16.11.5-1
  • links: PTS, VCS
  • area: contrib
  • in suites: experimental
  • size: 7,072 kB
  • sloc: makefile: 193; sh: 55; ruby: 3
file content (111 lines) | stat: -rw-r--r-- 3,402 bytes parent folder | download
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
package gitaly

import (
	"context"
	"fmt"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/gitaly/vendored/gitalypb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/encoding"
	protoenc "google.golang.org/grpc/encoding/proto"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/proto"
)

const (
	DefaultBranch = "HEAD"
)

var (
	_ encoding.Codec = (*pollingCodec)(nil)
)

// PollerInterface does the following:
// - polls ref advertisement for updates to the repository
// - detects which is the main branch, if branch or tag name is not specified
// - compares the commit id the branch or tag is referring to with the last processed one
// - returns the information about the change
type PollerInterface interface {
	// Poll performs a poll on the repository.
	// revision can be a branch name or a tag.
	// Poll returns a wrapped context.Canceled, context.DeadlineExceeded or gRPC error if ctx signals done and interrupts a running gRPC call.
	// Poll returns *Error when an error occurs.
	Poll(ctx context.Context, repo *gitalypb.Repository, lastProcessedCommitID, refName string) (*PollInfo, error)
}

type Poller struct {
	Client   gitalypb.CommitServiceClient
	Features map[string]string
}

type PollInfo struct {
	CommitID        string
	UpdateAvailable bool
	// RefNotFound is true when no commits were found for the provided ref.
	RefNotFound bool
}

// Poll searched the given repository for the given fullRefName and returns a PollInfo containing a resolved Commit Object ID.
// Valid fullRefNames are:
// * `refs/heads/*` => for branches
// * `refs/tags/*` => for tags
// * `HEAD` => for the repository's default branch
func (p *Poller) Poll(ctx context.Context, repo *gitalypb.Repository, lastProcessedCommitID, fullRefName string) (*PollInfo, error) {
	ctx = appendFeatureFlagsToContext(ctx, p.Features)
	commit, err := p.Client.FindCommit(ctx, &gitalypb.FindCommitRequest{
		Repository: repo,
		Revision:   []byte(fullRefName),
	}, grpc.ForceCodec(pollingCodec{}))
	if err != nil {
		switch status.Code(err) { //nolint:exhaustive
		case codes.NotFound:
			return nil, NewNotFoundError("FindCommit", fullRefName)
		default:
			return nil, NewRPCError(err, "FindCommit", fullRefName)
		}
	}
	if commit.Commit == nil {
		return &PollInfo{
			RefNotFound: true,
		}, nil
	}
	return &PollInfo{
		CommitID:        commit.Commit.Id,
		UpdateAvailable: commit.Commit.Id != lastProcessedCommitID,
	}, nil
}

// pollingCodec avoids unmarshaling fields of the FindCommitResponse message that are not needed.
// This reduces CPU usage and generates much less garbage on the heap.
type pollingCodec struct {
}

func (c pollingCodec) Marshal(v any) ([]byte, error) {
	return proto.Marshal(v.(proto.Message))
}

func (c pollingCodec) Unmarshal(data []byte, v any) error {
	typedV, ok := v.(*gitalypb.FindCommitResponse)
	if !ok {
		return fmt.Errorf("unexpected message type: %T", v)
	}
	var pollingV FindCommitResponseForPolling
	err := proto.UnmarshalOptions{
		DiscardUnknown: true, // don't care about other fields, that's the point of this codec!
	}.Unmarshal(data, &pollingV)
	if err != nil {
		return err
	}
	if pollingV.Commit != nil {
		typedV.Commit = &gitalypb.GitCommit{
			Id: pollingV.Commit.Id,
		}
	}
	return nil
}

func (c pollingCodec) Name() string {
	// Pretend to be a codec for protobuf.
	return protoenc.Name
}