diff --git a/Dockerfile b/Dockerfile index c49e168..aa8a0e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,59 @@ +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS builder + +# upgrade first to avoid fixable vulnerabilities +# do this in builder as well as in buildee, so builder does not have different pkg versions from buildee image +RUN microdnf -y upgrade --refresh --best --nodocs --noplugins --setopt=install_weak_deps=0 --setopt=keepcache=0 \ + && microdnf clean all -y + +RUN microdnf -y --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install \ + rpm-build \ + gcc gcc-c++ make cmake pkgconfig \ + cyrus-sasl-devel openssl-devel libuuid-devel \ + python3-devel python3-pip python3-wheel \ + libnghttp2-devel \ + wget tar patch findutils git \ + libtool \ + && microdnf clean all -y + +WORKDIR /build +# Clone skupper-router so repo contents are in /build (not /build/skupper-router) +RUN git clone --depth 1 --branch main https://github.com/skupperproject/skupper-router.git . +ENV PROTON_VERSION=main +ENV PROTON_SOURCE_URL=${PROTON_SOURCE_URL:-https://github.com/apache/qpid-proton/archive/${PROTON_VERSION}.tar.gz} +ENV LWS_VERSION=v4.3.3 +ENV LIBUNWIND_VERSION=v1.8.1 +ENV LWS_SOURCE_URL=${LWS_SOURCE_URL:-https://github.com/warmcat/libwebsockets/archive/refs/tags/${LWS_VERSION}.tar.gz} +ENV LIBUNWIND_SOURCE_URL=${LIBUNWIND_SOURCE_URL:-https://github.com/libunwind/libunwind/archive/refs/tags/${LIBUNWIND_VERSION}.tar.gz} +ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig + +ARG VERSION=0.0.0 +ENV VERSION=$VERSION +ARG TARGETARCH +ENV PLATFORM=$TARGETARCH +RUN .github/scripts/compile.sh +RUN mkdir -p /image && if [ "$PLATFORM" = "amd64" ]; then tar zxpf /qpid-proton-image.tar.gz -C /image && tar zxpf /skupper-router-image.tar.gz -C /image && tar zxpf /libwebsockets-image.tar.gz -C /image && tar zxpf /libunwind-image.tar.gz -C /image; fi +RUN if [ "$PLATFORM" = "arm64" ]; then tar zxpf /qpid-proton-image.tar.gz -C /image && tar zxpf /skupper-router-image.tar.gz -C /image && tar zxpf /libwebsockets-image.tar.gz -C /image; fi +RUN if [ "$PLATFORM" = "s390x" ]; then tar zxpf /qpid-proton-image.tar.gz -C /image && tar zxpf /skupper-router-image.tar.gz -C /image && tar zxpf /libwebsockets-image.tar.gz -C /image; fi +RUN if [ "$PLATFORM" = "ppc64le" ]; then tar zxpf /qpid-proton-image.tar.gz -C /image && tar zxpf /skupper-router-image.tar.gz -C /image && tar zxpf /libwebsockets-image.tar.gz -C /image; fi + +RUN mkdir /image/licenses && cp ./LICENSE /image/licenses + +FROM registry.access.redhat.com/ubi9/ubi:latest AS packager + +RUN dnf -y --setopt=install_weak_deps=0 --nodocs \ + --installroot /output install \ + coreutils-single \ + cyrus-sasl-lib cyrus-sasl-plain openssl \ + python3 \ + libnghttp2 \ + hostname iputils \ + shadow-utils \ + && chroot /output useradd --uid 10000 runner \ + && dnf -y --installroot /output remove shadow-utils \ + && dnf clean all --installroot /output +RUN [ -d /usr/share/buildinfo ] && cp -a /usr/share/buildinfo /output/usr/share/buildinfo ||: +RUN [ -d /root/buildinfo ] && cp -a /root/buildinfo /output/root/buildinfo ||: + FROM golang:1.23-alpine AS go-builder ARG TARGETOS @@ -6,15 +62,33 @@ ARG TARGETARCH RUN mkdir -p /go/src/github.com/datasance/router WORKDIR /go/src/github.com/datasance/router COPY . /go/src/github.com/datasance/router -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o bin/router +RUN go fmt ./... +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w" -o bin/router . FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS tz RUN microdnf install -y tzdata && microdnf reinstall -y tzdata -FROM quay.io/skupper/skupper-router:main +FROM scratch + +COPY --from=packager /output / +COPY --from=packager /etc/yum.repos.d /etc/yum.repos.d + +USER 10000 + +COPY --from=builder /image / + +WORKDIR /home/skrouterd/bin +COPY ./scripts/* /home/skrouterd/bin/ + +ARG version=latest +ENV VERSION=${version} +ENV QDROUTERD_HOME=/home/skrouterd + COPY LICENSE /licenses/LICENSE COPY --from=go-builder /go/src/github.com/datasance/router/bin/router /home/skrouterd/bin/router -# COPY scripts/launch.sh /home/skrouterd/bin/launch.sh + COPY --from=tz /usr/share/zoneinfo /usr/share/zoneinfo -CMD ["/home/skrouterd/bin/router"] +# Env: SKUPPER_PLATFORM=pot|kubernetes (default pot), QDROUTERD_CONF (default /tmp/skrouterd.json), +# SSL_PROFILE_PATH (default /etc/skupper-router-certs). In K8s mode operator mounts config at QDROUTERD_CONF. +CMD ["/home/skrouterd/bin/router"] \ No newline at end of file diff --git a/Dockerfile-dev b/Dockerfile-dev index 0236b1f..927e3fd 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,39 +1,4 @@ - -FROM registry.access.redhat.com/ubi9/ubi-minimal:latest as builder - - -RUN microdnf -y upgrade --refresh --best --nodocs --noplugins --setopt=install_weak_deps=0 --setopt=keepcache=0 \ - && microdnf clean all -y - -RUN microdnf -y --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install \ - rpm-build \ - gcc gcc-c++ make cmake pkgconfig \ - cyrus-sasl-devel openssl-devel libuuid-devel \ - python3-devel python3-pip \ - libnghttp2-devel \ - wget tar patch findutils git \ - libtool \ - && microdnf clean all -y - -RUN git clone https://github.com/skupperproject/skupper-router.git --branch=2.6.x -WORKDIR /skupper-router -ENV PROTON_VERSION=0.39.0 -ENV PROTON_SOURCE_URL=${PROTON_SOURCE_URL:-https://github.com/apache/qpid-proton/archive/${PROTON_VERSION}.tar.gz} -ENV LWS_VERSION=v4.3.3 -ENV LIBUNWIND_VERSION=v1.6.2 -ENV LWS_SOURCE_URL=${LWS_SOURCE_URL:-https://github.com/warmcat/libwebsockets/archive/refs/tags/${LWS_VERSION}.tar.gz} -ENV LIBUNWIND_SOURCE_URL=${LIBUNWIND_SOURCE_URL:-https://github.com/libunwind/libunwind/archive/refs/tags/${LIBUNWIND_VERSION}.tar.gz} -ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig - -ARG VERSION=2.6.0 -ARG PLATFORM=amd64 -ENV PLATFORM=$PLATFORM -RUN .github/scripts/compile.sh -RUN if [ "$PLATFORM" = "amd64" ]; then tar zxpf /qpid-proton-image.tar.gz --one-top-level=/image && tar zxpf /skupper-router-image.tar.gz --one-top-level=/image && tar zxpf /libwebsockets-image.tar.gz --one-top-level=/image && tar zxpf /libunwind-image.tar.gz --one-top-level=/image; fi -RUN if [ "$PLATFORM" = "arm64" ]; then tar zxpf /qpid-proton-image.tar.gz --one-top-level=/image && tar zxpf /skupper-router-image.tar.gz --one-top-level=/image && tar zxpf /libwebsockets-image.tar.gz --one-top-level=/image; fi - - -FROM golang:1.20.14-alpine AS go-builder +FROM golang:1.23-alpine AS go-builder ARG TARGETOS ARG TARGETARCH @@ -41,39 +6,18 @@ ARG TARGETARCH RUN mkdir -p /go/src/github.com/datasance/router WORKDIR /go/src/github.com/datasance/router COPY . /go/src/github.com/datasance/router -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o bin/router - - -FROM registry.access.redhat.com/ubi9/ubi-minimal:latest - -# upgrade first to avoid fixable vulnerabilities -RUN microdnf -y upgrade --refresh --best --nodocs --noplugins --setopt=install_weak_deps=0 --setopt=keepcache=0 \ - && microdnf clean all -y - -RUN microdnf -y --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install \ - glibc \ - cyrus-sasl-lib cyrus-sasl-plain cyrus-sasl-gssapi openssl \ - python3 \ - libnghttp2 \ - gettext hostname iputils \ - shadow-utils \ - && microdnf clean all - -RUN useradd --uid 10000 runner -USER 10000 - -WORKDIR / -COPY --from=builder /image / - -WORKDIR /home/skrouterd/etc -WORKDIR /home/skrouterd/bin -COPY ./scripts/* /home/skrouterd/bin/ +RUN go fmt ./... +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags="-s -w" -o bin/router . -ARG version=latest -ENV VERSION=${version} -ENV QDROUTERD_HOME=/home/skrouterd +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS tz +RUN microdnf install -y tzdata && microdnf reinstall -y tzdata +FROM quay.io/skupper/skupper-router:main +COPY LICENSE /licenses/LICENSE COPY --from=go-builder /go/src/github.com/datasance/router/bin/router /home/skrouterd/bin/router -COPY scripts/launch.sh /home/skrouterd/bin/launch.sh +# COPY scripts/launch.sh /home/skrouterd/bin/launch.sh +COPY --from=tz /usr/share/zoneinfo /usr/share/zoneinfo -CMD ["/home/skrouterd/bin/router"] \ No newline at end of file +# Env: SKUPPER_PLATFORM=pot|kubernetes (default pot), QDROUTERD_CONF (default /tmp/skrouterd.json), +# SSL_PROFILE_PATH (default /etc/skupper-router-certs). In K8s mode operator mounts config at QDROUTERD_CONF. +CMD ["/home/skrouterd/bin/router"] diff --git a/Dockerfile-qpid1.20 b/Dockerfile-qpid1.20 deleted file mode 100644 index 3fb8bb7..0000000 --- a/Dockerfile-qpid1.20 +++ /dev/null @@ -1,61 +0,0 @@ -FROM ubuntu:22.04 AS qpid-builder - -ENV TZ=Europe/Berlin -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && \ - apt-get install -y curl gcc g++ automake libwebsockets-dev libtool zlib1g-dev cmake libsasl2-dev libssl-dev python3-dev python3-venv python-is-python3 libstdc++-11-dev libuv1-dev sasl2-bin swig maven git && \ - apt-get -y clean - -RUN git clone https://gitbox.apache.org/repos/asf/qpid-dispatch.git && cd /qpid-dispatch && git submodule add https://gitbox.apache.org/repos/asf/qpid-proton.git && git submodule update --init - -WORKDIR /qpid-dispatch - -RUN mkdir qpid-proton/build && cd qpid-proton/build && cmake .. -DSYSINSTALL_BINDINGS=ON -DCMAKE_INSTALL_PREFIX=/usr -DSYSINSTALL_PYTHON=ON && make install - -WORKDIR /qpid-dispatch -RUN sed -i '1i #include ' /qpid-dispatch/tests/cpp-stub/cpp_stub.h -RUN mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DUSE_VALGRIND=NO && cmake --build . --target install - - - -FROM golang:1.20.14-alpine AS go-builder - -ARG TARGETOS -ARG TARGETARCH - -RUN mkdir -p /go/src/github.com/datasance/router -WORKDIR /go/src/github.com/datasance/router -COPY . /go/src/github.com/datasance/router -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o bin/router - -FROM ubuntu:22.04 - -RUN apt-get update && \ - apt-get install -y python3 python-is-python3 iputils-ping && \ - apt-get -y clean - -COPY --from=qpid-builder /usr/lib/python3.10 /usr/lib/python3.10 -COPY --from=qpid-builder /qpid-dispatch/qpid-proton/python/proton /usr/lib/python3.10/site-packages/proton -COPY --from=qpid-builder /qpid-dispatch/qpid-proton/build/python/cproton.py /qpid-dispatch/qpid-proton/build/python/cproton_ffi.abi3.so /qpid-dispatch/qpid-proton/build/python/pytest_env/lib/python3.10/site-packages/*cffi_backend*\ - /usr/lib/python3.10/site-packages/ - -COPY --from=qpid-builder /etc/qpid-dispatch /etc/qpid-dispatch -COPY --from=qpid-builder /usr/lib/lib* /usr/lib/ -COPY --from=qpid-builder /usr/lib/qpid-dispatch /usr/lib/qpid-dispatch -COPY --from=qpid-builder /usr/lib/ssl /usr/lib/ssl -COPY --from=qpid-builder /usr/lib/sasl2 /usr/lib/sasl2 -COPY --from=qpid-builder /usr/lib/openssh /usr/lib/openssh -COPY --from=qpid-builder /usr/lib/*-linux-* /usr/lib/ -COPY --from=qpid-builder /usr/sbin/qdrouterd /usr/sbin/qdrouterd -COPY --from=qpid-builder /usr/bin/qdmanage /usr/bin/qdmanage -COPY --from=qpid-builder /usr/bin/qdstat /usr/bin/qdstat - -COPY --from=go-builder /go/src/github.com/datasance/router/bin/router /qpid-dispatch/router - -COPY scripts/launch.sh /qpid-dispatch/launch.sh - -ENV PYTHONPATH=/usr/lib/python3.10/site-packages - -CMD ["/qpid-dispatch/router"] - diff --git a/Dockerfile-skupper.main b/Dockerfile-skupper.main deleted file mode 100644 index 0422674..0000000 --- a/Dockerfile-skupper.main +++ /dev/null @@ -1,87 +0,0 @@ -FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS builder - -# upgrade first to avoid fixable vulnerabilities -# do this in builder as well as in buildee, so builder does not have different pkg versions from buildee image -RUN microdnf -y upgrade --refresh --best --nodocs --noplugins --setopt=install_weak_deps=0 --setopt=keepcache=0 \ - && microdnf clean all -y - - RUN microdnf -y --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install \ - rpm-build \ - gcc gcc-c++ make cmake pkgconfig \ - cyrus-sasl-devel openssl-devel libuuid-devel \ - python3-devel python3-pip python3-wheel \ - libnghttp2-devel \ - wget tar patch findutils git \ - libtool \ - && microdnf clean all -y - -WORKDIR /build -RUN git clone https://github.com/skupperproject/skupper-router.git && mv skupper-router/* skupper-router/.[!.]* ./ && rm -rf skupper-router -ENV PROTON_VERSION=0.39.0 -ENV PROTON_SOURCE_URL=${PROTON_SOURCE_URL:-https://github.com/apache/qpid-proton/archive/${PROTON_VERSION}.tar.gz} -ENV LWS_VERSION=v4.3.3 -ENV LIBUNWIND_VERSION=v1.8.1 -ENV LWS_SOURCE_URL=${LWS_SOURCE_URL:-https://github.com/warmcat/libwebsockets/archive/refs/tags/${LWS_VERSION}.tar.gz} -ENV LIBUNWIND_SOURCE_URL=${LIBUNWIND_SOURCE_URL:-https://github.com/libunwind/libunwind/archive/refs/tags/${LIBUNWIND_VERSION}.tar.gz} -ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig - -ARG VERSION=0.0.0 -ENV VERSION=$VERSION -ARG PLATFORM=amd64 -ENV PLATFORM=$PLATFORM -RUN .github/scripts/compile.sh -RUN if [ "$PLATFORM" = "amd64" ]; then tar zxpf /qpid-proton-image.tar.gz --one-top-level=/image && tar zxpf /skupper-router-image.tar.gz --one-top-level=/image && tar zxpf /libwebsockets-image.tar.gz --one-top-level=/image && tar zxpf /libunwind-image.tar.gz --one-top-level=/image; fi -RUN if [ "$PLATFORM" = "arm64" ]; then tar zxpf /qpid-proton-image.tar.gz --one-top-level=/image && tar zxpf /skupper-router-image.tar.gz --one-top-level=/image && tar zxpf /libwebsockets-image.tar.gz --one-top-level=/image; fi - - -FROM golang:1.20.14-alpine AS go-builder - -ARG TARGETOS -ARG TARGETARCH - -RUN mkdir -p /go/src/github.com/datasance/router -WORKDIR /go/src/github.com/datasance/router -COPY . /go/src/github.com/datasance/router -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o bin/router - - -FROM registry.access.redhat.com/ubi9/ubi-minimal:latest - -# upgrade first to avoid fixable vulnerabilities -RUN microdnf -y upgrade --refresh --best --nodocs --noplugins --setopt=install_weak_deps=0 --setopt=keepcache=0 \ - && microdnf clean all -y - -RUN microdnf -y --setopt=install_weak_deps=0 --setopt=tsflags=nodocs install \ - glibc \ - cyrus-sasl-lib cyrus-sasl-plain cyrus-sasl-gssapi openssl \ - python3 \ - libnghttp2 \ - gettext hostname iputils \ - shadow-utils \ - && microdnf clean all - -# Remove gnutls, libarchive and everything that depends on it. -# https://github.com/skupperproject/skupper-router/issues/1477 -# https://github.com/skupperproject/skupper-router/issues/1639 -RUN microdnf -y remove gnutls glib2 gobject-introspection libpeas microdnf gnupg2 gpgme libdnf json-glib libmodulemd librepo librhsm libsolv rpm rpm-libs libarchive - -RUN useradd --uid 10000 runner -USER 10000 - -WORKDIR / -COPY --from=builder /image / - -WORKDIR /home/skrouterd/etc -WORKDIR /home/skrouterd/bin -COPY ./scripts/* /home/skrouterd/bin/ - -COPY LICENSE /licenses/LICENSE -COPY --from=go-builder /go/src/github.com/datasance/router/bin/router /home/skrouterd/bin/router -COPY scripts/launch.sh /home/skrouterd/bin/launch.sh - -ARG version=latest -ENV VERSION=${version} -ENV QDROUTERD_HOME=/home/skrouterd - -EXPOSE 5672 55672 5671 -CMD ["/home/skrouterd/bin/router"] \ No newline at end of file diff --git a/README.md b/README.md index 6fec2fc..d34b435 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ # iofog-router -Builds an image of the Apache Qpid Dispatch Router designed for use with Eclipse ioFog +Builds an image of the Apache Qpid Dispatch Router designed for use with Eclipse ioFog and Datasance Pot. The router can run in **Pot** mode (config from iofog agent) or **Kubernetes** mode (config from a volume-mounted file at `QDROUTERD_CONF`). + +## Environment variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `SKUPPER_PLATFORM` | `pot` | Mode: `pot` (config from iofog SDK) or `kubernetes` (config from file at `QDROUTERD_CONF`). | +| `QDROUTERD_CONF` | `/tmp/skrouterd.json` | Path to the router JSON config file. In Kubernetes mode the operator must volume-mount the router ConfigMap at this path. | +| `SSL_PROFILE_PATH` | `/etc/skupper-router-certs` | Directory under which SSL profile certs reside (e.g. `SSL_PROFILE_PATH//ca.crt`, `tls.crt`, `tls.key`). Certs are mounted here in both K8s and Pot. | + +In Kubernetes mode the router does not use the Kubernetes API; the operator is responsible for mounting the router config at `QDROUTERD_CONF`. Config file changes are watched and applied to the running router via qdr (same as Pot mode). diff --git a/go.mod b/go.mod index 0e63ed0..6957387 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.23.0 toolchain go1.24.3 require ( - github.com/datasance/iofog-go-sdk/v3 v3.5.2 + github.com/datasance/iofog-go-sdk/v3 v3.6.0 + github.com/fsnotify/fsnotify v1.7.0 github.com/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5 gotest.tools/v3 v3.5.2 - k8s.io/api v0.32.0 - k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 ) require ( @@ -21,24 +21,9 @@ require ( github.com/eapache/channels v1.1.0 // indirect github.com/eapache/queue v1.1.0 // indirect github.com/fortytw2/leaktest v1.3.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/x448/float16 v0.8.4 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/text v0.19.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - k8s.io/apimachinery v0.32.0 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index 6f6fcbd..aae8f31 100644 --- a/go.sum +++ b/go.sum @@ -19,61 +19,31 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/datasance/iofog-go-sdk/v3 v3.5.2 h1:URjli6rEJl1leGSujYD1lZdyDSy8scM+4DA4kCyPpPM= -github.com/datasance/iofog-go-sdk/v3 v3.5.2/go.mod h1:Gx/T77nGu3QvC93c96IDMEHJ2cdqZA84icl7R87ds84= +github.com/datasance/iofog-go-sdk/v3 v3.6.0 h1:HQ7AK3FrNDirgL8Kp5FPsfmRtdTImu+BXV36bZPGFqs= +github.com/datasance/iofog-go-sdk/v3 v3.6.0/go.mod h1:Gx/T77nGu3QvC93c96IDMEHJ2cdqZA84icl7R87ds84= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/eapache/channels v1.1.0 h1:F1taHcn7/F0i8DYqKXJnyhJcVpp2kgFcNePxXtnyu4k= github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5 h1:n3J6cCOpmsEXSEEFpXszM5kNsqtb7XiX3Q2bxeplWeQ= github.com/interconnectedcloud/go-amqp v0.12.6-0.20200506124159-f51e540008b5/go.mod h1:laGtnFhRcIocSgShx6P6FqnRqQoaXGEz87QpNXSnPS8= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -81,44 +51,25 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -127,6 +78,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -139,39 +92,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= -k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= -k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= -k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= -k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/internal/config/env.go b/internal/config/env.go new file mode 100644 index 0000000..e361cc5 --- /dev/null +++ b/internal/config/env.go @@ -0,0 +1,30 @@ +package config + +import ( + "os" + + "github.com/datasance/router/internal/resources/types" +) + +const ( + DefaultConfigPath = "/tmp/skrouterd.json" + DefaultSSLProfilePath = "/etc/skupper-router-certs" +) + +// GetConfigPath returns the router config file path from QDROUTERD_CONF, +// or DefaultConfigPath if unset. +func GetConfigPath() string { + if p := os.Getenv(types.TransportEnvConfig); p != "" { + return p + } + return DefaultConfigPath +} + +// GetSSLProfilePath returns the directory under which SSL profile certs +// reside (SSL_PROFILE_PATH env), or DefaultSSLProfilePath if unset. +func GetSSLProfilePath() string { + if p := os.Getenv(types.EnvSSLProfilePath); p != "" { + return p + } + return DefaultSSLProfilePath +} diff --git a/internal/config/env_test.go b/internal/config/env_test.go new file mode 100644 index 0000000..b21b8ee --- /dev/null +++ b/internal/config/env_test.go @@ -0,0 +1,44 @@ +package config + +import ( + "os" + "testing" + + "github.com/datasance/router/internal/resources/types" +) + +func TestGetConfigPath(t *testing.T) { + key := types.TransportEnvConfig + defer func() { _ = os.Unsetenv(key) }() + + // Default when unset + os.Unsetenv(key) + if got := GetConfigPath(); got != DefaultConfigPath { + t.Errorf("GetConfigPath() with unset env = %q, want %q", got, DefaultConfigPath) + } + + // Uses env when set + want := "/custom/skrouterd.json" + os.Setenv(key, want) + if got := GetConfigPath(); got != want { + t.Errorf("GetConfigPath() with env set = %q, want %q", got, want) + } +} + +func TestGetSSLProfilePath(t *testing.T) { + key := types.EnvSSLProfilePath + defer func() { _ = os.Unsetenv(key) }() + + // Default when unset + os.Unsetenv(key) + if got := GetSSLProfilePath(); got != DefaultSSLProfilePath { + t.Errorf("GetSSLProfilePath() with unset env = %q, want %q", got, DefaultSSLProfilePath) + } + + // Uses env when set + want := "/custom/certs" + os.Setenv(key, want) + if got := GetSSLProfilePath(); got != want { + t.Errorf("GetSSLProfilePath() with env set = %q, want %q", got, want) + } +} diff --git a/internal/config/platform.go b/internal/config/platform.go index 2379dcb..fd2b5b4 100644 --- a/internal/config/platform.go +++ b/internal/config/platform.go @@ -56,8 +56,18 @@ func GetPlatform() types.Platform { configuredPlatform = &platform case types.PlatformLinux: configuredPlatform = &platform + case types.PlatformPot: + configuredPlatform = &platform + case types.PlatformKubernetes: + configuredPlatform = &platform default: configuredPlatform = ptr.To(types.PlatformKubernetes) } return *configuredPlatform } + +// IsKubernetesRouterMode returns true when SKUPPER_PLATFORM is "kubernetes" +// (router config from ConfigMap). Default is pot (config from iofog SDK). +func IsKubernetesRouterMode() bool { + return os.Getenv(types.ENV_PLATFORM) == string(types.PlatformKubernetes) +} diff --git a/internal/qdr/amqp_mgmt.go b/internal/qdr/amqp_mgmt.go index ea66780..980ea55 100644 --- a/internal/qdr/amqp_mgmt.go +++ b/internal/qdr/amqp_mgmt.go @@ -10,10 +10,10 @@ import ( "strings" "time" - amqp "github.com/interconnectedcloud/go-amqp" - "github.com/datasance/router/internal/resources/types" "github.com/datasance/router/internal/config" + "github.com/datasance/router/internal/resources/types" "github.com/datasance/router/internal/utils" + amqp "github.com/interconnectedcloud/go-amqp" ) type RouterNode struct { diff --git a/internal/qdr/qdr.go b/internal/qdr/qdr.go index b4c3a3f..0fa31a6 100644 --- a/internal/qdr/qdr.go +++ b/internal/qdr/qdr.go @@ -10,8 +10,6 @@ import ( "strconv" "strings" - corev1 "k8s.io/api/core/v1" - "github.com/datasance/router/internal/resources/types" ) @@ -137,8 +135,8 @@ func (r *RouterConfig) IsEdge() bool { return r.Metadata.Mode == ModeEdge } -const SSL_PROFILE_PATH = "/etc/skupper-router-certs" - +// ConfigureSslProfile builds an SslProfile with file paths under the given base path. +// For the default SSL profile directory, use config.GetSSLProfilePath(). func ConfigureSslProfile(name string, path string, clientAuth bool) SslProfile { profile := SslProfile{ Name: name, @@ -791,29 +789,6 @@ func (r *RouterConfig) AsConfigMapData() (map[string]string, error) { return result, nil } -func (r *RouterConfig) WriteToConfigMap(configmap *corev1.ConfigMap) error { - var err error - configmap.Data, err = r.AsConfigMapData() - return err -} - -func (r *RouterConfig) UpdateConfigMap(configmap *corev1.ConfigMap) (bool, error) { - if configmap.Data != nil && configmap.Data[types.TransportConfigFile] != "" { - existing, err := UnmarshalRouterConfig(configmap.Data[types.TransportConfigFile]) - if err != nil { - return false, err - } - if reflect.DeepEqual(existing, *r) { - return false, nil - } - } - err := r.WriteToConfigMap(configmap) - if err != nil { - return false, err - } - return true, nil -} - type ListenerPredicate func(Listener) bool func IsNotNormalListener(l Listener) bool { @@ -834,47 +809,6 @@ func (config *RouterConfig) GetMatchingListeners(predicate ListenerPredicate) ma return FilterListeners(config.Listeners, predicate) } -func (b *BridgeConfig) UpdateConfigMap(configmap *corev1.ConfigMap) (bool, error) { - if configmap.Data != nil && configmap.Data[types.TransportConfigFile] != "" { - existing, err := UnmarshalRouterConfig(configmap.Data[types.TransportConfigFile]) - if err != nil { - return false, err - } - if reflect.DeepEqual(existing.Bridges, *b) { - return false, nil - } else { - existing.Bridges = *b - configmap.Data, err = existing.AsConfigMapData() - if err != nil { - return false, err - } - return true, nil - } - } else { - return false, fmt.Errorf("Router config not defined") - } -} - -func GetRouterConfigFromConfigMap(configmap *corev1.ConfigMap) (*RouterConfig, error) { - if configmap.Data == nil || configmap.Data[types.TransportConfigFile] == "" { - return nil, nil - } else { - routerConfig, err := UnmarshalRouterConfig(configmap.Data[types.TransportConfigFile]) - if err != nil { - return nil, err - } - return &routerConfig, nil - } -} - -func GetBridgeConfigFromConfigMap(configmap *corev1.ConfigMap) (*BridgeConfig, error) { - routerConfig, err := GetRouterConfigFromConfigMap(configmap) - if err != nil { - return nil, err - } - return &routerConfig.Bridges, nil -} - type ConnectorDifference struct { Deleted []Connector Added []Connector @@ -1210,35 +1144,6 @@ func GetInterRouterOrEdgeConnection(host string, connections []Connection) *Conn return nil } -func GetLinkStatus(s *corev1.Secret, edge bool, connections []Connection) types.LinkStatus { - link := types.LinkStatus{ - Name: s.ObjectMeta.Name, - } - if s.ObjectMeta.Labels[types.SkupperTypeQualifier] == types.TypeClaimRequest { - link.Url = s.ObjectMeta.Annotations[types.ClaimUrlAnnotationKey] - if desc, ok := s.ObjectMeta.Annotations[types.StatusAnnotationKey]; ok { - link.Description = "Failed to redeem claim: " + desc - } - link.Configured = false - } else { - if edge { - link.Url = fmt.Sprintf("%s:%s", s.ObjectMeta.Annotations["edge-host"], s.ObjectMeta.Annotations["edge-port"]) - } else { - link.Url = fmt.Sprintf("%s:%s", s.ObjectMeta.Annotations["inter-router-host"], s.ObjectMeta.Annotations["inter-router-port"]) - } - link.Configured = true - if connection := GetInterRouterOrEdgeConnection(link.Url, connections); connection != nil && connection.Active { - link.Connected = true - link.Cost, _ = strconv.Atoi(s.ObjectMeta.Annotations[types.TokenCost]) - link.Created = s.ObjectMeta.CreationTimestamp.String() - } - if s.ObjectMeta.Labels[types.SkupperDisabledQualifier] == "true" { - link.Description = "Destination host is not allowed" - } - } - return link -} - type ConfigUpdate interface { Apply(config *RouterConfig) bool } diff --git a/internal/resources/types/types.go b/internal/resources/types/types.go index ecd9e70..6b4d1c9 100644 --- a/internal/resources/types/types.go +++ b/internal/resources/types/types.go @@ -19,7 +19,8 @@ import ( ) const ( - ENV_PLATFORM = "SKUPPER_PLATFORM" + ENV_PLATFORM = "SKUPPER_PLATFORM" + EnvSSLProfilePath = "SSL_PROFILE_PATH" ) const ( @@ -65,7 +66,6 @@ const ( RouterMaxSessionFramesDefault int = 640 ) - // Controller and Collector constants const ( ControllerDeploymentName string = "skupper-service-controller" @@ -187,6 +187,7 @@ type Platform string const ( PlatformKubernetes Platform = "kubernetes" + PlatformPot Platform = "pot" PlatformPodman Platform = "podman" PlatformDocker Platform = "docker" PlatformLinux Platform = "linux" @@ -271,7 +272,6 @@ const ( // PrometheusCredentials []Credential `json:"prometheusCredentials,omitempty"` // } - // AssemblySpec for the links and connectors that form the VAN topology type AssemblySpec struct { Name string `json:"name,omitempty"` @@ -406,4 +406,4 @@ type LinkStatus struct { Configured bool Description string Created string -} \ No newline at end of file +} diff --git a/internal/router/router.go b/internal/router/router.go index 707b96a..7e1251c 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -14,133 +14,35 @@ package router import ( - "encoding/base64" "encoding/json" "fmt" - // "io/ioutil" "log" "os" "path/filepath" "time" + "github.com/datasance/router/internal/config" "github.com/datasance/router/internal/exec" "github.com/datasance/router/internal/qdr" ) -type IncomingSslProfile struct { - Name string `json:"name"` - TlsCert string `json:"tlsCert"` - TlsKey string `json:"tlsKey"` - CaCert string `json:"caCert"` -} - type Config struct { - Metadata qdr.RouterMetadata - SslProfiles map[string]IncomingSslProfile - ConvertedProfiles map[string]qdr.SslProfile - Listeners map[string]qdr.Listener - Connectors map[string]qdr.Connector - Addresses map[string]qdr.Address - LogConfig map[string]qdr.LogConfig - SiteConfig *qdr.SiteConfig - Bridges qdr.BridgeConfig + Metadata qdr.RouterMetadata + SslProfiles map[string]qdr.SslProfile + Listeners map[string]qdr.Listener + Connectors map[string]qdr.Connector + Addresses map[string]qdr.Address + LogConfig map[string]qdr.LogConfig + SiteConfig *qdr.SiteConfig + Bridges qdr.BridgeConfig } type Router struct { Config *Config } -func (router *Router) handleTLSFiles(sslProfile IncomingSslProfile) (qdr.SslProfile, error) { - log.Printf("DEBUG: Processing SSL profile: %s", sslProfile.Name) - - // Create base certs directory first with full permissions for non-root user - baseCertDir := "/home/runner/skupper-router-certs" - if err := os.MkdirAll(baseCertDir, 0777); err != nil { - log.Printf("ERROR: Failed to create base cert directory: %v", err) - return qdr.SslProfile{}, fmt.Errorf("failed to create base cert directory: %v", err) - } - - // Create profile-specific directory with full permissions - certDir := fmt.Sprintf("%s/%s", baseCertDir, sslProfile.Name) - if err := os.MkdirAll(certDir, 0777); err != nil { - log.Printf("ERROR: Failed to create cert directory: %v", err) - return qdr.SslProfile{}, fmt.Errorf("failed to create cert directory: %v", err) - } - - // Create a new qdr.SslProfile with the name - profile := qdr.SslProfile{ - Name: sslProfile.Name, - } - - // Convert base64 encoded strings to files and update the profile - if sslProfile.TlsCert != "" { - log.Printf("DEBUG: Processing TLS certificate for profile %s", sslProfile.Name) - certPath := filepath.Join(certDir, "tls.crt") - if err := decodeCertToFile(sslProfile.TlsCert, certPath); err != nil { - log.Printf("ERROR: Failed to write TLS certificate: %v", err) - return qdr.SslProfile{}, fmt.Errorf("failed to write TLS certificate: %v", err) - } - profile.CertFile = certPath - } - - if sslProfile.TlsKey != "" { - log.Printf("DEBUG: Processing TLS key for profile %s", sslProfile.Name) - keyPath := filepath.Join(certDir, "tls.key") - if err := decodeCertToFile(sslProfile.TlsKey, keyPath); err != nil { - log.Printf("ERROR: Failed to write TLS key: %v", err) - return qdr.SslProfile{}, fmt.Errorf("failed to write TLS key: %v", err) - } - profile.PrivateKeyFile = keyPath - } - - if sslProfile.CaCert != "" { - log.Printf("DEBUG: Processing CA certificate for profile %s", sslProfile.Name) - caPath := filepath.Join(certDir, "ca.crt") - if err := decodeCertToFile(sslProfile.CaCert, caPath); err != nil { - log.Printf("ERROR: Failed to write CA certificate: %v", err) - return qdr.SslProfile{}, fmt.Errorf("failed to write CA certificate: %v", err) - } - profile.CaCertFile = caPath - } - - log.Printf("DEBUG: Successfully processed SSL profile: %s", sslProfile.Name) - return profile, nil -} - -func decodeCertToFile(certString string, outputPath string) error { - log.Printf("DEBUG: Decoding certificate to file: %s", outputPath) - - decoded, err := base64.StdEncoding.DecodeString(certString) - if err != nil { - log.Printf("ERROR: Failed to decode certificate: %v", err) - return fmt.Errorf("failed to decode certificate: %v", err) - } - - if err := os.WriteFile(outputPath, decoded, 0644); err != nil { - log.Printf("ERROR: Failed to write certificate file: %v", err) - return fmt.Errorf("failed to write certificate file: %v", err) - } - - log.Printf("DEBUG: Successfully wrote certificate to %s", outputPath) - return nil -} - func (router *Router) UpdateRouter(newConfig *Config) error { log.Printf("DEBUG: Starting router configuration update") - // log.Printf("DEBUG: New configuration: %+v", newConfig) - - // Handle SSL profiles first and convert profiles - convertedProfiles := make(map[string]qdr.SslProfile) - for name, profile := range newConfig.SslProfiles { - log.Printf("DEBUG: Converting SSL profile: %s", name) - convertedProfile, err := router.handleTLSFiles(profile) - if err != nil { - log.Printf("ERROR: Failed to handle TLS files: %v", err) - return fmt.Errorf("failed to handle TLS files: %v", err) - } - convertedProfiles[name] = convertedProfile - } - newConfig.ConvertedProfiles = convertedProfiles // Create agent pool and get client log.Printf("DEBUG: Creating agent pool") @@ -171,13 +73,15 @@ func (router *Router) UpdateRouter(newConfig *Config) error { return fmt.Errorf("failed to update bridge config: %v", err) } - // Update the configuration file with the new TLS file paths - log.Printf("DEBUG: Updating router configuration file") - configJSON := router.GetRouterConfig() - configPath := "/home/runner/skupper-router-certs/skrouterd.json" - if err := os.WriteFile(configPath, []byte(configJSON), 0644); err != nil { - log.Printf("ERROR: Failed to write router configuration: %v", err) - return fmt.Errorf("failed to write router configuration: %v", err) + // Update the configuration file (skip on Kubernetes; config is read-only from ConfigMap) + if !config.IsKubernetesRouterMode() { + log.Printf("DEBUG: Updating router configuration file") + configJSON := router.GetRouterConfig() + configPath := config.GetConfigPath() + if err := os.WriteFile(configPath, []byte(configJSON), 0644); err != nil { + log.Printf("ERROR: Failed to write router configuration: %v", err) + return fmt.Errorf("failed to write router configuration: %v", err) + } } // Update the in-memory configuration @@ -192,6 +96,39 @@ func (router *Router) UpdateRouter(newConfig *Config) error { return nil } +// OnSSLProfilesFromDisk merges profiles (from SSL_PROFILE_PATH scan) into Config.SslProfiles, +// writes the router config file, and calls qdr ReloadSslProfile for each profile so the +// running router picks up cert rotation without restart. +func (r *Router) OnSSLProfilesFromDisk(profiles map[string]qdr.SslProfile) { + if r.Config == nil || r.Config.SslProfiles == nil { + return + } + for name, profile := range profiles { + r.Config.SslProfiles[name] = profile + } + // Write config file only on Pot; on Kubernetes config is read-only from ConfigMap + if !config.IsKubernetesRouterMode() { + configPath := config.GetConfigPath() + configJSON := r.GetRouterConfig() + if err := os.WriteFile(configPath, []byte(configJSON), 0644); err != nil { + log.Printf("ERROR: Failed to write router config after SSL profile update: %v", err) + return + } + } + agentPool := qdr.NewAgentPool("amqp://localhost:5672", nil) + client, err := agentPool.Get() + if err != nil { + log.Printf("ERROR: Failed to get qdr client for SSL profile reload: %v", err) + return + } + defer agentPool.Put(client) + for name := range profiles { + if err := client.ReloadSslProfile(name); err != nil { + log.Printf("ERROR: Failed to reload SSL profile %s: %v", name, err) + } + } +} + func (router *Router) GetRouterConfig() string { config := router.Config configElements := [][]interface{}{} @@ -202,18 +139,11 @@ func (router *Router) GetRouterConfig() string { config.Metadata, }) - // Add SSL profiles with updated file paths - for _, profile := range config.ConvertedProfiles { - // Create a clean sslProfile entry for the config file - sslProfile := qdr.SslProfile{ - Name: profile.Name, - CertFile: profile.CertFile, - PrivateKeyFile: profile.PrivateKeyFile, - CaCertFile: profile.CaCertFile, - } + // Add SSL profiles (file paths are already absolute) + for _, profile := range config.SslProfiles { configElements = append(configElements, []interface{}{ "sslProfile", - sslProfile, + profile, }) } @@ -285,47 +215,36 @@ func (router *Router) GetRouterConfig() string { func (router *Router) StartRouter(ch chan<- error) { log.Printf("DEBUG: Starting router with configuration") - // log.Printf("DEBUG: Router config: %+v", router.Config) - - // Handle TLS files first and convert profiles - convertedProfiles := make(map[string]qdr.SslProfile) - for name, profile := range router.Config.SslProfiles { - log.Printf("DEBUG: Converting SSL profile: %s", name) - convertedProfile, err := router.handleTLSFiles(profile) - if err != nil { - log.Printf("ERROR: Failed to handle TLS files: %v", err) - ch <- fmt.Errorf("failed to handle TLS files: %v", err) + + configPath := config.GetConfigPath() + // On Pot we create and write initial config; on Kubernetes config is already mounted at QDROUTERD_CONF + if !config.IsKubernetesRouterMode() { + log.Printf("DEBUG: Creating initial router configuration") + configJSON := router.GetRouterConfig() + + log.Printf("DEBUG: Ensuring configuration directory exists") + if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { + log.Printf("ERROR: Failed to create configuration directory: %v", err) + ch <- fmt.Errorf("failed to create configuration directory: %v", err) return } - convertedProfiles[name] = convertedProfile - } - router.Config.ConvertedProfiles = convertedProfiles - - // Create initial configuration - log.Printf("DEBUG: Creating initial router configuration") - config := router.GetRouterConfig() - configPath := "/home/runner/skupper-router-certs/skrouterd.json" - - // Ensure directory exists - log.Printf("DEBUG: Ensuring configuration directory exists") - if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { - log.Printf("ERROR: Failed to create configuration directory: %v", err) - ch <- fmt.Errorf("failed to create configuration directory: %v", err) - return - } - // Write initial configuration - log.Printf("DEBUG: Writing initial configuration to %s", configPath) - if err := os.WriteFile(configPath, []byte(config), 0644); err != nil { - log.Printf("ERROR: Failed to write initial configuration: %v", err) - ch <- fmt.Errorf("failed to write initial configuration: %v", err) - return + log.Printf("DEBUG: Writing initial configuration to %s", configPath) + if err := os.WriteFile(configPath, []byte(configJSON), 0644); err != nil { + log.Printf("ERROR: Failed to write initial configuration: %v", err) + ch <- fmt.Errorf("failed to write initial configuration: %v", err) + return + } } - // Start router with JSON configuration + // Start router with QDROUTERD_CONF and QDROUTERD_CONF_TYPE so launch script uses our config file + env := []string{ + fmt.Sprintf("QDROUTERD_CONF=%s", configPath), + "QDROUTERD_CONF_TYPE=json", + } exitChannel := make(chan error) log.Printf("DEBUG: Starting router process") - go exec.Run(ch, "/home/skrouterd/bin/launch.sh", []string{}, []string{}) + go exec.Run(ch, "/home/skrouterd/bin/launch.sh", []string{}, env) // Monitor for configuration updates go func() { diff --git a/internal/watch/config.go b/internal/watch/config.go new file mode 100644 index 0000000..41724d4 --- /dev/null +++ b/internal/watch/config.go @@ -0,0 +1,86 @@ +package watch + +import ( + "context" + "log" + "os" + "path/filepath" + "sync" + "time" + + "github.com/fsnotify/fsnotify" +) + +const configDebounceDuration = 500 * time.Millisecond + +// WatchConfigFile watches the config file at configPath for changes. On write/create +// (after debounce), it reads the file and calls onUpdate with the content. Loop +// prevention is the caller's responsibility: compare content with last applied and +// skip calling UpdateRouter if unchanged. Runs until ctx is cancelled. +func WatchConfigFile(ctx context.Context, configPath string, onUpdate func(configJSON string) error) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Printf("ERROR: Failed to create fsnotify watcher for config file: %v", err) + return + } + defer watcher.Close() + + dir := filepath.Dir(configPath) + if err := os.MkdirAll(dir, 0755); err != nil { + log.Printf("ERROR: Failed to create config dir %s: %v", dir, err) + return + } + if err := watcher.Add(dir); err != nil { + log.Printf("ERROR: Failed to add watch on %s: %v", dir, err) + return + } + + var debounceTimer *time.Timer + var debounceMu sync.Mutex + scheduleRead := func() { + debounceMu.Lock() + if debounceTimer != nil { + debounceTimer.Stop() + } + debounceTimer = time.AfterFunc(configDebounceDuration, func() { + data, err := os.ReadFile(configPath) + if err != nil { + if !os.IsNotExist(err) { + log.Printf("ERROR: Failed to read config file %s: %v", configPath, err) + } + return + } + if len(data) == 0 { + return + } + if onUpdate(string(data)) != nil { + // Caller logs the error + return + } + }) + debounceMu.Unlock() + } + + for { + select { + case <-ctx.Done(): + return + case event, ok := <-watcher.Events: + if !ok { + return + } + // We watch the directory; only react to changes to our config file + if filepath.Clean(event.Name) != filepath.Clean(configPath) { + continue + } + if event.Op&(fsnotify.Create|fsnotify.Write) != 0 { + scheduleRead() + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Printf("ERROR: Config file watcher error: %v", err) + } + } +} diff --git a/internal/watch/ssl.go b/internal/watch/ssl.go new file mode 100644 index 0000000..8ba3209 --- /dev/null +++ b/internal/watch/ssl.go @@ -0,0 +1,141 @@ +package watch + +import ( + "context" + "log" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/fsnotify/fsnotify" + + "github.com/datasance/router/internal/qdr" +) + +const debounceDuration = 500 * time.Millisecond + +// ScanSSLProfileDir scans basePath for profile subdirs (each with ca.crt, and optionally tls.crt/tls.key) +// and returns a map of profile name to qdr.SslProfile with absolute paths. +func ScanSSLProfileDir(basePath string) (map[string]qdr.SslProfile, error) { + entries, err := os.ReadDir(basePath) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, err + } + profiles := make(map[string]qdr.SslProfile) + for _, e := range entries { + if !e.IsDir() { + continue + } + name := e.Name() + dir := filepath.Join(basePath, name) + caPath := filepath.Join(dir, "ca.crt") + certPath := filepath.Join(dir, "tls.crt") + keyPath := filepath.Join(dir, "tls.key") + if _, err := os.Stat(caPath); err != nil { + continue + } + profile := qdr.SslProfile{ + Name: name, + CaCertFile: caPath, + } + if _, err := os.Stat(certPath); err == nil { + profile.CertFile = certPath + } + if _, err := os.Stat(keyPath); err == nil { + profile.PrivateKeyFile = keyPath + } + profiles[name] = profile + } + return profiles, nil +} + +// WatchSSLProfileDir watches basePath (and subdirs) for changes, debounces events, +// then rescans and calls onUpdate with the new profiles map. Runs until ctx is cancelled. +func WatchSSLProfileDir(ctx context.Context, basePath string, onUpdate func(profiles map[string]qdr.SslProfile)) { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Printf("ERROR: Failed to create fsnotify watcher for SSL profile path: %v", err) + return + } + defer watcher.Close() + if err := watcher.Add(basePath); err != nil { + if !os.IsNotExist(err) { + log.Printf("ERROR: Failed to add watch on %s: %v", basePath, err) + } + return + } + // Watch new subdirs when they appear + subdirs := make(map[string]struct{}) + var mu sync.Mutex + addSubdir := func(path string) { + if path == basePath { + return + } + rel, err := filepath.Rel(basePath, path) + if err != nil || len(rel) == 0 || rel == ".." || strings.HasPrefix(rel, "..") { + return + } + if filepath.Dir(rel) != "." { + return + } + mu.Lock() + if _, ok := subdirs[path]; !ok { + subdirs[path] = struct{}{} + _ = watcher.Add(path) + } + mu.Unlock() + } + // Initial scan of subdirs + if entries, err := os.ReadDir(basePath); err == nil { + for _, e := range entries { + if e.IsDir() { + addSubdir(filepath.Join(basePath, e.Name())) + } + } + } + var debounceTimer *time.Timer + var debounceMu sync.Mutex + scheduleRescan := func() { + debounceMu.Lock() + if debounceTimer != nil { + debounceTimer.Stop() + } + debounceTimer = time.AfterFunc(debounceDuration, func() { + profiles, err := ScanSSLProfileDir(basePath) + if err != nil { + log.Printf("ERROR: Failed to rescan SSL profile dir: %v", err) + return + } + if len(profiles) > 0 { + onUpdate(profiles) + } + }) + debounceMu.Unlock() + } + for { + select { + case <-ctx.Done(): + return + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Op&(fsnotify.Create|fsnotify.Write|fsnotify.Remove) != 0 { + if info, err := os.Stat(event.Name); err == nil && info.IsDir() && event.Op == fsnotify.Create { + addSubdir(event.Name) + } + scheduleRescan() + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Printf("ERROR: SSL profile watcher error: %v", err) + } + } +} diff --git a/internal/watch/ssl_test.go b/internal/watch/ssl_test.go new file mode 100644 index 0000000..24fa533 --- /dev/null +++ b/internal/watch/ssl_test.go @@ -0,0 +1,112 @@ +package watch + +import ( + "os" + "path/filepath" + "testing" + + "github.com/datasance/router/internal/qdr" +) + +func TestScanSSLProfileDir(t *testing.T) { + dir := t.TempDir() + + // Empty dir returns empty map + profiles, err := ScanSSLProfileDir(dir) + if err != nil { + t.Fatal(err) + } + if len(profiles) != 0 { + t.Errorf("empty dir: got %d profiles, want 0", len(profiles)) + } + + // Subdir with ca.crt only + profile1 := filepath.Join(dir, "profile1") + if err := os.MkdirAll(profile1, 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(profile1, "ca.crt"), []byte("ca"), 0644); err != nil { + t.Fatal(err) + } + profiles, err = ScanSSLProfileDir(dir) + if err != nil { + t.Fatal(err) + } + if len(profiles) != 1 { + t.Fatalf("got %d profiles, want 1", len(profiles)) + } + p, ok := profiles["profile1"] + if !ok { + t.Fatal("profile1 not found") + } + if p.Name != "profile1" { + t.Errorf("profile name = %q, want profile1", p.Name) + } + absCa := filepath.Join(dir, "profile1", "ca.crt") + if p.CaCertFile != absCa { + t.Errorf("CaCertFile = %q, want %q", p.CaCertFile, absCa) + } + if p.CertFile != "" || p.PrivateKeyFile != "" { + t.Errorf("expected no cert/key when only ca.crt present") + } + + // Subdir with ca.crt, tls.crt, tls.key + profile2 := filepath.Join(dir, "profile2") + if err := os.MkdirAll(profile2, 0755); err != nil { + t.Fatal(err) + } + for _, f := range []string{"ca.crt", "tls.crt", "tls.key"} { + if err := os.WriteFile(filepath.Join(profile2, f), []byte(f), 0644); err != nil { + t.Fatal(err) + } + } + profiles, err = ScanSSLProfileDir(dir) + if err != nil { + t.Fatal(err) + } + if len(profiles) != 2 { + t.Fatalf("got %d profiles, want 2", len(profiles)) + } + p2, ok := profiles["profile2"] + if !ok { + t.Fatal("profile2 not found") + } + if p2.CaCertFile != filepath.Join(dir, "profile2", "ca.crt") { + t.Errorf("profile2 CaCertFile = %q", p2.CaCertFile) + } + if p2.CertFile != filepath.Join(dir, "profile2", "tls.crt") { + t.Errorf("profile2 CertFile = %q", p2.CertFile) + } + if p2.PrivateKeyFile != filepath.Join(dir, "profile2", "tls.key") { + t.Errorf("profile2 PrivateKeyFile = %q", p2.PrivateKeyFile) + } +} + +func TestScanSSLProfileDir_Nonexistent(t *testing.T) { + profiles, err := ScanSSLProfileDir(filepath.Join(t.TempDir(), "nonexistent")) + if err != nil { + t.Fatal(err) + } + if profiles != nil { + t.Errorf("nonexistent dir: got %v, want nil", profiles) + } +} + +func TestScanSSLProfileDir_ProfilesAreQdrSslProfile(t *testing.T) { + dir := t.TempDir() + profileDir := filepath.Join(dir, "myprofile") + if err := os.MkdirAll(profileDir, 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(profileDir, "ca.crt"), []byte("x"), 0644); err != nil { + t.Fatal(err) + } + profiles, err := ScanSSLProfileDir(dir) + if err != nil { + t.Fatal(err) + } + var _ map[string]qdr.SslProfile = profiles + if len(profiles) != 1 { + t.Fatalf("got %d profiles", len(profiles)) + } +} diff --git a/main.go b/main.go index e24d244..2162854 100644 --- a/main.go +++ b/main.go @@ -14,14 +14,18 @@ package main import ( + "context" "errors" "log" "os" - - rt "github.com/datasance/router/internal/router" + "sync" + "time" sdk "github.com/datasance/iofog-go-sdk/v3/pkg/microservices" + "github.com/datasance/router/internal/config" qdr "github.com/datasance/router/internal/qdr" + rt "github.com/datasance/router/internal/router" + "github.com/datasance/router/internal/watch" ) var ( @@ -31,12 +35,11 @@ var ( func init() { router = new(rt.Router) router.Config = &rt.Config{ - SslProfiles: make(map[string]rt.IncomingSslProfile), - ConvertedProfiles: make(map[string]qdr.SslProfile), - Listeners: make(map[string]qdr.Listener), - Connectors: make(map[string]qdr.Connector), - Addresses: make(map[string]qdr.Address), - LogConfig: make(map[string]qdr.LogConfig), + SslProfiles: make(map[string]qdr.SslProfile), + Listeners: make(map[string]qdr.Listener), + Connectors: make(map[string]qdr.Connector), + Addresses: make(map[string]qdr.Address), + LogConfig: make(map[string]qdr.LogConfig), Bridges: qdr.BridgeConfig{ TcpListeners: make(map[string]qdr.TcpEndpoint), TcpConnectors: make(map[string]qdr.TcpEndpoint), @@ -45,32 +48,108 @@ func init() { } func main() { + if config.IsKubernetesRouterMode() { + runKubernetesMode() + return + } + runPotMode() +} + +func runKubernetesMode() { + configPath := config.GetConfigPath() + // Config file is volume-mounted by the operator at QDROUTERD_CONF; retry briefly if not yet present. + var data []byte + var err error + for i := 0; i < 30; i++ { + data, err = os.ReadFile(configPath) + if err == nil { + break + } + if os.IsNotExist(err) && i < 29 { + time.Sleep(time.Second) + continue + } + log.Fatalf("Failed to read router config from %s: %v", configPath, err) + } + qdrConfig, err := qdr.UnmarshalRouterConfig(string(data)) + if err != nil { + log.Fatalf("Failed to unmarshal router config: %v", err) + } + router.Config = &rt.Config{ + Metadata: qdrConfig.Metadata, + SslProfiles: qdrConfig.SslProfiles, + Listeners: qdrConfig.Listeners, + Connectors: qdrConfig.Connectors, + Addresses: qdrConfig.Addresses, + LogConfig: qdrConfig.LogConfig, + SiteConfig: qdrConfig.SiteConfig, + Bridges: qdrConfig.Bridges, + } + exitChannel := make(chan error) + go router.StartRouter(exitChannel) + ctx := context.Background() + var lastAppliedMu sync.Mutex + lastApplied := string(data) + go watch.WatchConfigFile(ctx, configPath, func(configJSON string) error { + lastAppliedMu.Lock() + same := lastApplied == configJSON + lastAppliedMu.Unlock() + if same { + return nil + } + qdrConfig, err := qdr.UnmarshalRouterConfig(configJSON) + if err != nil { + log.Printf("ERROR: Failed to unmarshal router config from file: %v", err) + return err + } + newConfig := &rt.Config{ + Metadata: qdrConfig.Metadata, + SslProfiles: qdrConfig.SslProfiles, + Listeners: qdrConfig.Listeners, + Connectors: qdrConfig.Connectors, + Addresses: qdrConfig.Addresses, + LogConfig: qdrConfig.LogConfig, + SiteConfig: qdrConfig.SiteConfig, + Bridges: qdrConfig.Bridges, + } + if err := router.UpdateRouter(newConfig); err != nil { + log.Printf("ERROR: Failed to update router from config file: %v", err) + return err + } + lastAppliedMu.Lock() + lastApplied = configJSON + lastAppliedMu.Unlock() + return nil + }) + go watch.WatchSSLProfileDir(ctx, config.GetSSLProfilePath(), router.OnSSLProfilesFromDisk) + <-exitChannel + os.Exit(0) +} + +func runPotMode() { ioFogClient, clientError := sdk.NewDefaultIoFogClient() if clientError != nil { log.Fatalln(clientError.Error()) } - if err := updateConfig(ioFogClient, router.Config); err != nil { log.Fatalln(err.Error()) } - confChannel := ioFogClient.EstablishControlWsConnection(0) - exitChannel := make(chan error) go router.StartRouter(exitChannel) - + ctx := context.Background() + go watch.WatchSSLProfileDir(ctx, config.GetSSLProfilePath(), router.OnSSLProfilesFromDisk) for { select { case <-exitChannel: os.Exit(0) case <-confChannel: newConfig := &rt.Config{ - SslProfiles: make(map[string]rt.IncomingSslProfile), - ConvertedProfiles: make(map[string]qdr.SslProfile), - Listeners: make(map[string]qdr.Listener), - Connectors: make(map[string]qdr.Connector), - Addresses: make(map[string]qdr.Address), - LogConfig: make(map[string]qdr.LogConfig), + SslProfiles: make(map[string]qdr.SslProfile), + Listeners: make(map[string]qdr.Listener), + Connectors: make(map[string]qdr.Connector), + Addresses: make(map[string]qdr.Address), + LogConfig: make(map[string]qdr.LogConfig), Bridges: qdr.BridgeConfig{ TcpListeners: make(map[string]qdr.TcpEndpoint), TcpConnectors: make(map[string]qdr.TcpEndpoint), diff --git a/scripts/launch.sh b/scripts/launch.sh index 1ff6e4b..8e51a1c 100755 --- a/scripts/launch.sh +++ b/scripts/launch.sh @@ -1,13 +1,12 @@ -#!/bin/sh +#!/bin/bash -QDROUTERD_HOME=/home/skrouterd -CONFIG_FILE=/tmp/skrouterd.conf +export HOSTNAME_IP_ADDRESS=$(hostname -i) -rm -f $CONFIG_FILE -echo "${QDROUTERD_CONF}" | awk '{gsub(/\\n/,"\n")}1' >> $CONFIG_FILE +EXT=${QDROUTERD_CONF_TYPE:-conf} +CONFIG_FILE=/tmp/skrouterd.${EXT} -echo "--------------------------------------------------------------" -cat $CONFIG_FILE -echo "--------------------------------------------------------------" +if [ -f $CONFIG_FILE ]; then + ARGS="-c $CONFIG_FILE" +fi -exec skrouterd -c $CONFIG_FILE \ No newline at end of file +exec skrouterd $ARGS \ No newline at end of file