Skip to content

Enable the devfile registry viewer #73

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

Merged
merged 11 commits into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@

# IDE related files
.idea

# Ignore the registry-viewer repo that we clone
registry-viewer
2 changes: 1 addition & 1 deletion build_registry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
set -eux

# Build the index server base image
docker build -t devfile-index-base:latest ./index/server/
./index/server/build.sh

# Build the test devfile registry image
docker build -t devfile-index:latest -f .ci/Dockerfile .
4 changes: 2 additions & 2 deletions deploy/chart/devfile-registry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ helm install devfile-registry deploy/chart/devfile-registry --set global.isOpenS

or, if you want to install a specific devfile index image, you can run:
```
helm install devfile-registry deploy/chart/devfile-registry --set global.isOpenShift=true --set devfileIndex.image=quay.io/myuser/devfile-index --set defileIndex.tag=latest
helm install devfile-registry deploy/chart/devfile-registry --set global.isOpenShift=true --set devfileIndex.image=quay.io/myuser/devfile-index --set devfileIndex.tag=latest
```

## Updating the Devfile Registry
Expand All @@ -43,7 +43,7 @@ helm upgrade <release-name> <path-to-chart> [--set options]

For example, updating the devfile index image of the devfile registry `my-registry` might look like:
```bash
helm upgrade my-registry deploy/chart/devfile-registry --set devfileIndex.image=docker.io/myuser/devfile-index --set defileIndex.tag=2.0
helm upgrade my-registry deploy/chart/devfile-registry --set devfileIndex.image=docker.io/myuser/devfile-index --set devfileIndex.tag=2.0
```

Alternatively to using `--set`, you can change the fields in `values.yaml` and then run the `helm upgrade` command.
Expand Down
11 changes: 9 additions & 2 deletions deploy/chart/devfile-registry/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: devfile-registry
name: {{ template "devfileregistry.fullname" . }}
labels:
app: {{ template "devfileregistry.name" . }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
Expand All @@ -35,4 +35,11 @@ data:
addr: :5001
prometheus:
enabled: true
path: /metrics
path: /metrics

devfile-registry-hosts.json: |
{
"Community": {
"url": "http://localhost:8080"
}
}
31 changes: 27 additions & 4 deletions deploy/chart/devfile-registry/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ spec:
items:
- key: registry-config.yml
path: config.yml
- name: viewer-config
configMap:
name: {{ template "devfileregistry.fullname" . }}
items:
- key: devfile-registry-hosts.json
path: devfile-registry-hosts.json

containers:
- image: "{{ .Values.devfileIndex.image }}:{{ .Values.devfileIndex.tag }}"
imagePullPolicy: {{ .Values.devfileIndex.imagePullPolicy }}
Expand All @@ -54,25 +61,41 @@ spec:
- containerPort: 8080
livenessProbe:
httpGet:
path: /
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 30
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 3
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 3
startupProbe:
httpGet:
path: /viewer
port: 8080
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 1
timeoutSeconds: 10
resources:
limits:
memory: {{ .Values.devfileIndex.memoryLimit }}
requests:
memory: 64Mi
env:
- name: DEVFILE_VIEWER_ROOT
value: "/viewer"
volumeMounts:
- name: viewer-config
mountPath: "/app/config"
readOnly: false

- image: "{{ .Values.ociRegistry.image }}:{{ .Values.ociRegistry.tag }}"
imagePullPolicy: {{ .Values.ociRegistry.imagePullPolicy }}
name: oci-registry
Expand Down
34 changes: 27 additions & 7 deletions index/server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Builder image
FROM golang:alpine3.11 AS builder
# Index Server build stage
FROM golang:alpine3.11 AS index-builder
WORKDIR /tools
COPY . .
RUN CGO_ENABLED=0 go build -mod=vendor -o index-server main.go

# Application image
FROM registry.access.redhat.com/ubi8-minimal:8.4
FROM registry.access.redhat.com/ubi8/nodejs-14-minimal AS runner
USER root

# Install and configure dependencies
RUN microdnf update -y && microdnf install shadow-utils findutils && rm -rf /var/cache/yum

COPY entrypoint.sh /

# Copy index server
COPY --from=builder /tools/index-server /registry/index-server
COPY --from=index-builder /tools/index-server /registry/index-server
RUN chgrp -R 0 /registry && \
chmod -R g=u /registry

Expand All @@ -24,7 +24,24 @@ RUN set -x ; \
# Modify the permissions on the necessary files to allow the container to properly run as a non-root UID
RUN mkdir -p /www/data && chmod -R g+rwx /www/data

USER www-data
WORKDIR /app

# Copy over the registry viewer's resources
COPY --from=registry-viewer --chown=www-data:root /app/next.config.js ./
COPY --from=registry-viewer --chown=www-data:root /app/public ./public
COPY --from=registry-viewer --chown=www-data:root /app/.next ./.next
COPY --from=registry-viewer /app/node_modules ./node_modules
COPY --from=registry-viewer /app/package.json ./package.json
COPY --from=registry-viewer --chown=www-data:root /app/config ./config

# Make sure .next is writable
RUN chmod -R g+rwx /app/.next

# Run the registry viewer in production
ENV NODE_ENV production

ENV DEVFILE_VIEWER_ROOT /viewer
ENV DEVFILE_COMMUNITY_HOST false

# Set env vars for the locations of the devfile stacks and index.json
ENV DEVFILE_STACKS /registry/stacks
Expand All @@ -36,5 +53,8 @@ ENV DEVFILE_SAMPLE_BASE64_INDEX /www/data/sample_base64_index.json
ENV DEVFILE_STACK_INDEX /www/data/stack_index.json
ENV DEVFILE_STACK_BASE64_INDEX /www/data/stack_base64_index.json

EXPOSE 8080 7071
USER www-data

EXPOSE 8080

ENTRYPOINT ["/entrypoint.sh"]
13 changes: 12 additions & 1 deletion index/server/build.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
#!/bin/sh

# Build the index container for the registry
buildfolder="$(basename "$(dirname "$0")")"
buildfolder="$(realpath $(dirname $0))"

# Clone the registry-support repo
if [ -d $buildfolder/registry-viewer ]; then
rm -rf $buildfolder/registry-viewer
fi
git clone https://github.com/devfile/registry-viewer.git $buildfolder/registry-viewer

# Build the registry viewer
docker build -t registry-viewer --target builder --build-arg DEVFILE_VIEWER_ROOT=/viewer --build-arg DEVFILE_COMMUNITY_HOST=false $buildfolder/registry-viewer

# Build the index server
docker build -t devfile-index-base:latest $buildfolder
16 changes: 12 additions & 4 deletions index/server/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/sh
#!/bin/bash
set -eux

# Check if devfile stacks and index.json exist
if [ ! -d "$DEVFILE_STACKS" ]; then
Expand All @@ -10,7 +11,14 @@ if [ ! -e "$DEVFILE_INDEX" ]; then
exit 1
fi

# Start the index server
/registry/index-server
# Start the registry viewer
npm start &

# Wait for server to start
until $(curl --output /dev/null --silent --head --fail http://localhost:3000/viewer); do
printf '.'
sleep 1
done

fg %1
# Start the index server
/registry/index-server
57 changes: 53 additions & 4 deletions index/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"os"
"path"
"path/filepath"
"strings"
"time"

"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -49,6 +50,7 @@ const (

scheme = "http"
registryService = "localhost:5000"
viewerService = "localhost:3000"
encodeFormat = "base64"
)

Expand Down Expand Up @@ -150,10 +152,9 @@ func main() {
// Start the server and serve requests and index.json
router := gin.Default()

router.GET("/", serveDevfileIndex)
router.GET("/index", serveDevfileIndex)
router.GET("/index.json", serveDevfileIndex)
router.GET("/", serveRootEndpoint)

router.GET("/index", serveDevfileIndex)
router.GET("/index/:type", func(c *gin.Context) {
indexType := c.Param("type")
iconType := c.Query("icon")
Expand Down Expand Up @@ -197,11 +198,40 @@ func main() {
router.HEAD("/v2/*proxyPath", ociServerProxy)
router.GET("/v2/*proxyPath", ociServerProxy)

// Set up routes for the registry viewer
router.GET("/viewer", serveUI)
router.GET("/viewer/*proxyPath", serveUI)
// Static content not available under /viewer that the registry viewer needs
router.Static("/images", "/app/public/images")
router.StaticFile("/manifest.json/", "/app/public/manifest.json")

// Serve static content for stacks
router.Static("/stacks", stacksPath)

router.Run(":8080")
}

// serveRootEndpoint sets up the handler for the root (/) endpoint on the server
// If html is requested (i.e. from a web browser), the viewer is displayed, otherwise the devfile index is served.
func serveRootEndpoint(c *gin.Context) {
// Determine if text/html was requested by the client
acceptHeader := c.Request.Header.Values("Accept")
if isHtmlRequested(acceptHeader) {
c.Redirect(http.StatusFound, "/viewer")
} else {
serveDevfileIndex(c)
}
}

func isHtmlRequested(acceptHeader []string) bool {
for _, header := range acceptHeader {
if strings.Contains(header, "text/html") {
return true
}
}
return false
}

// pushStackToRegistry pushes the given devfile stack to the OCI registry
func pushStackToRegistry(devfileIndex indexSchema.Schema) error {
// Load the devfile into memory and set up the pushing resource (file name, file content, media type, ref)
Expand Down Expand Up @@ -319,6 +349,26 @@ func ociServerProxy(c *gin.Context) {

proxy := httputil.NewSingleHostReverseProxy(remote)

// Set up the request to the proxy
// This is a good place to set up telemetry for requests to the OCI server (e.g. by parsing the path)
proxy.Director = func(req *http.Request) {
req.Header.Add("X-Forwarded-Host", req.Host)
req.Header.Add("X-Origin-Host", remote.Host)
req.URL.Scheme = remote.Scheme
req.URL.Host = remote.Host
}

proxy.ServeHTTP(c.Writer, c.Request)
}

func serveUI(c *gin.Context) {
remote, err := url.Parse(scheme + "://" + viewerService + "/viewer/")
if err != nil {
panic(err)
}

proxy := httputil.NewSingleHostReverseProxy(remote)

// Set up the request to the proxy
// This is a good place to set up telemetry for requests to the OCI server (e.g. by parsing the path)
proxy.Director = func(req *http.Request) {
Expand Down Expand Up @@ -428,7 +478,6 @@ func buildIndexAPIResponse(c *gin.Context, indexType string, iconType string) {
})
return
}

if iconType != "" {
if iconType == encodeFormat {
if _, err := os.Stat(responseBase64IndexPath); os.IsNotExist(err) {
Expand Down
43 changes: 43 additions & 0 deletions index/server/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,46 @@ func TestEncodeIndexIconToBase64(t *testing.T) {
})
}
}

func TestIsHtmlRequested(t *testing.T) {
tests := []struct {
name string
header []string
want bool
}{
{
name: "Case 1: Empty header",
header: []string{},
want: false,
},
{
name: "Case 2: Single header, no html",
header: []string{"application/xml"},
want: false,
},
{
name: "Case 3: Single header, html",
header: []string{"application/xml,text/html"},
want: true,
},
{
name: "Case 4: Multiple headers, no html",
header: []string{"Header1", "Header2", "Header3"},
want: false,
},
{
name: "Case 5: Multiple headers, html",
header: []string{"Header1", "Header2", "Header3", "text/html"},
want: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
htmlRequested := isHtmlRequested(test.header)
if htmlRequested != test.want {
t.Errorf("Got: %v, Expected: %v", htmlRequested, test.want)
}
})
}
}
Loading