From 9245b7347b7749096623108b40c9a5c1005ba3c3 Mon Sep 17 00:00:00 2001 From: Kairo de Araujo Date: Tue, 12 Dec 2023 10:26:47 +0100 Subject: [PATCH] refactoring: testifysec/archivista-api as a pkg The current "github.com/testifysec/archivista-api" was detached from the Archivista from the historical strategy by TestifySec. TestifySec donated the Archivista project to the in-toto project, which makes more sense to the API package being part of the Archivista source code. This commit adds "github.com/testifysec/archivista-api" as pkg in Archivista. Closes #113 Signed-off-by: Kairo de Araujo --- cmd/archivistactl/cmd/retrieve.go | 6 +- cmd/archivistactl/cmd/search.go | 4 +- cmd/archivistactl/cmd/store.go | 4 +- pkg/api/download.go | 74 +++++++++++++++++++++++++ pkg/api/graphql.go | 92 +++++++++++++++++++++++++++++++ pkg/api/store.go | 77 ++++++++++++++++++++++++++ 6 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 pkg/api/download.go create mode 100644 pkg/api/graphql.go create mode 100644 pkg/api/store.go diff --git a/cmd/archivistactl/cmd/retrieve.go b/cmd/archivistactl/cmd/retrieve.go index 9f1df67e..c8265682 100644 --- a/cmd/archivistactl/cmd/retrieve.go +++ b/cmd/archivistactl/cmd/retrieve.go @@ -21,7 +21,7 @@ import ( "strings" "github.com/spf13/cobra" - archivistaapi "github.com/testifysec/archivista-api" + "github.com/testifysec/archivista/pkg/api" ) var ( @@ -50,7 +50,7 @@ var ( out = file } - return archivistaapi.DownloadWithWriter(cmd.Context(), archivistaUrl, args[0], out) + return api.DownloadWithWriter(cmd.Context(), archivistaUrl, args[0], out) }, } @@ -60,7 +60,7 @@ var ( SilenceUsage: true, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - results, err := archivistaapi.GraphQlQuery[retrieveSubjectResults](cmd.Context(), archivistaUrl, retrieveSubjectsQuery, retrieveSubjectVars{Gitoid: args[0]}) + results, err := api.GraphQlQuery[retrieveSubjectResults](cmd.Context(), archivistaUrl, retrieveSubjectsQuery, retrieveSubjectVars{Gitoid: args[0]}) if err != nil { return err } diff --git a/cmd/archivistactl/cmd/search.go b/cmd/archivistactl/cmd/search.go index a75d50dd..e15c610e 100644 --- a/cmd/archivistactl/cmd/search.go +++ b/cmd/archivistactl/cmd/search.go @@ -20,7 +20,7 @@ import ( "strings" "github.com/spf13/cobra" - archivistaapi "github.com/testifysec/archivista-api" + "github.com/testifysec/archivista/pkg/api" ) var ( @@ -49,7 +49,7 @@ Digests are expected to be in the form algorithm:digest, for instance: sha256:45 return err } - results, err := archivistaapi.GraphQlQuery[searchResults](cmd.Context(), archivistaUrl, searchQuery, searchVars{Algorithm: algo, Digest: digest}) + results, err := api.GraphQlQuery[searchResults](cmd.Context(), archivistaUrl, searchQuery, searchVars{Algorithm: algo, Digest: digest}) if err != nil { return err } diff --git a/cmd/archivistactl/cmd/store.go b/cmd/archivistactl/cmd/store.go index 0f5b5a3d..5c6ce233 100644 --- a/cmd/archivistactl/cmd/store.go +++ b/cmd/archivistactl/cmd/store.go @@ -20,7 +20,7 @@ import ( "os" "github.com/spf13/cobra" - archivistaapi "github.com/testifysec/archivista-api" + "github.com/testifysec/archivista/pkg/api" ) var ( @@ -54,7 +54,7 @@ func storeAttestationByPath(ctx context.Context, baseUrl, path string) (string, } defer file.Close() - resp, err := archivistaapi.StoreWithReader(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 new file mode 100644 index 00000000..2645dc53 --- /dev/null +++ b/pkg/api/download.go @@ -0,0 +1,74 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/url" + + "github.com/testifysec/go-witness/dsse" +) + +func Download(ctx context.Context, baseUrl string, gitoid string) (dsse.Envelope, error) { + buf := &bytes.Buffer{} + if err := DownloadWithWriter(ctx, baseUrl, gitoid, buf); err != nil { + return dsse.Envelope{}, err + } + + env := dsse.Envelope{} + dec := json.NewDecoder(buf) + if err := dec.Decode(&env); err != nil { + return env, err + } + + return env, nil +} + +func DownloadWithWriter(ctx context.Context, baseUrl, 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) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + hc := &http.Client{} + resp, err := hc.Do(req) + if err != nil { + return nil + } + + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + errMsg, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + return errors.New(string(errMsg)) + } + + _, err = io.Copy(dst, resp.Body) + return err +} diff --git a/pkg/api/graphql.go b/pkg/api/graphql.go new file mode 100644 index 00000000..73dbfb4e --- /dev/null +++ b/pkg/api/graphql.go @@ -0,0 +1,92 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" +) + +type graphQLError struct { + Message string `json:"message"` +} + +type graphQLResponse[T any] struct { + Data T `json:"data,omitempty"` + Errors []graphQLError `json:"errors,omitempty"` +} + +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) { + var response TRes + queryUrl, err := url.JoinPath(baseUrl, "query") + if err != nil { + return response, err + } + + requestBody := graphQLRequestBody[TVars]{ + Query: query, + Variables: vars, + } + + reqBody, err := json.Marshal(requestBody) + if err != nil { + return response, err + } + + req, err := http.NewRequestWithContext(ctx, "POST", queryUrl, bytes.NewReader(reqBody)) + if err != nil { + return response, err + } + + req.Header.Set("Content-Type", "application/json") + hc := &http.Client{} + res, err := hc.Do(req) + if err != nil { + return response, err + } + + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + errMsg, err := io.ReadAll(res.Body) + if err != nil { + return response, err + } + + return response, errors.New(string(errMsg)) + } + + dec := json.NewDecoder(res.Body) + gqlRes := graphQLResponse[TRes]{} + if err := dec.Decode(&gqlRes); err != nil { + return response, err + } + + if len(gqlRes.Errors) > 0 { + return response, fmt.Errorf("graph ql query failed: %v", gqlRes.Errors) + } + + return gqlRes.Data, nil +} diff --git a/pkg/api/store.go b/pkg/api/store.go new file mode 100644 index 00000000..5dbcf667 --- /dev/null +++ b/pkg/api/store.go @@ -0,0 +1,77 @@ +// Copyright 2023 The Witness Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/url" + + "github.com/testifysec/go-witness/dsse" +) + +type StoreResponse struct { + Gitoid string `json:"gitoid"` +} + +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 StoreWithReader(ctx, baseUrl, buf) +} + +func StoreWithReader(ctx context.Context, baseUrl string, r io.Reader) (StoreResponse, error) { + uploadPath, err := url.JoinPath(baseUrl, "upload") + if err != nil { + return StoreResponse{}, err + } + + req, err := http.NewRequestWithContext(ctx, "POST", uploadPath, r) + if err != nil { + return StoreResponse{}, err + } + + req.Header.Set("Content-Type", "application/json") + hc := &http.Client{} + resp, err := hc.Do(req) + if err != nil { + return StoreResponse{}, err + } + + defer resp.Body.Close() + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return StoreResponse{}, err + } + + if resp.StatusCode != http.StatusOK { + return StoreResponse{}, errors.New(string(bodyBytes)) + } + + storeResp := StoreResponse{} + if err := json.Unmarshal(bodyBytes, &storeResp); err != nil { + return StoreResponse{}, err + } + + return storeResp, nil +}