penguin/monok8s

k8s image for Mono Gateway Dev Kit

clitools/pkg/catalog/resolver.go

raw ยท 2358 bytes

package catalog

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"

	"sigs.k8s.io/yaml"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"

	monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
)

const (
	DefaultCachePath = "/var/lib/monok8s/catalog.yaml"
)

// ResolveCatalog resolves catalog using priority:
// Inline > ConfigMap > URL > cached
func ResolveCatalog(
	ctx context.Context,
	kubeClient kubernetes.Interface,
	namespace string,
	src *monov1alpha1.VersionCatalogSource,
) (*VersionCatalog, error) {

	// 1. Inline
	if src != nil && src.Inline != "" {
		return parseCatalog([]byte(src.Inline))
	}

	// 2. ConfigMap
	if src != nil && src.ConfigMap != "" {
		cm, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(ctx, src.ConfigMap, metav1.GetOptions{})
		if err != nil {
			return nil, fmt.Errorf("get catalog configmap: %w", err)
		}

		data, ok := cm.Data["catalog.yaml"]
		if !ok {
			return nil, fmt.Errorf("configmap %s missing key catalog.yaml", src.ConfigMap)
		}

		return parseCatalog([]byte(data))
	}

	// 3. URL
	if src != nil && src.URL != "" {
		cat, err := fetchCatalog(src.URL)
		if err == nil {
			_ = os.WriteFile(DefaultCachePath, mustMarshal(cat), 0644)
			return cat, nil
		}

		// fallback to cache
		if cached, err2 := loadCached(); err2 == nil {
			return cached, nil
		}

		return nil, fmt.Errorf("fetch catalog failed and no cache: %w", err)
	}

	// 4. cached fallback
	if cached, err := loadCached(); err == nil {
		return cached, nil
	}

	return nil, fmt.Errorf("no catalog source available")
}

func fetchCatalog(url string) (*VersionCatalog, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("http %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return parseCatalog(body)
}

func loadCached() (*VersionCatalog, error) {
	data, err := os.ReadFile(DefaultCachePath)
	if err != nil {
		return nil, err
	}
	return parseCatalog(data)
}

func parseCatalog(data []byte) (*VersionCatalog, error) {
	var c VersionCatalog
	if err := yaml.Unmarshal(data, &c); err != nil {
		return nil, fmt.Errorf("parse catalog: %w", err)
	}
	return &c, nil
}

func mustMarshal(c *VersionCatalog) []byte {
	b, _ := yaml.Marshal(c)
	return b
}