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

View File

@@ -4,41 +4,90 @@ import (
"flink-kube-operator/internal/jar"
"flink-kube-operator/pkg"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"go.uber.org/zap"
)
// upload jar file and set the jarId for later usages
// upload pulls/downloads the jar and uploads it to Flink, then patches the CRD status.
func (job *ManagedJob) upload() error {
jarFile, err := jar.NewJarFile(job.def.Spec.JarURI, job.def.Spec.JarURIBasicAuthUsername, job.def.Spec.JarURIBasicAuthPassword)
spec := job.def.Spec
var jarFile *jar.JarFile
var err error
if spec.IsOCISource() {
auth, authErr := job.resolveAuth()
if authErr != nil {
pkg.Logger.Error("[managed-job] [upload] auth resolution failed", zap.Error(authErr))
return authErr
}
jarFile, err = jar.NewJarFile(spec.EffectiveJarRef(), auth)
} else {
jarFile, err = jar.NewJarFileFromURI(spec.JarURI, spec.JarURIBasicAuthUsername, spec.JarURIBasicAuthPassword)
}
if err != nil {
pkg.Logger.Debug("[manage-job] [upload] error on download jar", zap.Error(err))
pkg.Logger.Debug("[managed-job] [upload] error on pull/download jar", zap.Error(err))
return err
}
jarId, err := jarFile.Upload(job.client)
if err != nil {
pkg.Logger.Debug("[manage-job] [upload] error on upload jar", zap.Error(err))
pkg.Logger.Debug("[managed-job] [upload] error on upload jar", zap.Error(err))
return err
}
pkg.Logger.Info("[manage-job] [upload] uploaded", zap.Any("upload-jar-resp", jarId))
pkg.Logger.Info("[managed-job] [upload] uploaded", zap.String("jarId", jarId))
job.def.Status.JarId = &jarId
job.crd.Patch(job.def.UID, map[string]interface{}{
patchData := map[string]interface{}{
"status": map[string]interface{}{
"jarId": job.def.Status.JarId,
"jarId": &jarId,
},
})
}
if spec.IsOCISource() {
digest := jarFile.Digest()
patchData["status"].(map[string]interface{})["runningJarDigest"] = &digest
}
job.crd.Patch(job.def.UID, patchData)
return nil
}
func (job *ManagedJob) resolveAuth() (authn.Authenticator, error) {
s := job.def.Spec
server := ""
ref := s.EffectiveJarRef()
if reg, err := extractRegistry(ref); err == nil {
server = reg
}
return job.crd.ResolveAuth(s.JarPullSecret, server)
}
func extractRegistry(ref string) (string, error) {
r, err := name.NewRegistry(ref)
if err != nil {
t, err2 := name.NewTag(ref)
if err2 != nil {
return "", err
}
return t.RegistryStr(), nil
}
return r.RegistryStr(), nil
}
func (job *ManagedJob) RemoveJar() {
if job.def.Status.JarId != nil {
err := job.client.DeleteJar(*job.def.Status.JarId)
pkg.Logger.Error("[managed-job] [jar]", zap.Error(err))
if err != nil {
pkg.Logger.Error("[managed-job] [jar] delete jar error", zap.Error(err))
}
err = job.crd.Patch(job.def.UID, map[string]interface{}{
"status": map[string]interface{}{
"jarId": nil,
},
})
pkg.Logger.Error("[managed-job] [jar]", zap.Error(err))
if err != nil {
pkg.Logger.Error("[managed-job] [jar] patch jarId error", zap.Error(err))
}
}
}