mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 07:49:41 +02:00
feat(oci): oci helm support [r8s-361] (#787)
This commit is contained in:
parent
b6a6ce9aaf
commit
2697d6c5d7
80 changed files with 4264 additions and 812 deletions
126
pkg/libhelm/cache/cache.go
vendored
Normal file
126
pkg/libhelm/cache/cache.go
vendored
Normal file
|
@ -0,0 +1,126 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/rs/zerolog/log"
|
||||
"helm.sh/helm/v3/pkg/registry"
|
||||
)
|
||||
|
||||
// Cache manages Helm registry clients with TTL-based expiration
|
||||
// Registry clients are cached per registry ID rather than per user session
|
||||
// to optimize rate limiting - one login per registry per Portainer instance
|
||||
type Cache struct {
|
||||
cache *cache.Cache
|
||||
}
|
||||
|
||||
// CachedRegistryClient wraps a registry client with metadata
|
||||
type CachedRegistryClient struct {
|
||||
Client *registry.Client
|
||||
RegistryID portainer.RegistryID
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// newCache creates a new Helm registry client cache with the specified timeout
|
||||
func newCache(userSessionTimeout string) (*Cache, error) {
|
||||
timeout, err := time.ParseDuration(userSessionTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid user session timeout: %w", err)
|
||||
}
|
||||
|
||||
return &Cache{
|
||||
cache: cache.New(timeout, timeout),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getByRegistryID retrieves a cached registry client by registry ID
|
||||
// Cache key strategy: use registryID for maximum efficiency against rate limits
|
||||
// This means one login per registry per Portainer instance, regardless of user/environment
|
||||
func (c *Cache) getByRegistryID(registryID portainer.RegistryID) (*registry.Client, bool) {
|
||||
key := generateRegistryIDCacheKey(registryID)
|
||||
|
||||
cachedClient, found := c.cache.Get(key)
|
||||
if !found {
|
||||
log.Debug().
|
||||
Str("cache_key", key).
|
||||
Int("registry_id", int(registryID)).
|
||||
Str("context", "HelmRegistryCache").
|
||||
Msg("Cache miss for registry client")
|
||||
return nil, false
|
||||
}
|
||||
|
||||
client := cachedClient.(CachedRegistryClient)
|
||||
|
||||
log.Debug().
|
||||
Str("cache_key", key).
|
||||
Int("registry_id", int(registryID)).
|
||||
Str("context", "HelmRegistryCache").
|
||||
Msg("Cache hit for registry client")
|
||||
|
||||
return client.Client, true
|
||||
}
|
||||
|
||||
// setByRegistryID stores a registry client in the cache with registry ID context
|
||||
func (c *Cache) setByRegistryID(registryID portainer.RegistryID, client *registry.Client) {
|
||||
if client == nil {
|
||||
log.Warn().
|
||||
Int("registry_id", int(registryID)).
|
||||
Str("context", "HelmRegistryCache").
|
||||
Msg("Attempted to cache nil registry client")
|
||||
return
|
||||
}
|
||||
|
||||
key := generateRegistryIDCacheKey(registryID)
|
||||
|
||||
cachedClient := CachedRegistryClient{
|
||||
Client: client,
|
||||
RegistryID: registryID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
c.cache.Set(key, cachedClient, cache.DefaultExpiration)
|
||||
|
||||
log.Debug().
|
||||
Str("cache_key", key).
|
||||
Int("registry_id", int(registryID)).
|
||||
Str("context", "HelmRegistryCache").
|
||||
Msg("Cached registry client")
|
||||
}
|
||||
|
||||
// flushRegistry removes cached registry client for a specific registry ID
|
||||
// This should be called whenever registry credentials change
|
||||
func (c *Cache) flushRegistry(registryID portainer.RegistryID) {
|
||||
key := generateRegistryIDCacheKey(registryID)
|
||||
|
||||
c.cache.Delete(key)
|
||||
log.Info().
|
||||
Int("registry_id", int(registryID)).
|
||||
Str("context", "HelmRegistryCache").
|
||||
Msg("Flushed registry client due to registry change")
|
||||
}
|
||||
|
||||
// flushAll removes all cached registry clients
|
||||
func (c *Cache) flushAll() {
|
||||
itemCount := c.cache.ItemCount()
|
||||
c.cache.Flush()
|
||||
|
||||
if itemCount > 0 {
|
||||
log.Info().
|
||||
Int("cached_clients_removed", itemCount).
|
||||
Str("context", "HelmRegistryCache").
|
||||
Msg("Flushed all registry clients")
|
||||
}
|
||||
}
|
||||
|
||||
// generateRegistryIDCacheKey creates a cache key from registry ID
|
||||
// Key strategy decision: Use registry ID instead of user sessions or URL+username
|
||||
// This provides optimal rate limiting protection since each registry only gets
|
||||
// logged into once per Portainer instance, regardless of how many users access it
|
||||
// RBAC security is enforced before reaching this caching layer
|
||||
// When a new user needs access, they reuse the same cached client
|
||||
func generateRegistryIDCacheKey(registryID portainer.RegistryID) string {
|
||||
return fmt.Sprintf("registry:%d", registryID)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue