diff --git a/applications/sia/Chart.yaml b/applications/sia/Chart.yaml new file mode 100644 index 0000000000..8ddf1e2c6d --- /dev/null +++ b/applications/sia/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +appVersion: 0.1.2 +description: Simple Image Access (SIA) IVOA Service using Butler +name: sia +sources: +- https://github.com/lsst-sqre/sia +type: application +version: 1.0.0 +annotations: + phalanx.lsst.io/docs: | + - id: "SQR-095" + title: "SIAv2 over Butler FastAPI service" + url: "https://sqr-095.lsst.io" diff --git a/applications/sia/README.md b/applications/sia/README.md new file mode 100644 index 0000000000..e030a859fa --- /dev/null +++ b/applications/sia/README.md @@ -0,0 +1,35 @@ +# sia + +Simple Image Access (SIA) IVOA Service using Butler + +## Source Code + +* + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Affinity rules for the sia deployment pod | +| config.butlerDataCollections | list | `[]` | List of data (Butler) Collections Expected attributes: `config`, `label`, `name`, `butler_type`, `repository`, `datalink_url` & `default_instrument` | +| config.directButlerEnabled | bool | `false` | Whether direct butler access is enabled | +| config.logLevel | string | `"INFO"` | Logging level | +| config.logProfile | string | `"production"` | Logging profile (`production` for JSON, `development` for human-friendly) | +| config.pathPrefix | string | `"/api/sia"` | URL path prefix | +| config.pgUser | string | `"rubin"` | User to use from the PGPASSFILE if sia is using a direct Butler connection | +| config.slackAlerts | bool | `false` | Whether to send alerts and status to Slack. | +| fullnameOverride | string | `""` | Override the full name for resources (includes the release name) | +| global.baseUrl | string | Set by Argo CD | Base URL for the environment | +| global.host | string | Set by Argo CD | Host name for ingress | +| global.vaultSecretsPath | string | Set by Argo CD | Base path for Vault secrets | +| image.pullPolicy | string | `"IfNotPresent"` | Pull policy for the sia image | +| image.repository | string | `"ghcr.io/lsst-sqre/sia"` | Image to use in the sia deployment | +| image.tag | string | The appVersion of the chart | Tag of image to use | +| ingress.annotations | object | `{}` | Additional annotations for the ingress rule | +| ingress.path | string | `"/api/sia"` | Path prefix where app is hosted | +| nameOverride | string | `""` | Override the base name for resources | +| nodeSelector | object | `{}` | Node selection rules for the sia deployment pod | +| podAnnotations | object | `{}` | Annotations for the sia deployment pod | +| replicaCount | int | `1` | Number of web deployment pods to start | +| resources | object | See `values.yaml` | Resource limits and requests for the sia deployment pod | +| tolerations | list | `[]` | Tolerations for the sia deployment pod | diff --git a/applications/sia/secrets.yaml b/applications/sia/secrets.yaml new file mode 100644 index 0000000000..05d15deeea --- /dev/null +++ b/applications/sia/secrets.yaml @@ -0,0 +1,23 @@ +"aws-credentials.ini": + if: config.directButlerEnabled + copy: + application: nublado + key: "aws-credentials.ini" +"butler-gcs-idf-creds.json": + if: config.directButlerEnabled + copy: + application: nublado + key: "butler-gcs-idf-creds.json" +"postgres-credentials.txt": + if: config.directButlerEnabled + copy: + application: nublado + key: "postgres-credentials.txt" +slack-webhook: + description: >- + Slack web hook used to report internal errors to Slack. This secret may be + changed at any time. + if: config.slackAlerts + copy: + application: mobu + key: app-alert-webhook diff --git a/applications/sia/templates/_helpers.tpl b/applications/sia/templates/_helpers.tpl new file mode 100644 index 0000000000..92bdc6ea01 --- /dev/null +++ b/applications/sia/templates/_helpers.tpl @@ -0,0 +1,52 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "sia.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "sia.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "sia.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "sia.labels" -}} +helm.sh/chart: {{ include "sia.chart" . }} +{{ include "sia.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "sia.selectorLabels" -}} +app.kubernetes.io/name: "sia" +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + diff --git a/applications/sia/templates/configmap.yaml b/applications/sia/templates/configmap.yaml new file mode 100644 index 0000000000..f594419d9f --- /dev/null +++ b/applications/sia/templates/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "sia" + labels: + {{- include "sia.labels" . | nindent 4 }} +data: + SIA_LOG_LEVEL: {{ .Values.config.logLevel | quote }} + SIA_PATH_PREFIX: {{ .Values.config.pathPrefix | quote }} + SIA_PROFILE: {{ .Values.config.logProfile | quote }} diff --git a/applications/sia/templates/deployment.yaml b/applications/sia/templates/deployment.yaml new file mode 100644 index 0000000000..e7da6b12f4 --- /dev/null +++ b/applications/sia/templates/deployment.yaml @@ -0,0 +1,117 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "sia" + labels: + {{- include "sia.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "sia.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "sia.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: false + {{- if .Values.config.directButlerEnabled }} + initContainers: + - name: fix-secret-permissions + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + command: + - "/bin/sh" + - "-c" + - | + cp -RL /tmp/secrets-raw/* /etc/butler/secrets/ + chmod 0400 /etc/butler/secrets/* + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "all" + volumeMounts: + - name: "secrets" + mountPath: "/etc/butler/secrets" + - name: "secrets-raw" + mountPath: "/tmp/secrets-raw" + {{- end }} + containers: + - name: {{ .Chart.Name }} + envFrom: + - configMapRef: + name: "sia" + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: "http" + containerPort: 8080 + protocol: "TCP" + readinessProbe: + httpGet: + path: {{ .Values.config.pathPrefix }} + port: "http" + resources: + {{- toYaml .Values.resources | nindent 12 }} + env: + - name: "SIA_BUTLER_DATA_COLLECTIONS" + value: {{ .Values.config.butlerDataCollections | toJson | quote }} + {{- if .Values.config.slackAlerts }} + - name: "SIA_SLACK_WEBHOOK" + valueFrom: + secretKeyRef: + name: "sia" + key: "slack-webhook" + {{- end }} + {{- if .Values.config.directButlerEnabled }} + - name: "AWS_SHARED_CREDENTIALS_FILE" + value: "/tmp/secrets/aws-credentials.ini" + - name: "PGUSER" + value: {{ .Values.config.pgUser }} + - name: "PGPASSFILE" + value: "/etc/butler/secrets/postgres-credentials.txt" + - name: "GOOGLE_APPLICATION_CREDENTIALS" + value: "/tmp/secrets/butler-gcs-idf-creds.json" + {{- end }} + {{- if .Values.config.directButlerEnabled }} + volumeMounts: + - name: "secrets" + mountPath: "/etc/butler/secrets" + readOnly: true + {{- end }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "all" + readOnlyRootFilesystem: false + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.config.directButlerEnabled }} + volumes: + - name: "secrets-raw" + secret: + secretName: "sia" + - name: "secrets" + emptyDir: {} + {{- end }} + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 diff --git a/applications/sia/templates/ingress-anonymous.yaml b/applications/sia/templates/ingress-anonymous.yaml new file mode 100644 index 0000000000..c683c4e2d1 --- /dev/null +++ b/applications/sia/templates/ingress-anonymous.yaml @@ -0,0 +1,44 @@ +apiVersion: gafaelfawr.lsst.io/v1alpha1 +kind: GafaelfawrIngress +metadata: + name: {{ template "sia.fullname" . }}-anonymous + labels: + {{- include "sia.labels" . | nindent 4 }} +config: + baseUrl: {{ .Values.global.baseUrl | quote }} + scopes: + anonymous: true +template: + metadata: + name: {{ template "sia.fullname" . }}-anonymous + {{- with .Values.ingress.annotations }} + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + {{- toYaml . | nindent 6 }} + {{- end }} + spec: + rules: + - host: {{ .Values.global.host | quote }} + http: + paths: + - path: "{{ .Values.ingress.path }}/openapi.json" + pathType: "Exact" + backend: + service: + name: {{ template "sia.fullname" . }} + port: + number: 8080 + - path: "{{ .Values.ingress.path }}/.+/capabilities" + pathType: "ImplementationSpecific" + backend: + service: + name: {{ template "sia.fullname" . }} + port: + number: 8080 + - path: "{{ .Values.ingress.path }}/.+/availability" + pathType: "ImplementationSpecific" + backend: + service: + name: {{ template "sia.fullname" . }} + port: + number: 8080 diff --git a/applications/sia/templates/ingress.yaml b/applications/sia/templates/ingress.yaml new file mode 100644 index 0000000000..bb9638b596 --- /dev/null +++ b/applications/sia/templates/ingress.yaml @@ -0,0 +1,35 @@ +apiVersion: gafaelfawr.lsst.io/v1alpha1 +kind: GafaelfawrIngress +metadata: + name: {{ template "sia.fullname" . }} + labels: + {{- include "sia.labels" . | nindent 4 }} +config: + baseUrl: {{ .Values.global.baseUrl | quote }} + scopes: + all: + - "read:image" + delegate: + internal: + service: "sia" + scopes: + - "read:image" +template: + metadata: + name: {{ template "sia.fullname" . }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 6 }} + {{- end }} + spec: + rules: + - host: {{ required "global.host must be set" .Values.global.host | quote }} + http: + paths: + - path: {{ .Values.config.pathPrefix | quote }} + pathType: "Prefix" + backend: + service: + name: "sia" + port: + number: 8080 diff --git a/applications/sia/templates/networkpolicy.yaml b/applications/sia/templates/networkpolicy.yaml new file mode 100644 index 0000000000..4edbb84b29 --- /dev/null +++ b/applications/sia/templates/networkpolicy.yaml @@ -0,0 +1,21 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: "sia" +spec: + podSelector: + matchLabels: + {{- include "sia.selectorLabels" . | nindent 6 }} + policyTypes: + - "Ingress" + ingress: + # Allow inbound access from pods (in any namespace) labeled + # gafaelfawr.lsst.io/ingress: true. + - from: + - namespaceSelector: {} + podSelector: + matchLabels: + gafaelfawr.lsst.io/ingress: "true" + ports: + - protocol: "TCP" + port: 8080 diff --git a/applications/sia/templates/service.yaml b/applications/sia/templates/service.yaml new file mode 100644 index 0000000000..679e84dffa --- /dev/null +++ b/applications/sia/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: "sia" + labels: + {{- include "sia.labels" . | nindent 4 }} +spec: + type: "ClusterIP" + ports: + - port: 8080 + targetPort: "http" + protocol: "TCP" + name: "http" + selector: + {{- include "sia.selectorLabels" . | nindent 4 }} diff --git a/applications/sia/templates/vault-secrets.yaml b/applications/sia/templates/vault-secrets.yaml new file mode 100644 index 0000000000..3b1ebc978a --- /dev/null +++ b/applications/sia/templates/vault-secrets.yaml @@ -0,0 +1,9 @@ +apiVersion: ricoberger.de/v1alpha1 +kind: VaultSecret +metadata: + name: "sia" + labels: + {{- include "sia.labels" . | nindent 4 }} +spec: + path: "{{ .Values.global.vaultSecretsPath }}/sia" + type: Opaque diff --git a/applications/sia/values-idfdev.yaml b/applications/sia/values-idfdev.yaml new file mode 100644 index 0000000000..92ae953404 --- /dev/null +++ b/applications/sia/values-idfdev.yaml @@ -0,0 +1,11 @@ +config: + + # Data (Butler) Collections + butlerDataCollections: + - config: "https://raw.githubusercontent.com/lsst-dm/dax_obscore/refs/heads/main/configs/dp02.yaml" + label: "LSST.DP02" + name: "dp02" + butler_type: "REMOTE" + repository: "https://data-dev.lsst.cloud/api/butler/repo/dp02/butler.yaml" + datalink_url: "https://data-dev.lsst.cloud/api/datalink/links?ID=butler%3A//dp02/{id}" + default_instrument: "LSSTCam-imSim" diff --git a/applications/sia/values-idfint.yaml b/applications/sia/values-idfint.yaml new file mode 100644 index 0000000000..687799b059 --- /dev/null +++ b/applications/sia/values-idfint.yaml @@ -0,0 +1,11 @@ +config: + + # Data (Butler) Collections + butlerDataCollections: + - config: "https://raw.githubusercontent.com/lsst-dm/dax_obscore/refs/heads/main/configs/dp02.yaml" + label: "LSST.DP02" + name: "dp02" + butler_type: "REMOTE" + repository: "https://data-int.lsst.cloud/api/butler/repo/dp02/butler.yaml" + datalink_url: "https://data-int.lsst.cloud/api/datalink/links?ID=butler%3A//dp02/{id}" + default_instrument: "LSSTCam-imSim" diff --git a/applications/sia/values.yaml b/applications/sia/values.yaml new file mode 100644 index 0000000000..fe76b45dd4 --- /dev/null +++ b/applications/sia/values.yaml @@ -0,0 +1,87 @@ +# Default values for sia. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# -- Override the base name for resources +nameOverride: "" + +# -- Override the full name for resources (includes the release name) +fullnameOverride: "" + +# -- Number of web deployment pods to start +replicaCount: 1 + +image: + # -- Image to use in the sia deployment + repository: "ghcr.io/lsst-sqre/sia" + + # -- Pull policy for the sia image + pullPolicy: "IfNotPresent" + + # -- Tag of image to use + # @default -- The appVersion of the chart + tag: "" + +config: + # -- Whether to send alerts and status to Slack. + slackAlerts: false + + # -- Logging level + logLevel: "INFO" + + # -- Logging profile (`production` for JSON, `development` for + # human-friendly) + logProfile: "production" + + # -- URL path prefix + pathPrefix: "/api/sia" + + # -- Whether direct butler access is enabled + directButlerEnabled: false + + # -- List of data (Butler) Collections + # Expected attributes: `config`, `label`, `name`, `butler_type`, `repository`, `datalink_url` & `default_instrument` + butlerDataCollections: [] + + # -- User to use from the PGPASSFILE if sia is using a direct Butler + # connection + pgUser: "rubin" + + +ingress: + # -- Additional annotations for the ingress rule + annotations: {} + + # -- Path prefix where app is hosted + path: "/api/sia" + +# -- Affinity rules for the sia deployment pod +affinity: {} + +# -- Node selection rules for the sia deployment pod +nodeSelector: {} + +# -- Annotations for the sia deployment pod +podAnnotations: {} + +# -- Resource limits and requests for the sia deployment pod +# @default -- See `values.yaml` +resources: {} + +# -- Tolerations for the sia deployment pod +tolerations: [] + +# The following will be set by parameters injected by Argo CD and should not +# be set in the individual environment values files. +global: + # -- Base URL for the environment + # @default -- Set by Argo CD + baseUrl: null + + # -- Host name for ingress + # @default -- Set by Argo CD + host: null + + # -- Base path for Vault secrets + # @default -- Set by Argo CD + vaultSecretsPath: null diff --git a/docs/applications/rsp.rst b/docs/applications/rsp.rst index e866ff94af..172d953ae3 100644 --- a/docs/applications/rsp.rst +++ b/docs/applications/rsp.rst @@ -19,6 +19,7 @@ Argo CD project: ``rsp`` nublado/index portal/index semaphore/index + sia/index siav2/index squareone/index ssotap/index diff --git a/docs/applications/sia/index.rst b/docs/applications/sia/index.rst new file mode 100644 index 0000000000..ec4a177b15 --- /dev/null +++ b/docs/applications/sia/index.rst @@ -0,0 +1,28 @@ +.. px-app:: sia + +###################################### +sia — Simple Image Access (v2) service +###################################### + +``sia`` is an image-access API complying with the IVOA SIA (v2) specification. +This application is designed to interact with Butler repositories, through the dax_obscore package https://github.com/lsst-dm/dax_obscore and allows users to find image links for objects that match one or more filter criteria, listed in the IVOA SIA specification https://www.ivoa.net/documents/SIA/. + +Results of an SIAv2 query will be contain either a datalink if the images are stored behind an authenticated store, or a direct link to the images. + +The SIA service will have as client the RSP Portal Aspect but can also be accessed by other IVOA-compatible clients. + +If the SIA application does not appear under a VO Registry, use of it by IVOA-compatible clients will require users to input the SIA service URL manually. + +Both POST & GET methods are implemented for the /query API, as well as the VOSI-availability and VOSI-capabilities endpoints. + + +.. jinja:: sia + :file: applications/_summary.rst.jinja + +Guides +====== + +.. toctree:: + :maxdepth: 1 + + values diff --git a/docs/applications/sia/values.md b/docs/applications/sia/values.md new file mode 100644 index 0000000000..88ea24dbc4 --- /dev/null +++ b/docs/applications/sia/values.md @@ -0,0 +1,12 @@ +```{px-app-values} sia +``` + +# sia Helm values reference + +Helm values reference table for the {px-app}`sia` application. + +```{include} ../../../applications/sia/README.md +--- +start-after: "## Values" +--- +``` \ No newline at end of file diff --git a/environments/README.md b/environments/README.md index d44f6c7e2d..e92de4f65a 100644 --- a/environments/README.md +++ b/environments/README.md @@ -57,6 +57,7 @@ | applications.sasquatch-backpack | bool | `false` | Enable the sasquatch-backpack application | | applications.schedview-snapshot | bool | `false` | Enable the schedview-snapshot application | | applications.semaphore | bool | `false` | Enable the semaphore application | +| applications.sia | bool | `false` | Enable the sia over butler application | | applications.siav2 | bool | `false` | Enable the siav2 application | | applications.simonyitel | bool | `false` | Enable the simonyitel control system application | | applications.sqlproxy-cross-project | bool | `false` | Enable the sqlproxy-cross-project application | diff --git a/environments/templates/applications/rsp/sia.yaml b/environments/templates/applications/rsp/sia.yaml new file mode 100644 index 0000000000..ace746138e --- /dev/null +++ b/environments/templates/applications/rsp/sia.yaml @@ -0,0 +1,34 @@ +{{- if (index .Values "applications" "sia") -}} +apiVersion: v1 +kind: Namespace +metadata: + name: "sia" +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: "sia" + namespace: "argocd" + finalizers: + - "resources-finalizer.argocd.argoproj.io" +spec: + destination: + namespace: "sia" + server: "https://kubernetes.default.svc" + project: "rsp" + source: + path: "applications/sia" + repoURL: {{ .Values.repoUrl | quote }} + targetRevision: {{ .Values.targetRevision | quote }} + helm: + parameters: + - name: "global.host" + value: {{ .Values.fqdn | quote }} + - name: "global.baseUrl" + value: "https://{{ .Values.fqdn }}" + - name: "global.vaultSecretsPath" + value: {{ .Values.vaultPathPrefix | quote }} + valueFiles: + - "values.yaml" + - "values-{{ .Values.name }}.yaml" +{{- end -}} diff --git a/environments/values-idfdev.yaml b/environments/values-idfdev.yaml index 6283dddfbd..4f56c10fba 100644 --- a/environments/values-idfdev.yaml +++ b/environments/values-idfdev.yaml @@ -25,6 +25,7 @@ applications: portal: true sasquatch: true semaphore: true + sia: true siav2: false ssotap: true squareone: true diff --git a/environments/values-idfint.yaml b/environments/values-idfint.yaml index 64873171d9..804062f9b1 100644 --- a/environments/values-idfint.yaml +++ b/environments/values-idfint.yaml @@ -22,6 +22,7 @@ applications: nublado: true portal: true sasquatch: true + sia: true siav2: false ssotap: true production-tools: true diff --git a/environments/values-idfprod.yaml b/environments/values-idfprod.yaml index 0a6a26cc37..f0b77f439c 100644 --- a/environments/values-idfprod.yaml +++ b/environments/values-idfprod.yaml @@ -23,6 +23,7 @@ applications: nublado: true portal: true semaphore: true + sia: false siav2: false squareone: true ssotap: true diff --git a/environments/values.yaml b/environments/values.yaml index cd11a31959..b65965df03 100644 --- a/environments/values.yaml +++ b/environments/values.yaml @@ -171,6 +171,9 @@ applications: # -- Enable the schedview-snapshot application schedview-snapshot: false + # -- Enable the sia over butler application + sia: false + # -- Enable the siav2 application siav2: false diff --git a/tests/data/input/docs/applications/rsp.rst b/tests/data/input/docs/applications/rsp.rst index 234524c178..02c38e2fec 100644 --- a/tests/data/input/docs/applications/rsp.rst +++ b/tests/data/input/docs/applications/rsp.rst @@ -18,6 +18,7 @@ Argo CD project: ``rsp`` nublado/index portal/index semaphore/index + sia/index siav2/index sqlproxy-cross-project/index squareone/index diff --git a/tests/data/output/docs/rsp.rst b/tests/data/output/docs/rsp.rst index a1a7d3ea67..23462b5e28 100644 --- a/tests/data/output/docs/rsp.rst +++ b/tests/data/output/docs/rsp.rst @@ -19,6 +19,7 @@ Argo CD project: ``rsp`` nublado/index portal/index semaphore/index + sia/index siav2/index sqlproxy-cross-project/index squareone/index