diff --git a/.air.toml b/.air.toml index e6d7edb..63a9bb5 100644 --- a/.air.toml +++ b/.air.toml @@ -4,7 +4,7 @@ tmp_dir = "tmp" [build] args_bin = [] - bin = "./tmp/main" + bin = ";set -o allexport && source ./.env && set +o allexport; ./tmp/main" cmd = "go build -o ./tmp/main ./cmd/operator" delay = 1000 exclude_dir = ["assets", "tmp", "vendor", "testdata"] diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4b8a6f6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +Tiltfile +Dockerfile +.env.example +.git +.gitignore \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..18ded18 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +FLINK_API_URL=127.0.0.1:8081 +SAVEPOINT_PATH=/opt/flink/savepoints \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1331240..071e3b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ db *.log tmp -*.jar \ No newline at end of file +*.jar +.env \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e4bbc26 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "env": { + "FLINK_API_URL": "127.0.0.1:8081", + "SAVEPOINT_PATH": "/opt/flink/savepoints" + }, + "cwd": "${workspaceFolder}", + "program": "${workspaceFolder}/cmd/operator" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 62cdd69..2d47207 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,19 @@ { "cSpell.words": [ - "flink" + "apiextensions", + "clientcmd", + "controllerutil", + "deepcopy", + "Finalizer", + "flink", + "gitea", + "gonanoid", + "logicamp", + "Namespaceable", + "nindent", + "reactivex", + "repsert", + "rxgo", + "tolerations" ] } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ef568c7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +FROM public.ecr.aws/docker/library/golang:1.23.4-bookworm AS build + +ARG upx_version=4.2.4 + +RUN apt-get update && apt-get install -y --no-install-recommends xz-utils && \ + curl -Ls https://github.com/upx/upx/releases/download/v${upx_version}/upx-${upx_version}-amd64_linux.tar.xz -o - | tar xvJf - -C /tmp && \ + cp /tmp/upx-${upx_version}-amd64_linux/upx /usr/local/bin/ && \ + chmod +x /usr/local/bin/upx && \ + apt-get remove -y xz-utils && \ + rm -rf /var/lib/apt/lists/* + +# Set destination for COPY +WORKDIR /app + +# Download Go modules +COPY go.mod go.sum ./ +RUN --mount=type=cache,target="/go" go mod download -x + +COPY . . + +# Build +ENV GOCACHE=/root/.cache/go-build +RUN --mount=type=cache,target="/go" --mount=type=cache,target="/root/.cache/go-build" CGO_ENABLED=1 GOOS=linux go build -ldflags '-s -w' -o /flink-kube-operator ./cmd/operator +RUN upx -q -5 /flink-kube-operator + +FROM public.ecr.aws/docker/library/busybox:1.37.0 AS final + + +COPY --from=build /flink-kube-operator /flink-kube-operator + +EXPOSE 8083 + +# Run +CMD ["/flink-kube-operator"] \ No newline at end of file diff --git a/Dockerfile.flink b/Dockerfile.flink new file mode 100644 index 0000000..e32884a --- /dev/null +++ b/Dockerfile.flink @@ -0,0 +1,22 @@ +FROM public.ecr.aws/docker/library/flink:1.20.0-scala_2.12-java17 + +# Set working directory +WORKDIR /opt/flink + +# Set environment variables for Flink mini-cluster +ENV FLINK_HOME /opt/flink +ENV PATH=$FLINK_HOME/bin:$PATH + +# Expose necessary ports for the Flink UI (JobManager) and job manager +EXPOSE 8081 6123 + +COPY ./start-cluster.sh /opt/flink/bin/start-cluster.sh +RUN chmod +x /opt/flink/bin/start-cluster.sh + +RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-connector-kafka/3.4.0-1.20/flink-connector-kafka-3.4.0-1.20.jar -P /opt/flink/lib/ +RUN wget -q https://repo1.maven.org/maven2/org/apache/kafka/kafka-clients/3.9.0/kafka-clients-3.9.0.jar -P /opt/flink/lib/ +RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-avro/1.20.0/flink-avro-1.20.0.jar -P /opt/flink/lib/ +RUN wget -q https://repo1.maven.org/maven2/org/apache/flink/flink-avro-confluent-registry/1.20.0/flink-avro-confluent-registry-1.20.0.jar -P /opt/flink/lib/ + +# Command to start Flink JobManager and TaskManager in a mini-cluster setup +CMD ["bin/start-cluster.sh"] \ No newline at end of file diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 0000000..cb4b330 --- /dev/null +++ b/Tiltfile @@ -0,0 +1,108 @@ +# Welcome to Tilt! +# To get you started as quickly as possible, we have created a +# starter Tiltfile for you. +# +# Uncomment, modify, and delete any commands as needed for your +# project's configuration. + + +# Output diagnostic messages +# You can print log messages, warnings, and fatal errors, which will +# appear in the (Tiltfile) resource in the web UI. Tiltfiles support +# multiline strings and common string operations such as formatting. +# +# More info: https://docs.tilt.dev/api.html#api.warn +print(""" +----------------------------------------------------------------- +✨ Hello Tilt! This appears in the (Tiltfile) pane whenever Tilt + evaluates this file. +----------------------------------------------------------------- +""".strip()) +warn('ℹ️ Open {tiltfile_path} in your favorite editor to get started.'.format( + tiltfile_path=config.main_path)) + + +# Build Docker image +# Tilt will automatically associate image builds with the resource(s) +# that reference them (e.g. via Kubernetes or Docker Compose YAML). +# +# More info: https://docs.tilt.dev/api.html#api.docker_build +# +docker_build('lcr.logicamp.tech/library/flink-kube-operator:latest', + context='.', + # (Optional) Use a custom Dockerfile path + dockerfile='Dockerfile', + # (Optional) Filter the paths used in the build + # only=['./tmp/main'], + # (Recommended) Updating a running container in-place + # https://docs.tilt.dev/live_update_reference.html + # live_update=[ + # # Sync files from host to container + # sync('./tmp/main', '/flink-kube-operator'), + # #sync('./app', '/src/'), + # # Execute commands inside the container when certain + # # paths change + # #run('/src/codegen.sh', trigger=['./app/api']) + # run('/flink-kube-operator', trigger=["/flink-kube-operator"]) + # ] +) + + +# Apply Kubernetes manifests +# Tilt will build & push any necessary images, re-deploying your +# resources as they change. +# +# More info: https://docs.tilt.dev/api.html#api.k8s_yaml +# +# k8s_yaml(['k8s/deployment.yaml', 'k8s/service.yaml']) + + +# Customize a Kubernetes resource +# By default, Kubernetes resource names are automatically assigned +# based on objects in the YAML manifests, e.g. Deployment name. +# +# Tilt strives for sane defaults, so calling k8s_resource is +# optional, and you only need to pass the arguments you want to +# override. +# +# More info: https://docs.tilt.dev/api.html#api.k8s_resource +# +# k8s_resource('my-deployment', +# # map one or more local ports to ports on your Pod +# port_forwards=['5000:8080'], +# # change whether the resource is started by default +# auto_init=False, +# # control whether the resource automatically updates +# trigger_mode=TRIGGER_MODE_MANUAL +# ) + +# local_resource('build', cmd='go build -o ./tmp/main -ldflags \'-s -w\' ./cmd/operator && upx -q -5 ./tmp/main') +# Run local commands +# Local commands can be helpful for one-time tasks like installing +# project prerequisites. They can also manage long-lived processes +# for non-containerized services or dependencies. +# +# More info: https://docs.tilt.dev/local_resource.html +# +# local_resource('install-helm', +# cmd='which helm > /dev/null || brew install helm', +# # `cmd_bat`, when present, is used instead of `cmd` on Windows. +# cmd_bat=[ +# 'powershell.exe', +# '-Noninteractive', +# '-Command', +# '& {if (!(Get-Command helm -ErrorAction SilentlyContinue)) {scoop install helm}}' +# ] +# ) + + +# Extensions are open-source, pre-packaged functions that extend Tilt +# +# More info: https://github.com/tilt-dev/tilt-extensions +# +load('ext://git_resource', 'git_checkout') + + +k8s_yaml(helm('./helm', name="flink-operator", namespace='logiline')) +allow_k8s_contexts('logicamp-staging-admin@logicamp-staging') + diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 8bbb233..b2d8ab5 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -1,28 +1,23 @@ package main import ( - "flink-kube-operator/internal/config" - "flink-kube-operator/internal/managed_job" - "fmt" + "flink-kube-operator/internal/crd" + "flink-kube-operator/internal/manager" + "flink-kube-operator/pkg" "log" "os" - "gitea.com/logicamp/lc" - "github.com/dgraph-io/badger/v4" api "github.com/logi-camp/go-flink-client" "go.uber.org/zap" ) func main() { - lc.Logger.Debug("start") - db, err := badger.Open(badger.DefaultOptions("./db")) - if err != nil { - lc.Logger.Fatal("[main] error on open db", zap.Error(err)) - } - defer db.Close() + // init kubernetes flink job crd watch + crdInstance := crd.New() - c, err := api.New("127.0.0.1:8081") + // create flink api instance + c, err := api.New(os.Getenv("FLINK_API_URL")) if err != nil { panic(err) } @@ -32,36 +27,26 @@ func main() { if err != nil { panic(err) } - fmt.Println(clusterConfig) + pkg.Logger.Info("[main]", zap.Any("cluster-config", clusterConfig)) - jars, err := c.Jars() - if err != nil { - lc.Logger.Error("error on getting jars", zap.Error(err)) - } - lc.Logger.Debug("[main] jars", zap.Any("jars", jars)) + // init flink job manager + manager.NewManager(c, crdInstance) - jobs, err := c.Jobs() - if err != nil { - lc.Logger.Error("error on getting jars", zap.Error(err)) - } - lc.Logger.Debug("[main] jobs", zap.Any("jobs", jobs)) + // for _, jobDef := range config.Jobs { + // managed_job.NewManagedJob(c, db, jobDef) + // } - config := lc.LoadYamlConfig[config.Config]("./config.yaml") - for _, jobDef := range config.Jobs { - managed_job.NewManagedJob(c, db, jobDef) - } - - for _, job := range jobs.Jobs { - job, err := c.Job(job.ID) - if err != nil { - lc.Logger.Error("error getting job info", zap.Error(err)) - continue - } - if job.State == "RUNNING" { - lc.Logger.Debug("[main] running job", zap.String("jobId", job.ID)) - } - // lc.Logger.Debug("[main]", zap.Any("job", job)) - } + // for _, job := range jobs.Jobs { + // job, err := c.Job(job.ID) + // if err != nil { + // pkg.Logger.Error("error getting job info", zap.Error(err)) + // continue + // } + // if job.State == "RUNNING" { + // pkg.Logger.Debug("[main] running job", zap.String("jobId", job.ID)) + // } + // // pkg.Logger.Debug("[main]", zap.Any("job", job)) + // } cancelChan := make(chan os.Signal, 1) sig := <-cancelChan diff --git a/config.yaml b/config.yaml deleted file mode 100644 index f3e43d3..0000000 --- a/config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -jobs: - - key: price-processor - name: Price Processor - entryClass: top.bazargam.Main - jarURI: http://price-processor.bz2/price-processor-v0.0.1.jar - savepointInterval: 3m \ No newline at end of file diff --git a/crds.yaml b/crds.yaml new file mode 100644 index 0000000..191d083 --- /dev/null +++ b/crds.yaml @@ -0,0 +1,115 @@ +# flink-crd.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: flink-jobs.flink.logicamp.tech +spec: + group: flink.logicamp.tech + names: + kind: FlinkJob + plural: flink-jobs + singular: flink-job + shortNames: + - lfj + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + required: + - key + - jarUri + properties: + key: + type: string + name: + type: string + entryClass: + type: string + parallelism: + type: integer + jarUri: + type: string + savepointInterval: + type: string + format: duration + flinkConfiguration: + type: object + additionalProperties: + type: string + resources: + type: object + properties: + requests: + type: object + properties: + memory: + type: string + cpu: + type: string + limits: + type: object + properties: + memory: + type: string + cpu: + type: string + status: + type: object + properties: + jobStatus: + type: string + startTime: + type: string + jobId: + type: string + jarId: + type: string + error: + type: string + lastSavepointPath: + type: string + lifeCycleStatus: + type: string + savepointTriggerId: + type: string + lastSavepointDate: + type: string + format: time + lastRestoredSavepointDate: + type: string + format: time + lastRestoredSavepointRestoredDate: + type: string + format: time + runningJarURI: + type: string + pauseSavepointTriggerId: + type: string + restoredCount: + type: number + additionalPrinterColumns: + - name: Status + type: string + jsonPath: .status.jobStatus + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + - name: Life Cycle Status + type: string + jsonPath: .status.lifeCycleStatus + - name: Last Savepoint + type: date + jsonPath: .status.lastSavepointDate + - name: Last Restored Savepoint + type: date + jsonPath: .status.lastRestoredSavepointDate + - name: Restored Count + type: number + jsonPath: .status.restoredCount diff --git a/example-job.yaml b/example-job.yaml new file mode 100644 index 0000000..ebd74a2 --- /dev/null +++ b/example-job.yaml @@ -0,0 +1,15 @@ +# flink-job-instance.yaml +apiVersion: flink.logicamp.tech/v1alpha1 +kind: FlinkJob +metadata: + name: my-flink-job + namespace: default +spec: + key: word-count + name: "Word Count Example" + entryClass: "org.apache.flink.examples.java.wordcount.WordCount" + parallelism: 2 + jarUri: "http://192.168.7.7:8080/product-enrichment-processor.jar" + flinkConfiguration: + taskmanager.numberOfTaskSlots: "2" + parallelism.default: "2" \ No newline at end of file diff --git a/go.mod b/go.mod index 274e0ab..a1ffb1a 100644 --- a/go.mod +++ b/go.mod @@ -3,50 +3,69 @@ module flink-kube-operator go 1.23.2 require ( - gitea.com/logicamp/lc v1.14.6 // indirect - github.com/IBM/sarama v1.43.3 // indirect - github.com/arangodb/go-driver v1.6.4 // indirect - github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/confluentinc/confluent-kafka-go/v2 v2.6.0 // indirect - github.com/danielgtaylor/huma/v2 v2.26.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dgraph-io/badger/v4 v4.5.0 // indirect - github.com/dgraph-io/ristretto/v2 v2.0.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/eapache/go-resiliency v1.7.0 // indirect - github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect - github.com/eapache/queue v1.1.0 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/flatbuffers v24.3.25+incompatible // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/jcmturner/aescts/v2 v2.0.0 // indirect - github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect - github.com/jcmturner/gofork v1.7.6 // indirect - github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect - github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/joho/godotenv v1.5.1 // indirect - github.com/klauspost/compress v1.17.11 // indirect - github.com/logi-camp/go-flink-client v0.1.0 // indirect - github.com/matoous/go-nanoid/v2 v2.1.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/nats-io/nats.go v1.37.0 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - go.opencensus.io v0.24.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.29.0 // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/logi-camp/go-flink-client v0.2.0 + github.com/matoous/go-nanoid/v2 v2.1.0 + github.com/samber/lo v1.47.0 + go.uber.org/zap v1.27.0 + k8s.io/apimachinery v0.31.3 + k8s.io/client-go v0.31.3 +) + +require ( + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect + google.golang.org/protobuf v1.35.1 // indirect +) + +require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mattn/go-colorable v0.1.13 + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/reactivex/rxgo/v2 v2.5.0 + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/teivah/onecontext v0.0.0-20200513185103-40f981bfd775 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.6.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.31.3 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241127205056-99599406b04f // indirect + k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 // indirect + sigs.k8s.io/controller-runtime v0.19.2 + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.3 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 8d28637..a386679 100644 --- a/go.sum +++ b/go.sum @@ -1,99 +1,69 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -gitea.com/logicamp/lc v1.14.6 h1:jgtrYwbqvWU/cxT9mDFCToK7D+tEpGyhV3FnSn3hKBw= -gitea.com/logicamp/lc v1.14.6/go.mod h1:P29QjvOVa1VnEZIFi+Mab7uRbO/qRrbZPdARSpi4qXs= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA= -github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ= -github.com/arangodb/go-driver v1.6.4 h1:tuizDP620e3OFaoFkEh07n4bHSTvCOvm5qVGQ/+zMrs= -github.com/arangodb/go-driver v1.6.4/go.mod h1:oLZCzkEGh82I2XBcMkrDtqsMXFg6QkIRgbqLGalh6jc= -github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e h1:Xg+hGrY2LcQBbxd0ZFdbGSyRKTYMZCfBbw/pMJFOk1g= -github.com/arangodb/go-velocypack v0.0.0-20200318135517-5af53c29c67e/go.mod h1:mq7Shfa/CaixoDxiyAAc5jZ6CVBAyPaNQCGS7mkj4Ho= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/confluentinc/confluent-kafka-go/v2 v2.6.0 h1:VKnMT71Tl0dCp3lfGBp2D8eqQwc+amoDY5EeUgFHDDE= -github.com/confluentinc/confluent-kafka-go/v2 v2.6.0/go.mod h1:hScqtFIGUI1wqHIgM3mjoqEou4VweGGGX7dMpcUKves= -github.com/danielgtaylor/huma/v2 v2.26.0 h1:lON4pIcckuSQJNDi6WkOu0sS7mxvlNkTAGbc3BrRXTc= -github.com/danielgtaylor/huma/v2 v2.26.0/go.mod h1:NbSFXRoOMh3BVmiLJQ9EbUpnPas7D9BeOxF/pZBAGa0= +github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/badger/v4 v4.5.0 h1:TeJE3I1pIWLBjYhIYCA1+uxrjWEoJXImFBMEBVSm16g= -github.com/dgraph-io/badger/v4 v4.5.0/go.mod h1:ysgYmIeG8dS/E8kwxT7xHyc7MkmwNYLRoYnFbr7387A= -github.com/dgraph-io/ristretto/v2 v2.0.0 h1:l0yiSOtlJvc0otkqyMaDNysg8E9/F/TYZwMbxscNOAQ= -github.com/dgraph-io/ristretto/v2 v2.0.0/go.mod h1:FVFokF2dRqXyPyeMnK1YDy8Fc6aTe0IKgbcd03CYeEk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= -github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= -github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/flink-go/api v0.0.1 h1:YK3TBZLV4TivPhjJ+Z316b2mSg72sPy3IOcm21OyaO4= -github.com/flink-go/api v0.0.1/go.mod h1:nzM6X8dmpVRe3jqfQ4y05JeTIRWZPD4iT6kyEaFBG9A= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= -github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= -github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= -github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= -github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= -github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= -github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/logi-camp/go-flink-client v0.0.1 h1:VZI5ctoJEArwtBprIXZNk+cv5fEQZ4EcuKAbOY8Emvw= -github.com/logi-camp/go-flink-client v0.0.1/go.mod h1:A79abedX6wGQI0FoICdZI7SRoGHj15QwMwWowgsKYFI= -github.com/logi-camp/go-flink-client v0.0.3 h1:NT9FYJG7jqroq44jfJfRnva3NO/jgpgURcU0mFysK1A= -github.com/logi-camp/go-flink-client v0.0.3/go.mod h1:A79abedX6wGQI0FoICdZI7SRoGHj15QwMwWowgsKYFI= -github.com/logi-camp/go-flink-client v0.1.0 h1:uzBV6RGkyzZVdSQ8zAlUGbJ5hdXRspaPjzzYJUQ2aJU= -github.com/logi-camp/go-flink-client v0.1.0/go.mod h1:A79abedX6wGQI0FoICdZI7SRoGHj15QwMwWowgsKYFI= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/logi-camp/go-flink-client v0.2.0 h1:PIyfJq7FjW28bnvemReCicIuQD7JzVgJDk2xPTZUS2s= +github.com/logi-camp/go-flink-client v0.2.0/go.mod h1:A79abedX6wGQI0FoICdZI7SRoGHj15QwMwWowgsKYFI= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE= github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -101,121 +71,139 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= -github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/reactivex/rxgo/v2 v2.5.0 h1:FhPgHwX9vKdNQB2gq9EPt+EKk9QrrzoeztGbEEnZam4= +github.com/reactivex/rxgo/v2 v2.5.0/go.mod h1:bs4fVZxcb5ZckLIOeIeVH942yunJLWDABWGbrHAW+qU= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/teivah/onecontext v0.0.0-20200513185103-40f981bfd775 h1:BLNsFR8l/hj/oGjnJXkd4Vi3s4kQD3/3x8HSAE4bzN0= +github.com/teivah/onecontext v0.0.0-20200513185103-40f981bfd775/go.mod h1:XUZ4x3oGhWfiOnUvTslnKKs39AWUct3g3yJvXTQSJOQ= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.31.3 h1:umzm5o8lFbdN/hIXbrK9oRpOproJO62CV1zqxXrLgk8= +k8s.io/api v0.31.3/go.mod h1:UJrkIp9pnMOI9K2nlL6vwpxRzzEX5sWgn8kGQe92kCE= +k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= +k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apimachinery v0.31.3 h1:6l0WhcYgasZ/wk9ktLq5vLaoXJJr5ts6lkaQzgeYPq4= +k8s.io/apimachinery v0.31.3/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4= +k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241127205056-99599406b04f h1:nLHvOvs1CZ+FAEwR4EqLeRLfbtWQNlIu5g393Hq/1UM= +k8s.io/kube-openapi v0.0.0-20241127205056-99599406b04f/go.mod h1:iZjdMQzunI7O/sUrf/5WRX1gvaAIam32lKx9+paoLbU= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4wbQ6cZIItJCSHno= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.19.2 h1:3sPrF58XQEPzbE8T81TN6selQIMGbtYwuaJ6eDssDF8= +sigs.k8s.io/controller-runtime v0.19.2/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/structured-merge-diff/v4 v4.4.3 h1:sCP7Vv3xx/CWIuTPVN38lUPx0uw0lcLfzaiDa8Ja01A= +sigs.k8s.io/structured-merge-diff/v4 v4.4.3/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/helm/.helmignore b/helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 0000000..48cc0c5 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: flink-kube-operator +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt new file mode 100644 index 0000000..8236e73 --- /dev/null +++ b/helm/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "flink-kube-operator.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "flink-kube-operator.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "flink-kube-operator.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "flink-kube-operator.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 0000000..0e2b911 --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "flink-kube-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "flink-kube-operator.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "flink-kube-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "flink-kube-operator.labels" -}} +helm.sh/chart: {{ include "flink-kube-operator.chart" . }} +{{ include "flink-kube-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "flink-kube-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "flink-kube-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "flink-kube-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "flink-kube-operator.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/templates/flink/deploy.yaml b/helm/templates/flink/deploy.yaml new file mode 100644 index 0000000..06a8a88 --- /dev/null +++ b/helm/templates/flink/deploy.yaml @@ -0,0 +1,71 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-flink # Adding the flink prefix to the name + labels: + app.kubernetes.io/name: {{ .Release.Name }}-flink # Adding the flink prefix to the labels + app.kubernetes.io/instance: {{ .Release.Name }} # Using the release name for instance + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: {{ .Release.Name }}-flink # Adding the flink prefix to the selector + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ .Release.Name }}-flink # Adding the flink prefix to the template labels + app.kubernetes.io/instance: {{ .Release.Name }} + spec: + serviceAccountName: {{ include "flink-kube-operator.serviceAccountName" . }} + + containers: + - name: flink + image: {{ .Values.flink.image.repository }}:{{ .Values.flink.image.tag }} + imagePullPolicy: Always + ports: + - containerPort: 8081 # JobManager Web UI port + - containerPort: 6121 # TaskManager communication port + - containerPort: 6122 # TaskManager communication port + env: + - name: JOB_MANAGER_RPC_ADDRESS + value: "localhost" # JobManager and TaskManager in the same container + - name: FLINK_PROPERTIES + value: | + jobmanager.rpc.address: localhost + jobmanager.memory.process.size: 2048m + taskmanager.memory.process.size: 2048m + taskmanager.data.port: 6125 + taskmanager.numberOfTaskSlots: {{ .Values.flink.taskManager.numberOfTaskSlots }} + parallelism.default: {{ .Values.flink.parallelism.default }} + state.backend: {{ .Values.flink.state.backend }} + state.savepoints.dir: {{ .Values.flink.state.savepoints.dir }} + rest.port: 8081 + rootLogger.level = DEBUG + rootLogger.appenderRef.console.ref = ConsoleAppender + web.upload.dir: /opt/flink/data/web-upload + state.checkpoints.dir: file:///tmp/flink-checkpoints + high-availability.type: kubernetes + high-availability.storageDir: file:///opt/flink/ha + kubernetes.cluster-id: cluster-one + kubernetes.namespace: {{ .Release.Namespace }} + volumeMounts: + - name: flink-data + mountPath: /opt/flink/data + subPath: data + - name: flink-data + mountPath: /opt/flink/web-upload + subPath: web-upload + - name: flink-savepoints + mountPath: /opt/flink/savepoints + - name: flink-savepoints + mountPath: /opt/flink/ha + subPath: ha + + volumes: + - name: flink-data + emptyDir: {} # Temporary storage for internal data + - name: flink-savepoints + persistentVolumeClaim: + claimName: {{ .Values.flink.state.savepoints.pvcName }} # PVC for savepoints persistence diff --git a/helm/templates/flink/pvc.yaml b/helm/templates/flink/pvc.yaml new file mode 100644 index 0000000..08cc355 --- /dev/null +++ b/helm/templates/flink/pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Values.flink.state.savepoints.pvcName }} # Adding the flink prefix to PVC name +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.flink.persistence.size }} # Use size defined in values.yaml diff --git a/helm/templates/flink/service.yaml b/helm/templates/flink/service.yaml new file mode 100644 index 0000000..5def261 --- /dev/null +++ b/helm/templates/flink/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: flink # Adding the flink prefix to the service name + labels: + app.kubernetes.io/name: {{ .Release.Name }}-flink # Adding the flink prefix to labels + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + ports: + - port: 8081 + targetPort: 8081 + selector: + app.kubernetes.io/name: {{ .Release.Name }}-flink # Adding the flink prefix to selector + app.kubernetes.io/instance: {{ .Release.Name }} + type: ClusterIP # Change to LoadBalancer if you want external access diff --git a/helm/templates/operator/deployment.yaml b/helm/templates/operator/deployment.yaml new file mode 100644 index 0000000..8466a80 --- /dev/null +++ b/helm/templates/operator/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "flink-kube-operator.fullname" . }} + labels: + {{- include "flink-kube-operator.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "flink-kube-operator.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "flink-kube-operator.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "flink-kube-operator.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + env: + - name: FLINK_API_URL + value: {{ .Values.config.flinkApiUrl }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + + + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/templates/operator/hpa.yaml b/helm/templates/operator/hpa.yaml new file mode 100644 index 0000000..3a7bba4 --- /dev/null +++ b/helm/templates/operator/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "flink-kube-operator.fullname" . }} + labels: + {{- include "flink-kube-operator.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "flink-kube-operator.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/templates/operator/ingress.yaml b/helm/templates/operator/ingress.yaml new file mode 100644 index 0000000..2036d9e --- /dev/null +++ b/helm/templates/operator/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "flink-kube-operator.fullname" . }} + labels: + {{- include "flink-kube-operator.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include "flink-kube-operator.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/templates/operator/role.yaml b/helm/templates/operator/role.yaml new file mode 100644 index 0000000..99c8d30 --- /dev/null +++ b/helm/templates/operator/role.yaml @@ -0,0 +1,50 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "flink-kube-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} # Namespace where the role is created + labels: + {{- include "flink-kube-operator.labels" . | nindent 4 }} +rules: + - apiGroups: + - flink.logicamp.tech # API group of the FlinkJob CRD + resources: + - flink-jobs # The plural name of your custom resource + verbs: + - get + - list + - create + - update + - delete + - patch + - watch + - apiGroups: [""] + resources: ["configmaps", "pods", "services"] + verbs: ["create", "get", "list", "watch", "update", "delete", "patch"] + - apiGroups: ["apps"] + resources: ["statefulsets"] + verbs: ["get", "list", "watch"] + - apiGroups: ["storage.k8s.io"] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "create", "delete"] + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list"] + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "flink-kube-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} # Namespace where the RoleBinding is created + labels: + {{- include "flink-kube-operator.labels" . | nindent 4 }} +subjects: + - kind: ServiceAccount + name: {{ include "flink-kube-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} # Ensure that the service account is in the same namespace +roleRef: + kind: Role + name: {{ include "flink-kube-operator.serviceAccountName" . }} + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/helm/templates/operator/service.yaml b/helm/templates/operator/service.yaml new file mode 100644 index 0000000..887cfb0 --- /dev/null +++ b/helm/templates/operator/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "flink-kube-operator.fullname" . }} + labels: + {{- include "flink-kube-operator.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "flink-kube-operator.selectorLabels" . | nindent 4 }} diff --git a/helm/templates/operator/serviceaccount.yaml b/helm/templates/operator/serviceaccount.yaml new file mode 100644 index 0000000..7791afe --- /dev/null +++ b/helm/templates/operator/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "flink-kube-operator.serviceAccountName" . }} + labels: + {{- include "flink-kube-operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 0000000..fade56f --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,138 @@ +# Default values for flink-kube-operator. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ +replicaCount: 1 + +# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +image: + repository: lcr.logicamp.tech/library/flink-kube-operator + # This sets the pull policy for images. + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: latest + +# This is for the secretes for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +#This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +service: + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + port: 80 + +# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +livenessProbe: + httpGet: + path: / + port: http +readinessProbe: + httpGet: + path: / + port: http + +#This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +config: + flinkApiUrl: flink:8081 + + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# Global values for the Flink deployment +flink: + image: + repository: lcr.logicamp.tech/library/flink + tag: 1.20.0-scala_2.12-java17-minicluster + + parallelism: + default: 1 # Default parallelism for Flink jobs + + state: + backend: rocksdb # Use RocksDB for state backend + savepoints: + dir: "file:///opt/flink/savepoints" # Directory to store savepoints + pvcName: flink-savepoints-pvc # PVC for savepoints persistence + + taskManager: + numberOfTaskSlots: 100 # Number of task slots for TaskManager + + persistence: + enabled: true + size: 10Gi # PVC size for savepoints storage + accessModes: + - ReadWriteOnce diff --git a/internal/config/config.type.go b/internal/config/config.type.go index a940044..a1043f3 100644 --- a/internal/config/config.type.go +++ b/internal/config/config.type.go @@ -1,15 +1,6 @@ package config -import "time" - -type JobDef struct { - Key string `yaml:"key"` - Name string `yaml:"name"` - EntryClass string `yaml:"entryClass"` - JarURI string `yaml:"jarURI"` - SavepointInterval time.Duration `yaml:"savepointInterval"` -} - type Config struct { - Jobs []JobDef `yaml:"jobs"` + FlinkApiUrl string `yaml:"flinkApiUrl"` + DatabasePath string `yaml:"databasePath"` } diff --git a/internal/crd/client/client.go b/internal/crd/client/client.go new file mode 100644 index 0000000..50c90b6 --- /dev/null +++ b/internal/crd/client/client.go @@ -0,0 +1,74 @@ +package client + +import ( + "context" + "flink-kube-operator/internal/crd/v1alpha1" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" +) + +type FlinkJobInterface interface { + List(opts metav1.ListOptions) (*v1alpha1.FlinkJobList, error) + Get(name string, options metav1.GetOptions) (*v1alpha1.FlinkJob, error) + Create(*v1alpha1.FlinkJob) (*v1alpha1.FlinkJob, error) + Watch(opts metav1.ListOptions) (watch.Interface, error) + // ... +} + +type FlinkJobClient struct { + restClient rest.Interface + ns string +} + +func (c *FlinkJobClient) List(opts metav1.ListOptions) (*v1alpha1.FlinkJobList, error) { + result := v1alpha1.FlinkJobList{} + err := c.restClient. + Get(). + Namespace(c.ns). + Resource("FlinkJobs"). + VersionedParams(&opts, scheme.ParameterCodec). + Do(context.Background()). + Into(&result) + + return &result, err +} + +func (c *FlinkJobClient) Get(name string, opts metav1.GetOptions) (*v1alpha1.FlinkJob, error) { + result := v1alpha1.FlinkJob{} + err := c.restClient. + Get(). + Namespace(c.ns). + Resource("FlinkJobs"). + Name(name). + VersionedParams(&opts, scheme.ParameterCodec). + Do(context.Background()). + Into(&result) + + return &result, err +} + +func (c *FlinkJobClient) Create(FlinkJob *v1alpha1.FlinkJob) (*v1alpha1.FlinkJob, error) { + result := v1alpha1.FlinkJob{} + err := c.restClient. + Post(). + Namespace(c.ns). + Resource("FlinkJobs"). + Body(FlinkJob). + Do(context.Background()). + Into(&result) + + return &result, err +} + +func (c *FlinkJobClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { + opts.Watch = true + return c.restClient. + Get(). + Namespace(c.ns). + Resource("FlinkJobs"). + VersionedParams(&opts, scheme.ParameterCodec). + Watch(context.Background()) +} diff --git a/internal/crd/convert.go b/internal/crd/convert.go new file mode 100644 index 0000000..eb2beff --- /dev/null +++ b/internal/crd/convert.go @@ -0,0 +1,33 @@ +package crd + +import ( + "flink-kube-operator/internal/crd/v1alpha1" + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +func convertToUnstructured(obj v1alpha1.FlinkJob) (*unstructured.Unstructured, error) { + // Convert the structured object to an unstructured map + unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj) + if err != nil { + return nil, fmt.Errorf("failed to convert to unstructured: %v", err) + } + + // Create an Unstructured object from the map + unstructuredObj := &unstructured.Unstructured{ + Object: unstructuredMap, + } + + return unstructuredObj, nil +} + +func convertFromUnstructured(in *unstructured.Unstructured) (*v1alpha1.FlinkJob, error) { + job := &v1alpha1.FlinkJob{} + err := runtime.DefaultUnstructuredConverter.FromUnstructured(in.Object, job) + if err != nil { + return nil, nil + } + return job, nil +} diff --git a/internal/crd/finalizer.go b/internal/crd/finalizer.go new file mode 100644 index 0000000..dc9f599 --- /dev/null +++ b/internal/crd/finalizer.go @@ -0,0 +1,14 @@ +package crd + +import ( + "github.com/reactivex/rxgo/v2" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (crd Crd) manageFinalizer(jobEventObservable rxgo.Observable) { + for j := range jobEventObservable.Observe() { + jobEvent := j.V.(*FlinkJobCrdEvent) + //pkg.Logger.Debug("[crd] [manage-finalizer] adding finalizer for", zap.String("name", jobEvent.Job.GetName())) + controllerutil.AddFinalizer(jobEvent.Job, "") + } +} diff --git a/internal/crd/new.go b/internal/crd/new.go new file mode 100644 index 0000000..fb8d2c1 --- /dev/null +++ b/internal/crd/new.go @@ -0,0 +1,45 @@ +package crd + +import ( + "flink-kube-operator/internal/crd/v1alpha1" + + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +type Crd struct { + client dynamic.NamespaceableResourceInterface +} + +func New() *Crd { + // Get Kubernetes config + config, err := clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile) + if err != nil { + config, err = rest.InClusterConfig() + if err != nil { + panic(err) + } + } + + // Create dynamic client + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + panic(err) + } + + // Get FlinkJob resource interface + flinkJobClient := dynamicClient.Resource(v1alpha1.FlinkJobGVR) + + crd := Crd{ + client: flinkJobClient, + } + + // Watch for FlinkJob creation + jobEventObservable := crd.watchFlinkJobs() + + // add finalizer to new resources + go crd.manageFinalizer(jobEventObservable) + + return &crd +} diff --git a/internal/crd/patch.go b/internal/crd/patch.go new file mode 100644 index 0000000..afda2e4 --- /dev/null +++ b/internal/crd/patch.go @@ -0,0 +1,62 @@ +package crd + +import ( + "context" + "encoding/json" + "fmt" + + "flink-kube-operator/pkg" + + "go.uber.org/zap" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +func (crd *Crd) Patch(jobUid types.UID, patchData map[string]interface{}) error { + job := GetJob(jobUid) + + patchBytes, err := json.Marshal(patchData) + if err != nil { + return fmt.Errorf("error marshaling patch data: %w", err) + } + + // Patch the status sub-resource + unstructuredJob, err := crd.client. + Namespace(job.GetNamespace()). + Patch( + context.Background(), + job.GetName(), + types.MergePatchType, // Use MergePatchType for JSON Merge Patch + patchBytes, + metaV1.PatchOptions{}, + ) + if err != nil { + pkg.Logger.Error( + "[crd] [status] error patching custom resource status", + zap.String("namespace", job.GetNamespace()), + zap.Error(err), + ) + return err + } + newJob, err := convertFromUnstructured(unstructuredJob) + if err != nil { + pkg.Logger.Error("[crd] [status] error in structure unstructured patched", zap.Error(err)) + } + jobs.Store(jobUid, newJob) + if err != nil { + pkg.Logger.Error("[crd] [status] ", zap.Error(err)) + return err + } + return nil +} + +func (crd Crd) PatchAll(patchData map[string]interface{}) error { + keys := GetAllJobKeys() + for _, key := range keys { + err := crd.Patch(key, patchData) + if err != nil { + return err + } + } + return nil +} diff --git a/internal/crd/repo.go b/internal/crd/repo.go new file mode 100644 index 0000000..d96c94f --- /dev/null +++ b/internal/crd/repo.go @@ -0,0 +1,29 @@ +package crd + +import ( + "flink-kube-operator/internal/crd/v1alpha1" + "flink-kube-operator/pkg" + + "k8s.io/apimachinery/pkg/types" +) + +// var jobs = map[types.UID]*v1alpha1.FlinkJob{} +var jobs = pkg.SafeMap[types.UID, *v1alpha1.FlinkJob]{} + +func (crd *Crd) repsert(job *v1alpha1.FlinkJob) { + jobs.Store(job.GetUID(), job) +} + +func GetJob(uid types.UID) v1alpha1.FlinkJob { + job, _ := jobs.Load(uid) + return *job.DeepCopy() +} + +func GetAllJobKeys() []types.UID { + keys := []types.UID{} + jobs.Range(func(k types.UID, value *v1alpha1.FlinkJob) bool { + keys = append(keys, k) + return true + }) + return keys +} diff --git a/internal/crd/status.go b/internal/crd/status.go new file mode 100644 index 0000000..791bb56 --- /dev/null +++ b/internal/crd/status.go @@ -0,0 +1,15 @@ +package crd + +import ( + "flink-kube-operator/internal/crd/v1alpha1" + + "k8s.io/apimachinery/pkg/types" +) + +func (crd Crd) SetJobStatus(jobUid types.UID, status v1alpha1.FlinkJobStatus) error { + // Define the patch data (JSON Merge Patch format) + patchData := map[string]interface{}{ + "status": status, + } + return crd.Patch(jobUid, patchData) +} diff --git a/internal/crd/types.go b/internal/crd/types.go new file mode 100644 index 0000000..78064df --- /dev/null +++ b/internal/crd/types.go @@ -0,0 +1,12 @@ +package crd + +import ( + "flink-kube-operator/internal/crd/v1alpha1" + + "k8s.io/apimachinery/pkg/watch" +) + +type FlinkJobCrdEvent struct { + EventType watch.EventType + Job *v1alpha1.FlinkJob +} diff --git a/internal/crd/v1alpha1/flink_job.go b/internal/crd/v1alpha1/flink_job.go new file mode 100644 index 0000000..54acb62 --- /dev/null +++ b/internal/crd/v1alpha1/flink_job.go @@ -0,0 +1,87 @@ +package v1alpha1 + +import ( + "errors" + "time" + + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen object paths=$GOFILE + +type FlinkJobSpec struct { + Key string `json:"key"` + Name string `json:"name"` + Parallelism int `json:"parallelism"` + JarURI string `json:"jarUri"` + SavepointInterval metaV1.Duration `json:"savepointInterval"` + EntryClass string `json:"entryClass"` +} + +type FlinkJobStatus struct { + JobStatus JobStatus `json:"jobStatus,omitempty"` + LifeCycleStatus LifeCycleStatus `json:"lifeCycleStatus,omitempty"` + LastSavepointPath *string `json:"lastSavepointPath,omitempty"` + JarId *string `json:"jarId,omitempty"` + JobId *string `json:"jobId,omitempty"` + Error *string `json:"error,omitempty"` + SavepointTriggerId *string `json:"savepointTriggerId,omitempty"` + PauseSavepointTriggerId *string `json:"pauseSavepointTriggerId,omitempty"` + LastSavepointDate *time.Time `json:"lastSavepointDate,omitempty"` + LastRestoredSavepointDate *time.Time `json:"lastRestoredSavepointDate,omitempty"` + LastRestoredSavepointRestoredDate *time.Time `json:"lastRestoredSavepointRestoredDate,omitempty"` + RestoredCount int `json:"restoredCount,omitempty"` + RunningJarURI *string `json:"runningJarURI"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type FlinkJob struct { + metaV1.TypeMeta `json:",inline"` + metaV1.ObjectMeta `json:"metadata,omitempty"` + Spec FlinkJobSpec `json:"spec"` + Status FlinkJobStatus `json:"status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type FlinkJobList struct { + metaV1.TypeMeta `json:",inline"` + metaV1.ListMeta `json:"metadata,omitempty"` + Items []FlinkJob `json:"items"` +} + +var ( + ErrNoJobId = errors.New("[managed-job] no job id") + ErrNoJarId = errors.New("[managed-job] no jar id") + ErrNoSavepointTriggerId = errors.New("[managed-job] no savepoint trigger id") + ErrNoSavepointPath = errors.New("[managed-job] no savepoint path") +) + +type JobStatus string + +var ( + JobStatusInitializing JobStatus = "INITIALIZING" + JobStatusRunning JobStatus = "RUNNING" + JobStatusCreating JobStatus = "CREATING" + JobStatusError JobStatus = "ERROR" + JobStatusReconciling JobStatus = "RECONCILING" + JobStatusFailed JobStatus = "FAILED" + JobStatusFailing JobStatus = "FAILING" + JobStatusRestarting JobStatus = "RESTARTING" + JobStatusFinished JobStatus = "FINISHED" + JobStatusCanceled JobStatus = "CANCELED" + JobStatusCancelling JobStatus = "CANCELLING" + JobStatusSuspended JobStatus = "SUSPENDED" +) + +type LifeCycleStatus string + +const ( + LifeCycleStatusInitializing LifeCycleStatus = "INITIALIZING" + LifeCycleStatusRestoring LifeCycleStatus = "RESTORING" + LifeCycleStatusGracefulStopFailed LifeCycleStatus = "GRACEFUL_STOP_FAILED" + LifeCycleStatusUpgradeFailed LifeCycleStatus = "UPGRADE_FAILED" + LifeCycleStatusGracefullyPaused LifeCycleStatus = "GRACEFULLY_PAUSED" + LifeCycleStatusUnhealthyJobManager LifeCycleStatus = "UNHEALTHY_JOB_MANAGER" + LifeCycleStatusHealthy LifeCycleStatus = "HEALTHY" + LifeCycleStatusFailed LifeCycleStatus = "FAILED" +) diff --git a/internal/crd/v1alpha1/register.go b/internal/crd/v1alpha1/register.go new file mode 100644 index 0000000..68de54d --- /dev/null +++ b/internal/crd/v1alpha1/register.go @@ -0,0 +1,35 @@ +package v1alpha1 + +import ( + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const GroupName = "flink.logicamp.tech" +const GroupVersion = "v1alpha1" +const ResourceName = "flink-jobs" + +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion} + +// Define the FlinkJob resource GVR (Group, Version, Resource) +var FlinkJobGVR = schema.GroupVersionResource{ + Group: GroupName, + Version: GroupVersion, + Resource: ResourceName, +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &FlinkJob{}, + &FlinkJobList{}, + ) + + metaV1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/internal/crd/v1alpha1/zz_generated.deepcopy.go b/internal/crd/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..7e1529e --- /dev/null +++ b/internal/crd/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,67 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlinkJob) DeepCopyInto(out *FlinkJob) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlinkJob. +func (in *FlinkJob) DeepCopy() *FlinkJob { + if in == nil { + return nil + } + out := new(FlinkJob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FlinkJob) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlinkJobList) DeepCopyInto(out *FlinkJobList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FlinkJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlinkJobList. +func (in *FlinkJobList) DeepCopy() *FlinkJobList { + if in == nil { + return nil + } + out := new(FlinkJobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FlinkJobList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/internal/crd/watch.go b/internal/crd/watch.go new file mode 100644 index 0000000..fd4e7b9 --- /dev/null +++ b/internal/crd/watch.go @@ -0,0 +1,65 @@ +package crd + +import ( + "context" + "flink-kube-operator/internal/crd/v1alpha1" + + "flink-kube-operator/pkg" + + "github.com/reactivex/rxgo/v2" + "go.uber.org/zap" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" +) + +func (crd Crd) watchFlinkJobs() rxgo.Observable { + + ch := make(chan rxgo.Item) + + go func() { + pkg.Logger.Debug("[crd] starting watch") + watcher, err := crd.client.Watch(context.Background(), metaV1.ListOptions{}) + if err != nil { + panic(err) + } + defer watcher.Stop() + for event := range watcher.ResultChan() { + pkg.Logger.Debug("[crd] event received", zap.Any("type", event.Type)) + unstructuredJob := event.Object.(*unstructured.Unstructured) + unstructuredMap, _, err := unstructured.NestedMap(unstructuredJob.Object) + if err != nil { + pkg.Logger.Error("cannot create unstructured map", zap.Error(err)) + continue + } + job := &v1alpha1.FlinkJob{} + + err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredMap, job) + if err != nil { + pkg.Logger.Error("cannot convert unstructured to structured", zap.Error(err)) + continue + } + + ch <- rxgo.Item{ + V: &FlinkJobCrdEvent{ + EventType: event.Type, + Job: job, + }, + } + switch event.Type { + case watch.Bookmark: + case watch.Modified: + pkg.Logger.Info("[crd] [watch] flink job modified", zap.String("jobName", job.GetName())) + crd.repsert(job) + case watch.Added: + pkg.Logger.Info("[crd] [watch] new flink job created") + crd.repsert(job) + case watch.Deleted: + } + } + + }() + + return rxgo.FromChannel(ch) +} diff --git a/internal/jar/jar.go b/internal/jar/jar.go index 0211e06..438624b 100644 --- a/internal/jar/jar.go +++ b/internal/jar/jar.go @@ -7,7 +7,8 @@ import ( "os" "strings" - "gitea.com/logicamp/lc" + "flink-kube-operator/pkg" + api "github.com/logi-camp/go-flink-client" gonanoid "github.com/matoous/go-nanoid/v2" "go.uber.org/zap" @@ -29,23 +30,24 @@ func NewJarFile(URI string) (*JarFile, error) { return jarFile, nil } -func (JarFile JarFile) Upload(flinkClient *api.Client) (fileName string, err error) { +func (jarFile *JarFile) Upload(flinkClient *api.Client) (fileName string, err error) { - resp, err := flinkClient.UploadJar(JarFile.filePath) + resp, err := flinkClient.UploadJar(jarFile.filePath) if err != nil { - lc.Logger.Error("[main] error uploading jar", zap.Error(err)) + pkg.Logger.Error("[main] error uploading jar", zap.Error(err)) } filePathParts := strings.Split(resp.FileName, "/") fileName = filePathParts[len(filePathParts)-1] if resp.Status != "success" { err = errors.New("jar upload was not success") } + jarFile.delete() return } func (jarFile *JarFile) Download() error { fileName, _ := gonanoid.New() - jarFile.filePath = fileName + ".jar" + jarFile.filePath = "/tmp/" + fileName + ".jar" out, err := os.Create(jarFile.filePath) if err != nil { return err @@ -53,7 +55,8 @@ func (jarFile *JarFile) Download() error { defer out.Close() resp, err := http.Get(jarFile.uri) - if err != nil { + if err != nil || resp.StatusCode > 299 { + jarFile.delete() return err } @@ -65,6 +68,11 @@ func (jarFile *JarFile) Download() error { return nil } -func (jarFile JarFile) Delete() error { - return os.Remove(jarFile.filePath) +func (jarFile *JarFile) delete() error { + pkg.Logger.Info("[jar] [delete]", zap.String("path", jarFile.filePath)) + err := os.Remove(jarFile.filePath) + if err != nil { + pkg.Logger.Error("[jar] [delete]", zap.Error(err)) + } + return err } diff --git a/internal/managed_job/cycle.go b/internal/managed_job/cycle.go index 765adb1..0aa3448 100644 --- a/internal/managed_job/cycle.go +++ b/internal/managed_job/cycle.go @@ -1,71 +1,54 @@ package managed_job import ( - "errors" + "flink-kube-operator/internal/crd/v1alpha1" "time" - "gitea.com/logicamp/lc" + "flink-kube-operator/pkg" + "go.uber.org/zap" ) -func (job *ManagedJob) startCycle() { - ticker := time.NewTicker(5 * time.Second) - quit := make(chan struct{}) - - // load job state from db - job.loadState() - go func() { - for { - select { - case <-ticker.C: - job.cycle() - case <-quit: - ticker.Stop() - return - } - } - }() -} - -func (job *ManagedJob) cycle() { - lc.Logger.Debug("[managed-job] [new] check cycle", zap.String("jobKey", job.def.Key)) +func (job *ManagedJob) Cycle() { + pkg.Logger.Debug("[managed-job] [new] check cycle", zap.String("jobName", job.def.GetName())) // Init job - if job.state == nil { - err := job.upload() - if err != nil { - job.setError("[upload-error] " + err.Error()) - return - } - err = job.run() - if err != nil { - job.setError("[run-error] " + err.Error()) - return - } - return - } - // Check for set running or error state - if job.state.Status == JobStatusCreating { - err := job.checkStatus() - if errors.Is(err, ErrNoJobId) { - job.state = nil - } + if job.def.Status.LifeCycleStatus == "" && job.def.Status.JobStatus == "" { + job.run(false) return } - if job.state.Status == JobStatusRunning { - err := job.checkStatus() - if errors.Is(err, ErrNoJobId) { - job.state = nil - } - if job.state.LastSavepointDate == nil || time.Now().Add(-job.def.SavepointInterval).After(*job.state.LastSavepointDate) { - if job.state.SavepointTriggerId == nil { + if job.def.Status.JobStatus == v1alpha1.JobStatusFinished && job.def.Status.LifeCycleStatus == v1alpha1.LifeCycleStatusGracefullyPaused { + job.run(true) + return + } + + if job.def.Status.JobStatus == v1alpha1.JobStatusRunning { + if (job.def.Spec.SavepointInterval.Duration != 0) && ((job.def.Status.LastSavepointDate == nil) || time.Now().Add(-job.def.Spec.SavepointInterval.Duration).After(*job.def.Status.LastSavepointDate)) { + if job.def.Status.SavepointTriggerId == nil { job.createSavepoint() } else { job.trackSavepoint() } } + if job.def.Status.RunningJarURI != nil && job.def.Spec.JarURI != *job.def.Status.RunningJarURI { + job.upgrade() + } + return } + if job.def.Status.JobStatus == v1alpha1.JobStatusCreating { + return + } + // if job.def.Status.JobStatus == v1alpha1.JobStatusFailed && job.def.Status.LastSavepointPath != nil { + // //job.restore() + // return + // } + // if job.def.Status.JobStatus == v1alpha1.JobStatusFailed && job.def.Status.LastSavepointPath == nil { + // //job.restore() + // return + // } + + pkg.Logger.Warn("[managed-job] [cycle]", zap.String("unhanded job status", string(job.def.Status.JobStatus))) } diff --git a/internal/managed_job/new.go b/internal/managed_job/new.go index 1a47f65..31a5e62 100644 --- a/internal/managed_job/new.go +++ b/internal/managed_job/new.go @@ -1,26 +1,28 @@ package managed_job import ( - "flink-kube-operator/internal/config" + "flink-kube-operator/internal/crd" + "flink-kube-operator/internal/crd/v1alpha1" - "github.com/dgraph-io/badger/v4" api "github.com/logi-camp/go-flink-client" ) type ManagedJob struct { - def config.JobDef + def v1alpha1.FlinkJob client *api.Client - jarId string - db *badger.DB - state *jobState + crd *crd.Crd } -func NewManagedJob(client *api.Client, db *badger.DB, def config.JobDef) *ManagedJob { +func NewManagedJob(client *api.Client, def v1alpha1.FlinkJob, crd *crd.Crd) *ManagedJob { job := &ManagedJob{ def: def, client: client, - db: db, + crd: crd, } - job.startCycle() + //job.startCycle() return job } + +func (job *ManagedJob) Update(def v1alpha1.FlinkJob) { + job.def = def +} diff --git a/internal/managed_job/pause.go b/internal/managed_job/pause.go new file mode 100644 index 0000000..89c699b --- /dev/null +++ b/internal/managed_job/pause.go @@ -0,0 +1,71 @@ +package managed_job + +import ( + "flink-kube-operator/internal/crd/v1alpha1" + "flink-kube-operator/pkg" + "os" + "time" + + api "github.com/logi-camp/go-flink-client" + "go.uber.org/zap" +) + +func (job *ManagedJob) pause() error { + var err error + if job.def.Status.JobId != nil { + result, stopJobErr := job.client.StopJobWithSavepoint(*job.def.Status.JobId, os.Getenv("SAVEPOINT_PATH"), false) + if stopJobErr != nil { + err = stopJobErr + pkg.Logger.Error("[managed-job] [pause] cannot stop job", zap.Error(err)) + return err + } + var savepointPath string + for { + trackResult, err := job.client.TrackSavepoint(*job.def.Status.JobId, result.RequestID) + time.Sleep(time.Millisecond * 500) + if err == nil && trackResult.Status.Id == api.SavepointStatusInCompleted { + if trackResult.Operation.Location != "" { + savepointPath = trackResult.Operation.Location + } + break + } + } + if savepointPath != "" { + job.def.Status.LastSavepointPath = &savepointPath + job.def.Status.PauseSavepointTriggerId = nil + job.def.Status.JobStatus = "" + job.def.Status.LastSavepointPath = &savepointPath + lastSavepointDate := time.Now() + job.def.Status.LastSavepointDate = &lastSavepointDate + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "jobStatus": "FINISHED", + "lifeCycleStatus": v1alpha1.LifeCycleStatusGracefullyPaused, + "savepointTriggerId": nil, + "lastSavepointPath": savepointPath, + "lastSavepointDate": lastSavepointDate.Format(time.RFC3339), + }, + }) + pkg.Logger.Info( + "[managed-job] job paused successfully", + zap.String("jobName", job.def.GetName()), + zap.String("savepointPath", savepointPath), + ) + } else { + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "lifeCycleStatus": v1alpha1.LifeCycleStatusGracefulStopFailed, + "lastSavepointPath": savepointPath, + "lastSavepointDate": time.Now().Format(time.RFC3339), + }, + }) + pkg.Logger.Error( + "[managed-job] error in pausing job", + zap.Error(err), + ) + + return err + } + } + return nil +} diff --git a/internal/managed_job/remove.go b/internal/managed_job/remove.go new file mode 100644 index 0000000..4f10465 --- /dev/null +++ b/internal/managed_job/remove.go @@ -0,0 +1,5 @@ +package managed_job + +func (job *ManagedJob) Stop() { + job.client.StopJob(*job.def.Status.JobId) +} diff --git a/internal/managed_job/run.go b/internal/managed_job/run.go index 0dbbf3a..01adae7 100644 --- a/internal/managed_job/run.go +++ b/internal/managed_job/run.go @@ -1,46 +1,91 @@ package managed_job import ( - "flink-kube-operator/internal/jar" + "flink-kube-operator/internal/crd/v1alpha1" + "strings" + "time" + + "flink-kube-operator/pkg" - "gitea.com/logicamp/lc" api "github.com/logi-camp/go-flink-client" "go.uber.org/zap" ) -// upload jar file and set the jarId for later usages -func (job *ManagedJob) upload() error { - jarFile, err := jar.NewJarFile(job.def.JarURI) - if err != nil { - lc.Logger.Debug("[main] error on download jar", zap.Error(err)) - return err +// run the job from savepoint and jarId in managedJob +func (job *ManagedJob) run(restoreMode bool) error { + var savepointPath string + if job.def.Status.LastSavepointPath == nil { + pkg.Logger.Error("[managed-job] [restore]", zap.Error(v1alpha1.ErrNoSavepointPath)) + if restoreMode { + return v1alpha1.ErrNoSavepointPath + } + } else { + savepointPath = *job.def.Status.LastSavepointPath } - fileName, err := jarFile.Upload(job.client) - jarFile.Delete() - if err != nil { - lc.Logger.Debug("[main] error on upload jar", zap.Error(err)) - return err - } - lc.Logger.Debug("[main] after upload jar", zap.Any("upload-jar-resp", fileName)) - job.jarId = fileName + pkg.Logger.Info( + "[managed-job] [restore] starting job...", + zap.Bool("restoreMode", restoreMode), + zap.String("name", job.def.GetName()), + zap.String("savepointPath", savepointPath), + ) + var jobId *string + for { + shouldUpload := false + if job.def.Status.JarId == nil { + err := v1alpha1.ErrNoJarId + pkg.Logger.Warn("[managed-job] [run] will upload new jar...", zap.Error(err)) + shouldUpload = true + } else { + runJarResp, err := job.client.RunJar(api.RunOpts{ + JarID: *job.def.Status.JarId, + AllowNonRestoredState: true, + EntryClass: job.def.Spec.EntryClass, + SavepointPath: savepointPath, + }) + if err == nil { + pkg.Logger.Info("[managed-job] [run] jar successfully ran", zap.Any("run-jar-resp", runJarResp)) + jobId = &runJarResp.JobId + break + } else { + if strings.ContainsAny(err.Error(), ".jar does not exist") { + shouldUpload = true + } else { + pkg.Logger.Error("[managed-job] [run] unhandled jar run Flink error", zap.Error(err)) + } + } + } + + if shouldUpload { + err := job.upload() + if err != nil { + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "error": "[upload-error] " + err.Error(), + }, + }) + return nil + } + continue + } + return nil + } + + // job.def.Status.JobId = &runJarResp.JobId + // job.def.Status.JobStatus = v1alpha1.JobStatusCreating + // job.def.Status.Error = nil + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "jobId": jobId, + "runningJarURI": job.def.Spec.JarURI, + "jobStatus": v1alpha1.JobStatusCreating, + "lifeCycleStatus": v1alpha1.LifeCycleStatusRestoring, + "lastRestoredSavepointDate": job.def.Status.LastSavepointDate, + "restoredCount": job.def.Status.RestoredCount + 1, + "lastRestoredSavepointRestoredDate": time.Now().Format(time.RFC3339), + "error": nil, + }, + }) + return nil } - -// run the job from saved jarId in managedJob -func (job *ManagedJob) run() error { - runJarResp, err := job.client.RunJar(api.RunOpts{ - JarID: job.jarId, - AllowNonRestoredState: true, - EntryClass: job.def.EntryClass, - }) - if err != nil { - lc.Logger.Error("[managed-job] [run]", zap.Error(err)) - return err - } - lc.Logger.Debug("[main] after run jar", zap.Any("run-jar-resp", runJarResp)) - - job.updateState(jobState{JobId: &runJarResp.JobId, Status: JobStatusCreating}) - - return err -} diff --git a/internal/managed_job/savepoint.go b/internal/managed_job/savepoint.go index ba9c799..bc4beac 100644 --- a/internal/managed_job/savepoint.go +++ b/internal/managed_job/savepoint.go @@ -1,46 +1,81 @@ package managed_job import ( + "flink-kube-operator/internal/crd/v1alpha1" + "os" "strings" + "time" + + "flink-kube-operator/pkg" - "gitea.com/logicamp/lc" api "github.com/logi-camp/go-flink-client" "go.uber.org/zap" ) func (job ManagedJob) createSavepoint() error { - if job.state.JobId == nil { - lc.Logger.Debug("[managed-job] [savepoint] no job id") - return ErrNoJobId + if job.def.Status.JobId == nil { + pkg.Logger.Debug("[managed-job] [savepoint] no job id") + return v1alpha1.ErrNoJobId } - resp, err := job.client.SavePoints(*job.state.JobId, "/flink-data/savepoints-2/", false) + pkg.Logger.Info("[managed-job] [savepoint] creating savepoint", zap.String("interval", job.def.Spec.SavepointInterval.String())) + resp, err := job.client.SavePoints(*job.def.Status.JobId, os.Getenv("SAVEPOINT_PATH"), false) if err != nil { - lc.Logger.Error("[managed-job] [savepoint] error in creating savepoint", zap.Error(err)) + pkg.Logger.Error("[managed-job] [savepoint] error in creating savepoint", zap.Error(err)) return err } - lc.Logger.Debug("[managed-job] [savepoint]", zap.Any("savepoint-resp", resp)) - job.setSavepointTriggerId(resp.RequestID) + pkg.Logger.Debug("[managed-job] [savepoint] savepoint created successfully", zap.String("trigger-id", resp.RequestID)) + + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "savepointTriggerId": resp.RequestID, + }, + }) return nil } func (job ManagedJob) trackSavepoint() error { - if job.state.JobId == nil { - lc.Logger.Debug("[managed-job] [savepoint] no job id") - return ErrNoJobId + if job.def.Status.JobId == nil { + pkg.Logger.Debug("[managed-job] [savepoint] no job id") + return v1alpha1.ErrNoJobId } - if job.state.SavepointTriggerId == nil { - lc.Logger.Debug("[managed-job] [savepoint] no job id") - return ErrNoSavepointTriggerId + if job.def.Status.SavepointTriggerId == nil { + pkg.Logger.Debug("[managed-job] [savepoint] no job id") + return v1alpha1.ErrNoSavepointTriggerId } - resp, err := job.client.TrackSavepoint(*job.state.JobId, *job.state.SavepointTriggerId) - lc.Logger.Debug("[managed-job] [savepoint] track savepoint", zap.Any("resp", resp), zap.Error(err)) - if err != nil { - if strings.IndexAny(err.Error(), "http status not 2xx: 404") == 0 { - job.removeSavepointTriggerId() + resp, err := job.client.TrackSavepoint(*job.def.Status.JobId, *job.def.Status.SavepointTriggerId) + pkg.Logger.Info("[managed-job] [savepoint] savepoint track result", + zap.Any("status.Id", resp.Status.Id), + zap.Any("path", resp.Operation.Location), + zap.Any("failureCause.stacktrace", resp.Operation.FailureCause.StackTrace), + zap.Any("failureCause.class", resp.Operation.FailureCause.Class), + zap.Error(err), + ) + if err != nil || resp.Operation.FailureCause.Class != "" { + if err != nil { + if strings.IndexAny(err.Error(), "http status not 2xx: 404") == 0 { + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "savepointTriggerId": nil, + }, + }) + } + } else { + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "error": resp.Operation.FailureCause.StackTrace, + }, + }) + } + } else { + if resp.Status.Id == api.SavepointStatusInCompleted { + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "savepointTriggerId": nil, + "lastSavepointPath": resp.Operation.Location, + "lastSavepointDate": time.Now().Format(time.RFC3339), + }, + }) } - } - if resp.Status.Id == api.SavepointStatusInCompleted { - job.setSavepointLocation(resp.Operation.Location) } return nil diff --git a/internal/managed_job/state.go b/internal/managed_job/state.go index 583eb41..ed385fc 100644 --- a/internal/managed_job/state.go +++ b/internal/managed_job/state.go @@ -1,71 +1,78 @@ package managed_job -import ( - "encoding/json" - "errors" - "time" +// import ( +// "encoding/json" +// "flink-kube-operator/internal/crd/v1alpha1" +// "time" - "github.com/dgraph-io/badger/v4" -) +// "github.com/tidwall/buntdb" +// ) -// get state of job from local db -func (job *ManagedJob) loadState() { - err := job.db.View( - func(tx *badger.Txn) error { - if val, err := tx.Get([]byte(job.def.Key)); err != nil { - return err - } else if val != nil { - val.Value(func(val []byte) error { - job.state = &jobState{} - return json.Unmarshal(val, job.state) - }) - } - return nil - }) - if errors.Is(err, badger.ErrKeyNotFound) { - err = nil +// // get state of job from local db +// func (job *ManagedJob) loadState() { +// job.db.View( +// func(tx *buntdb.Tx) error { +// if val, err := tx.Get(string(job.def.GetUID())); err != nil { +// return err +// } else { +// return json.Unmarshal([]byte(val), job.state) +// } +// }) +// } + +// // save state of job to local db +// func (job *ManagedJob) updateState(state jobState) { +// job.state = &state + +// value, _ := json.Marshal(job.state) +// job.db.Update(func(tx *buntdb.Tx) error { +// _, _, err := tx.Set(string(job.def.GetUID()), string(value), nil) +// return err +// }) +// } + +// func (job *ManagedJob) setError(errMsg string) { +// if job.state == nil { +// job.state = &jobState{} +// } +// job.state.Error = &errMsg +// job.state.Status = v1alpha1.JobStatusError +// job.updateState(*job.state) +// job.crd.SetJobStatus(job.def.UID, v1alpha1.FlinkJobStatus{ +// JobStatus: job.state.Status, +// }) +// } + +// func (job *ManagedJob) setSavepointLocation(savepointId string) { +// job.state.LastSavepointPath = &savepointId +// job.state.SavepointTriggerId = nil +// n := time.Now() +// job.state.LastSavepointDate = &n +// job.updateState(*job.state) +// job.crd.SetJobStatus(job.def.UID, v1alpha1.FlinkJobStatus{ +// LastSavepointPath: job.state.LastSavepointPath, +// }) +// } + +// func (job *ManagedJob) setSavepointTriggerId(savepointReqId string) { +// job.state.SavepointTriggerId = &savepointReqId +// job.updateState(*job.state) +// } + +// func (job *ManagedJob) removeSavepointTriggerId() { +// job.state.SavepointTriggerId = nil +// job.updateState(*job.state) +// } + +// func (job *ManagedJob) SetStatus(status JobStatus) { +// job.state.Status = status +// job.updateState(*job.state) +// } + +func (job *ManagedJob) GetJobId() *string { + if job.def.Status.JobId != nil { + return job.def.Status.JobId + } else { + return nil } } - -// save state of job to local db -func (job *ManagedJob) updateState(state jobState) { - job.state = &state - - value, _ := json.Marshal(job.state) - job.db.Update(func(txn *badger.Txn) error { - err := txn.Set([]byte(job.def.Key), value) - if err != nil { - return err - } - return txn.Commit() - }) -} - -func (job *ManagedJob) setError(errMsg string) { - job.state.Error = &errMsg - job.state.Status = JobStatusError - job.updateState(*job.state) -} - -func (job *ManagedJob) setSavepointLocation(savepointId string) { - job.state.LastSavepointLocation = &savepointId - job.state.SavepointTriggerId = nil - n := time.Now() - job.state.LastSavepointDate = &n - job.updateState(*job.state) -} - -func (job *ManagedJob) setSavepointTriggerId(savepointReqId string) { - job.state.SavepointTriggerId = &savepointReqId - job.updateState(*job.state) -} - -func (job *ManagedJob) removeSavepointTriggerId() { - job.state.SavepointTriggerId = nil - job.updateState(*job.state) -} - -func (job *ManagedJob) setStatus(status JobStatus) { - job.state.Status = status - job.updateState(*job.state) -} diff --git a/internal/managed_job/status.go b/internal/managed_job/status.go index 274cad6..dcd25bb 100644 --- a/internal/managed_job/status.go +++ b/internal/managed_job/status.go @@ -1,29 +1,45 @@ package managed_job import ( + "flink-kube-operator/internal/crd/v1alpha1" "strings" - "gitea.com/logicamp/lc" + "flink-kube-operator/pkg" + "go.uber.org/zap" ) func (job *ManagedJob) checkStatus() error { - if job.state.JobId == nil { - lc.Logger.Debug("[managed-job] [status] no job id") - return ErrNoJobId + if job.def.Status.JobId == nil { + pkg.Logger.Debug("[managed-job] [status] no job id") + return v1alpha1.ErrNoJobId } - statusResp, err := job.client.Job(*job.state.JobId) + statusResp, err := job.client.Job(*job.def.Status.JobId) if err != nil { - lc.Logger.Debug("[managed-job] [status] cannot fetch status", zap.Error(err)) + pkg.Logger.Debug("[managed-job] [status] cannot fetch status", zap.Error(err)) if strings.IndexAny(err.Error(), "http status not 2xx: 404") == 0 { - job.updateState(jobState{ - JobId: job.state.JobId, - Status: JobStatusNotFound, + // job.updateState(jobState{ + // JobId: job.state.JobId, + // Status: v1alpha1.JobStatusNotFound, + // }) + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "jobId": &job.def.Status.JobId, + "jobStatus": "", + "lifeCycleStatus": v1alpha1.LifeCycleStatusFailed, + "error": "Job not found", + }, }) } return err } - //lc.Logger.Debug("[managed-job] [status]", zap.Any("status-resp", statusResp)) - job.setStatus(JobStatus(statusResp.State)) + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "jobId": &job.def.Status.JobId, + "jobStatus": statusResp.State, + "lifeCycleStatus": v1alpha1.LifeCycleStatusFailed, + "error": "Job not found", + }, + }) return err } diff --git a/internal/managed_job/type.go b/internal/managed_job/type.go index fa9c0b9..3a59ac5 100644 --- a/internal/managed_job/type.go +++ b/internal/managed_job/type.go @@ -1,31 +1,16 @@ package managed_job import ( - "errors" + "flink-kube-operator/internal/crd/v1alpha1" "time" ) -type JobStatus string - -var ( - ErrNoJobId = errors.New("[managed-job] no job id") - ErrNoSavepointTriggerId = errors.New("[managed-job] no savepoint trigger id") -) - -const ( - JobStatusRunning JobStatus = "RUNNING" - JobStatusCreating JobStatus = "CREATING" - JobStatusNotFound JobStatus = "NotFound" - JobStatusError JobStatus = "ERROR" - JobStatusReconciling JobStatus = "RECONCILING" -) - type jobState struct { - Status JobStatus `json:"status"` - Error *string `json:"error"` - Info *string `json:"info"` - JobId *string `json:"job_id"` - LastSavepointLocation *string `json:"last_savepoint_location"` - SavepointTriggerId *string `json:"savepoint_trigger_id"` - LastSavepointDate *time.Time `json:"last_savepoint_time"` + Status v1alpha1.JobStatus `json:"status"` + Error *string `json:"error"` + Info *string `json:"info"` + JobId *string `json:"job_id"` + LastSavepointPath *string `json:"last_savepoint_location"` + SavepointTriggerId *string `json:"savepoint_trigger_id"` + LastSavepointDate *time.Time `json:"last_savepoint_time"` } diff --git a/internal/managed_job/upgrade.go b/internal/managed_job/upgrade.go new file mode 100644 index 0000000..08f5873 --- /dev/null +++ b/internal/managed_job/upgrade.go @@ -0,0 +1,38 @@ +package managed_job + +import ( + "flink-kube-operator/pkg" + + "go.uber.org/zap" +) + +func (job *ManagedJob) upgrade() { + pkg.Logger.Info("[managed-job] [upgrade] pausing... ", + zap.String("jobName", job.def.GetName()), + zap.String("currentJarURI", job.def.Spec.JarURI), + zap.String("prevJarURI", *job.def.Status.RunningJarURI), + ) + job.def.Status.JarId = nil + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "jarId": job.def.Status.JarId, + }, + }) + err := job.pause() + if err != nil { + pkg.Logger.Error("[managed-job] [upgrade] error in pausing", zap.Error(err)) + return + } + pkg.Logger.Info("[managed-job] [upgrade] restoring... ", + zap.String("jobName", job.def.GetName()), + zap.String("currentJarURI", job.def.Spec.JarURI), + zap.String("prevJarURI", *job.def.Status.RunningJarURI), + zap.Error(err), + ) + + err = job.run(true) + if err != nil { + pkg.Logger.Error("[managed-job] [upgrade] error in running", zap.Error(err)) + return + } +} diff --git a/internal/managed_job/upload.go b/internal/managed_job/upload.go new file mode 100644 index 0000000..7f3097a --- /dev/null +++ b/internal/managed_job/upload.go @@ -0,0 +1,32 @@ +package managed_job + +import ( + "flink-kube-operator/internal/jar" + + "flink-kube-operator/pkg" + + "go.uber.org/zap" +) + +// upload jar file and set the jarId for later usages +func (job *ManagedJob) upload() error { + jarFile, err := jar.NewJarFile(job.def.Spec.JarURI) + if err != nil { + pkg.Logger.Debug("[manage-job] [upload] error on 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)) + return err + } + pkg.Logger.Info("[manage-job] [upload] uploaded", zap.Any("upload-jar-resp", jarId)) + + job.def.Status.JarId = &jarId + job.crd.Patch(job.def.UID, map[string]interface{}{ + "status": map[string]interface{}{ + "jarId": job.def.Status.JarId, + }, + }) + return nil +} diff --git a/internal/manager/manager.go b/internal/manager/manager.go new file mode 100644 index 0000000..2eeccf2 --- /dev/null +++ b/internal/manager/manager.go @@ -0,0 +1,111 @@ +package manager + +import ( + "flink-kube-operator/internal/crd" + "flink-kube-operator/internal/crd/v1alpha1" + "flink-kube-operator/internal/managed_job" + "time" + + "flink-kube-operator/pkg" + + api "github.com/logi-camp/go-flink-client" + "github.com/samber/lo" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/types" +) + +type Manager struct { + client *api.Client + managedJobs map[types.UID]managed_job.ManagedJob + processingJobsIds []types.UID +} + +func NewManager(client *api.Client, crdInstance *crd.Crd) Manager { + ticker := time.NewTicker(5 * time.Second) + quit := make(chan struct{}) + mgr := Manager{ + client: client, + managedJobs: map[types.UID]managed_job.ManagedJob{}, + processingJobsIds: []types.UID{}, + } + + mgr.cycle(client, crdInstance) + go func() { + for { + select { + case <-ticker.C: + mgr.cycle(client, crdInstance) + case <-quit: + ticker.Stop() + return + } + } + }() + return mgr +} + +func (mgr *Manager) cycle(client *api.Client, crdInstance *crd.Crd) { + jobManagerJobOverviews, jobManagerJobStatusError := mgr.client.JobsOverview() + if jobManagerJobStatusError != nil { + pkg.Logger.Error("[manager] [cycle] cannot check flink jobs status", zap.Error(jobManagerJobStatusError)) + crdInstance.PatchAll(map[string]interface{}{ + "status": map[string]interface{}{ + "lifeCycleStatus": v1alpha1.LifeCycleStatusUnhealthyJobManager, + }, + }) + } + //pkg.Logger.Debug("[manager] [cycle] overviews", zap.Any("overviews", jobsOverviews)) + + // Loop over job definitions as Kubernetes CRD + for _, uid := range crd.GetAllJobKeys() { + if lo.Contains(mgr.processingJobsIds, uid) { + pkg.Logger.Warn("[manager] already in process", zap.Any("uid", uid)) + continue + } + + // Get job definition from Kubernetes CRD + def := crd.GetJob(uid) + mgr.processingJobsIds = append(mgr.processingJobsIds, uid) + + // Check if job exists in manager managed jobs + managedJob, ok := mgr.managedJobs[uid] + if ok { + managedJob.Update(def) + } else { + // Add job to manager managed job + managedJob = *managed_job.NewManagedJob(client, def, crdInstance) + } + if jobManagerJobStatusError != nil { + + } + + jobManagerJobOverview, ok := lo.Find(jobManagerJobOverviews.Jobs, func(job api.JobOverview) bool { + jobId := managedJob.GetJobId() + if jobId != nil { + return job.ID == *jobId + } + return false + }) + if ok { + pkg.Logger.Debug("[manager] read status from flink", zap.String("name", jobManagerJobOverview.Name), zap.String("state", jobManagerJobOverview.State)) + patchStatusObj := map[string]interface{}{ + "jobStatus": v1alpha1.JobStatus(jobManagerJobOverview.State), + } + if jobManagerJobOverview.State == string(v1alpha1.JobStatusRunning) { + status := string(v1alpha1.LifeCycleStatusHealthy) + patchStatusObj["lifeCycleStatus"] = &status + } + + crdInstance.Patch(uid, map[string]interface{}{ + "status": patchStatusObj, + }) + } + + managedJob.Cycle() + mgr.managedJobs[uid] = managedJob + + mgr.processingJobsIds = lo.Filter(mgr.processingJobsIds, func(current types.UID, i int) bool { + return current != uid + }) + } +} diff --git a/pkg/logger.go b/pkg/logger.go new file mode 100644 index 0000000..67ff770 --- /dev/null +++ b/pkg/logger.go @@ -0,0 +1,93 @@ +package pkg + +import ( + "context" + + "github.com/mattn/go-colorable" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "gopkg.in/natefinch/lumberjack.v2" +) + +type LoggerConfig struct { + Level zapcore.Level + Filename string + MaxSize int + MaxAge int + MaxBackups int + Compress bool + ConsoleEncoding bool // true for Console output, false for JSON +} + +var loggerInstance *zap.Logger + +// GetLogger returns a singleton logger instance +func GetLogger(ctx context.Context, config LoggerConfig) *zap.Logger { + if loggerInstance == nil { + createOrUpdateInstance(config) + } + return loggerInstance +} + +func createOrUpdateInstance(config LoggerConfig) *zap.Logger { + core := zapcore.NewTee( + createFileCore(config), + createStdoutCore(config), + ) + loggerInstance = zap.New(core).WithOptions( + zap.IncreaseLevel(config.Level), + ) + return loggerInstance +} + +// createFileCore creates the file logging core +func createFileCore(config LoggerConfig) zapcore.Core { + fileEncoderConfig := zap.NewProductionEncoderConfig() + fileEncoder := zapcore.NewJSONEncoder(fileEncoderConfig) + + fileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{ + Filename: config.Filename, + MaxSize: config.MaxSize, + MaxAge: config.MaxAge, + MaxBackups: config.MaxBackups, + Compress: config.Compress, + }) + + return zapcore.NewCore(fileEncoder, fileWriteSyncer, zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl >= zapcore.ErrorLevel + })) +} + +// createStdoutCore creates the stdout logging core based on config.ConsoleEncoding +func createStdoutCore(config LoggerConfig) zapcore.Core { + var encoder zapcore.Encoder + encoderConfig := zap.NewDevelopmentEncoderConfig() + encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + encoderConfig.TimeKey = "" + + if config.ConsoleEncoding { + encoder = zapcore.NewConsoleEncoder(encoderConfig) + } else { + encoder = zapcore.NewJSONEncoder(encoderConfig) + } + + stdoutWriteSyncer := zapcore.AddSync(colorable.NewColorableStdout()) + + return zapcore.NewCore(encoder, stdoutWriteSyncer, zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return true + })) +} + +func OverrideLoggerConfig(config LoggerConfig) { + Logger = createOrUpdateInstance(config) +} + +var Logger = GetLogger(context.Background(), LoggerConfig{ + Level: zap.DebugLevel, + Filename: "./tmp/error.log", + MaxSize: 100, + MaxAge: 90, + MaxBackups: 30, + Compress: true, + ConsoleEncoding: true, +}) diff --git a/pkg/safemap.go b/pkg/safemap.go new file mode 100644 index 0000000..7c616f5 --- /dev/null +++ b/pkg/safemap.go @@ -0,0 +1,42 @@ +package pkg + +import "sync" + +// SafeMap is a type-safe wrapper around sync.Map +type SafeMap[K comparable, V any] struct { + m sync.Map +} + +// Store sets the value for a key. +func (t *SafeMap[K, V]) Store(key K, value V) { + t.m.Store(key, value) +} + +// Load returns the value for a key, and whether the key was found. +func (t *SafeMap[K, V]) Load(key K) (V, bool) { + value, ok := t.m.Load(key) + if !ok { + var zero V + return zero, false + } + return value.(V), true +} + +// LoadOrStore returns the existing value for the key if present. +// Otherwise, it stores and returns the given value. +func (t *SafeMap[K, V]) LoadOrStore(key K, value V) (V, bool) { + actual, loaded := t.m.LoadOrStore(key, value) + return actual.(V), loaded +} + +// Delete removes the key and its value from the map. +func (t *SafeMap[K, V]) Delete(key K) { + t.m.Delete(key) +} + +// Range iterates over all key-value pairs in the map. +func (t *SafeMap[K, V]) Range(f func(key K, value V) bool) { + t.m.Range(func(key, value interface{}) bool { + return f(key.(K), value.(V)) + }) +} diff --git a/start-cluster.sh b/start-cluster.sh new file mode 100644 index 0000000..15b56cf --- /dev/null +++ b/start-cluster.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Alternatively, start TaskManager in the foreground if necessary +$FLINK_HOME/bin/taskmanager.sh start + +# Start JobManager in the foreground +$FLINK_HOME/bin/jobmanager.sh start-foreground + +# If you want to submit a job and still keep the cluster alive, use this: +if [[ -n "$FLINK_JOB" ]]; then + echo "Running Flink job: $FLINK_JOB" + $FLINK_HOME/bin/flink run -d $FLINK_JOB +fi + +# Keep the container running +tail -f /dev/null # This will prevent the container from exiting \ No newline at end of file diff --git a/workflow.drawio b/workflow.drawio new file mode 100644 index 0000000..ae596b6 --- /dev/null +++ b/workflow.drawio @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file