diff --git a/internal/plugins/ostree/api.go b/internal/plugins/ostree/api.go index 32b2c9e..9631603 100644 --- a/internal/plugins/ostree/api.go +++ b/internal/plugins/ostree/api.go @@ -35,6 +35,14 @@ func (p *Plugin) DeleteRepository(ctx context.Context, repository string) (err e return p.repositoryManager.Get(ctx, repository).DeleteRepository(ctx) } +func (p *Plugin) ListRepositoryRefs(ctx context.Context, repository string) (refs []apiv1.OSTreeRef, err error) { + if err := checkRepository(repository); err != nil { + return nil, err + } + + return p.repositoryManager.Get(ctx, repository).ListRepositoryRefs(ctx, repository) +} + func (p *Plugin) AddRemote(ctx context.Context, repository string, properties *apiv1.OSTreeRemoteProperties) (err error) { if err := checkRepository(repository); err != nil { return err diff --git a/internal/plugins/ostree/pkg/libostree/repo.go b/internal/plugins/ostree/pkg/libostree/repo.go index c688b9d..99e55f0 100644 --- a/internal/plugins/ostree/pkg/libostree/repo.go +++ b/internal/plugins/ostree/pkg/libostree/repo.go @@ -294,11 +294,8 @@ func (r *Repo) ListRefsExt(flags ListRefsExtFlags, prefix ...string) ([]Ref, err break } - // GHashTable - //ref := (*C.OstreeCollectionRef)(unsafe.Pointer(&cRef)) - ret = append(ret, Ref{ - Name: C.GoString((*C.char)(cRef)), //C.GoString(ref.ref_name), + Name: C.GoString((*C.char)(cRef)), Checksum: C.GoString((*C.char)(cChecksum)), }) } diff --git a/internal/plugins/ostree/pkg/ostreerepository/api.go b/internal/plugins/ostree/pkg/ostreerepository/api.go index 2fa1be3..6d21173 100644 --- a/internal/plugins/ostree/pkg/ostreerepository/api.go +++ b/internal/plugins/ostree/pkg/ostreerepository/api.go @@ -135,6 +135,38 @@ func (h *Handler) DeleteRepository(ctx context.Context) (err error) { return nil } +func (h *Handler) ListRepositoryRefs(ctx context.Context, repository string) (refs []apiv1.OSTreeRef, err error) { + // Transition to provisioning state + if err := h.setState(StateProvisioning); err != nil { + return nil, err + } + defer h.clearState() + + if !h.checkRepoExists(ctx) { + return nil, ctl.Errf("repository does not exist") + } + + err = h.BeginLocalRepoTransaction(ctx, func(ctx context.Context, repo *libostree.Repo) (bool, error) { + // Get the refs from the local repo + loRefs, err := repo.ListRefsExt(libostree.ListRefsExtFlagsNone) + if err != nil { + return false, ctl.Errf("listing refs from ostree repository: %s", err) + } + + // Convert the refs to the API type + for _, loRef := range loRefs { + refs = append(refs, apiv1.OSTreeRef{ + Name: loRef.Name, + Commit: loRef.Checksum, + }) + } + + return false, nil + }) + + return +} + func (h *Handler) AddRemote(ctx context.Context, remote *apiv1.OSTreeRemoteProperties) (err error) { // Transition to provisioning state if err := h.setState(StateProvisioning); err != nil { @@ -285,6 +317,15 @@ func (h *Handler) SyncRepository(_ context.Context, properties *apiv1.OSTreeRepo return false, ctl.Errf("regenerating summary for ostree repository %s: %s", h.repoDir, err) } + // List the refs in the repository and store in the repoSync + loRefs, err := repo.ListRefsExt(libostree.ListRefsExtFlagsNone) + if err != nil { + return false, ctl.Errf("listing refs from ostree repository: %s", err) + } + repoSync := *h.repoSync.Load() + repoSync.SyncedRefs = loRefs + h.setRepoSync(&repoSync) + return true, nil }) }() @@ -297,10 +338,20 @@ func (h *Handler) GetRepositorySyncStatus(_ context.Context) (syncStatus *apiv1. if repoSync == nil { return nil, ctl.Errf("repository sync status not available") } + + var refs []apiv1.OSTreeRef + for _, loRef := range repoSync.SyncedRefs { + refs = append(refs, apiv1.OSTreeRef{ + Name: loRef.Name, + Commit: loRef.Checksum, + }) + } + return &apiv1.SyncStatus{ - Syncing: repoSync.Syncing, - StartTime: utils.TimeToString(repoSync.StartTime), - EndTime: utils.TimeToString(repoSync.EndTime), - SyncError: repoSync.SyncError, + Syncing: repoSync.Syncing, + StartTime: utils.TimeToString(repoSync.StartTime), + EndTime: utils.TimeToString(repoSync.EndTime), + SyncError: repoSync.SyncError, + SyncedRefs: refs, }, nil } diff --git a/internal/plugins/ostree/pkg/ostreerepository/sync.go b/internal/plugins/ostree/pkg/ostreerepository/sync.go index 74f6cf9..cd9e391 100644 --- a/internal/plugins/ostree/pkg/ostreerepository/sync.go +++ b/internal/plugins/ostree/pkg/ostreerepository/sync.go @@ -5,13 +5,16 @@ package ostreerepository import ( "time" + + "go.ciq.dev/beskar/internal/plugins/ostree/pkg/libostree" ) type RepoSync struct { - Syncing bool - StartTime int64 - EndTime int64 - SyncError string + Syncing bool + StartTime int64 + EndTime int64 + SyncError string + SyncedRefs []libostree.Ref } func (h *Handler) setRepoSync(repoSync *RepoSync) { diff --git a/pkg/plugins/ostree/api/v1/api.go b/pkg/plugins/ostree/api/v1/api.go index f8ad096..83d6064 100644 --- a/pkg/plugins/ostree/api/v1/api.go +++ b/pkg/plugins/ostree/api/v1/api.go @@ -29,6 +29,14 @@ type OSTreeRepositoryProperties struct { Remotes []OSTreeRemoteProperties `json:"remotes"` } +type OSTreeRef struct { + // Name - The name of the ref. + Name string `json:"name"` + + // Commit - The commit hash of the ref. + Commit string `json:"commit"` +} + type OSTreeRemoteProperties struct { // Name - The name of the remote repository. Name string `json:"name"` @@ -58,10 +66,11 @@ type OSTreeRepositorySyncRequest struct { // Mirror sync status. type SyncStatus struct { - Syncing bool `json:"syncing"` - StartTime string `json:"start_time"` - EndTime string `json:"end_time"` - SyncError string `json:"sync_error"` + Syncing bool `json:"syncing"` + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` + SyncError string `json:"sync_error"` + SyncedRefs []OSTreeRef `json:"synced_refs"` // TODO: Implement these // The data for these is present when performing a pull via the ostree cli, so it is in the libostree code base. @@ -88,6 +97,11 @@ type OSTree interface { //kun:success statusCode=202 DeleteRepository(ctx context.Context, repository string) (err error) + // List OSTree repository refs (A.K.A. Branches). + //kun:op GET /repository/refs + //kun:success statusCode=200 + ListRepositoryRefs(ctx context.Context, repository string) (refs []OSTreeRef, err error) + // Add a new remote to the OSTree repository. //kun:op POST /repository/remote //kun:success statusCode=200 diff --git a/pkg/plugins/ostree/api/v1/endpoint.go b/pkg/plugins/ostree/api/v1/endpoint.go index c8dfb89..48b541b 100644 --- a/pkg/plugins/ostree/api/v1/endpoint.go +++ b/pkg/plugins/ostree/api/v1/endpoint.go @@ -194,6 +194,43 @@ func MakeEndpointOfGetRepositorySyncStatus(s OSTree) endpoint.Endpoint { } } +type ListRepositoryRefsRequest struct { + Repository string `json:"repository"` +} + +// ValidateListRepositoryRefsRequest creates a validator for ListRepositoryRefsRequest. +func ValidateListRepositoryRefsRequest(newSchema func(*ListRepositoryRefsRequest) validating.Schema) httpoption.Validator { + return httpoption.FuncValidator(func(value interface{}) error { + req := value.(*ListRepositoryRefsRequest) + return httpoption.Validate(newSchema(req)) + }) +} + +type ListRepositoryRefsResponse struct { + Refs []OSTreeRef `json:"refs"` + Err error `json:"-"` +} + +func (r *ListRepositoryRefsResponse) Body() interface{} { return r } + +// Failed implements endpoint.Failer. +func (r *ListRepositoryRefsResponse) Failed() error { return r.Err } + +// MakeEndpointOfListRepositoryRefs creates the endpoint for s.ListRepositoryRefs. +func MakeEndpointOfListRepositoryRefs(s OSTree) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + req := request.(*ListRepositoryRefsRequest) + refs, err := s.ListRepositoryRefs( + ctx, + req.Repository, + ) + return &ListRepositoryRefsResponse{ + Refs: refs, + Err: err, + }, nil + } +} + type SyncRepositoryRequest struct { Repository string `json:"repository"` Properties *OSTreeRepositorySyncRequest `json:"properties"` diff --git a/pkg/plugins/ostree/api/v1/http.go b/pkg/plugins/ostree/api/v1/http.go index afbc422..d74abb9 100644 --- a/pkg/plugins/ostree/api/v1/http.go +++ b/pkg/plugins/ostree/api/v1/http.go @@ -94,6 +94,20 @@ func NewHTTPRouter(svc OSTree, codecs httpcodec.Codecs, opts ...httpoption.Optio ), ) + codec = codecs.EncodeDecoder("ListRepositoryRefs") + validator = options.RequestValidator("ListRepositoryRefs") + r.Method( + "GET", "/repository/refs", + kithttp.NewServer( + MakeEndpointOfListRepositoryRefs(svc), + decodeListRepositoryRefsRequest(codec, validator), + httpcodec.MakeResponseEncoder(codec, 200), + append(kitOptions, + kithttp.ServerErrorEncoder(httpcodec.MakeErrorEncoder(codec)), + )..., + ), + ) + codec = codecs.EncodeDecoder("SyncRepository") validator = options.RequestValidator("SyncRepository") r.Method( @@ -205,6 +219,22 @@ func decodeGetRepositorySyncStatusRequest(codec httpcodec.Codec, validator httpo } } +func decodeListRepositoryRefsRequest(codec httpcodec.Codec, validator httpoption.Validator) kithttp.DecodeRequestFunc { + return func(_ context.Context, r *http.Request) (interface{}, error) { + var _req ListRepositoryRefsRequest + + if err := codec.DecodeRequestBody(r, &_req); err != nil { + return nil, err + } + + if err := validator.Validate(&_req); err != nil { + return nil, err + } + + return &_req, nil + } +} + func decodeSyncRepositoryRequest(codec httpcodec.Codec, validator httpoption.Validator) kithttp.DecodeRequestFunc { return func(_ context.Context, r *http.Request) (interface{}, error) { var _req SyncRepositoryRequest diff --git a/pkg/plugins/ostree/api/v1/http_client.go b/pkg/plugins/ostree/api/v1/http_client.go index a50aa1e..b9f5bad 100644 --- a/pkg/plugins/ostree/api/v1/http_client.go +++ b/pkg/plugins/ostree/api/v1/http_client.go @@ -280,6 +280,58 @@ func (c *HTTPClient) GetRepositorySyncStatus(ctx context.Context, repository str return respBody.SyncStatus, nil } +func (c *HTTPClient) ListRepositoryRefs(ctx context.Context, repository string) (refs []OSTreeRef, err error) { + codec := c.codecs.EncodeDecoder("ListRepositoryRefs") + + path := "/repository/refs" + u := &url.URL{ + Scheme: c.scheme, + Host: c.host, + Path: c.pathPrefix + path, + } + + reqBody := struct { + Repository string `json:"repository"` + }{ + Repository: repository, + } + reqBodyReader, headers, err := codec.EncodeRequestBody(&reqBody) + if err != nil { + return nil, err + } + + _req, err := http.NewRequestWithContext(ctx, "GET", u.String(), reqBodyReader) + if err != nil { + return nil, err + } + + for k, v := range headers { + _req.Header.Set(k, v) + } + + _resp, err := c.httpClient.Do(_req) + if err != nil { + return nil, err + } + defer _resp.Body.Close() + + if _resp.StatusCode < http.StatusOK || _resp.StatusCode > http.StatusNoContent { + var respErr error + err := codec.DecodeFailureResponse(_resp.Body, &respErr) + if err == nil { + err = respErr + } + return nil, err + } + + respBody := &ListRepositoryRefsResponse{} + err = codec.DecodeSuccessResponse(_resp.Body, respBody.Body()) + if err != nil { + return nil, err + } + return respBody.Refs, nil +} + func (c *HTTPClient) SyncRepository(ctx context.Context, repository string, properties *OSTreeRepositorySyncRequest) (err error) { codec := c.codecs.EncodeDecoder("SyncRepository") diff --git a/pkg/plugins/ostree/api/v1/oas2.go b/pkg/plugins/ostree/api/v1/oas2.go index 800dfa2..67a2a1a 100644 --- a/pkg/plugins/ostree/api/v1/oas2.go +++ b/pkg/plugins/ostree/api/v1/oas2.go @@ -109,6 +109,18 @@ paths: schema: $ref: "#/definitions/SyncRepositoryRequestBody" %s + /repository/refs: + get: + description: "List OSTree repository refs (A.K.A. Branches)." + operationId: "ListRepositoryRefs" + tags: + - ostree + parameters: + - name: body + in: body + schema: + $ref: "#/definitions/ListRepositoryRefsRequestBody" + %s ` ) @@ -121,6 +133,7 @@ func getResponses(schema oas2.Schema) []oas2.OASResponses { oas2.GetOASResponses(schema, "DeleteRepository", 202, &DeleteRepositoryResponse{}), oas2.GetOASResponses(schema, "GetRepositorySyncStatus", 200, &GetRepositorySyncStatusResponse{}), oas2.GetOASResponses(schema, "SyncRepository", 202, &SyncRepositoryResponse{}), + oas2.GetOASResponses(schema, "ListRepositoryRefs", 200, &ListRepositoryRefsResponse{}), } } @@ -155,6 +168,11 @@ func getDefinitions(schema oas2.Schema) map[string]oas2.Definition { }{})) oas2.AddResponseDefinitions(defs, schema, "GetRepositorySyncStatus", 200, (&GetRepositorySyncStatusResponse{}).Body()) + oas2.AddDefinition(defs, "ListRepositoryRefsRequestBody", reflect.ValueOf(&struct { + Repository string `json:"repository"` + }{})) + oas2.AddResponseDefinitions(defs, schema, "ListRepositoryRefs", 200, (&ListRepositoryRefsResponse{}).Body()) + oas2.AddDefinition(defs, "SyncRepositoryRequestBody", reflect.ValueOf(&struct { Repository string `json:"repository"` Properties *OSTreeRepositorySyncRequest `json:"properties"`