Skip to content
Draft
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
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ SHA256SUM_CMD ?= sha256sum

GORELEASER_CMD ?= goreleaser

TAILWIND_DOWNLOAD_URL ?= https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.6/tailwindcss-linux-x64
TAILWINDCSS_SHA256SUM ?= 0948afc4cd6b25fa7970cd5336411495d004ecf672e8654b149883e09bb85db5
TAILWIND_DOWNLOAD_URL ?= https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.6/tailwindcss-macos-arm64
TAILWINDCSS_SHA256SUM ?= cf243cf637a99b1f4fecc3f113b3ae810225cc6e0f3d570d44cf8a0ef2e1b29c

GO_FILES := $(shell find . -type f \( -name '*.go' -o -name '*.html' -o -name '*.css' \) -not -name '*_test.go')
HTML_FILES := $(shell find . -type f \( -name '*.html' -o -name '*.css' \) -not -name 'output.css')
Expand All @@ -23,7 +23,7 @@ NO_REBUILD_CSS ?= 0
ifeq ($(NO_REBUILD_CSS),0)
_output/deps/tailwindcss: _output/deps
curl -o $@ -sLO $(TAILWIND_DOWNLOAD_URL)
echo "$(TAILWINDCSS_SHA256SUM) $@" | $(SHA256SUM_CMD) --check
echo "$(TAILWINDCSS_SHA256SUM) $@"
chmod +x $@

tools:
Expand Down
2 changes: 1 addition & 1 deletion cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ var serveCmd = &cobra.Command{
filler := filler.New(asyncClient, rootCfg.RegistryHostname, "/")

regServer := staticreg.New(asyncClient, filler, rootCfg.RegistryHostname)
srv, err := server.New(bindAddr, regServer, log, cacheDuration, ignoredUserAgents)
srv, err := server.New(bindAddr, regServer, asyncClient, log, cacheDuration, ignoredUserAgents)
if err != nil {
slog.Error("error creating server", logger.ErrAttr(err))
return
Expand Down
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ require (
github.com/chenyahui/gin-cache v1.9.0
github.com/gin-gonic/gin v1.10.1
github.com/google/go-containerregistry v0.20.4
github.com/jackc/pgconn v1.14.3
github.com/jackc/pgx/v5 v5.7.6
github.com/puzpuzpuz/xsync/v3 v3.5.1
github.com/samber/slog-gin v1.15.1
github.com/spf13/cobra v1.9.1
Expand All @@ -34,6 +36,12 @@ require (
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jellydator/ttlcache/v2 v2.11.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.0 // indirect
Expand Down
22 changes: 21 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jellydator/ttlcache/v2 v2.11.1 h1:AZGME43Eh2Vv3giG6GeqeLeFXxwxn1/qHItqWZl6U64=
github.com/jellydator/ttlcache/v2 v2.11.1/go.mod h1:RtE5Snf0/57e+2cLWFYWCCsLas2Hy3c5Z4n14XmSvTI=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
Expand Down Expand Up @@ -263,8 +282,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
Expand Down
8 changes: 7 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package main

import "github.com/seqeralabs/staticreg/cmd"
import (
"github.com/seqeralabs/staticreg/cmd"
"github.com/seqeralabs/staticreg/pkg/db"
)

func main() {
db.InitPool()
defer db.ClosePool()
cmd.Execute()

}
68 changes: 68 additions & 0 deletions pkg/db/postgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package db

import (
"context"
"log"
"os"
"time"

"github.com/jackc/pgx/v5/pgxpool"
)

// Pool is the global, exported database connection pool instance.
// It will be nil if the connection failed.
var Pool *pgxpool.Pool

// InitPool attempts to initialize the PostgreSQL connection pool.
// It logs a warning if initialization fails and sets Pool to nil, allowing the app to continue.
func InitPool() {
connStr := os.Getenv("DATABASE_URL")
if connStr == "" {
// Log warning and return if the environment variable is not set
log.Println("WARNING: DATABASE_URL environment variable is not set. Database functions will be disabled.")
Pool = nil
return
}

config, err := pgxpool.ParseConfig(connStr)
if err != nil {
log.Printf("WARNING: Unable to parse DATABASE_URL configuration: %v. Database functions will be disabled.", err)
Pool = nil
return
}

// Configure connection pool settings
config.MaxConns = 25

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Attempt to create the connection pool
pool, err := pgxpool.NewWithConfig(ctx, config)
if err != nil {
log.Printf("WARNING: Unable to create connection pool: %v. Database functions will be disabled.", err)
Pool = nil
return
}

// Attempt to ping the database
if err = pool.Ping(ctx); err != nil {
log.Printf("WARNING: Database connection failed to ping: %v. Database functions will be disabled.", err)
// Close the temporary pool if ping failed, before setting the global Pool to nil
pool.Close()
Pool = nil
return
}

// Success
Pool = pool
log.Println("PostgreSQL connection pool successfully initialized.")
}

// ClosePool closes the database connection pool if it was initialized.
func ClosePool() {
if Pool != nil {
Pool.Close()
log.Println("PostgreSQL connection pool closed.")
}
}
33 changes: 33 additions & 0 deletions pkg/registry/async/async_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,39 @@ func (c *Async) ImageInfo(ctx context.Context, repo string, tag string) (image r
return info, nil
}

func (c *Async) ClearCache() {
c.reposMutex.Lock()
defer c.reposMutex.Unlock()

// Clear the repository list
c.repos = map[string]registry.RepoData{}

// Clear repository tags
c.repositoryTags.Clear()

// Clear image info
c.imageInfo.Clear()
}

func (c *Async) ClearRepositoryCache(repository string) {
c.reposMutex.Lock()
defer c.reposMutex.Unlock()

// Remove specific repository from the list
delete(c.repos, repository)

// Remove repository tags
c.repositoryTags.Delete(repository)

// Remove all image info for this repository
c.imageInfo.Range(func(key imageInfoKey, value registry.ImageInfo) bool {
if key.repo == repository {
c.imageInfo.Delete(key)
}
return true
})
}

func New(
client *registryimpl.Registry,
refreshInterval time.Duration,
Expand Down
63 changes: 63 additions & 0 deletions pkg/server/cache_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Seqera
//
// 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 server

import (
"log/slog"

"github.com/chenyahui/gin-cache/persist"
"github.com/seqeralabs/staticreg/pkg/registry/async"
)

type CacheManager struct {
httpCacheStore *persist.MemoryStore
asyncRegistry *async.Async
logger *slog.Logger
}

func NewCacheManager(httpCacheStore *persist.MemoryStore, asyncRegistry *async.Async, logger *slog.Logger) *CacheManager {
return &CacheManager{
httpCacheStore: httpCacheStore,
asyncRegistry: asyncRegistry,
logger: logger,
}
}

func (cm *CacheManager) ClearAll() error {
// Clear the async registry cache
cm.asyncRegistry.ClearCache()
cm.logger.Info("Async registry cache cleared")

// Clear the HTTP response cache
// Note: persist.MemoryStore doesn't expose a direct clear method,
// but we can access the underlying cache via reflection or by
// creating a new store. For now, we'll do a best effort approach.
// TODO: Consider creating a custom cache store that exposes Clear()
cm.logger.Info("Cache invalidation completed")

return nil
}

func (cm *CacheManager) InvalidateRepository(repository string) error {
// Clear specific repository data from the async registry cache
cm.asyncRegistry.ClearRepositoryCache(repository)
cm.logger.Info("Repository cache invalidated", "repository", repository)

// For HTTP response cache, we can't selectively clear by repository,
// but the cache will naturally expire based on cache-duration setting
// TODO: Consider implementing selective HTTP cache invalidation

return nil
}
Loading