diff --git a/.github/workflows/build-publish.yaml b/.github/workflows/build-publish.yaml index df982ef88..82815262d 100644 --- a/.github/workflows/build-publish.yaml +++ b/.github/workflows/build-publish.yaml @@ -109,6 +109,10 @@ jobs: - name: cert-tool directory: tools/cert-tool file: tools/cert-tool/Dockerfile + - name: snippet-service + directory: snippet-service + file: .tmp/docker/snippet-service/Dockerfile + template-file: tools/docker/Dockerfile.in uses: ./.github/workflows/build-publish-cfg.yaml with: name: ${{ matrix.name }} diff --git a/Makefile b/Makefile index 14ec3c7ee..d0b18fcb0 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ CERT_TOOL_SIGN_ALG ?= ECDSA-SHA256 CERT_TOOL_ELLIPTIC_CURVE ?= P256 CERT_TOOL_IMAGE = ghcr.io/plgd-dev/hub/cert-tool:vnext -SUBDIRS := bundle certificate-authority cloud2cloud-connector cloud2cloud-gateway coap-gateway grpc-gateway resource-aggregate resource-directory http-gateway identity-store test/oauth-server tools/cert-tool +SUBDIRS := bundle certificate-authority cloud2cloud-connector cloud2cloud-gateway coap-gateway grpc-gateway resource-aggregate resource-directory http-gateway identity-store snippet-service test/oauth-server tools/cert-tool .PHONY: $(SUBDIRS) push proto/generate clean build test env mongo nats certificates hub-build http-gateway-www simulators default: build diff --git a/bundle/Dockerfile b/bundle/Dockerfile index a31a14308..b925b6277 100644 --- a/bundle/Dockerfile +++ b/bundle/Dockerfile @@ -105,6 +105,13 @@ RUN go build \ -o "/go/bin/$tool" \ ./ +#snippet-service +ARG service=snippet-service +WORKDIR $root_directory/$service +RUN go build -ldflags "-linkmode external -extldflags -static -X github.com/plgd-dev/hub/v2/pkg/build.CommitDate=$COMMIT_DATE -X github.com/plgd-dev/hub/v2/pkg/build.CommitHash=$SHORT_COMMIT -X github.com/plgd-dev/hub/v2/pkg/build.BuildDate=$DATE -X github.com/plgd-dev/hub/v2/pkg/build.Version=$VERSION -X github.com/plgd-dev/hub/v2/pkg/build.ReleaseURL=$RELEASE_URL" \ + -o "/go/bin/$service" \ + ./cmd/service + #nats WORKDIR $root_directory RUN apkArch="$(apk --print-arch)"; \ @@ -168,6 +175,8 @@ COPY --from=build /go/bin/cloud2cloud-connector /usr/local/bin/cloud2cloud-conne COPY --from=build /go/src/github.com/plgd-dev/hub/cloud2cloud-connector/config.yaml /configs/cloud2cloud-connector.yaml COPY --from=build /go/src/github.com/plgd-dev/hub/bundle/run.sh /usr/local/bin/run.sh COPY --from=build /go/src/github.com/plgd-dev/hub/bundle/nginx /nginx +COPY --from=build /go/bin/snippet-service /usr/local/bin/snippet-service +COPY --from=build /go/src/github.com/plgd-dev/hub/snippet-service/config.yaml /configs/snippet-service.yaml # install scylla RUN curl -sSf get.scylladb.com/server | sudo bash -s -- --scylla-version 5.2 @@ -222,6 +231,8 @@ ENV NATS_PORT=10001 ENV SCYLLA_SMP=1 ENV SCYLLA_DEVELOPER_MODE=true ENV SCYLLA_PORT=29142 +ENV SNIPPET_SERVICE_PORT=9091 +ENV HTTP_SNIPPET_SERVICE_PORT=9092 # OAuth ENV DEVICE_PROVIDER=plgd diff --git a/bundle/nginx/nginx.conf.template b/bundle/nginx/nginx.conf.template index 8ad47eb2c..3a20465d5 100644 --- a/bundle/nginx/nginx.conf.template +++ b/bundle/nginx/nginx.conf.template @@ -107,6 +107,20 @@ http { proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; } + location ~ ^(/snippet-service) { + set $upstream_snippet_service https://127.0.0.1:REPLACE_HTTP_SNIPPET_SERVICE_PORT; + proxy_pass $upstream_snippet_service; + proxy_ssl_certificate /data/certs/internal/endpoint.crt; + proxy_ssl_certificate_key /data/certs/internal/endpoint.key; + proxy_ssl_trusted_certificate /data/certs/root_ca.crt; + proxy_ssl_verify on; + set $cors_headers 'Authority,Method,Path,Scheme,Accept,Accept-Encoding,Accept-Language,Content-Type,Origin,Refer,Sec-Fetch-Dest,Sec-Fetch-Mode,Sec-Fetch-Site,Authorization,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,correlation-id'; + include /nginx/cors.conf; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + } location ~ ^/ { set $upstream_http_gateway https://127.0.0.1:REPLACE_HTTP_GATEWAY_PORT; proxy_pass $upstream_http_gateway; diff --git a/bundle/run.sh b/bundle/run.sh index 112f400ca..a139e2b9a 100755 --- a/bundle/run.sh +++ b/bundle/run.sh @@ -15,6 +15,7 @@ export NGINX_PATH="/data/nginx" export JETSTREAM_PATH="/data/jetstream" export CERTIFICATE_AUTHORITY_ADDRESS="localhost:${CERTIFICATE_AUTHORITY_PORT}" +export CERTIFICATE_AUTHORITY_HTTP_ADDRESS="localhost:${HTTP_CERTIFICATE_AUTHORITY_PORT}" export MOCK_OAUTH_SERVER_ADDRESS="localhost:${MOCK_OAUTH_SERVER_PORT}" export RESOURCE_AGGREGATE_ADDRESS="localhost:${RESOURCE_AGGREGATE_PORT}" export RESOURCE_DIRECTORY_ADDRESS="localhost:${RESOURCE_DIRECTORY_PORT}" @@ -23,6 +24,8 @@ export GRPC_GATEWAY_ADDRESS="localhost:${GRPC_GATEWAY_PORT}" export HTTP_GATEWAY_ADDRESS="localhost:${HTTP_GATEWAY_PORT}" export CLOUD2CLOUD_GATEWAY_ADDRESS="localhost:${CLOUD2CLOUD_GATEWAY_PORT}" export CLOUD2CLOUD_CONNECTOR_ADDRESS="localhost:${CLOUD2CLOUD_CONNECTOR_PORT}" +export SNIPPET_SERVICE_ADDRESS="localhost:${SNIPPET_SERVICE_PORT}" +export SNIPPET_SERVICE_HTTP_ADDRESS="localhost:${HTTP_SNIPPET_SERVICE_PORT}" export INTERNAL_CERT_DIR_PATH="$CERTIFICATES_PATH/internal" export GRPC_INTERNAL_CERT_NAME="endpoint.crt" @@ -451,6 +454,7 @@ if [ "${OVERRIDE_FILES}" = "true" ] || [ ! -f "${NGINX_PATH}/nginx.conf" ]; then sed -i "s/REPLACE_CLOUD2CLOUD_GATEWAY_PORT/$CLOUD2CLOUD_GATEWAY_PORT/g" ${NGINX_PATH}/nginx.conf sed -i "s/REPLACE_CLOUD2CLOUD_CONNECTOR_PORT/$CLOUD2CLOUD_CONNECTOR_PORT/g" ${NGINX_PATH}/nginx.conf sed -i "s/REPLACE_HTTP_CERTIFICATE_AUTHORITY_PORT/$HTTP_CERTIFICATE_AUTHORITY_PORT/g" ${NGINX_PATH}/nginx.conf + sed -i "s/REPLACE_HTTP_SNIPPET_SERVICE_PORT/$HTTP_SNIPPET_SERVICE_PORT/g" ${NGINX_PATH}/nginx.conf fi # nats @@ -917,6 +921,7 @@ cat /configs/certificate-authority.yaml | yq e "\ .apis.grpc.authorization.http.tls.useSystemCAPool = true | .apis.grpc.authorization.authority = \"https://${OAUTH_ENDPOINT}\" | .apis.grpc.authorization.ownerClaim = \"${OWNER_CLAIM}\" | + .apis.http.address = \"${CERTIFICATE_AUTHORITY_HTTP_ADDRESS}\" | .clients.storage.use = \"${DATABASE_USE}\" | .clients.storage.mongoDB.uri = \"${MONGODB_URI}\" | .clients.storage.cqlDB.hosts = [ \"${SCYLLA_HOSTNAME}\" ] | @@ -1144,7 +1149,51 @@ while true; do sleep 1 done +# snippet-service +echo "starting snippet-service" +## configuration +if [ "${OVERRIDE_FILES}" = "true" ] || [ ! -f "/data/snippet-service.yaml" ]; then +cat /configs/snippet-service.yaml | yq e "\ + .hubID = \"${HUB_ID}\" | + .log.level = \"${LOG_LEVEL}\" | + .apis.grpc.address = \"${SNIPPET_SERVICE_ADDRESS}\" | + .apis.grpc.authorization.audience = \"${SERVICE_OAUTH_AUDIENCE}\" | + .apis.grpc.authorization.http.tls.useSystemCAPool = true | + .apis.grpc.authorization.authority = \"https://${OAUTH_ENDPOINT}\" | + .apis.grpc.authorization.ownerClaim = \"${OWNER_CLAIM}\" | + .apis.http.address = \"${SNIPPET_SERVICE_HTTP_ADDRESS}\" | + .clients.storage.use = \"${DATABASE_USE}\" | + .clients.storage.mongoDB.uri = \"${MONGODB_URI}\" | + .clients.storage.cqlDB.hosts = [ \"${SCYLLA_HOSTNAME}\" ] | + .clients.storage.cqlDB.port = ${SCYLLA_PORT} | + .clients.openTelemetryCollector.grpc.enabled = ${OPEN_TELEMETRY_EXPORTER_ENABLED} | + .clients.openTelemetryCollector.grpc.address = \"${OPEN_TELEMETRY_EXPORTER_ADDRESS}\" | + .clients.openTelemetryCollector.grpc.tls.caPool = \"${OPEN_TELEMETRY_EXPORTER_CA_POOL}\" | + .clients.openTelemetryCollector.grpc.tls.keyFile = \"${OPEN_TELEMETRY_EXPORTER_KEY_FILE}\" | + .clients.openTelemetryCollector.grpc.tls.certFile = \"${OPEN_TELEMETRY_EXPORTER_CERT_FILE}\" | + .clients.openTelemetryCollector.grpc.tls.useSystemCAPool = true +" - > /data/snippet-service.yaml +fi +snippet-service --config /data/snippet-service.yaml >$LOGS_PATH/snippet-service.log 2>&1 & +status=$? +snippet_service_pid=$! +if [ $status -ne 0 ]; then + echo "Failed to start snippet-service: $status" + sync + cat $LOGS_PATH/snippet-service.log + exit $status +fi +# waiting for ca. Without wait, sometimes the service didn't connect. +i=0 +while true; do + i=$((i+1)) + if openssl s_client -connect ${SNIPPET_SERVICE_ADDRESS} -cert ${INTERNAL_CERT_DIR_PATH}/${GRPC_INTERNAL_CERT_NAME} -key ${INTERNAL_CERT_DIR_PATH}/${GRPC_INTERNAL_CERT_KEY_NAME} <<< "Q" 2>/dev/null > /dev/null; then + break + fi + echo "Try to reconnect to snippet-service(${SNIPPET_SERVICE_ADDRESS}) $i" + sleep 1 +done echo "Open browser at https://${DOMAIN}" @@ -1263,4 +1312,11 @@ while sleep 10; do exit 1 fi fi + ps aux |grep $snippet_service_pid |grep -q -v grep + if [ $? -ne 0 ]; then + echo "snippet-service has already exited." + sync + cat $LOGS_PATH/snippet-service.log + exit 1 + fi done diff --git a/charts/plgd-hub/templates/snippet-service/_helpers.tpl b/charts/plgd-hub/templates/snippet-service/_helpers.tpl new file mode 100644 index 000000000..6bcb656b5 --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/_helpers.tpl @@ -0,0 +1,60 @@ +{{- define "plgd-hub.snippetservice.fullname" -}} +{{- if .Values.snippetservice.fullnameOverride }} +{{- .Values.snippetservice.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Values.snippetservice.name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s-%s" .Release.Name $name .Values.snippetservice.name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "plgd-hub.snippetservice.image" -}} + {{- $registryName := .Values.snippetservice.image.registry | default "" -}} + {{- $repositoryName := .Values.snippetservice.image.repository -}} + {{- $tag := .Values.snippetservice.image.tag | default .Chart.AppVersion | toString -}} + {{- printf "%s%s:%s" $registryName $repositoryName $tag -}} +{{- end -}} + +{{- define "plgd-hub.snippetservice.configName" -}} + {{- $fullName := include "plgd-hub.snippetservice.fullname" . -}} + {{- printf "%s-cfg" $fullName }} +{{- end -}} + +{{- define "plgd-hub.snippetservice.createServiceCertByCm" }} + {{- $serviceTls := .Values.snippetservice.apis.grpc.tls.certFile }} + {{- if $serviceTls }} + {{- printf "" -}} + {{- else }} + {{- printf "true" -}} + {{- end }} +{{- end }} + +{{- define "plgd-hub.snippetservice.domain" -}} + {{- if .Values.snippetservice.domain }} + {{- printf "%s" .Values.snippetservice.domain }} + {{- else }} + {{- printf "api.%s" .Values.global.domain }} + {{- end }} +{{- end }} + +{{- define "plgd-hub.snippetservice.serviceCertName" -}} + {{- $fullName := include "plgd-hub.snippetservice.fullname" . -}} + {{- printf "%s-crt" $fullName -}} +{{- end }} + +{{- define "plgd-hub.snippetservice.domainCertName" -}} + {{- if .Values.snippetservice.ingress.secretName }} + {{- printf "%s" .Values.snippetservice.ingress.secretName -}} + {{- else }} + {{- $fullName := include "plgd-hub.snippetservice.fullname" . -}} + {{- printf "%s-domain-crt" $fullName -}} + {{- end }} +{{- end }} + +{{- define "plgd-hub.snippetservice.selectorLabels" -}} +app.kubernetes.io/name: {{ .Values.snippetservice.name }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} \ No newline at end of file diff --git a/charts/plgd-hub/templates/snippet-service/config.yaml b/charts/plgd-hub/templates/snippet-service/config.yaml new file mode 100644 index 000000000..67ad643cc --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/config.yaml @@ -0,0 +1,96 @@ +{{- if .Values.snippetservice.enabled }} +{{- $cert := "/certs" }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "plgd-hub.snippetservice.configName" . }} + namespace: {{ .Release.Namespace }} +data: + {{ .Values.snippetservice.config.fileName }}: | + {{- with .Values.snippetservice }} + hubID: {{ required "snippetservice.hubId or global.hubId is required" ( .hubId | default $.Values.global.hubId) | quote }} + log: + level: {{ .log.level }} + dumpBody: {{ .log.dumpBody }} + encoding: {{ .log.encoding }} + stacktrace: + enabled: {{ .log.stacktrace.enabled }} + level: {{ .log.stacktrace.level }} + encoderConfig: + timeEncoder: {{ .log.encoderConfig.timeEncoder }} + apis: + grpc: + address: {{ .apis.grpc.address | default (printf "0.0.0.0:%v" .port) | quote }} + sendMsgSize: {{ int64 .apis.grpc.sendMsgSize | default 4194304 }} + recvMsgSize: {{ int64 .apis.grpc.recvMsgSize | default 4194304 }} + enforcementPolicy: + minTime: {{ .apis.grpc.enforcementPolicy.minTime }} + permitWithoutStream: {{ .apis.grpc.enforcementPolicy.permitWithoutStream }} + keepAlive: + # 0s - means infinity + maxConnectionIdle: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + # 0s - means infinity + maxConnectionAge: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + # 0s - means infinity + maxConnectionAgeGrace: {{ .apis.grpc.keepAlive.maxConnectionAgeGrace }} + time: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + timeout: {{ .apis.grpc.keepAlive.maxConnectionIdle }} + tls: + {{- $tls := .apis.grpc.tls }} + {{- include "plgd-hub.certificateConfig" (list $ $tls $cert ) | indent 8 }} + clientCertificateRequired: {{ .apis.grpc.tls.clientCertificateRequired }} + authorization: + {{- $authorization := .apis.grpc.authorization }} + {{- include "plgd-hub.authorizationConfig" (list $ $authorization "snippetservice" ) | indent 8 }} + http: + maxIdleConns: {{ .apis.grpc.authorization.http.maxIdleConns }} + maxConnsPerHost: {{ .apis.grpc.authorization.http.maxIdleConnsPerHost }} + maxIdleConnsPerHost: {{ .apis.grpc.authorization.http.maxIdleConnsPerHost }} + idleConnTimeout: {{ .apis.grpc.authorization.http.idleConnTimeout }} + timeout: {{ .apis.grpc.authorization.http.timeout }} + tls: + {{- $grpcTls := .apis.grpc.authorization.http.tls }} + {{- include "plgd-hub.authorizationCaCertificateConfig" (list $ $grpcTls $cert ) | indent 12 }} + useSystemCAPool: {{ .apis.grpc.authorization.http.tls.useSystemCAPool }} + http: + address: {{ .apis.http.address | default (printf "0.0.0.0:%v" .httpPort) | quote }} + readTimeout: {{ .apis.http.readTimeout }} + readHeaderTimeout: {{ .apis.http.readHeaderTimeout }} + writeTimeout: {{ .apis.http.writeTimeout }} + idleTimeout: {{ .apis.http.idleTimeout }} + clients: + storage: + use: {{ include "plgd-hub.useDatabase" (list $ . .clients.storage.use) | quote }} + mongoDB: + uri: {{ include "plgd-hub.mongoDBUri" (list $ .clients.storage.mongoDB.uri ) | quote }} + database: {{ .clients.storage.mongoDB.database }} + maxPoolSize: {{ .clients.storage.mongoDB.maxPoolSize }} + maxConnIdleTime: {{ .clients.storage.mongoDB.maxConnIdleTime }} + tls: + {{- $mongoDbTls := .clients.storage.mongoDB.tls }} + {{- include "plgd-hub.certificateConfig" (list $ $mongoDbTls $cert ) | indent 10 }} + useSystemCAPool: {{ .clients.storage.mongoDB.tls.useSystemCAPool }} + cqlDB: + hosts: + {{- include "plgd-hub.cqlDBHosts" (list $ .clients.storage.cqlDB.hosts ) | indent 8 }} + port: {{ .clients.storage.cqlDB.port | default 9142 }} + table: {{ .clients.storage.cqlDB.table | quote }} + numConnections: {{ .clients.storage.cqlDB.numConnections }} + connectTimeout: {{ .clients.storage.cqlDB.connectTimeout }} + useHostnameResolution: {{ .clients.storage.cqlDB.useHostnameResolution }} + reconnectionPolicy: + constant: + interval: {{ .clients.storage.cqlDB.reconnectionPolicy.constant.interval }} + maxRetries: {{ .clients.storage.cqlDB.reconnectionPolicy.constant.maxRetries }} + keyspace: + name: {{ .clients.storage.cqlDB.keyspace.name }} + create: {{ .clients.storage.cqlDB.keyspace.create }} + replication: + {{- toYaml .clients.storage.cqlDB.keyspace.replication | nindent 14 }} + tls: + {{- $cqlDbTls := .clients.storage.cqlDB.tls }} + {{- include "plgd-hub.certificateConfig" (list $ $cqlDbTls $cert ) | indent 10 }} + useSystemCAPool: {{ .clients.storage.cqlDB.tls.useSystemCAPool }} + {{- include "plgd-hub.openTelemetryExporterConfig" (list $ $cert ) | nindent 6 }} + {{- end }} +{{- end }} diff --git a/charts/plgd-hub/templates/snippet-service/deployment.yaml b/charts/plgd-hub/templates/snippet-service/deployment.yaml new file mode 100644 index 000000000..04b20317e --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/deployment.yaml @@ -0,0 +1,129 @@ +{{- if .Values.snippetservice.enabled }} +{{- $cert := "/certs" }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "plgd-hub.snippetservice.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} + {{- with .Values.snippetservice.deploymentLabels }} + {{- . | toYaml | nindent 4 }} + {{- end }} + {{- with .Values.snippetservice.deploymentAnnotations }} + annotations: + {{- . | toYaml | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.snippetservice.replicas }} + selector: + matchLabels: + {{- include "plgd-hub.snippetservice.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.snippetservice.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "plgd-hub.snippetservice.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.snippetservice.securityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.snippetservice.rbac.enabled }} + serviceAccountName: {{ .Values.snippetservice.rbac.serviceAccountName }} + {{- end }} + {{- with .Values.snippetservice.image.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + restartPolicy: {{ .Values.snippetservice.restartPolicy }} + {{- if .Values.snippetservice.initContainersTpl }} + initContainers: + {{- tpl .Values.snippetservice.initContainersTpl . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Values.snippetservice.name }} + {{- with .Values.snippetservice.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: {{ include "plgd-hub.snippetservice.image" . | quote }} + imagePullPolicy: {{ .Values.snippetservice.image.pullPolicy }} + {{- if .Values.snippetservice.command }} + command: + {{- range .Values.snippetservice.command }} + - {{ . | quote }} + {{- end }} + {{- end}} + args: + - "--config" + - {{ printf "%s/%s" .Values.snippetservice.config.mountPath .Values.snippetservice.config.fileName | quote }} + ports: + - name: grpc + containerPort: {{ .Values.snippetservice.port }} + protocol: TCP + - name: http + containerPort: {{ .Values.snippetservice.httpPort }} + protocol: TCP + {{- with .Values.snippetservice.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.snippetservice.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.snippetservice.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: {{ .Values.snippetservice.config.volume }} + mountPath: {{ .Values.snippetservice.config.mountPath }} + {{- if ( include "plgd-hub.snippetservice.createServiceCertByCm" . ) }} + - name: service-crt + mountPath: {{ $cert }} + {{- end }} + {{- if .Values.global.authorizationCAPool }} + - name: {{ .Values.extraAuthorizationCAPool.name }} + mountPath: {{ .Values.extraAuthorizationCAPool.mountPath }} + {{- end }} + {{- with .Values.snippetservice.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.snippetservice.extraContainers }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.snippetservice.extraContainers "context" $ ) | nindent 8 }} + {{- end }} + volumes: + - name: {{ .Values.resourcedirectory.config.volume }} + configMap: + name: {{ include "plgd-hub.snippetservice.configName" . }} + {{- if ( include "plgd-hub.snippetservice.createServiceCertByCm" . ) }} + - name: service-crt + secret: + secretName: {{ include "plgd-hub.snippetservice.serviceCertName" . }} + {{- end }} + {{- if .Values.global.authorizationCAPool }} + - name: {{ .Values.extraAuthorizationCAPool.name }} + secret: + secretName: {{ .Values.extraAuthorizationCAPool.name }} + {{- end }} + {{- with .Values.snippetservice.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.snippetservice.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.snippetservice.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.snippetservice.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/plgd-hub/templates/snippet-service/domain-crt.yaml b/charts/plgd-hub/templates/snippet-service/domain-crt.yaml new file mode 100644 index 000000000..c1d06ff62 --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/domain-crt.yaml @@ -0,0 +1,34 @@ +{{- $domainCrt := include "plgd-hub.snippetservice.domainCertName" . }} +{{- if and $domainCrt .Values.certmanager.enabled .Values.snippetservice.enabled (not $.Values.global.enableWildCartCert ) }} +{{- $serviceDns := include "plgd-hub.snippetservice.fullname" . }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ $domainCrt }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} + {{- with .Values.certmanager.external.labels }} + {{- . | toYaml | nindent 4 }} + {{- end }} + {{- with .Values.certmanager.external.annotations }} + annotations: + {{- . | toYaml | nindent 4 }} + {{- end }} +spec: + secretName: {{ $domainCrt }} + privateKey: + algorithm: {{ .Values.certmanager.external.cert.key.algorithm | default .Values.certmanager.default.cert.key.algorithm }} + size: {{ .Values.certmanager.external.cert.key.size | default .Values.certmanager.default.cert.key.size }} + usages: + - server auth + - client auth + dnsNames: + - {{ include "plgd-hub.snippetservice.domain" . | quote }} + duration: {{ .Values.certmanager.external.cert.duration | default .Values.certmanager.default.cert.duration }} + renewBefore: {{ .Values.certmanager.external.cert.renewBefore | default .Values.certmanager.default.cert.renewBefore }} + issuerRef: + name: {{ .Values.certmanager.external.issuer.name | default .Values.certmanager.default.issuer.name }} + kind: {{ .Values.certmanager.external.issuer.kind | default .Values.certmanager.default.issuer.kind }} + group: {{ .Values.certmanager.external.issuer.group | default .Values.certmanager.default.issuer.group }} +{{- end }} diff --git a/charts/plgd-hub/templates/snippet-service/grpc-ingress.yaml b/charts/plgd-hub/templates/snippet-service/grpc-ingress.yaml new file mode 100644 index 000000000..12ef47117 --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/grpc-ingress.yaml @@ -0,0 +1,40 @@ +{{- if and .Values.snippetservice.enabled .Values.snippetservice.ingress.grpc.enabled }} +{{- $fullname := include "plgd-hub.snippetservice.fullname" . }} +{{- $port := .Values.snippetservice.port }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullname }}-grpc + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} + annotations: + {{- if .Values.snippetservice.ingress.grpc.annotations }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.snippetservice.ingress.grpc.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.snippetservice.ingress.grpc.customAnnotations }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.snippetservice.ingress.grpc.customAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + tls: + - hosts: + - {{ include "plgd-hub.snippetservice.domain" . | quote }} + {{- if $.Values.global.enableWildCartCert }} + secretName: {{ include "plgd-hub.wildCardCertName" . | quote }} + {{- else }} + secretName: {{ include "plgd-hub.snippetservice.domainCertName" . | quote }} + {{- end }} + rules: + - host: {{ include "plgd-hub.snippetservice.domain" . | quote }} + http: + paths: + {{- range .Values.snippetservice.ingress.grpc.paths }} + - path: {{ . }} + pathType: Prefix + backend: + service: + name: {{ $fullname }}-grpc + port: + number: {{ $port }} + {{- end }} +{{- end }} diff --git a/charts/plgd-hub/templates/snippet-service/grpc-service.yaml b/charts/plgd-hub/templates/snippet-service/grpc-service.yaml new file mode 100644 index 000000000..e2d37681f --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/grpc-service.yaml @@ -0,0 +1,25 @@ +{{- if .Values.snippetservice.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "plgd-hub.snippetservice.fullname" . }}-grpc + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} + {{- with .Values.snippetservice.service.grpc.labels }} + {{- . | toYaml | nindent 4 }} + {{- end }} + {{- if .Values.snippetservice.service.grpc.annotations }} + annotations: + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.snippetservice.service.grpc.annotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.snippetservice.service.grpc.type | default "ClusterIP" }} + ports: + - port: {{ .Values.snippetservice.port }} + targetPort: {{ .Values.snippetservice.service.grpc.targetPort }} + protocol: {{ .Values.snippetservice.service.grpc.protocol }} + name: {{ .Values.snippetservice.service.grpc.name }} + selector: + {{- include "plgd-hub.snippetservice.selectorLabels" . | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/charts/plgd-hub/templates/snippet-service/http-ingress.yaml b/charts/plgd-hub/templates/snippet-service/http-ingress.yaml new file mode 100644 index 000000000..3ee3cd2b2 --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/http-ingress.yaml @@ -0,0 +1,40 @@ +{{- if and .Values.snippetservice.enabled .Values.snippetservice.ingress.http.enabled }} +{{- $fullname := include "plgd-hub.snippetservice.fullname" . }} +{{- $port := .Values.snippetservice.httpPort }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullname }}-http + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} + annotations: + {{- if .Values.snippetservice.ingress.http.annotations }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.snippetservice.ingress.http.annotations "context" $ ) | nindent 4 }} + {{- end }} + {{- if .Values.snippetservice.ingress.http.customAnnotations }} + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.snippetservice.ingress.http.customAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + tls: + - hosts: + - {{ include "plgd-hub.snippetservice.domain" . | quote }} + {{- if $.Values.global.enableWildCartCert }} + secretName: {{ include "plgd-hub.wildCardCertName" . | quote }} + {{- else }} + secretName: {{ include "plgd-hub.snippetservice.domainCertName" . | quote }} + {{- end }} + rules: + - host: {{ include "plgd-hub.snippetservice.domain" . | quote }} + http: + paths: + {{- range .Values.snippetservice.ingress.http.paths }} + - path: {{ . }} + pathType: Prefix + backend: + service: + name: {{ $fullname }}-http + port: + number: {{ $port }} + {{- end }} +{{- end }} diff --git a/charts/plgd-hub/templates/snippet-service/http-service.yaml b/charts/plgd-hub/templates/snippet-service/http-service.yaml new file mode 100644 index 000000000..1e6cf659c --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/http-service.yaml @@ -0,0 +1,25 @@ +{{- if .Values.snippetservice.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "plgd-hub.snippetservice.fullname" . }}-http + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} + {{- with .Values.snippetservice.service.http.labels }} + {{- . | toYaml | nindent 4 }} + {{- end }} + {{- if .Values.snippetservice.service.http.annotations }} + annotations: + {{- include "plgd-hub.tplvalues.render" ( dict "value" .Values.snippetservice.service.http.annotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.snippetservice.service.http.type | default "ClusterIP" }} + ports: + - port: {{ .Values.snippetservice.httpPort }} + targetPort: {{ .Values.snippetservice.service.http.targetPort }} + protocol: {{ .Values.snippetservice.service.http.protocol }} + name: {{ .Values.snippetservice.service.http.name }} + selector: + {{- include "plgd-hub.snippetservice.selectorLabels" . | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/charts/plgd-hub/templates/snippet-service/role.yaml b/charts/plgd-hub/templates/snippet-service/role.yaml new file mode 100644 index 000000000..0dc01b10c --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/role.yaml @@ -0,0 +1,3 @@ +{{- if and .Values.snippetservice.rbac.enabled .Values.snippetservice.rbac.roleBindingDefitionTpl }} +{{- tpl .Values.snippetservice.rbac.roleBindingDefitionTpl $ }} +{{- end }} diff --git a/charts/plgd-hub/templates/snippet-service/service-account.yaml b/charts/plgd-hub/templates/snippet-service/service-account.yaml new file mode 100644 index 000000000..b1a4a7081 --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/service-account.yaml @@ -0,0 +1,9 @@ +{{- if and .Values.snippetservice.rbac.enabled .Values.snippetservice.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Values.snippetservice.rbac.serviceAccountName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} +{{- end }} diff --git a/charts/plgd-hub/templates/snippet-service/service-crt.yaml b/charts/plgd-hub/templates/snippet-service/service-crt.yaml new file mode 100644 index 000000000..afce0d74b --- /dev/null +++ b/charts/plgd-hub/templates/snippet-service/service-crt.yaml @@ -0,0 +1,44 @@ +{{- $createServiceCert := include "plgd-hub.snippetservice.serviceCertName" . }} +{{- if and $createServiceCert .Values.certmanager.enabled .Values.snippetservice.enabled }} +{{- $serviceCertName := include "plgd-hub.snippetservice.serviceCertName" . }} +{{- $serviceDns := include "plgd-hub.snippetservice.fullname" . }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ $serviceCertName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "plgd-hub.labels" . | nindent 4 }} + {{- with .Values.certmanager.internal.labels }} + {{- . | toYaml | nindent 4 }} + {{- end }} + {{- with .Values.certmanager.internal.annotations }} + annotations: + {{- . | toYaml | nindent 4 }} + {{- end }} +spec: + secretName: {{ $serviceCertName }} + privateKey: + algorithm: {{ .Values.certmanager.internal.cert.key.algorithm | default .Values.certmanager.default.cert.key.algorithm }} + size: {{ .Values.certmanager.internal.cert.key.size | default .Values.certmanager.default.cert.key.size }} + usages: + - server auth + - client auth + dnsNames: + - {{ printf "%s-http.%s.svc.%s" $serviceDns .Release.Namespace .Values.cluster.dns | quote }} + - {{ printf "%s-grpc.%s.svc.%s" $serviceDns .Release.Namespace .Values.cluster.dns | quote }} + - {{ printf "%s-http" $serviceDns | quote }} + - {{ printf "%s-grpc" $serviceDns | quote }} + {{- if .Values.snippetservice.service.grpc.crt.extraDnsNames }} + {{- toYaml .Values.snippetservice.service.grpc.crt.extraDnsNames | nindent 4}} + {{- end }} + {{- if .Values.snippetservice.service.http.crt.extraDnsNames }} + {{- toYaml .Values.snippetservice.service.http.crt.extraDnsNames | nindent 4}} + {{- end }} + duration: {{ .Values.certmanager.internal.cert.duration | default .Values.certmanager.default.cert.duration }} + renewBefore: {{ .Values.certmanager.internal.cert.renewBefore | default .Values.certmanager.default.cert.renewBefore }} + issuerRef: + name: {{ .Values.certmanager.internal.issuer.name | default .Values.certmanager.default.issuer.name }} + kind: {{ .Values.certmanager.internal.issuer.kind | default .Values.certmanager.default.issuer.kind }} + group: {{ .Values.certmanager.internal.issuer.group | default .Values.certmanager.default.issuer.group }} +{{- end }} diff --git a/charts/plgd-hub/values.yaml b/charts/plgd-hub/values.yaml index 811daff00..83b31e12b 100644 --- a/charts/plgd-hub/values.yaml +++ b/charts/plgd-hub/values.yaml @@ -2042,6 +2042,257 @@ certificateauthority: validFrom: "now-1h" expiresIn: "87600h" +snippetservice: + # -- Enable certificate-authority service + enabled: true + # -- Name of component. Used in label selectors + name: snippet-service + # -- Full name to override + fullnameOverride: + # -- Number of replicas + replicas: 1 + # -- Additional labels for certificate-authority deployment + deploymentLabels: {} + # -- Additional annotations for certificate-authority deployment + deploymentAnnotations: {} + # -- Pod security context + podSecurityContext: {} + # -- Labels for certificate-authority pod + podLabels: {} + # -- Annotations for certificate-authority pod + podAnnotations: {} + service: + grpc: + # -- Service type + type: ClusterIP + # -- Labels for certificate-authority service + labels: {} + # -- Annotations for certificate-authority service + annotations: {} + # -- Target port + targetPort: grpc + # -- Protocol + protocol: TCP + # -- Name + name: grpc + crt: + # -- Extra DNS names for service certificate + extraDnsNames: [] + http: + # -- Service type + type: ClusterIP + # -- Labels for snippet service + labels: {} + # -- Annotations for snippet service + annotations: {} + # -- Target port + targetPort: http + # -- Protocol + protocol: TCP + # -- Name + name: http + crt: + # -- Extra DNS names for service certificate + extraDnsNames: [] + # -- RBAC configuration + rbac: + # -- Enable RBAC + enabled: false + # -- Name of snippet service SA + serviceAccountName: snippet-service + # -- Template definition for Role/binding etc.. + roleBindingDefitionTpl: + # -- Security context for pod + securityContext: + # -- Image pull secrets + imagePullSecrets: + # -- Restart policy for pod + restartPolicy: Always + # -- Init containers definition + initContainersTpl: + # -- Extra POD containers + extraContainers: {} + image: + # -- Image registry + registry: ghcr.io/ + # -- Image repository + repository: plgd-dev/hub/snippet-service + # -- Image tag. + tag: + # -- Image pull policy + pullPolicy: Always + # -- Image pull secrets + imagePullSecrets: + # -- Liveness probe. snippet-service doesn't have any default liveness probe + livenessProbe: + # -- Readiness probe. snippet-service doesn't have aby default readiness probe + readinessProbe: + # -- Resources limit + resources: + # -- Node selector + nodeSelector: + # -- Affinity definition + affinity: + # -- Toleration definition + tolerations: + # -- Optional extra volumes + extraVolumes: + # -- Optional extra volume mounts + extraVolumeMounts: + # -- External domain for snippet-service. Default: api.{{ global.domain }} + domain: + ingress: + http: + # -- Enable ingress + enabled: true + # -- Override name of host/tls secret. If not specified, it will be generated + secretName: + # -- Pre defined map of Ingress annotation + annotations: + nginx.org/grpc-services: "{{ include \"plgd-hub.snippetservice.fullname\" . }}-http" + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + cert-manager.io/private-key-rotation-policy: always + # -- Custom map of Ingress annotation + customAnnotations: {} + # -- Ingress path + paths: + - /snippet-service + grpc: + # -- Enable ingress + enabled: true + # -- Override name of host/tls secret. If not specified, it will be generated + secretName: + # -- Pre defined map of Ingress annotation + annotations: + nginx.org/grpc-services: "{{ include \"plgd-hub.snippetservice.fullname\" . }}-grpc" + nginx.ingress.kubernetes.io/backend-protocol: "GRPCS" + ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" + cert-manager.io/private-key-rotation-policy: always + # -- Custom map of Ingress annotation + customAnnotations: { } + # -- Paths + paths: + - /snippetservice.pb.SnippetService + # -- Service configuration + config: + # -- File name for config file + fileName: service.yaml + # -- Config file volume name + volume: config + # -- Mount path + mountPath: /config + # -- Service and POD port + port: 9100 + httpPort: 9101 + # -- Hub ID. Overrides the global.hubId + hubId: + # -- Log section + log: + # -- Logging enabled from level + level: info + # -- Dump grpc messages + dumpBody: false + # -- The supported values are: "json", "console" + encoding: json + stacktrace: + # -- Log stacktrace + enabled: false + # -- Stacktrace from level + level: warn + encoderConfig: + # -- Time format for logs. The supported values are: "rfc3339nano", "rfc3339" + timeEncoder: rfc3339nano + # -- For complete certificate-authority service configuration see [plgd/certificate-authority](https://github.com/plgd-dev/hub/tree/main/certificate-authority) + apis: + grpc: + address: + sendMsgSize: 4194304 + recvMsgSize: 4194304 + enforcementPolicy: + minTime: 5s + permitWithoutStream: true + keepAlive: + # 0s - means infinity + maxConnectionIdle: 0s + # 0s - means infinity + maxConnectionAge: 0s + # 0s - means infinity + maxConnectionAgeGrace: 0s + time: 2h + timeout: 20s + tls: + caPool: + keyFile: + certFile: + clientCertificateRequired: false + authorization: + ownerClaim: + authority: + audience: + http: + maxIdleConns: 16 + maxConnsPerHost: 32 + maxIdleConnsPerHost: 16 + idleConnTimeout: "30s" + timeout: "10s" + tls: + caPool: + keyFile: + certFile: + useSystemCAPool: true + http: + address: + readTimeout: 8s + readHeaderTimeout: 4s + writeTimeout: 16s + idleTimeout: 30s + clients: + storage: + use: mongoDB + mongoDB: + uri: + database: snippetService + maxPoolSize: 16 + maxConnIdleTime: 4m0s + tls: + caPool: + keyFile: + certFile: + useSystemCAPool: false + bulkWrite: + # -- A time limit for write bulk to mongodb. A Timeout of zero means no timeout. + timeout: 1m0s + # -- The amount of time to wait until a record is written to mongodb. Any records collected during the throttle time will also be written. A throttle time of zero writes immediately. If recordLimit is reached, all records are written immediately + throttleTime: 500ms + # -- The maximum number of documents to cache before an immediate write. + documentLimit: 1000 + cqlDB: + table: snippetServiceRecords + hosts: [] + port: 9142 + numConnections: 16 + connectTimeout: 10s + # -- Resolve IP address to hostname before validate certificate. If false, the TLS validator will use ip/hostname advertised by the Cassandra node. + useHostnameResolution: true + reconnectionPolicy: + constant: + interval: 3s + # 0 - means infinity + maxRetries: 3 + keyspace: + name: plgdhub + create: true + replication: + class: SimpleStrategy + replication_factor: 1 + tls: + caPool: + keyFile: + certFile: + useSystemCAPool: false mockoauthserver: oauth: diff --git a/go.mod b/go.mod index 63c65499a..d54c33d0e 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/panjf2000/ants/v2 v2.9.1 github.com/pion/dtls/v2 v2.2.8-0.20240501061905-2c36d63320a0 github.com/pion/logging v0.2.2 - github.com/plgd-dev/device/v2 v2.5.1-0.20240513064831-b553d1a87e1c + github.com/plgd-dev/device/v2 v2.5.2-0.20240528123838-d8ca4ace7baf github.com/plgd-dev/go-coap/v3 v3.3.4 github.com/plgd-dev/kit/v2 v2.0.0-20211006190727-057b33161b90 github.com/pseudomuto/protoc-gen-doc v1.5.1 @@ -40,7 +40,7 @@ require ( github.com/tidwall/sjson v1.2.5 github.com/ugorji/go/codec v1.2.12 github.com/vincent-petithory/dataurl v1.0.0 - github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510130416-741fef736e1e + github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240513190706-79b5f39190eb go.mongodb.org/mongo-driver v1.15.0 go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.49.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 diff --git a/go.sum b/go.sum index 01aafa37e..b5be7de7a 100644 --- a/go.sum +++ b/go.sum @@ -226,8 +226,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= -github.com/plgd-dev/device/v2 v2.5.1-0.20240513064831-b553d1a87e1c h1:kNF2KvyCzA8IMERdHUrL/LMdsuZM/tXGjLVzEX2lcg4= -github.com/plgd-dev/device/v2 v2.5.1-0.20240513064831-b553d1a87e1c/go.mod h1:2mFPs55x2Li76zkrHdRNY3yOqVWSh59hiUw+6FYXA0k= +github.com/plgd-dev/device/v2 v2.5.2-0.20240528123838-d8ca4ace7baf h1:fAmllEckbJW4kpOIUod9kL42X/UdANyzyMukIVL+jRA= +github.com/plgd-dev/device/v2 v2.5.2-0.20240528123838-d8ca4ace7baf/go.mod h1:uplTm15S+2OBsGBpPdJ+YPUcnRnHdUbbRO87SHnnQA0= github.com/plgd-dev/go-coap/v2 v2.0.4-0.20200819112225-8eb712b901bc/go.mod h1:+tCi9Q78H/orWRtpVWyBgrr4vKFo2zYtbbxUllerBp4= github.com/plgd-dev/go-coap/v2 v2.4.1-0.20210517130748-95c37ac8e1fa/go.mod h1:rA7fc7ar+B/qa+Q0hRqv7yj/EMtIlmo1l7vkQGSrHPU= github.com/plgd-dev/go-coap/v3 v3.3.4 h1:clDLFOXXmXfhZqB0eSk6WJs2iYfjC2J22Ixwu5MHiO0= @@ -288,8 +288,8 @@ github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFX github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= -github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510130416-741fef736e1e h1:blQyU8WqqyRcBmaAPLiU5cTg9BSQu04CJZ/ffEzgI1s= -github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240510130416-741fef736e1e/go.mod h1:L/jWuWf+v7rmuFykpUP/runRXTnnA0QdGGgou8vzPrw= +github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240513190706-79b5f39190eb h1:6qIx0S8XOgCXkIMWew2dET+0Fcw2qTYZsDJ9LT8xQXg= +github.com/web-of-things-open-source/thingdescription-go v0.0.0-20240513190706-79b5f39190eb/go.mod h1:L/jWuWf+v7rmuFykpUP/runRXTnnA0QdGGgou8vzPrw= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= diff --git a/snippet-service/Makefile b/snippet-service/Makefile new file mode 100644 index 000000000..25a1a528d --- /dev/null +++ b/snippet-service/Makefile @@ -0,0 +1,64 @@ +SHELL = /bin/bash +SERVICE_NAME = $(notdir $(CURDIR)) +LATEST_TAG ?= vnext +BRANCH_TAG ?= $(shell git rev-parse --abbrev-ref HEAD | sed 's/[^a-zA-Z0-9]/-/g') +ifneq ($(BRANCH_TAG),main) + LATEST_TAG = $(BRANCH_TAG) +endif +VERSION_TAG ?= $(LATEST_TAG)-$(shell git rev-parse --short=7 --verify HEAD) +GOPATH ?= $(shell go env GOPATH) +WORKING_DIRECTORY := $(shell pwd) +REPOSITORY_DIRECTORY := $(shell cd .. && pwd) +BUILD_COMMIT_DATE ?= $(shell date -u +%FT%TZ --date=@`git show --format='%ct' HEAD --quiet`) +BUILD_SHORT_COMMIT ?= $(shell git show --format=%h HEAD --quiet) +BUILD_DATE ?= $(shell date -u +%FT%TZ) +BUILD_VERSION ?= $(shell git tag --sort version:refname | tail -1 | sed -e "s/^v//") + +default: build + +define build-docker-image + cd .. && \ + mkdir -p .tmp/docker/$(SERVICE_NAME) && \ + awk '{gsub("@NAME@","$(SERVICE_NAME)")} {gsub("@DIRECTORY@","$(SERVICE_NAME)")} {print}' tools/docker/Dockerfile.in > .tmp/docker/$(SERVICE_NAME)/Dockerfile && \ + docker build \ + --network=host \ + --tag ghcr.io/plgd-dev/hub/$(SERVICE_NAME):$(VERSION_TAG) \ + --tag ghcr.io/plgd-dev/hub/$(SERVICE_NAME):$(LATEST_TAG) \ + --tag ghcr.io/plgd-dev/hub/$(SERVICE_NAME):$(BRANCH_TAG) \ + --build-arg COMMIT_DATE="$(BUILD_COMMIT_DATE)" \ + --build-arg SHORT_COMMIT="$(BUILD_SHORT_COMMIT)" \ + --build-arg DATE="$(BUILD_DATE)" \ + --build-arg VERSION="$(BUILD_VERSION)" \ + --target $(1) \ + -f .tmp/docker/$(SERVICE_NAME)/Dockerfile \ + . +endef + +build-servicecontainer: + $(call build-docker-image,service) + +build: build-servicecontainer + +push: build-servicecontainer + docker push plgd/$(SERVICE_NAME):$(VERSION_TAG) + docker push plgd/$(SERVICE_NAME):$(LATEST_TAG) + +GOOGLEAPIS_PATH := $(REPOSITORY_DIRECTORY)/dependency/googleapis +GRPCGATEWAY_MODULE_PATH := $(shell go list -m -f '{{.Dir}}' github.com/grpc-ecosystem/grpc-gateway/v2 | head -1) + +proto/generate: + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --go_out=$(GOPATH)/src $(WORKING_DIRECTORY)/pb/service.proto + protoc-go-inject-tag -remove_tag_comment -input=$(WORKING_DIRECTORY)/pb/service.pb.go + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --openapiv2_out=$(REPOSITORY_DIRECTORY) \ + --openapiv2_opt logtostderr=true \ + $(WORKING_DIRECTORY)/pb/service.proto + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --grpc-gateway_out=$(REPOSITORY_DIRECTORY) \ + --grpc-gateway_opt logtostderr=true \ + --grpc-gateway_opt paths=source_relative \ + $(WORKING_DIRECTORY)/pb/service.proto + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --go-grpc_out=$(GOPATH)/src \ + $(WORKING_DIRECTORY)/pb/service.proto + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --doc_out=$(WORKING_DIRECTORY)/pb --doc_opt=markdown,README.md $(WORKING_DIRECTORY)/pb/*.proto + protoc -I=. -I=$(REPOSITORY_DIRECTORY) -I=$(GOPATH)/src -I=$(GOOGLEAPIS_PATH) -I=$(GRPCGATEWAY_MODULE_PATH) --doc_out=$(WORKING_DIRECTORY)/pb --doc_opt=html,doc.html $(WORKING_DIRECTORY)/pb/*.proto + +.PHONY: build-servicecontainer build push proto/generate diff --git a/snippet-service/cmd/service/main.go b/snippet-service/cmd/service/main.go new file mode 100644 index 000000000..ce496526c --- /dev/null +++ b/snippet-service/cmd/service/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + "fmt" + + "github.com/plgd-dev/hub/v2/pkg/build" + "github.com/plgd-dev/hub/v2/pkg/config" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + "github.com/plgd-dev/hub/v2/snippet-service/service" +) + +func run(cfg service.Config, logger log.Logger) error { + fileWatcher, err := fsnotify.NewWatcher(logger) + if err != nil { + return fmt.Errorf("cannot create file fileWatcher: %w", err) + } + defer func() { + _ = fileWatcher.Close() + }() + + s, err := service.New(context.Background(), cfg, fileWatcher, logger) + if err != nil { + return fmt.Errorf("cannot create service: %w", err) + } + err = s.Serve() + if err != nil { + return fmt.Errorf("cannot serve service: %w", err) + } + + return nil +} + +func main() { + var cfg service.Config + if err := config.LoadAndValidateConfig(&cfg); err != nil { + log.Fatalf("cannot load config: %v", err) + } + logger := log.NewLogger(cfg.Log) + log.Set(logger) + logger.Debugf("version: %v, buildDate: %v, buildRevision %v", build.Version, build.BuildDate, build.CommitHash) + log.Infof("config: %v", cfg.String()) + + if err := run(cfg, logger); err != nil { + log.Fatalf("cannot run service: %v", err) + } +} diff --git a/snippet-service/config.yaml b/snippet-service/config.yaml new file mode 100644 index 000000000..b9d27fd01 --- /dev/null +++ b/snippet-service/config.yaml @@ -0,0 +1,106 @@ +hubID: "" +log: + level: info + encoding: json + stacktrace: + enabled: false + level: warn + encoderConfig: + timeEncoder: rfc3339nano +apis: + grpc: + address: "0.0.0.0:9100" + sendMsgSize: 4194304 + recvMsgSize: 4194304 + enforcementPolicy: + minTime: 5s + permitWithoutStream: true + keepAlive: + # 0s - means infinity + maxConnectionIdle: 0s + # 0s - means infinity + maxConnectionAge: 0s + # 0s - means infinity + maxConnectionAgeGrace: 0s + time: 2h + timeout: 20s + tls: + caPool: "/secrets/public/rootca.crt" + keyFile: "/secrets/private/cert.key" + certFile: "/secrets/private/cert.crt" + clientCertificateRequired: true + authorization: + ownerClaim: "sub" + authority: "" + audience: "" + http: + maxIdleConns: 16 + maxConnsPerHost: 32 + maxIdleConnsPerHost: 16 + idleConnTimeout: "30s" + timeout: "10s" + tls: + caPool: "/secrets/public/rootca.crt" + keyFile: "/secrets/private/cert.key" + certFile: "/secrets/public/cert.crt" + useSystemCAPool: false + http: + address: "0.0.0.0:9101" + readTimeout: 8s + readHeaderTimeout: 4s + writeTimeout: 16s + idleTimeout: 30s +clients: + storage: + use: mongoDB + mongoDB: + uri: + database: snippetService + maxPoolSize: 16 + maxConnIdleTime: 4m0s + tls: + caPool: "/secrets/public/rootca.crt" + keyFile: "/secrets/private/cert.key" + certFile: "/secrets/public/cert.crt" + useSystemCAPool: false + bulkWrite: + timeout: 1m0s + throttleTime: 500ms + documentLimit: 1000 + cqlDB: + table: "snippets" + hosts: [] + port: 9142 + numConnections: 16 + connectTimeout: 10s + useHostnameResolution: true + reconnectionPolicy: + constant: + interval: 3s + maxRetries: 3 + keyspace: + name: plgdhub + create: true + replication: + class: SimpleStrategy + replication_factor: 1 + tls: + caPool: "/secrets/public/rootca.crt" + keyFile: "/secrets/private/cert.key" + certFile: "/secrets/public/cert.crt" + useSystemCAPool: false + openTelemetryCollector: + grpc: + enabled: false + address: "" + sendMsgSize: 4194304 + recvMsgSize: 4194304 + keepAlive: + time: 10s + timeout: 20s + permitWithoutStream: true + tls: + caPool: "/secrets/public/rootca.crt" + keyFile: "/secrets/private/cert.key" + certFile: "/secrets/public/cert.crt" + useSystemCAPool: false diff --git a/snippet-service/pb/README.md b/snippet-service/pb/README.md new file mode 100644 index 000000000..e021a4c5b --- /dev/null +++ b/snippet-service/pb/README.md @@ -0,0 +1,416 @@ +# Protocol Documentation + + +## Table of Contents + +- [snippet-service/pb/service.proto](#snippet-service_pb_service-proto) + - [AppliedDeviceConfiguration](#snippetservice-pb-AppliedDeviceConfiguration) + - [AppliedDeviceConfiguration.RelationTo](#snippetservice-pb-AppliedDeviceConfiguration-RelationTo) + - [AppliedDeviceConfiguration.Resource](#snippetservice-pb-AppliedDeviceConfiguration-Resource) + - [Condition](#snippetservice-pb-Condition) + - [Configuration](#snippetservice-pb-Configuration) + - [Configuration.Resource](#snippetservice-pb-Configuration-Resource) + - [DeleteAppliedDeviceConfigurationsRequest](#snippetservice-pb-DeleteAppliedDeviceConfigurationsRequest) + - [DeleteAppliedDeviceConfigurationsResponse](#snippetservice-pb-DeleteAppliedDeviceConfigurationsResponse) + - [DeleteConditionsRequest](#snippetservice-pb-DeleteConditionsRequest) + - [DeleteConditionsResponse](#snippetservice-pb-DeleteConditionsResponse) + - [DeleteConfigurationsRequest](#snippetservice-pb-DeleteConfigurationsRequest) + - [DeleteConfigurationsResponse](#snippetservice-pb-DeleteConfigurationsResponse) + - [GetAppliedDeviceConfigurationsRequest](#snippetservice-pb-GetAppliedDeviceConfigurationsRequest) + - [GetConditionsRequest](#snippetservice-pb-GetConditionsRequest) + - [GetConfigurationsRequest](#snippetservice-pb-GetConfigurationsRequest) + - [IDFilter](#snippetservice-pb-IDFilter) + - [InvokeConfigurationRequest](#snippetservice-pb-InvokeConfigurationRequest) + + - [AppliedDeviceConfiguration.Resource.Status](#snippetservice-pb-AppliedDeviceConfiguration-Resource-Status) + + - [SnippetService](#snippetservice-pb-SnippetService) + +- [Scalar Value Types](#scalar-value-types) + + + + +

Top

+ +## snippet-service/pb/service.proto + + + + + +### AppliedDeviceConfiguration +TODO naming + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id | [string](#string) | | | +| device_id | [string](#string) | | | +| configuration_id | [AppliedDeviceConfiguration.RelationTo](#snippetservice-pb-AppliedDeviceConfiguration-RelationTo) | | | +| on_demand | [bool](#bool) | | | +| condition_id | [AppliedDeviceConfiguration.RelationTo](#snippetservice-pb-AppliedDeviceConfiguration-RelationTo) | | TODO Naming | +| resources | [AppliedDeviceConfiguration.Resource](#snippetservice-pb-AppliedDeviceConfiguration-Resource) | repeated | TODO naming | +| owner | [string](#string) | | | +| timestamp | [int64](#int64) | | Unix timestamp in ns when the applied device configuration has been created/updated + +@gotags: bson:"timestamp" | + + + + + + + + +### AppliedDeviceConfiguration.RelationTo +TODO naming + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id | [string](#string) | | | +| version | [uint64](#uint64) | | | + + + + + + + + +### AppliedDeviceConfiguration.Resource + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| resource_id | [resourceaggregate.pb.ResourceId](#resourceaggregate-pb-ResourceId) | | TODO Jozo href only? | +| correlation_id | [string](#string) | | Reused from invoke command or generated. Can be used to retrieve corresponding pending command. | +| status | [AppliedDeviceConfiguration.Resource.Status](#snippetservice-pb-AppliedDeviceConfiguration-Resource-Status) | | | +| resource_updated | [resourceaggregate.pb.ResourceUpdated](#resourceaggregate-pb-ResourceUpdated) | | | + + + + + + + + +### Condition +driven by resource change event + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id | [string](#string) | | Condition ID + +@gotags: bson:"id" | +| version | [uint64](#uint64) | | @gotags: bson:"version" | +| name | [string](#string) | | @gotags: bson:"name,omitempty" | +| enabled | [bool](#bool) | | @gotags: bson:"enabled,omitempty" | +| configuration_id | [string](#string) | | ID of the configuration to be applied when the condition is satisfied + +@gotags: bson:"configurationId" | +| device_id_filter | [string](#string) | repeated | list of device IDs to which the condition applies + +@gotags: bson:"deviceIdFilter,omitempty" | +| resource_type_filter | [string](#string) | repeated | @gotags: bson:"resourceTypeFilter,omitempty" | +| resource_href_filter | [string](#string) | repeated | list of resource hrefs to which the condition applies + +@gotags: bson:"resourceHrefFilter,omitempty" | +| jq_expression_filter | [string](#string) | | @gotags: bson:"jqExpressionFilter,omitempty" | +| api_access_token | [string](#string) | | Token used to update resources in the configuration + +@gotags: bson:"apiAccessToken,omitempty" | +| owner | [string](#string) | | Condition owner + +@gotags: bson:"owner" | +| timestamp | [int64](#int64) | | Unix timestamp in ns when the condition has been created/updated + +@gotags: bson:"timestamp" | + + + + + + + + +### Configuration + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id | [string](#string) | | Configuration ID | +| version | [uint64](#uint64) | | Configuration version | +| name | [string](#string) | | User-friendly configuration name + +@gotags: bson:"name,omitempty" | +| resources | [Configuration.Resource](#snippetservice-pb-Configuration-Resource) | repeated | List of resource updates to be applied | +| owner | [string](#string) | | Configuration owner | +| timestamp | [int64](#int64) | | Unix timestamp in ns when the configuration has been created/updated + +@gotags: bson:"timestamp" | + + + + + + + + +### Configuration.Resource + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| href | [string](#string) | | href of the resource | +| content | [resourceaggregate.pb.Content](#resourceaggregate-pb-Content) | | content update of the resource | +| time_to_live | [int64](#int64) | | optional update command time to live, 0 is infinite | + + + + + + + + +### DeleteAppliedDeviceConfigurationsRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id_filter | [string](#string) | repeated | | + + + + + + + + +### DeleteAppliedDeviceConfigurationsResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| count | [int64](#int64) | | | + + + + + + + + +### DeleteConditionsRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id_filter | [IDFilter](#snippetservice-pb-IDFilter) | repeated | | + + + + + + + + +### DeleteConditionsResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| count | [int64](#int64) | | | + + + + + + + + +### DeleteConfigurationsRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id_filter | [IDFilter](#snippetservice-pb-IDFilter) | repeated | | + + + + + + + + +### DeleteConfigurationsResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| count | [int64](#int64) | | | + + + + + + + + +### GetAppliedDeviceConfigurationsRequest +TODO Naming + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id_filter | [string](#string) | repeated | | +| configuration_id_filter | [IDFilter](#snippetservice-pb-IDFilter) | repeated | | +| device_id_filter | [string](#string) | repeated | | +| condition_id_filter | [IDFilter](#snippetservice-pb-IDFilter) | repeated | | + + + + + + + + +### GetConditionsRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id_filter | [IDFilter](#snippetservice-pb-IDFilter) | repeated | | + + + + + + + + +### GetConfigurationsRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id_filter | [IDFilter](#snippetservice-pb-IDFilter) | repeated | | +| http_id_filter | [string](#string) | repeated | **Deprecated.** Format: {id}/{version}, e.g., "ae424c58-e517-4494-6de7-583536c48213/all" or "ae424c58-e517-4494-6de7-583536c48213/latest" or "ae424c58-e517-4494-6de7-583536c48213/{version}" | + + + + + + + + +### IDFilter +TODO: /configurations/123?version=latest + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| id | [string](#string) | | | +| value | [uint64](#uint64) | | | +| all | [bool](#bool) | | | +| latest | [bool](#bool) | | | + + + + + + + + +### InvokeConfigurationRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| configuration_id | [string](#string) | | applies latest configuration | +| device_id | [string](#string) | | | +| force | [bool](#bool) | | force update even if the configuration has already been applied to device | +| correlation_id | [string](#string) | | propagated down to the resource update command | + + + + + + + + + + +### AppliedDeviceConfiguration.Resource.Status + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| QUEUED | 0 | | +| PENDING | 1 | | +| DONE | 2 | If done look to resource_updated even update resource failed for resource aggregate. | +| TIMEOUT | 3 | | + + + + + + + + + +### SnippetService + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| CreateCondition | [Condition](#snippetservice-pb-Condition) | [Condition](#snippetservice-pb-Condition) | | +| GetConditions | [GetConditionsRequest](#snippetservice-pb-GetConditionsRequest) | [Condition](#snippetservice-pb-Condition) stream | | +| DeleteConditions | [DeleteConditionsRequest](#snippetservice-pb-DeleteConditionsRequest) | [DeleteConditionsResponse](#snippetservice-pb-DeleteConditionsResponse) | | +| UpdateCondition | [Condition](#snippetservice-pb-Condition) | [Condition](#snippetservice-pb-Condition) | | +| CreateConfiguration | [Configuration](#snippetservice-pb-Configuration) | [Configuration](#snippetservice-pb-Configuration) | | +| GetConfigurations | [GetConfigurationsRequest](#snippetservice-pb-GetConfigurationsRequest) | [Configuration](#snippetservice-pb-Configuration) stream | | +| DeleteConfigurations | [DeleteConfigurationsRequest](#snippetservice-pb-DeleteConfigurationsRequest) | [DeleteConfigurationsResponse](#snippetservice-pb-DeleteConfigurationsResponse) | | +| UpdateConfiguration | [Configuration](#snippetservice-pb-Configuration) | [Configuration](#snippetservice-pb-Configuration) | | +| InvokeConfiguration | [InvokeConfigurationRequest](#snippetservice-pb-InvokeConfigurationRequest) | [AppliedDeviceConfiguration](#snippetservice-pb-AppliedDeviceConfiguration) stream | streaming process of update configuration to invoker | +| GetAppliedConfigurations | [GetAppliedDeviceConfigurationsRequest](#snippetservice-pb-GetAppliedDeviceConfigurationsRequest) | [AppliedDeviceConfiguration](#snippetservice-pb-AppliedDeviceConfiguration) stream | | +| DeleteAppliedConfigurations | [DeleteAppliedDeviceConfigurationsRequest](#snippetservice-pb-DeleteAppliedDeviceConfigurationsRequest) | [DeleteAppliedDeviceConfigurationsResponse](#snippetservice-pb-DeleteAppliedDeviceConfigurationsResponse) | | + + + + + +## Scalar Value Types + +| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby | +| ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- | +| double | | double | double | float | float64 | double | float | Float | +| float | | float | float | float | float32 | float | float | Float | +| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) | +| uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) | +| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) | +| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum | +| sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | +| sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum | +| bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | +| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) | +| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) | + diff --git a/snippet-service/pb/configuration.go b/snippet-service/pb/configuration.go new file mode 100644 index 000000000..c39b996ad --- /dev/null +++ b/snippet-service/pb/configuration.go @@ -0,0 +1,26 @@ +package pb + +import ( + "errors" + "fmt" + "slices" + "strings" + + "github.com/google/uuid" +) + +func (c *Configuration) ValidateAndNormalize() error { + if _, err := uuid.Parse(c.GetId()); err != nil { + return fmt.Errorf("invalid configuration ID(%v): %w", c.GetId(), err) + } + if len(c.GetResources()) == 0 { + return errors.New("invalid configuration resources") + } + if c.GetOwner() == "" { + return errors.New("empty configuration owner") + } + slices.SortFunc(c.GetResources(), func(i, j *Configuration_Resource) int { + return strings.Compare(i.GetHref(), j.GetHref()) + }) + return nil +} diff --git a/snippet-service/pb/doc.html b/snippet-service/pb/doc.html new file mode 100644 index 000000000..3617c39de --- /dev/null +++ b/snippet-service/pb/doc.html @@ -0,0 +1,1448 @@ + + + + + Protocol Documentation + + + + + + + + + + +

Protocol Documentation

+ +

Table of Contents

+ +
+ +
+ + + +
+

snippet-service/pb/service.proto

Top +
+

+ + +

AppliedDeviceConfiguration

+

TODO naming

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idstring

device_idstring

configuration_idAppliedDeviceConfiguration.RelationTo

on_demandbool

condition_idAppliedDeviceConfiguration.RelationTo

TODO Naming

resourcesAppliedDeviceConfiguration.Resourcerepeated

TODO naming

ownerstring

timestampint64

Unix timestamp in ns when the applied device configuration has been created/updated + +@gotags: bson:"timestamp"

+ + + + + +

AppliedDeviceConfiguration.RelationTo

+

TODO naming

+ + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idstring

versionuint64

+ + + + + +

AppliedDeviceConfiguration.Resource

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
resource_idresourceaggregate.pb.ResourceId

TODO Jozo href only?

correlation_idstring

Reused from invoke command or generated. Can be used to retrieve corresponding pending command.

statusAppliedDeviceConfiguration.Resource.Status

resource_updatedresourceaggregate.pb.ResourceUpdated

+ + + + + +

Condition

+

driven by resource change event

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idstring

Condition ID + +@gotags: bson:"id"

versionuint64

@gotags: bson:"version"

namestring

@gotags: bson:"name,omitempty"

enabledbool

@gotags: bson:"enabled,omitempty"

configuration_idstring

ID of the configuration to be applied when the condition is satisfied + +@gotags: bson:"configurationId"

device_id_filterstringrepeated

list of device IDs to which the condition applies + +@gotags: bson:"deviceIdFilter,omitempty"

resource_type_filterstringrepeated

@gotags: bson:"resourceTypeFilter,omitempty"

resource_href_filterstringrepeated

list of resource hrefs to which the condition applies + +@gotags: bson:"resourceHrefFilter,omitempty"

jq_expression_filterstring

@gotags: bson:"jqExpressionFilter,omitempty"

api_access_tokenstring

Token used to update resources in the configuration + +@gotags: bson:"apiAccessToken,omitempty"

ownerstring

Condition owner + +@gotags: bson:"owner"

timestampint64

Unix timestamp in ns when the condition has been created/updated + +@gotags: bson:"timestamp"

+ + + + + +

Configuration

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idstring

Configuration ID

versionuint64

Configuration version

namestring

User-friendly configuration name + +@gotags: bson:"name,omitempty"

resourcesConfiguration.Resourcerepeated

List of resource updates to be applied

ownerstring

Configuration owner

timestampint64

Unix timestamp in ns when the configuration has been created/updated + +@gotags: bson:"timestamp"

+ + + + + +

Configuration.Resource

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
hrefstring

href of the resource

contentresourceaggregate.pb.Content

content update of the resource

time_to_liveint64

optional update command time to live, 0 is infinite

+ + + + + +

DeleteAppliedDeviceConfigurationsRequest

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
id_filterstringrepeated

+ + + + + +

DeleteAppliedDeviceConfigurationsResponse

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
countint64

+ + + + + +

DeleteConditionsRequest

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
id_filterIDFilterrepeated

+ + + + + +

DeleteConditionsResponse

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
countint64

+ + + + + +

DeleteConfigurationsRequest

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
id_filterIDFilterrepeated

+ + + + + +

DeleteConfigurationsResponse

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
countint64

+ + + + + +

GetAppliedDeviceConfigurationsRequest

+

TODO Naming

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
id_filterstringrepeated

configuration_id_filterIDFilterrepeated

device_id_filterstringrepeated

condition_id_filterIDFilterrepeated

+ + + + + +

GetConditionsRequest

+

+ + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
id_filterIDFilterrepeated

+ + + + + +

GetConfigurationsRequest

+

+ + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
id_filterIDFilterrepeated

http_id_filterstringrepeated

Deprecated. Format: {id}/{version}, e.g., "ae424c58-e517-4494-6de7-583536c48213/all" or "ae424c58-e517-4494-6de7-583536c48213/latest" or "ae424c58-e517-4494-6de7-583536c48213/{version}"

+ + + + +

Fields with deprecated option

+ + + + + + + + + + + + + + + +
NameOption
http_id_filter

true

+ + + + + +

IDFilter

+

TODO: /configurations/123?version=latest

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
idstring

valueuint64

allbool

latestbool

+ + + + + +

InvokeConfigurationRequest

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeLabelDescription
configuration_idstring

applies latest configuration

device_idstring

forcebool

force update even if the configuration has already been applied to device

correlation_idstring

propagated down to the resource update command

+ + + + + + + +

AppliedDeviceConfiguration.Resource.Status

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameNumberDescription
QUEUED0

PENDING1

DONE2

If done look to resource_updated even update resource failed for resource aggregate.

TIMEOUT3

+ + + + + +

SnippetService

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Method NameRequest TypeResponse TypeDescription
CreateConditionConditionCondition

GetConditionsGetConditionsRequestCondition stream

DeleteConditionsDeleteConditionsRequestDeleteConditionsResponse

UpdateConditionConditionCondition

CreateConfigurationConfigurationConfiguration

GetConfigurationsGetConfigurationsRequestConfiguration stream

DeleteConfigurationsDeleteConfigurationsRequestDeleteConfigurationsResponse

UpdateConfigurationConfigurationConfiguration

InvokeConfigurationInvokeConfigurationRequestAppliedDeviceConfiguration stream

streaming process of update configuration to invoker

GetAppliedConfigurationsGetAppliedDeviceConfigurationsRequestAppliedDeviceConfiguration stream

DeleteAppliedConfigurationsDeleteAppliedDeviceConfigurationsRequestDeleteAppliedDeviceConfigurationsResponse

+ + + + +

Methods with HTTP bindings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Method NameMethodPatternBody
CreateConditionPOST/snippet-service/api/v1/conditions*
GetConditionsGET/snippet-service/api/v1/conditions
DeleteConditionsDELETE/snippet-service/api/v1/conditions
UpdateConditionPUT/snippet-service/api/v1/conditions/{id}*
CreateConfigurationPOST/snippet-service/api/v1/configurations*
GetConfigurationsGET/snippet-service/api/v1/configurations
DeleteConfigurationsDELETE/snippet-service/api/v1/configurations
UpdateConfigurationPUT/snippet-service/api/v1/configurations/{id}*
InvokeConfigurationPOST/snippet-service/api/v1/configurations/{configuration_id}*
GetAppliedConfigurationsGET/snippet-service/api/v1/configurations/applied
DeleteAppliedConfigurationsDELETE/snippet-service/api/v1/configurations/applied
+ + + + +

Scalar Value Types

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
.proto TypeNotesC++JavaPythonGoC#PHPRuby
doubledoubledoublefloatfloat64doublefloatFloat
floatfloatfloatfloatfloat32floatfloatFloat
int32Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.int32intintint32intintegerBignum or Fixnum (as required)
int64Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.int64longint/longint64longinteger/stringBignum
uint32Uses variable-length encoding.uint32intint/longuint32uintintegerBignum or Fixnum (as required)
uint64Uses variable-length encoding.uint64longint/longuint64ulonginteger/stringBignum or Fixnum (as required)
sint32Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.int32intintint32intintegerBignum or Fixnum (as required)
sint64Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.int64longint/longint64longinteger/stringBignum
fixed32Always four bytes. More efficient than uint32 if values are often greater than 2^28.uint32intintuint32uintintegerBignum or Fixnum (as required)
fixed64Always eight bytes. More efficient than uint64 if values are often greater than 2^56.uint64longint/longuint64ulonginteger/stringBignum
sfixed32Always four bytes.int32intintint32intintegerBignum or Fixnum (as required)
sfixed64Always eight bytes.int64longint/longint64longinteger/stringBignum
boolboolbooleanbooleanboolboolbooleanTrueClass/FalseClass
stringA string must always contain UTF-8 encoded or 7-bit ASCII text.stringStringstr/unicodestringstringstringString (UTF-8)
bytesMay contain any arbitrary sequence of bytes.stringByteStringstr[]byteByteStringstringString (ASCII-8BIT)
+ + + diff --git a/snippet-service/pb/service.pb.go b/snippet-service/pb/service.pb.go new file mode 100644 index 000000000..c36093c54 --- /dev/null +++ b/snippet-service/pb/service.pb.go @@ -0,0 +1,1936 @@ +// TODO overit ze pending command sa nezmaze na neexistujucom resource ak sa device pripoji a nepublishne hned resource +// Overit correlation id - ak sa pouziva rovnake napriec viacerymi resourcami + +// scenare +// - Uzivatel vie vytvorit config, automaticka (backend) inkrementacia verzie +// - Uzivatel updatne config, verzia sa inkrementuje, Modal -> chces aplikovat na vsetky uz provisionnute devici? Informovat uzivatela, ze niektore devici mozu byt offline a command moze vyexpirovat. +// - Uzivatel updatne config, verzia sa inkrementuje, informujeme uzivatela ze vsetky pending commandy z predoslej verzie budu cancelnute ako aj dalsie sekvencne updaty resourcov pre predoslu verziu + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.1 +// protoc v5.26.1 +// source: snippet-service/pb/service.proto + +package pb + +import ( + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + commands "github.com/plgd-dev/hub/v2/resource-aggregate/commands" + events "github.com/plgd-dev/hub/v2/resource-aggregate/events" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AppliedDeviceConfiguration_Resource_Status int32 + +const ( + AppliedDeviceConfiguration_Resource_QUEUED AppliedDeviceConfiguration_Resource_Status = 0 + AppliedDeviceConfiguration_Resource_PENDING AppliedDeviceConfiguration_Resource_Status = 1 + AppliedDeviceConfiguration_Resource_DONE AppliedDeviceConfiguration_Resource_Status = 2 // If done look to resource_updated even update resource failed for resource aggregate. + AppliedDeviceConfiguration_Resource_TIMEOUT AppliedDeviceConfiguration_Resource_Status = 3 +) + +// Enum value maps for AppliedDeviceConfiguration_Resource_Status. +var ( + AppliedDeviceConfiguration_Resource_Status_name = map[int32]string{ + 0: "QUEUED", + 1: "PENDING", + 2: "DONE", + 3: "TIMEOUT", + } + AppliedDeviceConfiguration_Resource_Status_value = map[string]int32{ + "QUEUED": 0, + "PENDING": 1, + "DONE": 2, + "TIMEOUT": 3, + } +) + +func (x AppliedDeviceConfiguration_Resource_Status) Enum() *AppliedDeviceConfiguration_Resource_Status { + p := new(AppliedDeviceConfiguration_Resource_Status) + *p = x + return p +} + +func (x AppliedDeviceConfiguration_Resource_Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AppliedDeviceConfiguration_Resource_Status) Descriptor() protoreflect.EnumDescriptor { + return file_snippet_service_pb_service_proto_enumTypes[0].Descriptor() +} + +func (AppliedDeviceConfiguration_Resource_Status) Type() protoreflect.EnumType { + return &file_snippet_service_pb_service_proto_enumTypes[0] +} + +func (x AppliedDeviceConfiguration_Resource_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AppliedDeviceConfiguration_Resource_Status.Descriptor instead. +func (AppliedDeviceConfiguration_Resource_Status) EnumDescriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{9, 0, 0} +} + +// TODO: /configurations/123?version=latest +type IDFilter struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Types that are assignable to Version: + // + // *IDFilter_Value + // *IDFilter_All + // *IDFilter_Latest + Version isIDFilter_Version `protobuf_oneof:"version"` +} + +func (x *IDFilter) Reset() { + *x = IDFilter{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IDFilter) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IDFilter) ProtoMessage() {} + +func (x *IDFilter) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IDFilter.ProtoReflect.Descriptor instead. +func (*IDFilter) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{0} +} + +func (x *IDFilter) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (m *IDFilter) GetVersion() isIDFilter_Version { + if m != nil { + return m.Version + } + return nil +} + +func (x *IDFilter) GetValue() uint64 { + if x, ok := x.GetVersion().(*IDFilter_Value); ok { + return x.Value + } + return 0 +} + +func (x *IDFilter) GetAll() bool { + if x, ok := x.GetVersion().(*IDFilter_All); ok { + return x.All + } + return false +} + +func (x *IDFilter) GetLatest() bool { + if x, ok := x.GetVersion().(*IDFilter_Latest); ok { + return x.Latest + } + return false +} + +type isIDFilter_Version interface { + isIDFilter_Version() +} + +type IDFilter_Value struct { + Value uint64 `protobuf:"varint,2,opt,name=value,proto3,oneof"` +} + +type IDFilter_All struct { + All bool `protobuf:"varint,3,opt,name=all,proto3,oneof"` +} + +type IDFilter_Latest struct { + Latest bool `protobuf:"varint,4,opt,name=latest,proto3,oneof"` +} + +func (*IDFilter_Value) isIDFilter_Version() {} + +func (*IDFilter_All) isIDFilter_Version() {} + +func (*IDFilter_Latest) isIDFilter_Version() {} + +type Condition struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Condition ID + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" bson:"id"` + Version uint64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty" bson:"version"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty" bson:"name,omitempty"` + Enabled bool `protobuf:"varint,4,opt,name=enabled,proto3" json:"enabled,omitempty" bson:"enabled,omitempty"` + // ID of the configuration to be applied when the condition is satisfied + ConfigurationId string `protobuf:"bytes,5,opt,name=configuration_id,json=configurationId,proto3" json:"configuration_id,omitempty" bson:"configurationId"` + // list of device IDs to which the condition applies + DeviceIdFilter []string `protobuf:"bytes,6,rep,name=device_id_filter,json=deviceIdFilter,proto3" json:"device_id_filter,omitempty" bson:"deviceIdFilter,omitempty"` + ResourceTypeFilter []string `protobuf:"bytes,7,rep,name=resource_type_filter,json=resourceTypeFilter,proto3" json:"resource_type_filter,omitempty" bson:"resourceTypeFilter,omitempty"` + // list of resource hrefs to which the condition applies + ResourceHrefFilter []string `protobuf:"bytes,8,rep,name=resource_href_filter,json=resourceHrefFilter,proto3" json:"resource_href_filter,omitempty" bson:"resourceHrefFilter,omitempty"` + JqExpressionFilter string `protobuf:"bytes,9,opt,name=jq_expression_filter,json=jqExpressionFilter,proto3" json:"jq_expression_filter,omitempty" bson:"jqExpressionFilter,omitempty"` + // Token used to update resources in the configuration + ApiAccessToken string `protobuf:"bytes,10,opt,name=api_access_token,json=apiAccessToken,proto3" json:"api_access_token,omitempty" bson:"apiAccessToken,omitempty"` + // Condition owner + Owner string `protobuf:"bytes,11,opt,name=owner,proto3" json:"owner,omitempty" bson:"owner"` + // Unix timestamp in ns when the condition has been created/updated + Timestamp int64 `protobuf:"varint,12,opt,name=timestamp,proto3" json:"timestamp,omitempty" bson:"timestamp"` +} + +func (x *Condition) Reset() { + *x = Condition{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Condition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Condition) ProtoMessage() {} + +func (x *Condition) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Condition.ProtoReflect.Descriptor instead. +func (*Condition) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{1} +} + +func (x *Condition) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Condition) GetVersion() uint64 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Condition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Condition) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *Condition) GetConfigurationId() string { + if x != nil { + return x.ConfigurationId + } + return "" +} + +func (x *Condition) GetDeviceIdFilter() []string { + if x != nil { + return x.DeviceIdFilter + } + return nil +} + +func (x *Condition) GetResourceTypeFilter() []string { + if x != nil { + return x.ResourceTypeFilter + } + return nil +} + +func (x *Condition) GetResourceHrefFilter() []string { + if x != nil { + return x.ResourceHrefFilter + } + return nil +} + +func (x *Condition) GetJqExpressionFilter() string { + if x != nil { + return x.JqExpressionFilter + } + return "" +} + +func (x *Condition) GetApiAccessToken() string { + if x != nil { + return x.ApiAccessToken + } + return "" +} + +func (x *Condition) GetOwner() string { + if x != nil { + return x.Owner + } + return "" +} + +func (x *Condition) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type GetConditionsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IdFilter []*IDFilter `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` +} + +func (x *GetConditionsRequest) Reset() { + *x = GetConditionsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConditionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConditionsRequest) ProtoMessage() {} + +func (x *GetConditionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConditionsRequest.ProtoReflect.Descriptor instead. +func (*GetConditionsRequest) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{2} +} + +func (x *GetConditionsRequest) GetIdFilter() []*IDFilter { + if x != nil { + return x.IdFilter + } + return nil +} + +type DeleteConditionsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IdFilter []*IDFilter `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` +} + +func (x *DeleteConditionsRequest) Reset() { + *x = DeleteConditionsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteConditionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteConditionsRequest) ProtoMessage() {} + +func (x *DeleteConditionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteConditionsRequest.ProtoReflect.Descriptor instead. +func (*DeleteConditionsRequest) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{3} +} + +func (x *DeleteConditionsRequest) GetIdFilter() []*IDFilter { + if x != nil { + return x.IdFilter + } + return nil +} + +type DeleteConditionsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *DeleteConditionsResponse) Reset() { + *x = DeleteConditionsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteConditionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteConditionsResponse) ProtoMessage() {} + +func (x *DeleteConditionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteConditionsResponse.ProtoReflect.Descriptor instead. +func (*DeleteConditionsResponse) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{4} +} + +func (x *DeleteConditionsResponse) GetCount() int64 { + if x != nil { + return x.Count + } + return 0 +} + +type Configuration struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Configuration ID + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Configuration version + Version uint64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + // User-friendly configuration name + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty" bson:"name,omitempty"` + // List of resource updates to be applied + Resources []*Configuration_Resource `protobuf:"bytes,4,rep,name=resources,proto3" json:"resources,omitempty"` + // Configuration owner + Owner string `protobuf:"bytes,5,opt,name=owner,proto3" json:"owner,omitempty"` + // Unix timestamp in ns when the configuration has been created/updated + Timestamp int64 `protobuf:"varint,6,opt,name=timestamp,proto3" json:"timestamp,omitempty" bson:"timestamp"` +} + +func (x *Configuration) Reset() { + *x = Configuration{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Configuration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Configuration) ProtoMessage() {} + +func (x *Configuration) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Configuration.ProtoReflect.Descriptor instead. +func (*Configuration) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{5} +} + +func (x *Configuration) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Configuration) GetVersion() uint64 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Configuration) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Configuration) GetResources() []*Configuration_Resource { + if x != nil { + return x.Resources + } + return nil +} + +func (x *Configuration) GetOwner() string { + if x != nil { + return x.Owner + } + return "" +} + +func (x *Configuration) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type GetConfigurationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IdFilter []*IDFilter `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` + // Format: {id}/{version}, e.g., "ae424c58-e517-4494-6de7-583536c48213/all" or "ae424c58-e517-4494-6de7-583536c48213/latest" or "ae424c58-e517-4494-6de7-583536c48213/{version}" + // + // Deprecated: Marked as deprecated in snippet-service/pb/service.proto. + HttpIdFilter []string `protobuf:"bytes,2,rep,name=http_id_filter,json=httpIdFilter,proto3" json:"http_id_filter,omitempty"` +} + +func (x *GetConfigurationsRequest) Reset() { + *x = GetConfigurationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetConfigurationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetConfigurationsRequest) ProtoMessage() {} + +func (x *GetConfigurationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetConfigurationsRequest.ProtoReflect.Descriptor instead. +func (*GetConfigurationsRequest) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{6} +} + +func (x *GetConfigurationsRequest) GetIdFilter() []*IDFilter { + if x != nil { + return x.IdFilter + } + return nil +} + +// Deprecated: Marked as deprecated in snippet-service/pb/service.proto. +func (x *GetConfigurationsRequest) GetHttpIdFilter() []string { + if x != nil { + return x.HttpIdFilter + } + return nil +} + +type DeleteConfigurationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IdFilter []*IDFilter `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` +} + +func (x *DeleteConfigurationsRequest) Reset() { + *x = DeleteConfigurationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteConfigurationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteConfigurationsRequest) ProtoMessage() {} + +func (x *DeleteConfigurationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteConfigurationsRequest.ProtoReflect.Descriptor instead. +func (*DeleteConfigurationsRequest) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{7} +} + +func (x *DeleteConfigurationsRequest) GetIdFilter() []*IDFilter { + if x != nil { + return x.IdFilter + } + return nil +} + +type DeleteConfigurationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *DeleteConfigurationsResponse) Reset() { + *x = DeleteConfigurationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteConfigurationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteConfigurationsResponse) ProtoMessage() {} + +func (x *DeleteConfigurationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteConfigurationsResponse.ProtoReflect.Descriptor instead. +func (*DeleteConfigurationsResponse) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{8} +} + +func (x *DeleteConfigurationsResponse) GetCount() int64 { + if x != nil { + return x.Count + } + return 0 +} + +type AppliedDeviceConfiguration struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + DeviceId string `protobuf:"bytes,2,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + ConfigurationId *AppliedDeviceConfiguration_RelationTo `protobuf:"bytes,3,opt,name=configuration_id,json=configurationId,proto3" json:"configuration_id,omitempty"` + // Types that are assignable to ExecutedBy: + // + // *AppliedDeviceConfiguration_OnDemand + // *AppliedDeviceConfiguration_ConditionId + ExecutedBy isAppliedDeviceConfiguration_ExecutedBy `protobuf_oneof:"executed_by"` + Resources []*AppliedDeviceConfiguration_Resource `protobuf:"bytes,6,rep,name=resources,proto3" json:"resources,omitempty"` //TODO naming + Owner string `protobuf:"bytes,7,opt,name=owner,proto3" json:"owner,omitempty"` + // Unix timestamp in ns when the applied device configuration has been created/updated + Timestamp int64 `protobuf:"varint,8,opt,name=timestamp,proto3" json:"timestamp,omitempty" bson:"timestamp"` +} + +func (x *AppliedDeviceConfiguration) Reset() { + *x = AppliedDeviceConfiguration{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AppliedDeviceConfiguration) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AppliedDeviceConfiguration) ProtoMessage() {} + +func (x *AppliedDeviceConfiguration) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AppliedDeviceConfiguration.ProtoReflect.Descriptor instead. +func (*AppliedDeviceConfiguration) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{9} +} + +func (x *AppliedDeviceConfiguration) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AppliedDeviceConfiguration) GetDeviceId() string { + if x != nil { + return x.DeviceId + } + return "" +} + +func (x *AppliedDeviceConfiguration) GetConfigurationId() *AppliedDeviceConfiguration_RelationTo { + if x != nil { + return x.ConfigurationId + } + return nil +} + +func (m *AppliedDeviceConfiguration) GetExecutedBy() isAppliedDeviceConfiguration_ExecutedBy { + if m != nil { + return m.ExecutedBy + } + return nil +} + +func (x *AppliedDeviceConfiguration) GetOnDemand() bool { + if x, ok := x.GetExecutedBy().(*AppliedDeviceConfiguration_OnDemand); ok { + return x.OnDemand + } + return false +} + +func (x *AppliedDeviceConfiguration) GetConditionId() *AppliedDeviceConfiguration_RelationTo { + if x, ok := x.GetExecutedBy().(*AppliedDeviceConfiguration_ConditionId); ok { + return x.ConditionId + } + return nil +} + +func (x *AppliedDeviceConfiguration) GetResources() []*AppliedDeviceConfiguration_Resource { + if x != nil { + return x.Resources + } + return nil +} + +func (x *AppliedDeviceConfiguration) GetOwner() string { + if x != nil { + return x.Owner + } + return "" +} + +func (x *AppliedDeviceConfiguration) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type isAppliedDeviceConfiguration_ExecutedBy interface { + isAppliedDeviceConfiguration_ExecutedBy() +} + +type AppliedDeviceConfiguration_OnDemand struct { + OnDemand bool `protobuf:"varint,4,opt,name=on_demand,json=onDemand,proto3,oneof"` +} + +type AppliedDeviceConfiguration_ConditionId struct { + ConditionId *AppliedDeviceConfiguration_RelationTo `protobuf:"bytes,5,opt,name=condition_id,json=conditionId,proto3,oneof"` //TODO Naming +} + +func (*AppliedDeviceConfiguration_OnDemand) isAppliedDeviceConfiguration_ExecutedBy() {} + +func (*AppliedDeviceConfiguration_ConditionId) isAppliedDeviceConfiguration_ExecutedBy() {} + +type InvokeConfigurationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ConfigurationId string `protobuf:"bytes,1,opt,name=configuration_id,json=configurationId,proto3" json:"configuration_id,omitempty"` // applies latest configuration + DeviceId string `protobuf:"bytes,2,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + Force bool `protobuf:"varint,3,opt,name=force,proto3" json:"force,omitempty"` // force update even if the configuration has already been applied to device + CorrelationId string `protobuf:"bytes,4,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty"` // propagated down to the resource update command +} + +func (x *InvokeConfigurationRequest) Reset() { + *x = InvokeConfigurationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InvokeConfigurationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InvokeConfigurationRequest) ProtoMessage() {} + +func (x *InvokeConfigurationRequest) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InvokeConfigurationRequest.ProtoReflect.Descriptor instead. +func (*InvokeConfigurationRequest) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{10} +} + +func (x *InvokeConfigurationRequest) GetConfigurationId() string { + if x != nil { + return x.ConfigurationId + } + return "" +} + +func (x *InvokeConfigurationRequest) GetDeviceId() string { + if x != nil { + return x.DeviceId + } + return "" +} + +func (x *InvokeConfigurationRequest) GetForce() bool { + if x != nil { + return x.Force + } + return false +} + +func (x *InvokeConfigurationRequest) GetCorrelationId() string { + if x != nil { + return x.CorrelationId + } + return "" +} + +type GetAppliedDeviceConfigurationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IdFilter []string `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` + ConfigurationIdFilter []*IDFilter `protobuf:"bytes,2,rep,name=configuration_id_filter,json=configurationIdFilter,proto3" json:"configuration_id_filter,omitempty"` + DeviceIdFilter []string `protobuf:"bytes,3,rep,name=device_id_filter,json=deviceIdFilter,proto3" json:"device_id_filter,omitempty"` + ConditionIdFilter []*IDFilter `protobuf:"bytes,4,rep,name=condition_id_filter,json=conditionIdFilter,proto3" json:"condition_id_filter,omitempty"` +} + +func (x *GetAppliedDeviceConfigurationsRequest) Reset() { + *x = GetAppliedDeviceConfigurationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetAppliedDeviceConfigurationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAppliedDeviceConfigurationsRequest) ProtoMessage() {} + +func (x *GetAppliedDeviceConfigurationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAppliedDeviceConfigurationsRequest.ProtoReflect.Descriptor instead. +func (*GetAppliedDeviceConfigurationsRequest) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{11} +} + +func (x *GetAppliedDeviceConfigurationsRequest) GetIdFilter() []string { + if x != nil { + return x.IdFilter + } + return nil +} + +func (x *GetAppliedDeviceConfigurationsRequest) GetConfigurationIdFilter() []*IDFilter { + if x != nil { + return x.ConfigurationIdFilter + } + return nil +} + +func (x *GetAppliedDeviceConfigurationsRequest) GetDeviceIdFilter() []string { + if x != nil { + return x.DeviceIdFilter + } + return nil +} + +func (x *GetAppliedDeviceConfigurationsRequest) GetConditionIdFilter() []*IDFilter { + if x != nil { + return x.ConditionIdFilter + } + return nil +} + +type DeleteAppliedDeviceConfigurationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IdFilter []string `protobuf:"bytes,1,rep,name=id_filter,json=idFilter,proto3" json:"id_filter,omitempty"` +} + +func (x *DeleteAppliedDeviceConfigurationsRequest) Reset() { + *x = DeleteAppliedDeviceConfigurationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteAppliedDeviceConfigurationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAppliedDeviceConfigurationsRequest) ProtoMessage() {} + +func (x *DeleteAppliedDeviceConfigurationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAppliedDeviceConfigurationsRequest.ProtoReflect.Descriptor instead. +func (*DeleteAppliedDeviceConfigurationsRequest) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{12} +} + +func (x *DeleteAppliedDeviceConfigurationsRequest) GetIdFilter() []string { + if x != nil { + return x.IdFilter + } + return nil +} + +type DeleteAppliedDeviceConfigurationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Count int64 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *DeleteAppliedDeviceConfigurationsResponse) Reset() { + *x = DeleteAppliedDeviceConfigurationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteAppliedDeviceConfigurationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAppliedDeviceConfigurationsResponse) ProtoMessage() {} + +func (x *DeleteAppliedDeviceConfigurationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAppliedDeviceConfigurationsResponse.ProtoReflect.Descriptor instead. +func (*DeleteAppliedDeviceConfigurationsResponse) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{13} +} + +func (x *DeleteAppliedDeviceConfigurationsResponse) GetCount() int64 { + if x != nil { + return x.Count + } + return 0 +} + +type Configuration_Resource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // href of the resource + Href string `protobuf:"bytes,1,opt,name=href,proto3" json:"href,omitempty"` + // content update of the resource + Content *commands.Content `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` + // optional update command time to live, 0 is infinite + TimeToLive int64 `protobuf:"varint,3,opt,name=time_to_live,json=timeToLive,proto3" json:"time_to_live,omitempty"` +} + +func (x *Configuration_Resource) Reset() { + *x = Configuration_Resource{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Configuration_Resource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Configuration_Resource) ProtoMessage() {} + +func (x *Configuration_Resource) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Configuration_Resource.ProtoReflect.Descriptor instead. +func (*Configuration_Resource) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{5, 0} +} + +func (x *Configuration_Resource) GetHref() string { + if x != nil { + return x.Href + } + return "" +} + +func (x *Configuration_Resource) GetContent() *commands.Content { + if x != nil { + return x.Content + } + return nil +} + +func (x *Configuration_Resource) GetTimeToLive() int64 { + if x != nil { + return x.TimeToLive + } + return 0 +} + +type AppliedDeviceConfiguration_Resource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResourceId *commands.ResourceId `protobuf:"bytes,1,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` // TODO Jozo href only? + CorrelationId string `protobuf:"bytes,2,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty"` // Reused from invoke command or generated. Can be used to retrieve corresponding pending command. + Status AppliedDeviceConfiguration_Resource_Status `protobuf:"varint,3,opt,name=status,proto3,enum=snippetservice.pb.AppliedDeviceConfiguration_Resource_Status" json:"status,omitempty"` + ResourceUpdated *events.ResourceUpdated `protobuf:"bytes,4,opt,name=resource_updated,json=resourceUpdated,proto3" json:"resource_updated,omitempty"` +} + +func (x *AppliedDeviceConfiguration_Resource) Reset() { + *x = AppliedDeviceConfiguration_Resource{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AppliedDeviceConfiguration_Resource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AppliedDeviceConfiguration_Resource) ProtoMessage() {} + +func (x *AppliedDeviceConfiguration_Resource) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AppliedDeviceConfiguration_Resource.ProtoReflect.Descriptor instead. +func (*AppliedDeviceConfiguration_Resource) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{9, 0} +} + +func (x *AppliedDeviceConfiguration_Resource) GetResourceId() *commands.ResourceId { + if x != nil { + return x.ResourceId + } + return nil +} + +func (x *AppliedDeviceConfiguration_Resource) GetCorrelationId() string { + if x != nil { + return x.CorrelationId + } + return "" +} + +func (x *AppliedDeviceConfiguration_Resource) GetStatus() AppliedDeviceConfiguration_Resource_Status { + if x != nil { + return x.Status + } + return AppliedDeviceConfiguration_Resource_QUEUED +} + +func (x *AppliedDeviceConfiguration_Resource) GetResourceUpdated() *events.ResourceUpdated { + if x != nil { + return x.ResourceUpdated + } + return nil +} + +type AppliedDeviceConfiguration_RelationTo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Version uint64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *AppliedDeviceConfiguration_RelationTo) Reset() { + *x = AppliedDeviceConfiguration_RelationTo{} + if protoimpl.UnsafeEnabled { + mi := &file_snippet_service_pb_service_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AppliedDeviceConfiguration_RelationTo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AppliedDeviceConfiguration_RelationTo) ProtoMessage() {} + +func (x *AppliedDeviceConfiguration_RelationTo) ProtoReflect() protoreflect.Message { + mi := &file_snippet_service_pb_service_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AppliedDeviceConfiguration_RelationTo.ProtoReflect.Descriptor instead. +func (*AppliedDeviceConfiguration_RelationTo) Descriptor() ([]byte, []int) { + return file_snippet_service_pb_service_proto_rawDescGZIP(), []int{9, 1} +} + +func (x *AppliedDeviceConfiguration_RelationTo) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AppliedDeviceConfiguration_RelationTo) GetVersion() uint64 { + if x != nil { + return x.Version + } + return 0 +} + +var File_snippet_service_pb_service_proto protoreflect.FileDescriptor + +var file_snippet_service_pb_service_proto_rawDesc = []byte{ + 0x0a, 0x20, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x11, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x2e, 0x70, 0x62, 0x1a, 0x25, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2d, + 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x2f, 0x70, 0x62, 0x2f, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x22, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2d, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, + 0x2f, 0x70, 0x62, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, + 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, + 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, + 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6b, + 0x0a, 0x08, 0x49, 0x44, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x61, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, + 0x00, 0x52, 0x03, 0x61, 0x6c, 0x6c, 0x12, 0x18, 0x0a, 0x06, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x42, 0x09, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xac, 0x03, 0x0a, 0x09, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, + 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x07, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x68, 0x72, 0x65, 0x66, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x48, 0x72, 0x65, 0x66, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x6a, 0x71, + 0x5f, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6a, 0x71, 0x45, 0x78, 0x70, 0x72, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, + 0x61, 0x70, 0x69, 0x5f, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x70, 0x69, 0x41, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x50, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x44, 0x46, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x53, 0x0a, 0x17, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x6e, 0x69, + 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x49, + 0x44, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x22, 0x30, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x22, 0xc5, 0x02, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x47, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, + 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x1a, 0x79, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x68, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x72, 0x65, 0x66, + 0x12, 0x37, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x61, 0x67, 0x67, 0x72, + 0x65, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0c, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x54, 0x6f, 0x4c, 0x69, 0x76, 0x65, 0x22, 0x7e, 0x0a, 0x18, 0x47, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x6e, 0x69, + 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x49, + 0x44, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x12, 0x28, 0x0a, 0x0e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0c, 0x68, + 0x74, 0x74, 0x70, 0x49, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x57, 0x0a, 0x1b, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x64, + 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, + 0x62, 0x2e, 0x49, 0x44, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x22, 0x34, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xd7, 0x06, 0x0a, 0x1a, 0x41, + 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x63, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x38, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x09, 0x6f, + 0x6e, 0x5f, 0x64, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, + 0x52, 0x08, 0x6f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x5d, 0x0a, 0x0c, 0x63, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x38, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x48, 0x00, 0x52, 0x0b, 0x63, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x54, 0x0a, 0x09, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x73, + 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, + 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x1a, 0xd7, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x41, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x72, + 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x55, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3d, 0x2e, 0x73, 0x6e, 0x69, + 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x41, + 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x50, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x2e, + 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x22, 0x38, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x0a, + 0x06, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, + 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x4f, 0x4e, 0x45, 0x10, 0x02, + 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x03, 0x1a, 0x36, 0x0a, + 0x0a, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x0d, 0x0a, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, + 0x64, 0x5f, 0x62, 0x79, 0x22, 0xa1, 0x01, 0x0a, 0x1a, 0x49, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1b, + 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x66, + 0x6f, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, + 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x90, 0x02, 0x0a, 0x25, 0x47, 0x65, 0x74, + 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, + 0x53, 0x0a, 0x17, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x44, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x15, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, + 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x4b, + 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x5f, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x6e, + 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, + 0x49, 0x44, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x47, 0x0a, 0x28, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x64, 0x5f, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, 0x46, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x22, 0x41, 0x0a, 0x29, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, + 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xaa, 0x0f, 0x0a, 0x0e, 0x53, 0x6e, 0x69, 0x70, + 0x70, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x8b, 0x01, 0x0a, 0x0f, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, + 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1c, 0x2e, 0x73, + 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, + 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3c, 0x92, 0x41, 0x0c, 0x0a, + 0x0a, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x27, 0x3a, 0x01, 0x2a, 0x22, 0x22, 0x2f, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x93, 0x01, 0x0a, 0x0d, 0x47, 0x65, 0x74, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x2e, 0x73, 0x6e, 0x69, + 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x47, + 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x39, 0x92, 0x41, 0x0c, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x12, 0x22, 0x2f, 0x73, 0x6e, 0x69, 0x70, 0x70, + 0x65, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x30, 0x01, 0x12, 0xa6, + 0x01, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x2a, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2b, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x92, 0x41, + 0x0c, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x24, 0x2a, 0x22, 0x2f, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x90, 0x01, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x73, 0x6e, + 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1c, 0x2e, 0x73, 0x6e, 0x69, 0x70, + 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x41, 0x92, 0x41, 0x0c, 0x0a, 0x0a, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, 0x01, + 0x2a, 0x1a, 0x27, 0x2f, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x9f, 0x01, 0x0a, 0x13, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x20, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x44, 0x92, 0x41, 0x10, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x2b, 0x3a, 0x01, 0x2a, 0x22, 0x26, 0x2f, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0xa7, 0x01, 0x0a, + 0x11, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x2b, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x20, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x41, 0x92, 0x41, 0x10, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x12, 0x26, 0x2f, 0x73, + 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x30, 0x01, 0x12, 0xba, 0x01, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x2e, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2f, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2e, 0x70, 0x62, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x41, 0x92, 0x41, 0x10, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x2a, 0x26, 0x2f, 0x73, 0x6e, + 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0xa4, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x73, 0x6e, + 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x20, 0x2e, + 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, + 0x62, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, + 0x49, 0x92, 0x41, 0x10, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x3a, 0x01, 0x2a, 0x1a, 0x2b, 0x2f, + 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xce, 0x01, 0x0a, 0x13, 0x49, + 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x2d, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2d, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x57, 0x92, 0x41, 0x10, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3e, 0x3a, 0x01, 0x2a, 0x22, 0x39, + 0x2f, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x7b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x7d, 0x30, 0x01, 0x12, 0xd0, 0x01, 0x0a, 0x18, + 0x47, 0x65, 0x74, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x38, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, + 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, + 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x49, 0x92, 0x41, 0x10, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x30, 0x12, 0x2e, 0x2f, 0x73, + 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x30, 0x01, 0x12, 0xe3, + 0x01, 0x0a, 0x1b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3b, + 0x2e, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x70, 0x62, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x73, 0x6e, + 0x69, 0x70, 0x70, 0x65, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x62, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x49, 0x92, 0x41, 0x10, 0x0a, 0x0e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x30, 0x2a, 0x2e, 0x2f, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x70, 0x70, + 0x6c, 0x69, 0x65, 0x64, 0x42, 0xd5, 0x02, 0x92, 0x41, 0x9f, 0x02, 0x12, 0xc7, 0x01, 0x0a, 0x14, + 0x50, 0x4c, 0x47, 0x44, 0x20, 0x52, 0x75, 0x6c, 0x65, 0x20, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, + 0x20, 0x41, 0x50, 0x49, 0x12, 0x27, 0x41, 0x50, 0x49, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x72, 0x75, 0x6c, 0x65, 0x20, 0x65, + 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x4c, 0x47, 0x44, 0x22, 0x3a, 0x0a, + 0x08, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x12, 0x1f, 0x68, 0x74, 0x74, 0x70, 0x73, + 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, + 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, 0x1a, 0x0d, 0x69, 0x6e, 0x66, 0x6f, + 0x40, 0x70, 0x6c, 0x67, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2a, 0x45, 0x0a, 0x12, 0x41, 0x70, 0x61, + 0x63, 0x68, 0x65, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x32, 0x2e, 0x30, 0x12, + 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x68, 0x75, 0x62, + 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, + 0x32, 0x03, 0x31, 0x2e, 0x30, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x32, 0x15, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, + 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, + 0x73, 0x6f, 0x6e, 0x3a, 0x15, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6c, 0x67, 0x64, 0x2d, 0x64, 0x65, 0x76, 0x2f, + 0x68, 0x75, 0x62, 0x2f, 0x76, 0x32, 0x2f, 0x73, 0x6e, 0x69, 0x70, 0x70, 0x65, 0x74, 0x2d, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x70, 0x62, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_snippet_service_pb_service_proto_rawDescOnce sync.Once + file_snippet_service_pb_service_proto_rawDescData = file_snippet_service_pb_service_proto_rawDesc +) + +func file_snippet_service_pb_service_proto_rawDescGZIP() []byte { + file_snippet_service_pb_service_proto_rawDescOnce.Do(func() { + file_snippet_service_pb_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_snippet_service_pb_service_proto_rawDescData) + }) + return file_snippet_service_pb_service_proto_rawDescData +} + +var file_snippet_service_pb_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_snippet_service_pb_service_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_snippet_service_pb_service_proto_goTypes = []interface{}{ + (AppliedDeviceConfiguration_Resource_Status)(0), // 0: snippetservice.pb.AppliedDeviceConfiguration.Resource.Status + (*IDFilter)(nil), // 1: snippetservice.pb.IDFilter + (*Condition)(nil), // 2: snippetservice.pb.Condition + (*GetConditionsRequest)(nil), // 3: snippetservice.pb.GetConditionsRequest + (*DeleteConditionsRequest)(nil), // 4: snippetservice.pb.DeleteConditionsRequest + (*DeleteConditionsResponse)(nil), // 5: snippetservice.pb.DeleteConditionsResponse + (*Configuration)(nil), // 6: snippetservice.pb.Configuration + (*GetConfigurationsRequest)(nil), // 7: snippetservice.pb.GetConfigurationsRequest + (*DeleteConfigurationsRequest)(nil), // 8: snippetservice.pb.DeleteConfigurationsRequest + (*DeleteConfigurationsResponse)(nil), // 9: snippetservice.pb.DeleteConfigurationsResponse + (*AppliedDeviceConfiguration)(nil), // 10: snippetservice.pb.AppliedDeviceConfiguration + (*InvokeConfigurationRequest)(nil), // 11: snippetservice.pb.InvokeConfigurationRequest + (*GetAppliedDeviceConfigurationsRequest)(nil), // 12: snippetservice.pb.GetAppliedDeviceConfigurationsRequest + (*DeleteAppliedDeviceConfigurationsRequest)(nil), // 13: snippetservice.pb.DeleteAppliedDeviceConfigurationsRequest + (*DeleteAppliedDeviceConfigurationsResponse)(nil), // 14: snippetservice.pb.DeleteAppliedDeviceConfigurationsResponse + (*Configuration_Resource)(nil), // 15: snippetservice.pb.Configuration.Resource + (*AppliedDeviceConfiguration_Resource)(nil), // 16: snippetservice.pb.AppliedDeviceConfiguration.Resource + (*AppliedDeviceConfiguration_RelationTo)(nil), // 17: snippetservice.pb.AppliedDeviceConfiguration.RelationTo + (*commands.Content)(nil), // 18: resourceaggregate.pb.Content + (*commands.ResourceId)(nil), // 19: resourceaggregate.pb.ResourceId + (*events.ResourceUpdated)(nil), // 20: resourceaggregate.pb.ResourceUpdated +} +var file_snippet_service_pb_service_proto_depIdxs = []int32{ + 1, // 0: snippetservice.pb.GetConditionsRequest.id_filter:type_name -> snippetservice.pb.IDFilter + 1, // 1: snippetservice.pb.DeleteConditionsRequest.id_filter:type_name -> snippetservice.pb.IDFilter + 15, // 2: snippetservice.pb.Configuration.resources:type_name -> snippetservice.pb.Configuration.Resource + 1, // 3: snippetservice.pb.GetConfigurationsRequest.id_filter:type_name -> snippetservice.pb.IDFilter + 1, // 4: snippetservice.pb.DeleteConfigurationsRequest.id_filter:type_name -> snippetservice.pb.IDFilter + 17, // 5: snippetservice.pb.AppliedDeviceConfiguration.configuration_id:type_name -> snippetservice.pb.AppliedDeviceConfiguration.RelationTo + 17, // 6: snippetservice.pb.AppliedDeviceConfiguration.condition_id:type_name -> snippetservice.pb.AppliedDeviceConfiguration.RelationTo + 16, // 7: snippetservice.pb.AppliedDeviceConfiguration.resources:type_name -> snippetservice.pb.AppliedDeviceConfiguration.Resource + 1, // 8: snippetservice.pb.GetAppliedDeviceConfigurationsRequest.configuration_id_filter:type_name -> snippetservice.pb.IDFilter + 1, // 9: snippetservice.pb.GetAppliedDeviceConfigurationsRequest.condition_id_filter:type_name -> snippetservice.pb.IDFilter + 18, // 10: snippetservice.pb.Configuration.Resource.content:type_name -> resourceaggregate.pb.Content + 19, // 11: snippetservice.pb.AppliedDeviceConfiguration.Resource.resource_id:type_name -> resourceaggregate.pb.ResourceId + 0, // 12: snippetservice.pb.AppliedDeviceConfiguration.Resource.status:type_name -> snippetservice.pb.AppliedDeviceConfiguration.Resource.Status + 20, // 13: snippetservice.pb.AppliedDeviceConfiguration.Resource.resource_updated:type_name -> resourceaggregate.pb.ResourceUpdated + 2, // 14: snippetservice.pb.SnippetService.CreateCondition:input_type -> snippetservice.pb.Condition + 3, // 15: snippetservice.pb.SnippetService.GetConditions:input_type -> snippetservice.pb.GetConditionsRequest + 4, // 16: snippetservice.pb.SnippetService.DeleteConditions:input_type -> snippetservice.pb.DeleteConditionsRequest + 2, // 17: snippetservice.pb.SnippetService.UpdateCondition:input_type -> snippetservice.pb.Condition + 6, // 18: snippetservice.pb.SnippetService.CreateConfiguration:input_type -> snippetservice.pb.Configuration + 7, // 19: snippetservice.pb.SnippetService.GetConfigurations:input_type -> snippetservice.pb.GetConfigurationsRequest + 8, // 20: snippetservice.pb.SnippetService.DeleteConfigurations:input_type -> snippetservice.pb.DeleteConfigurationsRequest + 6, // 21: snippetservice.pb.SnippetService.UpdateConfiguration:input_type -> snippetservice.pb.Configuration + 11, // 22: snippetservice.pb.SnippetService.InvokeConfiguration:input_type -> snippetservice.pb.InvokeConfigurationRequest + 12, // 23: snippetservice.pb.SnippetService.GetAppliedConfigurations:input_type -> snippetservice.pb.GetAppliedDeviceConfigurationsRequest + 13, // 24: snippetservice.pb.SnippetService.DeleteAppliedConfigurations:input_type -> snippetservice.pb.DeleteAppliedDeviceConfigurationsRequest + 2, // 25: snippetservice.pb.SnippetService.CreateCondition:output_type -> snippetservice.pb.Condition + 2, // 26: snippetservice.pb.SnippetService.GetConditions:output_type -> snippetservice.pb.Condition + 5, // 27: snippetservice.pb.SnippetService.DeleteConditions:output_type -> snippetservice.pb.DeleteConditionsResponse + 2, // 28: snippetservice.pb.SnippetService.UpdateCondition:output_type -> snippetservice.pb.Condition + 6, // 29: snippetservice.pb.SnippetService.CreateConfiguration:output_type -> snippetservice.pb.Configuration + 6, // 30: snippetservice.pb.SnippetService.GetConfigurations:output_type -> snippetservice.pb.Configuration + 9, // 31: snippetservice.pb.SnippetService.DeleteConfigurations:output_type -> snippetservice.pb.DeleteConfigurationsResponse + 6, // 32: snippetservice.pb.SnippetService.UpdateConfiguration:output_type -> snippetservice.pb.Configuration + 10, // 33: snippetservice.pb.SnippetService.InvokeConfiguration:output_type -> snippetservice.pb.AppliedDeviceConfiguration + 10, // 34: snippetservice.pb.SnippetService.GetAppliedConfigurations:output_type -> snippetservice.pb.AppliedDeviceConfiguration + 14, // 35: snippetservice.pb.SnippetService.DeleteAppliedConfigurations:output_type -> snippetservice.pb.DeleteAppliedDeviceConfigurationsResponse + 25, // [25:36] is the sub-list for method output_type + 14, // [14:25] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name +} + +func init() { file_snippet_service_pb_service_proto_init() } +func file_snippet_service_pb_service_proto_init() { + if File_snippet_service_pb_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_snippet_service_pb_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IDFilter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Condition); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConditionsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteConditionsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteConditionsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Configuration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetConfigurationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteConfigurationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteConfigurationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AppliedDeviceConfiguration); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InvokeConfigurationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetAppliedDeviceConfigurationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAppliedDeviceConfigurationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteAppliedDeviceConfigurationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Configuration_Resource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AppliedDeviceConfiguration_Resource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_snippet_service_pb_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AppliedDeviceConfiguration_RelationTo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_snippet_service_pb_service_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*IDFilter_Value)(nil), + (*IDFilter_All)(nil), + (*IDFilter_Latest)(nil), + } + file_snippet_service_pb_service_proto_msgTypes[9].OneofWrappers = []interface{}{ + (*AppliedDeviceConfiguration_OnDemand)(nil), + (*AppliedDeviceConfiguration_ConditionId)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_snippet_service_pb_service_proto_rawDesc, + NumEnums: 1, + NumMessages: 17, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_snippet_service_pb_service_proto_goTypes, + DependencyIndexes: file_snippet_service_pb_service_proto_depIdxs, + EnumInfos: file_snippet_service_pb_service_proto_enumTypes, + MessageInfos: file_snippet_service_pb_service_proto_msgTypes, + }.Build() + File_snippet_service_pb_service_proto = out.File + file_snippet_service_pb_service_proto_rawDesc = nil + file_snippet_service_pb_service_proto_goTypes = nil + file_snippet_service_pb_service_proto_depIdxs = nil +} diff --git a/snippet-service/pb/service.pb.gw.go b/snippet-service/pb/service.pb.gw.go new file mode 100644 index 000000000..14d3b3609 --- /dev/null +++ b/snippet-service/pb/service.pb.gw.go @@ -0,0 +1,977 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: snippet-service/pb/service.proto + +/* +Package pb is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package pb + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_SnippetService_CreateCondition_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Condition + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.CreateCondition(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SnippetService_CreateCondition_0(ctx context.Context, marshaler runtime.Marshaler, server SnippetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Condition + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CreateCondition(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_SnippetService_GetConditions_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_SnippetService_GetConditions_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (SnippetService_GetConditionsClient, runtime.ServerMetadata, error) { + var protoReq GetConditionsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SnippetService_GetConditions_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + stream, err := client.GetConditions(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + +var ( + filter_SnippetService_DeleteConditions_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_SnippetService_DeleteConditions_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteConditionsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SnippetService_DeleteConditions_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DeleteConditions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SnippetService_DeleteConditions_0(ctx context.Context, marshaler runtime.Marshaler, server SnippetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteConditionsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SnippetService_DeleteConditions_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DeleteConditions(ctx, &protoReq) + return msg, metadata, err + +} + +func request_SnippetService_UpdateCondition_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Condition + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.UpdateCondition(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SnippetService_UpdateCondition_0(ctx context.Context, marshaler runtime.Marshaler, server SnippetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Condition + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.UpdateCondition(ctx, &protoReq) + return msg, metadata, err + +} + +func request_SnippetService_CreateConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Configuration + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.CreateConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SnippetService_CreateConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, server SnippetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Configuration + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CreateConfiguration(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_SnippetService_GetConfigurations_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_SnippetService_GetConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (SnippetService_GetConfigurationsClient, runtime.ServerMetadata, error) { + var protoReq GetConfigurationsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SnippetService_GetConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + stream, err := client.GetConfigurations(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + +var ( + filter_SnippetService_DeleteConfigurations_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_SnippetService_DeleteConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteConfigurationsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SnippetService_DeleteConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DeleteConfigurations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SnippetService_DeleteConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, server SnippetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteConfigurationsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SnippetService_DeleteConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DeleteConfigurations(ctx, &protoReq) + return msg, metadata, err + +} + +func request_SnippetService_UpdateConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Configuration + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.UpdateConfiguration(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SnippetService_UpdateConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, server SnippetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Configuration + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.UpdateConfiguration(ctx, &protoReq) + return msg, metadata, err + +} + +func request_SnippetService_InvokeConfiguration_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (SnippetService_InvokeConfigurationClient, runtime.ServerMetadata, error) { + var protoReq InvokeConfigurationRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["configuration_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "configuration_id") + } + + protoReq.ConfigurationId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "configuration_id", err) + } + + stream, err := client.InvokeConfiguration(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + +var ( + filter_SnippetService_GetAppliedConfigurations_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_SnippetService_GetAppliedConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (SnippetService_GetAppliedConfigurationsClient, runtime.ServerMetadata, error) { + var protoReq GetAppliedDeviceConfigurationsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SnippetService_GetAppliedConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + stream, err := client.GetAppliedConfigurations(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + +var ( + filter_SnippetService_DeleteAppliedConfigurations_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_SnippetService_DeleteAppliedConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, client SnippetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteAppliedDeviceConfigurationsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SnippetService_DeleteAppliedConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DeleteAppliedConfigurations(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SnippetService_DeleteAppliedConfigurations_0(ctx context.Context, marshaler runtime.Marshaler, server SnippetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteAppliedDeviceConfigurationsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_SnippetService_DeleteAppliedConfigurations_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DeleteAppliedConfigurations(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterSnippetServiceHandlerServer registers the http handlers for service SnippetService to "mux". +// UnaryRPC :call SnippetServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterSnippetServiceHandlerFromEndpoint instead. +func RegisterSnippetServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server SnippetServiceServer) error { + + mux.Handle("POST", pattern_SnippetService_CreateCondition_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/snippetservice.pb.SnippetService/CreateCondition", runtime.WithHTTPPathPattern("/snippet-service/api/v1/conditions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SnippetService_CreateCondition_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_CreateCondition_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_SnippetService_GetConditions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("DELETE", pattern_SnippetService_DeleteConditions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/snippetservice.pb.SnippetService/DeleteConditions", runtime.WithHTTPPathPattern("/snippet-service/api/v1/conditions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SnippetService_DeleteConditions_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_DeleteConditions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PUT", pattern_SnippetService_UpdateCondition_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/snippetservice.pb.SnippetService/UpdateCondition", runtime.WithHTTPPathPattern("/snippet-service/api/v1/conditions/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SnippetService_UpdateCondition_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_UpdateCondition_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SnippetService_CreateConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/snippetservice.pb.SnippetService/CreateConfiguration", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SnippetService_CreateConfiguration_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_CreateConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_SnippetService_GetConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("DELETE", pattern_SnippetService_DeleteConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/snippetservice.pb.SnippetService/DeleteConfigurations", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SnippetService_DeleteConfigurations_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_DeleteConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PUT", pattern_SnippetService_UpdateConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/snippetservice.pb.SnippetService/UpdateConfiguration", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SnippetService_UpdateConfiguration_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_UpdateConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SnippetService_InvokeConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("GET", pattern_SnippetService_GetAppliedConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + + mux.Handle("DELETE", pattern_SnippetService_DeleteAppliedConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/snippetservice.pb.SnippetService/DeleteAppliedConfigurations", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations/applied")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SnippetService_DeleteAppliedConfigurations_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_DeleteAppliedConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterSnippetServiceHandlerFromEndpoint is same as RegisterSnippetServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterSnippetServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterSnippetServiceHandler(ctx, mux, conn) +} + +// RegisterSnippetServiceHandler registers the http handlers for service SnippetService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterSnippetServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterSnippetServiceHandlerClient(ctx, mux, NewSnippetServiceClient(conn)) +} + +// RegisterSnippetServiceHandlerClient registers the http handlers for service SnippetService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "SnippetServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "SnippetServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "SnippetServiceClient" to call the correct interceptors. +func RegisterSnippetServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client SnippetServiceClient) error { + + mux.Handle("POST", pattern_SnippetService_CreateCondition_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/CreateCondition", runtime.WithHTTPPathPattern("/snippet-service/api/v1/conditions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_CreateCondition_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_CreateCondition_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_SnippetService_GetConditions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/GetConditions", runtime.WithHTTPPathPattern("/snippet-service/api/v1/conditions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_GetConditions_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_GetConditions_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_SnippetService_DeleteConditions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/DeleteConditions", runtime.WithHTTPPathPattern("/snippet-service/api/v1/conditions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_DeleteConditions_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_DeleteConditions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PUT", pattern_SnippetService_UpdateCondition_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/UpdateCondition", runtime.WithHTTPPathPattern("/snippet-service/api/v1/conditions/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_UpdateCondition_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_UpdateCondition_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SnippetService_CreateConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/CreateConfiguration", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_CreateConfiguration_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_CreateConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_SnippetService_GetConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/GetConfigurations", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_GetConfigurations_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_GetConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_SnippetService_DeleteConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/DeleteConfigurations", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_DeleteConfigurations_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_DeleteConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PUT", pattern_SnippetService_UpdateConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/UpdateConfiguration", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_UpdateConfiguration_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_UpdateConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_SnippetService_InvokeConfiguration_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/InvokeConfiguration", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations/{configuration_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_InvokeConfiguration_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_InvokeConfiguration_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_SnippetService_GetAppliedConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/GetAppliedConfigurations", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations/applied")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_GetAppliedConfigurations_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_GetAppliedConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_SnippetService_DeleteAppliedConfigurations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/snippetservice.pb.SnippetService/DeleteAppliedConfigurations", runtime.WithHTTPPathPattern("/snippet-service/api/v1/configurations/applied")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SnippetService_DeleteAppliedConfigurations_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SnippetService_DeleteAppliedConfigurations_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_SnippetService_CreateCondition_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"snippet-service", "api", "v1", "conditions"}, "")) + + pattern_SnippetService_GetConditions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"snippet-service", "api", "v1", "conditions"}, "")) + + pattern_SnippetService_DeleteConditions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"snippet-service", "api", "v1", "conditions"}, "")) + + pattern_SnippetService_UpdateCondition_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"snippet-service", "api", "v1", "conditions", "id"}, "")) + + pattern_SnippetService_CreateConfiguration_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"snippet-service", "api", "v1", "configurations"}, "")) + + pattern_SnippetService_GetConfigurations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"snippet-service", "api", "v1", "configurations"}, "")) + + pattern_SnippetService_DeleteConfigurations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"snippet-service", "api", "v1", "configurations"}, "")) + + pattern_SnippetService_UpdateConfiguration_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"snippet-service", "api", "v1", "configurations", "id"}, "")) + + pattern_SnippetService_InvokeConfiguration_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"snippet-service", "api", "v1", "configurations", "configuration_id"}, "")) + + pattern_SnippetService_GetAppliedConfigurations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"snippet-service", "api", "v1", "configurations", "applied"}, "")) + + pattern_SnippetService_DeleteAppliedConfigurations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"snippet-service", "api", "v1", "configurations", "applied"}, "")) +) + +var ( + forward_SnippetService_CreateCondition_0 = runtime.ForwardResponseMessage + + forward_SnippetService_GetConditions_0 = runtime.ForwardResponseStream + + forward_SnippetService_DeleteConditions_0 = runtime.ForwardResponseMessage + + forward_SnippetService_UpdateCondition_0 = runtime.ForwardResponseMessage + + forward_SnippetService_CreateConfiguration_0 = runtime.ForwardResponseMessage + + forward_SnippetService_GetConfigurations_0 = runtime.ForwardResponseStream + + forward_SnippetService_DeleteConfigurations_0 = runtime.ForwardResponseMessage + + forward_SnippetService_UpdateConfiguration_0 = runtime.ForwardResponseMessage + + forward_SnippetService_InvokeConfiguration_0 = runtime.ForwardResponseStream + + forward_SnippetService_GetAppliedConfigurations_0 = runtime.ForwardResponseStream + + forward_SnippetService_DeleteAppliedConfigurations_0 = runtime.ForwardResponseMessage +) diff --git a/snippet-service/pb/service.proto b/snippet-service/pb/service.proto new file mode 100644 index 000000000..7fab50eac --- /dev/null +++ b/snippet-service/pb/service.proto @@ -0,0 +1,273 @@ +// TODO overit ze pending command sa nezmaze na neexistujucom resource ak sa device pripoji a nepublishne hned resource +// Overit correlation id - ak sa pouziva rovnake napriec viacerymi resourcami + +// scenare +// - Uzivatel vie vytvorit config, automaticka (backend) inkrementacia verzie +// - Uzivatel updatne config, verzia sa inkrementuje, Modal -> chces aplikovat na vsetky uz provisionnute devici? Informovat uzivatela, ze niektore devici mozu byt offline a command moze vyexpirovat. +// - Uzivatel updatne config, verzia sa inkrementuje, informujeme uzivatela ze vsetky pending commandy z predoslej verzie budu cancelnute ako aj dalsie sekvencne updaty resourcov pre predoslu verziu + +syntax = "proto3"; + +package snippetservice.pb; + +import "resource-aggregate/pb/resources.proto"; +import "resource-aggregate/pb/events.proto"; + +import "google/api/annotations.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { + info: { + title: "PLGD Rule Engine API"; + version: "1.0"; + description: "API for configuring rule engine in PLGD"; + contact: { + name: "plgd.dev"; + url: "https://github.com/plgd-dev/hub"; + email: "info@plgd.dev"; + }; + license: { + name: "Apache License 2.0"; + url: "https://github.com/plgd-dev/hub/blob/v2/LICENSE"; + }; + }; + schemes: [HTTPS]; + consumes: ["application/json", "application/protojson"]; + produces: ["application/json", "application/protojson"]; +}; + +option go_package = "github.com/plgd-dev/hub/v2/snippet-service/pb;pb"; + +// TODO: /configurations/123?version=latest +message IDFilter { + string id = 1; + oneof version { + uint64 value = 2; + bool all = 3; + bool latest = 4; + } +} + +message Condition { // driven by resource change event + // Condition ID + string id = 1; // @gotags: bson:"id" + uint64 version = 2; // @gotags: bson:"version" + string name = 3; // @gotags: bson:"name,omitempty" + bool enabled = 4; // @gotags: bson:"enabled,omitempty" + // ID of the configuration to be applied when the condition is satisfied + string configuration_id = 5; // @gotags: bson:"configurationId" + // list of device IDs to which the condition applies + repeated string device_id_filter = 6; // @gotags: bson:"deviceIdFilter,omitempty" + repeated string resource_type_filter = 7; // @gotags: bson:"resourceTypeFilter,omitempty" + // list of resource hrefs to which the condition applies + repeated string resource_href_filter = 8; // @gotags: bson:"resourceHrefFilter,omitempty" + string jq_expression_filter = 9; // @gotags: bson:"jqExpressionFilter,omitempty" + // Token used to update resources in the configuration + string api_access_token = 10; // @gotags: bson:"apiAccessToken,omitempty" + // Condition owner + string owner = 11; // @gotags: bson:"owner" + // Unix timestamp in ns when the condition has been created/updated + int64 timestamp = 12; // @gotags: bson:"timestamp" +} + +message GetConditionsRequest { repeated IDFilter id_filter = 1; } +message DeleteConditionsRequest { repeated IDFilter id_filter = 1; } +message DeleteConditionsResponse { int64 count = 1; } + +message Configuration { + message Resource { + // href of the resource + string href = 1; + // content update of the resource + resourceaggregate.pb.Content content = 2; + // optional update command time to live, 0 is infinite + int64 time_to_live = 3; + } + // Configuration ID + string id = 1; + // Configuration version + uint64 version = 2; + // User-friendly configuration name + string name = 3; // @gotags: bson:"name,omitempty" + // List of resource updates to be applied + repeated Resource resources = 4; + // Configuration owner + string owner = 5; + // Unix timestamp in ns when the configuration has been created/updated + int64 timestamp = 6; // @gotags: bson:"timestamp" +} + +message GetConfigurationsRequest { + repeated IDFilter id_filter = 1; + // Format: {id}/{version}, e.g., "ae424c58-e517-4494-6de7-583536c48213/all" or "ae424c58-e517-4494-6de7-583536c48213/latest" or "ae424c58-e517-4494-6de7-583536c48213/{version}" + repeated string http_id_filter = 2 [ deprecated = true ]; +} +message DeleteConfigurationsRequest { repeated IDFilter id_filter = 1; } +message DeleteConfigurationsResponse { int64 count = 1; } + +message AppliedDeviceConfiguration { //TODO naming + message Resource { + resourceaggregate.pb.ResourceId resource_id = 1; // TODO Jozo href only? + string correlation_id = 2; // Reused from invoke command or generated. Can be used to retrieve corresponding pending command. + enum Status { + QUEUED = 0; + PENDING = 1; + DONE = 2; // If done look to resource_updated even update resource failed for resource aggregate. + TIMEOUT = 3; + }; + Status status = 3; + resourceaggregate.pb.ResourceUpdated resource_updated = 4; + } + string id = 1; + string device_id = 2; + message RelationTo { //TODO naming + string id = 1; + uint64 version = 2; + } + RelationTo configuration_id = 3; + oneof executed_by { + bool on_demand = 4; + RelationTo condition_id = 5; //TODO Naming + } + repeated Resource resources = 6; //TODO naming + string owner = 7; + // Unix timestamp in ns when the applied device configuration has been created/updated + int64 timestamp = 8; // @gotags: bson:"timestamp" +} + +message InvokeConfigurationRequest { + string configuration_id = 1; // applies latest configuration + string device_id = 2; + bool force = 3; // force update even if the configuration has already been applied to device + string correlation_id = 4; // propagated down to the resource update command +} + +message GetAppliedDeviceConfigurationsRequest { //TODO Naming + repeated string id_filter = 1; + repeated IDFilter configuration_id_filter = 2; + repeated string device_id_filter = 3; + repeated IDFilter condition_id_filter = 4; +} + +message DeleteAppliedDeviceConfigurationsRequest { repeated string id_filter = 1; } +message DeleteAppliedDeviceConfigurationsResponse { int64 count = 1; } + +service SnippetService { + rpc CreateCondition(Condition) returns (Condition) { + option (google.api.http) = { + post: "/snippet-service/api/v1/conditions"; + body: "*"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Conditions" ]; + }; + } + + rpc GetConditions(GetConditionsRequest) returns (stream Condition) { + option (google.api.http) = { + get: "/snippet-service/api/v1/conditions"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Conditions" ]; + }; + } + + rpc DeleteConditions(DeleteConditionsRequest) returns (DeleteConditionsResponse) { + option (google.api.http) = { + delete: "/snippet-service/api/v1/conditions"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Conditions" ]; + }; + } + + rpc UpdateCondition(Condition) returns (Condition) { + option (google.api.http) = { + put: "/snippet-service/api/v1/conditions/{id}"; + body: "*"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Conditions" ]; + }; + } + + rpc CreateConfiguration(Configuration) returns (Configuration) { + option (google.api.http) = { + post: "/snippet-service/api/v1/configurations"; + body: "*"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Configurations" ]; + }; + } + + rpc GetConfigurations(GetConfigurationsRequest) returns (stream Configuration) { + option (google.api.http) = { + get: "/snippet-service/api/v1/configurations"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Configurations" ]; + }; + } + + rpc DeleteConfigurations(DeleteConfigurationsRequest) returns (DeleteConfigurationsResponse) { + option (google.api.http) = { + delete: "/snippet-service/api/v1/configurations"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Configurations" ]; + }; + } + + rpc UpdateConfiguration(Configuration) returns (Configuration) { + option (google.api.http) = { + put: "/snippet-service/api/v1/configurations/{id}"; + body: "*"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Configurations" ]; + }; + } + + // streaming process of update configuration to invoker + rpc InvokeConfiguration(InvokeConfigurationRequest) returns (stream AppliedDeviceConfiguration) { + option (google.api.http) = { + post: "/snippet-service/api/v1/configurations/{configuration_id}"; + body: "*"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Configurations" ]; + }; + } + + rpc GetAppliedConfigurations(GetAppliedDeviceConfigurationsRequest) returns (stream AppliedDeviceConfiguration) { + option (google.api.http) = { + get: "/snippet-service/api/v1/configurations/applied"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Configurations" ]; + }; + } + + rpc DeleteAppliedConfigurations(DeleteAppliedDeviceConfigurationsRequest) returns (DeleteAppliedDeviceConfigurationsResponse) { + option (google.api.http) = { + delete: "/snippet-service/api/v1/configurations/applied"; + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: [ "Configurations" ]; + }; + } +} + +// Trigger resource changed event from NATs. +// 1. Check if condition is satisfied. +// 2. Invoke configuration with ID. +// 3. If the configuration has already been applied to the device, skip applying the configuration. +// 4. Create applied configuration with ConfigurationId and for each resource with resourceId, QUEUED status, valid until the theoretical maximum timeout. +// 5. Iterate over resources and +// 6. Set start time, valid until, and status to IN PROGRESS. +// 7. Register NATs for resource changed event. +// 8. If the resource has not been published yet (href, resource types not exist), set status to WAITING RESOURCE and wait for resource changed event until timeout. +// 9. If getting resource content fails or not found, set status to WAITING RESOURCE and wait for resource changed event until timeout. +// 10. If timeout occurs, set status to TIMEOUT. +// 11. Otherwise, set status to IN PROGRESS and update resource with new content. If the update fails, set status to FAIL; otherwise, set status to DONE. diff --git a/snippet-service/pb/service.swagger.json b/snippet-service/pb/service.swagger.json new file mode 100644 index 000000000..e9559eaa2 --- /dev/null +++ b/snippet-service/pb/service.swagger.json @@ -0,0 +1,944 @@ +{ + "swagger": "2.0", + "info": { + "title": "PLGD Rule Engine API", + "description": "API for configuring rule engine in PLGD", + "version": "1.0", + "contact": { + "name": "plgd.dev", + "url": "https://github.com/plgd-dev/hub", + "email": "info@plgd.dev" + }, + "license": { + "name": "Apache License 2.0", + "url": "https://github.com/plgd-dev/hub/blob/v2/LICENSE" + } + }, + "tags": [ + { + "name": "SnippetService" + } + ], + "schemes": [ + "https" + ], + "consumes": [ + "application/json", + "application/protojson" + ], + "produces": [ + "application/json", + "application/protojson" + ], + "paths": { + "/snippet-service/api/v1/conditions": { + "get": { + "operationId": "SnippetService_GetConditions", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/pbCondition" + }, + "error": { + "$ref": "#/definitions/googlerpcStatus" + } + }, + "title": "Stream result of pbCondition" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "tags": [ + "Conditions" + ] + }, + "delete": { + "operationId": "SnippetService_DeleteConditions", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbDeleteConditionsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "tags": [ + "Conditions" + ] + }, + "post": { + "operationId": "SnippetService_CreateCondition", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbCondition" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/pbCondition" + } + } + ], + "tags": [ + "Conditions" + ] + } + }, + "/snippet-service/api/v1/conditions/{id}": { + "put": { + "operationId": "SnippetService_UpdateCondition", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbCondition" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "description": "Condition ID\n\n@gotags: bson:\"id\"", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/SnippetServiceUpdateConditionBody" + } + } + ], + "tags": [ + "Conditions" + ] + } + }, + "/snippet-service/api/v1/configurations": { + "get": { + "operationId": "SnippetService_GetConfigurations", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/pbConfiguration" + }, + "error": { + "$ref": "#/definitions/googlerpcStatus" + } + }, + "title": "Stream result of pbConfiguration" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "httpIdFilter", + "description": "Format: {id}/{version}, e.g., \"ae424c58-e517-4494-6de7-583536c48213/all\" or \"ae424c58-e517-4494-6de7-583536c48213/latest\" or \"ae424c58-e517-4494-6de7-583536c48213/{version}\"", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "tags": [ + "Configurations" + ] + }, + "delete": { + "operationId": "SnippetService_DeleteConfigurations", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbDeleteConfigurationsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "tags": [ + "Configurations" + ] + }, + "post": { + "operationId": "SnippetService_CreateConfiguration", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbConfiguration" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/pbConfiguration" + } + } + ], + "tags": [ + "Configurations" + ] + } + }, + "/snippet-service/api/v1/configurations/applied": { + "get": { + "operationId": "SnippetService_GetAppliedConfigurations", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/pbAppliedDeviceConfiguration" + }, + "error": { + "$ref": "#/definitions/googlerpcStatus" + } + }, + "title": "Stream result of pbAppliedDeviceConfiguration" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "idFilter", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + }, + { + "name": "deviceIdFilter", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "tags": [ + "Configurations" + ] + }, + "delete": { + "operationId": "SnippetService_DeleteAppliedConfigurations", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbDeleteAppliedDeviceConfigurationsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "idFilter", + "in": "query", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "tags": [ + "Configurations" + ] + } + }, + "/snippet-service/api/v1/configurations/{configurationId}": { + "post": { + "summary": "streaming process of update configuration to invoker", + "operationId": "SnippetService_InvokeConfiguration", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/pbAppliedDeviceConfiguration" + }, + "error": { + "$ref": "#/definitions/googlerpcStatus" + } + }, + "title": "Stream result of pbAppliedDeviceConfiguration" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "configurationId", + "description": "applies latest configuration", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/SnippetServiceInvokeConfigurationBody" + } + } + ], + "tags": [ + "Configurations" + ] + } + }, + "/snippet-service/api/v1/configurations/{id}": { + "put": { + "operationId": "SnippetService_UpdateConfiguration", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/pbConfiguration" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googlerpcStatus" + } + } + }, + "parameters": [ + { + "name": "id", + "description": "Configuration ID", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/SnippetServiceUpdateConfigurationBody" + } + } + ], + "tags": [ + "Configurations" + ] + } + } + }, + "definitions": { + "AppliedDeviceConfigurationRelationTo": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "version": { + "type": "string", + "format": "uint64" + } + }, + "title": "TODO naming" + }, + "AppliedDeviceConfigurationResourceStatus": { + "type": "string", + "enum": [ + "QUEUED", + "PENDING", + "DONE", + "TIMEOUT" + ], + "default": "QUEUED", + "description": " - DONE: If done look to resource_updated even update resource failed for resource aggregate." + }, + "SnippetServiceInvokeConfigurationBody": { + "type": "object", + "properties": { + "deviceId": { + "type": "string" + }, + "force": { + "type": "boolean", + "title": "force update even if the configuration has already been applied to device" + }, + "correlationId": { + "type": "string", + "title": "propagated down to the resource update command" + } + } + }, + "SnippetServiceUpdateConditionBody": { + "type": "object", + "properties": { + "version": { + "type": "string", + "format": "uint64", + "title": "@gotags: bson:\"version\"" + }, + "name": { + "type": "string", + "title": "@gotags: bson:\"name,omitempty\"" + }, + "enabled": { + "type": "boolean", + "title": "@gotags: bson:\"enabled,omitempty\"" + }, + "configurationId": { + "type": "string", + "description": "@gotags: bson:\"configurationId\"", + "title": "ID of the configuration to be applied when the condition is satisfied" + }, + "deviceIdFilter": { + "type": "array", + "items": { + "type": "string" + }, + "description": "@gotags: bson:\"deviceIdFilter,omitempty\"", + "title": "list of device IDs to which the condition applies" + }, + "resourceTypeFilter": { + "type": "array", + "items": { + "type": "string" + }, + "title": "@gotags: bson:\"resourceTypeFilter,omitempty\"" + }, + "resourceHrefFilter": { + "type": "array", + "items": { + "type": "string" + }, + "description": "@gotags: bson:\"resourceHrefFilter,omitempty\"", + "title": "list of resource hrefs to which the condition applies" + }, + "jqExpressionFilter": { + "type": "string", + "title": "@gotags: bson:\"jqExpressionFilter,omitempty\"" + }, + "apiAccessToken": { + "type": "string", + "description": "@gotags: bson:\"apiAccessToken,omitempty\"", + "title": "Token used to update resources in the configuration" + }, + "owner": { + "type": "string", + "description": "@gotags: bson:\"owner\"", + "title": "Condition owner" + }, + "timestamp": { + "type": "string", + "format": "int64", + "description": "@gotags: bson:\"timestamp\"", + "title": "Unix timestamp in ns when the condition has been created/updated" + } + }, + "title": "driven by resource change event" + }, + "SnippetServiceUpdateConfigurationBody": { + "type": "object", + "properties": { + "version": { + "type": "string", + "format": "uint64", + "title": "Configuration version" + }, + "name": { + "type": "string", + "description": "@gotags: bson:\"name,omitempty\"", + "title": "User-friendly configuration name" + }, + "resources": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/pbConfigurationResource" + }, + "title": "List of resource updates to be applied" + }, + "owner": { + "type": "string", + "title": "Configuration owner" + }, + "timestamp": { + "type": "string", + "format": "int64", + "description": "@gotags: bson:\"timestamp\"", + "title": "Unix timestamp in ns when the configuration has been created/updated" + } + } + }, + "googlerpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "pbAppliedDeviceConfiguration": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "deviceId": { + "type": "string" + }, + "configurationId": { + "$ref": "#/definitions/AppliedDeviceConfigurationRelationTo" + }, + "onDemand": { + "type": "boolean" + }, + "conditionId": { + "$ref": "#/definitions/AppliedDeviceConfigurationRelationTo", + "title": "TODO Naming" + }, + "resources": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/pbAppliedDeviceConfigurationResource" + }, + "title": "TODO naming" + }, + "owner": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "int64", + "description": "@gotags: bson:\"timestamp\"", + "title": "Unix timestamp in ns when the applied device configuration has been created/updated" + } + }, + "title": "TODO naming" + }, + "pbAppliedDeviceConfigurationResource": { + "type": "object", + "properties": { + "resourceId": { + "$ref": "#/definitions/pbResourceId", + "title": "TODO Jozo href only?" + }, + "correlationId": { + "type": "string", + "description": "Reused from invoke command or generated. Can be used to retrieve corresponding pending command." + }, + "status": { + "$ref": "#/definitions/AppliedDeviceConfigurationResourceStatus" + }, + "resourceUpdated": { + "$ref": "#/definitions/pbResourceUpdated" + } + } + }, + "pbAuditContext": { + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "correlationId": { + "type": "string" + }, + "owner": { + "type": "string" + } + } + }, + "pbCondition": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "@gotags: bson:\"id\"", + "title": "Condition ID" + }, + "version": { + "type": "string", + "format": "uint64", + "title": "@gotags: bson:\"version\"" + }, + "name": { + "type": "string", + "title": "@gotags: bson:\"name,omitempty\"" + }, + "enabled": { + "type": "boolean", + "title": "@gotags: bson:\"enabled,omitempty\"" + }, + "configurationId": { + "type": "string", + "description": "@gotags: bson:\"configurationId\"", + "title": "ID of the configuration to be applied when the condition is satisfied" + }, + "deviceIdFilter": { + "type": "array", + "items": { + "type": "string" + }, + "description": "@gotags: bson:\"deviceIdFilter,omitempty\"", + "title": "list of device IDs to which the condition applies" + }, + "resourceTypeFilter": { + "type": "array", + "items": { + "type": "string" + }, + "title": "@gotags: bson:\"resourceTypeFilter,omitempty\"" + }, + "resourceHrefFilter": { + "type": "array", + "items": { + "type": "string" + }, + "description": "@gotags: bson:\"resourceHrefFilter,omitempty\"", + "title": "list of resource hrefs to which the condition applies" + }, + "jqExpressionFilter": { + "type": "string", + "title": "@gotags: bson:\"jqExpressionFilter,omitempty\"" + }, + "apiAccessToken": { + "type": "string", + "description": "@gotags: bson:\"apiAccessToken,omitempty\"", + "title": "Token used to update resources in the configuration" + }, + "owner": { + "type": "string", + "description": "@gotags: bson:\"owner\"", + "title": "Condition owner" + }, + "timestamp": { + "type": "string", + "format": "int64", + "description": "@gotags: bson:\"timestamp\"", + "title": "Unix timestamp in ns when the condition has been created/updated" + } + }, + "title": "driven by resource change event" + }, + "pbConfiguration": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "Configuration ID" + }, + "version": { + "type": "string", + "format": "uint64", + "title": "Configuration version" + }, + "name": { + "type": "string", + "description": "@gotags: bson:\"name,omitempty\"", + "title": "User-friendly configuration name" + }, + "resources": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/pbConfigurationResource" + }, + "title": "List of resource updates to be applied" + }, + "owner": { + "type": "string", + "title": "Configuration owner" + }, + "timestamp": { + "type": "string", + "format": "int64", + "description": "@gotags: bson:\"timestamp\"", + "title": "Unix timestamp in ns when the configuration has been created/updated" + } + } + }, + "pbConfigurationResource": { + "type": "object", + "properties": { + "href": { + "type": "string", + "title": "href of the resource" + }, + "content": { + "$ref": "#/definitions/pbContent", + "title": "content update of the resource" + }, + "timeToLive": { + "type": "string", + "format": "int64", + "title": "optional update command time to live, 0 is infinite" + } + } + }, + "pbContent": { + "type": "object", + "properties": { + "data": { + "type": "string", + "format": "byte" + }, + "contentType": { + "type": "string" + }, + "coapContentFormat": { + "type": "integer", + "format": "int32", + "title": "-1 means content-format was not provided" + } + } + }, + "pbDeleteAppliedDeviceConfigurationsResponse": { + "type": "object", + "properties": { + "count": { + "type": "string", + "format": "int64" + } + } + }, + "pbDeleteConditionsResponse": { + "type": "object", + "properties": { + "count": { + "type": "string", + "format": "int64" + } + } + }, + "pbDeleteConfigurationsResponse": { + "type": "object", + "properties": { + "count": { + "type": "string", + "format": "int64" + } + } + }, + "pbEventMetadata": { + "type": "object", + "properties": { + "version": { + "type": "string", + "format": "uint64" + }, + "timestamp": { + "type": "string", + "format": "int64" + }, + "connectionId": { + "type": "string" + }, + "sequence": { + "type": "string", + "format": "uint64", + "title": "sequence number within the same connection_id; the ResourceChanged event uses the value to skip old events, other event types might not fill the value" + }, + "hubId": { + "type": "string", + "title": "the hub which sent the event" + } + } + }, + "pbIDFilter": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "value": { + "type": "string", + "format": "uint64" + }, + "all": { + "type": "boolean" + }, + "latest": { + "type": "boolean" + } + }, + "title": "TODO: /configurations/123?version=latest" + }, + "pbResourceId": { + "type": "object", + "properties": { + "deviceId": { + "type": "string" + }, + "href": { + "type": "string" + } + } + }, + "pbResourceUpdated": { + "type": "object", + "properties": { + "resourceId": { + "$ref": "#/definitions/pbResourceId" + }, + "status": { + "$ref": "#/definitions/resourceaggregatepbStatus" + }, + "content": { + "$ref": "#/definitions/pbContent" + }, + "auditContext": { + "$ref": "#/definitions/pbAuditContext" + }, + "eventMetadata": { + "$ref": "#/definitions/pbEventMetadata" + }, + "resourceTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "openTelemetryCarrier": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "title": "Open telemetry data propagated to asynchronous events" + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "resourceaggregatepbStatus": { + "type": "string", + "enum": [ + "UNKNOWN", + "OK", + "BAD_REQUEST", + "UNAUTHORIZED", + "FORBIDDEN", + "NOT_FOUND", + "UNAVAILABLE", + "NOT_IMPLEMENTED", + "ACCEPTED", + "ERROR", + "METHOD_NOT_ALLOWED", + "CREATED", + "CANCELED", + "NOT_MODIFIED" + ], + "default": "UNKNOWN", + "description": " - CANCELED: Canceled indicates the operation was canceled (typically by the user).\n - NOT_MODIFIED: Valid indicates the content hasn't changed. (provided etag in GET request is same as the resource etag)." + } + } +} diff --git a/snippet-service/pb/service_grpc.pb.go b/snippet-service/pb/service_grpc.pb.go new file mode 100644 index 000000000..a31219a27 --- /dev/null +++ b/snippet-service/pb/service_grpc.pb.go @@ -0,0 +1,598 @@ +// TODO overit ze pending command sa nezmaze na neexistujucom resource ak sa device pripoji a nepublishne hned resource +// Overit correlation id - ak sa pouziva rovnake napriec viacerymi resourcami + +// scenare +// - Uzivatel vie vytvorit config, automaticka (backend) inkrementacia verzie +// - Uzivatel updatne config, verzia sa inkrementuje, Modal -> chces aplikovat na vsetky uz provisionnute devici? Informovat uzivatela, ze niektore devici mozu byt offline a command moze vyexpirovat. +// - Uzivatel updatne config, verzia sa inkrementuje, informujeme uzivatela ze vsetky pending commandy z predoslej verzie budu cancelnute ako aj dalsie sekvencne updaty resourcov pre predoslu verziu + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v5.26.1 +// source: snippet-service/pb/service.proto + +package pb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + SnippetService_CreateCondition_FullMethodName = "/snippetservice.pb.SnippetService/CreateCondition" + SnippetService_GetConditions_FullMethodName = "/snippetservice.pb.SnippetService/GetConditions" + SnippetService_DeleteConditions_FullMethodName = "/snippetservice.pb.SnippetService/DeleteConditions" + SnippetService_UpdateCondition_FullMethodName = "/snippetservice.pb.SnippetService/UpdateCondition" + SnippetService_CreateConfiguration_FullMethodName = "/snippetservice.pb.SnippetService/CreateConfiguration" + SnippetService_GetConfigurations_FullMethodName = "/snippetservice.pb.SnippetService/GetConfigurations" + SnippetService_DeleteConfigurations_FullMethodName = "/snippetservice.pb.SnippetService/DeleteConfigurations" + SnippetService_UpdateConfiguration_FullMethodName = "/snippetservice.pb.SnippetService/UpdateConfiguration" + SnippetService_InvokeConfiguration_FullMethodName = "/snippetservice.pb.SnippetService/InvokeConfiguration" + SnippetService_GetAppliedConfigurations_FullMethodName = "/snippetservice.pb.SnippetService/GetAppliedConfigurations" + SnippetService_DeleteAppliedConfigurations_FullMethodName = "/snippetservice.pb.SnippetService/DeleteAppliedConfigurations" +) + +// SnippetServiceClient is the client API for SnippetService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type SnippetServiceClient interface { + CreateCondition(ctx context.Context, in *Condition, opts ...grpc.CallOption) (*Condition, error) + GetConditions(ctx context.Context, in *GetConditionsRequest, opts ...grpc.CallOption) (SnippetService_GetConditionsClient, error) + DeleteConditions(ctx context.Context, in *DeleteConditionsRequest, opts ...grpc.CallOption) (*DeleteConditionsResponse, error) + UpdateCondition(ctx context.Context, in *Condition, opts ...grpc.CallOption) (*Condition, error) + CreateConfiguration(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) + GetConfigurations(ctx context.Context, in *GetConfigurationsRequest, opts ...grpc.CallOption) (SnippetService_GetConfigurationsClient, error) + DeleteConfigurations(ctx context.Context, in *DeleteConfigurationsRequest, opts ...grpc.CallOption) (*DeleteConfigurationsResponse, error) + UpdateConfiguration(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) + // streaming process of update configuration to invoker + InvokeConfiguration(ctx context.Context, in *InvokeConfigurationRequest, opts ...grpc.CallOption) (SnippetService_InvokeConfigurationClient, error) + GetAppliedConfigurations(ctx context.Context, in *GetAppliedDeviceConfigurationsRequest, opts ...grpc.CallOption) (SnippetService_GetAppliedConfigurationsClient, error) + DeleteAppliedConfigurations(ctx context.Context, in *DeleteAppliedDeviceConfigurationsRequest, opts ...grpc.CallOption) (*DeleteAppliedDeviceConfigurationsResponse, error) +} + +type snippetServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewSnippetServiceClient(cc grpc.ClientConnInterface) SnippetServiceClient { + return &snippetServiceClient{cc} +} + +func (c *snippetServiceClient) CreateCondition(ctx context.Context, in *Condition, opts ...grpc.CallOption) (*Condition, error) { + out := new(Condition) + err := c.cc.Invoke(ctx, SnippetService_CreateCondition_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *snippetServiceClient) GetConditions(ctx context.Context, in *GetConditionsRequest, opts ...grpc.CallOption) (SnippetService_GetConditionsClient, error) { + stream, err := c.cc.NewStream(ctx, &SnippetService_ServiceDesc.Streams[0], SnippetService_GetConditions_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &snippetServiceGetConditionsClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type SnippetService_GetConditionsClient interface { + Recv() (*Condition, error) + grpc.ClientStream +} + +type snippetServiceGetConditionsClient struct { + grpc.ClientStream +} + +func (x *snippetServiceGetConditionsClient) Recv() (*Condition, error) { + m := new(Condition) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *snippetServiceClient) DeleteConditions(ctx context.Context, in *DeleteConditionsRequest, opts ...grpc.CallOption) (*DeleteConditionsResponse, error) { + out := new(DeleteConditionsResponse) + err := c.cc.Invoke(ctx, SnippetService_DeleteConditions_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *snippetServiceClient) UpdateCondition(ctx context.Context, in *Condition, opts ...grpc.CallOption) (*Condition, error) { + out := new(Condition) + err := c.cc.Invoke(ctx, SnippetService_UpdateCondition_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *snippetServiceClient) CreateConfiguration(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) { + out := new(Configuration) + err := c.cc.Invoke(ctx, SnippetService_CreateConfiguration_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *snippetServiceClient) GetConfigurations(ctx context.Context, in *GetConfigurationsRequest, opts ...grpc.CallOption) (SnippetService_GetConfigurationsClient, error) { + stream, err := c.cc.NewStream(ctx, &SnippetService_ServiceDesc.Streams[1], SnippetService_GetConfigurations_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &snippetServiceGetConfigurationsClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type SnippetService_GetConfigurationsClient interface { + Recv() (*Configuration, error) + grpc.ClientStream +} + +type snippetServiceGetConfigurationsClient struct { + grpc.ClientStream +} + +func (x *snippetServiceGetConfigurationsClient) Recv() (*Configuration, error) { + m := new(Configuration) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *snippetServiceClient) DeleteConfigurations(ctx context.Context, in *DeleteConfigurationsRequest, opts ...grpc.CallOption) (*DeleteConfigurationsResponse, error) { + out := new(DeleteConfigurationsResponse) + err := c.cc.Invoke(ctx, SnippetService_DeleteConfigurations_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *snippetServiceClient) UpdateConfiguration(ctx context.Context, in *Configuration, opts ...grpc.CallOption) (*Configuration, error) { + out := new(Configuration) + err := c.cc.Invoke(ctx, SnippetService_UpdateConfiguration_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *snippetServiceClient) InvokeConfiguration(ctx context.Context, in *InvokeConfigurationRequest, opts ...grpc.CallOption) (SnippetService_InvokeConfigurationClient, error) { + stream, err := c.cc.NewStream(ctx, &SnippetService_ServiceDesc.Streams[2], SnippetService_InvokeConfiguration_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &snippetServiceInvokeConfigurationClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type SnippetService_InvokeConfigurationClient interface { + Recv() (*AppliedDeviceConfiguration, error) + grpc.ClientStream +} + +type snippetServiceInvokeConfigurationClient struct { + grpc.ClientStream +} + +func (x *snippetServiceInvokeConfigurationClient) Recv() (*AppliedDeviceConfiguration, error) { + m := new(AppliedDeviceConfiguration) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *snippetServiceClient) GetAppliedConfigurations(ctx context.Context, in *GetAppliedDeviceConfigurationsRequest, opts ...grpc.CallOption) (SnippetService_GetAppliedConfigurationsClient, error) { + stream, err := c.cc.NewStream(ctx, &SnippetService_ServiceDesc.Streams[3], SnippetService_GetAppliedConfigurations_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &snippetServiceGetAppliedConfigurationsClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type SnippetService_GetAppliedConfigurationsClient interface { + Recv() (*AppliedDeviceConfiguration, error) + grpc.ClientStream +} + +type snippetServiceGetAppliedConfigurationsClient struct { + grpc.ClientStream +} + +func (x *snippetServiceGetAppliedConfigurationsClient) Recv() (*AppliedDeviceConfiguration, error) { + m := new(AppliedDeviceConfiguration) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *snippetServiceClient) DeleteAppliedConfigurations(ctx context.Context, in *DeleteAppliedDeviceConfigurationsRequest, opts ...grpc.CallOption) (*DeleteAppliedDeviceConfigurationsResponse, error) { + out := new(DeleteAppliedDeviceConfigurationsResponse) + err := c.cc.Invoke(ctx, SnippetService_DeleteAppliedConfigurations_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SnippetServiceServer is the server API for SnippetService service. +// All implementations must embed UnimplementedSnippetServiceServer +// for forward compatibility +type SnippetServiceServer interface { + CreateCondition(context.Context, *Condition) (*Condition, error) + GetConditions(*GetConditionsRequest, SnippetService_GetConditionsServer) error + DeleteConditions(context.Context, *DeleteConditionsRequest) (*DeleteConditionsResponse, error) + UpdateCondition(context.Context, *Condition) (*Condition, error) + CreateConfiguration(context.Context, *Configuration) (*Configuration, error) + GetConfigurations(*GetConfigurationsRequest, SnippetService_GetConfigurationsServer) error + DeleteConfigurations(context.Context, *DeleteConfigurationsRequest) (*DeleteConfigurationsResponse, error) + UpdateConfiguration(context.Context, *Configuration) (*Configuration, error) + // streaming process of update configuration to invoker + InvokeConfiguration(*InvokeConfigurationRequest, SnippetService_InvokeConfigurationServer) error + GetAppliedConfigurations(*GetAppliedDeviceConfigurationsRequest, SnippetService_GetAppliedConfigurationsServer) error + DeleteAppliedConfigurations(context.Context, *DeleteAppliedDeviceConfigurationsRequest) (*DeleteAppliedDeviceConfigurationsResponse, error) + mustEmbedUnimplementedSnippetServiceServer() +} + +// UnimplementedSnippetServiceServer must be embedded to have forward compatible implementations. +type UnimplementedSnippetServiceServer struct { +} + +func (UnimplementedSnippetServiceServer) CreateCondition(context.Context, *Condition) (*Condition, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateCondition not implemented") +} +func (UnimplementedSnippetServiceServer) GetConditions(*GetConditionsRequest, SnippetService_GetConditionsServer) error { + return status.Errorf(codes.Unimplemented, "method GetConditions not implemented") +} +func (UnimplementedSnippetServiceServer) DeleteConditions(context.Context, *DeleteConditionsRequest) (*DeleteConditionsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteConditions not implemented") +} +func (UnimplementedSnippetServiceServer) UpdateCondition(context.Context, *Condition) (*Condition, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateCondition not implemented") +} +func (UnimplementedSnippetServiceServer) CreateConfiguration(context.Context, *Configuration) (*Configuration, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateConfiguration not implemented") +} +func (UnimplementedSnippetServiceServer) GetConfigurations(*GetConfigurationsRequest, SnippetService_GetConfigurationsServer) error { + return status.Errorf(codes.Unimplemented, "method GetConfigurations not implemented") +} +func (UnimplementedSnippetServiceServer) DeleteConfigurations(context.Context, *DeleteConfigurationsRequest) (*DeleteConfigurationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteConfigurations not implemented") +} +func (UnimplementedSnippetServiceServer) UpdateConfiguration(context.Context, *Configuration) (*Configuration, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateConfiguration not implemented") +} +func (UnimplementedSnippetServiceServer) InvokeConfiguration(*InvokeConfigurationRequest, SnippetService_InvokeConfigurationServer) error { + return status.Errorf(codes.Unimplemented, "method InvokeConfiguration not implemented") +} +func (UnimplementedSnippetServiceServer) GetAppliedConfigurations(*GetAppliedDeviceConfigurationsRequest, SnippetService_GetAppliedConfigurationsServer) error { + return status.Errorf(codes.Unimplemented, "method GetAppliedConfigurations not implemented") +} +func (UnimplementedSnippetServiceServer) DeleteAppliedConfigurations(context.Context, *DeleteAppliedDeviceConfigurationsRequest) (*DeleteAppliedDeviceConfigurationsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteAppliedConfigurations not implemented") +} +func (UnimplementedSnippetServiceServer) mustEmbedUnimplementedSnippetServiceServer() {} + +// UnsafeSnippetServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SnippetServiceServer will +// result in compilation errors. +type UnsafeSnippetServiceServer interface { + mustEmbedUnimplementedSnippetServiceServer() +} + +func RegisterSnippetServiceServer(s grpc.ServiceRegistrar, srv SnippetServiceServer) { + s.RegisterService(&SnippetService_ServiceDesc, srv) +} + +func _SnippetService_CreateCondition_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Condition) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SnippetServiceServer).CreateCondition(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SnippetService_CreateCondition_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SnippetServiceServer).CreateCondition(ctx, req.(*Condition)) + } + return interceptor(ctx, in, info, handler) +} + +func _SnippetService_GetConditions_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetConditionsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(SnippetServiceServer).GetConditions(m, &snippetServiceGetConditionsServer{stream}) +} + +type SnippetService_GetConditionsServer interface { + Send(*Condition) error + grpc.ServerStream +} + +type snippetServiceGetConditionsServer struct { + grpc.ServerStream +} + +func (x *snippetServiceGetConditionsServer) Send(m *Condition) error { + return x.ServerStream.SendMsg(m) +} + +func _SnippetService_DeleteConditions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteConditionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SnippetServiceServer).DeleteConditions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SnippetService_DeleteConditions_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SnippetServiceServer).DeleteConditions(ctx, req.(*DeleteConditionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SnippetService_UpdateCondition_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Condition) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SnippetServiceServer).UpdateCondition(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SnippetService_UpdateCondition_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SnippetServiceServer).UpdateCondition(ctx, req.(*Condition)) + } + return interceptor(ctx, in, info, handler) +} + +func _SnippetService_CreateConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Configuration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SnippetServiceServer).CreateConfiguration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SnippetService_CreateConfiguration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SnippetServiceServer).CreateConfiguration(ctx, req.(*Configuration)) + } + return interceptor(ctx, in, info, handler) +} + +func _SnippetService_GetConfigurations_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetConfigurationsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(SnippetServiceServer).GetConfigurations(m, &snippetServiceGetConfigurationsServer{stream}) +} + +type SnippetService_GetConfigurationsServer interface { + Send(*Configuration) error + grpc.ServerStream +} + +type snippetServiceGetConfigurationsServer struct { + grpc.ServerStream +} + +func (x *snippetServiceGetConfigurationsServer) Send(m *Configuration) error { + return x.ServerStream.SendMsg(m) +} + +func _SnippetService_DeleteConfigurations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteConfigurationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SnippetServiceServer).DeleteConfigurations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SnippetService_DeleteConfigurations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SnippetServiceServer).DeleteConfigurations(ctx, req.(*DeleteConfigurationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SnippetService_UpdateConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Configuration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SnippetServiceServer).UpdateConfiguration(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SnippetService_UpdateConfiguration_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SnippetServiceServer).UpdateConfiguration(ctx, req.(*Configuration)) + } + return interceptor(ctx, in, info, handler) +} + +func _SnippetService_InvokeConfiguration_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(InvokeConfigurationRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(SnippetServiceServer).InvokeConfiguration(m, &snippetServiceInvokeConfigurationServer{stream}) +} + +type SnippetService_InvokeConfigurationServer interface { + Send(*AppliedDeviceConfiguration) error + grpc.ServerStream +} + +type snippetServiceInvokeConfigurationServer struct { + grpc.ServerStream +} + +func (x *snippetServiceInvokeConfigurationServer) Send(m *AppliedDeviceConfiguration) error { + return x.ServerStream.SendMsg(m) +} + +func _SnippetService_GetAppliedConfigurations_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetAppliedDeviceConfigurationsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(SnippetServiceServer).GetAppliedConfigurations(m, &snippetServiceGetAppliedConfigurationsServer{stream}) +} + +type SnippetService_GetAppliedConfigurationsServer interface { + Send(*AppliedDeviceConfiguration) error + grpc.ServerStream +} + +type snippetServiceGetAppliedConfigurationsServer struct { + grpc.ServerStream +} + +func (x *snippetServiceGetAppliedConfigurationsServer) Send(m *AppliedDeviceConfiguration) error { + return x.ServerStream.SendMsg(m) +} + +func _SnippetService_DeleteAppliedConfigurations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAppliedDeviceConfigurationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SnippetServiceServer).DeleteAppliedConfigurations(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SnippetService_DeleteAppliedConfigurations_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SnippetServiceServer).DeleteAppliedConfigurations(ctx, req.(*DeleteAppliedDeviceConfigurationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// SnippetService_ServiceDesc is the grpc.ServiceDesc for SnippetService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var SnippetService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "snippetservice.pb.SnippetService", + HandlerType: (*SnippetServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateCondition", + Handler: _SnippetService_CreateCondition_Handler, + }, + { + MethodName: "DeleteConditions", + Handler: _SnippetService_DeleteConditions_Handler, + }, + { + MethodName: "UpdateCondition", + Handler: _SnippetService_UpdateCondition_Handler, + }, + { + MethodName: "CreateConfiguration", + Handler: _SnippetService_CreateConfiguration_Handler, + }, + { + MethodName: "DeleteConfigurations", + Handler: _SnippetService_DeleteConfigurations_Handler, + }, + { + MethodName: "UpdateConfiguration", + Handler: _SnippetService_UpdateConfiguration_Handler, + }, + { + MethodName: "DeleteAppliedConfigurations", + Handler: _SnippetService_DeleteAppliedConfigurations_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "GetConditions", + Handler: _SnippetService_GetConditions_Handler, + ServerStreams: true, + }, + { + StreamName: "GetConfigurations", + Handler: _SnippetService_GetConfigurations_Handler, + ServerStreams: true, + }, + { + StreamName: "InvokeConfiguration", + Handler: _SnippetService_InvokeConfiguration_Handler, + ServerStreams: true, + }, + { + StreamName: "GetAppliedConfigurations", + Handler: _SnippetService_GetAppliedConfigurations_Handler, + ServerStreams: true, + }, + }, + Metadata: "snippet-service/pb/service.proto", +} diff --git a/snippet-service/service/config.go b/snippet-service/service/config.go new file mode 100644 index 000000000..99f8c6755 --- /dev/null +++ b/snippet-service/service/config.go @@ -0,0 +1,120 @@ +package service + +import ( + "fmt" + "net" + "time" + + "github.com/go-co-op/gocron/v2" + "github.com/google/uuid" + "github.com/plgd-dev/hub/v2/pkg/config" + "github.com/plgd-dev/hub/v2/pkg/log" + httpServer "github.com/plgd-dev/hub/v2/pkg/net/http/server" + otelClient "github.com/plgd-dev/hub/v2/pkg/opentelemetry/collector/client" + grpcService "github.com/plgd-dev/hub/v2/snippet-service/service/grpc" + storeConfig "github.com/plgd-dev/hub/v2/snippet-service/store/config" +) + +type Config struct { + HubID string `yaml:"hubID" json:"hubId"` + Log log.Config `yaml:"log" json:"log"` + APIs APIsConfig `yaml:"apis" json:"apis"` + Clients ClientsConfig `yaml:"clients" json:"clients"` +} + +func (c *Config) Validate() error { + if err := c.Log.Validate(); err != nil { + return fmt.Errorf("log.%w", err) + } + if err := c.APIs.Validate(); err != nil { + return fmt.Errorf("apis.%w", err) + } + if err := c.Clients.Validate(); err != nil { + return fmt.Errorf("clients.%w", err) + } + if _, err := uuid.Parse(c.HubID); err != nil { + return fmt.Errorf("hubID('%v') - %w", c.HubID, err) + } + + return nil +} + +// Config represent application configuration +type APIsConfig struct { + GRPC grpcService.Config `yaml:"grpc" json:"grpc"` + HTTP HTTPConfig `yaml:"http" json:"http"` +} + +func (c *APIsConfig) Validate() error { + if err := c.GRPC.Validate(); err != nil { + return fmt.Errorf("grpc.%w", err) + } + if err := c.HTTP.Validate(); err != nil { + return fmt.Errorf("http.%w", err) + } + return nil +} + +type HTTPConfig struct { + Addr string `yaml:"address" json:"address"` + Server httpServer.Config `yaml:",inline" json:",inline"` +} + +func (c *HTTPConfig) Validate() error { + if _, err := net.ResolveTCPAddr("tcp", c.Addr); err != nil { + return fmt.Errorf("address('%v') - %w", c.Addr, err) + } + return nil +} + +type StorageConfig struct { + Embedded storeConfig.Config `yaml:",inline" json:",inline"` + ExtendCronParserBySeconds bool `yaml:"-" json:"-"` + CleanUpRecords string `yaml:"cleanUpRecords" json:"cleanUpRecords"` +} + +func (c *StorageConfig) Validate() error { + if err := c.Embedded.Validate(); err != nil { + return err + } + if c.CleanUpRecords == "" { + return nil + } + s, err := gocron.NewScheduler(gocron.WithLocation(time.Local)) //nolint:gosmopolitan + if err != nil { + return fmt.Errorf("cannot create cron job: %w", err) + } + defer func() { + if errS := s.Shutdown(); errS != nil { + log.Errorf("failed to shutdown cron job: %w", errS) + } + }() + _, err = s.NewJob(gocron.CronJob(c.CleanUpRecords, c.ExtendCronParserBySeconds), + gocron.NewTask(func() { + // do nothing + })) + if err != nil { + return fmt.Errorf("cleanUpRecords('%v') - %w", c.CleanUpRecords, err) + } + return nil +} + +type ClientsConfig struct { + Storage StorageConfig `yaml:"storage" json:"storage"` + OpenTelemetryCollector otelClient.Config `yaml:"openTelemetryCollector" json:"openTelemetryCollector"` +} + +func (c *ClientsConfig) Validate() error { + if err := c.Storage.Validate(); err != nil { + return fmt.Errorf("storage.%w", err) + } + if err := c.OpenTelemetryCollector.Validate(); err != nil { + return fmt.Errorf("openTelemetryCollector.%w", err) + } + return nil +} + +// String return string representation of Config +func (c Config) String() string { + return config.ToString(c) +} diff --git a/snippet-service/service/grpc/config.go b/snippet-service/service/grpc/config.go new file mode 100644 index 000000000..655e8170e --- /dev/null +++ b/snippet-service/service/grpc/config.go @@ -0,0 +1,7 @@ +package grpc + +import ( + "github.com/plgd-dev/hub/v2/pkg/net/grpc/server" +) + +type Config = server.Config diff --git a/snippet-service/service/grpc/server.go b/snippet-service/service/grpc/server.go new file mode 100644 index 000000000..aff5ba003 --- /dev/null +++ b/snippet-service/service/grpc/server.go @@ -0,0 +1,121 @@ +package grpc + +import ( + "context" + "errors" + + "github.com/plgd-dev/hub/v2/pkg/log" + "github.com/plgd-dev/hub/v2/pkg/net/grpc" + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/plgd-dev/hub/v2/snippet-service/store" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// SnippetServiceServer handles incoming requests. +type SnippetServiceServer struct { + pb.UnimplementedSnippetServiceServer + + logger log.Logger + ownerClaim string + store store.Store + hubID string +} + +func NewSnippetServiceServer(ownerClaim string, hubID string, store store.Store, logger log.Logger) (*SnippetServiceServer, error) { + s := &SnippetServiceServer{ + logger: logger, + ownerClaim: ownerClaim, + store: store, + hubID: hubID, + } + + return s, nil +} + +func (s *SnippetServiceServer) checkOwner(ctx context.Context, owner string) (string, error) { + ownerFromToken, err := grpc.OwnerFromTokenMD(ctx, s.ownerClaim) + if err != nil { + return "", err + } + if owner != "" && ownerFromToken != owner { + return "", errors.New("owner mismatch") + } + return ownerFromToken, nil +} + +func (s *SnippetServiceServer) CreateConfiguration(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) { + owner, err := s.checkOwner(ctx, conf.GetOwner()) + if err != nil { + return nil, s.logger.LogAndReturnError(status.Errorf(codes.PermissionDenied, "cannot create configuration: %v", err)) + } + + conf.Owner = owner + c, err := s.store.CreateConfiguration(ctx, conf) + if err != nil { + return nil, s.logger.LogAndReturnError(status.Errorf(codes.Internal, "cannot create configuration: %v", err)) + } + return c, nil +} + +func (s *SnippetServiceServer) UpdateConfiguration(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) { + owner, err := s.checkOwner(ctx, conf.GetOwner()) + if err != nil { + return nil, s.logger.LogAndReturnError(status.Errorf(codes.PermissionDenied, "cannot update configuration: %v", err)) + } + + conf.Owner = owner + c, err := s.store.UpdateConfiguration(ctx, conf) + if err != nil { + return nil, s.logger.LogAndReturnError(status.Errorf(codes.Internal, "cannot update configuration: %v", err)) + } + return c, nil +} + +func (s *SnippetServiceServer) GetConfigurations(req *pb.GetConfigurationsRequest, srv pb.SnippetService_GetConfigurationsServer) error { + owner, err := s.checkOwner(srv.Context(), "") + if err != nil { + return s.logger.LogAndReturnError(status.Errorf(codes.PermissionDenied, "cannot get configurations: %v", err)) + } + + err = s.store.GetConfigurations(srv.Context(), owner, req, func(ctx context.Context, iter store.Iterator[store.Configuration]) error { + storedCfg := store.Configuration{} + for iter.Next(ctx, &storedCfg) { + for _, version := range storedCfg.Versions { + errS := srv.Send(&pb.Configuration{ + Id: storedCfg.Id, + Owner: storedCfg.Owner, + Name: storedCfg.Name, + Version: version.Version, + Resources: version.Resources, + }) + if errS != nil { + return errS + } + } + } + return nil + }) + if err != nil { + return s.logger.LogAndReturnError(status.Errorf(codes.Internal, "cannot get configurations: %v", err)) + } + return nil +} + +func (s *SnippetServiceServer) DeleteConfigurations(ctx context.Context, req *pb.DeleteConfigurationsRequest) (*pb.DeleteConfigurationsResponse, error) { + owner, err := s.checkOwner(ctx, "") + if err != nil { + return nil, s.logger.LogAndReturnError(status.Errorf(codes.PermissionDenied, "cannot delete configurations: %v", err)) + } + count, err := s.store.DeleteConfigurations(ctx, owner, req) + if err != nil { + return nil, s.logger.LogAndReturnError(status.Errorf(codes.Internal, "cannot delete configurations: %v", err)) + } + return &pb.DeleteConfigurationsResponse{ + Count: count, + }, nil +} + +func (s *SnippetServiceServer) Close(ctx context.Context) error { + return s.store.Close(ctx) +} diff --git a/snippet-service/service/grpc/service.go b/snippet-service/service/grpc/service.go new file mode 100644 index 000000000..0ee12344e --- /dev/null +++ b/snippet-service/service/grpc/service.go @@ -0,0 +1,35 @@ +package grpc + +import ( + "fmt" + + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + "github.com/plgd-dev/hub/v2/pkg/net/grpc/server" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "go.opentelemetry.io/otel/trace" +) + +type Service struct { + *server.Server +} + +func New(config Config, snippetServiceServer *SnippetServiceServer, validator *validator.Validator, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Service, error) { + opts, err := server.MakeDefaultOptions(server.NewAuth(validator), logger, tracerProvider) + if err != nil { + return nil, fmt.Errorf("cannot create grpc server options: %w", err) + } + server, err := server.New(config, fileWatcher, logger, opts...) + if err != nil { + return nil, err + } + pb.RegisterSnippetServiceServer(server.Server, snippetServiceServer) + + // SnippetService needs to stop gracefully to ensure that all commands are processed. + server.SetGracefulStop(true) + + return &Service{ + Server: server, + }, nil +} diff --git a/snippet-service/service/http/config.go b/snippet-service/service/http/config.go new file mode 100644 index 000000000..5ae74d064 --- /dev/null +++ b/snippet-service/service/http/config.go @@ -0,0 +1,13 @@ +package http + +import ( + "github.com/plgd-dev/hub/v2/pkg/net/http/server" + "github.com/plgd-dev/hub/v2/pkg/net/listener" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" +) + +type Config struct { + Connection listener.Config `yaml:",inline" json:",inline"` + Authorization validator.Config `yaml:"authorization" json:"authorization"` + Server server.Config `yaml:",inline" json:",inline"` +} diff --git a/snippet-service/service/http/createConfiguration_test.go b/snippet-service/service/http/createConfiguration_test.go new file mode 100644 index 000000000..76de8fdad --- /dev/null +++ b/snippet-service/service/http/createConfiguration_test.go @@ -0,0 +1,204 @@ +package http_test + +import ( + "bytes" + "context" + "net/http" + "testing" + + "github.com/google/uuid" + "github.com/plgd-dev/go-coap/v3/message" + "github.com/plgd-dev/hub/v2/grpc-gateway/pb" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + "github.com/plgd-dev/hub/v2/resource-aggregate/commands" + snippetPb "github.com/plgd-dev/hub/v2/snippet-service/pb" + snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" + snippetTest "github.com/plgd-dev/hub/v2/snippet-service/test" + "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/hub/v2/test/config" + httpTest "github.com/plgd-dev/hub/v2/test/http" + oauthService "github.com/plgd-dev/hub/v2/test/oauth-server/service" + oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + "github.com/plgd-dev/hub/v2/test/service" + "github.com/stretchr/testify/require" +) + +func makeTestResource(t *testing.T, href string, power int) *snippetPb.Configuration_Resource { + return &snippetPb.Configuration_Resource{ + Href: href, + Content: &commands.Content{ + Data: test.EncodeToCbor(t, map[string]interface{}{"power": power}), + ContentType: message.AppOcfCbor.String(), + CoapContentFormat: int32(message.AppOcfCbor), + }, + TimeToLive: 60, + } +} + +func TestRequestHandlerCreateConfiguration(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + + shutDown := service.SetUpServices(ctx, t, service.SetUpServicesOAuth) + defer shutDown() + + snippetCfg := snippetTest.MakeConfig(t) + shutdownHttp := snippetTest.New(t, snippetCfg) + defer shutdownHttp() + + token := oauthTest.GetDefaultAccessToken(t) + confID1 := uuid.NewString() + + type args struct { + accept string + conf *snippetPb.Configuration + token string + } + tests := []struct { + name string + args args + wantHTTPCode int + wantErr bool + }{ + { + name: "create", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + conf: &snippetPb.Configuration{ + Id: confID1, + Name: "first", + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/1", 41), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusOK, + }, + { + name: "create (with owner)", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + conf: &snippetPb.Configuration{ + Id: uuid.New().String(), + Owner: oauthService.DeviceUserID, + Name: "second", + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/2", 42), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusOK, + }, + { + name: "non-matching owner", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + conf: &snippetPb.Configuration{ + Id: uuid.New().String(), + Owner: "non-matching-owner", + Name: "third", + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/3", 43), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusForbidden, + wantErr: true, + }, + { + name: "missing id", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + conf: &snippetPb.Configuration{ + Name: "fourth", + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/4", 44), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusInternalServerError, + wantErr: true, + }, + { + name: "duplicit ID", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + conf: &snippetPb.Configuration{ + Id: confID1, + Name: "fifth", + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/5", 45), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusInternalServerError, + wantErr: true, + }, + { + name: "missing resources", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + conf: &snippetPb.Configuration{ + Id: uuid.New().String(), + Name: "fifth", + }, + token: token, + }, + wantHTTPCode: http.StatusInternalServerError, + wantErr: true, + }, + { + name: "missing owner in token", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + conf: &snippetPb.Configuration{ + Id: uuid.New().String(), + Name: "sixth", + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/6", 46), + }, + }, + token: oauthTest.GetAccessToken(t, config.OAUTH_SERVER_HOST, oauthTest.ClientTest, map[string]interface{}{ + snippetCfg.APIs.GRPC.Authorization.OwnerClaim: nil, + }), + }, + wantHTTPCode: http.StatusForbidden, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := httpTest.GetContentData(&pb.Content{ + ContentType: message.AppOcfCbor.String(), + Data: test.EncodeToCbor(t, tt.args.conf), + }, message.AppJSON.String()) + require.NoError(t, err) + + rb := httpTest.NewRequest(http.MethodPost, snippetTest.HTTPURI(snippetHttp.Configurations), bytes.NewReader(data)).AuthToken(tt.args.token) + rb.Accept(tt.args.accept).ContentType(message.AppJSON.String()) + resp := httpTest.Do(t, rb.Build(ctx, t)) + defer func() { + _ = resp.Body.Close() + }() + require.Equal(t, tt.wantHTTPCode, resp.StatusCode) + + var got snippetPb.Configuration + err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + want := tt.args.conf + want.Owner = oauthService.DeviceUserID + snippetTest.CmpConfiguration(t, want, &got) + }) + } +} diff --git a/snippet-service/service/http/deleteConfiguration_test.go b/snippet-service/service/http/deleteConfiguration_test.go new file mode 100644 index 000000000..0959ee24e --- /dev/null +++ b/snippet-service/service/http/deleteConfiguration_test.go @@ -0,0 +1,115 @@ +package http_test + +import ( + "context" + "crypto/tls" + "errors" + "io" + "net/http" + "testing" + + "github.com/plgd-dev/go-coap/v3/message" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + "github.com/plgd-dev/hub/v2/snippet-service/pb" + snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" + "github.com/plgd-dev/hub/v2/snippet-service/test" + hubTest "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/hub/v2/test/config" + httpTest "github.com/plgd-dev/hub/v2/test/http" + oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + "github.com/plgd-dev/hub/v2/test/service" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +func TestRequestHandlerDeleteConfigurations(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + + shutDown := service.SetUpServices(context.Background(), t, service.SetUpServicesOAuth) + defer shutDown() + + snippetCfg := test.MakeConfig(t) + shutdownHttp := test.New(t, snippetCfg) + defer shutdownHttp() + + conn, err := grpc.NewClient(config.SNIPPET_SERVICE_HOST, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + RootCAs: hubTest.GetRootCertificatePool(t), + }))) + require.NoError(t, err) + defer func() { + _ = conn.Close() + }() + c := pb.NewSnippetServiceClient(conn) + /*confs :*/ _ = test.AddConfigurations(ctx, t, snippetCfg.APIs.GRPC.Authorization.OwnerClaim, c, 30, nil) + + type args struct { + accept string + token string + } + tests := []struct { + name string + args args + wantHTTPCode int + wantErr bool + want func(*testing.T) + }{ + { + name: "missing owner", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + token: oauthTest.GetAccessToken(t, config.OAUTH_SERVER_HOST, oauthTest.ClientTest, map[string]interface{}{ + snippetCfg.APIs.GRPC.Authorization.OwnerClaim: nil, + }), + }, + wantHTTPCode: http.StatusForbidden, + wantErr: true, + }, + { + name: "owner1/all", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + token: oauthTest.GetAccessToken(t, config.OAUTH_SERVER_HOST, oauthTest.ClientTest, map[string]interface{}{ + snippetCfg.APIs.GRPC.Authorization.OwnerClaim: test.ConfigurationOwner(1), + }), + }, + wantHTTPCode: http.StatusOK, + want: func(t *testing.T) { + getClient, errG := c.GetConfigurations(ctx, &pb.GetConfigurationsRequest{}) + require.NoError(t, errG) + defer func() { + _ = getClient.CloseSend() + }() + for { + conf, errR := getClient.Recv() + if errors.Is(errR, io.EOF) { + break + } + require.NoError(t, errR) + require.Equal(t, test.ConfigurationOwner(1), conf.GetOwner()) + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb := httpTest.NewRequest(http.MethodDelete, test.HTTPURI(snippetHttp.Configurations), nil).AuthToken(tt.args.token) + rb = rb.Accept(tt.args.accept).ContentType(message.AppCBOR.String()) + resp := httpTest.Do(t, rb.Build(ctx, t)) + defer func() { + _ = resp.Body.Close() + }() + require.Equal(t, tt.wantHTTPCode, resp.StatusCode) + + var deleteResp pb.DeleteConfigurationsResponse + err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &deleteResp) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/snippet-service/service/http/getConfigurations_test.go b/snippet-service/service/http/getConfigurations_test.go new file mode 100644 index 000000000..c9a0e763a --- /dev/null +++ b/snippet-service/service/http/getConfigurations_test.go @@ -0,0 +1,137 @@ +package http_test + +import ( + "context" + "crypto/tls" + "errors" + "io" + "net/http" + "testing" + + "github.com/plgd-dev/go-coap/v3/message" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + "github.com/plgd-dev/hub/v2/snippet-service/pb" + snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" + "github.com/plgd-dev/hub/v2/snippet-service/test" + hubTest "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/hub/v2/test/config" + httpTest "github.com/plgd-dev/hub/v2/test/http" + oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + "github.com/plgd-dev/hub/v2/test/service" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +func TestRequestHandlerGetConfigurations(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + + shutDown := service.SetUpServices(context.Background(), t, service.SetUpServicesOAuth) + defer shutDown() + + snippetCfg := test.MakeConfig(t) + shutdownHttp := test.New(t, snippetCfg) + defer shutdownHttp() + + conn, err := grpc.NewClient(config.SNIPPET_SERVICE_HOST, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + RootCAs: hubTest.GetRootCertificatePool(t), + }))) + require.NoError(t, err) + defer func() { + _ = conn.Close() + }() + c := pb.NewSnippetServiceClient(conn) + confs := test.AddConfigurations(ctx, t, snippetCfg.APIs.GRPC.Authorization.OwnerClaim, c, 30, nil) + + type args struct { + accept string + token string + uri string + id string + version string + } + tests := []struct { + name string + args args + wantHTTPCode int + wantErr bool + want func(*testing.T, []*pb.Configuration) + }{ + { + name: "missing owner", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + token: oauthTest.GetAccessToken(t, config.OAUTH_SERVER_HOST, oauthTest.ClientTest, map[string]interface{}{ + snippetCfg.APIs.GRPC.Authorization.OwnerClaim: nil, + }), + uri: test.HTTPURI(snippetHttp.Configurations), + }, + wantHTTPCode: http.StatusForbidden, + wantErr: true, + }, + { + name: "owner1/all", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + token: oauthTest.GetAccessToken(t, config.OAUTH_SERVER_HOST, oauthTest.ClientTest, map[string]interface{}{ + snippetCfg.APIs.GRPC.Authorization.OwnerClaim: test.ConfigurationOwner(1), + }), + uri: test.HTTPURI(snippetHttp.Configurations), + }, + wantHTTPCode: http.StatusOK, + want: func(t *testing.T, values []*pb.Configuration) { + require.NotEmpty(t, values) + for _, v := range values { + conf, ok := confs[v.GetId()] + require.True(t, ok) + test.ConfigurationContains(t, conf, v) + } + }, + }, + // { + // name: "owner0/id0?version=all", + // args: args{ + // accept: pkgHttp.ApplicationProtoJsonContentType, + // token: oauthTest.GetAccessToken(t, config.OAUTH_SERVER_HOST, oauthTest.ClientTest, map[string]interface{}{ + // snippetCfg.APIs.GRPC.Authorization.OwnerClaim: test.ConfigurationOwner(0), + // }), + // uri: test.HTTPURI(snippetHttp.AliasConfigurations), + // id: test.ConfigurationID(0), + // version: "all", + // }, + // wantHTTPCode: http.StatusOK, + // want: func(t *testing.T, values []*pb.Configuration) { + // require.NotEmpty(t, values) + // }, + // }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rb := httpTest.NewRequest(http.MethodGet, tt.args.uri, nil).AuthToken(tt.args.token) + rb = rb.Accept(tt.args.accept).ContentType(message.AppCBOR.String()).ID(tt.args.id).Version(tt.args.version) + resp := httpTest.Do(t, rb.Build(ctx, t)) + defer func() { + _ = resp.Body.Close() + }() + require.Equal(t, tt.wantHTTPCode, resp.StatusCode) + + values := make([]*pb.Configuration, 0, 1) + for { + var value pb.Configuration + err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &value) + if errors.Is(err, io.EOF) { + break + } + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + values = append(values, &value) + } + tt.want(t, values) + }) + } +} diff --git a/snippet-service/service/http/requestHandler.go b/snippet-service/service/http/requestHandler.go new file mode 100644 index 000000000..a5e284e0c --- /dev/null +++ b/snippet-service/service/http/requestHandler.go @@ -0,0 +1,78 @@ +package http + +import ( + "context" + "fmt" + + "github.com/fullstorydev/grpchan/inprocgrpc" + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/plgd-dev/hub/v2/http-gateway/serverMux" + "github.com/plgd-dev/hub/v2/snippet-service/pb" + grpcService "github.com/plgd-dev/hub/v2/snippet-service/service/grpc" +) + +// RequestHandler for handling incoming request +type RequestHandler struct { + config *Config + mux *runtime.ServeMux +} + +/* +// TODO: the GRPC query parser doesn't seem to support oneOf fields, so we have to manually encode and decode the query +func (requestHandler *RequestHandler) getConfigurationVersion(w http.ResponseWriter, r *http.Request) { + // /api/v1/configuration/{id}?version=latest -> rpc GetConfigurations + IDFilter{IDFilter_Latest} + // /api/v1/configuration/{id}?version=all -> rpc GetConfigurations + IDFilter{IDFilter_All} + // /api/v1/configuration/{id}?version={version} -> rpc GetConfigurations + IDFilter{IDFilter_Version{version}} + vars := mux.Vars(r) + configurationID := vars[IDKey] + + versionStr := r.URL.Query().Get(VersionQueryKey) + if versionStr != "" && versionStr != "all" && versionStr == "latest" { + var err error + _, err = strconv.ParseUint(versionStr, 10, 64) + if err != nil { + serverMux.WriteError(w, fmt.Errorf("invalid configuration('%v') version: %w", configurationID, err)) + return + } + } + + type Options struct { + HTTPIDFilter []string `url:"httpIdFilter"` + } + opt := &Options{ + HTTPIDFilter: []string{configurationID + "/" + versionStr}, + } + q, err := query.Values(opt) + if err != nil { + serverMux.WriteError(w, fmt.Errorf("invalid configuration('%v') version: %w", configurationID, err)) + return + } + + r.URL.Path = Configurations + r.URL.RawQuery = q.Encode() + requestHandler.mux.ServeHTTP(w, r) +} +*/ + +// NewHTTP returns HTTP handler +func NewRequestHandler(config *Config, r *mux.Router, snippetServiceServer *grpcService.SnippetServiceServer) (*RequestHandler, error) { + requestHandler := &RequestHandler{ + config: config, + mux: serverMux.New(), + } + + // Aliases + // r.HandleFunc(AliasConfigurations, requestHandler.getConfigurationVersion).Methods(http.MethodGet) + + ch := new(inprocgrpc.Channel) + pb.RegisterSnippetServiceServer(ch, snippetServiceServer) + grpcClient := pb.NewSnippetServiceClient(ch) + // register grpc-proxy handler + if err := pb.RegisterSnippetServiceHandlerClient(context.Background(), requestHandler.mux, grpcClient); err != nil { + return nil, fmt.Errorf("failed to register snippet-service handler: %w", err) + } + r.PathPrefix("/").Handler(requestHandler.mux) + + return requestHandler, nil +} diff --git a/snippet-service/service/http/service.go b/snippet-service/service/http/service.go new file mode 100644 index 000000000..d9ca71cae --- /dev/null +++ b/snippet-service/service/http/service.go @@ -0,0 +1,48 @@ +package http + +import ( + "fmt" + + "github.com/plgd-dev/hub/v2/http-gateway/uri" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + httpService "github.com/plgd-dev/hub/v2/pkg/net/http/service" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" + grpcService "github.com/plgd-dev/hub/v2/snippet-service/service/grpc" + "go.opentelemetry.io/otel/trace" +) + +// Service handle HTTP request +type Service struct { + *httpService.Service + requestHandler *RequestHandler +} + +// New parses configuration and creates new Server with provided store and bus +func New(serviceName string, config Config, snippetServiceServer *grpcService.SnippetServiceServer, validator *validator.Validator, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Service, error) { + service, err := httpService.New(httpService.Config{ + HTTPConnection: config.Connection, + HTTPServer: config.Server, + ServiceName: serviceName, + AuthRules: pkgHttp.NewDefaultAuthorizationRules(uri.API), + FileWatcher: fileWatcher, + Logger: logger, + TraceProvider: tracerProvider, + Validator: validator, + }) + if err != nil { + return nil, fmt.Errorf("cannot create http service: %w", err) + } + + requestHandler, err := NewRequestHandler(&config, service.GetRouter(), snippetServiceServer) + if err != nil { + _ = service.Close() + return nil, err + } + + return &Service{ + Service: service, + requestHandler: requestHandler, + }, nil +} diff --git a/snippet-service/service/http/updateConfiguration_test.go b/snippet-service/service/http/updateConfiguration_test.go new file mode 100644 index 000000000..2d016acb4 --- /dev/null +++ b/snippet-service/service/http/updateConfiguration_test.go @@ -0,0 +1,198 @@ +package http_test + +import ( + "bytes" + "context" + "crypto/tls" + "net/http" + "testing" + + "github.com/google/uuid" + "github.com/plgd-dev/go-coap/v3/message" + "github.com/plgd-dev/hub/v2/grpc-gateway/pb" + pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + pkgHttp "github.com/plgd-dev/hub/v2/pkg/net/http" + snippetPb "github.com/plgd-dev/hub/v2/snippet-service/pb" + snippetHttp "github.com/plgd-dev/hub/v2/snippet-service/service/http" + snippetTest "github.com/plgd-dev/hub/v2/snippet-service/test" + "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/hub/v2/test/config" + httpTest "github.com/plgd-dev/hub/v2/test/http" + oauthService "github.com/plgd-dev/hub/v2/test/oauth-server/service" + oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + "github.com/plgd-dev/hub/v2/test/service" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +func TestRequestHandlerUpdateConfiguration(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + + shutDown := service.SetUpServices(context.Background(), t, service.SetUpServicesOAuth) + defer shutDown() + + shutdownHttp := snippetTest.SetUp(t) + defer shutdownHttp() + + token := oauthTest.GetDefaultAccessToken(t) + + conn, err := grpc.NewClient(config.SNIPPET_SERVICE_HOST, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ + RootCAs: test.GetRootCertificatePool(t), + }))) + require.NoError(t, err) + defer func() { + _ = conn.Close() + }() + c := snippetPb.NewSnippetServiceClient(conn) + conf := &snippetPb.Configuration{ + Id: uuid.NewString(), + Version: 0, + Name: "configurationToUpdate", + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/1", 1), + }, + } + _, err = c.CreateConfiguration(pkgGrpc.CtxWithToken(ctx, token), conf) + require.NoError(t, err) + + type args struct { + accept string + id string + conf *snippetPb.Configuration + token string + } + tests := []struct { + name string + args args + wantHTTPCode int + wantErr bool + }{ + { + name: "update", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + id: conf.GetId(), + conf: &snippetPb.Configuration{ + Version: 1, + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/1", 42), + makeTestResource(t, "/test/2", 52), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusOK, + }, + { + name: "update (with owner)", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + id: conf.GetId(), + conf: &snippetPb.Configuration{ + Version: 2, + Owner: oauthService.DeviceUserID, + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/3", 62), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusOK, + }, + { + name: "update (with overwritten ID)", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + id: conf.GetId(), + conf: &snippetPb.Configuration{ + Id: uuid.NewString(), // this ID will get overwritten by the ID in the query + Version: 3, + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/4", 72), + makeTestResource(t, "/test/5", 82), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusOK, + }, + { + name: "invalid ID", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + id: "invalid", + conf: &snippetPb.Configuration{ + Version: 42, + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/6", 92), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusInternalServerError, + wantErr: true, + }, + { + name: "duplicit version", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + id: conf.GetId(), + conf: &snippetPb.Configuration{ + Version: 1, + Resources: []*snippetPb.Configuration_Resource{ + makeTestResource(t, "/test/7", 102), + }, + }, + token: token, + }, + wantHTTPCode: http.StatusInternalServerError, + wantErr: true, + }, + { + name: "missing resources", + args: args{ + accept: pkgHttp.ApplicationProtoJsonContentType, + id: conf.GetId(), + conf: &snippetPb.Configuration{ + Version: 42, + }, + token: token, + }, + wantHTTPCode: http.StatusInternalServerError, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := httpTest.GetContentData(&pb.Content{ + ContentType: message.AppOcfCbor.String(), + Data: test.EncodeToCbor(t, tt.args.conf), + }, message.AppJSON.String()) + require.NoError(t, err) + + rb := httpTest.NewRequest(http.MethodPut, snippetTest.HTTPURI(snippetHttp.AliasConfigurations), bytes.NewReader(data)).AuthToken(tt.args.token) + rb.Accept(tt.args.accept).ContentType(message.AppJSON.String()).ID(tt.args.id) + resp := httpTest.Do(t, rb.Build(ctx, t)) + defer func() { + _ = resp.Body.Close() + }() + require.Equal(t, tt.wantHTTPCode, resp.StatusCode) + + var got snippetPb.Configuration + err = httpTest.Unmarshal(resp.StatusCode, resp.Body, &got) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + want := tt.args.conf + want.Id = tt.args.id + want.Owner = oauthService.DeviceUserID + snippetTest.CmpConfiguration(t, want, &got) + }) + } +} diff --git a/snippet-service/service/http/uri.go b/snippet-service/service/http/uri.go new file mode 100644 index 000000000..1cb4daccb --- /dev/null +++ b/snippet-service/service/http/uri.go @@ -0,0 +1,26 @@ +package http + +const ( + IDKey = "id" + ConfigurationIDKey = "configurationId" + + VersionQueryKey = "version" + + API string = "/snippet-service/api/v1" + + // GET /api/v1/conditions -> rpc GetConditions + // DELETE /api/v1/conditions -> rpc DeleteConditions + // POST /api/v1/conditions -> rpc CreateCondition + Conditions = API + "/conditions" + + // GET /api/v1/configurations -> rpc GetConfigurations + // DELETE /api/v1/configurations -> rpc DeleteConfigurations + // POST /api/v1/configurations -> rpc CreateConfiguration + Configurations = API + "/configurations" + + // PUT /api/v1/configurations/{id} -> rpc Update Configuration + // GET /api/v1/configuration/{id}?version=latest -> rpc GetConfigurations + IDFilter{IDFilter_Latest} + // GET /api/v1/configuration/{id}?version=all -> rpc GetConfigurations + IDFilter{IDFilter_All} + // GET /api/v1/configuration/{id}?version={version} -> rpc GetConfigurations + IDFilter{IDFilter_Version{version}} + AliasConfigurations = Configurations + "/{" + IDKey + "}" +) diff --git a/snippet-service/service/service.go b/snippet-service/service/service.go new file mode 100644 index 000000000..5549c410d --- /dev/null +++ b/snippet-service/service/service.go @@ -0,0 +1,147 @@ +package service + +import ( + "context" + "fmt" + "time" + + "github.com/go-co-op/gocron/v2" + "github.com/plgd-dev/hub/v2/pkg/config/database" + "github.com/plgd-dev/hub/v2/pkg/fn" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + "github.com/plgd-dev/hub/v2/pkg/net/listener" + otelClient "github.com/plgd-dev/hub/v2/pkg/opentelemetry/collector/client" + "github.com/plgd-dev/hub/v2/pkg/security/jwt/validator" + "github.com/plgd-dev/hub/v2/pkg/service" + grpcService "github.com/plgd-dev/hub/v2/snippet-service/service/grpc" + httpService "github.com/plgd-dev/hub/v2/snippet-service/service/http" + "github.com/plgd-dev/hub/v2/snippet-service/store" + storeConfig "github.com/plgd-dev/hub/v2/snippet-service/store/config" + "github.com/plgd-dev/hub/v2/snippet-service/store/cqldb" + "github.com/plgd-dev/hub/v2/snippet-service/store/mongodb" + "go.opentelemetry.io/otel/trace" +) + +const serviceName = "snippet-service" + +func createStore(ctx context.Context, config storeConfig.Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (store.Store, error) { + switch config.Use { + case database.MongoDB: + s, err := mongodb.New(ctx, config.MongoDB, fileWatcher, logger, tracerProvider) + if err != nil { + return nil, fmt.Errorf("mongodb: %w", err) + } + return s, nil + case database.CqlDB: + s, err := cqldb.New(ctx, config.CqlDB, fileWatcher, logger, tracerProvider) + if err != nil { + return nil, fmt.Errorf("cqldb: %w", err) + } + return s, nil + } + return nil, fmt.Errorf("invalid store use('%v')", config.Use) +} + +func newStore(ctx context.Context, config StorageConfig, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (store.Store, func(), error) { + var fl fn.FuncList + db, err := createStore(ctx, config.Embedded, fileWatcher, logger, tracerProvider) + if err != nil { + fl.Execute() + return nil, nil, err + } + fl.AddFunc(func() { + if errC := db.Close(ctx); errC != nil { + log.Errorf("failed to close mongodb store: %w", errC) + } + }) + if config.CleanUpRecords == "" { + return db, fl.ToFunction(), nil + } + s, err := gocron.NewScheduler(gocron.WithLocation(time.Local)) //nolint:gosmopolitan + if err != nil { + fl.Execute() + return nil, nil, fmt.Errorf("cannot create cron job: %w", err) + } + _, err = s.NewJob(gocron.CronJob(config.CleanUpRecords, config.ExtendCronParserBySeconds), gocron.NewTask(func() { + /* + _, errDel := db.DeleteNonDeviceExpiredRecords(ctx, time.Now()) + if errDel != nil && !errors.Is(errDel, store.ErrNotSupported) { + log.Errorf("failed to delete expired signing records: %w", errDel) + } + */ + })) + if err != nil { + fl.Execute() + return nil, nil, fmt.Errorf("cannot create cron job: %w", err) + } + fl.AddFunc(func() { + if errS := s.Shutdown(); errS != nil { + log.Errorf("failed to shutdown cron job: %w", errS) + } + }) + s.Start() + return db, fl.ToFunction(), nil +} + +func New(ctx context.Context, config Config, fileWatcher *fsnotify.Watcher, logger log.Logger) (*service.Service, error) { + otelClient, err := otelClient.New(ctx, config.Clients.OpenTelemetryCollector, serviceName, fileWatcher, logger) + if err != nil { + return nil, fmt.Errorf("cannot create open telemetry collector client: %w", err) + } + var closerFn fn.FuncList + closerFn.AddFunc(otelClient.Close) + tracerProvider := otelClient.GetTracerProvider() + + dbStorage, closeStore, err := newStore(ctx, config.Clients.Storage, fileWatcher, logger, tracerProvider) + if err != nil { + closerFn.Execute() + return nil, fmt.Errorf("cannot create store: %w", err) + } + closerFn.AddFunc(closeStore) + + ca, err := grpcService.NewSnippetServiceServer(config.APIs.GRPC.Authorization.OwnerClaim, config.HubID, dbStorage, logger) + if err != nil { + closerFn.Execute() + return nil, fmt.Errorf("cannot create grpc %s server: %w", serviceName, err) + } + closerFn.AddFunc(func() { + errC := ca.Close(ctx) + if errC != nil { + log.Errorf("failed to close grpc %s server: %w", serviceName, errC) + } + }) + httpValidator, err := validator.New(ctx, config.APIs.GRPC.Authorization.Config, fileWatcher, logger, tracerProvider) + if err != nil { + closerFn.Execute() + return nil, fmt.Errorf("cannot create http validator: %w", err) + } + closerFn.AddFunc(httpValidator.Close) + httpService, err := httpService.New(serviceName, httpService.Config{ + Connection: listener.Config{ + Addr: config.APIs.HTTP.Addr, + TLS: config.APIs.GRPC.TLS, + }, + Authorization: config.APIs.GRPC.Authorization.Config, + Server: config.APIs.HTTP.Server, + }, ca, httpValidator, fileWatcher, logger, tracerProvider) + if err != nil { + closerFn.Execute() + return nil, fmt.Errorf("cannot create http service: %w", err) + } + grpcValidator, err := validator.New(ctx, config.APIs.GRPC.Authorization.Config, fileWatcher, logger, tracerProvider) + if err != nil { + closerFn.Execute() + _ = httpService.Close() + return nil, fmt.Errorf("cannot create grpc validator: %w", err) + } + grpcService, err := grpcService.New(config.APIs.GRPC, ca, grpcValidator, fileWatcher, logger, tracerProvider) + if err != nil { + closerFn.Execute() + _ = httpService.Close() + return nil, fmt.Errorf("cannot create grpc service: %w", err) + } + s := service.New(httpService, grpcService) + s.AddCloseFunc(closerFn.Execute) + return s, nil +} diff --git a/snippet-service/service/service_test.go b/snippet-service/service/service_test.go new file mode 100644 index 000000000..367b6fe22 --- /dev/null +++ b/snippet-service/service/service_test.go @@ -0,0 +1,38 @@ +// ************************************************************************ +// Copyright (C) 2022 plgd.dev, s.r.o. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ************************************************************************ + +package service_test + +import ( + "context" + "fmt" + "testing" + + "github.com/plgd-dev/hub/v2/snippet-service/test" + testService "github.com/plgd-dev/hub/v2/test/service" + "github.com/stretchr/testify/require" +) + +func TestServiceServe(t *testing.T) { + fmt.Printf("%v\n\n", test.MakeConfig(t)) + + shutDown := testService.SetUpServices(context.Background(), t, testService.SetUpServicesCertificateAuthority|testService.SetUpServicesOAuth) + defer shutDown() + + cfg := test.MakeConfig(t) + cfg.Clients.Storage.CleanUpRecords = "/2 * * * *" + require.Error(t, cfg.Validate()) +} diff --git a/snippet-service/store/condition.go b/snippet-service/store/condition.go new file mode 100644 index 000000000..a4d71103e --- /dev/null +++ b/snippet-service/store/condition.go @@ -0,0 +1,7 @@ +package store + +import ( + "github.com/plgd-dev/hub/v2/snippet-service/pb" +) + +type Condition = pb.Condition diff --git a/snippet-service/store/config/config.go b/snippet-service/store/config/config.go new file mode 100644 index 000000000..20220af87 --- /dev/null +++ b/snippet-service/store/config/config.go @@ -0,0 +1,9 @@ +package config + +import ( + "github.com/plgd-dev/hub/v2/pkg/config/database" + "github.com/plgd-dev/hub/v2/snippet-service/store/cqldb" + "github.com/plgd-dev/hub/v2/snippet-service/store/mongodb" +) + +type Config = database.Config[*mongodb.Config, *cqldb.Config] diff --git a/snippet-service/store/configuration.go b/snippet-service/store/configuration.go new file mode 100644 index 000000000..8d6b2f781 --- /dev/null +++ b/snippet-service/store/configuration.go @@ -0,0 +1,41 @@ +package store + +import ( + "slices" + + "github.com/plgd-dev/hub/v2/snippet-service/pb" +) + +const ( + VersionsKey = "versions" // must match with Versions field tag +) + +type ConfigurationVersion struct { + Version uint64 `bson:"version"` + Resources []*pb.Configuration_Resource `bson:"resources"` +} + +type Configuration struct { + Id string `bson:"_id"` + Name string `bson:"name,omitempty"` + Owner string `bson:"owner"` + Versions []ConfigurationVersion `bson:"versions,omitempty"` +} + +func MakeConfiguration(c *pb.Configuration) Configuration { + return Configuration{ + Id: c.GetId(), + Name: c.GetName(), + Owner: c.GetOwner(), + Versions: []ConfigurationVersion{{Version: c.GetVersion(), Resources: c.GetResources()}}, + } +} + +func (c *Configuration) Clone() *Configuration { + return &Configuration{ + Id: c.Id, + Name: c.Name, + Owner: c.Owner, + Versions: slices.Clone(c.Versions), + } +} diff --git a/snippet-service/store/cqldb/config.go b/snippet-service/store/cqldb/config.go new file mode 100644 index 000000000..9ef1fcdd7 --- /dev/null +++ b/snippet-service/store/cqldb/config.go @@ -0,0 +1,15 @@ +package cqldb + +import ( + "github.com/plgd-dev/hub/v2/pkg/cqldb" +) + +// Config provides Mongo DB configuration options +type Config struct { + Embedded cqldb.Config `yaml:",inline" json:",inline"` + Table string `yaml:"table" json:"table"` +} + +func (c *Config) Validate() error { + return c.Embedded.Validate() +} diff --git a/snippet-service/store/cqldb/configuration.go b/snippet-service/store/cqldb/configuration.go new file mode 100644 index 000000000..ecb08bf68 --- /dev/null +++ b/snippet-service/store/cqldb/configuration.go @@ -0,0 +1,24 @@ +package cqldb + +import ( + "context" + + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/plgd-dev/hub/v2/snippet-service/store" +) + +func (s *Store) CreateConfiguration(context.Context, *pb.Configuration) (*pb.Configuration, error) { + return nil, store.ErrNotSupported +} + +func (s *Store) DeleteConfigurations(context.Context, string, *pb.DeleteConfigurationsRequest) (int64, error) { + return 0, store.ErrNotSupported +} + +func (s *Store) GetConfigurations(context.Context, string, *pb.GetConfigurationsRequest, store.GetConfigurationsFunc) error { + return store.ErrNotSupported +} + +func (s *Store) UpdateConfiguration(context.Context, *pb.Configuration) (*pb.Configuration, error) { + return nil, store.ErrNotSupported +} diff --git a/snippet-service/store/cqldb/store.go b/snippet-service/store/cqldb/store.go new file mode 100644 index 000000000..01d6fa75c --- /dev/null +++ b/snippet-service/store/cqldb/store.go @@ -0,0 +1,95 @@ +package cqldb + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/plgd-dev/hub/v2/pkg/cqldb" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + "github.com/plgd-dev/hub/v2/pkg/security/certManager/client" + "go.opentelemetry.io/otel/trace" +) + +// Document +const ( + // cqldb has all keys in lowercase + idKey = "id" + ownerKey = "owner" + deviceIDKey = "deviceid" + commonNameKey = "commonname" + dataKey = "data" +) + +type Index struct { + Name string + PartitionKey string + SecondaryColumn string +} + +// partition key: idKey +// clustering key: deviceIDKey +var primaryKey = []string{idKey, ownerKey, commonNameKey} + +// Store implements an Store for cqldb. +type Store struct { + *cqldb.Store +} + +func New(ctx context.Context, config *Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Store, error) { + certManager, err := client.New(config.Embedded.TLS, fileWatcher, logger) + if err != nil { + return nil, fmt.Errorf("could not create cert manager: %w", err) + } + cqldbClient, err := cqldb.New(ctx, config.Embedded, certManager.GetTLSConfig(), logger, tracerProvider) + if err != nil { + certManager.Close() + return nil, err + } + store, err := newEventStoreWithClient(ctx, cqldbClient, config, logger) + if err != nil { + cqldbClient.Close() + certManager.Close() + return nil, err + } + store.AddCloseFunc(certManager.Close) + return store, nil +} + +func createEventsTable(ctx context.Context, client *cqldb.Client, table string) error { + q := "create table if not exists " + client.Keyspace() + "." + table + " (" + + idKey + " " + cqldb.UUIDType + "," + + ownerKey + " " + cqldb.StringType + "," + + deviceIDKey + " " + cqldb.UUIDType + "," + + commonNameKey + " " + cqldb.StringType + "," + + dataKey + " " + cqldb.BytesType + "," + + "primary key (" + strings.Join(primaryKey, ",") + ")" + + ")" + err := client.Session().Query(q).WithContext(ctx).Exec() + if err != nil { + return fmt.Errorf("failed to create table(%v): %w", table, err) + } + return nil +} + +// NewEventStoreWithClient creates a new Store with a session. +func newEventStoreWithClient(ctx context.Context, client *cqldb.Client, config *Config, logger log.Logger) (*Store, error) { + if client == nil { + return nil, errors.New("invalid client") + } + + if config.Table == "" { + config.Table = "snippets" + } + + err := createEventsTable(ctx, client, config.Table) + if err != nil { + return nil, err + } + + return &Store{ + Store: cqldb.NewStore(config.Table, client, logger), + }, nil +} diff --git a/snippet-service/store/mongodb/config.go b/snippet-service/store/mongodb/config.go new file mode 100644 index 000000000..8034a68c7 --- /dev/null +++ b/snippet-service/store/mongodb/config.go @@ -0,0 +1,13 @@ +package mongodb + +import ( + pkgMongo "github.com/plgd-dev/hub/v2/pkg/mongodb" +) + +type Config struct { + Mongo pkgMongo.Config `yaml:",inline"` +} + +func (c *Config) Validate() error { + return c.Mongo.Validate() +} diff --git a/snippet-service/store/mongodb/configuration.go b/snippet-service/store/mongodb/configuration.go new file mode 100644 index 000000000..b5721a3da --- /dev/null +++ b/snippet-service/store/mongodb/configuration.go @@ -0,0 +1,365 @@ +package mongodb + +import ( + "cmp" + "context" + "slices" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/plgd-dev/hub/v2/snippet-service/store" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +func (s *Store) CreateConfiguration(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) { + if err := conf.ValidateAndNormalize(); err != nil { + return nil, err + } + + _, err := s.Collection(configurationsCol).InsertOne(ctx, store.MakeConfiguration(conf)) + if err != nil { + return nil, err + } + return conf, nil +} + +func (s *Store) UpdateConfiguration(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) { + if err := conf.ValidateAndNormalize(); err != nil { + return nil, err + } + upd, err := s.Collection(configurationsCol).UpdateOne(ctx, + bson.M{ + "_id": conf.GetId(), + store.OwnerKey: conf.GetOwner(), + store.VersionsKey + "." + store.VersionKey: bson.M{"$ne": conf.GetVersion()}, + }, + bson.M{"$push": bson.M{ + "versions": store.ConfigurationVersion{ + Version: conf.GetVersion(), + Resources: conf.GetResources(), + }, + }}, + ) + if err != nil { + return nil, err + } + if upd.MatchedCount == 0 { + return nil, store.ErrNotFound + } + return conf, nil +} + +func compareIdFilter(i, j *pb.IDFilter) int { + if i.GetId() != j.GetId() { + return strings.Compare(i.GetId(), j.GetId()) + } + if i.GetAll() { + if j.GetAll() { + return 0 + } + return -1 + } + if i.GetLatest() { + if j.GetLatest() { + return 0 + } + if j.GetAll() { + return 1 + } + return -1 + } + if j.GetAll() || j.GetLatest() { + return 1 + } + return cmp.Compare(i.GetValue(), j.GetValue()) +} + +func checkEmptyIdFilter(idfilter []*pb.IDFilter) []*pb.IDFilter { + // if an empty query is provided, return all + if len(idfilter) == 0 { + return nil + } + slices.SortFunc(idfilter, compareIdFilter) + // if the first filter is All, we can ignore all other filters + first := idfilter[0] + if first.GetId() == "" && first.GetAll() { + return nil + } + return idfilter +} + +func normalizeIdFilter(idfilter []*pb.IDFilter) []*pb.IDFilter { + idfilter = checkEmptyIdFilter(idfilter) + if len(idfilter) == 0 { + return nil + } + + updatedFilter := make([]*pb.IDFilter, 0) + var idAll bool + var idLatest bool + var idValue bool + var idValueVersion uint64 + setNextLatest := func(idf *pb.IDFilter) { + // we already have the latest filter + if idLatest { + // skip + return + } + idLatest = true + updatedFilter = append(updatedFilter, idf) + } + setNextValue := func(idf *pb.IDFilter) { + value := idf.GetValue() + if idValue && value == idValueVersion { + // skip + return + } + idValue = true + idValueVersion = value + updatedFilter = append(updatedFilter, idf) + } + prevID := "" + for _, idf := range idfilter { + if idf.GetId() != prevID { + idAll = idf.GetAll() + idLatest = idf.GetLatest() + idValue = !idAll && !idLatest + idValueVersion = idf.GetValue() + updatedFilter = append(updatedFilter, idf) + } + + if idAll { + goto next + } + + if idf.GetLatest() { + setNextLatest(idf) + goto next + } + + setNextValue(idf) + + next: + prevID = idf.GetId() + } + return updatedFilter +} + +type versionFilter struct { + latest bool + versions []uint64 +} + +func partitionQuery(idfilter []*pb.IDFilter) ([]string, map[string]versionFilter) { + idFilter := normalizeIdFilter(idfilter) + if len(idFilter) == 0 { + return nil, nil + } + idVersionAll := make([]string, 0) + idVersions := make(map[string]versionFilter, 0) + hasAllIdsLatest := func() bool { + vf, ok := idVersions[""] + return ok && vf.latest + } + hasAllIdsVersion := func(version uint64) bool { + vf, ok := idVersions[""] + return ok && slices.Contains(vf.versions, version) + } + for _, idf := range idFilter { + if idf.GetAll() { + idVersionAll = append(idVersionAll, idf.GetId()) + continue + } + vf := idVersions[idf.GetId()] + if idf.GetLatest() { + if hasAllIdsLatest() { + continue + } + vf.latest = true + idVersions[idf.GetId()] = vf + continue + } + version := idf.GetValue() + if hasAllIdsVersion(version) { + continue + } + vf.versions = append(vf.versions, version) + idVersions[idf.GetId()] = vf + } + return idVersionAll, idVersions +} + +func toIdFilterQuery(owner string, idfAlls []string) interface{} { + filters := make([]interface{}, 0, 2) + if owner != "" { + filters = append(filters, bson.D{{Key: store.OwnerKey, Value: owner}}) + } + if len(idfAlls) > 0 { + idfilter := make([]bson.D, 0, len(idfAlls)) + for _, idfall := range idfAlls { + idfilter = append(idfilter, bson.D{{Key: "_id", Value: idfall}}) + } + filters = append(filters, bson.M{"$or": idfilter}) + } + if len(filters) == 0 { + return bson.D{} + } + if len(filters) == 1 { + return filters[0] + } + return bson.M{"$and": filters} +} + +func processCursor(ctx context.Context, cr *mongo.Cursor, h store.GetConfigurationsFunc) error { + if h == nil { + return nil + } + var errors *multierror.Error + i := store.MongoIterator[store.Configuration]{ + Cursor: cr, + } + err := h(ctx, &i) + errors = multierror.Append(errors, err) + errClose := cr.Close(ctx) + errors = multierror.Append(errors, errClose) + return errors.ErrorOrNil() +} + +func (s *Store) getConfigurationsByFind(ctx context.Context, owner string, idfAlls []string, h store.GetConfigurationsFunc) error { + cur, err := s.Collection(configurationsCol).Find(ctx, toIdFilterQuery(owner, idfAlls)) + if err != nil { + return err + } + return processCursor(ctx, cur, h) +} + +func addMatchCondition(owner, id string) bson.D { + match := bson.D{ + {Key: store.VersionsKey + ".0", Value: bson.M{"$exists": true}}, + } + if id != "" { + match = append(match, bson.E{Key: "_id", Value: id}) + } + if owner != "" { + match = append(match, bson.E{Key: store.OwnerKey, Value: owner}) + } + return match +} + +func addLatestVersionField() bson.D { + return bson.D{{Key: "$addFields", Value: bson.M{ + "latestVersion": bson.M{"$reduce": bson.M{ + "input": "$versions", + "initialValue": bson.M{"version": 0}, + "in": bson.M{ + "$cond": bson.A{ + bson.M{"$gte": bson.A{"$$this.version", "$$value.version"}}, + "$$this.version", + "$$value.version", + }, + }, + }}, + }}} +} + +func getVersionsPipeline(pl mongo.Pipeline, vf versionFilter, exclude bool) mongo.Pipeline { + versions := make([]interface{}, 0, len(vf.versions)+1) + for _, version := range vf.versions { + versions = append(versions, version) + } + + if vf.latest { + pl = append(pl, addLatestVersionField()) + versions = append(versions, "$latestVersion") + } + + if len(versions) > 0 { + cond := bson.M{"$in": bson.A{"$$version.version", versions}} + if exclude { + cond = bson.M{"$not": cond} + } + pl = append(pl, bson.D{{Key: "$addFields", Value: bson.M{ + "versions": bson.M{ + "$filter": bson.M{ + "input": "$versions", + "as": "version", + "cond": cond, + }, + }, + }}}) + } + return pl +} + +func (s *Store) getConfigurationsByAggregation(ctx context.Context, owner, id string, vf versionFilter, h store.GetConfigurationsFunc) error { + pl := mongo.Pipeline{bson.D{{Key: "$match", Value: addMatchCondition(owner, id)}}} + pl = getVersionsPipeline(pl, vf, false) + cur, err := s.Collection(configurationsCol).Aggregate(ctx, pl) + if err != nil { + return err + } + return processCursor(ctx, cur, h) +} + +func (s *Store) GetConfigurations(ctx context.Context, owner string, query *pb.GetConfigurationsRequest, h store.GetConfigurationsFunc) error { + idVersionAll, idVersions := partitionQuery(query.GetIdFilter()) + var errors *multierror.Error + if len(idVersionAll) > 0 || len(idVersions) == 0 { + err := s.getConfigurationsByFind(ctx, owner, idVersionAll, h) + errors = multierror.Append(errors, err) + } + if len(idVersions) > 0 { + for id, vf := range idVersions { + err := s.getConfigurationsByAggregation(ctx, owner, id, vf, h) + errors = multierror.Append(errors, err) + } + } + return errors.ErrorOrNil() +} + +func (s *Store) removeDocument(ctx context.Context, owner string, idfAlls []string) error { + _, err := s.Collection(configurationsCol).DeleteMany(ctx, toIdFilterQuery(owner, idfAlls)) + return err +} + +func (s *Store) removeVersion(ctx context.Context, owner string, id string, vf versionFilter) error { + pl := getVersionsPipeline(mongo.Pipeline{}, vf, true) + if vf.latest { + pl = append(pl, bson.D{{Key: "$unset", Value: "latestVersion"}}) + } + _, err := s.Collection(configurationsCol).UpdateMany(ctx, addMatchCondition(owner, id), pl) + // TODO: delete document if no versions remain in the array + return err +} + +func (s *Store) DeleteConfigurations(ctx context.Context, owner string, query *pb.DeleteConfigurationsRequest) (int64, error) { + success := false + idVersionAll, idVersions := partitionQuery(query.GetIdFilter()) + var errors *multierror.Error + if len(idVersionAll) > 0 || len(idVersions) == 0 { + err := s.removeDocument(ctx, owner, idVersionAll) + if err == nil { + success = true + } + errors = multierror.Append(errors, err) + } + if len(idVersions) > 0 { + for id, vf := range idVersions { + err := s.removeVersion(ctx, owner, id, vf) + if err == nil { + success = true + } + errors = multierror.Append(errors, err) + } + } + err := errors.ErrorOrNil() + if err != nil { + if success { + return 2, err + } + return 0, err + } + return 1, nil +} diff --git a/snippet-service/store/mongodb/configuration_internal_test.go b/snippet-service/store/mongodb/configuration_internal_test.go new file mode 100644 index 000000000..c5179ad21 --- /dev/null +++ b/snippet-service/store/mongodb/configuration_internal_test.go @@ -0,0 +1,112 @@ +package mongodb + +import ( + "testing" + + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/stretchr/testify/require" +) + +func TestConfigurationNormalizeQuery(t *testing.T) { + tests := []struct { + name string + query *pb.GetConfigurationsRequest + want []*pb.IDFilter + }{ + { + name: "nil", + query: nil, + want: nil, + }, + { + name: "empty", + query: &pb.GetConfigurationsRequest{}, + want: nil, + }, + { + // if the query contains an IDFilter with an empty ID and All set to true, then all other filters are ignored + name: "all", + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + {Id: "id1", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id2", Version: &pb.IDFilter_Value{Value: 1}}, + {Version: &pb.IDFilter_All{All: true}}, + }, + }, + want: nil, + }, + { + // if the query contains an IDFilter with All set to true then other filters for the ID are ignored + name: "remove non-All", + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + {Id: "id1", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id1", Version: &pb.IDFilter_Value{Value: 1}}, + {Id: "id1", Version: &pb.IDFilter_All{All: true}}, + }, + }, + want: []*pb.IDFilter{ + {Id: "id1", Version: &pb.IDFilter_All{All: true}}, + }, + }, + { + name: "remove duplicates", + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + {Id: "id1", Version: &pb.IDFilter_All{All: true}}, + {Id: "id1", Version: &pb.IDFilter_All{All: true}}, + {Id: "id2", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id2", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id2", Version: &pb.IDFilter_Value{Value: 42}}, + {Id: "id2", Version: &pb.IDFilter_Value{Value: 42}}, + {Id: "id2", Version: &pb.IDFilter_Value{Value: 1}}, + {Id: "id2", Version: &pb.IDFilter_Value{Value: 1}}, + {Id: "id2", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id2", Version: &pb.IDFilter_Value{Value: 42}}, + }, + }, + want: []*pb.IDFilter{ + {Id: "id1", Version: &pb.IDFilter_All{All: true}}, + {Id: "id2", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id2", Version: &pb.IDFilter_Value{Value: 1}}, + {Id: "id2", Version: &pb.IDFilter_Value{Value: 42}}, + }, + }, + { + name: "normalize", + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + {Id: "id3", Version: &pb.IDFilter_Value{Value: 3}}, + {Id: "id1", Version: &pb.IDFilter_Value{Value: 0}}, + {Id: "id3", Version: &pb.IDFilter_Value{Value: 3}}, + {Id: "id2", Version: &pb.IDFilter_All{All: true}}, + {Id: "id3", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id1", Version: &pb.IDFilter_Value{Value: 1}}, + {Id: "id3", Version: &pb.IDFilter_Value{Value: 1}}, + {Id: "id2", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id3", Version: &pb.IDFilter_Value{Value: 2}}, + {Id: "id2", Version: &pb.IDFilter_Value{Value: 42}}, + {Id: "id1", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id3", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id1", Version: &pb.IDFilter_Latest{Latest: true}}, + }, + }, + want: []*pb.IDFilter{ + {Id: "id1", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id1", Version: &pb.IDFilter_Value{Value: 0}}, + {Id: "id1", Version: &pb.IDFilter_Value{Value: 1}}, + {Id: "id2", Version: &pb.IDFilter_All{All: true}}, + {Id: "id3", Version: &pb.IDFilter_Latest{Latest: true}}, + {Id: "id3", Version: &pb.IDFilter_Value{Value: 1}}, + {Id: "id3", Version: &pb.IDFilter_Value{Value: 2}}, + {Id: "id3", Version: &pb.IDFilter_Value{Value: 3}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, normalizeIdFilter(tt.query.GetIdFilter())) + }) + } +} diff --git a/snippet-service/store/mongodb/createConfiguration_test.go b/snippet-service/store/mongodb/createConfiguration_test.go new file mode 100644 index 000000000..6a62fcca4 --- /dev/null +++ b/snippet-service/store/mongodb/createConfiguration_test.go @@ -0,0 +1,138 @@ +package mongodb_test + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/plgd-dev/go-coap/v3/message" + "github.com/plgd-dev/hub/v2/resource-aggregate/commands" + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/plgd-dev/hub/v2/snippet-service/test" + hubTest "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/hub/v2/test/config" + "github.com/stretchr/testify/require" +) + +func makeLightResourceConfiguration(t *testing.T, id string, power int, ttl int64) *pb.Configuration_Resource { + return &pb.Configuration_Resource{ + Href: hubTest.TestResourceLightInstanceHref(id), + Content: &commands.Content{ + Data: hubTest.EncodeToCbor(t, map[string]interface{}{ + "power": power, + }), + ContentType: message.AppOcfCbor.String(), + CoapContentFormat: int32(message.AppOcfCbor), + }, + TimeToLive: ttl, + } +} + +func TestStoreCreateConfiguration(t *testing.T) { + s, cleanUpStore := test.NewMongoStore(t) + defer cleanUpStore() + + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + + confID := uuid.New().String() + const owner = "owner1" + resources := []*pb.Configuration_Resource{ + makeLightResourceConfiguration(t, "1", 1, 1337), + } + + type args struct { + create *pb.Configuration + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "valid", + args: args{ + create: &pb.Configuration{ + Id: confID, + Name: "valid", + Owner: owner, + Version: 0, + Resources: resources, + }, + }, + }, + { + name: "duplicit item (ID)", + args: args{ + create: &pb.Configuration{ + Id: confID, + Name: "duplicit ID", + Owner: owner, + Resources: resources, + Version: 42, + }, + }, + wantErr: true, + }, + { + name: "missing ID", + args: args{ + create: &pb.Configuration{ + Name: "missing ID", + Owner: owner, + Version: 0, + Resources: resources, + }, + }, + wantErr: true, + }, + { + name: "invalid ID", + args: args{ + create: &pb.Configuration{ + Id: "invalid", + Name: "invalid ID", + Owner: owner, + Version: 0, + Resources: resources, + }, + }, + wantErr: true, + }, + { + name: "missing owner", + args: args{ + create: &pb.Configuration{ + Id: confID, + Name: "missing owner", + Version: 0, + Resources: resources, + }, + }, + wantErr: true, + }, + { + name: "missing resources", + args: args{ + create: &pb.Configuration{ + Id: confID, + Name: "missing resources", + Owner: owner, + Version: 0, + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := s.CreateConfiguration(ctx, tt.args.create) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/snippet-service/store/mongodb/deleteConfigurations_test.go b/snippet-service/store/mongodb/deleteConfigurations_test.go new file mode 100644 index 000000000..6cf8a6159 --- /dev/null +++ b/snippet-service/store/mongodb/deleteConfigurations_test.go @@ -0,0 +1,366 @@ +package mongodb_test + +import ( + "context" + "testing" + + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/plgd-dev/hub/v2/snippet-service/store" + "github.com/plgd-dev/hub/v2/snippet-service/store/mongodb" + "github.com/plgd-dev/hub/v2/snippet-service/test" + "github.com/plgd-dev/hub/v2/test/config" + "github.com/stretchr/testify/require" +) + +func TestStoreDeleteConfigurations(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + + getConfigurations := func(t *testing.T, s *mongodb.Store, owner string, query *pb.GetConfigurationsRequest) []*store.Configuration { + var configurations []*store.Configuration + err := s.GetConfigurations(ctx, owner, query, func(iterCtx context.Context, iter store.Iterator[store.Configuration]) error { + var conf store.Configuration + for iter.Next(iterCtx, &conf) { + configurations = append(configurations, conf.Clone()) + } + return iter.Err() + }) + require.NoError(t, err) + return configurations + } + + type args struct { + owner string + query *pb.DeleteConfigurationsRequest + } + tests := []struct { + name string + args args + wantErr bool + want func(t *testing.T, s *mongodb.Store, stored map[string]store.Configuration) + }{ + { + name: "all", + args: args{ + owner: "", + query: nil, + }, + want: func(t *testing.T, s *mongodb.Store, _ map[string]store.Configuration) { + confs := getConfigurations(t, s, "", nil) + require.Empty(t, confs) + }, + }, + { + name: "owner1", + args: args{ + owner: test.ConfigurationOwner(1), + query: nil, + }, + want: func(t *testing.T, s *mongodb.Store, stored map[string]store.Configuration) { + confs := getConfigurations(t, s, "", nil) + require.NotEmpty(t, confs) + newCount := 0 + for _, conf := range confs { + require.NotEqual(t, test.ConfigurationOwner(1), conf.Owner) + newCount += len(conf.Versions) + } + storedCount := 0 + for _, conf := range stored { + if conf.Owner != test.ConfigurationOwner(1) { + storedCount += len(conf.Versions) + } + } + require.Equal(t, storedCount, newCount) + }, + }, + { + name: "id{1,3,4,5}", + args: args{ + owner: "", + query: &pb.DeleteConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Id: test.ConfigurationID(1), + Version: &pb.IDFilter_All{All: true}, + }, + { + Id: test.ConfigurationID(3), + Version: &pb.IDFilter_All{All: true}, + }, + { + Id: test.ConfigurationID(4), + Version: &pb.IDFilter_All{All: true}, + }, + { + Id: test.ConfigurationID(5), + Version: &pb.IDFilter_All{All: true}, + }, + }, + }, + }, + want: func(t *testing.T, s *mongodb.Store, stored map[string]store.Configuration) { + confs := getConfigurations(t, s, "", nil) + require.NotEmpty(t, confs) + newCount := 0 + for _, conf := range confs { + require.NotEqual(t, test.ConfigurationID(1), conf.Id) + require.NotEqual(t, test.ConfigurationID(3), conf.Id) + require.NotEqual(t, test.ConfigurationID(4), conf.Id) + require.NotEqual(t, test.ConfigurationID(5), conf.Id) + newCount += len(conf.Versions) + } + storedCount := 0 + for _, conf := range stored { + if conf.Id == test.ConfigurationID(1) || + conf.Id == test.ConfigurationID(3) || + conf.Id == test.ConfigurationID(4) || + conf.Id == test.ConfigurationID(5) { + continue + } + storedCount += len(conf.Versions) + } + require.Equal(t, storedCount, newCount) + }, + }, + { + name: "owner2/id2", + args: args{ + owner: test.ConfigurationOwner(2), + query: &pb.DeleteConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Id: test.ConfigurationID(2), + Version: &pb.IDFilter_All{All: true}, + }, + // Ids not owned by owner2 should not be deleted + { + Id: test.ConfigurationID(1), + Version: &pb.IDFilter_All{All: true}, + }, + { + Id: test.ConfigurationID(3), + Version: &pb.IDFilter_All{All: true}, + }, + }, + }, + }, + want: func(t *testing.T, s *mongodb.Store, stored map[string]store.Configuration) { + confs := getConfigurations(t, s, "", nil) + require.NotEmpty(t, confs) + newCount := 0 + for _, conf := range confs { + require.NotEqual(t, test.ConfigurationID(2), conf.Id) + newCount += len(conf.Versions) + } + storedCount := 0 + for _, conf := range stored { + if conf.Id == test.ConfigurationID(2) { + continue + } + storedCount += len(conf.Versions) + } + require.Equal(t, storedCount, newCount) + }, + }, + { + name: "latest", + args: args{ + query: &pb.DeleteConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + }, + }, + }, + want: func(t *testing.T, s *mongodb.Store, stored map[string]store.Configuration) { + storedLatest := make(map[string]store.ConfigurationVersion) + storedCount := 0 + for _, conf := range stored { + storedLatest[conf.Id] = conf.Versions[len(conf.Versions)-1] + storedCount += len(conf.Versions) + } + confs := getConfigurations(t, s, "", nil) + require.NotEmpty(t, confs) + count := 0 + for _, conf := range confs { + require.NotEqual(t, storedLatest[conf.Id], conf.Versions[len(conf.Versions)-1]) + count += len(conf.Versions) + } + require.Equal(t, storedCount-len(storedLatest), count) + }, + }, + { + name: "owner1/latest", + args: args{ + owner: test.ConfigurationOwner(1), + query: &pb.DeleteConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + // duplicates should be ignored + { + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + { + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + }, + }, + }, + want: func(t *testing.T, s *mongodb.Store, stored map[string]store.Configuration) { + storedLatest := make(map[string]store.ConfigurationVersion) + storedCount := 0 + for _, conf := range stored { + storedLatest[conf.Id] = conf.Versions[len(conf.Versions)-1] + storedCount += len(conf.Versions) + } + confs := getConfigurations(t, s, "", nil) + require.NotEmpty(t, confs) + count := 0 + removed := 0 + for _, conf := range confs { + if conf.Owner == test.ConfigurationOwner(1) { + require.NotEqual(t, storedLatest[conf.Id], conf.Versions[len(conf.Versions)-1]) + removed++ + } else { + require.Equal(t, storedLatest[conf.Id], conf.Versions[len(conf.Versions)-1]) + } + count += len(conf.Versions) + } + require.Equal(t, storedCount-removed, count) + }, + }, + { + name: "owner2/id1/latest - non-matching owner", + args: args{ + owner: test.ConfigurationOwner(2), + query: &pb.DeleteConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Id: test.ConfigurationID(1), + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + }, + }, + }, + want: func(t *testing.T, s *mongodb.Store, stored map[string]store.Configuration) { + confs := getConfigurations(t, s, "", nil) + confsMap := make(map[string]store.Configuration) + for _, conf := range confs { + confsMap[conf.Id] = *conf + } + test.CmpStoredConfigurationMaps(t, stored, confsMap) + }, + }, + { + name: "version/{42, 142, 242, 342, 442, 542}", args: args{ + owner: "", + query: &pb.DeleteConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + {Version: &pb.IDFilter_Value{Value: 42}}, + {Version: &pb.IDFilter_Value{Value: 142}}, + {Version: &pb.IDFilter_Value{Value: 242}}, + {Version: &pb.IDFilter_Value{Value: 342}}, + {Version: &pb.IDFilter_Value{Value: 442}}, + {Version: &pb.IDFilter_Value{Value: 542}}, + }, + }, + }, + want: func(t *testing.T, s *mongodb.Store, stored map[string]store.Configuration) { + confs := getConfigurations(t, s, "", nil) + confsMap := make(map[string]store.Configuration) + for _, conf := range confs { + confsMap[conf.Id] = *conf + } + for _, conf := range stored { + versions := make([]store.ConfigurationVersion, 0) + for _, version := range conf.Versions { + if version.Version == 42 || + version.Version == 142 || + version.Version == 242 || + version.Version == 342 || + version.Version == 442 || + version.Version == 542 { + continue + } + versions = append(versions, version) + } + conf.Versions = versions + stored[conf.Id] = conf + } + test.CmpStoredConfigurationMaps(t, stored, confsMap) + }, + }, + { + name: "owner2/version/{213, 237, 242}", args: args{ + owner: test.ConfigurationOwner(2), + query: &pb.DeleteConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + {Version: &pb.IDFilter_Value{Value: 213}}, + {Version: &pb.IDFilter_Value{Value: 237}}, + {Version: &pb.IDFilter_Value{Value: 242}}, + // duplicates should be ignored + {Version: &pb.IDFilter_Value{Value: 237}}, + // filter with Id should be ignored if there are filters without Id + { + Id: test.ConfigurationID(2), + Version: &pb.IDFilter_Value{Value: 237}, + }, + }, + }, + }, + want: func(t *testing.T, s *mongodb.Store, stored map[string]store.Configuration) { + confs := getConfigurations(t, s, "", nil) + confsMap := make(map[string]store.Configuration) + for _, conf := range confs { + confsMap[conf.Id] = *conf + } + for _, conf := range stored { + if conf.Owner == test.ConfigurationOwner(2) { + versions := make([]store.ConfigurationVersion, 0) + for _, version := range conf.Versions { + if version.Version == 213 || + version.Version == 237 || + version.Version == 242 { + continue + } + versions = append(versions, version) + } + conf.Versions = versions + stored[conf.Id] = conf + } + } + test.CmpStoredConfigurationMaps(t, stored, confsMap) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, cleanUpStore := test.NewMongoStore(t) + defer cleanUpStore() + inserted := test.AddConfigurationsToStore(ctx, t, s, 500, func(iteration int) uint64 { + return uint64(iteration * 100) + }) + _, err := s.DeleteConfigurations(ctx, tt.args.owner, tt.args.query) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + tt.want(t, s, inserted) + }) + } +} diff --git a/snippet-service/store/mongodb/getConfigurations_test.go b/snippet-service/store/mongodb/getConfigurations_test.go new file mode 100644 index 000000000..2dac743ca --- /dev/null +++ b/snippet-service/store/mongodb/getConfigurations_test.go @@ -0,0 +1,301 @@ +package mongodb_test + +import ( + "cmp" + "context" + "slices" + "testing" + + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/plgd-dev/hub/v2/snippet-service/store" + "github.com/plgd-dev/hub/v2/snippet-service/test" + "github.com/plgd-dev/hub/v2/test/config" + "github.com/stretchr/testify/require" +) + +func TestStoreGetConfigurations(t *testing.T) { + s, cleanUpStore := test.NewMongoStore(t) + defer cleanUpStore() + + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + confs := test.AddConfigurationsToStore(ctx, t, s, 500, nil) + + type args struct { + owner string + query *pb.GetConfigurationsRequest + } + tests := []struct { + name string + args args + wantErr bool + want func(t *testing.T, configurations []*store.Configuration) + }{ + { + name: "all", + args: args{ + owner: "", + query: nil, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.Len(t, configurations, len(confs)) + }, + }, + { + name: "owner0", + args: args{ + owner: test.ConfigurationOwner(0), + query: nil, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.NotEmpty(t, configurations) + for _, c := range configurations { + require.Equal(t, test.ConfigurationOwner(0), c.Owner) + conf, ok := confs[c.Id] + require.True(t, ok) + test.CmpJSON(t, &conf, c) + } + }, + }, + { + name: "id1/all", + args: args{ + owner: "", + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Id: test.ConfigurationID(1), + Version: &pb.IDFilter_All{ + All: true, + }, + }, + }, + }, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.Len(t, configurations, 1) + c := configurations[0] + conf, ok := confs[c.Id] + require.True(t, ok) + test.CmpJSON(t, &conf, c) + }, + }, + { + name: "owner2/id2/all", + args: args{ + owner: test.ConfigurationOwner(2), + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Id: test.ConfigurationID(2), + Version: &pb.IDFilter_All{ + All: true, + }, + }, + }, + }, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.Len(t, configurations, 1) + c := configurations[0] + conf, ok := confs[c.Id] + require.True(t, ok) + require.Equal(t, test.ConfigurationID(2), conf.Id) + require.Equal(t, test.ConfigurationOwner(2), conf.Owner) + test.CmpJSON(t, &conf, c) + }, + }, + { + name: "latest", + args: args{ + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + }, + }, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.Len(t, configurations, 10) + for _, c := range configurations { + _, ok := confs[c.Id] + require.True(t, ok) + require.Len(t, c.Versions, 1) + } + }, + }, + { + name: "owner1/latest", + args: args{ + owner: test.ConfigurationOwner(1), + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + }, + }, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.Len(t, configurations, 3) + for _, c := range configurations { + conf, ok := confs[c.Id] + require.True(t, ok) + require.Equal(t, test.ConfigurationOwner(1), conf.Owner) + require.Len(t, c.Versions, 1) + } + }, + }, + { + name: "owner2/id1/latest - non-matching owner", + args: args{ + owner: test.ConfigurationOwner(2), + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Id: test.ConfigurationID(1), + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + }, + }, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.Empty(t, configurations) + }, + }, + { + name: "owner2{latest, id2/latest, id5/latest} - non-matching owner", args: args{ + owner: test.ConfigurationOwner(2), + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + { + Id: test.ConfigurationID(2), + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + { + Id: test.ConfigurationID(5), + Version: &pb.IDFilter_Latest{ + Latest: true, + }, + }, + }, + }, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.Len(t, configurations, 3) + for _, c := range configurations { + conf, ok := confs[c.Id] + require.True(t, ok) + require.Equal(t, test.ConfigurationOwner(2), conf.Owner) + require.Len(t, c.Versions, 1) + } + }, + }, + { + name: "version/42", args: args{ + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Version: &pb.IDFilter_Value{ + Value: 42, + }, + }, + }, + }, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.Len(t, configurations, 10) + for _, c := range configurations { + _, ok := confs[c.Id] + require.True(t, ok) + require.Len(t, c.Versions, 1) + require.Equal(t, uint64(42), c.Versions[0].Version) + } + }, + }, + { + name: "owner2/version/{13, 37, 42}", args: args{ + owner: test.ConfigurationOwner(2), + query: &pb.GetConfigurationsRequest{ + IdFilter: []*pb.IDFilter{ + { + Version: &pb.IDFilter_Value{ + Value: 13, + }, + }, + { + Version: &pb.IDFilter_Value{ + Value: 37, + }, + }, + { + Version: &pb.IDFilter_Value{ + Value: 42, + }, + }, + // duplicates should be ignored + { + Version: &pb.IDFilter_Value{ + Value: 37, + }, + }, + // filter with Id should be ignored if there are filters without Id + { + Id: test.ConfigurationID(2), + Version: &pb.IDFilter_Value{ + Value: 37, + }, + }, + }, + }, + }, + want: func(t *testing.T, configurations []*store.Configuration) { + require.Len(t, configurations, 3) + for _, c := range configurations { + _, ok := confs[c.Id] + require.True(t, ok) + require.Len(t, c.Versions, 3) + slices.SortFunc(c.Versions, func(i, j store.ConfigurationVersion) int { + return cmp.Compare(i.Version, j.Version) + }) + require.Equal(t, uint64(13), c.Versions[0].Version) + require.Equal(t, uint64(37), c.Versions[1].Version) + require.Equal(t, uint64(42), c.Versions[2].Version) + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var configurations []*store.Configuration + err := s.GetConfigurations(ctx, tt.args.owner, tt.args.query, func(iterCtx context.Context, iter store.Iterator[store.Configuration]) error { + var conf store.Configuration + for iter.Next(iterCtx, &conf) { + configurations = append(configurations, conf.Clone()) + } + return iter.Err() + }) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + tt.want(t, configurations) + }) + } +} diff --git a/snippet-service/store/mongodb/store.go b/snippet-service/store/mongodb/store.go new file mode 100644 index 000000000..20cfe5ad9 --- /dev/null +++ b/snippet-service/store/mongodb/store.go @@ -0,0 +1,68 @@ +package mongodb + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-multierror" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + pkgMongo "github.com/plgd-dev/hub/v2/pkg/mongodb" + "github.com/plgd-dev/hub/v2/pkg/security/certManager/client" + "github.com/plgd-dev/hub/v2/snippet-service/store" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.opentelemetry.io/otel/trace" +) + +type Store struct { + *pkgMongo.Store +} + +const ( + // conditionsCol = "conditions" + configurationsCol = "configurations" +) + +var configurationIdVersionUniqueIndex = mongo.IndexModel{ + Keys: bson.D{ + {Key: "_id", Value: 1}, + {Key: store.VersionsKey + "." + store.VersionKey, Value: 1}, + }, + Options: options.Index().SetUnique(true), +} + +func New(ctx context.Context, cfg *Config, fileWatcher *fsnotify.Watcher, logger log.Logger, tracerProvider trace.TracerProvider) (*Store, error) { + certManager, err := client.New(cfg.Mongo.TLS, fileWatcher, logger) + if err != nil { + return nil, fmt.Errorf("could not create cert manager: %w", err) + } + + m, err := pkgMongo.NewStoreWithCollections(ctx, &cfg.Mongo, certManager.GetTLSConfig(), tracerProvider, map[string][]mongo.IndexModel{ + // conditionsCol: {deviceIDFilterAndOwnerIndex}, + configurationsCol: {configurationIdVersionUniqueIndex}, + }) + if err != nil { + certManager.Close() + return nil, err + } + s := Store{Store: m} + s.SetOnClear(s.clearDatabases) + s.AddCloseFunc(certManager.Close) + return &s, nil +} + +func (s *Store) clearDatabases(ctx context.Context) error { + var errors *multierror.Error + collections := []string{configurationsCol} + for _, collection := range collections { + err := s.Collection(collection).Drop(ctx) + errors = multierror.Append(errors, err) + } + return errors.ErrorOrNil() +} + +func (s *Store) Close(ctx context.Context) error { + return s.Store.Close(ctx) +} diff --git a/snippet-service/store/mongodb/updateConfiguration_test.go b/snippet-service/store/mongodb/updateConfiguration_test.go new file mode 100644 index 000000000..f7e35d0c7 --- /dev/null +++ b/snippet-service/store/mongodb/updateConfiguration_test.go @@ -0,0 +1,103 @@ +package mongodb_test + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/plgd-dev/hub/v2/snippet-service/test" + "github.com/plgd-dev/hub/v2/test/config" + "github.com/stretchr/testify/require" +) + +func TestUpdateConfiguration(t *testing.T) { + s, cleanUpStore := test.NewMongoStore(t) + defer cleanUpStore() + + ctx, cancel := context.WithTimeout(context.Background(), config.TEST_TIMEOUT) + defer cancel() + + confID := uuid.New().String() + const owner = "owner1" + resources := []*pb.Configuration_Resource{ + makeLightResourceConfiguration(t, "1", 1, 1337), + } + _, err := s.CreateConfiguration(ctx, &pb.Configuration{ + Id: confID, + Name: "valid", + Owner: owner, + Version: 0, + Resources: resources, + }) + require.NoError(t, err) + + type args struct { + update *pb.Configuration + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "non-matching owner", + args: args{ + update: &pb.Configuration{ + Id: confID, + Owner: "invalid", + Version: 0, + Resources: resources, + }, + }, + wantErr: true, + }, + { + name: "duplicit version", + args: args{ + update: &pb.Configuration{ + Id: confID, + Owner: owner, + Version: 0, + Resources: resources, + }, + }, + wantErr: true, + }, + { + name: "missing ID", + args: args{ + update: &pb.Configuration{ + Owner: owner, + Version: 1, + Resources: resources, + }, + }, + wantErr: true, + }, + { + name: "valid", + args: args{ + update: &pb.Configuration{ + Id: confID, + Owner: owner, + Version: 1, + Resources: []*pb.Configuration_Resource{ + makeLightResourceConfiguration(t, "2", 2, 42), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := s.UpdateConfiguration(ctx, tt.args.update) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/snippet-service/store/store.go b/snippet-service/store/store.go new file mode 100644 index 000000000..a2fbc0040 --- /dev/null +++ b/snippet-service/store/store.go @@ -0,0 +1,79 @@ +package store + +import ( + "context" + "errors" + + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "go.mongodb.org/mongo-driver/mongo" +) + +const ( + IDKey = "id" // must match with Id field tag + VersionKey = "version" // must match with Version field tag + OwnerKey = "owner" // must match with Owner field tag + // DeviceIDFilterKey = "deviceIdFilter" // must match with Condition.DeviceIdFilter tag + // ResourceHrefFilterKey = "resourceHrefFilter" // must match with Condition.ResourceHrefFilter tag + // ResourceTypeFilterKey = "resourceTypeFilter" // must match with Condition.ResourceTypeFilter tag + +) + +// type ( +// ConditionsQuery struct { +// DeviceID string +// ResourceHref string +// ResourceTypeFilter []string +// } +// ) + +type Iterator[T any] interface { + Next(ctx context.Context, v *T) bool + Err() error +} + +type ( + // LoadConditionsFunc = func(ctx context.Context, iter Iterator[Condition]) (err error) + GetConfigurationsFunc = func(ctx context.Context, iter Iterator[Configuration]) (err error) +) + +var ( + ErrNotSupported = errors.New("not supported") + ErrNotFound = errors.New("not found") + ErrInvalidArgument = errors.New("invalid argument") +) + +type MongoIterator[T any] struct { + Cursor *mongo.Cursor +} + +func (i *MongoIterator[T]) Next(ctx context.Context, s *T) bool { + if !i.Cursor.Next(ctx) { + return false + } + err := i.Cursor.Decode(s) + return err == nil +} + +func (i *MongoIterator[T]) Err() error { + return i.Cursor.Err() +} + +type Store interface { + // CreateCondition creates a new condition. If the condition already exists, it will throw an error. + // CreateCondition(ctx context.Context, condition *Condition) error + // UpdateSigningRecord updates an existing signing record. If the record does not exist, it will create a new one. + // UpdateSigningRecord(ctx context.Context, record *SigningRecord) error + // DeleteSigningRecords(ctx context.Context, ownerID string, query *DeleteSigningRecordsQuery) (int64, error) + // LoadConditions(ctx context.Context, ownerID string, query *ConditionsQuery, h LoadConditionsFunc) error + + // CreateConfiguration creates a new configuration in the database. + CreateConfiguration(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) + // UpdateConfiguration updates an existing configuration in the database. + UpdateConfiguration(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) + // GetConfigurations loads a configuration from the database. + GetConfigurations(ctx context.Context, owner string, query *pb.GetConfigurationsRequest, h GetConfigurationsFunc) error + // DeleteConfigurations deletes configurations from the database. + DeleteConfigurations(ctx context.Context, owner string, query *pb.DeleteConfigurationsRequest) (int64, error) + + Close(ctx context.Context) error +} diff --git a/snippet-service/test/configuration.go b/snippet-service/test/configuration.go new file mode 100644 index 000000000..d295de3de --- /dev/null +++ b/snippet-service/test/configuration.go @@ -0,0 +1,145 @@ +package test + +import ( + "context" + "strconv" + "testing" + + "github.com/google/uuid" + "github.com/plgd-dev/go-coap/v3/message" + pkgGrpc "github.com/plgd-dev/hub/v2/pkg/net/grpc" + "github.com/plgd-dev/hub/v2/resource-aggregate/commands" + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/plgd-dev/hub/v2/snippet-service/store" + hubTest "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/hub/v2/test/config" + oauthTest "github.com/plgd-dev/hub/v2/test/oauth-server/test" + "github.com/stretchr/testify/require" +) + +var testConfigurationIDs = make(map[int]string) + +func ConfigurationID(i int) string { + if id, ok := testConfigurationIDs[i]; ok { + return id + } + id := uuid.New().String() + testConfigurationIDs[i] = id + return id +} + +func ConfigurationName(i int) string { + return "cfg" + strconv.Itoa(i) +} + +func ConfigurationOwner(i int) string { + return "owner" + strconv.Itoa(i) +} + +func ConfigurationResources(t *testing.T, start, n int) []*pb.Configuration_Resource { + resources := make([]*pb.Configuration_Resource, 0, n) + for i := start; i < start+n; i++ { + resources = append(resources, &pb.Configuration_Resource{ + Href: hubTest.TestResourceLightInstanceHref(strconv.Itoa(i)), + Content: &commands.Content{ + Data: hubTest.EncodeToCbor(t, map[string]interface{}{ + "power": i, + }), + ContentType: message.AppOcfCbor.String(), + CoapContentFormat: int32(message.AppOcfCbor), + }, + TimeToLive: 1337, + }) + } + return resources +} + +type ( + onCreateConfiguration = func(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) + onUpdateConfiguration = func(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) + calculateInitialVersionNumber = func(iteration int) uint64 +) + +func addConfigurations(ctx context.Context, t *testing.T, n int, calcVersion calculateInitialVersionNumber, create onCreateConfiguration, update onUpdateConfiguration) map[string]store.Configuration { + const numConfigs = 10 + const numOwners = 3 + versions := make(map[int]uint64, numConfigs) + owners := make(map[int]string, numConfigs) + configurations := make(map[string]store.Configuration) + for i := 0; i < n; i++ { + version, ok := versions[i%numConfigs] + if !ok { + version = 0 + if calcVersion != nil { + version = calcVersion(i) + } + versions[i%numConfigs] = version + } + versions[i%numConfigs]++ + owner, ok := owners[i%numConfigs] + if !ok { + owner = ConfigurationOwner(i % numOwners) + owners[i%numConfigs] = owner + } + confIn := &pb.Configuration{ + Id: ConfigurationID(i % numConfigs), + Version: version, + Resources: ConfigurationResources(t, i%16, (i%5)+1), + Owner: owner, + } + var conf *pb.Configuration + var err error + if !ok { + confIn.Name = ConfigurationName(i % numConfigs) + conf, err = create(ctx, confIn) + require.NoError(t, err) + } else { + conf, err = update(ctx, confIn) + require.NoError(t, err) + } + + configuration, ok := configurations[conf.GetId()] + if !ok { + configuration = store.Configuration{ + Id: conf.GetId(), + Owner: conf.GetOwner(), + Name: conf.GetName(), + } + configurations[conf.GetId()] = configuration + } + configuration.Versions = append(configuration.Versions, store.ConfigurationVersion{ + Version: conf.GetVersion(), + Resources: conf.GetResources(), + }) + configurations[conf.GetId()] = configuration + } + return configurations +} + +func AddConfigurationsToStore(ctx context.Context, t *testing.T, s store.Store, n int, calcVersion calculateInitialVersionNumber) map[string]store.Configuration { + return addConfigurations(ctx, t, n, calcVersion, s.CreateConfiguration, s.UpdateConfiguration) +} + +func AddConfigurations(ctx context.Context, t *testing.T, ownerClaim string, c pb.SnippetServiceClient, n int, calcVersion calculateInitialVersionNumber) map[string]store.Configuration { + tokens := make(map[string]string) + getTokenWithOwnerClaim := func(owner string) string { + token, ok := tokens[owner] + if ok { + return token + } + token = oauthTest.GetAccessToken(t, config.OAUTH_SERVER_HOST, oauthTest.ClientTest, map[string]interface{}{ + ownerClaim: owner, + }) + tokens[owner] = token + return token + } + + return addConfigurations(ctx, t, n, calcVersion, func(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) { + ctxWithToken := pkgGrpc.CtxWithToken(ctx, getTokenWithOwnerClaim(conf.GetOwner())) + return c.CreateConfiguration(ctxWithToken, conf) + }, func(ctx context.Context, conf *pb.Configuration) (*pb.Configuration, error) { + ctxWithToken := pkgGrpc.CtxWithToken(ctx, getTokenWithOwnerClaim(conf.GetOwner())) + return c.UpdateConfiguration(ctxWithToken, conf) + }, + ) +} diff --git a/snippet-service/test/service.go b/snippet-service/test/service.go new file mode 100644 index 000000000..8cea9d0f2 --- /dev/null +++ b/snippet-service/test/service.go @@ -0,0 +1,120 @@ +package test + +import ( + "context" + "sync" + "time" + + "github.com/plgd-dev/hub/v2/pkg/config/database" + "github.com/plgd-dev/hub/v2/pkg/fsnotify" + "github.com/plgd-dev/hub/v2/pkg/log" + "github.com/plgd-dev/hub/v2/pkg/mongodb" + "github.com/plgd-dev/hub/v2/snippet-service/service" + storeConfig "github.com/plgd-dev/hub/v2/snippet-service/store/config" + storeCqlDB "github.com/plgd-dev/hub/v2/snippet-service/store/cqldb" + storeMongo "github.com/plgd-dev/hub/v2/snippet-service/store/mongodb" + "github.com/plgd-dev/hub/v2/test/config" + httpTest "github.com/plgd-dev/hub/v2/test/http" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" +) + +func HTTPURI(uri string) string { + return httpTest.HTTPS_SCHEME + config.SNIPPET_SERVICE_HTTP_HOST + uri +} + +func MakeConfig(t require.TestingT) service.Config { + var cfg service.Config + + cfg.HubID = config.HubID() + cfg.Log = log.MakeDefaultConfig() + + cfg.APIs.GRPC = config.MakeGrpcServerConfig(config.SNIPPET_SERVICE_HOST) + cfg.APIs.HTTP.Addr = config.SNIPPET_SERVICE_HTTP_HOST + cfg.APIs.HTTP.Server = config.MakeHttpServerConfig() + cfg.APIs.GRPC.TLS.ClientCertificateRequired = false + + cfg.Clients.OpenTelemetryCollector = config.MakeOpenTelemetryCollectorClient() + cfg.Clients.Storage = MakeStorageConfig() + + err := cfg.Validate() + require.NoError(t, err) + + return cfg +} + +func SetUp(t require.TestingT) (tearDown func()) { + return New(t, MakeConfig(t)) +} + +func New(t require.TestingT, cfg service.Config) func() { + ctx := context.Background() + logger := log.NewLogger(cfg.Log) + + fileWatcher, err := fsnotify.NewWatcher(logger) + require.NoError(t, err) + + s, err := service.New(ctx, cfg, fileWatcher, logger) + require.NoError(t, err) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + _ = s.Serve() + }() + + return func() { + _ = s.Close() + wg.Wait() + err = fileWatcher.Close() + require.NoError(t, err) + } +} + +func MakeStorageConfig() service.StorageConfig { + return service.StorageConfig{ + CleanUpRecords: "0 1 * * *", + Embedded: storeConfig.Config{ + // TODO: add cqldb support + // Use: config.ACTIVE_DATABASE(), + Use: database.MongoDB, + MongoDB: &storeMongo.Config{ + Mongo: mongodb.Config{ + MaxPoolSize: 16, + MaxConnIdleTime: time.Minute * 4, + URI: config.MONGODB_URI, + Database: "snippetService", + TLS: config.MakeTLSClientConfig(), + }, + }, + CqlDB: &storeCqlDB.Config{ + Embedded: config.MakeCqlDBConfig(), + Table: "snippets", + }, + }, + } +} + +func NewMongoStore(t require.TestingT) (*storeMongo.Store, func()) { + cfg := MakeConfig(t) + logger := log.NewLogger(cfg.Log) + + fileWatcher, err := fsnotify.NewWatcher(logger) + require.NoError(t, err) + + ctx := context.Background() + store, err := storeMongo.New(ctx, cfg.Clients.Storage.Embedded.MongoDB, fileWatcher, logger, noop.NewTracerProvider()) + require.NoError(t, err) + + cleanUp := func() { + err := store.Clear(ctx) + require.NoError(t, err) + _ = store.Close(ctx) + + err = fileWatcher.Close() + require.NoError(t, err) + } + + return store, cleanUp +} diff --git a/snippet-service/test/test.go b/snippet-service/test/test.go new file mode 100644 index 000000000..1c913bfbd --- /dev/null +++ b/snippet-service/test/test.go @@ -0,0 +1,65 @@ +package test + +import ( + "testing" + + "github.com/plgd-dev/hub/v2/snippet-service/pb" + "github.com/plgd-dev/hub/v2/snippet-service/store" + "github.com/plgd-dev/hub/v2/test" + "github.com/plgd-dev/kit/v2/codec/json" + "github.com/stretchr/testify/require" +) + +func CmpJSON(t *testing.T, want, got interface{}) { + wantJson, err := json.Encode(want) + require.NoError(t, err) + gotJson, err := json.Encode(got) + require.NoError(t, err) + require.JSONEq(t, string(wantJson), string(gotJson)) +} + +func cmpConfigurationResources(t *testing.T, want, got []*pb.Configuration_Resource) { + require.Len(t, got, len(want)) + for i := range want { + wantData, ok := test.DecodeCbor(t, want[i].GetContent().GetData()).(map[interface{}]interface{}) + require.True(t, ok) + gotData, ok := test.DecodeCbor(t, got[i].GetContent().GetData()).(map[interface{}]interface{}) + require.True(t, ok) + require.Equal(t, wantData, gotData) + want[i].Content.Data = nil + got[i].Content.Data = nil + } + CmpJSON(t, want, got) +} + +func CmpConfiguration(t *testing.T, want, got *pb.Configuration) { + if want.GetResources() != nil && got.GetResources() != nil { + cmpConfigurationResources(t, want.GetResources(), got.GetResources()) + want.Resources = nil + got.Resources = nil + } + CmpJSON(t, want, got) +} + +func ConfigurationContains(t *testing.T, storeConf store.Configuration, conf *pb.Configuration) { + require.Equal(t, storeConf.Id, conf.GetId()) + require.Equal(t, storeConf.Owner, conf.GetOwner()) + require.Equal(t, storeConf.Name, conf.GetName()) + for _, v := range storeConf.Versions { + if v.Version != conf.GetVersion() { + continue + } + test.CheckProtobufs(t, v.Resources, conf.GetResources(), test.RequireToCheckFunc(require.Equal)) + return + } + require.Fail(t, "version not found") +} + +func CmpStoredConfigurationMaps(t *testing.T, want, got map[string]store.Configuration) { + require.Len(t, got, len(want)) + for _, v := range want { + gotV, ok := got[v.Id] + require.True(t, ok) + CmpJSON(t, v, gotV) + } +} diff --git a/test/config/config.go b/test/config/config.go index b77415684..7e39c1d74 100644 --- a/test/config/config.go +++ b/test/config/config.go @@ -43,6 +43,8 @@ const ( RESOURCE_DIRECTORY_HOST = "localhost:20004" CERTIFICATE_AUTHORITY_HOST = "localhost:20011" CERTIFICATE_AUTHORITY_HTTP_HOST = "localhost:20012" + SNIPPET_SERVICE_HOST = "localhost:20013" + SNIPPET_SERVICE_HTTP_HOST = "localhost:20014" GRPC_GW_HOST = "localhost:20005" C2C_CONNECTOR_HOST = "localhost:20006" C2C_CONNECTOR_DB = "cloud2cloudConnector" diff --git a/test/http/request.go b/test/http/request.go index a86a9faf5..668963725 100644 --- a/test/http/request.go +++ b/test/http/request.go @@ -102,6 +102,14 @@ func (c *RequestBuilder) DeviceId(deviceID string) *RequestBuilder { return c } +func (c *RequestBuilder) ID(id string) *RequestBuilder { + if id == "" { + return c + } + c.uriParams[IDKey] = id + return c +} + func (c *RequestBuilder) ResourceHref(resourceHref string) *RequestBuilder { if resourceHref == "" { return c @@ -121,6 +129,14 @@ func (c *RequestBuilder) SubscriptionID(subscriptionID string) *RequestBuilder { return c } +func (c *RequestBuilder) Version(version string) *RequestBuilder { + if version == "" { + return c + } + c.AddQuery(VersionKey, version) + return c +} + func (c *RequestBuilder) SetQuery(value string) *RequestBuilder { c.query = value return c diff --git a/test/http/uri.go b/test/http/uri.go index f3ba99c3f..e4f221989 100644 --- a/test/http/uri.go +++ b/test/http/uri.go @@ -3,9 +3,12 @@ package http const ( HTTPS_SCHEME = "https://" - DeviceIDKey = "deviceID" - ResourceHrefKey = "resourceHref" - SubscriptionIDKey = "subscriptionID" + IDKey = "id" + DeviceIDKey = "deviceID" + ResourceHrefKey = "resourceHref" + SubscriptionIDKey = "subscriptionID" + ConfigurationIDKey = "configurationId" + VersionKey = "version" ContentQueryKey = "content" ) diff --git a/test/iotivity-lite/service/offboard_test.go b/test/iotivity-lite/service/offboard_test.go index 36941eeb4..f8c09c2df 100644 --- a/test/iotivity-lite/service/offboard_test.go +++ b/test/iotivity-lite/service/offboard_test.go @@ -55,7 +55,7 @@ func TestOffboard(t *testing.T) { require.Equal(t, 1, publishCount) singOffCount, ok := h.CallCounter.Data[iotService.SignOffKey] require.True(t, ok) - require.Positive(t, singOffCount, 0) + require.Positive(t, singOffCount) } coapShutdown := coapgwTest.SetUp(t, makeHandler, validateHandler) @@ -337,7 +337,7 @@ func TestOffboardWithSignInByRefreshToken(t *testing.T) { require.Greater(t, signInCount, 1) refreshCount, ok := h.CallCounter.Data[iotService.RefreshTokenKey] require.True(t, ok) - require.Positive(t, refreshCount, 0) + require.Positive(t, refreshCount) signOffCount, ok := h.CallCounter.Data[iotService.SignOffKey] require.True(t, ok) require.Equal(t, 1, signOffCount) diff --git a/test/iotivity-lite/service/republish_test.go b/test/iotivity-lite/service/republish_test.go index 7ee0a2093..a6367aae6 100644 --- a/test/iotivity-lite/service/republish_test.go +++ b/test/iotivity-lite/service/republish_test.go @@ -45,7 +45,7 @@ func TestRepublishAfterRefresh(t *testing.T) { require.Greater(t, signInCount, 1) refreshCount, ok := h.CallCounter.Data[iotService.RefreshTokenKey] require.True(t, ok) - require.Positive(t, refreshCount, 0) + require.Positive(t, refreshCount) publishCount, ok := h.CallCounter.Data[iotService.PublishKey] require.True(t, ok) require.Equal(t, 1, publishCount)