diff --git a/cmd/archivistactl/cmd/store.go b/cmd/archivistactl/cmd/store.go index 87b3e59d..ef7e76a2 100644 --- a/cmd/archivistactl/cmd/store.go +++ b/cmd/archivistactl/cmd/store.go @@ -54,7 +54,7 @@ func storeAttestationByPath(ctx context.Context, baseUrl, path string) (string, } defer file.Close() - resp, err := api.UploadWithReader(ctx, baseUrl, file) + resp, err := api.StoreWithReader(ctx, baseUrl, file) if err != nil { return "", err } diff --git a/pkg/api/download.go b/pkg/api/download.go index 06c67883..21487acf 100644 --- a/pkg/api/download.go +++ b/pkg/api/download.go @@ -26,9 +26,40 @@ import ( "github.com/in-toto/go-witness/dsse" ) -func Download(ctx context.Context, baseUrl string, gitoid string) (dsse.Envelope, error) { +func DownloadReadCloser(ctx context.Context, baseURL string, gitoid string) (io.ReadCloser, error) { + return DownloadReadCloserWithHTTPClient(ctx, &http.Client{}, baseURL, gitoid) +} + +func DownloadReadCloserWithHTTPClient(ctx context.Context, client *http.Client, baseURL string, gitoid string) (io.ReadCloser, error) { + downloadURL, err := url.JoinPath(baseURL, "download", gitoid) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + // NOTE: attempt to read body on error and + // only close if an error occurs + defer resp.Body.Close() + errMsg, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return nil, errors.New(string(errMsg)) + } + return resp.Body, nil +} + +func Download(ctx context.Context, baseURL string, gitoid string) (dsse.Envelope, error) { buf := &bytes.Buffer{} - if err := DownloadWithWriter(ctx, baseUrl, gitoid, buf); err != nil { + if err := DownloadWithWriter(ctx, baseURL, gitoid, buf); err != nil { return dsse.Envelope{}, err } @@ -41,13 +72,17 @@ func Download(ctx context.Context, baseUrl string, gitoid string) (dsse.Envelope return env, nil } -func DownloadWithWriter(ctx context.Context, baseUrl, gitoid string, dst io.Writer) error { - downloadUrl, err := url.JoinPath(baseUrl, "download", gitoid) +func DownloadWithWriter(ctx context.Context, baseURL string, gitoid string, dst io.Writer) error { + return DownloadWithWriterWithHTTPClient(ctx, &http.Client{}, baseURL, gitoid, dst) +} + +func DownloadWithWriterWithHTTPClient(ctx context.Context, client *http.Client, baseURL string, gitoid string, dst io.Writer) error { + downloadUrl, err := url.JoinPath(baseURL, "download", gitoid) if err != nil { return err } - req, err := http.NewRequestWithContext(ctx, "GET", downloadUrl, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadUrl, nil) if err != nil { return err } diff --git a/pkg/api/graphql.go b/pkg/api/graphql.go index 73dbfb4e..67c309ab 100644 --- a/pkg/api/graphql.go +++ b/pkg/api/graphql.go @@ -25,28 +25,69 @@ import ( "net/url" ) -type graphQLError struct { - Message string `json:"message"` -} +const RetrieveSubjectsQuery = `query($gitoid: String!) { + subjects( + where: { + hasStatementWith:{ + hasDsseWith:{ + gitoidSha256: $gitoid + } + } + } + ) { + edges { + node{ + name + subjectDigests{ + algorithm + value + } + } + } + } +}` -type graphQLResponse[T any] struct { - Data T `json:"data,omitempty"` - Errors []graphQLError `json:"errors,omitempty"` -} +const SearchQuery = `query($algo: String!, $digest: String!) { + dsses( + where: { + hasStatementWith: { + hasSubjectsWith: { + hasSubjectDigestsWith: { + value: $digest, + algorithm: $algo + } + } + } + } + ) { + edges { + node { + gitoidSha256 + statement { + attestationCollections { + name + attestations { + type + } + } + } + } + } + } +}` -type graphQLRequestBody[TVars any] struct { - Query string `json:"query"` - Variables TVars `json:"variables,omitempty"` +func GraphQlQuery[TRes any, TVars any](ctx context.Context, baseUrl, query string, vars TVars) (TRes, error) { + return GraphQlQueryWithHeaders[TRes, TVars](ctx, baseUrl, query, vars, nil) } -func GraphQlQuery[TRes any, TVars any](ctx context.Context, baseUrl, query string, vars TVars) (TRes, error) { +func GraphQlQueryWithHeaders[TRes any, TVars any](ctx context.Context, baseUrl, query string, vars TVars, headers map[string]string) (TRes, error) { var response TRes queryUrl, err := url.JoinPath(baseUrl, "query") if err != nil { return response, err } - requestBody := graphQLRequestBody[TVars]{ + requestBody := GraphQLRequestBodyGeneric[TVars]{ Query: query, Variables: vars, } @@ -56,11 +97,15 @@ func GraphQlQuery[TRes any, TVars any](ctx context.Context, baseUrl, query strin return response, err } - req, err := http.NewRequestWithContext(ctx, "POST", queryUrl, bytes.NewReader(reqBody)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, queryUrl, bytes.NewReader(reqBody)) if err != nil { return response, err } + for k, v := range headers { + req.Header.Set(k, v) + } + req.Header.Set("Content-Type", "application/json") hc := &http.Client{} res, err := hc.Do(req) @@ -79,7 +124,7 @@ func GraphQlQuery[TRes any, TVars any](ctx context.Context, baseUrl, query strin } dec := json.NewDecoder(res.Body) - gqlRes := graphQLResponse[TRes]{} + gqlRes := GraphQLResponseGeneric[TRes]{} if err := dec.Decode(&gqlRes); err != nil { return response, err } diff --git a/pkg/api/structs.go b/pkg/api/structs.go new file mode 100644 index 00000000..9cdfff01 --- /dev/null +++ b/pkg/api/structs.go @@ -0,0 +1,76 @@ +package api + +type GraphQLError struct { + Message string `json:"message"` +} + +type GraphQLResponseGeneric[T any] struct { + Data T `json:"data,omitempty"` + Errors []GraphQLError `json:"errors,omitempty"` +} + +type GraphQLRequestBodyGeneric[TVars any] struct { + Query string `json:"query"` + Variables TVars `json:"variables,omitempty"` +} + +type RetrieveSubjectVars struct { + Gitoid string `json:"gitoid"` +} + +type SearchVars struct { + Algorithm string `json:"algo"` + Digest string `json:"digest"` +} + +type RetrieveSubjectResults struct { + Subjects Subjects `json:"subjects"` +} + +type Subjects struct { + Edges []SubjectEdge `json:"edges"` +} + +type SubjectEdge struct { + Node SubjectNode `json:"node"` +} + +type SubjectNode struct { + Name string `json:"name"` + SubjectDigests []SubjectDigest `json:"subjectDigests"` +} + +type SubjectDigest struct { + Algorithm string `json:"algorithm"` + Value string `json:"value"` +} + +type SearchResults struct { + Dsses DSSES `json:"dsses"` +} + +type DSSES struct { + Edges []SearchEdge `json:"edges"` +} + +type SearchEdge struct { + Node SearchNode `json:"node"` +} + +type SearchNode struct { + GitoidSha256 string `json:"gitoidSha256"` + Statement Statement `json:"statement"` +} + +type Statement struct { + AttestationCollection AttestationCollection `json:"attestationCollections"` +} + +type AttestationCollection struct { + Name string `json:"name"` + Attestations []Attestation `json:"attestations"` +} + +type Attestation struct { + Type string `json:"type"` +} diff --git a/pkg/api/upload.go b/pkg/api/upload.go index 94504412..2357499b 100644 --- a/pkg/api/upload.go +++ b/pkg/api/upload.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Witness Contributors +// Copyright 2023 The Archivista Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -33,28 +33,27 @@ type UploadResponse struct { // Deprecated: Use UploadResponse instead. It will be removed in version >= v0.6.0 type StoreResponse = UploadResponse -// Deprecated: Use Upload instead. It will be removed in version >= v0.6.0 -func Store(ctx context.Context, baseUrl string, envelope dsse.Envelope) (StoreResponse, error) { - return Upload(ctx, baseUrl, envelope) +// Deprecated: Use Store instead. It will be removed in version >= v0.6.0 +func Upload(ctx context.Context, baseURL string, envelope dsse.Envelope) (UploadResponse, error) { + return Store(ctx, baseURL, envelope) } -func Upload(ctx context.Context, baseUrl string, envelope dsse.Envelope) (StoreResponse, error) { +func Store(ctx context.Context, baseURL string, envelope dsse.Envelope) (StoreResponse, error) { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) if err := enc.Encode(envelope); err != nil { return StoreResponse{}, err } - return UploadWithReader(ctx, baseUrl, buf) + return StoreWithReader(ctx, baseURL, buf) } -// Deprecated: Use UploadWithReader instead. It will be removed in version >= v0.6.0 -func StoreWithReader(ctx context.Context, baseUrl string, r io.Reader) (StoreResponse, error) { - return UploadWithReader(ctx, baseUrl, r) +func StoreWithReader(ctx context.Context, baseURL string, r io.Reader) (StoreResponse, error) { + return StoreWithReaderWithHTTPClient(ctx, &http.Client{}, baseURL, r) } -func UploadWithReader(ctx context.Context, baseUrl string, r io.Reader) (StoreResponse, error) { - uploadPath, err := url.JoinPath(baseUrl, "upload") +func StoreWithReaderWithHTTPClient(ctx context.Context, client *http.Client, baseURL string, r io.Reader) (StoreResponse, error) { + uploadPath, err := url.JoinPath(baseURL, "upload") if err != nil { return UploadResponse{}, err }