Commit 4f50db4a authored by Sam Roque-Worcel's avatar Sam Roque-Worcel Committed by Patrick Rice
Browse files

feat(integrations): Add attestations integrations

Changelog: Improvements
parent 6ab316dc
Loading
Loading
Loading
Loading

attestations.go

0 → 100644
+64 −0
Original line number Diff line number Diff line
package gitlab

import (
	"bytes"
	"net/http"
	"time"
)

type (
	AttestationsServiceInterface interface {
		// ListAttestations gets a list of all attestations
		//
		// GitLab API docs: https://docs.gitlab.com/api/attestations
		ListAttestations(pid any, subjectDigest string, options ...RequestOptionFunc) ([]*Attestation, *Response, error)

		// DownloadAttestation
		//
		// GitLab API docs: https://docs.gitlab.com/api/attestations
		DownloadAttestation(pid any, attestationIID int64, options ...RequestOptionFunc) ([]byte, *Response, error)
	}

	// AttestationsService handles communication with the keys related methods
	// of the GitLab API.
	//
	// GitLab API docs: https://docs.gitlab.com/api/attestations
	AttestationsService struct {
		client *Client
	}
)

var _ AttestationsServiceInterface = (*AttestationsService)(nil)

type Attestation struct {
	ID            int64      `json:"id"`
	IID           int64      `json:"iid"`
	ProjectID     int64      `json:"project_id"`
	BuildID       int64      `json:"build_id"`
	Status        string     `json:"status"`
	CreatedAt     *time.Time `json:"created_at"`
	UpdatedAt     *time.Time `json:"updated_at"`
	ExpireAt      *time.Time `json:"expire_at"`
	PredicateKind string     `json:"predicate_kind"`
	PredicateType string     `json:"predicate_type"`
	SubjectDigest string     `json:"subject_digest"`
	DownloadURL   string     `json:"download_url"`
}

func (s *AttestationsService) ListAttestations(pid any, subjectDigest string, options ...RequestOptionFunc) ([]*Attestation, *Response, error) {
	return do[[]*Attestation](s.client,
		withMethod(http.MethodGet),
		withPath("projects/%s/attestations/%s", ProjectID{pid}, subjectDigest),
		withRequestOpts(options...),
	)
}

func (s *AttestationsService) DownloadAttestation(pid any, attestationIID int64, options ...RequestOptionFunc) ([]byte, *Response, error) {
	b, resp, err := do[bytes.Buffer](s.client,
		withMethod(http.MethodGet),
		withPath("projects/%s/attestations/%d/download", ProjectID{pid}, attestationIID),
		withRequestOpts(options...),
	)

	return b.Bytes(), resp, err
}

attestations_test.go

0 → 100644
+74 −0
Original line number Diff line number Diff line
package gitlab

import (
	"fmt"
	"net/http"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestListAttestations(t *testing.T) {
	t.Parallel()
	mux, client := setup(t)

	mux.HandleFunc("/api/v4/projects/1337/attestations/76c34666f719ef14bd2b124a7db51e9c05e4db2e12a84800296d559064eebe2c", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprintf(w, `[
		{
			"build_id": 1337,
			"created_at": "2025-10-07T20:59:27.085Z",
			"download_url": "https://gitlab.com/api/v4/projects/72356192/attestations/1/download",
			"expire_at": "2027-10-07T20:59:26.967Z",
			"id": 1,
			"iid": 1,
			"predicate_kind": "provenance",
			"predicate_type": "https://slsa.dev/provenance/v1",
			"project_id": 1337,
			"status": "success",
			"subject_digest": "76c34666f719ef14bd2b124a7db51e9c05e4db2e12a84800296d559064eebe2c",
			"updated_at": "2025-10-07T20:59:27.085Z"
		}
		]`)
	})

	attestations, resp, err := client.Attestations.ListAttestations("1337", "76c34666f719ef14bd2b124a7db51e9c05e4db2e12a84800296d559064eebe2c")
	assert.NoError(t, err)
	assert.NotNil(t, resp)

	want := []*Attestation{
		{
			ID:            1,
			IID:           1,
			BuildID:       1337,
			DownloadURL:   "https://gitlab.com/api/v4/projects/72356192/attestations/1/download",
			CreatedAt:     mustParseTime("2025-10-07T20:59:27.085Z"),
			UpdatedAt:     mustParseTime("2025-10-07T20:59:27.085Z"),
			ExpireAt:      mustParseTime("2027-10-07T20:59:26.967Z"),
			PredicateKind: "provenance",
			PredicateType: "https://slsa.dev/provenance/v1",
			ProjectID:     1337,
			Status:        "success",
			SubjectDigest: "76c34666f719ef14bd2b124a7db51e9c05e4db2e12a84800296d559064eebe2c",
		},
	}
	assert.Equal(t, want, attestations)
}

func TestDownloadAttestation(t *testing.T) {
	t.Parallel()
	mux, client := setup(t)

	const expectedOut = "expected_output"

	mux.HandleFunc("/api/v4/projects/1337/attestations/1/download", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprintf(w, expectedOut)
	})

	outBytes, resp, err := client.Attestations.DownloadAttestation("1337", 1)
	assert.NoError(t, err)
	assert.NotNil(t, resp)

	assert.Equal(t, []byte(expectedOut), outBytes)
}
+2 −0
Original line number Diff line number Diff line
@@ -123,6 +123,7 @@ type Client struct {
	Appearance                       AppearanceServiceInterface
	Applications                     ApplicationsServiceInterface
	ApplicationStatistics            ApplicationStatisticsServiceInterface
	Attestations                     AttestationsServiceInterface
	AuditEvents                      AuditEventsServiceInterface
	Avatar                           AvatarRequestsServiceInterface
	AwardEmoji                       AwardEmojiServiceInterface
@@ -437,6 +438,7 @@ func NewAuthSourceClient(as AuthSource, options ...ClientOptionFunc) (*Client, e
	c.Appearance = &AppearanceService{client: c}
	c.Applications = &ApplicationsService{client: c}
	c.ApplicationStatistics = &ApplicationStatisticsService{client: c}
	c.Attestations = &AttestationsService{client: c}
	c.AuditEvents = &AuditEventsService{client: c}
	c.Avatar = &AvatarRequestsService{client: c}
	c.AwardEmoji = &AwardEmojiService{client: c}
+1 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ var serviceMap = map[any]any{
	&AppearanceService{}:                       (*AppearanceServiceInterface)(nil),
	&ApplicationStatisticsService{}:            (*ApplicationStatisticsServiceInterface)(nil),
	&ApplicationsService{}:                     (*ApplicationsServiceInterface)(nil),
	&AttestationsService{}:                     (*AttestationsServiceInterface)(nil),
	&AuditEventsService{}:                      (*AuditEventsServiceInterface)(nil),
	&AvatarRequestsService{}:                   (*AvatarRequestsServiceInterface)(nil),
	&AwardEmojiService{}:                       (*AwardEmojiServiceInterface)(nil),
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ package testing
//go:generate go run go.uber.org/mock/mockgen@v0.6.0 -typed -destination=appearance_mock.go -write_package_comment=false -package=testing gitlab.com/gitlab-org/api/client-go AppearanceServiceInterface
//go:generate go run go.uber.org/mock/mockgen@v0.6.0 -typed -destination=application_statistics_mock.go -write_package_comment=false -package=testing gitlab.com/gitlab-org/api/client-go ApplicationStatisticsServiceInterface
//go:generate go run go.uber.org/mock/mockgen@v0.6.0 -typed -destination=applications_mock.go -write_package_comment=false -package=testing gitlab.com/gitlab-org/api/client-go ApplicationsServiceInterface
//go:generate go run go.uber.org/mock/mockgen@v0.6.0 -typed -destination=attestations_mock.go -write_package_comment=false -package=testing gitlab.com/gitlab-org/api/client-go AttestationsServiceInterface
//go:generate go run go.uber.org/mock/mockgen@v0.6.0 -typed -destination=audit_events_mock.go -write_package_comment=false -package=testing gitlab.com/gitlab-org/api/client-go AuditEventsServiceInterface
//go:generate go run go.uber.org/mock/mockgen@v0.6.0 -typed -destination=avatar_mock.go -write_package_comment=false -package=testing gitlab.com/gitlab-org/api/client-go AvatarRequestsServiceInterface
//go:generate go run go.uber.org/mock/mockgen@v0.6.0 -typed -destination=award_emojis_mock.go -write_package_comment=false -package=testing gitlab.com/gitlab-org/api/client-go AwardEmojiServiceInterface
Loading