{{- if .Values.deployKF.kubeflow.pipelines.cache.namespaceRedirect }}
{{- $compatible_v1_keyFormat := .Values.deployKF.argoWorkflows.artifactRepository.keyFormat | hasPrefix "artifacts/{{ workflow.namespace }}/" }}
{{- $compatible_v2_defaultPipelineRoot := .Values.deployKF.kubeflow.pipelines.kfpV2.defaultPipelineRoot | regexMatch `^(?:[^:/]+)://(?:[^/]+)/v2/artifacts/{profile_name}.*` }}
{{- if and .Values.deployKF.kubeflow.pipelines.enabled $compatible_v1_keyFormat }}
################
## This EnvoyFilter redirects artifact requests that use an incorrect `?namespace=` query parameter for an artifact.
## This is required because bucket IAM policies restrict bucket access based on the namespace prefix.
## NOTE: Kubeflow Pipelines will sometimes use an artifact which was cached by another namespace.
## NOTE: the user might still not have access after the redirect if they are not a member of the correct namespace.
################
{{- /* the listener port-numbers to apply these patches to */ -}}
{{- /* NOTE: Istio does NOT allow `match.routeConfiguration.gateway` to be used on HTTP routes (https://github.com/istio/istio/issues/46459),
       so if HTTP routes are needed (when `deploykf_core.deploykf_istio_gateway.gateway.tls.redirect` is false),
       they are implemented by internally proxying them to the HTTPS routes (see `VirtualService/https-redirect`) */ -}}
{{- $listner_port_numbers := list }}
{{- if .Values.deployKF.gateway.tls.enabled }}
  {{- $listner_port_numbers = .Values.deployKF.gateway.ports.https | int | append $listner_port_numbers }}
{{- end }}
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: deploykf-istio-gateway--kubeflow-pipelines-redirect-artifact-namespaces
  labels:
    helm.sh/chart: {{ include "deploykf-istio-gateway.labels.chart" . }}
    app.kubernetes.io/name: {{ include "deploykf-istio-gateway.labels.name" . }}
    app.kubernetes.io/instance: {{ .Release.Name }}
    app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
  ## this filter must be applied after 'deploykf-istio-gateway--ext-authz'
  priority: 10
  workloadSelector:
    labels:
      {{- toYaml .Values.deployKF.gateway.selectorLabels | nindent 6 }}
  configPatches:
    ################################################################################
    ## FILTER - Define the Lua filter
    ################################################################################
    {{- range $port_number := $listner_port_numbers }}
    {{- "\n" }}
    ## for port {{ $port_number }}
    - applyTo: HTTP_FILTER
      match:
        context: GATEWAY
        listener:
          portNumber: {{ $port_number }}
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
              subFilter:
                name: envoy.filters.http.deploykf.set_userid_header
      patch:
        ## insert right AFTER the `envoy.filters.http.deploykf.set_userid_header` filter
        operation: INSERT_AFTER
        value:
          name: envoy.filters.http.deploykf.kfp_redirect_artifact_namespaces
          typed_config:
            ## https://www.envoyproxy.io/docs/envoy/v1.23.1/api-v3/extensions/filters/http/lua/v3/lua.proto
            ## https://www.envoyproxy.io/docs/envoy/v1.23.1/configuration/http/http_filters/lua_filter
            "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua

            ## by default, do nothing
            default_source_code:
              inline_string: |
                function envoy_on_request(request_handle)
                  -- empty lua function
                end

            ## in our LuaPerRoute filter, we choose which source code to use for the route
            source_codes:

              ## SOURCE CODE:
              kfp_redirect_artifact_namespaces.lua:
                inline_string: |
                  function extract_url_namespaces(url_path)
                      -- extract namespace from object key
                      local key_namespace
                      if url_path:match("^/pipeline/artifacts/get") then
                          -- FORMAT: "/pipeline/artifacts/get...&key=artifacts%2F<key_namespace>%2F..."
                          key_namespace = url_path:match("[?&]key=artifacts%%2F([^%%]+)%%2F")

                          {{- if $compatible_v2_defaultPipelineRoot }}
                          -- try v2 format, if v1 format did not match
                          if not key_namespace then
                            -- FORMAT: "/pipeline/artifacts/get...&key=v2%2Fartifacts%2F<key_namespace>%2F..."
                            key_namespace = url_path:match("[?&]key=v2%%2Fartifacts%%2F([^%%]+)%%2F")
                          end
                          {{- end }}
                      else
                          -- FORMAT: "/pipeline/artifacts/<object_store>/<bucket>/artifacts/<key_namespace>/..."
                          key_namespace = url_path:match("^/pipeline/artifacts/[^/?&]+/[^/?&]+/artifacts/([^/?&]+)/")

                          {{- if $compatible_v2_defaultPipelineRoot }}
                          -- try v2 format, if v1 format did not match
                          if not key_namespace then
                            -- FORMAT: "/pipeline/artifacts/<object_store>/<bucket>/v2/artifacts/<key_namespace>/..."
                            key_namespace = url_path:match("^/pipeline/artifacts/[^/?&]+/[^/?&]+/v2/artifacts/([^/?&]+)/")
                          end
                          {{- end }}
                      end

                      -- extract namespace from query, if key_namespace is not nil
                      local query_namespace
                      if key_namespace then
                          -- FORMAT: "/pipeline/artifacts/...&namespace=<query_namespace>..."
                          query_namespace = url_path:match("[?&]namespace=([^&]*)")
                      end

                      return key_namespace, query_namespace
                  end

                  function envoy_on_request(request_handle)
                      local url_path = request_handle:headers():get(":path")
                      local key_namespace, query_namespace = extract_url_namespaces(url_path)

                      if key_namespace then
                          local new_url_path

                          -- CASE 1: query_namespace is set and does not match key_namespace
                          if query_namespace and key_namespace ~= query_namespace then
                              local match_str = "([?&])namespace=[^?&]*"
                              local replace_str = "%1namespace=" .. key_namespace
                              new_url_path = url_path:gsub(match_str, replace_str)

                          -- CASE 2: query_namespace is unset
                          elseif not query_namespace then

                              -- CASE 2a: path already has a query parameter
                              if url_path:match("[?]") then
                                  new_url_path = url_path .. "&namespace=" .. key_namespace

                              -- CASE 2b: path does not have a query parameter
                              else
                                  new_url_path = url_path .. "?namespace=" .. key_namespace
                              end
                          end

                          -- redirect to new_url_path, if it was set
                          if new_url_path then
                              request_handle:respond(
                                  { [":status"] = "307", ["location"] = new_url_path },
                                    "Redirecting to correct namespace..."
                              )
                          end
                      end
                  end
    {{- end }}

    ################################################################################
    ## ROUTE - Enable lua filter for the 'ml-pipeline-ui-artifacts' route
    ################################################################################
    {{- range $port_number := $listner_port_numbers }}
    {{- "\n" }}
    ## for port {{ $port_number }}
    - applyTo: HTTP_ROUTE
      match:
        context: GATEWAY
        routeConfiguration:
          portNumber: {{ $port_number }}
          gateway: "{{ $.Release.Namespace }}/{{ $.Values.deployKF.gateway.name }}"
          vhost:
            route:
              ## HTTP route names are specified in VirtualServices
              name: ml-pipeline-ui-artifacts-route
      patch:
        operation: MERGE
        value:
          typed_per_filter_config:
            ## the Lua filter that we defined above
            envoy.filters.http.deploykf.kfp_redirect_artifact_namespaces:
              ## https://www.envoyproxy.io/docs/envoy/v1.23.1/api-v3/extensions/filters/http/lua/v3/lua.proto#extensions-filters-http-lua-v3-luaperroute
              "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute
              name: kfp_redirect_artifact_namespaces.lua
    {{- end }}
{{- end }}
{{- end }}