feat(jar): add OCI registry pull for JAR artifacts with backward compat

- Add jarRef and jarPullSecret fields to FlinkJob CRD (jarUri/basicAuth deprecated)
- OCI pull via go-containerregistry with dual auth (K8s pull secret + env vars)
- Media type validation on pulled layers
- Atomic status patches with runningJarRef/runningJarDigest tracking
- NeedsUpgrade/RunningRef/RunningRefPatchData domain helpers
- README with usage guide, pushing JARs, and GitHub Actions CI/CD workflow
- CONTEXT.md domain glossary
This commit is contained in:
2026-07-24 17:37:30 +03:30
parent 91c10c89da
commit a36e72877d
15 changed files with 750 additions and 92 deletions

83
internal/crd/auth.go Normal file
View File

@@ -0,0 +1,83 @@
package crd
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/google/go-containerregistry/pkg/authn"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)
type dockerConfigJSON struct {
Auths map[string]authn.AuthConfig `json:"auths"`
}
// ResolveAuth returns an OCI authenticator for the given registry.
// It checks in order: pull secret from K8s, then environment variables.
// Returns authn.Anonymous if no credentials are found.
func (crd *Crd) ResolveAuth(pullSecretName string, server string) (authn.Authenticator, error) {
if pullSecretName != "" {
return crd.authFromPullSecret(pullSecretName, server)
}
return authFromEnv(server), nil
}
func (crd *Crd) authFromPullSecret(secretName string, server string) (authn.Authenticator, error) {
ns := os.Getenv("NAMESPACE")
secret := &corev1.Secret{}
err := crd.runtimeClient.Get(context.Background(), types.NamespacedName{
Namespace: ns,
Name: secretName,
}, secret)
if err != nil {
return nil, fmt.Errorf("reading pull secret %q: %w", secretName, err)
}
if secret.Type != corev1.SecretTypeDockerConfigJson {
return nil, fmt.Errorf("secret %q is not of type kubernetes.io/dockerconfigjson", secretName)
}
dockerConfigJSONBytes, ok := secret.Data[corev1.DockerConfigJsonKey]
if !ok {
return nil, fmt.Errorf("secret %q missing .dockerconfigjson key", secretName)
}
var cfg dockerConfigJSON
if err := json.Unmarshal(dockerConfigJSONBytes, &cfg); err != nil {
return nil, fmt.Errorf("parsing docker config from secret %q: %w", secretName, err)
}
if server != "" {
if entry, ok := cfg.Auths[server]; ok {
return authn.FromConfig(entry), nil
}
}
for _, entry := range cfg.Auths {
return authn.FromConfig(entry), nil
}
return authn.Anonymous, nil
}
func authFromEnv(server string) authn.Authenticator {
username := os.Getenv("REGISTRY_USERNAME")
password := os.Getenv("REGISTRY_PASSWORD")
envServer := os.Getenv("REGISTRY_SERVER")
if username == "" || password == "" {
return authn.Anonymous
}
if envServer != "" && server != "" && envServer != server {
return authn.Anonymous
}
return authn.FromConfig(authn.AuthConfig{
Username: username,
Password: password,
})
}