diff --git a/.github/workflows/pre_merge.yml b/.github/workflows/pre_merge.yml index bae56f8..b77a048 100644 --- a/.github/workflows/pre_merge.yml +++ b/.github/workflows/pre_merge.yml @@ -15,7 +15,8 @@ jobs: - name: Setup go uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.23' + cache: false - name: Run tests run: | go test -v -race ./... \ No newline at end of file diff --git a/README.md b/README.md index 221625f..04923af 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ ## Features -| Package | Examples | Use Case | -| --------- | ------------------- | ---------------------------- | -| api | http | build gateway servers | -| broker | sns+sqs | asynchronous communication | -| client | grpc, http | synchronous communication | -| runner | docker | test after file (.yml) apply | -| runtime | kubernetes | service management | -| security | jwts, ssm, autocert | tokens, secrets, and certs | -| server | grpc | build backend servers | -| sidecar | custom | build sidecars | -| store | cockroach | data persistence | -| telemetry | otel | logs, metrics, and traces | +| Package | Examples | Use Case | +| --------- | -------------------- | -------------------------- | +| api | http | build gateway servers | +| broker | sns+sqs | asynchronous communication | +| client | grpc, http | synchronous communication | +| runner | docker, binary, http | setup and run tests | +| runtime | kubernetes | service management | +| security | jwts, ssm, autocert | tokens, secrets, and certs | +| server | grpc | build backend servers | +| sidecar | custom | build sidecars | +| store | cockroach | data persistence | +| telemetry | otel | logs, metrics, and traces | diff --git a/go.mod b/go.mod index eea9ffa..1c83347 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/sns v1.31.5 github.com/aws/aws-sdk-go-v2/service/sqs v1.34.5 github.com/aws/smithy-go v1.20.4 - github.com/docker/docker v27.2.0+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.6.0 github.com/gorilla/handlers v1.5.2 @@ -27,7 +26,6 @@ require ( ) require ( - github.com/Microsoft/go-winio v0.4.14 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.29 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect @@ -38,16 +36,11 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.4 // indirect @@ -60,25 +53,13 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/gomega v1.33.1 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sys v0.24.0 // indirect @@ -89,7 +70,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gotest.tools/v3 v3.5.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect diff --git a/go.sum b/go.sum index 7f0e483..60dfe6b 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,3 @@ -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= github.com/aws/aws-sdk-go-v2/config v1.27.30 h1:AQF3/+rOgeJBQP3iI4vojlPib5X6eeOYoa/af7OxAYg= @@ -32,34 +28,19 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.5 h1:OMsEmCyz2i89XwRwPouAJvhj81wI github.com/aws/aws-sdk-go-v2/service/sts v1.30.5/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= -github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 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.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= @@ -89,8 +70,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -99,7 +78,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr 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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -111,47 +89,29 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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.1.1/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= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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= @@ -164,22 +124,6 @@ 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= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 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= @@ -198,10 +142,8 @@ golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht 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/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -223,8 +165,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= @@ -242,8 +182,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= diff --git a/runner/binary/options.go b/runner/binary/options.go new file mode 100644 index 0000000..b704aed --- /dev/null +++ b/runner/binary/options.go @@ -0,0 +1 @@ +package binary diff --git a/runner/binary/process.go b/runner/binary/process.go new file mode 100644 index 0000000..e09dc3f --- /dev/null +++ b/runner/binary/process.go @@ -0,0 +1,90 @@ +package binary + +import ( + "os" + "os/exec" + "sync" + "time" + + "github.com/w-h-a/pkg/runner" +) + +type binaryProcess struct { + options runner.ProcessOptions + upCmd *exec.Cmd + downCmd *exec.Cmd + mtx sync.RWMutex +} + +func (p *binaryProcess) Options() runner.ProcessOptions { + return p.options +} + +func (p *binaryProcess) Apply() error { + p.mtx.Lock() + defer p.mtx.Unlock() + + p.upCmd = exec.Command(p.options.UpBinPath, p.options.UpArgs...) + + if err := runner.Outputs(p.upCmd); err != nil { + return err + } + + for k, v := range p.options.EnvVars { + p.upCmd.Env = append(p.upCmd.Env, k+"="+v) + } + + errCh := make(chan error) + + go func() { + err := p.upCmd.Start() + for p.upCmd.Process == nil { + time.Sleep(1 * time.Second) + } + errCh <- err + }() + + return <-errCh +} + +func (p *binaryProcess) Destroy() error { + p.mtx.Lock() + defer p.mtx.Unlock() + + if p.upCmd != nil && p.upCmd.Process != nil { + if err := p.upCmd.Process.Signal(os.Interrupt); err != nil { + return err + } + } + + if len(p.options.DownBinPath) == 0 { + return nil + } + + p.downCmd = exec.Command(p.options.DownBinPath, p.options.DownArgs...) + + if err := runner.Outputs(p.downCmd); err != nil { + return err + } + + if err := p.downCmd.Run(); err != nil { + return err + } + + return nil +} + +func (p *binaryProcess) String() string { + return "binary" +} + +func NewProcess(opts ...runner.ProcessOption) runner.Process { + options := runner.NewProcessOptions(opts...) + + p := &binaryProcess{ + options: options, + mtx: sync.RWMutex{}, + } + + return p +} diff --git a/runner/docker/client.go b/runner/docker/client.go deleted file mode 100644 index 7280791..0000000 --- a/runner/docker/client.go +++ /dev/null @@ -1,28 +0,0 @@ -package docker - -import ( - dockerclient "github.com/docker/docker/client" - "github.com/w-h-a/pkg/telemetry/log" -) - -type DockerClient interface { -} - -type dockerClient struct { - *dockerclient.Client -} - -func NewDockerClient() DockerClient { - client, err := dockerclient.NewClientWithOpts(dockerclient.WithHost(dockerclient.DefaultDockerHost)) - if err != nil { - log.Fatal(err) - } - - return &dockerClient{client} -} - -type dockerMock struct{} - -func NewDockerMock() DockerClient { - return &dockerMock{} -} diff --git a/runner/docker/manager.go b/runner/docker/manager.go deleted file mode 100644 index da3c167..0000000 --- a/runner/docker/manager.go +++ /dev/null @@ -1,109 +0,0 @@ -package docker - -import ( - "bufio" - "fmt" - "io" - "os" - "os/exec" - "strings" - - "github.com/w-h-a/pkg/runner" -) - -type dockerManager struct { - options runner.ManagerOptions -} - -type ioPair struct { - in io.ReadCloser - out *os.File -} - -func (d *dockerManager) Options() runner.ManagerOptions { - return d.options -} - -func (d *dockerManager) Apply() error { - cmd := exec.Command("docker", "compose", "--file", d.options.File.Path, "up", "--build", "--detach") - - if err := d.outputs(cmd); err != nil { - return err - } - - if err := cmd.Run(); err != nil { - return err - } - - return nil -} - -func (d *dockerManager) Destroy() error { - cmd := exec.Command("docker", "compose", "--file", d.options.File.Path, "down", "--volumes") - - if err := d.outputs(cmd); err != nil { - return err - } - - if err := cmd.Run(); err != nil { - return err - } - - return nil -} - -func (d *dockerManager) String() string { - return "docker" -} - -func (*dockerManager) outputs(cmd *exec.Cmd) error { - stdout, err := cmd.StdoutPipe() - if err != nil { - return err - } - - stderr, err := cmd.StderrPipe() - if err != nil { - return err - } - - pairs := []ioPair{ - {in: stdout, out: os.Stdout}, - {in: stderr, out: os.Stderr}, - } - - for _, ioPair := range pairs { - go func(in io.ReadCloser, out *os.File) { - defer in.Close() - - reader := bufio.NewReader(in) - - for { - s, err := reader.ReadString('\n') - if err == nil || err == io.EOF { - if len(strings.TrimSpace(s)) != 0 { - fmt.Fprintf(out, "%s\n", s) - } - if err == io.EOF { - return - } - } else { - fmt.Fprintf(out, "error: %s\n", err.Error()) - return - } - } - }(ioPair.in, ioPair.out) - } - - return nil -} - -func NewManager(opts ...runner.ManagerOption) runner.Manager { - options := runner.NewManagerOptions(opts...) - - d := &dockerManager{ - options: options, - } - - return d -} diff --git a/runner/docker/options.go b/runner/docker/options.go index 4d676f3..1cdc3ff 100644 --- a/runner/docker/options.go +++ b/runner/docker/options.go @@ -1,20 +1 @@ package docker - -import ( - "context" - - "github.com/w-h-a/pkg/runner" -) - -type clientKey struct{} - -func DockerRunnerWithClient(c DockerClient) runner.RunnerOption { - return func(o *runner.RunnerOptions) { - o.Context = context.WithValue(o.Context, clientKey{}, c) - } -} - -func GetClientFromContext(ctx context.Context) (DockerClient, bool) { - c, ok := ctx.Value(clientKey{}).(DockerClient) - return c, ok -} diff --git a/runner/docker/process.go b/runner/docker/process.go new file mode 100644 index 0000000..291d614 --- /dev/null +++ b/runner/docker/process.go @@ -0,0 +1,68 @@ +package docker + +import ( + "os/exec" + "sync" + + "github.com/w-h-a/pkg/runner" +) + +type dockerProcess struct { + options runner.ProcessOptions + upCmd *exec.Cmd + downCmd *exec.Cmd + mtx sync.RWMutex +} + +func (p *dockerProcess) Options() runner.ProcessOptions { + return p.options +} + +func (p *dockerProcess) Apply() error { + p.mtx.Lock() + defer p.mtx.Unlock() + + p.upCmd = exec.Command(p.options.UpBinPath, p.options.UpArgs...) + + if err := runner.Outputs(p.upCmd); err != nil { + return err + } + + if err := p.upCmd.Run(); err != nil { + return err + } + + return nil +} + +func (p *dockerProcess) Destroy() error { + p.mtx.Lock() + defer p.mtx.Unlock() + + p.downCmd = exec.Command(p.options.DownBinPath, p.options.DownArgs...) + + if err := runner.Outputs(p.downCmd); err != nil { + return err + } + + if err := p.downCmd.Run(); err != nil { + return err + } + + return nil +} + +func (p *dockerProcess) String() string { + return "docker" +} + +func NewProcess(opts ...runner.ProcessOption) runner.Process { + options := runner.NewProcessOptions(opts...) + + p := &dockerProcess{ + options: options, + mtx: sync.RWMutex{}, + } + + return p +} diff --git a/runner/docker/runner.go b/runner/docker/runner.go deleted file mode 100644 index 90fd63e..0000000 --- a/runner/docker/runner.go +++ /dev/null @@ -1,135 +0,0 @@ -package docker - -import ( - "sync" - "testing" - - "github.com/w-h-a/pkg/runner" - "github.com/w-h-a/pkg/telemetry/log" -) - -type dockerRunner struct { - options runner.RunnerOptions - inactive []runner.Manager - inactiveMtx sync.RWMutex - active []runner.Manager - activeMtx sync.RWMutex - client DockerClient -} - -func (r *dockerRunner) Options() runner.RunnerOptions { - return r.options -} - -func (r *dockerRunner) Start(m *testing.M) int { - defer r.Stop() - - if err := r.register(); err != nil { - return runner.FailExitCode - } - - if err := r.apply(); err != nil { - return runner.FailExitCode - } - - return m.Run() -} - -func (r *dockerRunner) Stop() { - r.destroy() -} - -func (r *dockerRunner) String() string { - return "docker" -} - -func (r *dockerRunner) register() error { - for _, f := range r.options.Files { - d := NewManager( - runner.ManagerWithFile(f), - ) - - r.inactiveMtx.Lock() - r.inactive = append(r.inactive, d) - r.inactiveMtx.Unlock() - } - - return nil -} - -func (r *dockerRunner) apply() error { - for d := r.dequeue(); d != nil; d = r.dequeue() { - if err := d.Apply(); err != nil { - log.Errorf("failed to apply %s: %v", d.Options().File.Path, err) - return err - } - - r.activeMtx.Lock() - r.active = append(r.active, d) - r.activeMtx.Unlock() - - log.Infof("successfully applied %s", d.Options().File.Path) - } - - return nil -} - -func (r *dockerRunner) destroy() { - for d := r.pop(); d != nil; d = r.pop() { - if err := d.Destroy(); err != nil { - log.Errorf("failed to destroy %s: %v", d.Options().File.Path, err) - } else { - log.Infof("successfully destroyed %s", d.Options().File.Path) - } - } -} - -func (r *dockerRunner) dequeue() runner.Manager { - r.inactiveMtx.Lock() - defer r.inactiveMtx.Unlock() - - if len(r.inactive) == 0 { - return nil - } - - d := r.inactive[0] - - r.inactive = r.inactive[1:] - - return d -} - -func (r *dockerRunner) pop() runner.Manager { - r.activeMtx.Lock() - defer r.activeMtx.Unlock() - - if len(r.active) == 0 { - return nil - } - - d := r.active[len(r.active)-1] - - r.active = r.active[:len(r.active)-1] - - return d -} - -func NewTestRunner(opts ...runner.RunnerOption) runner.TestRunner { - options := runner.NewRunnerOptions(opts...) - - r := &dockerRunner{ - options: options, - inactive: []runner.Manager{}, - inactiveMtx: sync.RWMutex{}, - active: []runner.Manager{}, - activeMtx: sync.RWMutex{}, - } - - if c, ok := GetClientFromContext(options.Context); ok { - r.client = c - } else { - r.client = NewDockerClient() - } - - return r -} diff --git a/runner/domain.go b/runner/domain.go deleted file mode 100644 index f050cd5..0000000 --- a/runner/domain.go +++ /dev/null @@ -1,5 +0,0 @@ -package runner - -type File struct { - Path string -} diff --git a/runner/http/options.go b/runner/http/options.go new file mode 100644 index 0000000..1e4dbdc --- /dev/null +++ b/runner/http/options.go @@ -0,0 +1,30 @@ +package http + +import ( + "context" + "net/http" + + "github.com/w-h-a/pkg/runner" +) + +type handlerFuncsKey struct{} + +func HttpProcessWithHandlerFuncs(route string, fun http.HandlerFunc) runner.ProcessOption { + return func(o *runner.ProcessOptions) { + funs := map[string]http.HandlerFunc{} + + if m, ok := GetHandlerFuncsFromContext(o.Context); ok && m != nil { + m[route] = fun + funs = m + } else { + funs[route] = fun + } + + o.Context = context.WithValue(o.Context, handlerFuncsKey{}, funs) + } +} + +func GetHandlerFuncsFromContext(ctx context.Context) (map[string]http.HandlerFunc, bool) { + funs, ok := ctx.Value(handlerFuncsKey{}).(map[string]http.HandlerFunc) + return funs, ok +} diff --git a/runner/http/process.go b/runner/http/process.go new file mode 100644 index 0000000..741984a --- /dev/null +++ b/runner/http/process.go @@ -0,0 +1,104 @@ +package http + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "strconv" + "time" + + "github.com/w-h-a/pkg/runner" + "github.com/w-h-a/pkg/telemetry/log" +) + +type httpProcess struct { + options runner.ProcessOptions + listener net.Listener + server *http.Server + errCh chan error + exit chan struct{} +} + +func (p *httpProcess) Options() runner.ProcessOptions { + return p.options +} + +func (p *httpProcess) Apply() error { + go func() { + err := p.server.Serve(p.listener) + if !errors.Is(err, http.ErrServerClosed) { + p.errCh <- err + } else { + p.errCh <- nil + } + }() + + go func() { + <-p.exit + log.Infof("http process received interrupt signal") + p.errCh <- p.server.Shutdown(context.Background()) + }() + + return nil +} + +func (p *httpProcess) Destroy() error { + close(p.exit) + + var err error + + for i := 0; i < 2; i++ { + err = <-p.errCh + } + + return err +} + +func (p *httpProcess) String() string { + return "http" +} + +func NewProcess(opts ...runner.ProcessOption) runner.Process { + options := runner.NewProcessOptions(opts...) + + mux := http.NewServeMux() + + if handlers, ok := GetHandlerFuncsFromContext(options.Context); ok { + for path, handler := range handlers { + mux.HandleFunc(path, handler) + } + } + + var port int + + if prt, ok := options.EnvVars["PORT"]; ok { + var err error + port, err = strconv.Atoi(prt) + if err != nil { + log.Fatal(err) + } + } + + listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + log.Fatal(err) + } + + log.Infof("http service is listening on port %d", port) + + p := &httpProcess{ + options: options, + listener: listener, + server: &http.Server{ + ReadHeaderTimeout: time.Second, + Handler: mux, + TLSConfig: nil, + }, + errCh: make(chan error, 2), + exit: make(chan struct{}), + } + + return p +} diff --git a/runner/http/subscriber/domain.go b/runner/http/subscriber/domain.go new file mode 100644 index 0000000..7278217 --- /dev/null +++ b/runner/http/subscriber/domain.go @@ -0,0 +1,8 @@ +package subscriber + +import "github.com/w-h-a/pkg/sidecar" + +type RouteEvent struct { + Route string + Event *sidecar.Event +} diff --git a/runner/http/subscriber/options.go b/runner/http/subscriber/options.go new file mode 100644 index 0000000..225355b --- /dev/null +++ b/runner/http/subscriber/options.go @@ -0,0 +1,20 @@ +package subscriber + +import ( + "context" + + "github.com/w-h-a/pkg/runner" +) + +type routesKey struct{} + +func HttpSubscriberWithRoutes(routes ...string) runner.ProcessOption { + return func(o *runner.ProcessOptions) { + o.Context = context.WithValue(o.Context, routesKey{}, routes) + } +} + +func GetRoutesFromContext(ctx context.Context) ([]string, bool) { + routes, ok := ctx.Value(routesKey{}).([]string) + return routes, ok +} diff --git a/runner/http/subscriber/process.go b/runner/http/subscriber/process.go new file mode 100644 index 0000000..9f30fde --- /dev/null +++ b/runner/http/subscriber/process.go @@ -0,0 +1,98 @@ +package subscriber + +import ( + "context" + "encoding/json" + gohttp "net/http" + "time" + + "github.com/w-h-a/pkg/runner" + "github.com/w-h-a/pkg/runner/http" + "github.com/w-h-a/pkg/sidecar" +) + +type HttpSubscriber struct { + proc runner.Process + event chan *RouteEvent + exit chan struct{} +} + +func (p *HttpSubscriber) Options() runner.ProcessOptions { + return p.proc.Options() +} + +func (p *HttpSubscriber) Apply() error { + return p.proc.Apply() +} + +func (p *HttpSubscriber) Destroy() error { + close(p.exit) + return p.proc.Destroy() +} + +func (p *HttpSubscriber) String() string { + return "httpSubscriber" +} + +func (p *HttpSubscriber) Receive() *RouteEvent { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + select { + case <-ctx.Done(): + return nil + case routeEvent := <-p.event: + return routeEvent + } +} + +func NewSubscriber(opts ...runner.ProcessOption) *HttpSubscriber { + options := runner.NewProcessOptions(opts...) + + routes := []string{} + + if rs, ok := GetRoutesFromContext(options.Context); ok { + routes = rs + } + + event := make(chan *RouteEvent, 100) + + exit := make(chan struct{}) + + for _, route := range routes { + opts = append(opts, http.HttpProcessWithHandlerFuncs(route, func(w gohttp.ResponseWriter, r *gohttp.Request) { + var sidecarEvent sidecar.Event + + if err := json.NewDecoder(r.Body).Decode(&sidecarEvent); err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + select { + case <-exit: + w.WriteHeader(500) + return + case <-r.Context().Done(): + w.WriteHeader(500) + return + case event <- &RouteEvent{Route: r.URL.Path, Event: &sidecarEvent}: + w.WriteHeader(200) + return + } + })) + } + + opts = append(opts, http.HttpProcessWithHandlerFuncs("/health/check", func(w gohttp.ResponseWriter, r *gohttp.Request) { + w.WriteHeader(200) + w.Write([]byte("ok")) + })) + + s := &HttpSubscriber{ + proc: http.NewProcess(opts...), + event: event, + exit: exit, + } + + return s +} diff --git a/runner/options.go b/runner/options.go index 55ad492..9f720e5 100644 --- a/runner/options.go +++ b/runner/options.go @@ -9,9 +9,9 @@ import ( type RunnerOption func(o *RunnerOptions) type RunnerOptions struct { - Id string - Files []File - Context context.Context + Id string + Processes []Process + Context context.Context } func RunnerWithId(id string) RunnerOption { @@ -20,17 +20,17 @@ func RunnerWithId(id string) RunnerOption { } } -func RunnerWithFiles(files ...File) RunnerOption { +func RunnerWithProcesses(ps ...Process) RunnerOption { return func(o *RunnerOptions) { - o.Files = files + o.Processes = ps } } func NewRunnerOptions(opts ...RunnerOption) RunnerOptions { options := RunnerOptions{ - Id: uuid.New().String(), - Files: []File{}, - Context: context.Background(), + Id: uuid.New().String(), + Processes: []Process{}, + Context: context.Background(), } for _, fn := range opts { @@ -40,22 +40,60 @@ func NewRunnerOptions(opts ...RunnerOption) RunnerOptions { return options } -type ManagerOption func(o *ManagerOptions) +type ProcessOption func(o *ProcessOptions) -type ManagerOptions struct { - File File - Context context.Context +type ProcessOptions struct { + Id string + UpBinPath string + UpArgs []string + DownBinPath string + DownArgs []string + EnvVars map[string]string + Context context.Context } -func ManagerWithFile(file File) ManagerOption { - return func(o *ManagerOptions) { - o.File = file +func ProcessWithId(id string) ProcessOption { + return func(o *ProcessOptions) { + o.Id = id + } +} + +func ProcessWithUpBinPath(path string) ProcessOption { + return func(o *ProcessOptions) { + o.UpBinPath = path + } +} + +func ProcessWithUpArgs(args ...string) ProcessOption { + return func(o *ProcessOptions) { + o.UpArgs = args + } +} + +func ProcessWithDownBinPath(path string) ProcessOption { + return func(o *ProcessOptions) { + o.DownBinPath = path + } +} + +func ProcessWithDownArgs(args ...string) ProcessOption { + return func(o *ProcessOptions) { + o.DownArgs = args + } +} + +func ProcessWithEnvVars(envs map[string]string) ProcessOption { + return func(o *ProcessOptions) { + o.EnvVars = envs } } -func NewManagerOptions(opts ...ManagerOption) ManagerOptions { - options := ManagerOptions{ - Context: context.Background(), +func NewProcessOptions(opts ...ProcessOption) ProcessOptions { + options := ProcessOptions{ + UpArgs: []string{}, + DownArgs: []string{}, + EnvVars: map[string]string{}, + Context: context.Background(), } for _, fn := range opts { diff --git a/runner/manager.go b/runner/process.go similarity index 56% rename from runner/manager.go rename to runner/process.go index b682f7e..21f9634 100644 --- a/runner/manager.go +++ b/runner/process.go @@ -1,7 +1,7 @@ package runner -type Manager interface { - Options() ManagerOptions +type Process interface { + Options() ProcessOptions Apply() error Destroy() error String() string diff --git a/runner/runner.go b/runner/runner.go index 6f8e302..e2d07f1 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -1,16 +1,124 @@ package runner import ( + "sync" "testing" + + "github.com/w-h-a/pkg/telemetry/log" ) const ( - FailExitCode = 1 + SuccessExitCode = 0 + FailExitCode = 1 ) -type TestRunner interface { - Options() RunnerOptions - Start(m *testing.M) int - Stop() - String() string +type TestRunner struct { + options RunnerOptions + inactive []Process + inactiveMtx sync.RWMutex + active []Process + activeMtx sync.RWMutex +} + +func (r *TestRunner) Options() RunnerOptions { + return r.options +} + +func (r *TestRunner) Start(m *testing.M) int { + defer r.Stop() + + if err := r.Register(); err != nil { + return FailExitCode + } + + if err := r.Apply(); err != nil { + return FailExitCode + } + + return m.Run() +} + +func (r *TestRunner) Stop() { + r.Destroy() +} + +func (r *TestRunner) Register() error { + for _, p := range r.options.Processes { + r.inactiveMtx.Lock() + r.inactive = append(r.inactive, p) + r.inactiveMtx.Unlock() + } + + return nil +} + +func (r *TestRunner) Apply() error { + for p := r.dequeue(); p != nil; p = r.dequeue() { + if err := p.Apply(); err != nil { + log.Errorf("failed to apply %s: %v", p.Options().Id, err) + return err + } + + r.activeMtx.Lock() + r.active = append(r.active, p) + r.activeMtx.Unlock() + + log.Infof("successfully applied %s", p.Options().Id) + } + + return nil +} + +func (r *TestRunner) Destroy() { + for p := r.pop(); p != nil; p = r.pop() { + if err := p.Destroy(); err != nil { + log.Errorf("failed to destroy process %s: %v", p.Options().Id, err) + } else { + log.Infof("successfully destroyed process %s", p.Options().Id) + } + } +} + +func (r *TestRunner) dequeue() Process { + r.inactiveMtx.Lock() + defer r.inactiveMtx.Unlock() + + if len(r.inactive) == 0 { + return nil + } + + m := r.inactive[0] + + r.inactive = r.inactive[1:] + + return m +} + +func (r *TestRunner) pop() Process { + r.activeMtx.Lock() + defer r.activeMtx.Unlock() + + if len(r.active) == 0 { + return nil + } + + m := r.active[len(r.active)-1] + + r.active = r.active[:len(r.active)-1] + + return m +} + +func NewTestRunner(opts ...RunnerOption) *TestRunner { + options := NewRunnerOptions(opts...) + + r := &TestRunner{ + options: options, + inactive: []Process{}, + inactiveMtx: sync.RWMutex{}, + active: []Process{}, + activeMtx: sync.RWMutex{}, + } + + return r } diff --git a/runner/utils.go b/runner/utils.go new file mode 100644 index 0000000..b577c54 --- /dev/null +++ b/runner/utils.go @@ -0,0 +1,140 @@ +package runner + +import ( + "bufio" + "fmt" + "io" + "net" + "os" + "os/exec" + "strings" + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +type ioPair struct { + in io.ReadCloser + out *os.File +} + +func Outputs(cmd *exec.Cmd) error { + stdout, err := cmd.StdoutPipe() + if err != nil { + return err + } + + stderr, err := cmd.StderrPipe() + if err != nil { + return err + } + + pairs := []ioPair{ + {in: stdout, out: os.Stdout}, + {in: stderr, out: os.Stderr}, + } + + for _, ioPair := range pairs { + go func(in io.ReadCloser, out *os.File) { + defer in.Close() + + reader := bufio.NewReader(in) + + for { + s, err := reader.ReadString('\n') + if err == nil || err == io.EOF { + if len(strings.TrimSpace(s)) != 0 { + fmt.Fprintf(out, "%s", s) + } + if err == io.EOF { + return + } + } else { + fmt.Fprintf(out, "error: %s\n", err.Error()) + return + } + } + }(ioPair.in, ioPair.out) + } + + return nil +} + +func GetFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + if err != nil { + return 0, err + } + + listener, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + + defer listener.Close() + + return listener.Addr().(*net.TCPAddr).Port, nil +} + +type ParallelTest struct { + funs []func(c *assert.CollectT) + mtx sync.RWMutex +} + +func (p *ParallelTest) Add(fun func(c *assert.CollectT)) { + p.mtx.Lock() + defer p.mtx.Unlock() + + p.funs = append(p.funs, fun) +} + +func (p *ParallelTest) worker(jobCh <-chan func()) { + for job := range jobCh { + job() + } +} + +func NewParallelTest(t *testing.T, funs ...func(c *assert.CollectT)) *ParallelTest { + p := &ParallelTest{ + funs: funs, + } + + t.Cleanup(func() { + p.mtx.Lock() + defer p.mtx.Unlock() + + t.Helper() + + jobCh := make(chan func(), len(p.funs)) + + workerCount := 4 + + for i := 0; i < workerCount; i++ { + go p.worker(jobCh) + } + + cs := make([]*assert.CollectT, len(p.funs)) + + var wg sync.WaitGroup + + wg.Add(len(p.funs)) + + for i := range p.funs { + cs[i] = &assert.CollectT{} + + jobCh <- func() { + defer wg.Done() + defer recover() + + p.funs[i](cs[i]) + } + } + + wg.Wait() + + close(jobCh) + }) + + return p +} diff --git a/sidecar/custom/sidecar.go b/sidecar/custom/sidecar.go index af76a8c..99ad770 100644 --- a/sidecar/custom/sidecar.go +++ b/sidecar/custom/sidecar.go @@ -34,7 +34,7 @@ func (s *customSidecar) SaveStateToStore(state *sidecar.State) error { st, ok := s.options.Stores[state.StoreId] if !ok { log.Warnf("store %s was not found", state.StoreId) - return nil + return sidecar.ErrComponentNotFound } for _, record := range state.Records { @@ -63,7 +63,7 @@ func (c *customSidecar) ListStateFromStore(storeId string) ([]*store.Record, err st, ok := c.options.Stores[storeId] if !ok { log.Warnf("store %s was not found", storeId) - return nil, nil + return nil, sidecar.ErrComponentNotFound } // TODO: limit + offset @@ -79,7 +79,7 @@ func (s *customSidecar) SingleStateFromStore(storeId, key string) ([]*store.Reco st, ok := s.options.Stores[storeId] if !ok { log.Warnf("store %s was not found", storeId) - return nil, nil + return nil, sidecar.ErrComponentNotFound } recs, err := st.Read(key) @@ -94,7 +94,7 @@ func (s *customSidecar) RemoveStateFromStore(storeId, key string) error { st, ok := s.options.Stores[storeId] if !ok { log.Warnf("store %s was not found", storeId) - return nil + return sidecar.ErrComponentNotFound } if err := st.Delete(key); err != nil { @@ -106,11 +106,16 @@ func (s *customSidecar) RemoveStateFromStore(storeId, key string) error { func (s *customSidecar) WriteEventToBroker(event *sidecar.Event) error { if len(event.To) == 0 { + log.Warnf("event %#+event has no address", event) return nil } - if err := s.actOnEventFromService(event); err != nil { - return err + if len(event.Concurrent) > 0 { + s.sendEventToTargetsConcurrently(event) + } else { + if err := s.sendEventToTargetsSequentially(event); err != nil { + return err + } } return nil @@ -182,22 +187,6 @@ func (s *customSidecar) String() string { return "custom" } -func (s *customSidecar) actOnEventFromService(event *sidecar.Event) error { - if len(event.To) == 0 { - return nil - } - - if len(event.Concurrent) > 0 { - s.sendEventToTargetsConcurrently(event) - } else { - if err := s.sendEventToTargetsSequentially(event); err != nil { - return err - } - } - - return nil -} - func (s *customSidecar) sendEventToTargetsConcurrently(event *sidecar.Event) { for _, target := range event.To { go func() { @@ -224,7 +213,7 @@ func (s *customSidecar) sendEventToTarget(target string, event *sidecar.Event) e bk, ok := s.options.Brokers[target] if !ok { log.Warnf("broker %s was not found", target) - return nil + return sidecar.ErrComponentNotFound } if err := bk.Publish(event.Data, *bk.Options().PublishOptions); err != nil { @@ -243,7 +232,9 @@ func (s *customSidecar) sendEventToService(event *sidecar.Event) error { client.RequestWithNamespace(s.options.ServiceName), client.RequestWithName(s.options.ServiceName), client.RequestWithPort(p), + // TODO: make this better client.RequestWithMethod(event.EventName), + // TODO: does the service accept proto? client.RequestWithUnmarshaledRequest(event), ) @@ -253,10 +244,6 @@ func (s *customSidecar) sendEventToService(event *sidecar.Event) error { return err } - if err := s.actOnEventFromService(rsp); err != nil { - return err - } - return nil } diff --git a/sidecar/sidecar.go b/sidecar/sidecar.go index 3ea0058..2bc5cad 100644 --- a/sidecar/sidecar.go +++ b/sidecar/sidecar.go @@ -1,9 +1,15 @@ package sidecar import ( + "errors" + "github.com/w-h-a/pkg/store" ) +var ( + ErrComponentNotFound = errors.New("component not found") +) + type Sidecar interface { Options() SidecarOptions SaveStateToStore(state *State) error