Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add registry index server endpoint testing #130

Merged
merged 8 commits into from
Aug 19, 2022
26 changes: 26 additions & 0 deletions index/server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Devfile registry index server

## Overview

Provides REST API support for devfile registries and serves [devfile registry viewer](https://github.com/devfile/registry-viewer) client.

For more information on REST API docs: [registry-REST-API.adoc](registry-REST-API.adoc)

## Testing

Endpoint unit testing is defined under `pkg/server/endpoint_test.go` and can be performed by running the following:

```sh
go test pkg/server/endpoint_test.go
```

or by running all tests:

```sh
go test ./...
```

**Environment Variables**

- `DEVFILE_REGISTRY`: Optional environment variable for specifying testing registry path
- default: `../../tests/registry`
2 changes: 1 addition & 1 deletion index/server/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/gin-gonic/gin v1.7.7
github.com/hashicorp/go-version v1.4.0
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
github.com/opencontainers/go-digest v1.0.0-rc1
github.com/opencontainers/image-spec v1.0.1
github.com/prometheus/client_golang v1.11.0
golang.org/x/text v0.3.6
Expand Down Expand Up @@ -64,7 +65,6 @@ require (
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
Expand Down
2 changes: 0 additions & 2 deletions index/server/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ github.com/devfile/api/v2 v2.0.0-20220117162434-6e6e6a8bc14c h1:sjghKUov/WT71dBr
github.com/devfile/api/v2 v2.0.0-20220117162434-6e6e6a8bc14c/go.mod h1:d99eTN6QxgzihOOFyOZA+VpUyD4Q1pYRYHZ/ci9J96Q=
github.com/devfile/library v1.2.1-0.20220308191614-f0f7e11b17de h1:jImHtiAxjyul1UkPmf6C3EMS5wqNz+k84LKkCXkeqws=
github.com/devfile/library v1.2.1-0.20220308191614-f0f7e11b17de/go.mod h1:GSPfJaBg0+bBjBHbwBE5aerJLH6tWGQu2q2rHYd9czM=
github.com/devfile/registry-support/index/generator v0.0.0-20220316161530-f06d84c42b54 h1:k7F4Hc4svkA+qHerBTZzcU1iVrQAJHOh8KurPnL4uYk=
github.com/devfile/registry-support/index/generator v0.0.0-20220316161530-f06d84c42b54/go.mod h1:1fyDJL+fPHtcrYA6yjSVWeLmXmjCNth0d5Rq1rvtryc=
github.com/devfile/registry-support/index/generator v0.0.0-20220624203950-e7282a4695b6 h1:bTbZxKSjF9xfiUuOKpoyX7P/ZcnIRy993+JBvkQ91hw=
github.com/devfile/registry-support/index/generator v0.0.0-20220624203950-e7282a4695b6/go.mod h1:1fyDJL+fPHtcrYA6yjSVWeLmXmjCNth0d5Rq1rvtryc=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
Expand Down
127 changes: 127 additions & 0 deletions index/server/pkg/ocitest/ocitest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package ocitest

import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"net/http/httptest"

"github.com/gin-gonic/gin"
)

// ResponseError repersents an error returned in an errors response by an OCI server,
// see https://github.com/opencontainers/distribution-spec/blob/main/spec.md#error-codes
type ResponseError struct {
Code string `json:"code"` // Error code
Message string `json:"message"` // Error Message
Detail map[string]interface{} `json:"detail"` // Additional detail on the error (optional)
}

// MockOCIServer is an entity for mocking an OCI server
// for testing. At the moment, this is only needed for
// the devfile registry index server endpoint testing,
// however, this entity could be used in a testing scenario
// where an OCI server is needed.
//
// More on the OCI server specification, see https://github.com/opencontainers/distribution-spec/blob/main/spec.md
type MockOCIServer struct {
httpserver *httptest.Server // Test server entity
router *gin.Engine // Router engine for route management
ServeManifest func(c *gin.Context) // Handler for serving a manifest for a blob
ServeBlob func(c *gin.Context) // Handler for serving a blob from the OCI server
}

// servePing is a custom handler to test if
// MockOCIServer is listening for requests
func servePing(c *gin.Context) {
data, err := json.Marshal(gin.H{
"message": "ok",
})
if err != nil {
log.Fatal(err)
}

c.JSON(http.StatusOK, data)
}

// WriteErrors writes error response object for OCI server
// errors
func WriteErrors(errors []ResponseError) map[string]interface{} {
return gin.H{
"errors": errors,
}
}

// NewMockOCIServer creates a MockOCIServer entity
func NewMockOCIServer() *MockOCIServer {
gin.SetMode(gin.TestMode)

mockOCIServer := &MockOCIServer{
// Create router engine of mock OCI server
router: gin.Default(),
}

// Create mock OCI server using the router engine
mockOCIServer.httpserver = httptest.NewUnstartedServer(mockOCIServer.router)

return mockOCIServer
}

// Start listening on listenAddr for requests to the MockOCIServer
func (server *MockOCIServer) Start(listenAddr string) error {
// Testing Route for checking mock OCI server
server.router.GET("/v2/ping", servePing)

// Pull Routes, see https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pull
// Fetch manifest routes
if server.ServeManifest != nil {
server.router.GET("/v2/devfile-catalog/:name/manifests/:ref", server.ServeManifest)
server.router.HEAD("/v2/devfile-catalog/:name/manifests/:ref", server.ServeManifest)
}

// Fetch blob routes
if server.ServeBlob != nil {
server.router.GET("/v2/devfile-catalog/:name/blobs/:digest", server.ServeBlob)
server.router.HEAD("/v2/devfile-catalog/:name/blobs/:digest", server.ServeBlob)
}

l, err := net.Listen("tcp", listenAddr)
if err != nil {
return fmt.Errorf("unexpected error while creating listener: %v", err)
}

server.httpserver.Listener.Close()
server.httpserver.Listener = l

server.httpserver.Start()

return nil
}

// Close the MockOCIServer connection
func (server *MockOCIServer) Close() {
server.httpserver.Close()
}

// ProxyRecorder is an extension of the ResponseRecorder
// struct within httptest with an additional receiver CloseNotifier
// which is needed for testing the proxy route to the OCI server
type ProxyRecorder struct {
*httptest.ResponseRecorder
http.CloseNotifier
}

// NewProxyRecorder creates a new ProxyRecorder entity
func NewProxyRecorder() *ProxyRecorder {
return &ProxyRecorder{
ResponseRecorder: httptest.NewRecorder(),
}
}

// CloseNotify creates a bool channel for notifying a
// closure of a request
func (rec *ProxyRecorder) CloseNotify() <-chan bool {
return make(<-chan bool)
}
25 changes: 13 additions & 12 deletions index/server/pkg/server/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ import (

const (
// Constants for resource names and media types
archiveMediaType = "application/x-tar"
archiveName = "archive.tar"
devfileName = "devfile.yaml"
devfileNameHidden = ".devfile.yaml"
devfileConfigMediaType = "application/vnd.devfileio.devfile.config.v2+json"
devfileMediaType = "application/vnd.devfileio.devfile.layer.v1"
pngLogoMediaType = "image/png"
pngLogoName = "logo.png"
svgLogoMediaType = "image/svg+xml"
svgLogoName = "logo.svg"
vsxMediaType = "application/vnd.devfileio.vsx.layer.v1.tar"
vsxName = "vsx"
archiveMediaType = "application/x-tar"
archiveName = "archive.tar"
starterProjectMediaType = "application/zip"
devfileName = "devfile.yaml"
devfileNameHidden = ".devfile.yaml"
devfileConfigMediaType = "application/vnd.devfileio.devfile.config.v2+json"
devfileMediaType = "application/vnd.devfileio.devfile.layer.v1"
pngLogoMediaType = "image/png"
pngLogoName = "logo.png"
svgLogoMediaType = "image/svg+xml"
svgLogoName = "logo.svg"
vsxMediaType = "application/vnd.devfileio.vsx.layer.v1.tar"
vsxName = "vsx"

scheme = "http"
registryService = "localhost:5000"
Expand Down
3 changes: 2 additions & 1 deletion index/server/pkg/server/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func serveDevfileStarterProjectWithVersion(c *gin.Context) {
}

c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", starterProjectName))
c.Data(http.StatusAccepted, "application/zip", downloadBytes)
c.Data(http.StatusAccepted, starterProjectMediaType, downloadBytes)
}
}

Expand Down Expand Up @@ -521,6 +521,7 @@ func fetchDevfile(c *gin.Context, name string, version string) ([]byte, indexSch
if devfileIndex.Type == indexSchema.StackDevfileType {
bytes, err = pullStackFromRegistry(foundVersion)
if err != nil {
log.Print(err.Error())
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
"status": fmt.Sprintf("Problem pulling version %s from OCI Registry", foundVersion.Version),
Expand Down
Loading