From 51343e0d02a544ebd1c465ad977daa3abf066c9e Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Mon, 12 Jan 2026 16:34:42 +0100 Subject: [PATCH 1/5] HYPERFLEET-500 - feat: introduce oapi-codegen --- .bingo/Variables.mk | 6 ++ .bingo/oapi-codegen.mod | 5 + .bingo/oapi-codegen.sum | 147 +++++++++++++++++++++++++++ .bingo/variables.env | 1 + Dockerfile.openapi | 34 ------- Dockerfile.openapi.dockerignore | 5 - Makefile | 22 ++-- cmd/sentinel/main.go | 7 +- go.mod | 2 + go.sum | 8 ++ internal/client/client.go | 85 +++++++++------- internal/client/client_test.go | 39 +++---- internal/sentinel/sentinel_test.go | 18 ++-- openapi/README.md | 4 +- openapi/oapi-codegen.yaml | 12 +++ test/integration/integration_test.go | 10 +- 16 files changed, 272 insertions(+), 133 deletions(-) create mode 100644 .bingo/oapi-codegen.mod create mode 100644 .bingo/oapi-codegen.sum delete mode 100644 Dockerfile.openapi delete mode 100644 Dockerfile.openapi.dockerignore create mode 100644 openapi/oapi-codegen.yaml diff --git a/.bingo/Variables.mk b/.bingo/Variables.mk index 57d9bf9..fc89112 100644 --- a/.bingo/Variables.mk +++ b/.bingo/Variables.mk @@ -29,3 +29,9 @@ $(GOLANGCI_LINT): $(BINGO_DIR)/golangci-lint.mod @echo "(re)installing $(GOBIN)/golangci-lint-v2.7.0" @cd $(BINGO_DIR) && GOWORK=off GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) GOARM=$(GOHOSTARM) $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v2.7.0 "github.com/golangci/golangci-lint/v2/cmd/golangci-lint" +OAPI_CODEGEN := $(GOBIN)/oapi-codegen-v2.5.1 +$(OAPI_CODEGEN): $(BINGO_DIR)/oapi-codegen.mod + @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. + @echo "(re)installing $(GOBIN)/oapi-codegen-v2.5.1" + @cd $(BINGO_DIR) && GOWORK=off GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) GOARM=$(GOHOSTARM) $(GO) build -mod=mod -modfile=oapi-codegen.mod -o=$(GOBIN)/oapi-codegen-v2.5.1 "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" + diff --git a/.bingo/oapi-codegen.mod b/.bingo/oapi-codegen.mod new file mode 100644 index 0000000..905dc77 --- /dev/null +++ b/.bingo/oapi-codegen.mod @@ -0,0 +1,5 @@ +module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT + +go 1.24.0 + +require github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 // cmd/oapi-codegen diff --git a/.bingo/oapi-codegen.sum b/.bingo/oapi-codegen.sum new file mode 100644 index 0000000..e4d15a8 --- /dev/null +++ b/.bingo/oapi-codegen.sum @@ -0,0 +1,147 @@ +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +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/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 h1:5vHNY1uuPBRBWqB2Dp0G7YB03phxLQZupZTIZaeorjc= +github.com/oapi-codegen/oapi-codegen/v2 v2.5.1/go.mod h1:ro0npU1BWkcGpCgGD9QwPp44l5OIZ94tB3eabnT7DjQ= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= +github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +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/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20210112080510-489259a85091/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= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.25.1 h1:YeIyhd0M7gStYR9jb2IFXVVT+QJhgXu1ZECOuRwofh4= +golang.org/x/tools v0.25.1/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +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= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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= diff --git a/.bingo/variables.env b/.bingo/variables.env index 95a1a1a..71680ef 100644 --- a/.bingo/variables.env +++ b/.bingo/variables.env @@ -10,3 +10,4 @@ fi GOLANGCI_LINT="${GOBIN}/golangci-lint-v2.7.0" +OAPI_CODEGEN="${GOBIN}/oapi-codegen-v2.5.1" diff --git a/Dockerfile.openapi b/Dockerfile.openapi deleted file mode 100644 index acbb272..0000000 --- a/Dockerfile.openapi +++ /dev/null @@ -1,34 +0,0 @@ -FROM openapitools/openapi-generator-cli:v7.16.0 - -# -o APT::Sandbox::User=root is a workaround for rootless podman setgroups error in Prow env -RUN apt-get -o APT::Sandbox::User=root update -RUN apt-get -o APT::Sandbox::User=root install -y make sudo git golang-1.21 - -RUN mkdir -p /local -COPY . /local - -ENV PATH="/uhc/bin:/usr/lib/go-1.21/bin/:${PATH}" -ENV GOPATH="/uhc" -ENV GOBIN /usr/lib/go-1.21/bin/ -ENV CGO_ENABLED=0 - -WORKDIR /local - -# Ensure output directory exists before generation -RUN mkdir -p /local/pkg/api - -# Generate OpenAPI client for HyperFleet API -RUN bash /usr/local/bin/docker-entrypoint.sh generate \ - -i /local/openapi/openapi.yaml \ - -g go \ - -o /local/pkg/api/openapi \ - --package-name openapi \ - --global-property apiTests=false,modelTests=false - -# Remove files that conflict with our project structure -RUN rm -f /local/pkg/api/openapi/go.mod /local/pkg/api/openapi/go.sum -RUN rm -rf /local/pkg/api/openapi/test -RUN rm -f /local/pkg/api/openapi/git_push.sh - -# Format generated code -RUN gofmt -w /local/pkg/api/openapi diff --git a/Dockerfile.openapi.dockerignore b/Dockerfile.openapi.dockerignore deleted file mode 100644 index d034905..0000000 --- a/Dockerfile.openapi.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -# ignore all files -* - -# but the openapi contract, if it changes, we need to regenerate go types -!openapi/openapi.yaml diff --git a/Makefile b/Makefile index f6ba048..5343816 100755 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ GOFMT ?= gofmt BIN_DIR := bin BINARY_NAME := $(BIN_DIR)/sentinel + # Version information VERSION ?= dev COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") @@ -44,22 +45,13 @@ help: ## Display this help OPENAPI_SPEC_REF ?= main OPENAPI_SPEC_URL = https://raw.githubusercontent.com/openshift-hyperfleet/hyperfleet-api/$(OPENAPI_SPEC_REF)/openapi/openapi.yaml -.PHONY: generate -generate: ## Generate OpenAPI client from HyperFleet API spec - @echo "Fetching OpenAPI spec from hyperfleet-api (ref: $(OPENAPI_SPEC_REF))..." - @mkdir -p openapi - @curl -sSL -o openapi/openapi.yaml "$(OPENAPI_SPEC_URL)" || \ - (echo "Failed to download OpenAPI spec from $(OPENAPI_SPEC_URL)" && exit 1) - @echo "OpenAPI spec downloaded successfully" - @echo "Generating OpenAPI client..." - @rm -rf pkg/api/openapi - @mkdir -p pkg/api - $(CONTAINER_TOOL) build -t hyperfleet-sentinel-openapi -f Dockerfile.openapi . - @OPENAPI_IMAGE_ID=$$($(CONTAINER_TOOL) create hyperfleet-sentinel-openapi) && \ - $(CONTAINER_TOOL) cp $$OPENAPI_IMAGE_ID:/local/pkg/api/openapi ./pkg/api/openapi && \ - $(CONTAINER_TOOL) rm $$OPENAPI_IMAGE_ID - @echo "OpenAPI client generated successfully" +# Regenerate openapi types using oapi-codegen +generate: + rm -rf pkg/api/openapi + mkdir -p pkg/api/openapi + $(OAPI_CODEGEN) --config openapi/oapi-codegen.yaml openapi/openapi.yaml +.PHONY: generate ##@ Development .PHONY: build diff --git a/cmd/sentinel/main.go b/cmd/sentinel/main.go index 490ca07..ec1e437 100755 --- a/cmd/sentinel/main.go +++ b/cmd/sentinel/main.go @@ -158,7 +158,12 @@ func runServe(cfg *config.SentinelConfig, logCfg *logger.LogConfig) error { metrics.NewSentinelMetrics(registry) // Initialize components - hyperfleetClient := client.NewHyperFleetClient(cfg.HyperFleetAPI.Endpoint, cfg.HyperFleetAPI.Timeout) + hyperfleetClient, err := client.NewHyperFleetClient(cfg.HyperFleetAPI.Endpoint, cfg.HyperFleetAPI.Timeout) + if err != nil { + log.Errorf(ctx, "Failed to initialize OpenAPI client: %v", err) + return fmt.Errorf("failed to initialize OpenAPI client: %w", err) + + } decisionEngine := engine.NewDecisionEngine(cfg.MaxAgeNotReady, cfg.MaxAgeReady) // Initialize publisher using hyperfleet-broker library diff --git a/go.mod b/go.mod index 936fe8c..5f79ad4 100755 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 github.com/cloudevents/sdk-go/v2 v2.16.2 github.com/google/uuid v1.6.0 + github.com/oapi-codegen/runtime v1.1.2 github.com/openshift-hyperfleet/hyperfleet-broker v1.0.0 github.com/prometheus/client_golang v1.23.2 github.com/segmentio/ksuid v1.0.4 @@ -28,6 +29,7 @@ require ( github.com/ThreeDotsLabs/watermill v1.5.1 // indirect github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2 // indirect github.com/ThreeDotsLabs/watermill-googlecloud/v2 v2.0.0 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect diff --git a/go.sum b/go.sum index cad0208..47ba112 100755 --- a/go.sum +++ b/go.sum @@ -20,14 +20,18 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg6 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY= github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4= github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2 h1:aeyFSR4SUsbszmocuFiYY13nsHorc6CXIS2Hy7+xgFU= github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2/go.mod h1:+8tCh6VCuBcQWhfETCwzRINKQ1uyeg9moH3h7jMKxQk= github.com/ThreeDotsLabs/watermill-googlecloud/v2 v2.0.0 h1:GXR+tsxPs/Vpmm0t4yEJUZdqLP9EytWvR+KN3Un5mNY= github.com/ThreeDotsLabs/watermill-googlecloud/v2 v2.0.0/go.mod h1:3IHyi1bNqQ8J2/wVWj4cQjzWXoEPauLm8ViyOCNaKbM= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -133,6 +137,7 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -174,6 +179,8 @@ 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/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= +github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -227,6 +234,7 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= 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= diff --git a/internal/client/client.go b/internal/client/client.go index 161872f..d7b1e82 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -42,27 +42,25 @@ const ( // HyperFleetClient wraps the OpenAPI-generated client type HyperFleetClient struct { - apiClient *openapi.APIClient + apiClient *openapi.ClientWithResponses log logger.HyperFleetLogger } // NewHyperFleetClient creates a new HyperFleet API client using OpenAPI-generated client -func NewHyperFleetClient(endpoint string, timeout time.Duration) *HyperFleetClient { - cfg := openapi.NewConfiguration() - cfg.Servers = openapi.ServerConfigurations{ - { - URL: endpoint, - Description: "HyperFleet API", - }, - } - cfg.HTTPClient = &http.Client{ +func NewHyperFleetClient(endpoint string, timeout time.Duration) (*HyperFleetClient, error) { + httpClient := &http.Client{ Timeout: timeout, } + client, err := openapi.NewClientWithResponses(endpoint, openapi.WithHTTPClient(httpClient)) + if err != nil { + return nil, fmt.Errorf("failed to create OpenAPI client: %v", err) // This should only fail if the endpoint URL is invalid + } + return &HyperFleetClient{ - apiClient: openapi.NewAPIClient(cfg), + apiClient: client, log: logger.NewHyperFleetLogger(), - } + }, nil } // Resource represents a HyperFleet resource (cluster, nodepool, etc.) @@ -201,21 +199,14 @@ func (c *HyperFleetClient) fetchResourcesOnce(ctx context.Context, resourceType // fetchClusters fetches cluster resources from the API func (c *HyperFleetClient) fetchClusters(ctx context.Context, searchParam string) ([]Resource, error) { - req := c.apiClient.DefaultAPI.GetClusters(ctx) + params := &openapi.GetClustersParams{} if searchParam != "" { - req = req.Search(searchParam) + search := searchParam + params.Search = &search } - resourceList, resp, err := req.Execute() + response, err := c.apiClient.GetClustersWithResponse(ctx, params) if err != nil { - if resp != nil { - // Enhanced error with status code - return nil, &APIError{ - StatusCode: resp.StatusCode, - Message: fmt.Sprintf("API request failed: %v", err), - Retriable: isHTTPStatusRetriable(resp.StatusCode), - } - } // Network/timeout error - use errors.As for proper error unwrapping var urlErr *url.Error if errors.As(err, &urlErr) && urlErr.Timeout() { @@ -232,8 +223,17 @@ func (c *HyperFleetClient) fetchClusters(ctx context.Context, searchParam string } } - // Nil check for response - if resourceList == nil { + // Check HTTP response status + if response.HTTPResponse != nil && response.HTTPResponse.StatusCode >= 400 { + return nil, &APIError{ + StatusCode: response.HTTPResponse.StatusCode, + Message: fmt.Sprintf("API request failed with status %d", response.HTTPResponse.StatusCode), + Retriable: isHTTPStatusRetriable(response.HTTPResponse.StatusCode), + } + } + + // Nil check for response body + if response.JSON200 == nil { return nil, &APIError{ StatusCode: 0, Message: "received nil response from API", @@ -241,6 +241,8 @@ func (c *HyperFleetClient) fetchClusters(ctx context.Context, searchParam string } } + resourceList := response.JSON200 + // Convert OpenAPI models to internal models resources := make([]Resource, 0, len(resourceList.Items)) for _, item := range resourceList.Items { @@ -253,11 +255,15 @@ func (c *HyperFleetClient) fetchClusters(ctx context.Context, searchParam string if item.Href != nil { href = *item.Href } + kind := "" + if item.Kind != nil { + kind = *item.Kind + } resource := Resource{ ID: id, Href: href, - Kind: item.Kind, + Kind: kind, Generation: item.Generation, CreatedTime: item.CreatedTime, UpdatedTime: item.UpdatedTime, @@ -301,20 +307,14 @@ func (c *HyperFleetClient) fetchClusters(ctx context.Context, searchParam string // fetchNodePools fetches nodepool resources from the API func (c *HyperFleetClient) fetchNodePools(ctx context.Context, searchParam string) ([]Resource, error) { - req := c.apiClient.DefaultAPI.GetNodePools(ctx) + params := &openapi.GetNodePoolsParams{} if searchParam != "" { - req = req.Search(searchParam) + search := searchParam + params.Search = &search } - resourceList, resp, err := req.Execute() + response, err := c.apiClient.GetNodePoolsWithResponse(ctx, params) if err != nil { - if resp != nil { - return nil, &APIError{ - StatusCode: resp.StatusCode, - Message: fmt.Sprintf("API request failed: %v", err), - Retriable: isHTTPStatusRetriable(resp.StatusCode), - } - } var urlErr *url.Error if errors.As(err, &urlErr) && urlErr.Timeout() { return nil, &APIError{ @@ -330,7 +330,16 @@ func (c *HyperFleetClient) fetchNodePools(ctx context.Context, searchParam strin } } - if resourceList == nil { + // Check HTTP response status + if response.HTTPResponse != nil && response.HTTPResponse.StatusCode >= 400 { + return nil, &APIError{ + StatusCode: response.HTTPResponse.StatusCode, + Message: fmt.Sprintf("API request failed with status %d", response.HTTPResponse.StatusCode), + Retriable: isHTTPStatusRetriable(response.HTTPResponse.StatusCode), + } + } + + if response.JSON200 == nil { return nil, &APIError{ StatusCode: 0, Message: "received nil response from API", @@ -338,6 +347,8 @@ func (c *HyperFleetClient) fetchNodePools(ctx context.Context, searchParam strin } } + resourceList := response.JSON200 + // Convert OpenAPI models to internal models resources := make([]Resource, 0, len(resourceList.Items)) for _, item := range resourceList.Items { diff --git a/internal/client/client_test.go b/internal/client/client_test.go index 5b1ef85..cbba1b4 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -20,8 +20,8 @@ func createMockCluster(id string) map[string]interface{} { "generation": 5, "created_time": "2025-01-01T09:00:00Z", "updated_time": "2025-01-01T10:00:00Z", - "created_by": "test-user", - "updated_by": "test-user", + "created_by": "test-user@example.com", + "updated_by": "test-user@example.com", "spec": map[string]interface{}{}, "status": map[string]interface{}{ "phase": "Ready", @@ -69,12 +69,11 @@ func TestFetchResources_Success(t *testing.T) { defer server.Close() // Create client - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) // Fetch resources ctx := context.Background() resources, err := client.FetchResources(ctx, ResourceTypeClusters, nil) - // Verify if err != nil { t.Fatalf("Expected no error, got %v", err) @@ -105,10 +104,9 @@ func TestFetchResources_EmptyList(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) resources, err := client.FetchResources(context.Background(), ResourceTypeClusters, nil) - if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -127,7 +125,7 @@ func TestFetchResources_404NotFound(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) _, err := client.FetchResources(context.Background(), ResourceTypeClusters, nil) @@ -157,7 +155,7 @@ func TestFetchResources_500ServerError(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -202,10 +200,9 @@ func TestFetchResources_503ServiceUnavailable_ThenSuccess(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) resources, err := client.FetchResources(context.Background(), ResourceTypeClusters, nil) - if err != nil { t.Fatalf("Expected no error after retry, got %v", err) } @@ -241,10 +238,9 @@ func TestFetchResources_429RateLimited(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) _, err := client.FetchResources(context.Background(), ResourceTypeClusters, nil) - if err != nil { t.Fatalf("Expected no error after retry, got %v", err) } @@ -263,7 +259,7 @@ func TestFetchResources_Timeout(t *testing.T) { defer server.Close() // Create client with very short timeout - client := NewHyperFleetClient(server.URL, 100*time.Millisecond) + client, _ := NewHyperFleetClient(server.URL, 100*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() @@ -286,7 +282,7 @@ func TestFetchResources_ContextCancellation(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) ctx, cancel := context.WithCancel(context.Background()) @@ -315,7 +311,7 @@ func TestFetchResources_MalformedJSON(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) _, err := client.FetchResources(context.Background(), ResourceTypeClusters, nil) @@ -328,7 +324,7 @@ func TestFetchResources_MalformedJSON(t *testing.T) { // TestFetchResources_NilContext tests handling of nil context func TestFetchResources_NilContext(t *testing.T) { - client := NewHyperFleetClient("http://localhost", 10*time.Second) + client, _ := NewHyperFleetClient("http://localhost", 10*time.Second) // Intentionally pass nil context to test validation // nolint:staticcheck // Testing nil context validation @@ -345,7 +341,7 @@ func TestFetchResources_NilContext(t *testing.T) { // TestFetchResources_InvalidResourceType tests handling of invalid resource type func TestFetchResources_InvalidResourceType(t *testing.T) { - client := NewHyperFleetClient("http://localhost", 10*time.Second) + client, _ := NewHyperFleetClient("http://localhost", 10*time.Second) testCases := []struct { name string @@ -397,12 +393,11 @@ func TestFetchResources_NilStatus(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) // Note: A warning will be logged for cluster-1, but we can't easily // verify log output in tests. In production, logs are captured for monitoring. resources, err := client.FetchResources(context.Background(), ResourceTypeClusters, nil) - // Verify graceful degradation behavior: if err != nil { t.Fatalf("Expected no error (graceful degradation), got %v", err) @@ -575,9 +570,8 @@ func TestFetchResources_NodePools(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) resources, err := client.FetchResources(context.Background(), ResourceTypeNodePools, nil) - if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -615,7 +609,7 @@ func TestFetchResources_WithLabelSelector(t *testing.T) { })) defer server.Close() - client := NewHyperFleetClient(server.URL, 10*time.Second) + client, _ := NewHyperFleetClient(server.URL, 10*time.Second) labelSelector := map[string]string{ "region": "us-east", @@ -623,7 +617,6 @@ func TestFetchResources_WithLabelSelector(t *testing.T) { } resources, err := client.FetchResources(context.Background(), ResourceTypeClusters, labelSelector) - if err != nil { t.Fatalf("Expected no error, got %v", err) } diff --git a/internal/sentinel/sentinel_test.go b/internal/sentinel/sentinel_test.go index 98d21da..86e40b2 100644 --- a/internal/sentinel/sentinel_test.go +++ b/internal/sentinel/sentinel_test.go @@ -28,8 +28,8 @@ func createMockCluster(id string, generation int, observedGeneration int, phase "generation": generation, "created_time": "2025-01-01T09:00:00Z", "updated_time": "2025-01-01T10:00:00Z", - "created_by": "test-user", - "updated_by": "test-user", + "created_by": "test-user@example.com", + "updated_by": "test-user@example.com", "spec": map[string]interface{}{}, "status": map[string]interface{}{ "phase": phase, @@ -89,7 +89,7 @@ func TestTrigger_Success(t *testing.T) { defer server.Close() // Setup components - hyperfleetClient := client.NewHyperFleetClient(server.URL, 10*time.Second) + hyperfleetClient, _ := client.NewHyperFleetClient(server.URL, 10*time.Second) decisionEngine := engine.NewDecisionEngine(10*time.Second, 30*time.Minute) mockPublisher := &MockPublisher{} log := logger.NewHyperFleetLogger() @@ -109,7 +109,6 @@ func TestTrigger_Success(t *testing.T) { // Execute err := s.trigger(ctx) - // Verify if err != nil { t.Errorf("Expected no error, got %v", err) @@ -157,7 +156,7 @@ func TestTrigger_NoEventsPublished(t *testing.T) { defer server.Close() // Setup components - hyperfleetClient := client.NewHyperFleetClient(server.URL, 10*time.Second) + hyperfleetClient, _ := client.NewHyperFleetClient(server.URL, 10*time.Second) decisionEngine := engine.NewDecisionEngine(10*time.Second, 30*time.Minute) mockPublisher := &MockPublisher{} log := logger.NewHyperFleetLogger() @@ -177,7 +176,6 @@ func TestTrigger_NoEventsPublished(t *testing.T) { // Execute err := s.trigger(ctx) - // Verify if err != nil { t.Errorf("Expected no error, got %v", err) @@ -202,7 +200,7 @@ func TestTrigger_FetchError(t *testing.T) { defer server.Close() // Setup components - hyperfleetClient := client.NewHyperFleetClient(server.URL, 1*time.Second) // Short timeout + hyperfleetClient, _ := client.NewHyperFleetClient(server.URL, 1*time.Second) // Short timeout decisionEngine := engine.NewDecisionEngine(10*time.Second, 30*time.Minute) mockPublisher := &MockPublisher{} log := logger.NewHyperFleetLogger() @@ -249,7 +247,7 @@ func TestTrigger_PublishError(t *testing.T) { defer server.Close() // Setup components - hyperfleetClient := client.NewHyperFleetClient(server.URL, 10*time.Second) + hyperfleetClient, _ := client.NewHyperFleetClient(server.URL, 10*time.Second) decisionEngine := engine.NewDecisionEngine(10*time.Second, 30*time.Minute) mockPublisher := &MockPublisher{ publishError: errors.New("broker connection failed"), @@ -271,7 +269,6 @@ func TestTrigger_PublishError(t *testing.T) { // Execute err := s.trigger(ctx) - // Verify - trigger should succeed even if publish fails (graceful degradation) if err != nil { t.Errorf("Expected no error (graceful degradation), got %v", err) @@ -300,7 +297,7 @@ func TestTrigger_MixedResources(t *testing.T) { defer server.Close() // Setup components - hyperfleetClient := client.NewHyperFleetClient(server.URL, 10*time.Second) + hyperfleetClient, _ := client.NewHyperFleetClient(server.URL, 10*time.Second) decisionEngine := engine.NewDecisionEngine(10*time.Second, 30*time.Minute) mockPublisher := &MockPublisher{} log := logger.NewHyperFleetLogger() @@ -320,7 +317,6 @@ func TestTrigger_MixedResources(t *testing.T) { // Execute err := s.trigger(ctx) - // Verify if err != nil { t.Errorf("Expected no error, got %v", err) diff --git a/openapi/README.md b/openapi/README.md index dab5375..64437c8 100644 --- a/openapi/README.md +++ b/openapi/README.md @@ -50,10 +50,10 @@ make generate ## Generator Details -- **Tool**: OpenAPI Generator CLI v7.16.0 +- **Tool**: OAPI Codegen https://github.com/oapi-codegen/oapi-codegen - **Language**: Go - **Output**: `pkg/api/openapi/` (not committed to git) -- **Docker-based**: Uses `Dockerfile.openapi` for consistent generation across environments +- **Go-based**: Uses oapi-codegen to generate go types - **Wrapper**: `internal/client/client.go` provides a simplified interface to the generated client The generator configuration follows the same pattern as [rh-trex](https://github.com/openshift-online/rh-trex). diff --git a/openapi/oapi-codegen.yaml b/openapi/oapi-codegen.yaml new file mode 100644 index 0000000..b52bf78 --- /dev/null +++ b/openapi/oapi-codegen.yaml @@ -0,0 +1,12 @@ +# oapi-codegen configuration +# See: https://github.com/oapi-codegen/oapi-codegen + +package: openapi +output: pkg/api/openapi/openapi.gen.go +generate: + models: true + chi-server: false + client: true + embedded-spec: false +output-options: + skip-prune: false diff --git a/test/integration/integration_test.go b/test/integration/integration_test.go index f0bd011..dfa2399 100644 --- a/test/integration/integration_test.go +++ b/test/integration/integration_test.go @@ -55,8 +55,8 @@ func createMockClusterWithLabels(id string, generation int, observedGeneration i "generation": generation, "created_at": "2025-01-01T09:00:00Z", "updated_at": "2025-01-01T10:00:00Z", - "created_by": "test-user", - "updated_by": "test-user", + "created_by": "test-user@example.com", + "updated_by": "test-user@example.com", "spec": map[string]interface{}{}, "status": map[string]interface{}{ "phase": phase, @@ -120,7 +120,7 @@ func TestIntegration_EndToEnd(t *testing.T) { defer server.Close() // Setup components with real RabbitMQ broker - hyperfleetClient := client.NewHyperFleetClient(server.URL, 10*time.Second) + hyperfleetClient, _ := client.NewHyperFleetClient(server.URL, 10*time.Second) decisionEngine := engine.NewDecisionEngine(10*time.Second, 30*time.Minute) log := logger.NewHyperFleetLogger() @@ -229,7 +229,7 @@ func TestIntegration_LabelSelectorFiltering(t *testing.T) { defer server.Close() // Setup components with real RabbitMQ broker - hyperfleetClient := client.NewHyperFleetClient(server.URL, 10*time.Second) + hyperfleetClient, _ := client.NewHyperFleetClient(server.URL, 10*time.Second) decisionEngine := engine.NewDecisionEngine(10*time.Second, 30*time.Minute) log := logger.NewHyperFleetLogger() @@ -333,7 +333,7 @@ func TestIntegration_TSLSyntaxMultipleLabels(t *testing.T) { defer server.Close() // Setup components - hyperfleetClient := client.NewHyperFleetClient(server.URL, 10*time.Second) + hyperfleetClient, _ := client.NewHyperFleetClient(server.URL, 10*time.Second) decisionEngine := engine.NewDecisionEngine(10*time.Second, 30*time.Minute) log := logger.NewHyperFleetLogger() From 88018319192eabb9e9f48298d4b311ab31458dc3 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Wed, 14 Jan 2026 13:21:25 +0100 Subject: [PATCH 2/5] add dependency to oapi in Makefile generate target --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5343816..e0653c8 100755 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ OPENAPI_SPEC_URL = https://raw.githubusercontent.com/openshift-hyperfleet/hyperf # Regenerate openapi types using oapi-codegen -generate: +generate: $(OAPI_CODEGEN) rm -rf pkg/api/openapi mkdir -p pkg/api/openapi $(OAPI_CODEGEN) --config openapi/oapi-codegen.yaml openapi/openapi.yaml From 729f063b1652ad4d8e0bcac0747c9848628417f0 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Wed, 14 Jan 2026 13:33:22 +0100 Subject: [PATCH 3/5] fetch openapi.yaml from hyperfleet-api repo --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index e0653c8..1de4b6c 100755 --- a/Makefile +++ b/Makefile @@ -48,6 +48,11 @@ OPENAPI_SPEC_URL = https://raw.githubusercontent.com/openshift-hyperfleet/hyperf # Regenerate openapi types using oapi-codegen generate: $(OAPI_CODEGEN) + @echo "Fetching OpenAPI spec from hyperfleet-api (ref: $(OPENAPI_SPEC_REF))..." + @mkdir -p openapi + @curl -sSL -o openapi/openapi.yaml "$(OPENAPI_SPEC_URL)" || \ + (echo "Failed to download OpenAPI spec from $(OPENAPI_SPEC_URL)" && exit 1) + @echo "OpenAPI spec downloaded successfully" rm -rf pkg/api/openapi mkdir -p pkg/api/openapi $(OAPI_CODEGEN) --config openapi/oapi-codegen.yaml openapi/openapi.yaml From 055ad222c91c047f089d8de6d51ca756c668d492 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Wed, 14 Jan 2026 15:45:27 +0100 Subject: [PATCH 4/5] use make in dockerfile --- Dockerfile | 36 +++++++----------------------------- Makefile | 28 +++++++++++++++++++--------- openapi/README.md | 2 +- 3 files changed, 27 insertions(+), 39 deletions(-) diff --git a/Dockerfile b/Dockerfile index cd80303..f935842 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,9 @@ -FROM openapitools/openapi-generator-cli:v7.16.0 AS openapi-gen +ARG BASE_IMAGE=gcr.io/distroless/static-debian12:nonroot -WORKDIR /local +FROM golang:1.25 AS builder -# OpenAPI spec configuration from hyperfleet-api repository -ARG OPENAPI_SPEC_REF=main -ARG OPENAPI_SPEC_URL=https://raw.githubusercontent.com/openshift-hyperfleet/hyperfleet-api/${OPENAPI_SPEC_REF}/openapi/openapi.yaml - -# Fetch OpenAPI spec from hyperfleet-api -RUN echo "Fetching OpenAPI spec from hyperfleet-api (ref: ${OPENAPI_SPEC_REF})..." && \ - mkdir -p openapi && \ - wget -O openapi/openapi.yaml "${OPENAPI_SPEC_URL}" || \ - (echo "Failed to download OpenAPI spec from ${OPENAPI_SPEC_URL}" && exit 1) - - -# Generate Go client/models from OpenAPI spec -RUN bash /usr/local/bin/docker-entrypoint.sh generate \ - -i /local/openapi/openapi.yaml \ - -g go \ - -o /local/pkg/api/openapi && \ - rm -f /local/pkg/api/openapi/go.mod /local/pkg/api/openapi/go.sum && \ - rm -rf /local/pkg/api/openapi/test - -# Build stage -FROM golang:1.25-alpine AS builder +ARG GIT_SHA=unknown +ARG GIT_DIRTY="" WORKDIR /build @@ -30,22 +11,19 @@ WORKDIR /build COPY go.mod go.sum ./ RUN go mod download -# Copy generated OpenAPI client from openapi-gen stage -COPY --from=openapi-gen /local/pkg/api/openapi ./pkg/api/openapi - # Copy source code COPY . . # Build binary -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o sentinel ./cmd/sentinel +RUN CGO_ENABLED=0 GOOS=linux make build # Runtime stage -FROM gcr.io/distroless/static-debian12:nonroot +FROM ${BASE_IMAGE} WORKDIR /app # Copy binary from builder -COPY --from=builder /build/sentinel /app/sentinel +COPY --from=builder /build/bin/sentinel /app/sentinel # Config will be provided via Helm ConfigMap mount # COPY configs/sentinel.yaml /app/configs/sentinel.yaml diff --git a/Makefile b/Makefile index 1de4b6c..a4e472c 100755 --- a/Makefile +++ b/Makefile @@ -11,13 +11,14 @@ BINARY_NAME := $(BIN_DIR)/sentinel # Version information -VERSION ?= dev -COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") BUILD_DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") +GIT_SHA ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") +GIT_DIRTY ?= $(shell git diff --quiet 2>/dev/null || echo "-modified") +VERSION:=$(GIT_SHA)$(GIT_DIRTY) # Go build flags LDFLAGS := -X main.version=$(VERSION) \ - -X main.commit=$(COMMIT) \ + -X main.commit=$(GIT_SHA) \ -X main.date=$(BUILD_DATE) # Container tool (docker or podman) @@ -33,7 +34,7 @@ IMAGE_TAG ?= $(VERSION) # Dev image configuration - set QUAY_USER to push to personal registry # Usage: QUAY_USER=myuser make image-dev QUAY_USER ?= -DEV_TAG ?= dev-$(COMMIT) +DEV_TAG ?= dev-$(GIT_SHA) .PHONY: help help: ## Display this help @@ -152,13 +153,17 @@ download: ## Download dependencies .PHONY: image image: ## Build container image with configurable registry/tag - @echo "Building image $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)..." - $(CONTAINER_TOOL) build -t $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) . + @echo "Building image $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) ..." + $(CONTAINER_TOOL) build \ + --platform linux/amd64 \ + --build-arg GIT_SHA=$(GIT_SHA) \ + --build-arg GIT_DIRTY=$(GIT_DIRTY) \ + -t $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) . @echo "Image built: $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)" .PHONY: image-push image-push: image ## Build and push container image - @echo "Pushing image $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)..." + @echo "Pushing image $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) ..." $(CONTAINER_TOOL) push $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) @echo "Image pushed: $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)" @@ -172,8 +177,13 @@ ifndef QUAY_USER @echo "This will build and push to: quay.io/\$$QUAY_USER/$(IMAGE_NAME):$(DEV_TAG)" @exit 1 endif - @echo "Building dev image quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG)..." - $(CONTAINER_TOOL) build -t quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG) . + @echo "Building dev image quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG) ..." + $(CONTAINER_TOOL) build \ + --platform linux/amd64 \ + --build-arg BASE_IMAGE=alpine:3.21 \ + --build-arg GIT_SHA=$(GIT_SHA) \ + --build-arg GIT_DIRTY=$(GIT_DIRTY) \ + -t quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG) . @echo "Pushing dev image..." $(CONTAINER_TOOL) push quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG) @echo "" diff --git a/openapi/README.md b/openapi/README.md index 64437c8..bfa9966 100644 --- a/openapi/README.md +++ b/openapi/README.md @@ -50,7 +50,7 @@ make generate ## Generator Details -- **Tool**: OAPI Codegen https://github.com/oapi-codegen/oapi-codegen +- **Tool**: [OAPI Codegen](https://github.com/oapi-codegen/oapi-codegen) - **Language**: Go - **Output**: `pkg/api/openapi/` (not committed to git) - **Go-based**: Uses oapi-codegen to generate go types From f8f3faa3af4a69be10634b0c3c6a4b2c643e1599 Mon Sep 17 00:00:00 2001 From: Angel Marin Date: Wed, 14 Jan 2026 16:36:28 +0100 Subject: [PATCH 5/5] add generate as prerequisite to tests targets --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index a4e472c..b6472eb 100755 --- a/Makefile +++ b/Makefile @@ -82,11 +82,11 @@ clean: ## Remove build artifacts ##@ Testing .PHONY: test -test: ## Run unit tests (default) +test: generate ## Run unit tests (default) $(GO) test -v -race -coverprofile=coverage.out ./... .PHONY: test-unit -test-unit: ## Run unit tests only +test-unit: generate ## Run unit tests only $(GO) test -v -race -cover ./internal/config/ $(GO) test -v -race -cover ./internal/client/ $(GO) test -v -race -cover ./internal/engine/ @@ -95,7 +95,7 @@ test-unit: ## Run unit tests only $(GO) test -v -race -cover ./pkg/... .PHONY: test-integration -test-integration: ## Run integration tests only +test-integration: generate ## Run integration tests only @echo "Running integration tests..." TESTCONTAINERS_RYUK_DISABLED=true $(GO) test -v -race -tags=integration ./test/integration/... -timeout 30m