├── numaplane-controller.tar ├── tests ├── manifests │ ├── default │ │ ├── prometheus-ns.yaml │ │ ├── numaplane-ns.yaml │ │ ├── rollouts-ns.yaml │ │ ├── kustomization.yaml │ │ ├── config.yaml │ │ └── prometheus-monitors.yaml │ └── e2e │ │ ├── special-cases │ │ ├── no-strategy │ │ │ ├── kustomization.yaml │ │ │ └── controller-config.yaml │ │ └── pause-and-drain │ │ │ ├── kustomization.yaml │ │ │ └── controller-config.yaml │ │ └── default │ │ └── kustomization.yaml ├── e2e │ ├── coverage │ │ ├── entrypoint.sh │ │ └── Dockerfile │ └── analysistemplate.go └── config │ ├── testconfig2.yaml │ └── testconfig.yaml ├── extensions └── argo │ └── numa-rollout │ └── numa-rollout-extension │ ├── ui │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── rollout │ │ │ ├── default │ │ │ │ ├── ArgoSummary.css │ │ │ │ ├── ArgoRolloutSummary.test.tsx │ │ │ │ ├── ArgoRolloutComponent.tsx │ │ │ │ ├── ArgoRolloutComponent.test.tsx │ │ │ │ ├── ArgoRolloutContainers.test.tsx │ │ │ │ ├── ArgoRolloutComponent.css │ │ │ │ ├── ArgoRolloutSummary.tsx │ │ │ │ └── ArgoRolloutContainers.tsx │ │ │ ├── ISBRollout.test.tsx │ │ │ ├── PipelineRollout.test.tsx │ │ │ ├── MonovertexRollout.test.tsx │ │ │ ├── ControllerRollout.test.tsx │ │ │ ├── RolloutComponentWrapper.test.tsx │ │ │ ├── PipelineRollout.tsx │ │ │ ├── MonovertexRollout.tsx │ │ │ ├── ISBRollout.tsx │ │ │ └── ControllerRollout.tsx │ │ ├── setupTests.ts │ │ ├── index.css │ │ ├── utils │ │ │ ├── SquareCancelIcon.tsx │ │ │ ├── SquareCheckIcon.tsx │ │ │ ├── SquareCheckIcon.test.tsx │ │ │ ├── SquareCancelIcon.test.tsx │ │ │ └── Constants.tsx │ │ └── index.tsx │ ├── extension.tar │ ├── dist │ │ ├── extension.tar.gz │ │ └── resources │ │ │ └── extension-Numarollout.js │ │ │ └── extensions-Numarollout.js.LICENSE.txt │ ├── README.md │ ├── Makefile │ ├── tsconfig.json │ ├── tslint.json │ ├── webpack.config.js │ └── package.json │ ├── package.json │ └── yarn.lock ├── .dockerignore ├── .codecov.yml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── workflows │ ├── pr.yaml │ └── release.yml └── pull_request_template.md ├── internal ├── util │ ├── kubernetes │ │ ├── testdata │ │ │ ├── svc.yaml │ │ │ └── svc-with-invalid-data.yaml │ │ ├── kubectl.go │ │ ├── clientset.go │ │ ├── structured_util_test.go │ │ ├── structured_util.go │ │ └── kubernetes_util.go │ ├── metrics │ │ └── transportwrapper.go │ ├── queue.go │ ├── logger │ │ └── base_logger.go │ └── maps.go ├── sync │ └── testdata │ │ ├── pipeline-config.yaml │ │ └── pipeline-live.yaml └── controller │ ├── config │ ├── options.go │ ├── user_config.go │ └── usde_config.go │ ├── common │ ├── rollout_object.go │ ├── rollout_object_test.go │ ├── numaflowtypes │ │ ├── vertex.go │ │ ├── monovertex.go │ │ └── isbsvc.go │ ├── predicate_filters.go │ └── in_progress_strategy_test.go │ └── numaflowcontroller │ └── numaflowcontroller_controller_test.go ├── config ├── rbac │ ├── service_account.yaml │ ├── leader_election_role_binding.yaml │ ├── role_binding.yaml │ ├── pipelinerollout_viewer_role.yaml │ ├── isbservicerollout_viewer_role.yaml │ ├── monovertexrollout_viewer_role.yaml │ ├── numaflowcontrollerrollout_viewer_role.yaml │ ├── numaflowcontroller_viewer_role.yaml │ ├── pipelinerollout_editor_role.yaml │ ├── monovertexrollout_editor_role.yaml │ ├── isbservicerollout_editor_role.yaml │ ├── numaflowcontrollerrollout_editor_role.yaml │ ├── numaflowcontroller_editor_role.yaml │ ├── numaplane-aggregate-to-view.yaml │ ├── kustomization.yaml │ ├── numaplane-aggregate-to-edit.yaml │ ├── numaplane-aggregate-to-admin.yaml │ ├── leader_election_role.yaml │ └── role.yaml ├── default │ ├── kustomization.yaml │ └── manager_config_patch.yaml ├── manager │ ├── kustomization.yaml │ └── controller-config.yaml ├── samples │ ├── numaplane.numaproj.io_v1alpha1_numaflowcontroller.yaml │ ├── kustomization.yaml │ ├── numaplane.numaproj.io_v1alpha1_numaflowcontrollerrollout.yaml │ ├── namespace-level-config.yaml │ ├── numaplane.numaproj.io_v1alpha1_isbservicerollout.yaml │ └── numaplane.numaproj.io_v1alpha1_pipelinerollout.yaml ├── kustomize │ └── examples │ │ └── transformer │ │ ├── my-isbservicerollout.yaml │ │ ├── my-monovertexrollout.yaml │ │ ├── kustomization.yaml │ │ └── my-pipelinerollout.yaml └── crd │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── pkg ├── apis │ └── numaplane │ │ └── v1alpha1 │ │ ├── const.go │ │ ├── numaflowcontroller_types.go │ │ ├── groupversion_info.go │ │ └── numaflowcontrollerrollout_types.go └── client │ ├── clientset │ └── versioned │ │ ├── fake │ │ ├── doc.go │ │ ├── register.go │ │ └── clientset_generated.go │ │ ├── scheme │ │ ├── doc.go │ │ └── register.go │ │ └── typed │ │ └── numaplane │ │ └── v1alpha1 │ │ ├── fake │ │ ├── doc.go │ │ └── fake_numaplane_client.go │ │ ├── doc.go │ │ ├── generated_expansion.go │ │ ├── pipelinerollout.go │ │ ├── isbservicerollout.go │ │ ├── monovertexrollout.go │ │ └── numaflowcontroller.go │ ├── informers │ └── externalversions │ │ ├── internalinterfaces │ │ └── factory_interfaces.go │ │ ├── numaplane │ │ ├── interface.go │ │ └── v1alpha1 │ │ │ └── interface.go │ │ └── generic.go │ └── listers │ └── numaplane │ └── v1alpha1 │ ├── expansion_generated.go │ ├── pipelinerollout.go │ ├── isbservicerollout.go │ ├── monovertexrollout.go │ ├── numaflowcontroller.go │ └── numaflowcontrollerrollout.go ├── hack ├── numaflow-controller-def-generator │ ├── def-configmap.yaml │ └── kustomization.yaml ├── boilerplate.go.txt ├── update-codegen.sh ├── tools.go └── library.sh ├── .gitignore ├── Dockerfile ├── PROJECT └── README.md /numaplane-controller.tar: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/manifests/default/prometheus-ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: prometheus -------------------------------------------------------------------------------- /tests/manifests/default/numaplane-ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: numaplane-system -------------------------------------------------------------------------------- /tests/manifests/default/rollouts-ns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: argo-rollouts -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "react-test-renderer": "^18.2.0" 4 | } 5 | } -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/default/ArgoSummary.css: -------------------------------------------------------------------------------- 1 | .info-item{ 2 | width: 86px; 3 | justify-content: center; 4 | } -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "pkg/client/.*" 3 | - "tests/.*" 4 | - "**/*generated.deepcopy.go" 5 | - "internal/controller/common/test_common.go" 6 | - "vendor/.*" -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. Unless a later match takes precedence 3 | * @xdevxy @juliev0 @afugazzotto @dsimha @shakira -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/extension.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numaproj/numaplane/HEAD/extensions/argo/numa-rollout/numa-rollout-extension/ui/extension.tar -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/dist/extension.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numaproj/numaplane/HEAD/extensions/argo/numa-rollout/numa-rollout-extension/ui/dist/extension.tar.gz -------------------------------------------------------------------------------- /tests/manifests/e2e/special-cases/no-strategy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - ../../default 6 | 7 | patches: 8 | - path: controller-config.yaml -------------------------------------------------------------------------------- /tests/manifests/e2e/special-cases/pause-and-drain/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - ../../default 6 | 7 | patches: 8 | - path: controller-config.yaml -------------------------------------------------------------------------------- /internal/util/kubernetes/testdata/svc.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: my-service 5 | spec: 6 | selector: 7 | app: MyApp 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 9376 -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: serviceaccount 6 | app.kubernetes.io/component: rbac 7 | app.kubernetes.io/part-of: numaplane 8 | name: numaplane-sa -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | namespace: numaplane-system 5 | 6 | resources: 7 | - ../crd 8 | - ../rbac 9 | - ../manager 10 | 11 | patches: 12 | - path: manager_config_patch.yaml -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - controller-manager.yaml 6 | - controller-config.yaml 7 | - usde-config.yaml 8 | 9 | images: 10 | - name: quay.io/numaproj/numaplane-controller 11 | newTag: latest 12 | -------------------------------------------------------------------------------- /config/samples/numaplane.numaproj.io_v1alpha1_numaflowcontroller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: numaplane.numaproj.io/v1alpha1 2 | kind: NumaflowController 3 | metadata: 4 | name: numaflowcontroller-sample 5 | namespace: example-namespace 6 | spec: 7 | #instanceID: "0" # uncomment for Progressive rollout to set Numaflow Controller instance 8 | version: "1.5.2" 9 | -------------------------------------------------------------------------------- /config/kustomize/examples/transformer/my-isbservicerollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: numaplane.numaproj.io/v1alpha1 2 | kind: ISBServiceRollout 3 | metadata: 4 | name: simple-isbservice 5 | spec: 6 | interStepBufferService: 7 | spec: 8 | jetstream: 9 | replicas: 1 10 | version: latest 11 | persistence: 12 | volumeSize: 1Gi 13 | -------------------------------------------------------------------------------- /pkg/apis/numaplane/v1alpha1/const.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | const ( 4 | RolloutISBSvcName = "isbsvc-rollout" 5 | RolloutPipelineName = "pipeline-rollout" 6 | RolloutNumaflowControllerName = "numaflow-controller-rollout" 7 | RolloutMonoVertexName = "monovertex-rollout" 8 | NumaflowControllerName = "numaflow-controller" 9 | ) 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Have you read the docs? 5 | url: https://github.com/numaproj-labs/numaplane 6 | about: Much help can be found in the docs 7 | - name: Ask a question 8 | url: https://github.com/numaproj-labs/numaplane/discussions/new 9 | about: Ask a question or start a discussion about Numaplane -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: numaplane-controller-manager 5 | spec: 6 | template: 7 | spec: 8 | containers: 9 | - name: manager 10 | args: 11 | - "--health-probe-bind-address=:8081" 12 | - "--metrics-bind-address=:8080" 13 | - "--leader-elect" -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - numaplane.numaproj.io_v1alpha1_numaflowcontrollerrollout.yaml 4 | - numaplane.numaproj.io_v1alpha1_numaflowcontroller.yaml 5 | - numaplane.numaproj.io_v1alpha1_isbservicerollout.yaml 6 | - numaplane.numaproj.io_v1alpha1_pipelinerollout.yaml 7 | - namespace-level-config.yaml 8 | #+kubebuilder:scaffold:manifestskustomizesamples 9 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/README.md: -------------------------------------------------------------------------------- 1 | ## Available Scripts 2 | 3 | In the project directory, you can run: 4 | 5 | ### `yarn build` 6 | 7 | copy the contents in the extensions-Numarollout.js file and paste it in the console of the page running the ArgoCD UI. 8 | 9 | ### `yarn test` 10 | 11 | Runs unit tests inside the UI folder. i.e. the files with extension test.tsx 12 | 13 | 14 | -------------------------------------------------------------------------------- /config/samples/numaplane.numaproj.io_v1alpha1_numaflowcontrollerrollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: numaplane.numaproj.io/v1alpha1 2 | kind: NumaflowControllerRollout 3 | metadata: 4 | name: numaflow-controller 5 | #name: numaflow-controller-0 6 | namespace: example-namespace 7 | spec: 8 | controller: 9 | #instanceID: "0" # uncomment for Progressive rollout to set Numaflow Controller instance 10 | version: "1.5.2" 11 | -------------------------------------------------------------------------------- /internal/util/kubernetes/testdata/svc-with-invalid-data.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: my-service 5 | annotations: 6 | valid-annotation: existing-value 7 | invalid-annotation: null 8 | labels: 9 | valid-label: existing-value 10 | invalid-label: null 11 | spec: 12 | selector: 13 | app: MyApp 14 | ports: 15 | - protocol: TCP 16 | port: 80 17 | targetPort: 9376 -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | title-check: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check PR Title's semantic conformance 15 | uses: Slashgear/action-check-pr-title@v4.3.0 16 | with: 17 | regexp: "(feat|fix|docs|chore):.+" # Regex the title should match. 18 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | yarn build 3 | # dist/extension.tar must be committed into repo for install.yml to reference 4 | 5 | reapply: 6 | kubectl delete -f ../manifests/install.yml -n argocd 7 | kubectl apply -f ../manifests/install.yml -n argocd 8 | 9 | apply: 10 | kubectl apply -f ../manifests/install.yml -n argocd 11 | 12 | delete: 13 | kubectl delete -f ../manifests/install.yml -n argocd -------------------------------------------------------------------------------- /hack/numaflow-controller-def-generator/def-configmap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: numaflow-controller-definitions-$TMP_NFC_DEF_GEN_NUMAFLOW_VERSION 5 | namespace: numaplane-system 6 | labels: 7 | "numaplane.numaproj.io/config": numaflow-controller-definitions 8 | data: 9 | controller_definitions.yaml: | 10 | controllerDefinitions: 11 | - version: "$TMP_NFC_DEF_GEN_NUMAFLOW_VERSION" 12 | fullSpec: | 13 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | 7 | module.exports = { 8 | // other Jest configuration options 9 | transformIgnorePatterns: ['/node_modules/(?!@testing-library\\/dom)'], 10 | }; 11 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: rolebinding 6 | app.kubernetes.io/component: rbac 7 | app.kubernetes.io/part-of: numaplane 8 | name: numaplane-leader-election-rolebinding 9 | roleRef: 10 | apiGroup: rbac.authorization.k8s.io 11 | kind: Role 12 | name: numaplane-leader-election-role 13 | subjects: 14 | - kind: ServiceAccount 15 | name: numaplane-sa -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: clusterrolebinding 6 | app.kubernetes.io/component: rbac 7 | app.kubernetes.io/part-of: numaplane 8 | name: numaplane-rolebinding 9 | roleRef: 10 | apiGroup: rbac.authorization.k8s.io 11 | kind: ClusterRole 12 | name: numaplane-role 13 | subjects: 14 | - kind: ServiceAccount 15 | name: numaplane-sa 16 | namespace: numaplane-system -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - bases/numaplane.numaproj.io_pipelinerollouts.yaml 6 | - bases/numaplane.numaproj.io_numaflowcontrollerrollouts.yaml 7 | - bases/numaplane.numaproj.io_isbservicerollouts.yaml 8 | - bases/numaplane.numaproj.io_monovertexrollouts.yaml 9 | - bases/numaplane.numaproj.io_numaflowcontrollers.yaml 10 | # install numaflow minimal CRDs as a dependency 11 | - https://github.com/numaproj/numaflow/config/advanced-install/minimal-crds?ref=v1.5.2 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Propose an enhancement for this project 4 | labels: 'enhancement' 5 | --- 6 | # Summary 7 | 8 | What change needs making? 9 | 10 | # Use Cases 11 | 12 | When would you use this? 13 | 14 | --- 15 | 16 | **Message from the maintainers**: 17 | 18 | If you wish to see this enhancement implemented please add a 👍 reaction to this issue! We often sort issues this way to know what to prioritize. -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/default/ArgoRolloutSummary.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react"; 2 | import { ArgoRolloutSummary } from "./ArgoRolloutSummary"; 3 | 4 | const rolloutParams = { 5 | strategy: "blueGreen", 6 | setWeight: 10, 7 | actualWeight: 100, 8 | }; 9 | describe("ArgoRolloutSummary", () => { 10 | it("should render", () => { 11 | render(); 12 | expect(screen.getByText("blueGreen")).toBeInTheDocument(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /config/samples/namespace-level-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: my-config 5 | namespace: example-namespace 6 | labels: 7 | numaplane.numaproj.io/config: namespace-level-config 8 | data: 9 | # TODO-PROGRESSIVE: before the PROGRESSIVE strategy is implemented, users will only be able to choose "pause-and-drain". Afterwards, "progressive" should also be an option. Remove this comment line after implementing PROGRESSIVE strategy. 10 | # upgradeStrategy can be either "progressive" or "pause-and-drain" 11 | upgradeStrategy: "pause-and-drain" 12 | -------------------------------------------------------------------------------- /tests/e2e/coverage/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Start the main process in the background 5 | /manager --health-probe-bind-address=:8081 --metrics-bind-address=:8080 --leader-elect & 6 | export NUMAPLANE_PID=$! 7 | ## Write NUMAPLANE_PID to a file 8 | echo $NUMAPLANE_PID > /numaplane.pid 9 | 10 | # Function to handle signals 11 | trap "echo 'Stopping PID $PID'; kill $PID; wait $PID; /manager --health-probe-bind-address=:8081 --metrics-bind-address=:8080 --leader-elect &" SIGTERM SIGINT 12 | 13 | # Wait indefinitely 14 | while true 15 | do 16 | tail -f /dev/null & wait ${!} 17 | done -------------------------------------------------------------------------------- /config/rbac/pipelinerollout_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view pipelinerollouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: pipelinerollout-viewer-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io 12 | resources: 13 | - pipelinerollouts 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - numaplane.numaproj.io 20 | resources: 21 | - pipelinerollouts/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/rbac/isbservicerollout_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view isbservicerollouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: isbservicerollout-viewer-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io 12 | resources: 13 | - isbservicerollouts 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - numaplane.numaproj.io 20 | resources: 21 | - isbservicerollouts/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/monovertexrollout_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view monovertexrollouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: monovertexrollout-viewer-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io 12 | resources: 13 | - monovertexrollouts 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - numaplane.numaproj.io 20 | resources: 21 | - monovertexrollouts/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /internal/util/kubernetes/kubectl.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/argoproj/gitops-engine/pkg/utils/kube" 7 | "github.com/argoproj/gitops-engine/pkg/utils/tracing" 8 | 9 | "github.com/numaproj/numaplane/internal/util/logger" 10 | ) 11 | 12 | var tracer tracing.Tracer = &tracing.NopTracer{} 13 | 14 | func init() { 15 | if os.Getenv("NUMAPLANE_TRACING_ENABLED") == "1" { 16 | tracer = tracing.NewLoggingTracer(*logger.New().LogrLogger) 17 | } 18 | } 19 | 20 | func NewKubectl() kube.Kubectl { 21 | return &kube.KubectlCmd{Tracer: tracer, Log: *logger.New().LogrLogger} 22 | } 23 | -------------------------------------------------------------------------------- /tests/config/testconfig2.yaml: -------------------------------------------------------------------------------- 1 | logLevel: DEBUG # Supported log levels are: VERBOSE, DEBUG, INFO, WARN. ERROR, FATAL will be printed regardless of the log level 2 | defaultUpgradeStrategy: "progressive" 3 | permittedRiders: "group=autoscaling.k8s.io,kind=VerticalPodAutoscaler;group=,kind=ConfigMap;group=autoscaling,kind=HorizontalPodAutoscaler" 4 | progressive: 5 | defaultAssessmentSchedule: 6 | - kind: Pipeline 7 | schedule: "120,360,0,10" 8 | - kind: MonoVertex 9 | schedule: "120,360,0,10" 10 | - kind: InterstepBufferService 11 | schedule: "0,480,0,10" 12 | analysisRunTimeout: "600" 13 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /hack/numaflow-controller-def-generator/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | commonLabels: 4 | app.kubernetes.io/instance: '{{ .InstanceID }}' 5 | nameSuffix: "{{ .InstanceSuffix }}" 6 | 7 | resources: 8 | - namespace-install.yaml 9 | 10 | patches: 11 | # Remove all CRDs 12 | - target: 13 | group: apiextensions.k8s.io 14 | version: v1 15 | kind: CustomResourceDefinition 16 | name: .* 17 | patch: |- 18 | $patch: delete 19 | apiVersion: apiextensions.k8s.io/v1 20 | kind: CustomResourceDefinition 21 | metadata: 22 | name: not-important 23 | -------------------------------------------------------------------------------- /config/rbac/numaflowcontrollerrollout_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view numaflowcontrollerrollouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: numaflowcontrollerrollout-viewer-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io 12 | resources: 13 | - numaflowcontrollerrollouts 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - numaplane.numaproj.io 20 | resources: 21 | - numaflowcontrollerrollouts/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/numaflowcontroller_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view numaflowcontrollers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: numaflowcontroller-viewer-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io.github.com.numaproj 12 | resources: 13 | - numaflowcontrollers 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - numaplane.numaproj.io.github.com.numaproj 20 | resources: 21 | - numaflowcontrollers/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/pipelinerollout_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit pipelinerollouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: pipelinerollout-editor-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io 12 | resources: 13 | - pipelinerollouts 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - numaplane.numaproj.io 24 | resources: 25 | - pipelinerollouts/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /internal/util/metrics/transportwrapper.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/argoproj/pkg/kubeclientmetrics" 5 | "k8s.io/client-go/rest" 6 | ) 7 | 8 | // AddMetricsTransportWrapper adds a transport wrapper which increments 'numaplane_app_k8s_request_total' counter on each kubernetes request 9 | func AddMetricsTransportWrapper(metrics *CustomMetrics, config *rest.Config) *rest.Config { 10 | fn := func(resourceInfo kubeclientmetrics.ResourceInfo) error { 11 | metrics.KubeRequestCounter.WithLabelValues().Inc() 12 | return nil 13 | } 14 | 15 | newConfig := kubeclientmetrics.AddMetricsTransportWrapper(config, fn) 16 | return newConfig 17 | } 18 | -------------------------------------------------------------------------------- /config/rbac/monovertexrollout_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit monovertexrolouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: monovertexrollout-editor-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io 12 | resources: 13 | - monovertexrollouts 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - numaplane.numaproj.io 24 | resources: 25 | - monovertexrollouts/status 26 | verbs: 27 | - get -------------------------------------------------------------------------------- /config/rbac/isbservicerollout_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit isbservicerollouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: isbservicerollout-editor-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io 12 | resources: 13 | - isbservicerollouts 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - numaplane.numaproj.io 24 | resources: 25 | - isbservicerollouts/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /internal/sync/testdata/pipeline-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: numaflow.numaproj.io/v1alpha1 2 | kind: Pipeline 3 | metadata: 4 | name: simple-pipeline 5 | spec: 6 | vertices: 7 | - name: in 8 | source: 9 | # A self data generating source 10 | generator: 11 | rpu: 5 12 | duration: 1s 13 | - name: cat 14 | udf: 15 | container: 16 | image: "quay.io/numaio/numaflow-go/map-cat:stable" 17 | imagePullPolicy: Always, 18 | - name: out 19 | sink: 20 | # A simple log printing sink 21 | log: {} 22 | edges: 23 | - from: in 24 | to: cat 25 | - from: cat 26 | to: out -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/ISBRollout.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/react"; 2 | 3 | import { RolloutComponentContext } from "./RolloutComponentWrapper"; 4 | import { mockISBRolloutProps } from "../../mocks/mockProps"; 5 | import { ISBRollout } from "./ISBRollout"; 6 | describe("ISB Rollout", () => { 7 | it("should render", () => { 8 | const { getByText } = render( 9 | 10 | 11 | 12 | ); 13 | expect(getByText("ISB Name : my-isbsvc")).toBeInTheDocument(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /config/rbac/numaflowcontrollerrollout_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit numaflowcontrollerrollouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: numaflowcontrollerrollout-editor-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io 12 | resources: 13 | - numaflowcontrollerrollouts 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - numaplane.numaproj.io 24 | resources: 25 | - numaflowcontrollerrollouts/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/PipelineRollout.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/react"; 2 | 3 | import { PipelineRollout } from "./PipelineRollout"; 4 | import { RolloutComponentContext } from "./RolloutComponentWrapper"; 5 | import { mockISBRolloutProps } from "../../mocks/mockProps"; 6 | describe("PipelineRollout", () => { 7 | it("should render", () => { 8 | const { getByText } = render( 9 | 10 | 11 | 12 | ); 13 | expect(getByText("Pipeline Name: my-pipeline")).toBeInTheDocument(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /config/rbac/numaflowcontroller_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit numaflowcontrollers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: numaplane 7 | app.kubernetes.io/managed-by: kustomize 8 | name: numaflowcontroller-editor-role 9 | rules: 10 | - apiGroups: 11 | - numaplane.numaproj.io.github.com.numaproj 12 | resources: 13 | - numaflowcontrollers 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - numaplane.numaproj.io.github.com.numaproj 24 | resources: 25 | - numaflowcontrollers/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/samples/numaplane.numaproj.io_v1alpha1_isbservicerollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: numaplane.numaproj.io/v1alpha1 2 | kind: ISBServiceRollout 3 | metadata: 4 | name: my-isbsvc 5 | namespace: example-namespace 6 | spec: 7 | strategy: 8 | progressive: 9 | assessmentSchedule: "0,0,0,10" 10 | interStepBufferService: 11 | #uncomment for Progressive rollout to set Numaflow Controller instance: 12 | #metadata: 13 | # annotations: 14 | # numaflow.numaproj.io/instance: "0" 15 | spec: 16 | # Example from https://github.com/numaproj/numaflow/blob/main/examples/0-isbsvc-jetstream.yaml 17 | jetstream: 18 | version: 2.10.3 19 | persistence: 20 | volumeSize: 1Gi 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fixes #TODO 6 | 7 | ### Modifications 8 | 9 | 10 | 11 | 12 | ### Verification 13 | 14 | 15 | 16 | ### Backward incompatibilities 17 | 18 | 19 | -------------------------------------------------------------------------------- /config/kustomize/examples/transformer/my-monovertexrollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: numaplane.numaproj.io/v1alpha1 2 | kind: MonoVertexRollout 3 | metadata: 4 | name: simple-monovertex 5 | spec: 6 | monoVertex: 7 | spec: 8 | source: 9 | udsource: 10 | container: 11 | image: quay.io/numaio/numaflow-java/source-simple-source:stable 12 | # transformer is an optional container to do any transformation to the incoming data before passing to the sink 13 | transformer: 14 | container: 15 | image: quay.io/numaio/numaflow-rs/source-transformer-now:stable 16 | sink: 17 | udsink: 18 | container: 19 | image: quay.io/numaio/numaflow-java/simple-sink:stable -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/default/ArgoRolloutComponent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./ArgoRolloutComponent.css"; 4 | import { ArgoRolloutSummary } from "./ArgoRolloutSummary"; 5 | import { CANARY } from "../../utils/Constants"; 6 | import { ArgoRolloutContainers } from "./ArgoRolloutContainers"; 7 | import { Box } from "@mui/material"; 8 | 9 | const rolloutParams = { 10 | strategy: CANARY, 11 | setWeight: "0", 12 | actualWeight: "100", 13 | step: "1", 14 | }; 15 | export const ArgoRolloutComponent = () => { 16 | return ( 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", "tslint-react", "tslint-plugin-prettier", "tslint-config-prettier" 4 | ], 5 | "jsRules": {}, 6 | "rules": { 7 | "prettier": true, 8 | "quotemark": [true, "single"], 9 | "no-var-requires": false, 10 | "interface-name": false, 11 | "jsx-no-multiline-js": false, 12 | "object-literal-sort-keys": false, 13 | "jsx-alignment": false, 14 | "max-line-length": [true, 200], 15 | "jsx-no-lambda": false, 16 | "array-type": false, 17 | "max-classes-per-file": false, 18 | "newline-per-chained-call": false 19 | }, 20 | "rulesDirectory": [] 21 | } -------------------------------------------------------------------------------- /tests/manifests/e2e/special-cases/pause-and-drain/controller-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: numaplane-controller-config 5 | data: 6 | config.yaml: | 7 | # Supported log levels are: VERBOSE, DEBUG, INFO, WARN. ERROR, FATAL will be printed regardless of the log level 8 | logLevel: DEBUG 9 | includedResources: "group=apps,kind=Deployment;group=,kind=ConfigMap;group=,kind=ServiceAccount;group=,kind=Secret;group=,kind=Service;group=rbac.authorization.k8s.io,kind=RoleBinding;group=rbac.authorization.k8s.io,kind=Role" 10 | defaultUpgradeStrategy: "pause-and-drain" 11 | permittedRiders: "group=autoscaling.k8s.io,kind=VerticalPodAutoscaler;group=,kind=ConfigMap;group=autoscaling,kind=HorizontalPodAutoscaler" 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | bin/* 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories 19 | vendor/ 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | .vscode 24 | *.swp 25 | *.swo 26 | *~ 27 | **/.DS_Store 28 | 29 | # Go workspace file 30 | go.work 31 | 32 | config/crd/bases/_.yaml 33 | .idea 34 | 35 | config/crd/external/ 36 | 37 | tests/e2e/output 38 | 39 | .qodo 40 | -------------------------------------------------------------------------------- /tests/manifests/e2e/special-cases/no-strategy/controller-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: numaplane-controller-config 5 | data: 6 | config.yaml: | 7 | # Supported log levels are: VERBOSE, DEBUG, INFO, WARN. ERROR, FATAL will be printed regardless of the log level 8 | logLevel: DEBUG 9 | includedResources: "group=apps,kind=Deployment;\ 10 | group=,kind=ConfigMap;group=,kind=ServiceAccount;group=,kind=Secret;group=,kind=Service;\ 11 | group=rbac.authorization.k8s.io,kind=RoleBinding;group=rbac.authorization.k8s.io,kind=Role" 12 | defaultUpgradeStrategy: "no-strategy" 13 | permittedRiders: "group=autoscaling.k8s.io,kind=VerticalPodAutoscaler;group=,kind=ConfigMap;group=autoscaling,kind=HorizontalPodAutoscaler" 14 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package has the automatically generated fake clientset. 19 | package fake 20 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/MonovertexRollout.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render } from "@testing-library/react"; 3 | import { MonovertexRollout } from "./MonovertexRollout"; 4 | import { RolloutComponentContext } from "./RolloutComponentWrapper"; 5 | import { mockMonovertexRolloutProps } from "../../mocks/mockProps"; 6 | describe("MonovertexRollout", () => { 7 | it("should render", () => { 8 | const { getByText } = render( 9 | 12 | 13 | 14 | ); 15 | expect(getByText("Monovertex Rollout Name: my-mono")).toBeInTheDocument(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/ControllerRollout.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/react"; 2 | 3 | import { RolloutComponentContext } from "./RolloutComponentWrapper"; 4 | import { mockControllerRolloutProps } from "../../mocks/mockProps"; 5 | import { ControllerRollout } from "./ControllerRollout"; 6 | describe("Controller Rollout", () => { 7 | it("should render", () => { 8 | const { getByText } = render( 9 | 12 | 13 | 14 | ); 15 | expect( 16 | getByText("Controller Name : numaflow-controller") 17 | ).toBeInTheDocument(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package contains the scheme of the automatically generated clientset. 19 | package scheme 20 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/numaplane/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // Package fake has the automatically generated clients. 19 | package fake 20 | -------------------------------------------------------------------------------- /config/kustomize/examples/transformer/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | configurations: 5 | - https://raw.githubusercontent.com/numaproj/numaplane/main/config/kustomize/numaplane-transformer-config.yaml 6 | 7 | namePrefix: my- 8 | 9 | resources: 10 | - my-pipelinerollout.yaml 11 | - my-isbservicerollout.yaml 12 | - my-monovertexrollout.yaml 13 | 14 | configMapGenerator: 15 | - name: my-cm 16 | literals: 17 | - FOO=BAR 18 | 19 | secretGenerator: 20 | - name: my-secret 21 | literals: 22 | - password=Pa5SW0rD 23 | 24 | commonLabels: 25 | foo: bar 26 | 27 | commonAnnotations: 28 | foo: bar 29 | 30 | images: 31 | - name: my-pipeline/my-udf 32 | newTag: my-version 33 | 34 | replicas: 35 | - name: simple-isbservice 36 | count: 3 37 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/numaplane/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | // This package has the automatically generated typed clients. 19 | package v1alpha1 20 | -------------------------------------------------------------------------------- /config/rbac/numaplane-aggregate-to-view.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | rbac.authorization.k8s.io/aggregate-to-view: "true" 6 | name: numaplane-aggregate-to-view 7 | rules: 8 | - apiGroups: 9 | - numaplane.numaproj.io 10 | resources: 11 | - pipelinerollouts 12 | - isbservicerollouts 13 | - numaflowcontrollerrollouts 14 | - numaflowcontrollers 15 | - monovertexrollouts 16 | verbs: 17 | - get 18 | - list 19 | - watch 20 | - apiGroups: 21 | - numaplane.numaproj.io 22 | resources: 23 | - pipelinerollouts/status 24 | - isbservicerollouts/status 25 | - numaflowcontrollerrollouts/status 26 | - numaflowcontrollers/status 27 | - monovertexrollouts/status 28 | verbs: 29 | - get -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - service_account.yaml 6 | - role.yaml 7 | - role_binding.yaml 8 | - leader_election_role.yaml 9 | - leader_election_role_binding.yaml 10 | - pipelinerollout_editor_role.yaml 11 | - pipelinerollout_viewer_role.yaml 12 | - numaflowcontrollerrollout_editor_role.yaml 13 | - numaflowcontrollerrollout_viewer_role.yaml 14 | - isbservicerollout_editor_role.yaml 15 | - isbservicerollout_viewer_role.yaml 16 | - monovertexrollout_editor_role.yaml 17 | - monovertexrollout_viewer_role.yaml 18 | - numaplane-aggregate-to-admin.yaml 19 | - numaplane-aggregate-to-edit.yaml 20 | - numaplane-aggregate-to-view.yaml 21 | - numaflowcontroller_editor_role.yaml 22 | - numaflowcontroller_viewer_role.yaml 23 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/RolloutComponentWrapper.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react"; 2 | import { mockISBRolloutProps } from "../../mocks/mockProps"; 3 | import { 4 | RolloutComponentContext, 5 | RolloutComponentWrapper, 6 | } from "./RolloutComponentWrapper"; 7 | 8 | describe("RolloutComponentWrapper", () => { 9 | it("should render", () => { 10 | render( 11 | 17 | ); 18 | 19 | const doc = screen.getByText("Actual Weight"); 20 | expect(doc).toBeInTheDocument(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/manifests/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - ../../../config/default 6 | - ./controller_def_1.4.6.yaml 7 | - ./controller_def_1.5.2.yaml 8 | - ./controller_def_1.7.0.yaml 9 | - ./prometheus-monitors.yaml 10 | - https://raw.githubusercontent.com/kubernetes/autoscaler/vpa-release-1.0/vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml 11 | 12 | patches: 13 | - patch: |- 14 | - op: add 15 | path: /spec/template/spec/containers/0/imagePullPolicy 16 | value: IfNotPresent 17 | target: 18 | kind: Deployment 19 | name: numaplane-controller-manager 20 | 21 | configMapGenerator: 22 | - name: numaplane-controller-config 23 | namespace: numaplane-system 24 | files: 25 | - config.yaml 26 | behavior: merge # Optional, defaults to "create" 27 | -------------------------------------------------------------------------------- /config/rbac/numaplane-aggregate-to-edit.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | rbac.authorization.k8s.io/aggregate-to-edit: "true" 6 | name: numaplane-aggregate-to-edit 7 | rules: 8 | - apiGroups: 9 | - numaplane.numaproj.io 10 | resources: 11 | - pipelinerollouts 12 | - isbservicerollouts 13 | - numaflowcontrollerrollouts 14 | - numaflowcontrollers 15 | - monovertexrollouts 16 | verbs: 17 | - create 18 | - delete 19 | - get 20 | - list 21 | - patch 22 | - update 23 | - watch 24 | - apiGroups: 25 | - numaplane.numaproj.io 26 | resources: 27 | - pipelinerollouts/status 28 | - isbservicerollouts/status 29 | - numaflowcontrollerrollouts/status 30 | - numaflowcontrollers/status 31 | - monovertexrollouts/status 32 | verbs: 33 | - get -------------------------------------------------------------------------------- /config/rbac/numaplane-aggregate-to-admin.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | labels: 5 | rbac.authorization.k8s.io/aggregate-to-admin: "true" 6 | name: numaplane-aggregate-to-admin 7 | rules: 8 | - apiGroups: 9 | - numaplane.numaproj.io 10 | resources: 11 | - pipelinerollouts 12 | - isbservicerollouts 13 | - numaflowcontrollerrollouts 14 | - numaflowcontrollers 15 | - monovertexrollouts 16 | verbs: 17 | - create 18 | - delete 19 | - get 20 | - list 21 | - patch 22 | - update 23 | - watch 24 | - apiGroups: 25 | - numaplane.numaproj.io 26 | resources: 27 | - pipelinerollouts/status 28 | - isbservicerollouts/status 29 | - numaflowcontrollerrollouts/status 30 | - numaflowcontrollers/status 31 | - monovertexrollouts/status 32 | verbs: 33 | - get 34 | 35 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: role 7 | app.kubernetes.io/component: rbac 8 | app.kubernetes.io/part-of: numaplane 9 | name: numaplane-leader-election-role 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - configmaps 15 | verbs: 16 | - get 17 | - list 18 | - watch 19 | - create 20 | - update 21 | - patch 22 | - delete 23 | - apiGroups: 24 | - coordination.k8s.io 25 | resources: 26 | - leases 27 | verbs: 28 | - get 29 | - list 30 | - watch 31 | - create 32 | - update 33 | - patch 34 | - delete 35 | - apiGroups: 36 | - "" 37 | resources: 38 | - events 39 | verbs: 40 | - create 41 | - patch -------------------------------------------------------------------------------- /tests/config/testconfig.yaml: -------------------------------------------------------------------------------- 1 | logLevel: INFO # Supported log levels are: VERBOSE, DEBUG, INFO, WARN. ERROR, FATAL will be printed regardless of the log level 2 | numaflowControllerImageNames: 3 | - numaflow 4 | - numaflow-rc 5 | includedResources: "group=apps,kind=Deployment;group=,kind=ConfigMap;group=,kind=Secret;group=,kind=ServiceAccount;group=,kind=Namespace;group=rbac.authorization.k8s.io,kind=RoleBinding;group=rbac.authorization.k8s.io,kind=Role" 6 | defaultUpgradeStrategy: "pause-and-drain" 7 | permittedRiders: "group=autoscaling.k8s.io,kind=VerticalPodAutoscaler;group=,kind=ConfigMap;group=autoscaling,kind=HorizontalPodAutoscaler" 8 | progressive: 9 | defaultAssessmentSchedule: 10 | - kind: Pipeline 11 | schedule: "120,360,0,10" 12 | - kind: MonoVertex 13 | schedule: "120,360,0,10" 14 | - kind: InterstepBufferService 15 | schedule: "0,480,0,10" 16 | analysisRunTimeout: "1200" 17 | 18 | -------------------------------------------------------------------------------- /tests/manifests/default/config.yaml: -------------------------------------------------------------------------------- 1 | logLevel: DEBUG # Supported log levels are: VERBOSE, DEBUG, INFO, WARN. ERROR, FATAL will be printed regardless of the log level 2 | numaflowControllerImageNames: 3 | - numaflow 4 | - numaflow-rc 5 | includedResources: "group=apps,kind=Deployment;\ 6 | group=,kind=ConfigMap;group=,kind=ServiceAccount;group=,kind=Secret;group=,kind=Service;\ 7 | group=rbac.authorization.k8s.io,kind=RoleBinding;group=rbac.authorization.k8s.io,kind=Role" 8 | defaultUpgradeStrategy: "progressive" 9 | progressive: 10 | defaultAssessmentSchedule: 11 | - kind: Pipeline 12 | schedule: "10,180,60,10" 13 | - kind: MonoVertex 14 | schedule: "10,180,60,10" 15 | - kind: InterstepBufferService 16 | schedule: "0,0,0,10" 17 | analysisRunTimeout: "1200" 18 | permittedRiders: "group=autoscaling.k8s.io,kind=VerticalPodAutoscaler;group=,kind=ConfigMap;group=autoscaling,kind=HorizontalPodAutoscaler" 19 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/utils/SquareCancelIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Tooltip } from "@mui/material"; 2 | import React from "react"; 3 | import { CheckIconProps } from "./SquareCheckIcon"; 4 | 5 | export const SquareCancelIcon = ({ tooltipTitle }: CheckIconProps) => { 6 | return ( 7 | 8 | 24 | 25 | 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | labels: "bug" 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 13 | 1. .... 14 | 2. .... 15 | 3. .... 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Environment (please complete the following information):** 24 | 25 | - Kubernetes: [e.g. v1.18.6] 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | 30 | --- 31 | 32 | 33 | 34 | **Message from the maintainers**: 35 | 36 | Impacted by this bug? Give it a 👍. We often sort issues this way to know what to prioritize. -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/utils/SquareCheckIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Tooltip } from "@mui/material"; 2 | import React from "react"; 3 | 4 | export interface CheckIconProps { 5 | tooltipTitle?: string; 6 | } 7 | export const SquareCheckIcon = ({ tooltipTitle }: CheckIconProps) => { 8 | return ( 9 | 10 | 26 | 27 | 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/dist/resources/extension-Numarollout.js/extensions-Numarollout.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @license React 3 | * react-is.production.min.js 4 | * 5 | * Copyright (c) Facebook, Inc. and its affiliates. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE file in the root directory of this source tree. 9 | */ 10 | 11 | /** 12 | * @license React 13 | * react-jsx-runtime.production.min.js 14 | * 15 | * Copyright (c) Facebook, Inc. and its affiliates. 16 | * 17 | * This source code is licensed under the MIT license found in the 18 | * LICENSE file in the root directory of this source tree. 19 | */ 20 | 21 | /** @license React v16.13.1 22 | * react-is.production.min.js 23 | * 24 | * Copyright (c) Facebook, Inc. and its affiliates. 25 | * 26 | * This source code is licensed under the MIT license found in the 27 | * LICENSE file in the root directory of this source tree. 28 | */ 29 | -------------------------------------------------------------------------------- /tests/manifests/e2e/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This snippet is used to remove the liveness and readiness probes as well as others from deployment, which is required to run the e2e tests. 2 | apiVersion: kustomize.config.k8s.io/v1beta1 3 | kind: Kustomization 4 | 5 | resources: 6 | - ../../default 7 | 8 | patches: 9 | - patch: |- 10 | - op: add 11 | path: /spec/template/spec/containers/0/imagePullPolicy 12 | value: IfNotPresent 13 | - op: remove 14 | path: /spec/template/spec/containers/0/livenessProbe 15 | value: null 16 | - op: remove 17 | path: /spec/template/spec/containers/0/readinessProbe 18 | value: null 19 | - op: remove 20 | path: /spec/template/spec/containers/0/command 21 | value: null 22 | - op: remove 23 | path: /spec/template/spec/containers/0/args 24 | value: null 25 | target: 26 | kind: Deployment 27 | name: numaplane-controller-manager 28 | -------------------------------------------------------------------------------- /internal/util/kubernetes/clientset.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/numaproj/numaplane/pkg/client/clientset/versioned" 7 | "k8s.io/client-go/dynamic" 8 | clientkube "k8s.io/client-go/kubernetes" 9 | "k8s.io/client-go/rest" 10 | ) 11 | 12 | var DynamicClient *dynamic.DynamicClient 13 | var KubernetesClient *clientkube.Clientset 14 | var NumaplaneClient *versioned.Clientset 15 | 16 | func SetClientSets(restConfig *rest.Config) error { 17 | var err error 18 | DynamicClient, err = dynamic.NewForConfig(restConfig) 19 | if err != nil { 20 | return fmt.Errorf("failed to create dynamic client: %v", err) 21 | } 22 | 23 | KubernetesClient, err = clientkube.NewForConfig(restConfig) 24 | if err != nil { 25 | return fmt.Errorf("failed to create kubernetes clientset: %v", err) 26 | } 27 | 28 | NumaplaneClient, err = versioned.NewForConfig(restConfig) 29 | if err != nil { 30 | return fmt.Errorf("failed to create numaplane clientset: %v", err) 31 | } 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/numaplane/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | type ISBServiceRolloutExpansion interface{} 21 | 22 | type MonoVertexRolloutExpansion interface{} 23 | 24 | type NumaflowControllerExpansion interface{} 25 | 26 | type NumaflowControllerRolloutExpansion interface{} 27 | 28 | type PipelineRolloutExpansion interface{} 29 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/utils/SquareCheckIcon.test.tsx: -------------------------------------------------------------------------------- 1 | // Test SquareCancelIcon component 2 | 3 | import React from "react"; 4 | import { fireEvent, render, screen, waitFor } from "@testing-library/react"; 5 | import { SquareCheckIcon } from "./SquareCheckIcon"; 6 | 7 | describe("SquareCancelIcon", () => { 8 | it("should render the SquareCancelIcon component without errors", () => { 9 | const { container } = render(); 10 | expect(container).toBeInTheDocument(); 11 | }); 12 | it("should render correctly with an empty tooltipTitle", async () => { 13 | const tooltipTitle = "test1"; 14 | render(); 15 | const icon = screen.getByTestId(`tooltip-${tooltipTitle}`); 16 | expect(icon).toBeInTheDocument(); 17 | fireEvent.mouseOver(icon); 18 | await waitFor(() => { 19 | const tooltip = screen.getByText(`${tooltipTitle}`); 20 | expect(tooltip).toBeInTheDocument(); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/utils/SquareCancelIcon.test.tsx: -------------------------------------------------------------------------------- 1 | // Test SquareCancelIcon component 2 | 3 | import React from "react"; 4 | import { fireEvent, render, screen, waitFor } from "@testing-library/react"; 5 | import { SquareCancelIcon } from "./SquareCancelIcon"; 6 | 7 | describe("SquareCancelIcon", () => { 8 | it("should render the SquareCancelIcon component without errors", () => { 9 | const { container } = render(); 10 | expect(container).toBeInTheDocument(); 11 | }); 12 | it("should render correctly with an empty tooltipTitle", async () => { 13 | const tooltipTitle = "test1"; 14 | render(); 15 | const icon = screen.getByTestId(`tooltip-${tooltipTitle}`); 16 | expect(icon).toBeInTheDocument(); 17 | fireEvent.mouseOver(icon); 18 | await waitFor(() => { 19 | const tooltip = screen.getByText(`${tooltipTitle}`); 20 | expect(tooltip).toBeInTheDocument(); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /internal/util/kubernetes/structured_util_test.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 12 | ) 13 | 14 | func TestGetSecret(t *testing.T) { 15 | scheme := runtime.NewScheme() 16 | err := corev1.AddToScheme(scheme) 17 | assert.NoError(t, err) 18 | fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects( 19 | &corev1.Secret{ 20 | ObjectMeta: metav1.ObjectMeta{ 21 | Name: "test-secret", 22 | Namespace: "test-namespace", 23 | }, 24 | Data: map[string][]byte{ 25 | "key": []byte("value"), 26 | }, 27 | }, 28 | ).Build() 29 | 30 | ctx := context.TODO() 31 | 32 | secret, err := GetSecret(ctx, fakeClient, "test-namespace", "test-secret") 33 | 34 | assert.NoError(t, err) 35 | assert.NotNil(t, secret) 36 | assert.Equal(t, "value", string(secret.Data["key"])) 37 | } 38 | -------------------------------------------------------------------------------- /hack/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | source $(dirname $0)/library.sh 8 | header "running codegen" 9 | 10 | ensure_vendor 11 | 12 | CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${REPO_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)} 13 | 14 | source "${CODEGEN_PKG}/kube_codegen.sh" 15 | 16 | THIS_PKG="github.com/numaproj/numaplane" 17 | 18 | subheader "running deepcopy gen" 19 | 20 | kube::codegen::gen_helpers \ 21 | --boilerplate "${REPO_ROOT}/hack/boilerplate.go.txt" \ 22 | "${REPO_ROOT}/pkg/apis" 23 | 24 | subheader "running clients gen" 25 | 26 | kube::codegen::gen_client \ 27 | --with-watch \ 28 | --output-dir "${REPO_ROOT}/pkg/client" \ 29 | --output-pkg "${THIS_PKG}/pkg/client" \ 30 | --boilerplate "${REPO_ROOT}/hack/boilerplate.go.txt" \ 31 | --one-input-api "numaplane/v1alpha1" \ 32 | "${REPO_ROOT}/pkg/apis" 33 | 34 | # gofmt the tree 35 | subheader "running gofmt" 36 | find . -name "*.go" -type f -print0 | xargs -0 gofmt -s -w 37 | 38 | -------------------------------------------------------------------------------- /config/kustomize/examples/transformer/my-pipelinerollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: numaplane.numaproj.io/v1alpha1 2 | kind: PipelineRollout 3 | metadata: 4 | name: simple-pipeline 5 | spec: 6 | pipeline: 7 | spec: 8 | vertices: 9 | - name: in 10 | source: 11 | generator: 12 | rpu: 5 13 | duration: 1s 14 | - name: my-udf 15 | udf: 16 | container: 17 | image: my-pipeline/my-udf:v0.1 18 | volumeMounts: 19 | - name: config-volume 20 | mountPath: /etc/config 21 | - name: secret-volume 22 | mountPath: /etc/secrets 23 | volumes: 24 | - name: config-volume 25 | configMap: 26 | name: my-cm 27 | - name: secret-volume 28 | secret: 29 | secretName: my-secret 30 | - name: out 31 | sink: 32 | log: {} 33 | edges: 34 | - from: in 35 | to: my-udf 36 | - from: my-udf 37 | to: out 38 | -------------------------------------------------------------------------------- /config/manager/controller-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: numaplane-controller-config 5 | data: 6 | config.yaml: | 7 | # Supported log levels are: VERBOSE, DEBUG, INFO, WARN. ERROR, FATAL will be printed regardless of the log level 8 | logLevel: INFO 9 | includedResources: "group=apps,kind=Deployment;\ 10 | group=,kind=ConfigMap;group=,kind=ServiceAccount;group=,kind=Secret;group=,kind=Service;\ 11 | group=rbac.authorization.k8s.io,kind=RoleBinding;group=rbac.authorization.k8s.io,kind=Role" 12 | defaultUpgradeStrategy: "progressive" 13 | progressive: 14 | defaultAssessmentSchedule: 15 | - kind: Pipeline 16 | schedule: "10,200,60,10" 17 | - kind: MonoVertex 18 | schedule: "10,200,60,10" 19 | - kind: InterstepBufferService 20 | schedule: "0,0,0,10" 21 | analysisRunTimeout: 1200 22 | permittedRiders: "group=autoscaling.k8s.io,kind=VerticalPodAutoscaler;group=autoscaling,kind=HorizontalPodAutoscaler" 23 | pipeline: 24 | forceDrainFailureWaitDuration: 15 25 | maxRecyclableDurationMinutes: 240 # 4 hours -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/default/ArgoRolloutComponent.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react"; 2 | import { CANARY } from "../../utils/Constants"; 3 | import { ArgoRolloutComponent } from "./ArgoRolloutComponent"; 4 | const rolloutParams = { 5 | strategy: CANARY, 6 | setWeight: "0", 7 | actualWeight: "100", 8 | step: "1", 9 | }; 10 | 11 | //Mock the ArgoRolloutSummary component 12 | jest.mock("./ArgoRolloutSummary", () => { 13 | return { 14 | ArgoRolloutSummary: () => { 15 | return
ArgoRolloutSummary
; 16 | }, 17 | }; 18 | }); 19 | 20 | //Mock the ArgoRolloutContainers component 21 | jest.mock("./ArgoRolloutContainers", () => { 22 | return { 23 | ArgoRolloutContainers: () => { 24 | return
ArgoRolloutContainers
; 25 | }, 26 | }; 27 | }); 28 | 29 | describe("ArgoRolloutComponent", () => { 30 | it("should render ArgoRolloutComponent", () => { 31 | render(); 32 | expect(screen.getByText("ArgoRolloutSummary")).toBeInTheDocument(); 33 | expect(screen.getByText("ArgoRolloutContainers")).toBeInTheDocument(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /internal/controller/config/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | type options struct { 20 | configsPath string 21 | configFileName string 22 | fileType string 23 | } 24 | 25 | type Option func(*options) 26 | 27 | func defaultOptions() *options { 28 | return &options{ 29 | fileType: "yaml", 30 | } 31 | } 32 | 33 | func WithConfigsPath(configsPath string) Option { 34 | return func(o *options) { 35 | o.configsPath = configsPath 36 | } 37 | } 38 | 39 | func WithConfigFileName(configFileName string) Option { 40 | return func(o *options) { 41 | o.configFileName = configFileName 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/utils/Constants.tsx: -------------------------------------------------------------------------------- 1 | import exp from "constants"; 2 | 3 | export const HEALTHY = "HEALTHY"; 4 | export const UNHEALTHY = "UNHEALTHY"; 5 | export const PROGRESSING = "PROGRESSING"; 6 | export const SUCCESSFUL = "SUCCESSFUL"; 7 | export const RUNNING = "RUNNING"; 8 | export const FAILURE = "FAILURE"; 9 | export const DEGRADED = "DEGRADED"; 10 | export const ERROR = "ERROR"; 11 | export const POD = "Pod"; 12 | export const REPLICA_SET = "ReplicaSet"; 13 | export const ROLLOUT = "Rollout"; 14 | export const CANARY = "Canary"; 15 | export const REVISION = "REVISION"; 16 | export const STATUS_REASON = "Status Reason"; 17 | export const ANALYSIS_RUN = "AnalysisRun"; 18 | export const INTUIT_DOMAIN = "intuit.com"; 19 | export const ISB_SERVICE_ROLLOUT = "ISBServiceRollout"; 20 | export const NUMAFLOW_CONTROLLER_ROLLOUT = "NumaflowControllerRollout"; 21 | export const PIPELINE_ROLLOUT = "PipelineRollout"; 22 | export const MONOVERTEX_ROLLOUT = "MonoVertexRollout"; 23 | export const ISB_KUBERNETES = "isbsvc"; 24 | export const CONTROLLER_MANAGER = "controller-manager"; 25 | export const VERTEX = "vertex"; 26 | export const MONO_VERTEX = "mono-vertex"; 27 | export const KUBERNETES_APP = "app.kubernetes.io/component"; 28 | export const ISB = "InterStepBufferService"; 29 | -------------------------------------------------------------------------------- /internal/util/queue.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "k8s.io/client-go/util/workqueue" 5 | ) 6 | 7 | // rateLimitingQueue is a wrapper around RateLimitingInterface, which is essentially a queue that offers the following: 8 | // - if used correctly, only worker receives a given key at a time 9 | // - keys are rate limited to create fairness between keys 10 | // - keys can be scheduled to be re-processed at a certain time 11 | // TODO: Note that this wrapper doesn't provide any added benefit beyond the RateLimitingInterface that it wraps, but we can add 12 | // metrics for queue length to this by imitating this: https://github.com/argoproj/argo-workflows/blob/main/workflow/metrics/work_queue.go 13 | type rateLimitingQueue struct { 14 | workqueue.TypedRateLimitingInterface[interface{}] 15 | workerType string 16 | } 17 | 18 | func NewWorkQueue(queueName string) workqueue.TypedRateLimitingInterface[interface{}] { 19 | return rateLimitingQueue{ 20 | TypedRateLimitingInterface: workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[interface{}]()), 21 | workerType: queueName, 22 | } 23 | } 24 | 25 | func (w rateLimitingQueue) Get() (interface{}, bool) { 26 | item, shutdown := w.TypedRateLimitingInterface.Get() 27 | return item, shutdown 28 | } 29 | 30 | func (w rateLimitingQueue) Done(item interface{}) { 31 | w.TypedRateLimitingInterface.Done(item) 32 | } 33 | -------------------------------------------------------------------------------- /internal/sync/testdata/pipeline-live.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: numaflow.numaproj.io/v1alpha1 2 | kind: Pipeline 3 | metadata: 4 | labels: 5 | numaplane.numaproj.io/tracking-id: my-example 6 | creationTimestamp: "2024-03-08T22:09:18Z" 7 | finalizers: 8 | - pipeline-controller 9 | generation: 3 10 | name: simple-pipeline 11 | namespace: numaflow-pipeline-example 12 | resourceVersion: "716917" 13 | uid: a38b5758-de8f-483a-9fc4-b46acb8f36c8 14 | spec: 15 | edges: 16 | - from: in 17 | to: cat 18 | - from: cat 19 | to: out 20 | lifecycle: {} 21 | vertices: 22 | - name: in 23 | source: 24 | generator: 25 | duration: 1s 26 | rpu: 5 27 | - name: cat 28 | udf: 29 | container: 30 | image: "quay.io/numaio/numaflow-go/map-cat:stable" 31 | imagePullPolicy: Always, 32 | - name: out 33 | sink: 34 | log: {} 35 | watermark: {} 36 | status: 37 | conditions: 38 | - lastTransitionTime: "2024-03-09T02:17:58Z" 39 | message: Successful 40 | reason: Successful 41 | status: "True" 42 | type: Configured 43 | - lastTransitionTime: "2024-03-09T02:17:58Z" 44 | message: Successful 45 | reason: Successful 46 | status: "True" 47 | type: Deployed 48 | lastUpdated: "2024-03-09T02:17:58Z" 49 | phase: Running 50 | sinkCount: 1 51 | sourceCount: 1 52 | udfCount: 1 53 | vertexCount: 3 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.23 as builder 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | WORKDIR /workspace 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | # cache deps before building and copying source so that we don't need to re-download as much 11 | # and so that source changes don't invalidate our downloaded layer 12 | RUN go mod download 13 | 14 | # Copy the go source 15 | COPY cmd/main.go cmd/main.go 16 | COPY pkg/ pkg/ 17 | COPY internal/ internal/ 18 | 19 | # Add a go build cache. The persistent cache helps speed up build steps, 20 | # especially steps that involve installing packages using a package manager. 21 | ENV GOCACHE=/root/.cache/go-build 22 | # Build 23 | # the GOARCH doesn't have a default value to allow the binary be built according to the host where the command 24 | # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 25 | # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 26 | # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 27 | RUN --mount=type=cache,target="/root/.cache/go-build" CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -o manager cmd/main.go 28 | 29 | # Use alpine as minimal base image to package the manager binary 30 | FROM alpine 31 | WORKDIR / 32 | COPY --from=builder /workspace/manager . 33 | 34 | ENTRYPOINT ["/manager"] 35 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: github.com.numaproj 6 | layout: 7 | - go.kubebuilder.io/v4 8 | projectName: numaplane 9 | repo: github.com/numaproj/numaplane 10 | resources: 11 | - api: 12 | crdVersion: v1alpha1 13 | namespaced: true 14 | controller: true 15 | domain: github.com.numaproj 16 | group: numaplane.numaproj.io 17 | kind: PipelineRollout 18 | path: github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1 19 | version: v1alpha1 20 | - api: 21 | crdVersion: v1alpha1 22 | namespaced: true 23 | controller: true 24 | domain: github.com.numaproj 25 | group: numaplane.numaproj.io 26 | kind: NumaflowControllerRollout 27 | path: github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1 28 | version: v1alpha1 29 | - api: 30 | crdVersion: v1alpha1 31 | namespaced: true 32 | controller: true 33 | domain: github.com.numaproj 34 | group: numaplane.numaproj.io 35 | kind: ISBServiceRollout 36 | path: github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1 37 | version: v1alpha1 38 | - api: 39 | crdVersion: v1alpha1 40 | namespaced: true 41 | controller: true 42 | domain: github.com.numaproj 43 | group: numaplane.numaproj.io 44 | kind: NumaflowController 45 | path: github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1 46 | version: v1alpha1 47 | version: "3" 48 | -------------------------------------------------------------------------------- /hack/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | /* 4 | Copyright 2023. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // This package contains code generation utilities 20 | // This package imports things required by build scripts, to force `go mod` to see them as dependencies 21 | package tools 22 | 23 | import ( 24 | _ "github.com/ahmetb/gen-crd-api-reference-docs" 25 | _ "github.com/go-swagger/go-swagger/cmd/swagger" 26 | _ "github.com/gogo/protobuf/gogoproto" 27 | _ "github.com/gogo/protobuf/protoc-gen-gogo" 28 | _ "github.com/gogo/protobuf/protoc-gen-gogofast" 29 | _ "golang.org/x/tools/cmd/goimports" 30 | _ "k8s.io/code-generator" 31 | _ "k8s.io/code-generator/cmd/client-gen" 32 | _ "k8s.io/code-generator/cmd/deepcopy-gen" 33 | _ "k8s.io/code-generator/cmd/defaulter-gen" 34 | _ "k8s.io/code-generator/cmd/go-to-protobuf" 35 | _ "k8s.io/code-generator/cmd/go-to-protobuf/protoc-gen-gogo" 36 | _ "k8s.io/code-generator/cmd/informer-gen" 37 | _ "k8s.io/code-generator/cmd/lister-gen" 38 | _ "sigs.k8s.io/controller-tools/cmd/controller-gen" 39 | ) 40 | -------------------------------------------------------------------------------- /internal/util/logger/base_logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "strings" 5 | "sync" 6 | 7 | "github.com/numaproj/numaplane/internal/controller/config" 8 | ) 9 | 10 | // Singleton Base Logger from which other loggers can be derived 11 | // maintains current log level for the application as a whole 12 | 13 | var ( 14 | baseLogger *NumaLogger = New() 15 | baseLoggerMutex sync.RWMutex 16 | ) 17 | 18 | // SetBaseLogger is intended to be set once when application starts 19 | func SetBaseLogger(nl *NumaLogger) { 20 | baseLoggerMutex.Lock() 21 | defer baseLoggerMutex.Unlock() 22 | 23 | baseLogger = nl.DeepCopy() 24 | // if the Global ConfigMap changes, update the BaseLogger's log level 25 | config.GetConfigManagerInstance().RegisterCallback(refreshBaseLoggerLevel) 26 | } 27 | 28 | // Get the standard NumaLogger with current Log Level - deep copy it in case user modifies it 29 | func GetBaseLogger() *NumaLogger { 30 | baseLoggerMutex.RLock() 31 | defer baseLoggerMutex.RUnlock() 32 | return baseLogger.DeepCopy() 33 | } 34 | 35 | // Refresh the Logger's LogLevel based on current config value 36 | func refreshBaseLoggerLevel(newConfig config.GlobalConfig) { 37 | 38 | // if it changed, propagate it to our Base Logger 39 | if LogLevelMap[strings.ToUpper(newConfig.LogLevel)] != baseLogger.LogLevel { 40 | 41 | baseLoggerMutex.Lock() 42 | defer baseLoggerMutex.Unlock() 43 | 44 | // update the logger with the new log level 45 | baseLogger.SetLevel(LogLevelMap[strings.ToUpper(newConfig.LogLevel)]) 46 | baseLogger.Infof("updated log level=%s", newConfig.LogLevel) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package internalinterfaces 19 | 20 | import ( 21 | time "time" 22 | 23 | versioned "github.com/numaproj/numaplane/pkg/client/clientset/versioned" 24 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | cache "k8s.io/client-go/tools/cache" 27 | ) 28 | 29 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 30 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 31 | 32 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 33 | type SharedInformerFactory interface { 34 | Start(stopCh <-chan struct{}) 35 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 36 | } 37 | 38 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 39 | type TweakListOptionsFunc func(*v1.ListOptions) 40 | -------------------------------------------------------------------------------- /internal/controller/common/rollout_object.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package common 18 | 19 | import ( 20 | "fmt" 21 | "strconv" 22 | "strings" 23 | 24 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 | "k8s.io/apimachinery/pkg/runtime/schema" 26 | 27 | apiv1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 28 | ) 29 | 30 | type RolloutObject interface { 31 | GetRolloutGVR() metav1.GroupVersionResource 32 | 33 | GetRolloutGVK() schema.GroupVersionKind 34 | 35 | GetChildGVR() metav1.GroupVersionResource 36 | 37 | GetChildGVK() schema.GroupVersionKind 38 | 39 | GetRolloutObjectMeta() *metav1.ObjectMeta 40 | 41 | GetRolloutStatus() *apiv1.Status 42 | } 43 | 44 | // assume child name is "-" 45 | func GetRolloutParentName(childName string) (string, error) { 46 | 47 | index := strings.LastIndex(childName, "-") 48 | if index > 0 && index < len(childName)-1 { 49 | _, err := strconv.Atoi(childName[index+1:]) 50 | if err == nil { 51 | return childName[:index], nil 52 | } 53 | } 54 | return "", fmt.Errorf("unexpected child name %q doesn't end with '-'", childName) 55 | } 56 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/default/ArgoRolloutContainers.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react"; 2 | import { ArgoRolloutContainers } from "./ArgoRolloutContainers"; 3 | import { RolloutComponentContext } from "../RolloutComponentWrapper"; 4 | import { 5 | mockControllerRolloutProps, 6 | mockISBRolloutProps, 7 | mockPipelineRolloutProps, 8 | } from "../../../mocks/mockProps"; 9 | 10 | describe("ArgoRolloutContainers", () => { 11 | it("should render for isb rollout", () => { 12 | render( 13 | 14 | 15 | 16 | ); 17 | expect( 18 | screen.getByDisplayValue("docker.intuit.com/docker-rmt/nats:2.10.11") 19 | ).toBeInTheDocument(); 20 | }); 21 | 22 | it("should render for controller rollout", () => { 23 | render( 24 | 27 | 28 | 29 | ); 30 | expect( 31 | screen.getByDisplayValue( 32 | "docker.intuit.com/quay-rmt/numaproj/numaflow:v1.2.1" 33 | ) 34 | ).toBeInTheDocument(); 35 | }); 36 | 37 | it("should render for pipeline rollout", () => { 38 | render( 39 | 40 | 41 | 42 | ); 43 | expect( 44 | screen.getByDisplayValue( 45 | "docker.intuit.com/quay-rmt/numaproj/numaflow:v1.2.1" 46 | ) 47 | ).toBeInTheDocument(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /tests/manifests/default/prometheus-monitors.yaml: -------------------------------------------------------------------------------- 1 | # These are used by the Prometheus Operator so it can query Numaflow Pipeline and MonoVertex Pods for metrics 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | labels: 6 | app.kubernetes.io/part-of: numaflow 7 | name: numaflow-pipeline-metrics 8 | spec: 9 | namespaceSelector: 10 | any: true 11 | endpoints: 12 | - scheme: https 13 | port: metrics 14 | targetPort: 2469 15 | tlsConfig: 16 | insecureSkipVerify: true 17 | selector: 18 | matchLabels: 19 | app.kubernetes.io/component: vertex 20 | app.kubernetes.io/managed-by: vertex-controller 21 | app.kubernetes.io/part-of: numaflow 22 | matchExpressions: 23 | - key: numaflow.numaproj.io/pipeline-name 24 | operator: Exists 25 | - key: numaflow.numaproj.io/vertex-name 26 | operator: Exists 27 | --- 28 | apiVersion: monitoring.coreos.com/v1 29 | kind: PodMonitor 30 | metadata: 31 | labels: 32 | app.kubernetes.io/part-of: numaflow 33 | name: numaflow-mono-vertex-metrics 34 | spec: 35 | namespaceSelector: 36 | any: true 37 | podMetricsEndpoints: 38 | - scheme: https 39 | path: /metrics 40 | port: metrics 41 | tlsConfig: 42 | insecureSkipVerify: true 43 | selector: 44 | matchExpressions: 45 | - key: app.kubernetes.io/part-of 46 | operator: In 47 | values: 48 | - numaflow 49 | - key: app.kubernetes.io/component 50 | operator: In 51 | values: 52 | - mono-vertex 53 | - key: app.kubernetes.io/managed-by 54 | operator: In 55 | values: 56 | - mono-vertex-controller 57 | - key: numaflow.numaproj.io/mono-vertex-name 58 | operator: Exists 59 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/PipelineRollout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { RolloutComponentContext } from "./RolloutComponentWrapper"; 3 | import { Box } from "@mui/material"; 4 | import { SquareCheckIcon } from "../utils/SquareCheckIcon"; 5 | import { SquareCancelIcon } from "../utils/SquareCancelIcon"; 6 | 7 | export const PipelineRollout = () => { 8 | const { props, kindToNodeMap } = useContext(RolloutComponentContext); 9 | const conditions = props?.resource?.status?.conditions 10 | const hasChildResourcesHealthy = conditions.some(condition => condition.type === 'ChildResourcesHealthy'); 11 | 12 | return ( 13 | 14 | 15 | {kindToNodeMap.get("Pipeline")?.map((node) => { 16 | return ( 17 | 18 | Pipeline Name: {node?.name} 19 | 32 | Pipeline Status:{" "} 33 | {hasChildResourcesHealthy ? ( 34 | 35 | ) : ( 36 | 37 | )} 38 | 39 | 40 | ); 41 | })} 42 | 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /hack/library.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | readonly REPO_ROOT="$(git rev-parse --show-toplevel)" 4 | 5 | # Display a box banner. 6 | # Parameters: $1 - character to use for the box. 7 | # $2 - banner message. 8 | function make_banner() { 9 | local msg="$1$1$1$1 $2 $1$1$1$1" 10 | local border="${msg//[-0-9A-Za-z _.,:\/()]/$1}" 11 | echo -e "${border}\n${msg}\n${border}" 12 | } 13 | 14 | # Simple header for logging purposes. 15 | function header() { 16 | local upper="$(echo $1 | tr a-z A-Z)" 17 | make_banner "+" "${upper}" 18 | } 19 | 20 | # Simple subheader for logging purposes. 21 | function subheader() { 22 | make_banner "-" "$1" 23 | } 24 | 25 | # Simple warning banner for logging purposes. 26 | function warning() { 27 | make_banner "!" "$1" 28 | } 29 | 30 | function make_fake_paths() { 31 | FAKE_GOPATH="$(mktemp -d)" 32 | trap 'rm -rf ${FAKE_GOPATH}' EXIT 33 | FAKE_REPOPATH="${FAKE_GOPATH}/src/github.com/numaproj/numaplane" 34 | mkdir -p "$(dirname "${FAKE_REPOPATH}")" && ln -s "${REPO_ROOT}" "${FAKE_REPOPATH}" 35 | } 36 | 37 | ensure_vendor() { 38 | go mod vendor 39 | } 40 | 41 | ensure_pandoc() { 42 | if [ "`command -v pandoc`" = "" ]; then 43 | warning "Please install pandoc with - brew install pandoc" 44 | exit 1 45 | fi 46 | } 47 | 48 | ensure_protobuf() { 49 | if [ "`command -v protoc`" = "" ]; then 50 | warning "Please install protobuf with - brew install protobuf" 51 | exit 1 52 | fi 53 | } 54 | 55 | ensure_node(){ 56 | if [ "`command -v node`" = "" ]; then 57 | warning "Please install node with - brew install node" 58 | exit 1 59 | fi 60 | } 61 | 62 | ensure_yarn(){ 63 | if [ "`command -v yarn`" = "" ]; then 64 | warning "Please install yarn with - brew install yarn" 65 | exit 1 66 | fi 67 | } -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/MonovertexRollout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { RolloutComponentContext } from "./RolloutComponentWrapper"; 3 | import { Box } from "@mui/material"; 4 | import { SquareCheckIcon } from "../utils/SquareCheckIcon"; 5 | import { SquareCancelIcon } from "../utils/SquareCancelIcon"; 6 | 7 | export const MonovertexRollout = () => { 8 | const { props, kindToNodeMap } = useContext(RolloutComponentContext); 9 | const conditions = props?.resource?.status?.conditions 10 | const hasChildResourcesHealthy = conditions.some(condition => condition.type === 'ChildResourcesHealthy'); 11 | 12 | return ( 13 | 14 | 15 | {kindToNodeMap.get("MonoVertexRollout")?.map((node) => { 16 | return ( 17 | 18 | Monovertex Rollout Name: {node?.name} 19 | 32 | Monovertex Status:{" "} 33 | {hasChildResourcesHealthy ? ( 34 | 35 | ) : ( 36 | 37 | )} 38 | 39 | 40 | ); 41 | })} 42 | 43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /internal/controller/common/rollout_object_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package common 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | ) 23 | 24 | func TestGetRolloutParentName(t *testing.T) { 25 | tests := []struct { 26 | childName string 27 | expectedParent string 28 | expectError bool 29 | }{ 30 | {"parent-123", "parent", false}, // Valid case 31 | {"parent-child", "", true}, // Invalid case: no number 32 | {"parent-", "", true}, // Edge case: hyphen without number 33 | {"parent-0", "parent", false}, // Valid case: zero as number 34 | {"parent-abc", "", true}, // Invalid case: non-numeric suffix 35 | {"-123", "", true}, // Invalid case: no parent name 36 | {"parent-123-extra", "", true}, // Invalid case: extra suffix 37 | } 38 | 39 | for _, tt := range tests { 40 | t.Run(fmt.Sprintf("childName=%s", tt.childName), func(t *testing.T) { 41 | parentName, err := GetRolloutParentName(tt.childName) 42 | if (err != nil) != tt.expectError { 43 | t.Errorf("expected error: %v, got: %v", tt.expectError, err) 44 | } 45 | if parentName != tt.expectedParent { 46 | t.Errorf("expected parent name: %q, got: %q", tt.expectedParent, parentName) 47 | } 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/numaplane/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package numaplane 19 | 20 | import ( 21 | internalinterfaces "github.com/numaproj/numaplane/pkg/client/informers/externalversions/internalinterfaces" 22 | v1alpha1 "github.com/numaproj/numaplane/pkg/client/informers/externalversions/numaplane/v1alpha1" 23 | ) 24 | 25 | // Interface provides access to each of this group's versions. 26 | type Interface interface { 27 | // V1alpha1 provides access to shared informers for resources in V1alpha1. 28 | V1alpha1() v1alpha1.Interface 29 | } 30 | 31 | type group struct { 32 | factory internalinterfaces.SharedInformerFactory 33 | namespace string 34 | tweakListOptions internalinterfaces.TweakListOptionsFunc 35 | } 36 | 37 | // New returns a new Interface. 38 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 39 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 40 | } 41 | 42 | // V1alpha1 returns a new v1alpha1.Interface. 43 | func (g *group) V1alpha1() v1alpha1.Interface { 44 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) 45 | } 46 | -------------------------------------------------------------------------------- /internal/controller/common/numaflowtypes/vertex.go: -------------------------------------------------------------------------------- 1 | package numaflowtypes 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/numaproj/numaplane/internal/util" 7 | apiv1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 8 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 9 | ) 10 | 11 | // AbstractVertex keeps track of minimum number of fields we need to know about in Numaflow's AbstractVertex, which are presumed not to change from version to version 12 | type AbstractVertex struct { 13 | Name string `json:"name"` 14 | Scale Scale `json:"scale,omitempty"` 15 | } 16 | 17 | // Scale keeps track of minimum number of fields we need to know about in Numaflow's Scale struct, which are presumed not to change from version to version 18 | type Scale struct { 19 | // Minimum replicas. 20 | Min *int32 `json:"min,omitempty"` 21 | // Maximum replicas. 22 | Max *int32 `json:"max,omitempty"` 23 | } 24 | 25 | func ExtractScaleMinMax(object map[string]any, pathToScale []string) (*apiv1.ScaleDefinition, error) { 26 | 27 | scaleDef, foundScale, err := unstructured.NestedMap(object, pathToScale...) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | if !foundScale { 33 | return nil, nil 34 | } 35 | scaleMinMax := apiv1.ScaleDefinition{} 36 | minInterface := scaleDef["min"] 37 | maxInterface := scaleDef["max"] 38 | if minInterface != nil { 39 | min, valid := util.ToInt64(minInterface) 40 | if !valid { 41 | return nil, fmt.Errorf("scale min %+v of unexpected type", minInterface) 42 | } 43 | scaleMinMax.Min = &min 44 | } 45 | if maxInterface != nil { 46 | max, valid := util.ToInt64(maxInterface) 47 | if !valid { 48 | return nil, fmt.Errorf("scale max %+v of unexpected type", maxInterface) 49 | } 50 | scaleMinMax.Max = &max 51 | } 52 | disabledInterface := scaleDef["disabled"] 53 | if disabledInterface != nil && disabledInterface.(bool) { 54 | scaleMinMax.Disabled = true 55 | } 56 | 57 | return &scaleMinMax, nil 58 | } 59 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ArgoPropType } from "./ArgoPropType"; 3 | import { RolloutComponentWrapper } from "./rollout/RolloutComponentWrapper"; 4 | 5 | export const roundNumber = (num: number, dig: number): number => { 6 | return Math.round(num * 10 ** dig) / 10 ** dig; 7 | }; 8 | 9 | export const Extension = (props: ArgoPropType) => { 10 | return ( 11 | 12 | 17 | 18 | ); 19 | }; 20 | 21 | export const component = Extension; 22 | 23 | // Register the component extension in ArgoCD 24 | 25 | // registerResourceExtension(component: ExtensionComponent, group: string, kind: string, tabTitle: string) 26 | ((window: any) => { 27 | window?.extensionsAPI?.registerResourceExtension( 28 | component, 29 | "numaplane.numaproj.io", 30 | "ISBServiceRollout", 31 | "Numarollout", 32 | { icon: "fa fa-window-restore" } 33 | ); 34 | window?.extensionsAPI?.registerResourceExtension( 35 | component, 36 | "numaplane.numaproj.io", 37 | "NumaflowControllerRollout", 38 | "Numarollout", 39 | { icon: "fa fa-window-restore" } 40 | ); 41 | window?.extensionsAPI?.registerResourceExtension( 42 | component, 43 | "numaplane.numaproj.io", 44 | "PipelineRollout", 45 | "Numarollout", 46 | { icon: "fa fa-window-restore" } 47 | ); 48 | window?.extensionsAPI?.registerResourceExtension( 49 | component, 50 | "numaplane.numaproj.io", 51 | "MonoVertexRollout", 52 | "Numarollout", 53 | { icon: "fa fa-window-restore" } 54 | ); 55 | window?.extensionsAPI?.registerResourceExtension( 56 | component, 57 | "*", 58 | "Deployment", 59 | "Numarollout", 60 | { icon: "fa fa-window-restore" } 61 | ); 62 | })(window); 63 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: numaplane-role 6 | rules: 7 | - apiGroups: ["numaflow.numaproj.io"] 8 | resources: ["*"] 9 | verbs: ["*"] 10 | - apiGroups: ["numaplane.numaproj.io"] 11 | resources: ["*"] 12 | verbs: ["*"] 13 | - apiGroups: [""] 14 | resources: 15 | - configmaps 16 | - serviceaccounts 17 | - secrets 18 | - services 19 | verbs: 20 | - '*' 21 | - apiGroups: ["rbac.authorization.k8s.io"] 22 | resources: 23 | - rolebindings 24 | - roles 25 | verbs: 26 | - '*' 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - namespaces 31 | verbs: 32 | - 'get' 33 | - 'list' 34 | - 'watch' 35 | - apiGroups: ["apps"] 36 | resources: 37 | - deployments 38 | verbs: 39 | - '*' 40 | - apiGroups: ["apps"] 41 | resources: 42 | - statefulsets 43 | verbs: 44 | - 'get' 45 | - 'list' 46 | - 'watch' 47 | - apiGroups: ["policy"] 48 | resources: ["poddisruptionbudgets"] 49 | verbs: ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"] 50 | - apiGroups: 51 | - "" 52 | resources: 53 | - events 54 | verbs: 55 | - 'create' 56 | - 'patch' 57 | - apiGroups: 58 | - "" 59 | resources: 60 | - pods 61 | verbs: 62 | - 'list' 63 | - apiGroups: ["argoproj.io"] 64 | resources: 65 | - analysisruns 66 | verbs: ["*"] 67 | - apiGroups: ["argoproj.io"] 68 | resources: 69 | - analysistemplates 70 | - clusteranalysistemplates 71 | verbs: 72 | - 'get' 73 | - 'list' 74 | - 'watch' 75 | - apiGroups: ["autoscaling.k8s.io"] 76 | resources: 77 | - verticalpodautoscalers 78 | verbs: ["*"] 79 | - apiGroups: ["autoscaling"] 80 | resources: 81 | - horizontalpodautoscalers 82 | verbs: ["*"] 83 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | branches: 7 | - main 8 | - dev-* 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | build-push-linux-multi: 16 | name: Build & push linux/amd64 and linux/arm64 17 | runs-on: ubuntu-latest 18 | if: github.repository == 'numaproj/numaplane' 19 | strategy: 20 | matrix: 21 | target: [ numaplane ] 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v2 26 | 27 | - name: Registry Login 28 | uses: docker/login-action@v2 29 | with: 30 | registry: quay.io 31 | username: ${{ secrets.QUAYIO_USERNAME }} 32 | password: ${{ secrets.QUAYIO_PASSWORD }} 33 | 34 | - name: set Version 35 | id: version 36 | run: | 37 | tag=$(basename $GITHUB_REF) 38 | if [ $tag = "main" ]; then 39 | tag="latest" 40 | fi 41 | echo "VERSION=$tag" >> $GITHUB_OUTPUT 42 | - name: Container build and push with arm64/amd64 43 | run: make image docker-push IMAGE_FULL_PATH=${{ secrets.QUAYIO_ORG }}/numaplane-controller:${{ steps.version.outputs.VERSION }} CONTAINER_TOOL=docker 44 | 45 | Release: 46 | runs-on: ubuntu-latest 47 | if: github.repository == 'numaproj/numaplane' 48 | needs: [ build-push-linux-multi ] 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v4 52 | - name: Registry Login 53 | uses: docker/login-action@v2 54 | with: 55 | registry: quay.io 56 | username: ${{ secrets.QUAYIO_USERNAME }} 57 | password: ${{ secrets.QUAYIO_PASSWORD }} 58 | - name: Create Release 59 | uses: softprops/action-gh-release@v1 60 | if: startsWith(github.ref, 'refs/tags/') 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | -------------------------------------------------------------------------------- /tests/e2e/coverage/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.23 as builder 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | WORKDIR github.com/numaproj/numaplane 7 | # Copy the Go Modules manifests 8 | COPY go.mod go.mod 9 | COPY go.sum go.sum 10 | # cache deps before building and copying source so that we don't need to re-download as much 11 | # and so that source changes don't invalidate our downloaded layer 12 | RUN go mod download 13 | 14 | # Copy the go source 15 | COPY cmd/main.go cmd/main.go 16 | COPY pkg/ pkg/ 17 | COPY internal/ internal/ 18 | 19 | # Add a go build cache. The persistent cache helps speed up build steps, 20 | # especially steps that involve installing packages using a package manager. 21 | ENV GOCACHE=/root/.cache/go-build 22 | # Build 23 | # the GOARCH doesn't have a default value to allow the binary be built according to the host where the command 24 | # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 25 | # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 26 | # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 27 | RUN --mount=type=cache,target="/root/.cache/go-build" CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -cover -covermode=atomic -coverpkg=./... -o manager cmd/main.go 28 | 29 | ## Use golang as image to package the manager binary 30 | ## This image will be used to run the e2e tests and generate the coverage report 31 | FROM golang:1.23 32 | WORKDIR / 33 | COPY --from=builder /go/github.com/numaproj/numaplane/manager . 34 | # Setup coverage directory to store the coverage report generated by while running the e2e tests 35 | RUN mkdir coverage 36 | ENV GOCOVERDIR=coverage 37 | 38 | # Copy entrypoint script which will be used to run the numaplane process in background 39 | COPY tests/e2e/coverage/entrypoint.sh /entrypoint.sh 40 | RUN chmod +x /entrypoint.sh 41 | ENTRYPOINT ["/entrypoint.sh"] 42 | -------------------------------------------------------------------------------- /internal/controller/config/user_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "strings" 23 | ) 24 | 25 | type USDEUserStrategy string 26 | 27 | const ( 28 | ProgressiveStrategyID USDEUserStrategy = "progressive" 29 | PPNDStrategyID USDEUserStrategy = "pause-and-drain" 30 | NoStrategyID USDEUserStrategy = "no-strategy" 31 | ) 32 | 33 | func (s *USDEUserStrategy) UnmarshalJSON(data []byte) (err error) { 34 | var usdeUserStrategyStr string 35 | if err := json.Unmarshal(data, &usdeUserStrategyStr); err != nil { 36 | return err 37 | } 38 | 39 | allowedValues := map[USDEUserStrategy]struct{}{ 40 | ProgressiveStrategyID: {}, 41 | PPNDStrategyID: {}, 42 | NoStrategyID: {}} 43 | 44 | if strings.TrimSpace(usdeUserStrategyStr) == "" { 45 | usdeUserStrategyStr = string(NoStrategyID) 46 | } 47 | 48 | // Make sure the string is one of the possible strategy values 49 | _, found := allowedValues[USDEUserStrategy(usdeUserStrategyStr)] 50 | if !found { 51 | return fmt.Errorf("invalid strategy '%s' (allowed values: %+v)", usdeUserStrategyStr, allowedValues) 52 | } 53 | 54 | *s = USDEUserStrategy(usdeUserStrategyStr) 55 | 56 | return nil 57 | } 58 | 59 | func (s USDEUserStrategy) IsValid() bool { 60 | switch s { 61 | case ProgressiveStrategyID, PPNDStrategyID, NoStrategyID: 62 | return true 63 | default: 64 | return false 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | numaplanev1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | schema "k8s.io/apimachinery/pkg/runtime/schema" 25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 | ) 28 | 29 | var scheme = runtime.NewScheme() 30 | var codecs = serializer.NewCodecFactory(scheme) 31 | 32 | var localSchemeBuilder = runtime.SchemeBuilder{ 33 | numaplanev1alpha1.AddToScheme, 34 | } 35 | 36 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 37 | // of clientsets, like in: 38 | // 39 | // import ( 40 | // "k8s.io/client-go/kubernetes" 41 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 42 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 43 | // ) 44 | // 45 | // kclientset, _ := kubernetes.NewForConfig(c) 46 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 47 | // 48 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 49 | // correctly. 50 | var AddToScheme = localSchemeBuilder.AddToScheme 51 | 52 | func init() { 53 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 54 | utilruntime.Must(AddToScheme(scheme)) 55 | } 56 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/default/ArgoRolloutComponent.css: -------------------------------------------------------------------------------- 1 | .info__title { 2 | font-size: 18px; 3 | font-weight: 600; 4 | margin-bottom: .5em; 5 | } 6 | rollout__row .info { 7 | height: auto; 8 | } 9 | .rollout__info { 10 | width: 400px; 11 | margin-right: 15px; 12 | } 13 | .info { 14 | border-radius: 5px; 15 | padding: 15px; 16 | background-color: #fff; 17 | box-sizing: border-box; 18 | margin-left: 1rem; 19 | margin-bottom: 2rem; 20 | } 21 | .info-item--row { 22 | display: flex; 23 | align-items: center; 24 | flex-grow: 1; 25 | } 26 | .info-item--row label { 27 | margin-right: auto; 28 | padding-right: 5px; 29 | } 30 | 31 | .info-item--row__container { 32 | margin-left: auto; 33 | display: flex; 34 | min-width: 0; 35 | padding-left: 25px; 36 | flex-wrap: wrap; 37 | justify-content: flex-end; 38 | } 39 | .info-item--canary { 40 | background-color: #e4aa37; 41 | border: 1px solid #e4aa37; 42 | color: #09090f; 43 | } 44 | .info-item--row .info-item { 45 | margin: .25em 0; 46 | margin-left: 5px; 47 | } 48 | .ant-input { 49 | box-sizing: border-box; 50 | margin: 0; 51 | padding: 4px 11px; 52 | color: rgba(0, 0, 0, 0.88); 53 | font-size: 14px; 54 | line-height: 1.5714285714285714; 55 | list-style: none; 56 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 57 | position: relative; 58 | display: inline-block; 59 | width: 100%; 60 | min-width: 0; 61 | background-color: #ffffff; 62 | background-image: none; 63 | border-width: 1px; 64 | border-style: solid; 65 | border-color: #d9d9d9; 66 | border-radius: 6px; 67 | transition: all 0.2s; 68 | } 69 | .ant-input[disabled] { 70 | color: rgba(0, 0, 0, 0.25); 71 | background-color: rgba(0, 0, 0, 0.04); 72 | border-color: #d9d9d9; 73 | box-shadow: none; 74 | cursor: not-allowed; 75 | opacity: 1; 76 | } -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | // What are the options for groupKind 4 | const extName = "Numarollout"; 5 | 6 | const config = { 7 | mode: "production", 8 | entry: { 9 | extension: "./src/index.tsx", 10 | }, 11 | output: { 12 | filename: `extensions-${extName}.js`, 13 | path: __dirname + `/dist/resources/extension-${extName}.js`, 14 | libraryTarget: "window", 15 | library: ["tmp", "extensions"], 16 | }, 17 | resolve: { 18 | extensions: [".ts", ".tsx", ".js", ".json", ".ttf"], 19 | fallback: { 20 | url: require.resolve("url/"), 21 | // You can add other polyfills if needed 22 | }, 23 | }, 24 | externals: { 25 | react: "React", 26 | "react-dom": "ReactDOM", 27 | moment: "Moment", 28 | }, 29 | optimization: { 30 | minimize: true, 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.(ts|js)x?$/, 36 | exclude: /node_modules/, 37 | use: [ 38 | { 39 | loader: "ts-loader", 40 | options: { 41 | // specify TypeScript compiler options 42 | compilerOptions: { 43 | target: "es5", 44 | }, 45 | appendTsSuffixTo: [/\.vue$/], 46 | appendTsxSuffixTo: [/\.vue$/], 47 | transpileOnly: true, 48 | }, 49 | }, 50 | ], 51 | }, 52 | { 53 | test: /\.tsx?$/, 54 | include: [/argo-rollouts\/ui/, /argo-ui/], // Add argo-ui package here 55 | use: [ 56 | { 57 | loader: "ts-loader", 58 | options: { 59 | transpileOnly: true, 60 | }, 61 | }, 62 | ], 63 | }, 64 | { 65 | test: /\.scss$/, 66 | use: ["style-loader", "raw-loader", "sass-loader"], 67 | }, 68 | { 69 | test: /\.css$/, 70 | use: ["style-loader", "raw-loader"], 71 | }, 72 | ], 73 | }, 74 | }; 75 | 76 | module.exports = config; 77 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/numaplane/v1alpha1/fake/fake_numaplane_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | v1alpha1 "github.com/numaproj/numaplane/pkg/client/clientset/versioned/typed/numaplane/v1alpha1" 22 | rest "k8s.io/client-go/rest" 23 | testing "k8s.io/client-go/testing" 24 | ) 25 | 26 | type FakeNumaplaneV1alpha1 struct { 27 | *testing.Fake 28 | } 29 | 30 | func (c *FakeNumaplaneV1alpha1) ISBServiceRollouts(namespace string) v1alpha1.ISBServiceRolloutInterface { 31 | return &FakeISBServiceRollouts{c, namespace} 32 | } 33 | 34 | func (c *FakeNumaplaneV1alpha1) MonoVertexRollouts(namespace string) v1alpha1.MonoVertexRolloutInterface { 35 | return &FakeMonoVertexRollouts{c, namespace} 36 | } 37 | 38 | func (c *FakeNumaplaneV1alpha1) NumaflowControllers(namespace string) v1alpha1.NumaflowControllerInterface { 39 | return &FakeNumaflowControllers{c, namespace} 40 | } 41 | 42 | func (c *FakeNumaplaneV1alpha1) NumaflowControllerRollouts(namespace string) v1alpha1.NumaflowControllerRolloutInterface { 43 | return &FakeNumaflowControllerRollouts{c, namespace} 44 | } 45 | 46 | func (c *FakeNumaplaneV1alpha1) PipelineRollouts(namespace string) v1alpha1.PipelineRolloutInterface { 47 | return &FakePipelineRollouts{c, namespace} 48 | } 49 | 50 | // RESTClient returns a RESTClient that is used to communicate 51 | // with API server by this client implementation. 52 | func (c *FakeNumaplaneV1alpha1) RESTClient() rest.Interface { 53 | var ret *rest.RESTClient 54 | return ret 55 | } 56 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package scheme 19 | 20 | import ( 21 | numaplanev1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 22 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 | runtime "k8s.io/apimachinery/pkg/runtime" 24 | schema "k8s.io/apimachinery/pkg/runtime/schema" 25 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 26 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 27 | ) 28 | 29 | var Scheme = runtime.NewScheme() 30 | var Codecs = serializer.NewCodecFactory(Scheme) 31 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 32 | var localSchemeBuilder = runtime.SchemeBuilder{ 33 | numaplanev1alpha1.AddToScheme, 34 | } 35 | 36 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 37 | // of clientsets, like in: 38 | // 39 | // import ( 40 | // "k8s.io/client-go/kubernetes" 41 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 42 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 43 | // ) 44 | // 45 | // kclientset, _ := kubernetes.NewForConfig(c) 46 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 47 | // 48 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 49 | // correctly. 50 | var AddToScheme = localSchemeBuilder.AddToScheme 51 | 52 | func init() { 53 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 54 | utilruntime.Must(AddToScheme(Scheme)) 55 | } 56 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/ISBRollout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useMemo } from "react"; 2 | import { RolloutComponentContext } from "./RolloutComponentWrapper"; 3 | import { Box } from "@mui/material"; 4 | import { SquareCheckIcon } from "../utils/SquareCheckIcon"; 5 | import { SquareCancelIcon } from "../utils/SquareCancelIcon"; 6 | import { ISB, ISB_KUBERNETES, POD } from "../utils/Constants"; 7 | 8 | export const ISBRollout = () => { 9 | const { kindToNodeMap } = useContext(RolloutComponentContext); 10 | 11 | const isbPods = useMemo(() => { 12 | if (!kindToNodeMap || !kindToNodeMap.get(POD)) { 13 | return []; 14 | } 15 | const pods = kindToNodeMap.get(POD); 16 | if (!pods) { 17 | return []; 18 | } 19 | return pods.filter((node) => node.name.indexOf(ISB_KUBERNETES) >= 0); 20 | }, [kindToNodeMap]); 21 | 22 | const isbName = useMemo(() => { 23 | if (!kindToNodeMap || !kindToNodeMap.get(POD)) { 24 | return ""; 25 | } 26 | const isbService = kindToNodeMap.get(ISB); 27 | if (!isbService || !isbService[0]) { 28 | return ""; 29 | } 30 | return isbService[0].name; 31 | }, [kindToNodeMap]); 32 | 33 | return ( 34 | 35 | ISB Name : {isbName} 36 | 37 | ISB Pod Status:{" "} 38 | 51 | {isbPods.map((node) => { 52 | return ( 53 | 54 | 55 | {node?.health?.status === "Healthy" ? ( 56 | 57 | ) : ( 58 | 59 | )} 60 | 61 | 62 | ); 63 | })} 64 | 65 | 66 | 67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /internal/util/kubernetes/structured_util.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | corev1 "k8s.io/api/core/v1" 8 | policyv1 "k8s.io/api/policy/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/apimachinery/pkg/util/intstr" 11 | k8sClient "sigs.k8s.io/controller-runtime/pkg/client" 12 | ) 13 | 14 | // this file contains utility functions for working with standard Kubernetes types 15 | // (using their typed structs as opposed to Unstructured type) 16 | 17 | // GetSecret gets secret using the kubernetes client 18 | func GetSecret(ctx context.Context, client k8sClient.Client, namespace, secretName string) (*corev1.Secret, error) { 19 | if namespace == "" { 20 | return nil, fmt.Errorf("namespace cannot be empty") 21 | } 22 | if secretName == "" { 23 | return nil, fmt.Errorf("secretName cannot be empty") 24 | } 25 | secret := &corev1.Secret{} 26 | key := k8sClient.ObjectKey{ 27 | Namespace: namespace, 28 | Name: secretName, 29 | } 30 | if err := client.Get(ctx, key, secret); err != nil { 31 | return nil, err 32 | } 33 | return secret, nil 34 | } 35 | 36 | func NewPodDisruptionBudget(name, namespace string, maxUnavailable int32, ownerReference []metav1.OwnerReference) *policyv1.PodDisruptionBudget { 37 | return &policyv1.PodDisruptionBudget{ 38 | ObjectMeta: metav1.ObjectMeta{ 39 | Name: name, 40 | Namespace: namespace, 41 | OwnerReferences: ownerReference, 42 | }, 43 | Spec: policyv1.PodDisruptionBudgetSpec{ 44 | MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: maxUnavailable}, 45 | Selector: &metav1.LabelSelector{ 46 | MatchLabels: map[string]string{ 47 | "app.kubernetes.io/component": "isbsvc", 48 | "numaflow.numaproj.io/isbsvc-name": name, 49 | }, 50 | }, 51 | }, 52 | } 53 | } 54 | 55 | func ListPodsMetadataOnly(ctx context.Context, c k8sClient.Client, namespace, labels string) (*metav1.PartialObjectMetadataList, error) { 56 | podsMeta := &metav1.PartialObjectMetadataList{} 57 | 58 | err := KubernetesClient.CoreV1().RESTClient(). 59 | Get(). 60 | Namespace(namespace). 61 | Resource("pods"). 62 | Param("labelSelector", labels). 63 | SetHeader("Accept", "application/json;as=PartialObjectMetadataList;g=meta.k8s.io;v=v1"). 64 | Do(ctx). 65 | Into(podsMeta) 66 | 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | return podsMeta, nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/apis/numaplane/v1alpha1/numaflowcontroller_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // NumaflowControllerSpec defines the desired state of NumaflowController 27 | type NumaflowControllerSpec struct { 28 | InstanceID string `json:"instanceID,omitempty"` 29 | Version string `json:"version"` 30 | } 31 | 32 | // NumaflowControllerStatus defines the observed state of NumaflowController 33 | type NumaflowControllerStatus struct { 34 | Status `json:",inline"` 35 | } 36 | 37 | // +genclient 38 | // +kubebuilder:object:root=true 39 | // +kubebuilder:subresource:status 40 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 41 | // +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="The current phase" 42 | // +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version",description="The desired Numaflow Controller version" 43 | // NumaflowController is the Schema for the numaflowcontrollers API 44 | type NumaflowController struct { 45 | metav1.TypeMeta `json:",inline"` 46 | metav1.ObjectMeta `json:"metadata,omitempty"` 47 | 48 | Spec NumaflowControllerSpec `json:"spec,omitempty"` 49 | Status NumaflowControllerStatus `json:"status,omitempty"` 50 | } 51 | 52 | //+kubebuilder:object:root=true 53 | 54 | // NumaflowControllerList contains a list of NumaflowController 55 | type NumaflowControllerList struct { 56 | metav1.TypeMeta `json:",inline"` 57 | metav1.ListMeta `json:"metadata,omitempty"` 58 | Items []NumaflowController `json:"items"` 59 | } 60 | 61 | func init() { 62 | SchemeBuilder.Register(&NumaflowController{}, &NumaflowControllerList{}) 63 | } 64 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "numarollout", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.11.4", 7 | "@emotion/styled": "^11.11.5", 8 | "@fortawesome/fontawesome-svg-core": "^6.5.2", 9 | "@fortawesome/free-solid-svg-icons": "^6.5.2", 10 | "@fortawesome/react-fontawesome": "^0.2.2", 11 | "@mui/icons-material": "^5.15.19", 12 | "@mui/material": "^5.15.19", 13 | "@testing-library/jest-dom": "^6.4.6", 14 | "@testing-library/react": "^16.0.0", 15 | "@testing-library/user-event": "^13.5.0", 16 | "@types/jest": "^27.5.2", 17 | "@types/node": "^16.18.98", 18 | "@types/react": "^18.3.3", 19 | "@types/react-dom": "^18.3.0", 20 | "add": "^2.0.6", 21 | "react": "^18.3.1", 22 | "react-dom": "^18.3.1", 23 | "react-scripts": "5.0.1", 24 | "typescript": "^4.9.5", 25 | "url": "^0.11.3", 26 | "yarn": "^1.22.22" 27 | }, 28 | "peerDependencies": { 29 | "moment": "^2.29.4", 30 | "react": "^16.9.3", 31 | "react-dom": "^16.9.3" 32 | }, 33 | "devDependencies": { 34 | "@testing-library/dom": "^10.2.0", 35 | "@types/react": "^17.0.44", 36 | "@types/react-dom": "^17.0.9", 37 | "@types/react-helmet": "^6.1.0", 38 | "@types/react-router-dom": "^5.1.8", 39 | "@types/styled-components": "^5.1.25", 40 | "babel-preset-react": "^6.24.1", 41 | "esbuild-loader": "^3.0.1", 42 | "portable-fetch": "^3.0.0", 43 | "raw-loader": "0.5.1", 44 | "react-dom": "^17.0.2", 45 | "react-keyhooks": "^0.2.3", 46 | "rxjs": "^7.1.0", 47 | "sass": "1.34.1", 48 | "sass-loader": "10.2.1", 49 | "style-loader": "1.3.0", 50 | "ts-loader": "8.2.0", 51 | "typescript": "^4.3.5", 52 | "webpack": "^5.75.0", 53 | "webpack-bundle-analyzer": "^4.8.0", 54 | "webpack-cli": "^4.7.2" 55 | }, 56 | "scripts": { 57 | "start": "webpack --config ./webpack.config.js --watch", 58 | "build": "webpack --config ./webpack.config.js && tar -C dist -cvf extension.tar resources", 59 | "test": "react-scripts test" 60 | }, 61 | "eslintConfig": { 62 | "extends": [ 63 | "react-app", 64 | "react-app/jest" 65 | ] 66 | }, 67 | "browserslist": { 68 | "production": [ 69 | ">0.2%", 70 | "not dead", 71 | "not op_mini all" 72 | ], 73 | "development": [ 74 | "last 1 chrome version", 75 | "last 1 firefox version", 76 | "last 1 safari version" 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /internal/controller/config/usde_config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package config 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/rs/zerolog/log" 23 | ) 24 | 25 | type SpecField struct { 26 | Path string `json:"path" yaml:"path"` 27 | IncludeSubfields bool `json:"includeSubfields,omitempty" yaml:"includeSubfields,omitempty"` 28 | } 29 | 30 | type USDEResourceConfig struct { 31 | // Recreate indicates fields that require the resource to be recreated upon modification. 32 | // For PPND strategy, this list is checked before the other two lists. 33 | Recreate []SpecField `json:"recreate,omitempty" yaml:"recreate,omitempty"` 34 | // DataLoss represents fields that, when changed, may result in data loss. 35 | // For PPND strategy, this list is checked after the 'recreate' list. 36 | DataLoss []SpecField `json:"dataLoss,omitempty" yaml:"dataLoss,omitempty"` 37 | // Progressive contains fields that can be updated without requiring a full resource recreation and by performing an in-place update. 38 | // For PPND strategy, this list is checked after the other two lists. 39 | Progressive []SpecField `json:"progressive,omitempty" yaml:"progressive,omitempty"` 40 | } 41 | 42 | type USDEConfig map[string]USDEResourceConfig 43 | 44 | func (cm *ConfigManager) UpdateUSDEConfig(config USDEConfig) { 45 | cm.usdeConfigLock.Lock() 46 | defer cm.usdeConfigLock.Unlock() 47 | 48 | cm.usdeConfig = config 49 | 50 | log.Debug().Msg(fmt.Sprintf("USDE Config update: %+v", config)) // due to cyclical dependency, we can't call logger 51 | } 52 | 53 | func (cm *ConfigManager) UnsetUSDEConfig() { 54 | cm.usdeConfigLock.Lock() 55 | defer cm.usdeConfigLock.Unlock() 56 | 57 | cm.usdeConfig = USDEConfig{} 58 | 59 | log.Debug().Msg("USDE Config unset") // due to cyclical dependency, we can't call logger 60 | } 61 | 62 | func (cm *ConfigManager) GetUSDEConfig() USDEConfig { 63 | cm.usdeConfigLock.Lock() 64 | defer cm.usdeConfigLock.Unlock() 65 | 66 | return cm.usdeConfig 67 | } 68 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/default/ArgoRolloutSummary.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./ArgoSummary.css"; 4 | 5 | interface ArgoRolloutSummaryProps { 6 | rolloutParams: { 7 | strategy: string; 8 | setWeight: string; 9 | actualWeight: string; 10 | }; 11 | } 12 | export const ArgoRolloutSummary = ({ 13 | rolloutParams, 14 | }: ArgoRolloutSummaryProps) => { 15 | return ( 16 |
17 |
Summary
18 |
19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 |
{rolloutParams.strategy}
28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 |
38 | 39 | 40 | 41 |
1/1
42 |
43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 | 52 | 53 | 54 |
{rolloutParams.setWeight}
55 |
56 |
57 |
58 |
59 |
60 | 61 |
62 |
63 |
64 | 65 | 66 | 67 |
{rolloutParams.actualWeight}
68 |
69 |
70 |
71 |
72 |
73 | ); 74 | }; 75 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "js-tokens@^3.0.0 || ^4.0.0": 6 | version "4.0.0" 7 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 8 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 9 | 10 | loose-envify@^1.1.0: 11 | version "1.4.0" 12 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 13 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 14 | dependencies: 15 | js-tokens "^3.0.0 || ^4.0.0" 16 | 17 | object-assign@^4.1.1: 18 | version "4.1.1" 19 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 20 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 21 | 22 | "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.3.1: 23 | version "18.3.1" 24 | resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" 25 | integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== 26 | 27 | react-shallow-renderer@^16.15.0: 28 | version "16.15.0" 29 | resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" 30 | integrity sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA== 31 | dependencies: 32 | object-assign "^4.1.1" 33 | react-is "^16.12.0 || ^17.0.0 || ^18.0.0" 34 | 35 | react-test-renderer@^18.2.0: 36 | version "18.3.1" 37 | resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-18.3.1.tgz#e693608a1f96283400d4a3afead6893f958b80b4" 38 | integrity sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA== 39 | dependencies: 40 | react-is "^18.3.1" 41 | react-shallow-renderer "^16.15.0" 42 | scheduler "^0.23.2" 43 | 44 | scheduler@^0.23.2: 45 | version "0.23.2" 46 | resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" 47 | integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== 48 | dependencies: 49 | loose-envify "^1.1.0" 50 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/ControllerRollout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useMemo } from "react"; 2 | import { Box } from "@mui/material"; 3 | import { RolloutComponentContext } from "./RolloutComponentWrapper"; 4 | import { SquareCheckIcon } from "../utils/SquareCheckIcon"; 5 | import { SquareCancelIcon } from "../utils/SquareCancelIcon"; 6 | 7 | export const ControllerRollout = () => { 8 | const { kindToNodeMap } = useContext(RolloutComponentContext); 9 | 10 | const controllerPods = useMemo(() => { 11 | const pods = kindToNodeMap?.get("Pod") ?? []; 12 | return pods.filter( 13 | (node) => node?.name?.indexOf("numaflow-controller") >= 0 14 | ); 15 | }, [kindToNodeMap?.get("Pod")]); 16 | 17 | const controllerName = useMemo(() => { 18 | const numaflowController = kindToNodeMap?.get( 19 | "NumaflowControllerRollout" 20 | )?.[0]; 21 | if (numaflowController) { 22 | const numaControllerName = numaflowController.name; 23 | 24 | // Find the deployment object with the numaControllerName 25 | const deployment = kindToNodeMap.get("Deployment") ?? []; 26 | const controllerDeployment = deployment.find( 27 | (node) => node?.parentRefs?.[0]?.name === numaControllerName 28 | ); 29 | 30 | return controllerDeployment?.name ?? ""; 31 | } 32 | return ""; 33 | }, [ 34 | kindToNodeMap.get("NumaflowControllerRollout"), 35 | kindToNodeMap.get("Deployment"), 36 | ]); 37 | 38 | return ( 39 | 40 | Controller Name : {controllerName} 41 | 42 | Controller Pod Status:{" "} 43 | 56 | {controllerPods.map((node) => { 57 | return ( 58 | 59 | 60 | {node?.health?.status === "Healthy" ? ( 61 | 62 | ) : ( 63 | 64 | )} 65 | 66 | 67 | ); 68 | })} 69 | 70 | 71 | 72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /pkg/client/listers/numaplane/v1alpha1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | // ISBServiceRolloutListerExpansion allows custom methods to be added to 21 | // ISBServiceRolloutLister. 22 | type ISBServiceRolloutListerExpansion interface{} 23 | 24 | // ISBServiceRolloutNamespaceListerExpansion allows custom methods to be added to 25 | // ISBServiceRolloutNamespaceLister. 26 | type ISBServiceRolloutNamespaceListerExpansion interface{} 27 | 28 | // MonoVertexRolloutListerExpansion allows custom methods to be added to 29 | // MonoVertexRolloutLister. 30 | type MonoVertexRolloutListerExpansion interface{} 31 | 32 | // MonoVertexRolloutNamespaceListerExpansion allows custom methods to be added to 33 | // MonoVertexRolloutNamespaceLister. 34 | type MonoVertexRolloutNamespaceListerExpansion interface{} 35 | 36 | // NumaflowControllerListerExpansion allows custom methods to be added to 37 | // NumaflowControllerLister. 38 | type NumaflowControllerListerExpansion interface{} 39 | 40 | // NumaflowControllerNamespaceListerExpansion allows custom methods to be added to 41 | // NumaflowControllerNamespaceLister. 42 | type NumaflowControllerNamespaceListerExpansion interface{} 43 | 44 | // NumaflowControllerRolloutListerExpansion allows custom methods to be added to 45 | // NumaflowControllerRolloutLister. 46 | type NumaflowControllerRolloutListerExpansion interface{} 47 | 48 | // NumaflowControllerRolloutNamespaceListerExpansion allows custom methods to be added to 49 | // NumaflowControllerRolloutNamespaceLister. 50 | type NumaflowControllerRolloutNamespaceListerExpansion interface{} 51 | 52 | // PipelineRolloutListerExpansion allows custom methods to be added to 53 | // PipelineRolloutLister. 54 | type PipelineRolloutListerExpansion interface{} 55 | 56 | // PipelineRolloutNamespaceListerExpansion allows custom methods to be added to 57 | // PipelineRolloutNamespaceLister. 58 | type PipelineRolloutNamespaceListerExpansion interface{} 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # numaplane 2 | Numaplane is a control plane for installing, managing and running numaflow resources on Kubernetes. 3 | 4 | ## Getting Started 5 | 6 | ### Prerequisites 7 | - go version v1.20.0+ 8 | - docker version 17.03+. 9 | - kubectl version v1.11.3+. 10 | - Access to a Kubernetes v1.11.3+ cluster. 11 | 12 | ### To build Numaplane image and run it on your local cluster with latest manifests 13 | 14 | `make start` 15 | 16 | 17 | ### To auto-generate code and manifests from Go 18 | 19 | `make codegen` 20 | 21 | 22 | ## Contributing 23 | **NOTE:** Run `make --help` for more information on all potential `make` targets 24 | 25 | ## How To Release 26 | 27 | ### Release Branch 28 | 29 | Always create a release branch for the releases, for example branch `release-0.5` is for all the v0.5.x versions release. 30 | If it's a new release branch, simply create a branch from `main`. 31 | 32 | ### Release Steps 33 | 34 | 1. Cherry-pick fixes to the release branch, skip this and the following two steps if it's the first release in the branch. 35 | 2. Run `make test` to make sure all test cases pass locally. 36 | 3. Push to remote branch, and make sure all the CI jobs pass. 37 | 4. Run `make prepare-release VERSION=v{x.y.z}` to update version in manifests, where `x.y.z` is the expected new version. 38 | 5. Follow the output of last step, to confirm if all the changes are expected, and then run `make release VERSION=v{x.y.z}`. 39 | 6. Follow the output, push a new tag to the release branch, GitHub actions will automatically build and publish the new release, 40 | this will take around 10 minutes. 41 | 7. Test the new release, make sure everything is running as expected, and then recreate a `stable` tag against the latest release. 42 | ```shell 43 | git tag -d stable 44 | git tag -a stable -m stable 45 | git push -d {your-remote} stable 46 | git push {your-remote} stable 47 | ``` 48 | 8. Find the new release tag, and edit the release notes. 49 | 50 | 51 | ## License 52 | 53 | Copyright 2023 The Numaproj Authors. 54 | 55 | Licensed under the Apache License, Version 2.0 (the "License"); 56 | you may not use this file except in compliance with the License. 57 | You may obtain a copy of the License at 58 | 59 | http://www.apache.org/licenses/LICENSE-2.0 60 | 61 | Unless required by applicable law or agreed to in writing, software 62 | distributed under the License is distributed on an "AS IS" BASIS, 63 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 64 | See the License for the specific language governing permissions and 65 | limitations under the License. 66 | -------------------------------------------------------------------------------- /pkg/apis/numaplane/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the numaplane.numaproj.io v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=numaplane.numaproj.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // SchemeGroupVersion is group version used to register these objects 29 | SchemeGroupVersion = schema.GroupVersion{Group: "numaplane.numaproj.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | 37 | ISBServiceRolloutGroupVersionKind = SchemeGroupVersion.WithKind("ISBServiceRollout") 38 | ISBServiceRolloutGroupVersionResource = SchemeGroupVersion.WithResource("isbservicerollouts") 39 | 40 | PipelineRolloutGroupVersionKind = SchemeGroupVersion.WithKind("PipelineRollout") 41 | PipelineRolloutGroupVersionResource = SchemeGroupVersion.WithResource("pipelinerollouts") 42 | 43 | NumaflowControllerRolloutGroupVersionKind = SchemeGroupVersion.WithKind("NumaflowControllerRollout") 44 | NumaflowControllerRolloutGroupVersionResource = SchemeGroupVersion.WithResource("numaflowcontrollerrollouts") 45 | 46 | MonoVertexRolloutGroupVersionKind = SchemeGroupVersion.WithKind("MonoVertexRollout") 47 | MonoVertexRolloutGroupVersionResource = SchemeGroupVersion.WithResource("monovertexrollouts") 48 | 49 | NumaflowControllerGroupVersionKind = SchemeGroupVersion.WithKind("NumaflowController") 50 | NumaflowControllerGroupVersionResource = SchemeGroupVersion.WithResource("numaflowcontrollers") 51 | ) 52 | 53 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 54 | func Resource(resource string) schema.GroupResource { 55 | return SchemeGroupVersion.WithResource(resource).GroupResource() 56 | } 57 | -------------------------------------------------------------------------------- /internal/controller/common/predicate_filters.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package common 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | "sigs.k8s.io/controller-runtime/pkg/event" 22 | "sigs.k8s.io/controller-runtime/pkg/predicate" 23 | ) 24 | 25 | /* 26 | Reference: https://github.com/kubernetes-sigs/controller-runtime/issues/2355#issuecomment-2477548322 27 | There's not an explicit attribute that holds this info about sync trigger. However, the "update" events that come in 28 | when these batches of resync-triggered events happen, the resource version changes for every real update, 29 | but doesn't for the resync-triggered events. So, to detect these cases while also making use of the generation change check, 30 | added a custom predicate that is a logical OR of checking 31 | 1. If the generation changed between the old and new object. 32 | 2. If the resource version remained the same between the old and new object. 33 | Assuming there are no other edge cases where the resource version remains the same across an update event 34 | */ 35 | 36 | type TypedGenerationChangedPredicate[object metav1.Object] struct { 37 | // Returns true by default for all events; see overrides below. 38 | predicate.TypedFuncs[object] 39 | } 40 | 41 | func (p TypedGenerationChangedPredicate[object]) Update(e event.TypedUpdateEvent[object]) bool { 42 | // It will effectively check of ObjectOld and ObjectNew does not exist or if the name of the object is empty 43 | if e.ObjectOld.GetName() == "" || e.ObjectNew.GetName() == "" { 44 | return false 45 | } 46 | 47 | // Process the event if the generation changed (which happens e.g. if the spec 48 | // was updated, but not if the status or metadata fields were updated). 49 | if e.ObjectNew.GetGeneration() != e.ObjectOld.GetGeneration() { 50 | return true 51 | } 52 | 53 | // If the generation is unchanged, we generally don't want to process the event, 54 | // except events triggered by periodic resyncs (which are, for some 55 | // reason, categorized as updates). We identify such events by checking if the old 56 | // and new resource versions are equal. 57 | if e.ObjectNew.GetResourceVersion() == e.ObjectOld.GetResourceVersion() { 58 | return true 59 | } 60 | 61 | return false 62 | } 63 | -------------------------------------------------------------------------------- /internal/util/kubernetes/kubernetes_util.go: -------------------------------------------------------------------------------- 1 | package kubernetes 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "k8s.io/apimachinery/pkg/util/validation" 9 | ) 10 | 11 | // this file is designed to hold utility functions for Kubernetes that are not specific to 12 | // any type of resource 13 | 14 | // validManifestExtensions contains the supported extension for raw file. 15 | var validManifestExtensions = map[string]struct{}{"yaml": {}, "yml": {}, "json": {}} 16 | 17 | func IsValidKubernetesNamespace(name string) bool { 18 | // All namespace names must be valid RFC 1123 DNS labels. 19 | errs := validation.IsDNS1123Label(name) 20 | reservedNamesRegex := regexp.MustCompile(`^(kubernetes-|kube-)`) 21 | if len(errs) == 0 && !reservedNamesRegex.MatchString(name) { 22 | return true 23 | } 24 | return false 25 | } 26 | 27 | func IsValidKubernetesManifestFile(fileName string) bool { 28 | fileExt := strings.Split(fileName, ".") 29 | if _, ok := validManifestExtensions[fileExt[len(fileExt)-1]]; ok { 30 | return true 31 | } 32 | return false 33 | } 34 | 35 | // ResourceFilter filter resources based on allowed Resource Types 36 | type ResourceFilter struct { 37 | IncludedResources []ResourceType 38 | } 39 | 40 | type ResourceType struct { 41 | Group string 42 | Kind string 43 | } 44 | 45 | func (n *ResourceFilter) IsExcludedResource(group, kind, _ string) bool { 46 | for _, resource := range n.IncludedResources { 47 | if resource.Kind == "" { 48 | // When Kind is empty, we only check if Group matches 49 | if group == resource.Group { 50 | return false 51 | } 52 | } else if group == resource.Group && kind == resource.Kind { 53 | return false 54 | } 55 | } 56 | return true 57 | } 58 | 59 | // ParseResourceFilter parse the given rules to generate the 60 | // list of resources we allow or watch. The rules are delimited by ';', and 61 | // each rule is composed by both 'group' and 'kind' which can be empty. 62 | // For example, 'group=apps,kind=Deployment;group=,kind=ConfigMap'. 63 | // Note that empty rule is valid. 64 | func ParseResourceFilter(rules string) ([]ResourceType, error) { 65 | filteredResources := make([]ResourceType, 0) 66 | if rules == "" { 67 | return filteredResources, nil 68 | } 69 | rulesArr := strings.Split(rules, ";") 70 | err := fmt.Errorf("malformed resource filter rules %q", rules) 71 | for _, rule := range rulesArr { 72 | ruleArr := strings.Split(rule, ",") 73 | if len(ruleArr) != 2 { 74 | return nil, err 75 | } 76 | groupArr := strings.Split(ruleArr[0], "=") 77 | kindArr := strings.Split(ruleArr[1], "=") 78 | if !strings.EqualFold(groupArr[0], "group") || !strings.EqualFold(kindArr[0], "kind") { 79 | return nil, err 80 | } 81 | filteredResource := ResourceType{ 82 | Group: groupArr[1], 83 | Kind: kindArr[1], 84 | } 85 | filteredResources = append(filteredResources, filteredResource) 86 | } 87 | return filteredResources, nil 88 | 89 | } 90 | -------------------------------------------------------------------------------- /config/samples/numaplane.numaproj.io_v1alpha1_pipelinerollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: numaplane.numaproj.io/v1alpha1 2 | kind: PipelineRollout 3 | metadata: 4 | name: my-pipeline 5 | namespace: example-namespace 6 | spec: 7 | strategy: 8 | progressive: 9 | assessmentSchedule: "10,200,60,10" 10 | # analysis: 11 | # args: 12 | #templates: 13 | #- templateName: pipeline-template 14 | # clusterScope: false 15 | riders: 16 | - perVertex: true 17 | definition: 18 | apiVersion: autoscaling.k8s.io/v1beta2 19 | kind: VerticalPodAutoscaler 20 | metadata: 21 | name: vpa 22 | spec: 23 | targetRef: 24 | apiVersion: numaproj.io/v1alpha1 25 | kind: Vertex 26 | name: '{{.pipeline-name}}-{{.vertex-name}}' 27 | # - perVertex: true 28 | # definition: 29 | # apiVersion: autoscaling/v2 30 | # kind: HorizontalPodAutoscaler 31 | # metadata: 32 | # name: hpa 33 | # spec: 34 | # minReplicas: 1 35 | # maxReplicas: 10 36 | # metrics: 37 | # - object: 38 | # metric: 39 | # name: namespace_app_monovertex_container_cpu_utilization 40 | # selector: 41 | # matchLabels: 42 | # container: udsource 43 | # target: 44 | # type: Value 45 | # value: 80 46 | # describedObject: 47 | # apiVersion: apps/v1 48 | # kind: Deployment 49 | # name: '{{.pipeline-name}}-{{.vertex-name}}' 50 | # type: Object 51 | # scaleTargetRef: 52 | # apiVersion: numaflow.numaproj.io/v1alpha1 53 | # kind: Vertex 54 | # name: '{{.pipeline-name}}-{{.vertex-name}}' 55 | 56 | pipeline: 57 | metadata: 58 | annotations: 59 | # numaflow.numaproj.io/instance: "0" # uncomment for Progressive rollout to set Numaflow Controller instance 60 | my-templated-annotation: '{{.pipeline-namespace}}-{{.pipeline-name}}' 61 | 62 | spec: 63 | lifecycle: 64 | pauseGracePeriodSeconds: 60 65 | interStepBufferServiceName: my-isbsvc 66 | vertices: 67 | - name: in 68 | scale: 69 | #min: 3 70 | #max: 5 71 | loopbackSeconds: 180 72 | 73 | source: 74 | # A self data generating source 75 | generator: 76 | rpu: 500 77 | duration: 1s 78 | - name: cat 79 | scale: 80 | min: 3 81 | max: 5 82 | udf: 83 | container: 84 | image: "quay.io/numaio/numaflow-go/map-cat:stable" # A built-in UDF which simply cats the message 85 | imagePullPolicy: Always 86 | - name: out 87 | sink: 88 | # A simple log printing sink 89 | log: {} 90 | edges: 91 | - from: in 92 | to: cat 93 | - from: cat 94 | to: out 95 | -------------------------------------------------------------------------------- /tests/e2e/analysistemplate.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | import ( 4 | "fmt" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | 10 | argov1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" 11 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 | ) 13 | 14 | func CreateAnalysisTemplate(name, namespace string, spec argov1alpha1.AnalysisTemplateSpec) { 15 | analysisTemplateSpec := createAnalysisTemplateSpec(name, namespace, spec) 16 | _, err := argoAnalysisTemplateClient.Create(ctx, analysisTemplateSpec, metav1.CreateOptions{}) 17 | Expect(err).ShouldNot(HaveOccurred()) 18 | 19 | CheckEventually("Verifying that the Analysis Template was created", func() error { 20 | _, err := argoAnalysisTemplateClient.Get(ctx, name, metav1.GetOptions{}) 21 | return err 22 | }).Should(Succeed()) 23 | } 24 | 25 | func createAnalysisTemplateSpec(name, namespace string, spec argov1alpha1.AnalysisTemplateSpec) *argov1alpha1.AnalysisTemplate { 26 | analysisTemplate := &argov1alpha1.AnalysisTemplate{ 27 | TypeMeta: metav1.TypeMeta{ 28 | APIVersion: "argoproj.io/v1alpha1", 29 | Kind: "analysisTemplate", 30 | }, 31 | ObjectMeta: metav1.ObjectMeta{ 32 | Name: name, 33 | Namespace: namespace, 34 | }, 35 | Spec: argov1alpha1.AnalysisTemplateSpec{ 36 | Metrics: spec.Metrics, 37 | Args: spec.Args, 38 | }, 39 | } 40 | 41 | return analysisTemplate 42 | } 43 | 44 | // DeleteAnalysisTemplate will delete and verify deletion of the Analysis Template with the given name. 45 | func DeleteAnalysisTemplate(name string) { 46 | By("Deleting AnalysisTemplate") 47 | foregroundDeletion := metav1.DeletePropagationForeground 48 | err := argoAnalysisTemplateClient.Delete(ctx, name, metav1.DeleteOptions{PropagationPolicy: &foregroundDeletion}) 49 | Expect(err).ShouldNot(HaveOccurred()) 50 | 51 | CheckEventually(fmt.Sprintf("Verifying AnalysisTemplate deletion, (%s)", name), func() bool { 52 | _, err := argoAnalysisTemplateClient.Get(ctx, name, metav1.GetOptions{}) 53 | if err != nil { 54 | if !errors.IsNotFound(err) { 55 | Fail("An unexpected error occurred when fetching the AnalysisTemplate: " + err.Error()) 56 | } 57 | return false 58 | } 59 | return true 60 | }).WithTimeout(DefaultTestTimeout).Should(BeFalse(), "The AnalysisTemplate should have been deleted but it was found.") 61 | } 62 | 63 | func VerifyAnalysisRunStatus(metricName, name string, expectedStatus argov1alpha1.AnalysisPhase) { 64 | CheckEventually(fmt.Sprintf("Verifying AnalysisRun status (%s)", name), func() bool { 65 | analysisRun, err := argoAnalysisRunClient.Get(ctx, name, metav1.GetOptions{}) 66 | if err != nil { 67 | if !errors.IsNotFound(err) { 68 | Fail("An unexpected error occurred when fetching the AnalysisTemplate: " + err.Error()) 69 | } 70 | return false 71 | } 72 | for _, metric := range analysisRun.Status.MetricResults { 73 | if metric.Name == metricName { 74 | if metric.Phase == expectedStatus { 75 | return true 76 | } 77 | } 78 | } 79 | return false 80 | }).Should(BeTrue()) 81 | } 82 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package externalversions 19 | 20 | import ( 21 | "fmt" 22 | 23 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 24 | schema "k8s.io/apimachinery/pkg/runtime/schema" 25 | cache "k8s.io/client-go/tools/cache" 26 | ) 27 | 28 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 29 | // sharedInformers based on type 30 | type GenericInformer interface { 31 | Informer() cache.SharedIndexInformer 32 | Lister() cache.GenericLister 33 | } 34 | 35 | type genericInformer struct { 36 | informer cache.SharedIndexInformer 37 | resource schema.GroupResource 38 | } 39 | 40 | // Informer returns the SharedIndexInformer. 41 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 42 | return f.informer 43 | } 44 | 45 | // Lister returns the GenericLister. 46 | func (f *genericInformer) Lister() cache.GenericLister { 47 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 48 | } 49 | 50 | // ForResource gives generic access to a shared informer of the matching type 51 | // TODO extend this to unknown resources with a client pool 52 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 53 | switch resource { 54 | // Group=numaplane, Version=v1alpha1 55 | case v1alpha1.SchemeGroupVersion.WithResource("isbservicerollouts"): 56 | return &genericInformer{resource: resource.GroupResource(), informer: f.Numaplane().V1alpha1().ISBServiceRollouts().Informer()}, nil 57 | case v1alpha1.SchemeGroupVersion.WithResource("monovertexrollouts"): 58 | return &genericInformer{resource: resource.GroupResource(), informer: f.Numaplane().V1alpha1().MonoVertexRollouts().Informer()}, nil 59 | case v1alpha1.SchemeGroupVersion.WithResource("numaflowcontrollers"): 60 | return &genericInformer{resource: resource.GroupResource(), informer: f.Numaplane().V1alpha1().NumaflowControllers().Informer()}, nil 61 | case v1alpha1.SchemeGroupVersion.WithResource("numaflowcontrollerrollouts"): 62 | return &genericInformer{resource: resource.GroupResource(), informer: f.Numaplane().V1alpha1().NumaflowControllerRollouts().Informer()}, nil 63 | case v1alpha1.SchemeGroupVersion.WithResource("pipelinerollouts"): 64 | return &genericInformer{resource: resource.GroupResource(), informer: f.Numaplane().V1alpha1().PipelineRollouts().Informer()}, nil 65 | 66 | } 67 | 68 | return nil, fmt.Errorf("no informer found for %v", resource) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/apis/numaplane/v1alpha1/numaflowcontrollerrollout_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | type Controller struct { 27 | // NOTE: keeping the instanceID also in the NumaflowControllerRollout in case users want to 28 | // create multiple Numaflow controllers within the same namespace 29 | InstanceID string `json:"instanceID,omitempty"` 30 | Version string `json:"version"` 31 | } 32 | 33 | // NumaflowControllerRolloutSpec defines the desired state of NumaflowControllerRollout 34 | type NumaflowControllerRolloutSpec struct { 35 | Controller Controller `json:"controller"` 36 | } 37 | 38 | // NumaflowControllerRolloutStatus defines the observed state of NumaflowControllerRollout 39 | type NumaflowControllerRolloutStatus struct { 40 | Status `json:",inline"` 41 | PauseRequestStatus PauseStatus `json:"pauseRequestStatus,omitempty"` 42 | } 43 | 44 | // +genclient 45 | // +kubebuilder:object:root=true 46 | // +kubebuilder:subresource:status 47 | // +kubebuilder:validation:XValidation:rule="matches(self.metadata.name, '^numaflow-controller.*')",message="The metadata name must start with 'numaflow-controller'" 48 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 49 | // +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="The current phase" 50 | // +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.controller.version",description="The desired Numaflow Controller version" 51 | // NumaflowControllerRollout is the Schema for the numaflowcontrollerrollouts API 52 | type NumaflowControllerRollout struct { 53 | metav1.TypeMeta `json:",inline"` 54 | metav1.ObjectMeta `json:"metadata,omitempty"` 55 | 56 | Spec NumaflowControllerRolloutSpec `json:"spec,omitempty"` 57 | Status NumaflowControllerRolloutStatus `json:"status,omitempty"` 58 | } 59 | 60 | //+kubebuilder:object:root=true 61 | 62 | // NumaflowControllerRolloutList contains a list of NumaflowControllerRollout 63 | type NumaflowControllerRolloutList struct { 64 | metav1.TypeMeta `json:",inline"` 65 | metav1.ListMeta `json:"metadata,omitempty"` 66 | Items []NumaflowControllerRollout `json:"items"` 67 | } 68 | 69 | func init() { 70 | SchemeBuilder.Register(&NumaflowControllerRollout{}, &NumaflowControllerRolloutList{}) 71 | } 72 | -------------------------------------------------------------------------------- /pkg/client/listers/numaplane/v1alpha1/pipelinerollout.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "k8s.io/client-go/listers" 24 | "k8s.io/client-go/tools/cache" 25 | ) 26 | 27 | // PipelineRolloutLister helps list PipelineRollouts. 28 | // All objects returned here must be treated as read-only. 29 | type PipelineRolloutLister interface { 30 | // List lists all PipelineRollouts in the indexer. 31 | // Objects returned here must be treated as read-only. 32 | List(selector labels.Selector) (ret []*v1alpha1.PipelineRollout, err error) 33 | // PipelineRollouts returns an object that can list and get PipelineRollouts. 34 | PipelineRollouts(namespace string) PipelineRolloutNamespaceLister 35 | PipelineRolloutListerExpansion 36 | } 37 | 38 | // pipelineRolloutLister implements the PipelineRolloutLister interface. 39 | type pipelineRolloutLister struct { 40 | listers.ResourceIndexer[*v1alpha1.PipelineRollout] 41 | } 42 | 43 | // NewPipelineRolloutLister returns a new PipelineRolloutLister. 44 | func NewPipelineRolloutLister(indexer cache.Indexer) PipelineRolloutLister { 45 | return &pipelineRolloutLister{listers.New[*v1alpha1.PipelineRollout](indexer, v1alpha1.Resource("pipelinerollout"))} 46 | } 47 | 48 | // PipelineRollouts returns an object that can list and get PipelineRollouts. 49 | func (s *pipelineRolloutLister) PipelineRollouts(namespace string) PipelineRolloutNamespaceLister { 50 | return pipelineRolloutNamespaceLister{listers.NewNamespaced[*v1alpha1.PipelineRollout](s.ResourceIndexer, namespace)} 51 | } 52 | 53 | // PipelineRolloutNamespaceLister helps list and get PipelineRollouts. 54 | // All objects returned here must be treated as read-only. 55 | type PipelineRolloutNamespaceLister interface { 56 | // List lists all PipelineRollouts in the indexer for a given namespace. 57 | // Objects returned here must be treated as read-only. 58 | List(selector labels.Selector) (ret []*v1alpha1.PipelineRollout, err error) 59 | // Get retrieves the PipelineRollout from the indexer for a given namespace and name. 60 | // Objects returned here must be treated as read-only. 61 | Get(name string) (*v1alpha1.PipelineRollout, error) 62 | PipelineRolloutNamespaceListerExpansion 63 | } 64 | 65 | // pipelineRolloutNamespaceLister implements the PipelineRolloutNamespaceLister 66 | // interface. 67 | type pipelineRolloutNamespaceLister struct { 68 | listers.ResourceIndexer[*v1alpha1.PipelineRollout] 69 | } 70 | -------------------------------------------------------------------------------- /internal/controller/common/numaflowtypes/monovertex.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package numaflowtypes 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | numaflowv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" 24 | "github.com/numaproj/numaplane/internal/util" 25 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 | 27 | "github.com/numaproj/numaplane/internal/util/kubernetes" 28 | "github.com/numaproj/numaplane/internal/util/logger" 29 | ) 30 | 31 | type MonoVertexStatus = kubernetes.GenericStatus 32 | 33 | func ParseMonoVertexStatus(monoVertex *unstructured.Unstructured) (MonoVertexStatus, error) { 34 | if monoVertex == nil || len(monoVertex.Object) == 0 { 35 | return MonoVertexStatus{}, nil 36 | } 37 | 38 | var status MonoVertexStatus 39 | err := util.StructToStruct(monoVertex.Object["status"], &status) 40 | if err != nil { 41 | return MonoVertexStatus{}, err 42 | } 43 | 44 | return status, nil 45 | } 46 | 47 | func CheckMonoVertexPhase(ctx context.Context, monovertex *unstructured.Unstructured, phase numaflowv1.PipelinePhase) bool { 48 | numaLogger := logger.FromContext(ctx) 49 | pipelineStatus, err := ParseMonoVertexStatus(monovertex) 50 | if err != nil { 51 | numaLogger.Errorf(err, "failed to parse MonoVertex Status from monovertex CR: %+v, %v", monovertex, err) 52 | return false 53 | } 54 | 55 | return numaflowv1.PipelinePhase(pipelineStatus.Phase) == phase 56 | } 57 | 58 | func GetMonoVertexDesiredPhase(monovertex *unstructured.Unstructured) (string, error) { 59 | desiredPhase, _, err := unstructured.NestedString(monovertex.Object, "spec", "lifecycle", "desiredPhase") 60 | if err != nil { 61 | return desiredPhase, err 62 | } 63 | 64 | if desiredPhase == "" { 65 | desiredPhase = string(numaflowv1.MonoVertexPhaseRunning) 66 | } 67 | return desiredPhase, err 68 | } 69 | 70 | // CanMonoVertexIngestData verifies that the configuration of the MonoVertex would allow it to ingest data 71 | // (must be set to Running and must have scale > 0) 72 | func CanMonoVertexIngestData(ctx context.Context, monovertex *unstructured.Unstructured) (bool, error) { 73 | 74 | scaleMinMax, err := ExtractScaleMinMax(monovertex.Object, []string{"spec", "scale"}) 75 | if err != nil { 76 | return false, fmt.Errorf("cannot extract the scale min and max values from the monovertex: %w", err) 77 | } 78 | zeroScale := scaleMinMax != nil && scaleMinMax.Max != nil && *scaleMinMax.Max == 0 79 | desiredPhase, err := GetMonoVertexDesiredPhase(monovertex) 80 | if err != nil { 81 | return false, err 82 | } 83 | return desiredPhase == string(numaflowv1.MonoVertexPhaseRunning) && !zeroScale, nil 84 | } 85 | -------------------------------------------------------------------------------- /pkg/client/listers/numaplane/v1alpha1/isbservicerollout.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "k8s.io/client-go/listers" 24 | "k8s.io/client-go/tools/cache" 25 | ) 26 | 27 | // ISBServiceRolloutLister helps list ISBServiceRollouts. 28 | // All objects returned here must be treated as read-only. 29 | type ISBServiceRolloutLister interface { 30 | // List lists all ISBServiceRollouts in the indexer. 31 | // Objects returned here must be treated as read-only. 32 | List(selector labels.Selector) (ret []*v1alpha1.ISBServiceRollout, err error) 33 | // ISBServiceRollouts returns an object that can list and get ISBServiceRollouts. 34 | ISBServiceRollouts(namespace string) ISBServiceRolloutNamespaceLister 35 | ISBServiceRolloutListerExpansion 36 | } 37 | 38 | // iSBServiceRolloutLister implements the ISBServiceRolloutLister interface. 39 | type iSBServiceRolloutLister struct { 40 | listers.ResourceIndexer[*v1alpha1.ISBServiceRollout] 41 | } 42 | 43 | // NewISBServiceRolloutLister returns a new ISBServiceRolloutLister. 44 | func NewISBServiceRolloutLister(indexer cache.Indexer) ISBServiceRolloutLister { 45 | return &iSBServiceRolloutLister{listers.New[*v1alpha1.ISBServiceRollout](indexer, v1alpha1.Resource("isbservicerollout"))} 46 | } 47 | 48 | // ISBServiceRollouts returns an object that can list and get ISBServiceRollouts. 49 | func (s *iSBServiceRolloutLister) ISBServiceRollouts(namespace string) ISBServiceRolloutNamespaceLister { 50 | return iSBServiceRolloutNamespaceLister{listers.NewNamespaced[*v1alpha1.ISBServiceRollout](s.ResourceIndexer, namespace)} 51 | } 52 | 53 | // ISBServiceRolloutNamespaceLister helps list and get ISBServiceRollouts. 54 | // All objects returned here must be treated as read-only. 55 | type ISBServiceRolloutNamespaceLister interface { 56 | // List lists all ISBServiceRollouts in the indexer for a given namespace. 57 | // Objects returned here must be treated as read-only. 58 | List(selector labels.Selector) (ret []*v1alpha1.ISBServiceRollout, err error) 59 | // Get retrieves the ISBServiceRollout from the indexer for a given namespace and name. 60 | // Objects returned here must be treated as read-only. 61 | Get(name string) (*v1alpha1.ISBServiceRollout, error) 62 | ISBServiceRolloutNamespaceListerExpansion 63 | } 64 | 65 | // iSBServiceRolloutNamespaceLister implements the ISBServiceRolloutNamespaceLister 66 | // interface. 67 | type iSBServiceRolloutNamespaceLister struct { 68 | listers.ResourceIndexer[*v1alpha1.ISBServiceRollout] 69 | } 70 | -------------------------------------------------------------------------------- /pkg/client/listers/numaplane/v1alpha1/monovertexrollout.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "k8s.io/client-go/listers" 24 | "k8s.io/client-go/tools/cache" 25 | ) 26 | 27 | // MonoVertexRolloutLister helps list MonoVertexRollouts. 28 | // All objects returned here must be treated as read-only. 29 | type MonoVertexRolloutLister interface { 30 | // List lists all MonoVertexRollouts in the indexer. 31 | // Objects returned here must be treated as read-only. 32 | List(selector labels.Selector) (ret []*v1alpha1.MonoVertexRollout, err error) 33 | // MonoVertexRollouts returns an object that can list and get MonoVertexRollouts. 34 | MonoVertexRollouts(namespace string) MonoVertexRolloutNamespaceLister 35 | MonoVertexRolloutListerExpansion 36 | } 37 | 38 | // monoVertexRolloutLister implements the MonoVertexRolloutLister interface. 39 | type monoVertexRolloutLister struct { 40 | listers.ResourceIndexer[*v1alpha1.MonoVertexRollout] 41 | } 42 | 43 | // NewMonoVertexRolloutLister returns a new MonoVertexRolloutLister. 44 | func NewMonoVertexRolloutLister(indexer cache.Indexer) MonoVertexRolloutLister { 45 | return &monoVertexRolloutLister{listers.New[*v1alpha1.MonoVertexRollout](indexer, v1alpha1.Resource("monovertexrollout"))} 46 | } 47 | 48 | // MonoVertexRollouts returns an object that can list and get MonoVertexRollouts. 49 | func (s *monoVertexRolloutLister) MonoVertexRollouts(namespace string) MonoVertexRolloutNamespaceLister { 50 | return monoVertexRolloutNamespaceLister{listers.NewNamespaced[*v1alpha1.MonoVertexRollout](s.ResourceIndexer, namespace)} 51 | } 52 | 53 | // MonoVertexRolloutNamespaceLister helps list and get MonoVertexRollouts. 54 | // All objects returned here must be treated as read-only. 55 | type MonoVertexRolloutNamespaceLister interface { 56 | // List lists all MonoVertexRollouts in the indexer for a given namespace. 57 | // Objects returned here must be treated as read-only. 58 | List(selector labels.Selector) (ret []*v1alpha1.MonoVertexRollout, err error) 59 | // Get retrieves the MonoVertexRollout from the indexer for a given namespace and name. 60 | // Objects returned here must be treated as read-only. 61 | Get(name string) (*v1alpha1.MonoVertexRollout, error) 62 | MonoVertexRolloutNamespaceListerExpansion 63 | } 64 | 65 | // monoVertexRolloutNamespaceLister implements the MonoVertexRolloutNamespaceLister 66 | // interface. 67 | type monoVertexRolloutNamespaceLister struct { 68 | listers.ResourceIndexer[*v1alpha1.MonoVertexRollout] 69 | } 70 | -------------------------------------------------------------------------------- /internal/controller/numaflowcontroller/numaflowcontroller_controller_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package numaflowcontroller 18 | 19 | import ( 20 | "fmt" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | 25 | apiv1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 26 | ) 27 | 28 | func Test_resolveManifestTemplate(t *testing.T) { 29 | defaultInstanceID := "123" 30 | 31 | defaultController := &apiv1.NumaflowController{ 32 | Spec: apiv1.NumaflowControllerSpec{ 33 | InstanceID: defaultInstanceID, 34 | }, 35 | } 36 | 37 | testCases := []struct { 38 | name string 39 | manifest string 40 | controller *apiv1.NumaflowController 41 | expectedManifest string 42 | expectedError error 43 | }{ 44 | { 45 | name: "nil controller", 46 | manifest: "", 47 | controller: nil, 48 | expectedManifest: "", 49 | expectedError: nil, 50 | }, { 51 | name: "empty manifest", 52 | manifest: "", 53 | controller: defaultController, 54 | expectedManifest: "", 55 | expectedError: nil, 56 | }, { 57 | name: "manifest with invalid template field", 58 | manifest: "this is {{.Invalid}} invalid", 59 | controller: defaultController, 60 | expectedManifest: "", 61 | expectedError: fmt.Errorf("unable to apply information to manifest: template: manifest:1:10: executing \"manifest\" at <.Invalid>: can't evaluate field Invalid in type struct { InstanceSuffix string; InstanceID string }"), 62 | }, { 63 | name: "manifest with valid template and controller without instanceID", 64 | manifest: "valid-template-no-id{{.InstanceSuffix}}", 65 | controller: &apiv1.NumaflowController{ 66 | Spec: apiv1.NumaflowControllerSpec{}, 67 | }, 68 | expectedManifest: "valid-template-no-id", 69 | expectedError: nil, 70 | }, { 71 | name: "manifest with valid template and controller with instanceID", 72 | manifest: "valid-template-no-id{{.InstanceSuffix}}", 73 | controller: defaultController, 74 | expectedManifest: fmt.Sprintf("valid-template-no-id-%s", defaultInstanceID), 75 | expectedError: nil, 76 | }, 77 | } 78 | 79 | for _, tc := range testCases { 80 | t.Run(tc.name, func(t *testing.T) { 81 | manifestBytes, err := resolveManifestTemplate(tc.manifest, tc.controller) 82 | 83 | if tc.expectedError != nil { 84 | assert.Error(t, err) 85 | assert.EqualError(t, err, tc.expectedError.Error()) 86 | } else { 87 | assert.NoError(t, err) 88 | } 89 | 90 | assert.Equal(t, tc.expectedManifest, string(manifestBytes)) 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /extensions/argo/numa-rollout/numa-rollout-extension/ui/src/rollout/default/ArgoRolloutContainers.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | 3 | import "./ArgoRolloutComponent.css"; 4 | import { RolloutComponentContext } from "../RolloutComponentWrapper"; 5 | import { Node } from "../../ArgoPropType"; 6 | import { 7 | CONTROLLER_MANAGER, 8 | ISB_KUBERNETES, 9 | ISB_SERVICE_ROLLOUT, 10 | KUBERNETES_APP, 11 | MONO_VERTEX, 12 | MONOVERTEX_ROLLOUT, 13 | NUMAFLOW_CONTROLLER_ROLLOUT, 14 | PIPELINE_ROLLOUT, 15 | POD, 16 | VERTEX, 17 | } from "../../utils/Constants"; 18 | 19 | export const ArgoRolloutContainers = () => { 20 | const { props } = useContext(RolloutComponentContext); 21 | const [images, setImages] = useState>(new Set()); 22 | useEffect(() => { 23 | const currentNodeKind = props?.resource?.kind; 24 | // Get all pods 25 | const allPods = props?.tree?.nodes?.filter( 26 | (node: Node) => node.kind === POD 27 | ); 28 | let filteredPods: any[] = []; 29 | 30 | switch (currentNodeKind) { 31 | case ISB_SERVICE_ROLLOUT: 32 | filteredPods = allPods?.filter( 33 | (pod) => 34 | pod?.networkingInfo?.labels && 35 | pod.networkingInfo.labels[KUBERNETES_APP] === ISB_KUBERNETES 36 | ); 37 | break; 38 | case NUMAFLOW_CONTROLLER_ROLLOUT: 39 | filteredPods = allPods?.filter( 40 | (pod) => 41 | pod?.networkingInfo?.labels && 42 | pod.networkingInfo.labels[KUBERNETES_APP] === CONTROLLER_MANAGER 43 | ); 44 | break; 45 | case PIPELINE_ROLLOUT: 46 | filteredPods = allPods?.filter( 47 | (pod) => 48 | pod?.networkingInfo?.labels && 49 | pod.networkingInfo.labels[KUBERNETES_APP] === VERTEX 50 | ); 51 | break; 52 | case MONOVERTEX_ROLLOUT: 53 | filteredPods = allPods?.filter( 54 | (pod) => 55 | pod?.networkingInfo?.labels && 56 | pod.networkingInfo.labels[KUBERNETES_APP] === MONO_VERTEX 57 | ); 58 | break; 59 | default: 60 | break; 61 | } 62 | let imgs: Set = new Set(); 63 | filteredPods.forEach((pod) => { 64 | pod.images?.forEach((image: string) => { 65 | imgs.add(image); 66 | }); 67 | }); 68 | setImages(imgs); 69 | }, [props?.tree]); 70 | 71 | return ( 72 |
73 |
Containers
74 | {images && 75 | Array.from(images).map((dockerImages) => { 76 | return ( 77 |
86 | 93 |
94 | ); 95 | })} 96 |
97 | ); 98 | }; 99 | -------------------------------------------------------------------------------- /pkg/client/listers/numaplane/v1alpha1/numaflowcontroller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "k8s.io/client-go/listers" 24 | "k8s.io/client-go/tools/cache" 25 | ) 26 | 27 | // NumaflowControllerLister helps list NumaflowControllers. 28 | // All objects returned here must be treated as read-only. 29 | type NumaflowControllerLister interface { 30 | // List lists all NumaflowControllers in the indexer. 31 | // Objects returned here must be treated as read-only. 32 | List(selector labels.Selector) (ret []*v1alpha1.NumaflowController, err error) 33 | // NumaflowControllers returns an object that can list and get NumaflowControllers. 34 | NumaflowControllers(namespace string) NumaflowControllerNamespaceLister 35 | NumaflowControllerListerExpansion 36 | } 37 | 38 | // numaflowControllerLister implements the NumaflowControllerLister interface. 39 | type numaflowControllerLister struct { 40 | listers.ResourceIndexer[*v1alpha1.NumaflowController] 41 | } 42 | 43 | // NewNumaflowControllerLister returns a new NumaflowControllerLister. 44 | func NewNumaflowControllerLister(indexer cache.Indexer) NumaflowControllerLister { 45 | return &numaflowControllerLister{listers.New[*v1alpha1.NumaflowController](indexer, v1alpha1.Resource("numaflowcontroller"))} 46 | } 47 | 48 | // NumaflowControllers returns an object that can list and get NumaflowControllers. 49 | func (s *numaflowControllerLister) NumaflowControllers(namespace string) NumaflowControllerNamespaceLister { 50 | return numaflowControllerNamespaceLister{listers.NewNamespaced[*v1alpha1.NumaflowController](s.ResourceIndexer, namespace)} 51 | } 52 | 53 | // NumaflowControllerNamespaceLister helps list and get NumaflowControllers. 54 | // All objects returned here must be treated as read-only. 55 | type NumaflowControllerNamespaceLister interface { 56 | // List lists all NumaflowControllers in the indexer for a given namespace. 57 | // Objects returned here must be treated as read-only. 58 | List(selector labels.Selector) (ret []*v1alpha1.NumaflowController, err error) 59 | // Get retrieves the NumaflowController from the indexer for a given namespace and name. 60 | // Objects returned here must be treated as read-only. 61 | Get(name string) (*v1alpha1.NumaflowController, error) 62 | NumaflowControllerNamespaceListerExpansion 63 | } 64 | 65 | // numaflowControllerNamespaceLister implements the NumaflowControllerNamespaceLister 66 | // interface. 67 | type numaflowControllerNamespaceLister struct { 68 | listers.ResourceIndexer[*v1alpha1.NumaflowController] 69 | } 70 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/numaplane/v1alpha1/pipelinerollout.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | "context" 22 | 23 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 24 | scheme "github.com/numaproj/numaplane/pkg/client/clientset/versioned/scheme" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | types "k8s.io/apimachinery/pkg/types" 27 | watch "k8s.io/apimachinery/pkg/watch" 28 | gentype "k8s.io/client-go/gentype" 29 | ) 30 | 31 | // PipelineRolloutsGetter has a method to return a PipelineRolloutInterface. 32 | // A group's client should implement this interface. 33 | type PipelineRolloutsGetter interface { 34 | PipelineRollouts(namespace string) PipelineRolloutInterface 35 | } 36 | 37 | // PipelineRolloutInterface has methods to work with PipelineRollout resources. 38 | type PipelineRolloutInterface interface { 39 | Create(ctx context.Context, pipelineRollout *v1alpha1.PipelineRollout, opts v1.CreateOptions) (*v1alpha1.PipelineRollout, error) 40 | Update(ctx context.Context, pipelineRollout *v1alpha1.PipelineRollout, opts v1.UpdateOptions) (*v1alpha1.PipelineRollout, error) 41 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 42 | UpdateStatus(ctx context.Context, pipelineRollout *v1alpha1.PipelineRollout, opts v1.UpdateOptions) (*v1alpha1.PipelineRollout, error) 43 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error 44 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error 45 | Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.PipelineRollout, error) 46 | List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.PipelineRolloutList, error) 47 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) 48 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.PipelineRollout, err error) 49 | PipelineRolloutExpansion 50 | } 51 | 52 | // pipelineRollouts implements PipelineRolloutInterface 53 | type pipelineRollouts struct { 54 | *gentype.ClientWithList[*v1alpha1.PipelineRollout, *v1alpha1.PipelineRolloutList] 55 | } 56 | 57 | // newPipelineRollouts returns a PipelineRollouts 58 | func newPipelineRollouts(c *NumaplaneV1alpha1Client, namespace string) *pipelineRollouts { 59 | return &pipelineRollouts{ 60 | gentype.NewClientWithList[*v1alpha1.PipelineRollout, *v1alpha1.PipelineRolloutList]( 61 | "pipelinerollouts", 62 | c.RESTClient(), 63 | scheme.ParameterCodec, 64 | namespace, 65 | func() *v1alpha1.PipelineRollout { return &v1alpha1.PipelineRollout{} }, 66 | func() *v1alpha1.PipelineRolloutList { return &v1alpha1.PipelineRolloutList{} }), 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pkg/client/informers/externalversions/numaplane/v1alpha1/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by informer-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | internalinterfaces "github.com/numaproj/numaplane/pkg/client/informers/externalversions/internalinterfaces" 22 | ) 23 | 24 | // Interface provides access to all the informers in this group version. 25 | type Interface interface { 26 | // ISBServiceRollouts returns a ISBServiceRolloutInformer. 27 | ISBServiceRollouts() ISBServiceRolloutInformer 28 | // MonoVertexRollouts returns a MonoVertexRolloutInformer. 29 | MonoVertexRollouts() MonoVertexRolloutInformer 30 | // NumaflowControllers returns a NumaflowControllerInformer. 31 | NumaflowControllers() NumaflowControllerInformer 32 | // NumaflowControllerRollouts returns a NumaflowControllerRolloutInformer. 33 | NumaflowControllerRollouts() NumaflowControllerRolloutInformer 34 | // PipelineRollouts returns a PipelineRolloutInformer. 35 | PipelineRollouts() PipelineRolloutInformer 36 | } 37 | 38 | type version struct { 39 | factory internalinterfaces.SharedInformerFactory 40 | namespace string 41 | tweakListOptions internalinterfaces.TweakListOptionsFunc 42 | } 43 | 44 | // New returns a new Interface. 45 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 46 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 47 | } 48 | 49 | // ISBServiceRollouts returns a ISBServiceRolloutInformer. 50 | func (v *version) ISBServiceRollouts() ISBServiceRolloutInformer { 51 | return &iSBServiceRolloutInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 52 | } 53 | 54 | // MonoVertexRollouts returns a MonoVertexRolloutInformer. 55 | func (v *version) MonoVertexRollouts() MonoVertexRolloutInformer { 56 | return &monoVertexRolloutInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 57 | } 58 | 59 | // NumaflowControllers returns a NumaflowControllerInformer. 60 | func (v *version) NumaflowControllers() NumaflowControllerInformer { 61 | return &numaflowControllerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 62 | } 63 | 64 | // NumaflowControllerRollouts returns a NumaflowControllerRolloutInformer. 65 | func (v *version) NumaflowControllerRollouts() NumaflowControllerRolloutInformer { 66 | return &numaflowControllerRolloutInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 67 | } 68 | 69 | // PipelineRollouts returns a PipelineRolloutInformer. 70 | func (v *version) PipelineRollouts() PipelineRolloutInformer { 71 | return &pipelineRolloutInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 72 | } 73 | -------------------------------------------------------------------------------- /internal/util/maps.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "maps" 21 | "strings" 22 | 23 | "github.com/google/go-cmp/cmp" 24 | ) 25 | 26 | func MergeMaps(existing, new map[string]string) map[string]string { 27 | merged := make(map[string]string) 28 | if existing != nil { 29 | merged = existing 30 | } 31 | 32 | for key, val := range new { 33 | merged[key] = val 34 | } 35 | 36 | return merged 37 | } 38 | 39 | func CompareMaps(existing, new map[string]string) bool { 40 | if existing == nil || new == nil { 41 | return len(existing) == len(new) 42 | } 43 | return cmp.Equal(existing, new) 44 | } 45 | 46 | // ConvertInterfaceMapToStringMap converts a map[string]interface{} to map[string]string 47 | // by type asserting each value to string. Non-string values are skipped. 48 | func ConvertInterfaceMapToStringMap(interfaceMap map[string]interface{}) map[string]string { 49 | if interfaceMap == nil { 50 | return nil 51 | } 52 | 53 | stringMap := make(map[string]string) 54 | for k, v := range interfaceMap { 55 | if str, ok := v.(string); ok { 56 | stringMap[k] = str 57 | } 58 | } 59 | return stringMap 60 | } 61 | 62 | func IsMapSubset(requiredKVPairs map[string]string, mapToCheck map[string]string) bool { 63 | 64 | // If there are no required key-value pairs (nil or empty), always return true 65 | if len(requiredKVPairs) == 0 { 66 | return true 67 | } 68 | // If the map to look for is nil or empty, return false 69 | if len(mapToCheck) == 0 { 70 | return false 71 | } 72 | 73 | for key, value := range requiredKVPairs { 74 | if mapToCheck[key] != value { 75 | return false 76 | } 77 | } 78 | return true 79 | } 80 | 81 | // CompareMapsWithExceptions compares two maps but ignoring any differences where the keys are prefixed with any of the 'prefixExceptions' 82 | func CompareMapsWithExceptions(existing, new map[string]string, prefixExceptions ...string) bool { 83 | // clone the maps because we can make nil maps empty maps to make it easier to compare 84 | existingCopy := maps.Clone(existing) 85 | newCopy := maps.Clone(new) 86 | if existingCopy == nil { 87 | existingCopy = make(map[string]string) 88 | } 89 | if newCopy == nil { 90 | newCopy = make(map[string]string) 91 | } 92 | 93 | for existingKey, existingValue := range existingCopy { 94 | isException := false 95 | for _, prefixException := range prefixExceptions { 96 | if strings.HasPrefix(existingKey, prefixException) { 97 | isException = true 98 | break 99 | } 100 | } 101 | if !isException { 102 | // is this key in the other map and does it have the same value? 103 | newValue := newCopy[existingKey] 104 | if existingValue != newValue { 105 | return false 106 | } 107 | } 108 | 109 | } 110 | 111 | return true 112 | } 113 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/numaplane/v1alpha1/isbservicerollout.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | "context" 22 | 23 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 24 | scheme "github.com/numaproj/numaplane/pkg/client/clientset/versioned/scheme" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | types "k8s.io/apimachinery/pkg/types" 27 | watch "k8s.io/apimachinery/pkg/watch" 28 | gentype "k8s.io/client-go/gentype" 29 | ) 30 | 31 | // ISBServiceRolloutsGetter has a method to return a ISBServiceRolloutInterface. 32 | // A group's client should implement this interface. 33 | type ISBServiceRolloutsGetter interface { 34 | ISBServiceRollouts(namespace string) ISBServiceRolloutInterface 35 | } 36 | 37 | // ISBServiceRolloutInterface has methods to work with ISBServiceRollout resources. 38 | type ISBServiceRolloutInterface interface { 39 | Create(ctx context.Context, iSBServiceRollout *v1alpha1.ISBServiceRollout, opts v1.CreateOptions) (*v1alpha1.ISBServiceRollout, error) 40 | Update(ctx context.Context, iSBServiceRollout *v1alpha1.ISBServiceRollout, opts v1.UpdateOptions) (*v1alpha1.ISBServiceRollout, error) 41 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 42 | UpdateStatus(ctx context.Context, iSBServiceRollout *v1alpha1.ISBServiceRollout, opts v1.UpdateOptions) (*v1alpha1.ISBServiceRollout, error) 43 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error 44 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error 45 | Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ISBServiceRollout, error) 46 | List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.ISBServiceRolloutList, error) 47 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) 48 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ISBServiceRollout, err error) 49 | ISBServiceRolloutExpansion 50 | } 51 | 52 | // iSBServiceRollouts implements ISBServiceRolloutInterface 53 | type iSBServiceRollouts struct { 54 | *gentype.ClientWithList[*v1alpha1.ISBServiceRollout, *v1alpha1.ISBServiceRolloutList] 55 | } 56 | 57 | // newISBServiceRollouts returns a ISBServiceRollouts 58 | func newISBServiceRollouts(c *NumaplaneV1alpha1Client, namespace string) *iSBServiceRollouts { 59 | return &iSBServiceRollouts{ 60 | gentype.NewClientWithList[*v1alpha1.ISBServiceRollout, *v1alpha1.ISBServiceRolloutList]( 61 | "isbservicerollouts", 62 | c.RESTClient(), 63 | scheme.ParameterCodec, 64 | namespace, 65 | func() *v1alpha1.ISBServiceRollout { return &v1alpha1.ISBServiceRollout{} }, 66 | func() *v1alpha1.ISBServiceRolloutList { return &v1alpha1.ISBServiceRolloutList{} }), 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/numaplane/v1alpha1/monovertexrollout.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | "context" 22 | 23 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 24 | scheme "github.com/numaproj/numaplane/pkg/client/clientset/versioned/scheme" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | types "k8s.io/apimachinery/pkg/types" 27 | watch "k8s.io/apimachinery/pkg/watch" 28 | gentype "k8s.io/client-go/gentype" 29 | ) 30 | 31 | // MonoVertexRolloutsGetter has a method to return a MonoVertexRolloutInterface. 32 | // A group's client should implement this interface. 33 | type MonoVertexRolloutsGetter interface { 34 | MonoVertexRollouts(namespace string) MonoVertexRolloutInterface 35 | } 36 | 37 | // MonoVertexRolloutInterface has methods to work with MonoVertexRollout resources. 38 | type MonoVertexRolloutInterface interface { 39 | Create(ctx context.Context, monoVertexRollout *v1alpha1.MonoVertexRollout, opts v1.CreateOptions) (*v1alpha1.MonoVertexRollout, error) 40 | Update(ctx context.Context, monoVertexRollout *v1alpha1.MonoVertexRollout, opts v1.UpdateOptions) (*v1alpha1.MonoVertexRollout, error) 41 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 42 | UpdateStatus(ctx context.Context, monoVertexRollout *v1alpha1.MonoVertexRollout, opts v1.UpdateOptions) (*v1alpha1.MonoVertexRollout, error) 43 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error 44 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error 45 | Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.MonoVertexRollout, error) 46 | List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.MonoVertexRolloutList, error) 47 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) 48 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.MonoVertexRollout, err error) 49 | MonoVertexRolloutExpansion 50 | } 51 | 52 | // monoVertexRollouts implements MonoVertexRolloutInterface 53 | type monoVertexRollouts struct { 54 | *gentype.ClientWithList[*v1alpha1.MonoVertexRollout, *v1alpha1.MonoVertexRolloutList] 55 | } 56 | 57 | // newMonoVertexRollouts returns a MonoVertexRollouts 58 | func newMonoVertexRollouts(c *NumaplaneV1alpha1Client, namespace string) *monoVertexRollouts { 59 | return &monoVertexRollouts{ 60 | gentype.NewClientWithList[*v1alpha1.MonoVertexRollout, *v1alpha1.MonoVertexRolloutList]( 61 | "monovertexrollouts", 62 | c.RESTClient(), 63 | scheme.ParameterCodec, 64 | namespace, 65 | func() *v1alpha1.MonoVertexRollout { return &v1alpha1.MonoVertexRollout{} }, 66 | func() *v1alpha1.MonoVertexRolloutList { return &v1alpha1.MonoVertexRolloutList{} }), 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /internal/controller/common/numaflowtypes/isbsvc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package numaflowtypes 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | numaflowv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" 24 | "github.com/numaproj/numaplane/internal/util/kubernetes" 25 | appsv1 "k8s.io/api/apps/v1" 26 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 | "k8s.io/apimachinery/pkg/labels" 29 | "k8s.io/apimachinery/pkg/selection" 30 | "sigs.k8s.io/controller-runtime/pkg/client" 31 | ) 32 | 33 | // Each ISBService has one underlying StatefulSet 34 | // Find it 35 | // Depending on value "checkLive", either check K8S API directly or go to informer cache 36 | func GetISBSvcStatefulSetFromK8s(ctx context.Context, c client.Client, isbsvc *unstructured.Unstructured, checkLive bool) (*appsv1.StatefulSet, error) { 37 | statefulSetSelector := labels.NewSelector() 38 | requirement, err := labels.NewRequirement(numaflowv1.KeyISBSvcName, selection.Equals, []string{isbsvc.GetName()}) 39 | if err != nil { 40 | return nil, fmt.Errorf("Error creating label requirement: %v", err) 41 | } 42 | statefulSetSelector = statefulSetSelector.Add(*requirement) 43 | 44 | var statefulSetList appsv1.StatefulSetList 45 | if checkLive { 46 | statefulSets, err := kubernetes.KubernetesClient.AppsV1().StatefulSets(isbsvc.GetNamespace()).List(ctx, metav1.ListOptions{ 47 | LabelSelector: fmt.Sprintf("%s=%s", numaflowv1.KeyISBSvcName, isbsvc.GetName())}) 48 | if err != nil { 49 | return nil, fmt.Errorf("Error listing live StatefulSets: %v", err) 50 | } 51 | statefulSetList = *statefulSets 52 | } else { 53 | //TODO: this is currently making a Live K8S call, not a call to cache as it's supposed to 54 | // Option 1: figure out how we can watch a StatefulSet and have its owner's owner reconcile it 55 | // Option 2: update InterstepBufferService code in Numaflow to provide all the info we need so we don't need to access StatefulSet directly 56 | err = c.List(ctx, &statefulSetList, &client.ListOptions{Namespace: isbsvc.GetNamespace(), LabelSelector: statefulSetSelector}) 57 | if err != nil { 58 | return nil, fmt.Errorf("Error listing StatefulSets: %v", err) 59 | } 60 | } 61 | if len(statefulSetList.Items) > 1 { 62 | return nil, fmt.Errorf("unexpected: isbsvc %s/%s has multiple StatefulSets: %+v", isbsvc.GetNamespace(), isbsvc.GetName(), statefulSetList.Items) 63 | } else if len(statefulSetList.Items) == 0 { 64 | return nil, nil 65 | } else { 66 | return &(statefulSetList.Items[0]), nil 67 | } 68 | } 69 | 70 | func GetISBServiceChildResourceHealth(conditions []metav1.Condition) (metav1.ConditionStatus, string) { 71 | for _, cond := range conditions { 72 | if cond.Type == "ChildrenResourcesHealthy" && cond.Status != metav1.ConditionTrue { 73 | return cond.Status, cond.Reason 74 | } 75 | } 76 | return metav1.ConditionTrue, "" 77 | } 78 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/typed/numaplane/v1alpha1/numaflowcontroller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | "context" 22 | 23 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 24 | scheme "github.com/numaproj/numaplane/pkg/client/clientset/versioned/scheme" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | types "k8s.io/apimachinery/pkg/types" 27 | watch "k8s.io/apimachinery/pkg/watch" 28 | gentype "k8s.io/client-go/gentype" 29 | ) 30 | 31 | // NumaflowControllersGetter has a method to return a NumaflowControllerInterface. 32 | // A group's client should implement this interface. 33 | type NumaflowControllersGetter interface { 34 | NumaflowControllers(namespace string) NumaflowControllerInterface 35 | } 36 | 37 | // NumaflowControllerInterface has methods to work with NumaflowController resources. 38 | type NumaflowControllerInterface interface { 39 | Create(ctx context.Context, numaflowController *v1alpha1.NumaflowController, opts v1.CreateOptions) (*v1alpha1.NumaflowController, error) 40 | Update(ctx context.Context, numaflowController *v1alpha1.NumaflowController, opts v1.UpdateOptions) (*v1alpha1.NumaflowController, error) 41 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 42 | UpdateStatus(ctx context.Context, numaflowController *v1alpha1.NumaflowController, opts v1.UpdateOptions) (*v1alpha1.NumaflowController, error) 43 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error 44 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error 45 | Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.NumaflowController, error) 46 | List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.NumaflowControllerList, error) 47 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) 48 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NumaflowController, err error) 49 | NumaflowControllerExpansion 50 | } 51 | 52 | // numaflowControllers implements NumaflowControllerInterface 53 | type numaflowControllers struct { 54 | *gentype.ClientWithList[*v1alpha1.NumaflowController, *v1alpha1.NumaflowControllerList] 55 | } 56 | 57 | // newNumaflowControllers returns a NumaflowControllers 58 | func newNumaflowControllers(c *NumaplaneV1alpha1Client, namespace string) *numaflowControllers { 59 | return &numaflowControllers{ 60 | gentype.NewClientWithList[*v1alpha1.NumaflowController, *v1alpha1.NumaflowControllerList]( 61 | "numaflowcontrollers", 62 | c.RESTClient(), 63 | scheme.ParameterCodec, 64 | namespace, 65 | func() *v1alpha1.NumaflowController { return &v1alpha1.NumaflowController{} }, 66 | func() *v1alpha1.NumaflowControllerList { return &v1alpha1.NumaflowControllerList{} }), 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pkg/client/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by client-gen. DO NOT EDIT. 17 | 18 | package fake 19 | 20 | import ( 21 | clientset "github.com/numaproj/numaplane/pkg/client/clientset/versioned" 22 | numaplanev1alpha1 "github.com/numaproj/numaplane/pkg/client/clientset/versioned/typed/numaplane/v1alpha1" 23 | fakenumaplanev1alpha1 "github.com/numaproj/numaplane/pkg/client/clientset/versioned/typed/numaplane/v1alpha1/fake" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | "k8s.io/apimachinery/pkg/watch" 26 | "k8s.io/client-go/discovery" 27 | fakediscovery "k8s.io/client-go/discovery/fake" 28 | "k8s.io/client-go/testing" 29 | ) 30 | 31 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 32 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 33 | // without applying any field management, validations and/or defaults. It shouldn't be considered a replacement 34 | // for a real clientset and is mostly useful in simple unit tests. 35 | // 36 | // DEPRECATED: NewClientset replaces this with support for field management, which significantly improves 37 | // server side apply testing. NewClientset is only available when apply configurations are generated (e.g. 38 | // via --with-applyconfig). 39 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 40 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 41 | for _, obj := range objects { 42 | if err := o.Add(obj); err != nil { 43 | panic(err) 44 | } 45 | } 46 | 47 | cs := &Clientset{tracker: o} 48 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 49 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 50 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 51 | gvr := action.GetResource() 52 | ns := action.GetNamespace() 53 | watch, err := o.Watch(gvr, ns) 54 | if err != nil { 55 | return false, nil, err 56 | } 57 | return true, watch, nil 58 | }) 59 | 60 | return cs 61 | } 62 | 63 | // Clientset implements clientset.Interface. Meant to be embedded into a 64 | // struct to get a default implementation. This makes faking out just the method 65 | // you want to test easier. 66 | type Clientset struct { 67 | testing.Fake 68 | discovery *fakediscovery.FakeDiscovery 69 | tracker testing.ObjectTracker 70 | } 71 | 72 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 73 | return c.discovery 74 | } 75 | 76 | func (c *Clientset) Tracker() testing.ObjectTracker { 77 | return c.tracker 78 | } 79 | 80 | var ( 81 | _ clientset.Interface = &Clientset{} 82 | _ testing.FakeClient = &Clientset{} 83 | ) 84 | 85 | // NumaplaneV1alpha1 retrieves the NumaplaneV1alpha1Client 86 | func (c *Clientset) NumaplaneV1alpha1() numaplanev1alpha1.NumaplaneV1alpha1Interface { 87 | return &fakenumaplanev1alpha1.FakeNumaplaneV1alpha1{Fake: &c.Fake} 88 | } 89 | -------------------------------------------------------------------------------- /pkg/client/listers/numaplane/v1alpha1/numaflowcontrollerrollout.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | // Code generated by lister-gen. DO NOT EDIT. 17 | 18 | package v1alpha1 19 | 20 | import ( 21 | v1alpha1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 22 | "k8s.io/apimachinery/pkg/labels" 23 | "k8s.io/client-go/listers" 24 | "k8s.io/client-go/tools/cache" 25 | ) 26 | 27 | // NumaflowControllerRolloutLister helps list NumaflowControllerRollouts. 28 | // All objects returned here must be treated as read-only. 29 | type NumaflowControllerRolloutLister interface { 30 | // List lists all NumaflowControllerRollouts in the indexer. 31 | // Objects returned here must be treated as read-only. 32 | List(selector labels.Selector) (ret []*v1alpha1.NumaflowControllerRollout, err error) 33 | // NumaflowControllerRollouts returns an object that can list and get NumaflowControllerRollouts. 34 | NumaflowControllerRollouts(namespace string) NumaflowControllerRolloutNamespaceLister 35 | NumaflowControllerRolloutListerExpansion 36 | } 37 | 38 | // numaflowControllerRolloutLister implements the NumaflowControllerRolloutLister interface. 39 | type numaflowControllerRolloutLister struct { 40 | listers.ResourceIndexer[*v1alpha1.NumaflowControllerRollout] 41 | } 42 | 43 | // NewNumaflowControllerRolloutLister returns a new NumaflowControllerRolloutLister. 44 | func NewNumaflowControllerRolloutLister(indexer cache.Indexer) NumaflowControllerRolloutLister { 45 | return &numaflowControllerRolloutLister{listers.New[*v1alpha1.NumaflowControllerRollout](indexer, v1alpha1.Resource("numaflowcontrollerrollout"))} 46 | } 47 | 48 | // NumaflowControllerRollouts returns an object that can list and get NumaflowControllerRollouts. 49 | func (s *numaflowControllerRolloutLister) NumaflowControllerRollouts(namespace string) NumaflowControllerRolloutNamespaceLister { 50 | return numaflowControllerRolloutNamespaceLister{listers.NewNamespaced[*v1alpha1.NumaflowControllerRollout](s.ResourceIndexer, namespace)} 51 | } 52 | 53 | // NumaflowControllerRolloutNamespaceLister helps list and get NumaflowControllerRollouts. 54 | // All objects returned here must be treated as read-only. 55 | type NumaflowControllerRolloutNamespaceLister interface { 56 | // List lists all NumaflowControllerRollouts in the indexer for a given namespace. 57 | // Objects returned here must be treated as read-only. 58 | List(selector labels.Selector) (ret []*v1alpha1.NumaflowControllerRollout, err error) 59 | // Get retrieves the NumaflowControllerRollout from the indexer for a given namespace and name. 60 | // Objects returned here must be treated as read-only. 61 | Get(name string) (*v1alpha1.NumaflowControllerRollout, error) 62 | NumaflowControllerRolloutNamespaceListerExpansion 63 | } 64 | 65 | // numaflowControllerRolloutNamespaceLister implements the NumaflowControllerRolloutNamespaceLister 66 | // interface. 67 | type numaflowControllerRolloutNamespaceLister struct { 68 | listers.ResourceIndexer[*v1alpha1.NumaflowControllerRollout] 69 | } 70 | -------------------------------------------------------------------------------- /internal/controller/common/in_progress_strategy_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package common 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | 25 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | k8stypes "k8s.io/apimachinery/pkg/types" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | apiv1 "github.com/numaproj/numaplane/pkg/apis/numaplane/v1alpha1" 30 | ) 31 | 32 | func Test_inProgressStrategyMgr_getStrategy(t *testing.T) { 33 | 34 | progressiveStrategy := apiv1.UpgradeStrategyProgressive 35 | ppndStrategy := apiv1.UpgradeStrategyPPND 36 | noStrategy := apiv1.UpgradeStrategyNoOp 37 | 38 | testCases := []struct { 39 | name string 40 | inMemoryStrategy *apiv1.UpgradeStrategy 41 | rolloutStatusStrategy *apiv1.UpgradeStrategy 42 | resultStrategy apiv1.UpgradeStrategy 43 | }{ 44 | { 45 | name: "in memory and in Rollout Status (progressive result)", 46 | inMemoryStrategy: &progressiveStrategy, 47 | rolloutStatusStrategy: &noStrategy, 48 | resultStrategy: progressiveStrategy, 49 | }, 50 | { 51 | name: "in memory and in Rollout Status (no op result)", 52 | inMemoryStrategy: &noStrategy, 53 | rolloutStatusStrategy: &ppndStrategy, 54 | resultStrategy: noStrategy, 55 | }, 56 | { 57 | name: "in memory and not in Rollout Status", 58 | inMemoryStrategy: &progressiveStrategy, 59 | rolloutStatusStrategy: nil, 60 | resultStrategy: progressiveStrategy, 61 | }, 62 | { 63 | name: "in Rollout Status and not in memory", 64 | inMemoryStrategy: nil, 65 | rolloutStatusStrategy: &ppndStrategy, 66 | resultStrategy: ppndStrategy, 67 | }, 68 | { 69 | name: "neither in Rollout Status nor in memory", 70 | inMemoryStrategy: nil, 71 | rolloutStatusStrategy: nil, 72 | resultStrategy: noStrategy, 73 | }, 74 | } 75 | 76 | for _, tc := range testCases { 77 | t.Run(tc.name, func(t *testing.T) { 78 | inProgressStrategyMgr := NewInProgressStrategyMgr( 79 | // getRolloutStrategy function: 80 | func(ctx context.Context, rollout client.Object) *apiv1.UpgradeStrategy { 81 | return tc.rolloutStatusStrategy 82 | }, 83 | // setRolloutStrategy function: 84 | func(ctx context.Context, rollout client.Object, strategy apiv1.UpgradeStrategy) {}, 85 | ) 86 | pipelineRollout := &apiv1.PipelineRollout{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "my-pipeline"}} 87 | namespacedName := k8stypes.NamespacedName{Namespace: pipelineRollout.GetNamespace(), Name: pipelineRollout.GetName()} 88 | if tc.inMemoryStrategy != nil { 89 | inProgressStrategyMgr.Store.SetStrategy(namespacedName, *tc.inMemoryStrategy) 90 | } 91 | upgradeStrategyResult := inProgressStrategyMgr.GetStrategy(context.Background(), pipelineRollout) 92 | assert.Equal(t, tc.resultStrategy, upgradeStrategyResult) 93 | }) 94 | } 95 | } 96 | --------------------------------------------------------------------------------