diff --git a/go.sum b/go.sum index 0974c3f30..4e2ec754c 100644 --- a/go.sum +++ b/go.sum @@ -145,11 +145,6 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chirino/graphql v0.0.0-20210706215952-25574d349bd0/go.mod h1:QJryzmxY+8v+nP7+HjPsMxT4q+tfZZq387ZdG0md+kU= -github.com/chirino/graphql v0.0.0-20210707003802-dfaf250c773e h1:6nzwfRcXUlHwVelYS03FAjhGn1IRPrdBJ1ORzmgmk7A= -github.com/chirino/graphql v0.0.0-20210707003802-dfaf250c773e/go.mod h1:QJryzmxY+8v+nP7+HjPsMxT4q+tfZZq387ZdG0md+kU= -github.com/chirino/graphql-4-apis v0.0.0-20210708183827-e0ef9ee72fe3 h1:QYPhFi3XltRt7aTURUNCYcF5/C2C58eq4AJg8Kx6XDg= -github.com/chirino/graphql-4-apis v0.0.0-20210708183827-e0ef9ee72fe3/go.mod h1:Yk+Nac2yqgOYUxEN3FPNvWdcQBdvhEBvBLk+5vWqVVk= 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= @@ -161,7 +156,6 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -248,13 +242,10 @@ github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nI github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/friendsofgo/graphiql v0.2.2/go.mod h1:8Y2kZ36AoTGWs78+VRpvATyt3LJBx0SZXmay80ZTRWo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/getkin/kin-openapi v0.66.0 h1:GNEnjUYK5j5TR8CrXBQ5Nao9kVd7kPpoTr0Y0q4eaSA= -github.com/getkin/kin-openapi v0.66.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getsentry/sentry-go v0.3.1 h1:yTTEl5wqb8mECvTujvyqSi/WjKQnUQL9F921EIDuVqs= github.com/getsentry/sentry-go v0.3.1/go.mod h1:rnN2T5/zgkxv1oOpRVzfRqvjCw7XM6cjbDGmGyY/+8E= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -299,8 +290,6 @@ github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwds github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= @@ -328,8 +317,6 @@ github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/ github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= @@ -460,7 +447,6 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graphql-go/graphql v0.7.8/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKdORUqGDGmrI= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -610,8 +596,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -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/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -685,9 +669,6 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -795,8 +776,6 @@ github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= @@ -879,7 +858,6 @@ github.com/santhosh-tekuri/jsonschema/v3 v3.0.1 h1:tQVL4vmtH0NYlua++DZCaCUCIs8Jd github.com/santhosh-tekuri/jsonschema/v3 v3.0.1/go.mod h1:oOUSf2vgwmcYO4CkIJnEKle02MmEeI3cyItX+fxgpzg= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/segmentio/ksuid v1.0.2/go.mod h1:BXuJDr2byAiHuQaQtSKoXh1J0YmUDurywOXgB2w+OSU= github.com/segmentio/ksuid v1.0.3 h1:FoResxvleQwYiPAVKe1tMUlEirodZqlqglIuFsdDntY= github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/selvatico/go-mocket v1.0.7 h1:jbVa7RkoOCzBanQYiYF+VWgySHZogg25fOIKkM38q5k= @@ -889,9 +867,7 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -946,11 +922,6 @@ github.com/tidwall/pretty v0.0.0-20190325153808-1166b9ac2b65/go.mod h1:XNkn88O1C github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= -github.com/uber/jaeger-client-go v2.14.1-0.20180928181052-40fb3b2c4120+incompatible h1:Dw0AFQs6RGO8RxMPGP2LknN/VtHolVH82P9PP0Ni+9w= -github.com/uber/jaeger-client-go v2.14.1-0.20180928181052-40fb3b2c4120+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v1.5.0 h1:OHbgr8l656Ub3Fw5k9SWnBfIEwvoHQ+W2y+Aa9D1Uyo= -github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -1310,7 +1281,6 @@ golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200128220307-520188d60f50/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/internal/connector/internal/api/admin/private/api/openapi.yaml b/internal/connector/internal/api/admin/private/api/openapi.yaml index aa12b7e03..d374b2f2e 100644 --- a/internal/connector/internal/api/admin/private/api/openapi.yaml +++ b/internal/connector/internal/api/admin/private/api/openapi.yaml @@ -15,8 +15,9 @@ servers: url: / tags: - name: Connector Clusters Admin +- name: Connector Namespaces Admin paths: - /api/connector_mgmt/v1/admin/kafka_connector_clusters/: + /api/connector_mgmt/v1/admin/kafka_connector_clusters: get: operationId: listConnectorClusters parameters: @@ -132,6 +133,139 @@ paths: summary: Returns a list of connector clusters tags: - Connector Clusters Admin + /api/connector_mgmt/v1/admin/kafka_connector_clusters/{connector_cluster_id}/namespaces: + get: + operationId: getClusterNamespaces + parameters: + - description: The id of the connector cluster + explode: false + in: path + name: connector_cluster_id + required: true + schema: + type: string + style: simple + - description: Page index + examples: + page: + value: "1" + in: query + name: page + required: false + schema: + type: string + - description: Number of items in each page + examples: + size: + value: "100" + in: query + name: size + required: false + schema: + type: string + - description: |- + Specifies the order by criteria. The syntax of this parameter is + similar to the syntax of the `order by` clause of an SQL statement. + Each query can be ordered by any of the `ConnectorType` fields. + For example, to return all Connector types ordered by their name, use the following syntax: + + ```sql + name asc + ``` + + To return all Connector types ordered by their name _and_ version, use the following syntax: + + ```sql + name asc, version asc + ``` + + If the parameter isn't provided, or if the value is empty, then + the results are ordered by name. + examples: + orderBy: + value: name asc + explode: true + in: query + name: orderBy + required: false + schema: + type: string + style: form + - description: | + Search criteria. + + The syntax of this parameter is similar to the syntax of the `where` clause of a + SQL statement. Allowed fields in the search are `name`, `description`, `version`, `label`, and `channel`. + Allowed operators are `<>`, `=`, or `LIKE`. + Allowed conjunctive operators are `AND` and `OR`. However, you can use a maximum of 10 conjunctions in a search query. + + Examples: + + To return a Connector Type with the name `aws-sqs-source` and the channel `stable`, use the following syntax: + + ``` + name = aws-sqs-source and channel = stable + ```[p-] + + To return a Kafka instance with a name that starts with `aws`, use the following syntax: + + ``` + name like aws%25 + ``` + + If the parameter isn't provided, or if the value is empty, then all the Connector Type + that the user has permission to see are returned. + + Note. If the query is invalid, an error is returned. + examples: + search: + value: name = aws-sqs-source and channel = stable + explode: true + in: query + name: search + required: false + schema: + type: string + style: form + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespaceList' + description: Connector namespaces + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404Example": + $ref: '#/components/examples/404Example' + schema: + $ref: '#/components/schemas/Error' + description: No matching connector namespace exists + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: Get a list of available connector namespaces in cluster + tags: + - Connector Clusters Admin /api/connector_mgmt/v1/admin/kafka_connector_clusters/{connector_cluster_id}/upgrades/type: get: operationId: getConnectorUpgradesByType @@ -416,6 +550,230 @@ paths: summary: upgrade a connector cluster tags: - Connector Clusters Admin + /api/connector_mgmt/v1/admin/kafka_connector_namespaces: + get: + operationId: getConnectorNamespaces + parameters: + - description: Page index + examples: + page: + value: "1" + in: query + name: page + required: false + schema: + type: string + - description: Number of items in each page + examples: + size: + value: "100" + in: query + name: size + required: false + schema: + type: string + - description: |- + Specifies the order by criteria. The syntax of this parameter is + similar to the syntax of the `order by` clause of an SQL statement. + Each query can be ordered by any of the `ConnectorType` fields. + For example, to return all Connector types ordered by their name, use the following syntax: + + ```sql + name asc + ``` + + To return all Connector types ordered by their name _and_ version, use the following syntax: + + ```sql + name asc, version asc + ``` + + If the parameter isn't provided, or if the value is empty, then + the results are ordered by name. + examples: + orderBy: + value: name asc + explode: true + in: query + name: orderBy + required: false + schema: + type: string + style: form + - description: | + Search criteria. + + The syntax of this parameter is similar to the syntax of the `where` clause of a + SQL statement. Allowed fields in the search are `name`, `description`, `version`, `label`, and `channel`. + Allowed operators are `<>`, `=`, or `LIKE`. + Allowed conjunctive operators are `AND` and `OR`. However, you can use a maximum of 10 conjunctions in a search query. + + Examples: + + To return a Connector Type with the name `aws-sqs-source` and the channel `stable`, use the following syntax: + + ``` + name = aws-sqs-source and channel = stable + ```[p-] + + To return a Kafka instance with a name that starts with `aws`, use the following syntax: + + ``` + name like aws%25 + ``` + + If the parameter isn't provided, or if the value is empty, then all the Connector Type + that the user has permission to see are returned. + + Note. If the query is invalid, an error is returned. + examples: + search: + value: name = aws-sqs-source and channel = stable + explode: true + in: query + name: search + required: false + schema: + type: string + style: form + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespaceList' + description: Connector namespaces + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404Example": + $ref: '#/components/examples/404Example' + schema: + $ref: '#/components/schemas/Error' + description: No matching connector namespace exists + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: Get a list of available connector namespaces + tags: + - Connector Namespaces Admin + post: + operationId: createConnectorNamespace + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespaceWithTenantRequest' + description: Namespace to create + required: true + responses: + "202": + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespace' + description: Accepted + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404Example": + $ref: '#/components/examples/404Example' + schema: + $ref: '#/components/schemas/Error' + description: No matching connector namespace exists + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: Create a connector namespace + tags: + - Connector Namespaces Admin + /api/connector_mgmt/v1/admin/kafka_connector_namespaces/${namespace_id}: + delete: + operationId: deleteConnectorNamespace + parameters: + - description: The name of the namespace to delete + explode: false + in: path + name: namespace_id + required: true + schema: + type: string + style: simple + responses: + "204": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Deleted + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404Example": + $ref: '#/components/examples/404Example' + schema: + $ref: '#/components/schemas/Error' + description: No matching connector cluster exists + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: Delete a connector namespace + tags: + - Connector Clusters Admin components: examples: "401Example": @@ -449,6 +807,7 @@ components: ConnectorAvailableTypeUpgrade: description: An available type upgrade for a connector example: + namespace_id: namespace_id shard_metadata: available_id: 6 assigned_id: 0 @@ -458,6 +817,8 @@ components: properties: connector_id: type: string + namespace_id: + type: string connector_type_id: type: string channel: @@ -472,6 +833,7 @@ components: ConnectorAvailableOperatorUpgrade: description: An available operator upgrade for a connector example: + namespace_id: namespace_id connector_id: connector_id channel: channel operator: @@ -481,6 +843,8 @@ components: properties: connector_id: type: string + namespace_id: + type: string connector_type_id: type: string channel: @@ -488,6 +852,14 @@ components: operator: $ref: '#/components/schemas/ConnectorAvailableOperatorUpgrade_operator' type: object + ConnectorNamespaceWithTenantRequest: + allOf: + - $ref: '#/components/schemas/ConnectorNamespaceEvalRequest' + - $ref: '#/components/schemas/ConnectorNamespaceWithTenantRequest_allOf' + required: + - cluster_id + - name + - tenant ConnectorClusterList: allOf: - $ref: '#/components/schemas/List' @@ -555,6 +927,51 @@ components: allOf: - $ref: '#/components/schemas/ObjectReference' - $ref: '#/components/schemas/Error_allOf' + ConnectorNamespaceList: + allOf: + - $ref: '#/components/schemas/List' + - $ref: '#/components/schemas/ConnectorNamespaceList_allOf' + ConnectorNamespace: + allOf: + - $ref: '#/components/schemas/ObjectReference' + - $ref: '#/components/schemas/ConnectorNamespaceMeta' + - $ref: '#/components/schemas/ConnectorNamespace_allOf' + description: A connector namespace + ConnectorNamespaceMeta: + allOf: + - $ref: '#/components/schemas/ObjectMeta' + - $ref: '#/components/schemas/ConnectorNamespaceRequestMeta' + ConnectorNamespaceRequestMeta: + properties: + name: + type: string + annotations: + items: + $ref: '#/components/schemas/ConnectorNamespaceRequestMeta_annotations' + type: array + type: object + ConnectorNamespaceTenant: + properties: + kind: + $ref: '#/components/schemas/ConnectorNamespaceTenantKind' + id: + description: Either user or organisation id depending on the value of kind + type: string + required: + - id + - kind + type: object + ConnectorNamespaceTenantKind: + enum: + - user + - organisation + type: string + ConnectorNamespaceEvalRequest: + allOf: + - $ref: '#/components/schemas/ConnectorNamespaceRequestMeta' + description: An evaluation connector namespace create request + required: + - name ConnectorAvailableTypeUpgradeList_allOf: properties: items: @@ -587,6 +1004,12 @@ components: type: string available_id: type: string + ConnectorNamespaceWithTenantRequest_allOf: + properties: + cluster_id: + type: string + tenant: + $ref: '#/components/schemas/ConnectorNamespaceTenant' ConnectorClusterList_allOf: properties: items: @@ -607,6 +1030,40 @@ components: type: string operation_id: type: string + ConnectorNamespaceList_allOf: + properties: + items: + items: + $ref: '#/components/schemas/ConnectorNamespace' + type: array + ConnectorNamespace_allOf: + properties: + name: + type: string + version: + format: int64 + type: integer + cluster_id: + type: string + expiration: + type: string + tenant: + $ref: '#/components/schemas/ConnectorNamespaceTenant' + required: + - cluster_id + - id + - name + - tenant + - version + ConnectorNamespaceRequestMeta_annotations: + properties: + name: + type: string + value: + type: string + required: + - name + - value securitySchemes: Bearer: bearerFormat: JWT diff --git a/internal/connector/internal/api/admin/private/api_connector_clusters_admin.go b/internal/connector/internal/api/admin/private/api_connector_clusters_admin.go index 80fd8286a..91ec0eecf 100644 --- a/internal/connector/internal/api/admin/private/api_connector_clusters_admin.go +++ b/internal/connector/internal/api/admin/private/api_connector_clusters_admin.go @@ -26,6 +26,243 @@ var ( // ConnectorClustersAdminApiService ConnectorClustersAdminApi service type ConnectorClustersAdminApiService service +/* +DeleteConnectorNamespace Delete a connector namespace + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param namespaceId The name of the namespace to delete +@return Error +*/ +func (a *ConnectorClustersAdminApiService) DeleteConnectorNamespace(ctx _context.Context, namespaceId string) (Error, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodDelete + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue Error + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/admin/kafka_connector_namespaces/${namespace_id}" + localVarPath = strings.Replace(localVarPath, "{"+"namespace_id"+"}", _neturl.QueryEscape(parameterToString(namespaceId, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + +// GetClusterNamespacesOpts Optional parameters for the method 'GetClusterNamespaces' +type GetClusterNamespacesOpts struct { + Page optional.String + Size optional.String + OrderBy optional.String + Search optional.String +} + +/* +GetClusterNamespaces Get a list of available connector namespaces in cluster + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param connectorClusterId The id of the connector cluster + * @param optional nil or *GetClusterNamespacesOpts - Optional Parameters: + * @param "Page" (optional.String) - Page index + * @param "Size" (optional.String) - Number of items in each page + * @param "OrderBy" (optional.String) - Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the `order by` clause of an SQL statement. Each query can be ordered by any of the `ConnectorType` fields. For example, to return all Connector types ordered by their name, use the following syntax: ```sql name asc ``` To return all Connector types ordered by their name _and_ version, use the following syntax: ```sql name asc, version asc ``` If the parameter isn't provided, or if the value is empty, then the results are ordered by name. + * @param "Search" (optional.String) - Search criteria. The syntax of this parameter is similar to the syntax of the `where` clause of a SQL statement. Allowed fields in the search are `name`, `description`, `version`, `label`, and `channel`. Allowed operators are `<>`, `=`, or `LIKE`. Allowed conjunctive operators are `AND` and `OR`. However, you can use a maximum of 10 conjunctions in a search query. Examples: To return a Connector Type with the name `aws-sqs-source` and the channel `stable`, use the following syntax: ``` name = aws-sqs-source and channel = stable ```[p-] To return a Kafka instance with a name that starts with `aws`, use the following syntax: ``` name like aws%25 ``` If the parameter isn't provided, or if the value is empty, then all the Connector Type that the user has permission to see are returned. Note. If the query is invalid, an error is returned. +@return ConnectorNamespaceList +*/ +func (a *ConnectorClustersAdminApiService) GetClusterNamespaces(ctx _context.Context, connectorClusterId string, localVarOptionals *GetClusterNamespacesOpts) (ConnectorNamespaceList, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue ConnectorNamespaceList + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/admin/kafka_connector_clusters/{connector_cluster_id}/namespaces" + localVarPath = strings.Replace(localVarPath, "{"+"connector_cluster_id"+"}", _neturl.QueryEscape(parameterToString(connectorClusterId, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + if localVarOptionals != nil && localVarOptionals.Page.IsSet() { + localVarQueryParams.Add("page", parameterToString(localVarOptionals.Page.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.Size.IsSet() { + localVarQueryParams.Add("size", parameterToString(localVarOptionals.Size.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.OrderBy.IsSet() { + localVarQueryParams.Add("orderBy", parameterToString(localVarOptionals.OrderBy.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.Search.IsSet() { + localVarQueryParams.Add("search", parameterToString(localVarOptionals.Search.Value(), "")) + } + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + // GetConnectorUpgradesByOperatorOpts Optional parameters for the method 'GetConnectorUpgradesByOperator' type GetConnectorUpgradesByOperatorOpts struct { Page optional.String @@ -297,7 +534,7 @@ func (a *ConnectorClustersAdminApiService) ListConnectorClusters(ctx _context.Co ) // create path and map variables - localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/admin/kafka_connector_clusters/" + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/admin/kafka_connector_clusters" localVarHeaderParams := make(map[string]string) localVarQueryParams := _neturl.Values{} localVarFormParams := _neturl.Values{} diff --git a/internal/connector/internal/api/admin/private/api_connector_namespaces_admin.go b/internal/connector/internal/api/admin/private/api_connector_namespaces_admin.go new file mode 100644 index 000000000..bbfac794c --- /dev/null +++ b/internal/connector/internal/api/admin/private/api_connector_namespaces_admin.go @@ -0,0 +1,260 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +import ( + _context "context" + "github.com/antihax/optional" + _ioutil "io/ioutil" + _nethttp "net/http" + _neturl "net/url" +) + +// Linger please +var ( + _ _context.Context +) + +// ConnectorNamespacesAdminApiService ConnectorNamespacesAdminApi service +type ConnectorNamespacesAdminApiService service + +/* +CreateConnectorNamespace Create a connector namespace + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param connectorNamespaceWithTenantRequest Namespace to create +@return ConnectorNamespace +*/ +func (a *ConnectorNamespacesAdminApiService) CreateConnectorNamespace(ctx _context.Context, connectorNamespaceWithTenantRequest ConnectorNamespaceWithTenantRequest) (ConnectorNamespace, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodPost + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue ConnectorNamespace + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/admin/kafka_connector_namespaces" + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + // body params + localVarPostBody = &connectorNamespaceWithTenantRequest + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + +// GetConnectorNamespacesOpts Optional parameters for the method 'GetConnectorNamespaces' +type GetConnectorNamespacesOpts struct { + Page optional.String + Size optional.String + OrderBy optional.String + Search optional.String +} + +/* +GetConnectorNamespaces Get a list of available connector namespaces + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param optional nil or *GetConnectorNamespacesOpts - Optional Parameters: + * @param "Page" (optional.String) - Page index + * @param "Size" (optional.String) - Number of items in each page + * @param "OrderBy" (optional.String) - Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the `order by` clause of an SQL statement. Each query can be ordered by any of the `ConnectorType` fields. For example, to return all Connector types ordered by their name, use the following syntax: ```sql name asc ``` To return all Connector types ordered by their name _and_ version, use the following syntax: ```sql name asc, version asc ``` If the parameter isn't provided, or if the value is empty, then the results are ordered by name. + * @param "Search" (optional.String) - Search criteria. The syntax of this parameter is similar to the syntax of the `where` clause of a SQL statement. Allowed fields in the search are `name`, `description`, `version`, `label`, and `channel`. Allowed operators are `<>`, `=`, or `LIKE`. Allowed conjunctive operators are `AND` and `OR`. However, you can use a maximum of 10 conjunctions in a search query. Examples: To return a Connector Type with the name `aws-sqs-source` and the channel `stable`, use the following syntax: ``` name = aws-sqs-source and channel = stable ```[p-] To return a Kafka instance with a name that starts with `aws`, use the following syntax: ``` name like aws%25 ``` If the parameter isn't provided, or if the value is empty, then all the Connector Type that the user has permission to see are returned. Note. If the query is invalid, an error is returned. +@return ConnectorNamespaceList +*/ +func (a *ConnectorNamespacesAdminApiService) GetConnectorNamespaces(ctx _context.Context, localVarOptionals *GetConnectorNamespacesOpts) (ConnectorNamespaceList, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue ConnectorNamespaceList + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/admin/kafka_connector_namespaces" + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + if localVarOptionals != nil && localVarOptionals.Page.IsSet() { + localVarQueryParams.Add("page", parameterToString(localVarOptionals.Page.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.Size.IsSet() { + localVarQueryParams.Add("size", parameterToString(localVarOptionals.Size.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.OrderBy.IsSet() { + localVarQueryParams.Add("orderBy", parameterToString(localVarOptionals.OrderBy.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.Search.IsSet() { + localVarQueryParams.Add("search", parameterToString(localVarOptionals.Search.Value(), "")) + } + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} diff --git a/internal/connector/internal/api/admin/private/client.go b/internal/connector/internal/api/admin/private/client.go index 1a51679cd..9a3b61f73 100644 --- a/internal/connector/internal/api/admin/private/client.go +++ b/internal/connector/internal/api/admin/private/client.go @@ -49,6 +49,8 @@ type APIClient struct { // API Services ConnectorClustersAdminApi *ConnectorClustersAdminApiService + + ConnectorNamespacesAdminApi *ConnectorNamespacesAdminApiService } type service struct { @@ -68,6 +70,7 @@ func NewAPIClient(cfg *Configuration) *APIClient { // API Services c.ConnectorClustersAdminApi = (*ConnectorClustersAdminApiService)(&c.common) + c.ConnectorNamespacesAdminApi = (*ConnectorNamespacesAdminApiService)(&c.common) return c } diff --git a/internal/connector/internal/api/admin/private/model_connector_available_operator_upgrade.go b/internal/connector/internal/api/admin/private/model_connector_available_operator_upgrade.go index 8e9fefe01..f41298df2 100644 --- a/internal/connector/internal/api/admin/private/model_connector_available_operator_upgrade.go +++ b/internal/connector/internal/api/admin/private/model_connector_available_operator_upgrade.go @@ -12,6 +12,7 @@ package private // ConnectorAvailableOperatorUpgrade An available operator upgrade for a connector type ConnectorAvailableOperatorUpgrade struct { ConnectorId string `json:"connector_id,omitempty"` + NamespaceId string `json:"namespace_id,omitempty"` ConnectorTypeId string `json:"connector_type_id,omitempty"` Channel string `json:"channel,omitempty"` Operator ConnectorAvailableOperatorUpgradeOperator `json:"operator,omitempty"` diff --git a/internal/connector/internal/api/admin/private/model_connector_available_type_upgrade.go b/internal/connector/internal/api/admin/private/model_connector_available_type_upgrade.go index bf7bda5d1..623475179 100644 --- a/internal/connector/internal/api/admin/private/model_connector_available_type_upgrade.go +++ b/internal/connector/internal/api/admin/private/model_connector_available_type_upgrade.go @@ -12,6 +12,7 @@ package private // ConnectorAvailableTypeUpgrade An available type upgrade for a connector type ConnectorAvailableTypeUpgrade struct { ConnectorId string `json:"connector_id,omitempty"` + NamespaceId string `json:"namespace_id,omitempty"` ConnectorTypeId string `json:"connector_type_id,omitempty"` Channel string `json:"channel,omitempty"` ShardMetadata ConnectorAvailableTypeUpgradeShardMetadata `json:"shard_metadata,omitempty"` diff --git a/internal/connector/internal/api/admin/private/model_connector_namespace.go b/internal/connector/internal/api/admin/private/model_connector_namespace.go new file mode 100644 index 000000000..f56a75b08 --- /dev/null +++ b/internal/connector/internal/api/admin/private/model_connector_namespace.go @@ -0,0 +1,30 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +import ( + "time" +) + +// ConnectorNamespace A connector namespace +type ConnectorNamespace struct { + Id string `json:"id"` + Kind string `json:"kind,omitempty"` + Href string `json:"href,omitempty"` + Owner string `json:"owner,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + ModifiedAt time.Time `json:"modified_at,omitempty"` + Name string `json:"name"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` + Version int64 `json:"version"` + ClusterId string `json:"cluster_id"` + Expiration string `json:"expiration,omitempty"` + Tenant ConnectorNamespaceTenant `json:"tenant"` +} diff --git a/internal/connector/internal/api/admin/private/model_connector_namespace_eval_request.go b/internal/connector/internal/api/admin/private/model_connector_namespace_eval_request.go new file mode 100644 index 000000000..02b27f994 --- /dev/null +++ b/internal/connector/internal/api/admin/private/model_connector_namespace_eval_request.go @@ -0,0 +1,16 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +// ConnectorNamespaceEvalRequest An evaluation connector namespace create request +type ConnectorNamespaceEvalRequest struct { + Name string `json:"name"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` +} diff --git a/internal/connector/internal/api/admin/private/model_connector_namespace_list.go b/internal/connector/internal/api/admin/private/model_connector_namespace_list.go new file mode 100644 index 000000000..50f56c10f --- /dev/null +++ b/internal/connector/internal/api/admin/private/model_connector_namespace_list.go @@ -0,0 +1,19 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +// ConnectorNamespaceList struct for ConnectorNamespaceList +type ConnectorNamespaceList struct { + Kind string `json:"kind"` + Page int32 `json:"page"` + Size int32 `json:"size"` + Total int32 `json:"total"` + Items []ConnectorNamespace `json:"items"` +} diff --git a/internal/connector/internal/api/admin/private/model_connector_namespace_meta.go b/internal/connector/internal/api/admin/private/model_connector_namespace_meta.go new file mode 100644 index 000000000..208956202 --- /dev/null +++ b/internal/connector/internal/api/admin/private/model_connector_namespace_meta.go @@ -0,0 +1,23 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +import ( + "time" +) + +// ConnectorNamespaceMeta struct for ConnectorNamespaceMeta +type ConnectorNamespaceMeta struct { + Owner string `json:"owner,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + ModifiedAt time.Time `json:"modified_at,omitempty"` + Name string `json:"name,omitempty"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` +} diff --git a/internal/connector/internal/api/admin/private/model_connector_namespace_request_meta.go b/internal/connector/internal/api/admin/private/model_connector_namespace_request_meta.go new file mode 100644 index 000000000..96ddef504 --- /dev/null +++ b/internal/connector/internal/api/admin/private/model_connector_namespace_request_meta.go @@ -0,0 +1,16 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +// ConnectorNamespaceRequestMeta struct for ConnectorNamespaceRequestMeta +type ConnectorNamespaceRequestMeta struct { + Name string `json:"name,omitempty"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` +} diff --git a/internal/connector/internal/api/admin/private/model_connector_namespace_request_meta_annotations.go b/internal/connector/internal/api/admin/private/model_connector_namespace_request_meta_annotations.go new file mode 100644 index 000000000..087dab5fa --- /dev/null +++ b/internal/connector/internal/api/admin/private/model_connector_namespace_request_meta_annotations.go @@ -0,0 +1,16 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +// ConnectorNamespaceRequestMetaAnnotations struct for ConnectorNamespaceRequestMetaAnnotations +type ConnectorNamespaceRequestMetaAnnotations struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/internal/connector/internal/api/admin/private/model_connector_namespace_tenant.go b/internal/connector/internal/api/admin/private/model_connector_namespace_tenant.go new file mode 100644 index 000000000..efe434aca --- /dev/null +++ b/internal/connector/internal/api/admin/private/model_connector_namespace_tenant.go @@ -0,0 +1,17 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +// ConnectorNamespaceTenant struct for ConnectorNamespaceTenant +type ConnectorNamespaceTenant struct { + Kind ConnectorNamespaceTenantKind `json:"kind"` + // Either user or organisation id depending on the value of kind + Id string `json:"id"` +} diff --git a/internal/connector/internal/api/admin/private/model_connector_namespace_tenant_kind.go b/internal/connector/internal/api/admin/private/model_connector_namespace_tenant_kind.go new file mode 100644 index 000000000..8e59e7c09 --- /dev/null +++ b/internal/connector/internal/api/admin/private/model_connector_namespace_tenant_kind.go @@ -0,0 +1,19 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +// ConnectorNamespaceTenantKind the model 'ConnectorNamespaceTenantKind' +type ConnectorNamespaceTenantKind string + +// List of ConnectorNamespaceTenantKind +const ( + USER ConnectorNamespaceTenantKind = "user" + ORGANISATION ConnectorNamespaceTenantKind = "organisation" +) diff --git a/internal/connector/internal/api/admin/private/model_connector_namespace_with_tenant_request.go b/internal/connector/internal/api/admin/private/model_connector_namespace_with_tenant_request.go new file mode 100644 index 000000000..b7fd4e5a3 --- /dev/null +++ b/internal/connector/internal/api/admin/private/model_connector_namespace_with_tenant_request.go @@ -0,0 +1,18 @@ +/* + * Connector Service Fleet Manager Admin APIs + * + * Connector Service Fleet Manager Admin is a Rest API to manage connector clusters. + * + * API version: 0.0.3 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package private + +// ConnectorNamespaceWithTenantRequest struct for ConnectorNamespaceWithTenantRequest +type ConnectorNamespaceWithTenantRequest struct { + Name string `json:"name"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` + ClusterId string `json:"cluster_id"` + Tenant ConnectorNamespaceTenant `json:"tenant"` +} diff --git a/internal/connector/internal/api/dbapi/connector.go b/internal/connector/internal/api/dbapi/connector.go index df36896e6..aefd5d887 100644 --- a/internal/connector/internal/api/dbapi/connector.go +++ b/internal/connector/internal/api/dbapi/connector.go @@ -32,26 +32,13 @@ var AgentSetConnectorStatusPhase = []ConnectorStatusPhase{ ConnectorStatusPhaseDeleted, } -type TargetKind = string - -const ( - AddonTargetKind TargetKind = "addon" - CloudProviderTargetKind TargetKind = "cloud_provider" -) - -var AllTargetKind = []TargetKind{ - AddonTargetKind, - CloudProviderTargetKind, -} - type Connector struct { api.Meta - TargetKind TargetKind - AddonClusterId string - CloudProvider string - Region string - MultiAZ bool + NamespaceId *string + CloudProvider string + Region string + MultiAZ bool Name string Owner string @@ -71,8 +58,8 @@ type Connector struct { type ConnectorStatus struct { api.Meta - ClusterID string - Phase string + NamespaceID *string + Phase string } type ConnectorList []*Connector @@ -86,6 +73,8 @@ type ConnectorDeployment struct { ConnectorVersion int64 ConnectorTypeChannelId int64 ClusterID string + NamespaceID string + NamespaceName string `gorm:"->"` // readonly field, used to join connector_namespaces AllowUpgrade bool Status ConnectorDeploymentStatus `gorm:"foreignKey:ID"` } @@ -121,6 +110,7 @@ type ConnectorDeploymentTypeUpgrade struct { ConnectorID string `json:"connector_id,omitempty"` DeploymentID string `json:"deployment_id,omitempty"` ConnectorTypeId string `json:"connector_type_id,omitempty"` + NamespaceID string `json:"namespace_id,omitempty"` Channel string `json:"channel,omitempty"` ShardMetadata *ConnectorTypeUpgrade `json:"shard_metadata,omitempty"` } @@ -136,6 +126,7 @@ type ConnectorDeploymentOperatorUpgrade struct { ConnectorID string `json:"connector_id,omitempty"` DeploymentID string `json:"deployment_id,omitempty"` ConnectorTypeId string `json:"connector_type_id,omitempty"` + NamespaceID string `json:"namespace_id,omitempty"` Channel string `json:"channel,omitempty"` Operator *ConnectorOperatorUpgrade `json:"operator,omitempty"` } diff --git a/internal/connector/internal/api/dbapi/connector_namespace.go b/internal/connector/internal/api/dbapi/connector_namespace.go new file mode 100644 index 000000000..02e98b41d --- /dev/null +++ b/internal/connector/internal/api/dbapi/connector_namespace.go @@ -0,0 +1,41 @@ +package dbapi + +import ( + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/db" + "time" +) + +type ConnectorTenantUser struct { + db.Model // user id in Id, required for references and data consistency +} + +type ConnectorTenantOrganisation struct { + db.Model // org id in Id, required for references and data consistency +} + +type ConnectorNamespaceAnnotation struct { + NamespaceId string `gorm:"primaryKey;index"` + Name string `gorm:"primaryKey;not null"` + Value string `gorm:"not null"` +} + +type ConnectorNamespace struct { + db.Model + Name string `gorm:"not null;uniqueIndex:idx_connector_namespaces_name_cluster_id"` + ClusterId string `gorm:"not null;uniqueIndex:idx_connector_namespaces_name_cluster_id;index"` + + Owner string `gorm:"not null;index"` + Version int64 `gorm:"type:bigserial;index"` + Expiration *time.Time + + // metadata + Annotations []ConnectorNamespaceAnnotation `gorm:"foreignKey:NamespaceId;references:ID"` + + // tenant, only one of the below fields can be not null + TenantUserId *string `gorm:"index:connector_namespaces_user_organisation_idx;index:,where:tenant_user_id is not null"` + TenantOrganisationId *string `gorm:"index:connector_namespaces_user_organisation_idx;index:,where:tenant_organisation_id is not null"` + TenantUser *ConnectorTenantUser `gorm:"foreignKey:TenantUserId"` + TenantOrganisation *ConnectorTenantOrganisation `gorm:"foreignKey:TenantOrganisationId"` +} + +type ConnectorNamespaceList []*ConnectorNamespace diff --git a/internal/connector/internal/api/private/api/openapi.yaml b/internal/connector/internal/api/private/api/openapi.yaml index ec3acfcdc..0db1eb095 100644 --- a/internal/connector/internal/api/private/api/openapi.yaml +++ b/internal/connector/internal/api/private/api/openapi.yaml @@ -351,6 +351,10 @@ components: type: integer connector_type_id: type: string + namespace_id: + type: string + namespace_name: + type: string connector_spec: type: object allow_upgrade: diff --git a/internal/connector/internal/api/private/model_connector_deployment_spec.go b/internal/connector/internal/api/private/model_connector_deployment_spec.go index 232586a62..812b85d6e 100644 --- a/internal/connector/internal/api/private/model_connector_deployment_spec.go +++ b/internal/connector/internal/api/private/model_connector_deployment_spec.go @@ -17,6 +17,8 @@ type ConnectorDeploymentSpec struct { ConnectorId string `json:"connector_id,omitempty"` ConnectorResourceVersion int64 `json:"connector_resource_version,omitempty"` ConnectorTypeId string `json:"connector_type_id,omitempty"` + NamespaceId string `json:"namespace_id,omitempty"` + NamespaceName string `json:"namespace_name,omitempty"` ConnectorSpec map[string]interface{} `json:"connector_spec,omitempty"` // allow the connector to upgrade to a new operator // Deprecated diff --git a/internal/connector/internal/api/public/api/openapi.yaml b/internal/connector/internal/api/public/api/openapi.yaml index 8e8f8137e..c4960afd6 100644 --- a/internal/connector/internal/api/public/api/openapi.yaml +++ b/internal/connector/internal/api/public/api/openapi.yaml @@ -840,6 +840,545 @@ paths: summary: Get a connector cluster's addon parameters tags: - Connector Clusters + /api/connector_mgmt/v1/kafka_connector_clusters/{connector_cluster_id}/namespaces: + get: + description: Get a connector cluster's namespaces + operationId: getConnectorClusterNamespaces + parameters: + - description: The id of the connector cluster + explode: false + in: path + name: connector_cluster_id + required: true + schema: + type: string + style: simple + - description: Page index + examples: + page: + value: "1" + explode: true + in: query + name: page + required: false + schema: + type: string + style: form + - description: Number of items in each page + examples: + size: + value: "100" + explode: true + in: query + name: size + required: false + schema: + type: string + style: form + - description: |- + Specifies the order by criteria. The syntax of this parameter is + similar to the syntax of the `order by` clause of an SQL statement. + Each query can be ordered by any of the `ConnectorType` fields. + For example, to return all Connector types ordered by their name, use the following syntax: + + ```sql + name asc + ``` + + To return all Connector types ordered by their name _and_ version, use the following syntax: + + ```sql + name asc, version asc + ``` + + If the parameter isn't provided, or if the value is empty, then + the results are ordered by name. + examples: + orderBy: + value: name asc + explode: true + in: query + name: orderBy + required: false + schema: + type: string + style: form + - description: | + Search criteria. + + The syntax of this parameter is similar to the syntax of the `where` clause of a + SQL statement. Allowed fields in the search are `name`, `description`, `version`, `label`, and `channel`. + Allowed operators are `<>`, `=`, or `LIKE`. + Allowed conjunctive operators are `AND` and `OR`. However, you can use a maximum of 10 conjunctions in a search query. + + Examples: + + To return a Connector Type with the name `aws-sqs-source` and the channel `stable`, use the following syntax: + + ``` + name = aws-sqs-source and channel = stable + ```[p-] + + To return a Kafka instance with a name that starts with `aws`, use the following syntax: + + ``` + name like aws%25 + ``` + + If the parameter isn't provided, or if the value is empty, then all the Connector Type + that the user has permission to see are returned. + + Note. If the query is invalid, an error is returned. + examples: + search: + value: name = aws-sqs-source and channel = stable + explode: true + in: query + name: search + required: false + schema: + type: string + style: form + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespaceList' + description: The namespaces visible to user in the cluster. + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404Example": + $ref: '#/components/examples/404Example' + schema: + $ref: '#/components/schemas/Error' + description: No matching connector cluster type exists + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: Get a connector cluster's namespaces + tags: + - Connector Clusters + /api/connector_mgmt/v1/kafka_connector_namespaces: + get: + description: Returns a list of connector namespaces + operationId: listConnectorNamespaces + parameters: + - description: Page index + examples: + page: + value: "1" + explode: true + in: query + name: page + required: false + schema: + type: string + style: form + - description: Number of items in each page + examples: + size: + value: "100" + explode: true + in: query + name: size + required: false + schema: + type: string + style: form + - description: |- + Specifies the order by criteria. The syntax of this parameter is + similar to the syntax of the `order by` clause of an SQL statement. + Each query can be ordered by any of the `ConnectorType` fields. + For example, to return all Connector types ordered by their name, use the following syntax: + + ```sql + name asc + ``` + + To return all Connector types ordered by their name _and_ version, use the following syntax: + + ```sql + name asc, version asc + ``` + + If the parameter isn't provided, or if the value is empty, then + the results are ordered by name. + examples: + orderBy: + value: name asc + explode: true + in: query + name: orderBy + required: false + schema: + type: string + style: form + - description: | + Search criteria. + + The syntax of this parameter is similar to the syntax of the `where` clause of a + SQL statement. Allowed fields in the search are `name`, `description`, `version`, `label`, and `channel`. + Allowed operators are `<>`, `=`, or `LIKE`. + Allowed conjunctive operators are `AND` and `OR`. However, you can use a maximum of 10 conjunctions in a search query. + + Examples: + + To return a Connector Type with the name `aws-sqs-source` and the channel `stable`, use the following syntax: + + ``` + name = aws-sqs-source and channel = stable + ```[p-] + + To return a Kafka instance with a name that starts with `aws`, use the following syntax: + + ``` + name like aws%25 + ``` + + If the parameter isn't provided, or if the value is empty, then all the Connector Type + that the user has permission to see are returned. + + Note. If the query is invalid, an error is returned. + examples: + search: + value: name = aws-sqs-source and channel = stable + explode: true + in: query + name: search + required: false + schema: + type: string + style: form + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespaceList' + description: A list of connector namespaces + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: Returns a list of connector namespaces + tags: + - Connector Namespaces + post: + description: Create a new connector namespace + operationId: createConnectorNamespace + requestBody: + content: + application/json: + examples: + ConnectorNamespaceCreateExample: + $ref: '#/components/examples/ConnectorNamespaceCreateExample' + schema: + $ref: '#/components/schemas/ConnectorNamespaceRequest' + description: Connector namespace data + required: true + responses: + "202": + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespace' + description: Accepted + "400": + content: + application/json: + examples: + "400CreationExample": + $ref: '#/components/examples/400CreationExample' + schema: + $ref: '#/components/schemas/Error' + description: Validation errors occurred + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404Example": + $ref: '#/components/examples/404Example' + schema: + $ref: '#/components/schemas/Error' + description: The requested resource doesn't exist + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: An unexpected error occurred creating the connector namespace + security: + - Bearer: [] + summary: Create a new connector namespace + tags: + - Connector Namespaces + /api/connector_mgmt/v1/kafka_connector_namespaces/{connector_namespace_id}: + delete: + description: Delete a connector namespace + operationId: deleteConnectorNamespace + parameters: + - description: The id of the connector namespace + explode: false + in: path + name: connector_namespace_id + required: true + schema: + type: string + style: simple + responses: + "204": + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + description: Deleted + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404DeleteExample": + $ref: '#/components/examples/404DeleteExample' + schema: + $ref: '#/components/schemas/Error' + description: No resource with specified ID exists + "500": + content: + application/json: + examples: + "500DeleteExample": + $ref: '#/components/examples/500DeleteExample' + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: Delete a connector namespace + tags: + - Connector Namespaces + get: + description: Get a connector namespace + operationId: getConnectorNamespace + parameters: + - description: The id of the connector namespace + explode: false + in: path + name: connector_namespace_id + required: true + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespace' + description: The connector namespace matching the request + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404Example": + $ref: '#/components/examples/404Example' + schema: + $ref: '#/components/schemas/Error' + description: No matching connector namespace type exists + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: Get a connector namespace + tags: + - Connector Namespaces + patch: + description: udpate a connector namespace + operationId: updateConnectorNamespaceById + parameters: + - description: The id of the connector namespace + explode: false + in: path + name: connector_namespace_id + required: true + schema: + type: string + style: simple + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespacePatchRequest' + description: Data to update namespace with + required: true + responses: + "204": + description: Namespace status is updated + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404Example": + $ref: '#/components/examples/404Example' + schema: + $ref: '#/components/schemas/Error' + description: No matching connector namespace exists + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: Unexpected error occurred + security: + - Bearer: [] + summary: udpate a connector namespace + tags: + - Connector Namespaces + /api/connector_mgmt/v1/kafka_connector_namespaces/eval: + post: + description: Create a new evaluation connector namespace + operationId: createEvaluationNamespace + requestBody: + content: + application/json: + examples: + ConnectorNamespaceEvalCreateExample: + $ref: '#/components/examples/ConnectorNamespaceEvalCreateExample' + schema: + $ref: '#/components/schemas/ConnectorNamespaceEvalRequest' + description: Connector namespace data + required: true + responses: + "202": + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectorNamespace' + description: Accepted + "400": + content: + application/json: + examples: + "400CreationExample": + $ref: '#/components/examples/400CreationExample' + schema: + $ref: '#/components/schemas/Error' + description: Validation errors occurred + "401": + content: + application/json: + examples: + "401Example": + $ref: '#/components/examples/401Example' + schema: + $ref: '#/components/schemas/Error' + description: Auth token is invalid + "404": + content: + application/json: + examples: + "404Example": + $ref: '#/components/examples/404Example' + schema: + $ref: '#/components/schemas/Error' + description: The requested resource doesn't exist + "500": + content: + application/json: + examples: + "500Example": + $ref: '#/components/examples/500Example' + schema: + $ref: '#/components/schemas/Error' + description: An unexpected error occurred creating the connector namespace + security: + - Bearer: [] + summary: Create a new short lived evaluation connector namespace + tags: + - Connector Namespaces components: examples: ConnectorClusterCreateExample: @@ -944,9 +1483,7 @@ components: ConnectorCreateExample: value: name: MyLogger - deployment_location: - kind: ConnectoCluster - cluster_id: 9bsv0s7tne7g02gh5g4g + namespace_id: 9bsv0s7tne7g02gh5g4g kafka: id: 9bsv0s6brfr002pfnkh0 client_id: srvc-acct-162ef2d8-0209-4117-8462-df63c2025c26 @@ -977,6 +1514,19 @@ components: error_handling: dead_letter_queue: topic: dlq + ConnectorNamespaceCreateExample: + value: + name: MyNamespace + cluster_id: 9bsv0s7tne7g02gh5g4g + annotations: + - name: connector_mgmt.api.openshift.com/profile + value: default-profile + ConnectorNamespaceEvalCreateExample: + value: + name: MyEvalNamespace + annotations: + - name: connector_mgmt.api.openshift.com/profile + value: default-profile "400CreationExample": value: id: "103" @@ -1207,23 +1757,6 @@ components: required: - client_id - client_secret - DeploymentLocation: - discriminator: - mapping: - ConnectorCluster: '#/components/schemas/ConnectorClusterTarget' - propertyName: kind - oneOf: - - $ref: '#/components/schemas/ConnectorClusterTarget' - ConnectorClusterTarget: - description: Targets workloads to an addon cluster - properties: - kind: - type: string - cluster_id: - type: string - required: - - kind - type: object VersionMetadata: allOf: - $ref: '#/components/schemas/ObjectReference' @@ -1326,15 +1859,14 @@ components: type: string connector_type_id: type: string + namespace_id: + type: string channel: $ref: '#/components/schemas/Channel' - deployment_location: - $ref: '#/components/schemas/DeploymentLocation' desired_state: $ref: '#/components/schemas/ConnectorDesiredState' required: - connector_type_id - - deployment_location - desired_state - name ConnectorRequest: @@ -1378,6 +1910,65 @@ components: allOf: - $ref: '#/components/schemas/List' - $ref: '#/components/schemas/ConnectorTypeList_allOf' + ConnectorNamespaceRequestMeta: + properties: + name: + type: string + annotations: + items: + $ref: '#/components/schemas/ConnectorNamespaceRequestMeta_annotations' + type: array + type: object + ConnectorNamespaceMeta: + allOf: + - $ref: '#/components/schemas/ObjectMeta' + - $ref: '#/components/schemas/ConnectorNamespaceRequestMeta' + ConnectorNamespaceTenantKind: + enum: + - user + - organisation + type: string + ConnectorNamespaceTenant: + properties: + kind: + $ref: '#/components/schemas/ConnectorNamespaceTenantKind' + id: + description: Either user or organisation id depending on the value of kind + type: string + required: + - id + - kind + type: object + ConnectorNamespaceRequest: + allOf: + - $ref: '#/components/schemas/ConnectorNamespaceRequestMeta' + - $ref: '#/components/schemas/ConnectorNamespaceRequest_allOf' + description: A connector namespace create request + required: + - cluster_id + - kind + - name + ConnectorNamespacePatchRequest: + allOf: + - $ref: '#/components/schemas/ConnectorNamespaceRequestMeta' + - type: object + description: A connector namespace patch request + ConnectorNamespaceEvalRequest: + allOf: + - $ref: '#/components/schemas/ConnectorNamespaceRequestMeta' + description: An evaluation connector namespace create request + required: + - name + ConnectorNamespaceList: + allOf: + - $ref: '#/components/schemas/List' + - $ref: '#/components/schemas/ConnectorNamespaceList_allOf' + ConnectorNamespace: + allOf: + - $ref: '#/components/schemas/ObjectReference' + - $ref: '#/components/schemas/ConnectorNamespaceMeta' + - $ref: '#/components/schemas/ConnectorNamespace_allOf' + description: A connector namespace Error_allOf: properties: code: @@ -1466,6 +2057,46 @@ components: items: $ref: '#/components/schemas/ConnectorType' type: array + ConnectorNamespaceRequestMeta_annotations: + properties: + name: + type: string + value: + type: string + required: + - name + - value + ConnectorNamespaceRequest_allOf: + properties: + cluster_id: + type: string + kind: + $ref: '#/components/schemas/ConnectorNamespaceTenantKind' + ConnectorNamespaceList_allOf: + properties: + items: + items: + $ref: '#/components/schemas/ConnectorNamespace' + type: array + ConnectorNamespace_allOf: + properties: + name: + type: string + version: + format: int64 + type: integer + cluster_id: + type: string + expiration: + type: string + tenant: + $ref: '#/components/schemas/ConnectorNamespaceTenant' + required: + - cluster_id + - id + - name + - tenant + - version securitySchemes: Bearer: bearerFormat: JWT diff --git a/internal/connector/internal/api/public/api_connector_clusters.go b/internal/connector/internal/api/public/api_connector_clusters.go index d37cec841..3e44a9eb6 100644 --- a/internal/connector/internal/api/public/api_connector_clusters.go +++ b/internal/connector/internal/api/public/api_connector_clusters.go @@ -466,6 +466,138 @@ func (a *ConnectorClustersApiService) GetConnectorClusterAddonParameters(ctx _co return localVarReturnValue, localVarHTTPResponse, nil } +// GetConnectorClusterNamespacesOpts Optional parameters for the method 'GetConnectorClusterNamespaces' +type GetConnectorClusterNamespacesOpts struct { + Page optional.String + Size optional.String + OrderBy optional.String + Search optional.String +} + +/* +GetConnectorClusterNamespaces Get a connector cluster's namespaces +Get a connector cluster's namespaces + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param connectorClusterId The id of the connector cluster + * @param optional nil or *GetConnectorClusterNamespacesOpts - Optional Parameters: + * @param "Page" (optional.String) - Page index + * @param "Size" (optional.String) - Number of items in each page + * @param "OrderBy" (optional.String) - Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the `order by` clause of an SQL statement. Each query can be ordered by any of the `ConnectorType` fields. For example, to return all Connector types ordered by their name, use the following syntax: ```sql name asc ``` To return all Connector types ordered by their name _and_ version, use the following syntax: ```sql name asc, version asc ``` If the parameter isn't provided, or if the value is empty, then the results are ordered by name. + * @param "Search" (optional.String) - Search criteria. The syntax of this parameter is similar to the syntax of the `where` clause of a SQL statement. Allowed fields in the search are `name`, `description`, `version`, `label`, and `channel`. Allowed operators are `<>`, `=`, or `LIKE`. Allowed conjunctive operators are `AND` and `OR`. However, you can use a maximum of 10 conjunctions in a search query. Examples: To return a Connector Type with the name `aws-sqs-source` and the channel `stable`, use the following syntax: ``` name = aws-sqs-source and channel = stable ```[p-] To return a Kafka instance with a name that starts with `aws`, use the following syntax: ``` name like aws%25 ``` If the parameter isn't provided, or if the value is empty, then all the Connector Type that the user has permission to see are returned. Note. If the query is invalid, an error is returned. +@return ConnectorNamespaceList +*/ +func (a *ConnectorClustersApiService) GetConnectorClusterNamespaces(ctx _context.Context, connectorClusterId string, localVarOptionals *GetConnectorClusterNamespacesOpts) (ConnectorNamespaceList, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue ConnectorNamespaceList + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/kafka_connector_clusters/{connector_cluster_id}/namespaces" + localVarPath = strings.Replace(localVarPath, "{"+"connector_cluster_id"+"}", _neturl.QueryEscape(parameterToString(connectorClusterId, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + if localVarOptionals != nil && localVarOptionals.Page.IsSet() { + localVarQueryParams.Add("page", parameterToString(localVarOptionals.Page.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.Size.IsSet() { + localVarQueryParams.Add("size", parameterToString(localVarOptionals.Size.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.OrderBy.IsSet() { + localVarQueryParams.Add("orderBy", parameterToString(localVarOptionals.OrderBy.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.Search.IsSet() { + localVarQueryParams.Add("search", parameterToString(localVarOptionals.Search.Value(), "")) + } + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + // ListConnectorClustersOpts Optional parameters for the method 'ListConnectorClusters' type ListConnectorClustersOpts struct { Page optional.String diff --git a/internal/connector/internal/api/public/api_connector_namespaces.go b/internal/connector/internal/api/public/api_connector_namespaces.go new file mode 100644 index 000000000..62c70cf5b --- /dev/null +++ b/internal/connector/internal/api/public/api_connector_namespaces.go @@ -0,0 +1,693 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +import ( + _context "context" + "github.com/antihax/optional" + _ioutil "io/ioutil" + _nethttp "net/http" + _neturl "net/url" + "strings" +) + +// Linger please +var ( + _ _context.Context +) + +// ConnectorNamespacesApiService ConnectorNamespacesApi service +type ConnectorNamespacesApiService service + +/* +CreateConnectorNamespace Create a new connector namespace +Create a new connector namespace + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param connectorNamespaceRequest Connector namespace data +@return ConnectorNamespace +*/ +func (a *ConnectorNamespacesApiService) CreateConnectorNamespace(ctx _context.Context, connectorNamespaceRequest ConnectorNamespaceRequest) (ConnectorNamespace, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodPost + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue ConnectorNamespace + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/kafka_connector_namespaces" + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + // body params + localVarPostBody = &connectorNamespaceRequest + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + +/* +CreateEvaluationNamespace Create a new short lived evaluation connector namespace +Create a new evaluation connector namespace + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param connectorNamespaceEvalRequest Connector namespace data +@return ConnectorNamespace +*/ +func (a *ConnectorNamespacesApiService) CreateEvaluationNamespace(ctx _context.Context, connectorNamespaceEvalRequest ConnectorNamespaceEvalRequest) (ConnectorNamespace, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodPost + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue ConnectorNamespace + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/kafka_connector_namespaces/eval" + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + // body params + localVarPostBody = &connectorNamespaceEvalRequest + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + +/* +DeleteConnectorNamespace Delete a connector namespace +Delete a connector namespace + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param connectorNamespaceId The id of the connector namespace +@return Error +*/ +func (a *ConnectorNamespacesApiService) DeleteConnectorNamespace(ctx _context.Context, connectorNamespaceId string) (Error, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodDelete + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue Error + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/kafka_connector_namespaces/{connector_namespace_id}" + localVarPath = strings.Replace(localVarPath, "{"+"connector_namespace_id"+"}", _neturl.QueryEscape(parameterToString(connectorNamespaceId, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + +/* +GetConnectorNamespace Get a connector namespace +Get a connector namespace + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param connectorNamespaceId The id of the connector namespace +@return ConnectorNamespace +*/ +func (a *ConnectorNamespacesApiService) GetConnectorNamespace(ctx _context.Context, connectorNamespaceId string) (ConnectorNamespace, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue ConnectorNamespace + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/kafka_connector_namespaces/{connector_namespace_id}" + localVarPath = strings.Replace(localVarPath, "{"+"connector_namespace_id"+"}", _neturl.QueryEscape(parameterToString(connectorNamespaceId, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + +// ListConnectorNamespacesOpts Optional parameters for the method 'ListConnectorNamespaces' +type ListConnectorNamespacesOpts struct { + Page optional.String + Size optional.String + OrderBy optional.String + Search optional.String +} + +/* +ListConnectorNamespaces Returns a list of connector namespaces +Returns a list of connector namespaces + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param optional nil or *ListConnectorNamespacesOpts - Optional Parameters: + * @param "Page" (optional.String) - Page index + * @param "Size" (optional.String) - Number of items in each page + * @param "OrderBy" (optional.String) - Specifies the order by criteria. The syntax of this parameter is similar to the syntax of the `order by` clause of an SQL statement. Each query can be ordered by any of the `ConnectorType` fields. For example, to return all Connector types ordered by their name, use the following syntax: ```sql name asc ``` To return all Connector types ordered by their name _and_ version, use the following syntax: ```sql name asc, version asc ``` If the parameter isn't provided, or if the value is empty, then the results are ordered by name. + * @param "Search" (optional.String) - Search criteria. The syntax of this parameter is similar to the syntax of the `where` clause of a SQL statement. Allowed fields in the search are `name`, `description`, `version`, `label`, and `channel`. Allowed operators are `<>`, `=`, or `LIKE`. Allowed conjunctive operators are `AND` and `OR`. However, you can use a maximum of 10 conjunctions in a search query. Examples: To return a Connector Type with the name `aws-sqs-source` and the channel `stable`, use the following syntax: ``` name = aws-sqs-source and channel = stable ```[p-] To return a Kafka instance with a name that starts with `aws`, use the following syntax: ``` name like aws%25 ``` If the parameter isn't provided, or if the value is empty, then all the Connector Type that the user has permission to see are returned. Note. If the query is invalid, an error is returned. +@return ConnectorNamespaceList +*/ +func (a *ConnectorNamespacesApiService) ListConnectorNamespaces(ctx _context.Context, localVarOptionals *ListConnectorNamespacesOpts) (ConnectorNamespaceList, *_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue ConnectorNamespaceList + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/kafka_connector_namespaces" + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + if localVarOptionals != nil && localVarOptionals.Page.IsSet() { + localVarQueryParams.Add("page", parameterToString(localVarOptionals.Page.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.Size.IsSet() { + localVarQueryParams.Add("size", parameterToString(localVarOptionals.Size.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.OrderBy.IsSet() { + localVarQueryParams.Add("orderBy", parameterToString(localVarOptionals.OrderBy.Value(), "")) + } + if localVarOptionals != nil && localVarOptionals.Search.IsSet() { + localVarQueryParams.Add("search", parameterToString(localVarOptionals.Search.Value(), "")) + } + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + +/* +UpdateConnectorNamespaceById udpate a connector namespace +udpate a connector namespace + * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param connectorNamespaceId The id of the connector namespace + * @param connectorNamespacePatchRequest Data to update namespace with +*/ +func (a *ConnectorNamespacesApiService) UpdateConnectorNamespaceById(ctx _context.Context, connectorNamespaceId string, connectorNamespacePatchRequest ConnectorNamespacePatchRequest) (*_nethttp.Response, error) { + var ( + localVarHTTPMethod = _nethttp.MethodPatch + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + ) + + // create path and map variables + localVarPath := a.client.cfg.BasePath + "/api/connector_mgmt/v1/kafka_connector_namespaces/{connector_namespace_id}" + localVarPath = strings.Replace(localVarPath, "{"+"connector_namespace_id"+"}", _neturl.QueryEscape(parameterToString(connectorNamespaceId, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := _neturl.Values{} + localVarFormParams := _neturl.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{"application/json"} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + // body params + localVarPostBody = &connectorNamespacePatchRequest + r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(r) + if err != nil || localVarHTTPResponse == nil { + return localVarHTTPResponse, err + } + + localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + if err != nil { + return localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 401 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v Error + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarHTTPResponse, newErr + } + + return localVarHTTPResponse, nil +} diff --git a/internal/connector/internal/api/public/client.go b/internal/connector/internal/api/public/client.go index a693cd3a3..e6c6c9578 100644 --- a/internal/connector/internal/api/public/client.go +++ b/internal/connector/internal/api/public/client.go @@ -50,6 +50,8 @@ type APIClient struct { ConnectorClustersApi *ConnectorClustersApiService + ConnectorNamespacesApi *ConnectorNamespacesApiService + ConnectorServiceApi *ConnectorServiceApiService ConnectorTypesApi *ConnectorTypesApiService @@ -74,6 +76,7 @@ func NewAPIClient(cfg *Configuration) *APIClient { // API Services c.ConnectorClustersApi = (*ConnectorClustersApiService)(&c.common) + c.ConnectorNamespacesApi = (*ConnectorNamespacesApiService)(&c.common) c.ConnectorServiceApi = (*ConnectorServiceApiService)(&c.common) c.ConnectorTypesApi = (*ConnectorTypesApiService)(&c.common) c.ConnectorsApi = (*ConnectorsApiService)(&c.common) diff --git a/internal/connector/internal/api/public/model_connector.go b/internal/connector/internal/api/public/model_connector.go index a8f57d761..a8d034611 100644 --- a/internal/connector/internal/api/public/model_connector.go +++ b/internal/connector/internal/api/public/model_connector.go @@ -15,21 +15,21 @@ import ( // Connector struct for Connector type Connector struct { - Id string `json:"id,omitempty"` - Kind string `json:"kind,omitempty"` - Href string `json:"href,omitempty"` - Owner string `json:"owner,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - ModifiedAt time.Time `json:"modified_at,omitempty"` - Name string `json:"name"` - ConnectorTypeId string `json:"connector_type_id"` - Channel Channel `json:"channel,omitempty"` - DeploymentLocation DeploymentLocation `json:"deployment_location"` - DesiredState ConnectorDesiredState `json:"desired_state"` - ResourceVersion int64 `json:"resource_version,omitempty"` - Kafka KafkaConnectionSettings `json:"kafka"` - ServiceAccount ServiceAccount `json:"service_account"` - SchemaRegistry SchemaRegistryConnectionSettings `json:"schema_registry,omitempty"` - Connector map[string]interface{} `json:"connector"` - Status ConnectorStatusStatus `json:"status,omitempty"` + Id string `json:"id,omitempty"` + Kind string `json:"kind,omitempty"` + Href string `json:"href,omitempty"` + Owner string `json:"owner,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + ModifiedAt time.Time `json:"modified_at,omitempty"` + Name string `json:"name"` + ConnectorTypeId string `json:"connector_type_id"` + NamespaceId string `json:"namespace_id,omitempty"` + Channel Channel `json:"channel,omitempty"` + DesiredState ConnectorDesiredState `json:"desired_state"` + ResourceVersion int64 `json:"resource_version,omitempty"` + Kafka KafkaConnectionSettings `json:"kafka"` + ServiceAccount ServiceAccount `json:"service_account"` + SchemaRegistry SchemaRegistryConnectionSettings `json:"schema_registry,omitempty"` + Connector map[string]interface{} `json:"connector"` + Status ConnectorStatusStatus `json:"status,omitempty"` } diff --git a/internal/connector/internal/api/public/model_connector_meta.go b/internal/connector/internal/api/public/model_connector_meta.go index b6dbc0560..f4e78e591 100644 --- a/internal/connector/internal/api/public/model_connector_meta.go +++ b/internal/connector/internal/api/public/model_connector_meta.go @@ -15,13 +15,13 @@ import ( // ConnectorMeta struct for ConnectorMeta type ConnectorMeta struct { - Owner string `json:"owner,omitempty"` - CreatedAt time.Time `json:"created_at,omitempty"` - ModifiedAt time.Time `json:"modified_at,omitempty"` - Name string `json:"name"` - ConnectorTypeId string `json:"connector_type_id"` - Channel Channel `json:"channel,omitempty"` - DeploymentLocation DeploymentLocation `json:"deployment_location"` - DesiredState ConnectorDesiredState `json:"desired_state"` - ResourceVersion int64 `json:"resource_version,omitempty"` + Owner string `json:"owner,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + ModifiedAt time.Time `json:"modified_at,omitempty"` + Name string `json:"name"` + ConnectorTypeId string `json:"connector_type_id"` + NamespaceId string `json:"namespace_id,omitempty"` + Channel Channel `json:"channel,omitempty"` + DesiredState ConnectorDesiredState `json:"desired_state"` + ResourceVersion int64 `json:"resource_version,omitempty"` } diff --git a/internal/connector/internal/api/public/model_connector_namespace.go b/internal/connector/internal/api/public/model_connector_namespace.go new file mode 100644 index 000000000..39ede17b4 --- /dev/null +++ b/internal/connector/internal/api/public/model_connector_namespace.go @@ -0,0 +1,30 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +import ( + "time" +) + +// ConnectorNamespace A connector namespace +type ConnectorNamespace struct { + Id string `json:"id"` + Kind string `json:"kind,omitempty"` + Href string `json:"href,omitempty"` + Owner string `json:"owner,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + ModifiedAt time.Time `json:"modified_at,omitempty"` + Name string `json:"name"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` + Version int64 `json:"version"` + ClusterId string `json:"cluster_id"` + Expiration string `json:"expiration,omitempty"` + Tenant ConnectorNamespaceTenant `json:"tenant"` +} diff --git a/internal/connector/internal/api/public/model_connector_namespace_eval_request.go b/internal/connector/internal/api/public/model_connector_namespace_eval_request.go new file mode 100644 index 000000000..3941867e0 --- /dev/null +++ b/internal/connector/internal/api/public/model_connector_namespace_eval_request.go @@ -0,0 +1,16 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +// ConnectorNamespaceEvalRequest An evaluation connector namespace create request +type ConnectorNamespaceEvalRequest struct { + Name string `json:"name"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` +} diff --git a/internal/connector/internal/api/public/model_connector_namespace_list.go b/internal/connector/internal/api/public/model_connector_namespace_list.go new file mode 100644 index 000000000..f675524f0 --- /dev/null +++ b/internal/connector/internal/api/public/model_connector_namespace_list.go @@ -0,0 +1,19 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +// ConnectorNamespaceList struct for ConnectorNamespaceList +type ConnectorNamespaceList struct { + Kind string `json:"kind"` + Page int32 `json:"page"` + Size int32 `json:"size"` + Total int32 `json:"total"` + Items []ConnectorNamespace `json:"items"` +} diff --git a/internal/connector/internal/api/public/model_connector_namespace_meta.go b/internal/connector/internal/api/public/model_connector_namespace_meta.go new file mode 100644 index 000000000..aa66c88d7 --- /dev/null +++ b/internal/connector/internal/api/public/model_connector_namespace_meta.go @@ -0,0 +1,23 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +import ( + "time" +) + +// ConnectorNamespaceMeta struct for ConnectorNamespaceMeta +type ConnectorNamespaceMeta struct { + Owner string `json:"owner,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + ModifiedAt time.Time `json:"modified_at,omitempty"` + Name string `json:"name,omitempty"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` +} diff --git a/internal/connector/internal/api/public/model_connector_namespace_patch_request.go b/internal/connector/internal/api/public/model_connector_namespace_patch_request.go new file mode 100644 index 000000000..47fc9e0c3 --- /dev/null +++ b/internal/connector/internal/api/public/model_connector_namespace_patch_request.go @@ -0,0 +1,16 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +// ConnectorNamespacePatchRequest A connector namespace patch request +type ConnectorNamespacePatchRequest struct { + Name string `json:"name,omitempty"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` +} diff --git a/internal/connector/internal/api/public/model_connector_namespace_request.go b/internal/connector/internal/api/public/model_connector_namespace_request.go new file mode 100644 index 000000000..fd521f7c8 --- /dev/null +++ b/internal/connector/internal/api/public/model_connector_namespace_request.go @@ -0,0 +1,18 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +// ConnectorNamespaceRequest A connector namespace create request +type ConnectorNamespaceRequest struct { + Name string `json:"name"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` + ClusterId string `json:"cluster_id"` + Kind ConnectorNamespaceTenantKind `json:"kind"` +} diff --git a/internal/connector/internal/api/public/model_connector_namespace_request_meta.go b/internal/connector/internal/api/public/model_connector_namespace_request_meta.go new file mode 100644 index 000000000..464990f84 --- /dev/null +++ b/internal/connector/internal/api/public/model_connector_namespace_request_meta.go @@ -0,0 +1,16 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +// ConnectorNamespaceRequestMeta struct for ConnectorNamespaceRequestMeta +type ConnectorNamespaceRequestMeta struct { + Name string `json:"name,omitempty"` + Annotations []ConnectorNamespaceRequestMetaAnnotations `json:"annotations,omitempty"` +} diff --git a/internal/connector/internal/api/public/model_connector_cluster_target.go b/internal/connector/internal/api/public/model_connector_namespace_request_meta_annotations.go similarity index 52% rename from internal/connector/internal/api/public/model_connector_cluster_target.go rename to internal/connector/internal/api/public/model_connector_namespace_request_meta_annotations.go index 7bb8b5b55..e8254842a 100644 --- a/internal/connector/internal/api/public/model_connector_cluster_target.go +++ b/internal/connector/internal/api/public/model_connector_namespace_request_meta_annotations.go @@ -9,8 +9,8 @@ package public -// ConnectorClusterTarget Targets workloads to an addon cluster -type ConnectorClusterTarget struct { - Kind string `json:"kind"` - ClusterId string `json:"cluster_id,omitempty"` +// ConnectorNamespaceRequestMetaAnnotations struct for ConnectorNamespaceRequestMetaAnnotations +type ConnectorNamespaceRequestMetaAnnotations struct { + Name string `json:"name"` + Value string `json:"value"` } diff --git a/internal/connector/internal/api/public/model_connector_namespace_tenant.go b/internal/connector/internal/api/public/model_connector_namespace_tenant.go new file mode 100644 index 000000000..e376c5669 --- /dev/null +++ b/internal/connector/internal/api/public/model_connector_namespace_tenant.go @@ -0,0 +1,17 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +// ConnectorNamespaceTenant struct for ConnectorNamespaceTenant +type ConnectorNamespaceTenant struct { + Kind ConnectorNamespaceTenantKind `json:"kind"` + // Either user or organisation id depending on the value of kind + Id string `json:"id"` +} diff --git a/internal/connector/internal/api/public/model_connector_namespace_tenant_kind.go b/internal/connector/internal/api/public/model_connector_namespace_tenant_kind.go new file mode 100644 index 000000000..4d193cdbf --- /dev/null +++ b/internal/connector/internal/api/public/model_connector_namespace_tenant_kind.go @@ -0,0 +1,19 @@ +/* + * Connector Service Fleet Manager + * + * Connector Service Fleet Manager is a Rest API to manage connectors. + * + * API version: 0.1.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package public + +// ConnectorNamespaceTenantKind the model 'ConnectorNamespaceTenantKind' +type ConnectorNamespaceTenantKind string + +// List of ConnectorNamespaceTenantKind +const ( + CONNECTORNAMESPACETENANTKIND_USER ConnectorNamespaceTenantKind = "user" + CONNECTORNAMESPACETENANTKIND_ORGANISATION ConnectorNamespaceTenantKind = "organisation" +) diff --git a/internal/connector/internal/api/public/model_connector_request.go b/internal/connector/internal/api/public/model_connector_request.go index 1449adb30..917ebaaff 100644 --- a/internal/connector/internal/api/public/model_connector_request.go +++ b/internal/connector/internal/api/public/model_connector_request.go @@ -11,13 +11,13 @@ package public // ConnectorRequest struct for ConnectorRequest type ConnectorRequest struct { - Name string `json:"name"` - ConnectorTypeId string `json:"connector_type_id"` - Channel Channel `json:"channel,omitempty"` - DeploymentLocation DeploymentLocation `json:"deployment_location"` - DesiredState ConnectorDesiredState `json:"desired_state"` - Kafka KafkaConnectionSettings `json:"kafka"` - ServiceAccount ServiceAccount `json:"service_account"` - SchemaRegistry SchemaRegistryConnectionSettings `json:"schema_registry,omitempty"` - Connector map[string]interface{} `json:"connector"` + Name string `json:"name"` + ConnectorTypeId string `json:"connector_type_id"` + NamespaceId string `json:"namespace_id,omitempty"` + Channel Channel `json:"channel,omitempty"` + DesiredState ConnectorDesiredState `json:"desired_state"` + Kafka KafkaConnectionSettings `json:"kafka"` + ServiceAccount ServiceAccount `json:"service_account"` + SchemaRegistry SchemaRegistryConnectionSettings `json:"schema_registry,omitempty"` + Connector map[string]interface{} `json:"connector"` } diff --git a/internal/connector/internal/api/public/model_connector_request_meta.go b/internal/connector/internal/api/public/model_connector_request_meta.go index ad9a71fc3..a622f6466 100644 --- a/internal/connector/internal/api/public/model_connector_request_meta.go +++ b/internal/connector/internal/api/public/model_connector_request_meta.go @@ -11,9 +11,9 @@ package public // ConnectorRequestMeta struct for ConnectorRequestMeta type ConnectorRequestMeta struct { - Name string `json:"name"` - ConnectorTypeId string `json:"connector_type_id"` - Channel Channel `json:"channel,omitempty"` - DeploymentLocation DeploymentLocation `json:"deployment_location"` - DesiredState ConnectorDesiredState `json:"desired_state"` + Name string `json:"name"` + ConnectorTypeId string `json:"connector_type_id"` + NamespaceId string `json:"namespace_id,omitempty"` + Channel Channel `json:"channel,omitempty"` + DesiredState ConnectorDesiredState `json:"desired_state"` } diff --git a/internal/connector/internal/api/public/model_deployment_location.go b/internal/connector/internal/api/public/model_deployment_location.go deleted file mode 100644 index 42807f5e0..000000000 --- a/internal/connector/internal/api/public/model_deployment_location.go +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Connector Service Fleet Manager - * - * Connector Service Fleet Manager is a Rest API to manage connectors. - * - * API version: 0.1.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package public - -// DeploymentLocation struct for DeploymentLocation -type DeploymentLocation struct { - Kind string `json:"kind"` - ClusterId string `json:"cluster_id,omitempty"` -} diff --git a/internal/connector/internal/config/connectors.go b/internal/connector/internal/config/connectors.go index 2dfd0a3dc..d9d5898eb 100644 --- a/internal/connector/internal/config/connectors.go +++ b/internal/connector/internal/config/connectors.go @@ -13,11 +13,14 @@ import ( "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/shared" "github.com/golang/glog" "github.com/spf13/pflag" + "time" ) type ConnectorsConfig struct { - ConnectorCatalogDirs []string `json:"connector_types"` - CatalogEntries []ConnectorCatalogEntry `json:"connector_type_urls"` + ConnectorEvalDuration time.Duration `json:"connector_eval_duration"` + ConnectorEvalOrganizations []string `json:"connector_eval_organizations"` + ConnectorCatalogDirs []string `json:"connector_types"` + CatalogEntries []ConnectorCatalogEntry `json:"connector_type_urls"` } var _ environments.ConfigModule = &ConnectorsConfig{} @@ -38,6 +41,8 @@ func NewConnectorsConfig() *ConnectorsConfig { func (c *ConnectorsConfig) AddFlags(fs *pflag.FlagSet) { fs.StringArrayVar(&c.ConnectorCatalogDirs, "connector-catalog", c.ConnectorCatalogDirs, "Directory containing connector catalog entries") + fs.DurationVar(&c.ConnectorEvalDuration, "connector-eval-duration", c.ConnectorEvalDuration, "Connector eval duration in golang duration format") + fs.StringArrayVar(&c.ConnectorEvalOrganizations, "connector-eval-organizations", c.ConnectorEvalOrganizations, "Connector eval organization IDs") } func (c *ConnectorsConfig) ReadFiles() error { diff --git a/internal/connector/internal/environments/development.go b/internal/connector/internal/environments/development.go index 4daef64ec..9088ad205 100644 --- a/internal/connector/internal/environments/development.go +++ b/internal/connector/internal/environments/development.go @@ -19,5 +19,6 @@ func NewDevelopmentEnvLoader() environments.EnvLoader { "mas-sso-base-url": "https://identity.api.stage.openshift.com", "mas-sso-realm": "rhoas", "osd-idp-mas-sso-realm": "rhoas-kafka-sre", + "connector-eval-duration": "48h", } } diff --git a/internal/connector/internal/environments/integration.go b/internal/connector/internal/environments/integration.go index e5c232811..c72073708 100644 --- a/internal/connector/internal/environments/integration.go +++ b/internal/connector/internal/environments/integration.go @@ -33,6 +33,7 @@ func (b IntegrationEnvLoader) Defaults() map[string]string { "max-allowed-instances": "1", "mas-sso-base-url": "https://identity.api.stage.openshift.com", "mas-sso-realm": "rhoas", + "connector-eval-duration": "48h", } } diff --git a/internal/connector/internal/environments/production.go b/internal/connector/internal/environments/production.go index 669a49675..69bc5d27c 100644 --- a/internal/connector/internal/environments/production.go +++ b/internal/connector/internal/environments/production.go @@ -4,13 +4,14 @@ import "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/environments" func NewProductionEnvLoader() environments.EnvLoader { return environments.SimpleEnvLoader{ - "v": "1", - "ocm-debug": "false", - "enable-ocm-mock": "false", - "enable-sentry": "true", - "enable-deny-list": "true", - "max-allowed-instances": "1", - "mas-sso-realm": "rhoas", - "mas-sso-base-url": "https://identity.api.openshift.com", + "v": "1", + "ocm-debug": "false", + "enable-ocm-mock": "false", + "enable-sentry": "true", + "enable-deny-list": "true", + "max-allowed-instances": "1", + "mas-sso-realm": "rhoas", + "mas-sso-base-url": "https://identity.api.openshift.com", + "connector-eval-duration": "48h", } } diff --git a/internal/connector/internal/environments/stage.go b/internal/connector/internal/environments/stage.go index cc81ab4ab..69319d4b7 100644 --- a/internal/connector/internal/environments/stage.go +++ b/internal/connector/internal/environments/stage.go @@ -4,11 +4,12 @@ import "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/environments" func NewStageEnvLoader() environments.EnvLoader { return environments.SimpleEnvLoader{ - "ocm-base-url": "https://api.stage.openshift.com", - "enable-ocm-mock": "false", - "enable-deny-list": "true", - "max-allowed-instances": "1", - "mas-sso-base-url": "https://identity.api.stage.openshift.com", - "mas-sso-realm": "rhoas", + "ocm-base-url": "https://api.stage.openshift.com", + "enable-ocm-mock": "false", + "enable-deny-list": "true", + "max-allowed-instances": "1", + "mas-sso-base-url": "https://identity.api.stage.openshift.com", + "mas-sso-realm": "rhoas", + "connector-eval-duration": "48h", } } diff --git a/internal/connector/internal/generated/bindata.go b/internal/connector/internal/generated/bindata.go index 22b9ae7bd..4b4071277 100644 --- a/internal/connector/internal/generated/bindata.go +++ b/internal/connector/internal/generated/bindata.go @@ -78,7 +78,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _connector_mgmtYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\xff\x73\xdb\x38\xae\xf8\xef\xf9\x2b\xf8\x71\x3f\x37\xb9\x7b\x2f\x76\x6c\xc7\xf9\xe6\x79\xbd\x99\x34\x49\xbb\xd9\x4d\xd3\x6e\x92\x6e\xb7\x77\x73\xe3\xd0\x12\x6c\xb3\x91\x48\x85\xa4\x92\xba\x77\xef\x7f\x7f\x43\x52\xb2\x28\x89\xb2\x65\x27\xfd\xb6\x6b\xcf\xdc\x6d\x23\x81\x20\x08\x02\x20\x00\x82\x14\x8b\x80\xe2\x88\xf4\xd1\x4e\xab\xdd\x6a\xa3\x67\x88\x02\xf8\x48\x4e\x88\x40\x58\xa0\x11\xe1\x42\xa2\x80\x50\x40\x92\x21\x1c\x04\xec\x01\x09\x16\x02\x3a\x3b\x39\x15\xea\xd1\x2d\x65\x0f\x06\x5a\x35\xa0\x28\x41\x87\x7c\xe6\xc5\x21\x50\xd9\xda\x78\x86\x8e\x82\x00\x01\xf5\x23\x46\xa8\x14\xc8\x87\x11\xa1\xe0\xa3\x09\x70\x40\x0f\x24\x08\xd0\x10\x90\x4f\x84\xc7\xee\x81\xe3\x61\x00\x68\x38\x55\x3d\xa1\x58\x00\x17\x2d\x74\x36\x42\x52\xc3\xaa\x0e\x12\xea\x18\xba\x05\x88\x0c\x25\x33\xcc\x1b\xcf\x50\x23\xe2\xe4\x1e\x4b\x68\x6c\x21\xec\xab\x51\x40\xa8\x80\xe5\x04\x50\xc3\x63\x94\x82\x27\x19\x1f\x84\xe3\x50\x36\x13\xc8\xd6\x14\x87\x41\x03\x8d\x48\x00\x1b\x84\x8e\x58\x7f\x03\x21\x49\x64\x00\x7d\x74\x9c\x36\x40\x57\xc0\xef\x89\x07\xe8\x65\x00\x20\xd1\x6b\x4c\xf1\x18\xf8\x06\x42\xf7\xc0\x05\x61\xb4\x8f\xda\xad\x4e\xab\xbd\x81\x90\x0f\xc2\xe3\x24\x92\xfa\xe1\x82\xf6\x66\x3c\x97\x20\x24\x3a\x7a\x7b\xa6\xc8\x0c\xf5\x0b\x34\x23\x54\xb4\x36\x04\x70\xd5\x89\xa2\xaa\x89\x62\x1e\xf4\xd1\x44\xca\x48\xf4\xb7\xb7\x71\x44\x5a\x8a\xd9\x62\x42\x46\xb2\xe5\xb1\x70\x03\xa1\x02\x01\xaf\x31\xa1\xe8\xaf\x11\x67\x7e\xec\xa9\x27\x7f\x43\x06\x9d\x1b\x99\x90\x78\x0c\x8b\x50\x5e\x49\x3c\x26\x74\xec\x44\xd4\xdf\xde\x0e\x98\x87\x83\x09\x13\xb2\x7f\xd0\x6e\xb7\xcb\xcd\x67\xef\xb3\x96\xdb\x65\x28\x2f\xe6\x1c\xa8\x44\x3e\x0b\x31\xa1\x1b\x12\x8f\x13\x06\x50\x1c\xe6\xe6\xe5\x7a\x1a\x81\x28\xb7\x6f\x34\x5c\xd0\xb5\x01\xd1\x71\x10\x0b\x09\x4b\x34\x48\xe6\xd7\x09\xbf\x11\x61\x39\xd1\xf4\x3f\x53\xff\x43\xce\x66\xcf\x36\x36\x10\x6a\xa8\x69\xd8\xce\x8b\xe9\xf6\x7d\xa7\xd1\xd7\x78\xc7\x20\xcd\x3f\x10\x4a\x19\x62\x7e\xcd\x0a\x42\x90\xd2\x45\x8e\x15\x21\x67\x7e\x5f\xb5\xff\xcd\x88\xeb\x6b\x90\xd8\xc7\x12\x27\x50\x22\x0e\x43\xcc\xa7\x7d\x74\x09\x32\xe6\x54\x68\x6d\x49\x24\x1b\x85\x79\xd8\xdc\xe0\xea\x34\xe0\x20\x22\x46\x05\x58\xf4\x36\xba\xed\x76\x23\xfb\x13\x29\x79\x97\x40\xa5\xfd\x08\x21\x1c\x45\x01\xf1\x34\xf5\xdb\x1f\x05\xa3\xf9\xb7\x08\x09\x6f\x02\x21\x2e\x3e\x45\xe8\xff\x73\x18\xf5\xd1\xe6\xb3\x6d\x8f\x85\x11\xa3\x40\xa5\xd8\x36\xb0\x62\xbb\x30\xfe\x4d\xab\x71\x6e\x60\xbf\x15\xc7\x32\x9b\xbc\xb2\xe8\xcd\x9b\xb9\xed\x5b\x3c\xba\xc5\x83\xec\xb9\x54\x8d\xb6\xff\x9d\x7f\x30\x20\xfe\xff\x26\xfc\x88\x30\xc7\x21\xc8\x44\xe1\xcd\xe4\x1a\x59\x2b\x35\xd9\x70\x52\x7e\x3d\x01\x44\x7c\xc4\xb4\xc9\xcc\x1a\x21\xd5\x68\xa3\x9a\x75\xea\x75\x1f\x09\xc9\x09\x1d\xcf\x1e\x13\xda\x47\x4a\x76\x67\x0f\x38\xdc\xc5\x84\x83\xdf\x47\x92\xc7\x50\x5f\x28\x33\x2d\x45\x48\x80\x17\x73\x22\xa7\x36\xe4\x0b\xc0\x1c\x78\x1f\xfd\x13\xfd\xab\x42\x70\x67\xb8\x14\xaa\x17\xd3\xb3\x93\xa2\xe8\xbe\x02\x89\x70\x61\xbc\x6a\x19\x99\xf1\x29\x2f\xb8\x0b\xc1\xbf\x91\xd8\x36\x9c\x62\x9b\x1b\x7d\xa3\xd0\x14\x3e\xe1\x30\x0a\x6c\x42\xd3\x5f\xae\xd9\xa9\x01\x2b\x43\xb9\xbb\x4e\xb1\x6e\xbb\x90\x34\xaa\xf4\xe6\xba\x24\x73\x28\xc4\xd2\x9b\xa8\x05\x43\xc9\xa3\x12\x20\xd0\xb6\x3f\x61\x69\xaf\xdd\xf9\x36\x2c\x3d\xe5\x9c\xf1\xfa\xac\xec\xb5\x3b\xab\x32\x30\x6b\x5a\xc9\xb6\xa3\x58\x4e\x90\x64\xb7\x40\x95\x4b\x40\xe8\x3d\x0e\x2c\xfd\x6e\xf4\xda\xbd\x1f\x84\x49\xbd\xd5\x99\xd4\x5b\xc4\xa4\x0b\x96\xc9\x52\x41\xc6\xe0\x13\x11\x52\x64\x0c\xdb\xfd\x56\x8a\xba\x24\xc3\x76\xdb\xed\x55\x19\x96\x35\xad\x64\xd8\x3b\x0a\x9f\x22\xf0\x24\xf8\x08\x14\x5d\x88\x79\xda\xaf\xf2\x97\x5e\xb0\x96\x71\x40\x9e\xd8\xd6\x8b\x2a\x1f\x05\xa3\x80\x08\xa9\x16\xba\xbc\x30\x08\x97\xbd\xaf\xdb\xa8\xbc\xfc\x2a\x92\x5d\x13\x91\x41\x6e\x47\x78\x6c\x4d\xc2\x42\x70\x41\x3e\x2f\x03\xce\xb8\x0f\xfc\xc5\x74\x99\x0e\x00\x73\x6f\xd2\xf8\xee\x17\xb2\x73\x22\x64\xb5\x49\x5c\x30\x53\xeb\xb5\xa3\xde\xda\xb1\x36\x85\x0b\x4d\x61\xc1\xb1\x5f\xd2\xa5\x4f\x8d\x63\xa4\x62\xde\x45\xd6\xf1\x11\x86\xd1\xe3\x80\x25\xd8\x54\x22\xdb\x2c\x1e\xeb\xd7\x3a\x3d\xf2\x90\xa9\x8c\xcb\x16\xce\x85\x74\x1b\x40\x15\x08\xdc\xc5\xc0\xa7\x16\x7f\x4d\x54\x82\xc5\x94\x7a\x55\x5c\x7f\x0b\x7c\xc4\x78\xa8\x3d\x3f\xac\xf3\x0f\x88\x50\x84\xa9\x69\x35\xe1\x8c\xb2\x58\xa0\x10\x53\x0a\x7c\x63\xbe\xb4\x99\xf8\x64\xc8\x58\x00\x98\x5a\x6f\x1c\x11\x09\x4a\xbd\xcc\x17\xcc\xb7\x18\x5c\x91\x98\xb1\x22\x55\xa7\x72\xcc\x57\x0d\xb7\x62\xd4\xb2\x80\x97\x86\xc8\xbc\x86\x54\xe9\xc7\xac\x95\x99\xbc\x4a\x4d\xa9\xe7\xc9\xe7\x90\x34\xe6\x45\x77\x55\xcb\x47\xf7\x1b\x2f\x1f\xd5\xd6\xd0\xf3\x20\x92\x90\x73\x9e\x7f\x0c\x03\xd8\x6b\xb7\xf5\xbc\x10\x46\x57\x5f\x2d\x8a\x28\x2a\xf9\xf4\x9b\x5a\x25\x34\xa4\x31\x88\x22\xb3\x88\x16\xe7\xd6\xeb\xeb\x3a\x36\xab\x15\x9b\x5d\x67\xb1\x3d\xf8\xca\x66\xb0\x98\x7b\x80\x7c\x06\x82\x6e\x4a\x13\x9f\xad\x7d\x92\x82\x60\x51\x14\x57\xb9\x25\x66\xb5\x4f\xb3\x26\xf9\x45\xba\x4e\x14\xf6\x08\x3f\x43\xb9\xdd\x65\x3c\x7f\xb0\xe8\xeb\xbb\x8e\x8d\x96\x8d\x8b\xd6\x21\xd1\x3a\x24\xfa\x36\xd9\x21\xb1\xfd\xef\xf9\x5b\x17\x0b\x94\x91\xf8\x8d\xaf\x61\xd2\xec\x9c\x52\xd1\xa0\x15\x36\x02\x5c\xe6\xcb\x0d\xf2\x7d\xda\x8e\x9a\x99\xf9\x75\x52\x7e\xed\xf8\xd5\x61\xd2\x3a\x29\xbf\x14\xc3\x1e\x65\x76\x0d\x68\x00\x12\xbe\xa4\x2d\x34\x3d\x54\x9a\xc3\x13\xfd\x7a\x91\x45\xac\x84\x72\x1b\xc5\xef\x45\x51\x1c\x63\x58\x87\xbb\x7f\x58\xab\x67\x26\xf8\x11\xb6\x2f\x87\x60\x9e\x05\xd4\x5e\x51\xba\x8c\xa2\x07\x22\x27\x48\x44\xe0\x91\x11\x01\x1f\x9d\x9d\xfc\xc8\x96\xf0\x71\x4c\x2c\x22\x58\xd1\x2a\x46\x6a\x85\xf9\x92\x46\x51\x77\x50\x69\x13\xdf\xaa\xb7\x8b\x4c\x62\x15\xd0\xe2\x5c\xf4\x09\x96\x18\x49\x66\x88\x28\x54\xed\x28\x59\xda\x98\x23\x26\xb6\x90\x84\xc0\xc7\xd0\xd4\x58\xfe\xbb\x6e\xa6\xda\xa4\xd5\xd9\xf0\x23\x78\xb2\x02\xad\x42\xb5\x24\xd6\x42\xc0\xfa\xf3\xd5\x9b\x0b\xc3\x9f\x2d\x74\xf9\xf2\x18\xed\x1d\xb6\xbb\xa8\x39\xab\x3c\x94\x8c\x05\xa2\x45\x40\x8e\x5a\x8c\x8f\xb7\x27\x32\x0c\xb6\xf9\xc8\x53\x50\xab\x51\xfb\x25\x52\xf4\xb3\xe6\x7f\x84\x24\xf9\x3a\x16\xf8\xf3\xae\x8a\xeb\x58\xe0\x47\x88\x05\x1c\xe5\xa6\x49\x49\xf2\xb2\x05\xa7\x5e\x52\xc9\xbc\xcc\x2e\x75\xbe\xfc\x79\xfe\x3e\x74\x46\x16\xaa\xbd\xf6\x2e\xd8\xb4\x46\x5e\x0e\x67\x8d\xcd\xeb\x42\x8b\x3f\xdd\x26\x76\x32\xfc\x6f\xb7\x99\x9d\x48\xc1\x8a\x7b\xda\xa6\xf1\xd3\x6c\x6d\x3b\x70\xfd\x90\x3b\xdc\xc9\x40\xd6\x1b\xdd\xeb\x8d\x6e\x8b\x73\x6b\x1f\xa7\x06\x93\xd6\x1b\xdd\xdf\x97\x9b\xb3\xc2\x46\x77\x6e\x41\xaf\x55\x76\x5c\x70\x59\x1e\xbb\xf1\x5d\x44\x57\x67\xff\xdb\xcb\xb7\xa9\xbd\x05\x5e\x68\xf7\xb5\x6b\x90\xbf\xcf\x8d\xac\x64\x02\x96\xae\x11\x2e\x30\x73\x6d\xdd\xd7\x7b\xe2\x5f\xf9\xc4\x44\x2a\x81\xf6\x29\xbf\xe4\xd9\x92\x07\xfd\xb2\x56\xee\x00\xa0\xea\xac\x5f\x3e\x1a\xfa\xfa\xc7\xfd\x1e\x6f\x8b\xed\x1d\xfb\x42\x84\x59\x75\xe0\x6f\x4e\xd0\x38\x1f\xf4\xbb\xb6\x7f\x35\x73\x78\x69\x00\xb8\xce\xe5\xad\xfd\xdc\x3a\x4c\x5a\x31\x97\x97\x8a\xd9\x3a\xa7\xb7\xea\x4e\x56\xfc\x55\xcc\x67\x1c\xf9\x8e\x1c\xdd\x8b\xe9\x99\x5f\xb4\xa2\xb1\x1f\xe1\xfc\x4e\xfe\x3c\x43\xba\x10\xba\xfe\x6e\x97\x21\xd1\x5f\x71\xaf\xeb\xab\x24\xaf\x96\xc8\x16\xe5\x4d\x46\x3e\x4b\x97\xe8\x8c\x90\x58\xc6\xfa\x8e\x94\x64\xe8\x6b\xbb\xbc\xb6\xcb\x4f\x6c\x97\xd7\x26\xd9\xc5\xb3\x27\x29\xb9\x7a\x02\xab\x5c\x28\xbd\xaa\xf0\x6b\xcb\xb5\x55\xf3\x2c\xf2\x42\xe8\x75\x45\xd6\xda\x2e\x7e\x27\x4c\xfa\x8a\x15\x59\xb3\xc4\xec\xba\x18\xeb\x29\x8b\xb1\x9e\x2e\x0b\xb2\x8d\x7d\x9f\xd1\x41\x96\x05\x59\xa7\x45\x56\x4b\x8b\x1c\x29\x3e\xbe\x9d\x71\xad\xb8\x9a\x54\xa4\x3e\x36\x05\xd2\x13\x60\xf1\xdb\xb5\xba\x2c\xdd\xfa\xbb\xca\xa5\xe4\x59\x33\x37\x93\xac\x44\x26\x1b\x0c\x92\x13\x2c\x91\x98\xb0\x38\xf0\xd1\x10\x50\x2c\xcc\x8d\x83\x1e\xa3\x23\x32\x8e\x39\x68\xc1\x32\x77\xf5\xd9\x11\x8c\x61\x0a\xa3\x46\xee\x0c\xaf\x5a\xeb\xe5\xec\x8f\xba\x9c\xad\xd3\x2f\x3f\x92\xaf\xbf\x91\x61\x54\x1d\x27\xd4\xf7\xcd\x51\xd0\x67\xe6\xff\xd1\x31\x0b\x43\x46\x93\x47\xfa\x3f\xca\x6c\xf4\x67\xd6\x2d\x31\xfc\x96\xc5\xbe\x25\xd4\xb7\xfe\x8c\xf0\x18\xac\x3f\x05\xf9\x6c\xff\x29\x99\xc4\x81\xf5\x37\x91\x10\xa6\x53\xe8\x28\x6e\x8d\xb8\xb2\xfe\x92\xd8\x6c\x54\xfd\x2d\x5c\xb2\x14\x15\x65\x20\x42\x25\x8c\xed\xf5\x8f\x7c\xae\x01\xa5\x69\xae\x06\xd3\x2f\xb4\x08\xa4\x30\x38\x08\xde\x8c\x6c\x16\xcd\x13\x9e\x37\x7a\xbc\x97\x30\x02\x0e\xd4\xcb\x6d\x61\x56\x54\xfb\xba\x98\x82\xb4\xbc\xfb\x25\x89\x72\x32\x07\xe9\x99\xc4\x0e\xe9\xaf\x04\x9f\x2d\xc2\x03\xe2\xcf\x6d\xa4\xdf\x15\xc6\xd4\x5f\x6e\x82\xc9\xe2\xe9\xad\x25\x03\x13\xc5\xf5\x2a\x20\x8b\xce\xd7\x20\xf1\x92\x24\xb2\x07\x0a\x7c\x21\x01\xa6\x50\xd0\x1f\xe0\x9c\x0d\x1a\x31\x1e\x62\xd9\x47\x3e\x96\xd0\x94\x24\x84\x45\x68\x42\xe6\x6b\xdf\x7d\x55\x3c\xfa\x79\x72\x2b\x6a\xe2\x3c\x11\x46\xaf\x40\x4a\x42\x33\x4f\xcd\xa5\xda\xc4\x56\xec\x98\x07\x8f\x9b\xb4\x98\x3b\xb4\xc8\x41\xe3\x91\xe7\xb1\x98\xce\xb5\x39\x5e\x40\x80\xca\x41\x8e\xbe\xe4\x99\x00\x8f\xc3\xbc\xb9\x9b\xb5\x5d\x3c\x7f\x36\xc6\xf9\xa4\x9f\x40\x14\xb0\x69\x08\x54\x9e\x33\xb3\xba\xa4\xf0\x3e\x51\xc6\x39\x24\x14\x4b\x66\x89\x4c\x42\xd9\xf4\x42\xbb\xf6\x39\x1b\x1a\xe2\x28\x22\x74\x6c\x77\x58\xf4\x79\xeb\x66\x74\xaf\x31\x1f\xc3\xcc\xe9\x63\x14\xea\xdb\xa5\x2a\x54\x1b\x2e\x7a\xcc\xcb\xbe\xcb\x83\x6e\x98\x77\x02\x3d\x30\x7e\x1b\x30\xec\xeb\x2b\xb3\x31\x4d\x7c\x45\x2f\xbf\xcb\xe7\xd0\xbf\x05\x6b\xce\xca\x4b\x44\x16\x44\xcd\x9f\xda\xc2\x8d\xb9\x5f\xc9\xc8\x83\xcb\x43\xd0\xe3\x42\x8d\xa3\xb7\x67\x09\x51\x79\xa7\x83\xa8\x97\xf7\x9d\xfc\xc3\x89\x21\xab\xe2\x5e\xe5\xc2\x02\x12\x04\xc6\x38\x94\xbc\x96\xa6\x41\xae\x63\x5c\x51\x74\x75\x16\x74\x52\xbe\x2f\xac\xd4\x3e\x19\x58\xe5\x05\x10\xd5\x4b\x5e\x25\xc5\x86\xaf\x98\x73\x3c\x2d\xbc\xd1\x3e\x47\xd9\xf5\x2a\x4c\xa8\x3d\xf6\xa5\xa6\x36\xe7\x4e\x25\x26\x4d\xd8\x0e\xd5\x2f\x8a\x1d\xd5\x86\x38\xa7\x3d\x3f\xb1\xc0\x17\x69\x14\xaf\x43\x2f\x53\xc9\x69\x62\x31\x85\x41\x6b\x93\xc1\x89\xce\xa8\x90\x98\x7a\xd0\x5a\x45\x46\x2b\x57\x88\x6c\x22\x9e\x25\x07\xfd\x92\x74\x92\x67\xcd\x4b\x06\x53\x21\xd2\xcf\xf2\xb3\x68\x0c\xbe\xee\xfa\x12\xc6\x44\x48\x3e\x7d\x62\x96\x68\xe4\x28\x45\xfe\x15\x78\x63\x80\x11\x4f\x7b\x7c\x2a\x2e\xa5\xb2\xa4\xa3\xf9\x9c\x24\xe5\xe3\x7b\xb7\xf9\x3d\x2a\x66\x2a\xe6\x98\xda\x15\x17\xf6\x7b\x1c\xc4\x0e\x3f\xda\x36\xa2\xe5\x4c\x44\x15\xb5\x69\x41\x5b\x31\xbf\x92\x27\xdb\xd6\xeb\x82\x3e\xd7\x4f\x88\x34\x8a\xa1\x4f\xf9\xa4\xc9\x8c\xd5\xc5\x15\xef\x4a\x62\x59\x70\x6c\x73\x5c\x01\x1a\x87\xb6\x74\x29\x37\xc0\xa0\x00\xdb\x69\xe1\x80\xfd\xa9\xbb\x87\x64\x37\xd6\xf6\x4e\x5d\xf3\xa3\x73\x83\x73\x79\x5f\x81\xd8\x3d\x01\x46\x25\x95\x73\x69\x57\xd0\x64\xfb\xd5\x08\xeb\x33\x16\x28\x0a\x30\x05\x2b\x1d\x66\x36\x77\x1b\xab\x28\xd7\x9c\x81\x57\xb8\x1b\x36\x4f\x56\x58\x87\x0d\xe6\x2f\x45\xdc\x95\xe6\xc4\xbc\x29\x13\x39\x88\x0a\x5d\xac\x6a\x9c\x22\x28\x85\x7a\xcb\x8c\x42\x4b\x6f\xe1\x90\x8a\x1d\xc1\xd6\x17\xa6\xa7\x76\x87\x96\x19\xc5\x63\xe6\xf1\x2a\x91\x57\xe7\xa0\x6c\xfb\xb4\xd4\xc0\xf2\x7e\xcb\xd2\x11\xbc\xd3\x33\x59\xda\x91\x59\xae\xba\xce\x6d\x02\xad\xa7\xc7\x13\x4c\x29\x04\x73\x6c\x9d\x0f\x23\x1c\x07\x52\x3d\xc5\xc3\x00\x2a\x2c\x60\xf2\x32\xcf\xf0\x13\x10\xca\xb7\x5f\xd6\x9a\x1a\xb3\x69\xe3\x66\x51\x94\x33\xac\x7e\xb2\x95\x9a\xef\x6e\xd9\x7e\xb0\x10\x64\x4c\xed\xb5\x2e\x7d\x96\xeb\x4c\x9b\xc6\x3c\xd4\x62\x0a\x47\x98\x04\x65\x92\xf3\x58\xfc\xc2\x86\x70\x53\x89\xce\x3d\x51\xae\x7f\x11\x30\xf7\xa2\x20\xd5\xb6\x9f\x34\x37\x95\xa7\xbc\x3b\x9b\x68\xe3\xf6\x0c\xb0\x89\xc8\xad\x37\xa5\x5b\x73\x5d\x51\x98\xc2\x66\x8b\xe7\x3c\xc1\xac\x70\x8a\x33\x65\x2a\xd0\x52\xc6\xeb\xfe\xde\x49\x3e\xa7\x90\x7d\xee\xc4\xbc\x1f\xa4\xce\x5a\x5d\x32\x17\x79\xac\x19\xbd\x33\x0e\xd9\xa8\x9f\x59\xb9\xec\x79\xee\xa1\x82\xd4\xcb\xac\x98\xe0\x08\x72\x8f\x23\xce\x3c\x10\xc2\xbe\xf4\x4e\x3d\x36\xb9\xde\x09\xa6\x7e\x90\xcf\xdd\xe5\x4c\x50\x5e\x2e\x1c\x1e\x86\x4b\x2a\x94\x87\xe1\x9a\xfa\xd2\x67\x58\xb4\x18\x26\x69\x90\x41\x90\xe4\x41\x72\x6f\xb5\xb2\x0f\xf4\xf2\xb5\xaa\x4b\x53\xe2\x6f\x4a\xc6\xe2\x16\x79\x43\xb6\x68\xaa\x13\xbb\x97\xcd\xa8\x63\x70\x75\x71\x95\xd3\x43\x36\x5a\x8b\x2b\xb5\x89\x73\x19\xd0\xe2\x6a\x56\x70\xf4\x56\x73\xca\x72\x0e\xcf\x92\x6d\x73\x86\xa7\x48\xdd\xb7\x70\xe2\x2a\x06\xb3\xe4\x32\x9d\xd6\x56\x0c\xd2\x6f\x9f\x39\x57\xec\xe2\x36\x82\xf9\xa5\x59\x5b\x42\xe5\x5e\xcf\xb1\x3a\x7d\xb7\x9e\xe3\x13\xb8\x8c\xdf\xc4\x57\x7c\x0a\xc1\x5d\xb2\xb5\xdb\xb7\xfc\x13\x38\x95\x0d\xb7\x33\x69\x7d\x11\xa5\x18\x4d\xab\x37\xce\x40\xf4\xef\xcd\x19\x09\x97\x10\x71\x10\xaa\xc7\xf2\x07\xab\x44\x1c\x45\x8c\x4b\xf0\xd1\x70\xaa\x03\xd6\xa3\xb7\x67\x49\xc3\x52\xb6\xbb\xbc\xb6\xa1\xf2\xfa\x66\x1e\x25\x8a\x5d\x78\x6a\xc6\xfb\x94\x18\x3f\x0a\x46\x07\x39\xb4\xdf\x68\xef\xb0\xb8\xe4\x96\xe6\xe3\x02\x87\xe0\xfe\xa6\x5a\x6b\x9e\x01\xb0\x5f\x54\x58\x4b\xe7\x67\xe7\x1e\xd7\x53\xb2\xd2\x97\xc4\x38\x5f\x96\x9e\x00\x2d\xd3\xd7\x53\x29\x4c\xd1\xb5\x28\x12\x37\x8f\xee\x23\xfb\xcf\x12\xf1\xb5\x79\x44\x3c\x46\x07\xc5\x2d\xd2\x52\x67\xef\x2e\xcf\x93\xed\x1a\x05\xbf\x7a\x6f\x01\x1e\x2e\x9a\x8f\x73\x0d\x92\x95\x1a\x61\x09\x63\xc6\xc9\x67\x70\xdc\x00\xfe\x88\x79\xa9\x16\x1a\x1c\xe1\x21\x09\x48\x59\x39\x5c\x07\xcf\x2c\xe0\xb2\x11\xf2\xd4\x7c\x7f\x51\x62\x6b\x5c\x3b\x66\x59\xd0\xf4\x77\xa4\x0d\x4e\x9a\xa8\xd6\x35\x5e\x1e\xa6\x76\x81\xd7\xbd\xb9\x2c\x02\x10\x2e\xb9\x91\x25\x6c\x99\xc2\x8c\x08\x04\xbe\x5b\x16\x4a\x16\x08\xd9\x46\xef\xc7\x19\x40\x79\xd9\xfa\x13\xac\xe7\xe6\x63\x8b\x1b\xe5\xa2\xd4\x2c\xda\x32\xa5\xa9\xee\x6f\x4c\x2a\x45\x39\x3b\x51\x46\x83\x83\xc7\xf8\xec\xe8\x55\x61\xea\x1d\x42\x5e\xa8\x38\x75\xd4\x9b\xda\x05\x3e\x86\x06\xab\xf0\xa8\x78\xd1\x51\xe1\x9e\xc2\x31\x20\x42\x7d\xf8\x54\xc2\x3e\xc2\x81\x80\xfa\x54\x96\x4b\xbc\x8a\x65\x47\x66\x67\x04\x35\x92\x8d\x56\xbb\xde\xc8\x10\x6d\x95\x47\xcd\x25\xfa\x22\x0e\x87\xc0\x15\x2b\xf5\x7c\x22\x42\x11\x60\x6f\x62\x0f\xfa\x09\x87\x51\xac\x8b\x9a\x0d\xa3\xdd\x36\x03\x49\x3e\xc9\xe6\xf4\xdc\xfe\x93\xa9\xed\x55\x52\x76\x6e\xb6\xeb\x74\x23\x65\x22\x3d\x4e\x24\x70\x82\x5b\x5a\x42\xc4\x94\x4a\xfc\xc9\x2c\x2d\x44\x64\xa2\x86\x88\xb0\x08\x0a\x49\x80\x79\xfa\x1d\x6b\xbb\x09\xa0\x9b\x14\xf1\x0d\xf2\x02\x1c\x0b\xed\xa7\x60\x8a\xae\x7e\x3d\x37\xf1\x8e\xf9\x06\x77\x8a\xeb\x54\xf1\x4d\x33\x3a\xb5\x1d\xba\xbd\xb1\xde\x98\x4e\x67\x68\x73\x6a\x70\x63\x6c\x84\xc8\xf0\xbc\x64\x3c\x65\xdd\x96\x22\x8c\xeb\x0b\x2e\xf4\x57\xbb\x8f\x73\xae\x84\xb0\x3b\x90\x13\x20\x5c\x4f\xfe\x96\xb2\x59\xba\xa7\x11\x0b\x02\xf6\xa0\x3f\x29\xad\x07\xd6\xdf\x98\x75\x72\x73\x73\x23\xee\xb2\x82\x39\xd5\x0e\x61\xe1\xd9\xef\x33\xe0\xeb\xe5\x89\x40\x03\x4c\xfd\x41\xea\x9a\x3d\x86\xa4\xad\xd9\xe7\x8f\x2b\xe9\x33\x5f\x31\xcf\xcd\x30\xdd\x94\x26\xa5\xe9\x83\xbf\x85\x18\x47\xc4\xc0\x68\x89\x43\x44\x20\x08\x23\x39\xdd\x52\xcf\x32\xdf\xd9\x6c\x4c\x89\x38\x50\x11\x01\xcf\xcd\x9f\xa2\xa6\x35\x93\xeb\x28\x60\x3e\xe4\x0e\x2f\x96\x65\xbd\x20\xca\xb6\xb8\xa7\x43\x6b\x54\x68\xa8\x51\xe1\x04\xc1\x63\xb5\x50\xc8\x69\x00\x7d\x9d\x1f\x30\xb6\x42\x7f\xc3\xd0\xad\x61\x99\x82\x69\xa0\x4c\xa1\x2c\x59\x98\xaf\x59\x0b\x34\xea\x61\x02\x1c\x72\xea\x94\x75\x99\xd3\x2a\x74\xa4\xe4\x04\xfc\x44\x3b\x94\x5d\xd2\xe8\x0c\x5d\x6a\x72\x6e\x14\x97\x6e\xb6\xd0\x8d\x35\x04\xf5\x67\x22\x2d\xea\x9f\xda\x39\xbc\xd9\x42\x98\xfa\xe8\x26\xf1\xdd\x6f\x32\x45\x4b\xbb\x30\x35\x88\x8c\x9b\x49\xbf\xf9\x9f\xbf\xab\xb6\xcf\x6f\xb4\xd8\xdc\x9c\x9f\xfd\x72\xea\x68\xe3\x31\xfa\x31\xa6\x9e\x24\xf7\x50\x6c\x7f\x74\x71\x72\x63\xba\x7c\x73\x79\xd3\x42\x3f\xb1\x07\xb8\x07\xbe\x85\xa6\x2c\xd6\x86\x41\x8d\x1c\xa3\x10\x7f\x22\x61\x1c\x2a\x1e\x74\xda\x19\x3a\x46\xf5\x58\x71\x3a\x52\x2d\x16\x16\xfb\x4f\x67\x72\xe6\xd2\xce\x42\x68\x6c\xce\xe8\x28\xbe\x69\x89\xbb\xc1\x0f\xa2\x29\xee\x44\xd3\x64\x99\x0c\x91\xda\xad\x34\xac\x41\x37\x66\x2b\xe5\xa6\xae\xba\xe6\x75\xf5\x39\xca\xe3\xd7\xe8\x53\xd4\xcf\xf3\x7b\x38\xba\xf9\x3f\xa3\xe6\xbf\xdc\xc3\x30\x55\x27\x24\xa9\xac\x30\xc3\xc0\xa6\x17\x73\x80\x40\x62\x2e\x85\x79\xae\x46\xb5\x22\xc5\x01\xb9\x05\x45\xf4\x5f\xba\xbb\x5f\xc4\xb0\x68\x73\xa9\x5e\xe6\xa7\xc5\xb2\x37\x58\xea\xf7\xb1\x00\x8e\x26\x58\xa0\x08\x78\x48\x84\x48\xca\x4e\x04\x80\x16\x29\xc3\x17\xf0\x2d\x39\xb8\x60\x12\x5a\x29\x7d\x66\xd1\xc9\x6a\xfe\x95\xc4\x27\x89\x7b\x22\xac\xd6\xd5\xe6\x2b\x71\x1a\xb4\xcc\x55\x18\x25\xb7\x01\x72\xac\xf1\x39\xfb\x82\x8a\x66\xaf\x96\x94\x34\x56\x33\x6f\x1b\xd9\xd1\x1f\xbd\x9f\x92\x92\x95\x9c\xfd\xb1\x91\x42\x1f\x0d\xf5\xd3\xe4\xa1\xf9\xe3\x65\x92\x44\xfd\xf9\xfd\xf5\x86\xdd\xe3\x44\xca\x48\x61\xcf\x8f\xb6\xd6\x35\x8b\x85\x1a\x16\xc3\xe8\xc6\xeb\x69\xee\x16\x92\x79\x9f\x13\x2f\x20\x20\x7e\x1f\x05\x6c\x3c\x10\x84\xde\x0e\xda\xad\xce\xec\x85\x29\x75\xcb\x61\x9a\xbd\x5b\xaa\x8c\x2e\xf9\x92\xbe\xdd\x49\xa3\x40\xff\x39\x1b\xa3\x2b\x42\x6f\x67\x8f\xd3\x14\x0c\x6a\xe4\xa0\x5d\xf9\x92\x66\xd1\x12\xe4\x83\xf5\x22\xe6\x2c\x9d\xb0\x22\xfd\xad\x88\x8e\x33\x8a\xca\xf9\x82\x26\x12\x76\x7f\x55\xd1\x7a\x53\xef\x9b\x0d\x8a\xfb\x66\x4d\xd7\xbe\x59\x39\x06\xad\xae\x33\x0c\xc3\x72\x5a\x26\x53\xb5\x7f\xfe\xab\x18\x8f\x11\x19\x98\x09\xa8\x1b\x15\x57\x77\xae\x7e\x61\x1c\x48\x32\x08\x08\x75\x9e\x19\x99\x6d\xc0\xdb\x2a\x9f\x07\xb0\xe6\xee\xb5\xc2\x85\xce\x09\x75\x41\x26\x84\xcf\x87\xa9\xb8\xc5\x35\xfd\x7d\x6a\x8e\x39\x8b\xa3\x3e\x6a\x00\xf5\x23\x46\xa8\x2c\x57\x7c\x8a\x09\x7b\x18\xe0\x20\x78\xfc\x70\xae\x26\xec\x41\xad\xf7\xd5\x83\x99\x07\xf1\xc8\xa1\x48\x16\x11\x6f\x41\x9e\x91\x85\xa1\xf2\x13\xd4\xea\x24\xc1\x9f\x15\xb8\x99\xc5\x53\x23\xd0\xea\x2a\xdc\x22\x74\x5d\x0d\x50\x95\x1c\xb2\xc9\xd6\x4a\x97\xa7\x59\x48\x88\x1e\x9f\x40\x28\xa4\xd7\xb3\x5f\x73\xae\x20\x27\x38\xa9\x00\x2e\x07\xda\x69\xac\x82\xa9\x0e\x2b\xcb\xbf\x23\xdf\xd7\x9b\x03\xb1\x90\x2c\x34\xbe\x68\xea\x8d\x78\x4c\xbb\x27\x32\x59\xf9\x13\x7f\x37\x04\x21\x4c\x1e\x00\x49\x8e\xa9\x20\xb2\x98\xfd\xc9\x7e\x8b\x87\xa3\x7e\x0b\xc6\x52\x1a\xcf\x75\xea\xee\x25\x3e\xb7\x21\x5a\x32\x15\x90\x62\xdf\xb7\x8a\x3e\x5c\xbf\x44\x38\x5e\xaa\x46\xf3\x01\xab\x85\xc4\xfe\x95\x4a\x38\x6b\x50\x6f\x18\x6a\x93\x5f\x87\xe4\xdf\x54\xab\xc7\x93\xec\xde\x7a\xc9\xff\x9a\x0b\xa9\x6a\x9a\x41\x54\x42\x24\x34\x9f\x69\x71\x35\xdc\x46\x47\x9e\x2c\x6e\xe4\x94\xa9\x77\x1a\xf8\x7a\x94\x37\x73\xda\xe1\x04\x5a\xd0\x47\x1d\x0d\x84\x4f\x92\x63\x6f\x39\x15\x3c\x35\x6d\x10\x4e\x84\x75\xc4\x99\xb9\x15\x7c\xc8\xfc\xa2\xd5\xc8\x7e\x7f\x7c\xf5\x79\x0a\x59\x4c\x28\x4a\x59\xfc\xb5\x44\x2d\x27\x06\x5f\x4a\xd6\x26\x58\x0c\x26\x80\x7d\xe0\x83\x11\x09\x24\x94\x6a\x06\xb2\x5f\x6e\x8e\x5f\x6a\x60\x34\xc4\x42\x45\xff\x26\xb3\x60\xb6\x82\x3d\x3d\xef\x8c\x02\x32\x78\x1f\x29\x7c\xae\xdd\xcf\x39\x74\x29\xd9\x33\xfd\x26\xb1\x2e\x43\xa0\xec\x48\x56\xd5\xe4\xfe\xa5\x67\x7a\x92\xc6\x17\xc5\x5d\xe2\xe2\x2f\x91\x89\x9f\x4c\x57\x8b\xc1\x9f\x4e\x56\x4b\x1b\xd8\x2e\xb2\xb0\x48\x49\x4b\x26\xea\xcb\x8b\x6b\x49\x92\xea\x89\xec\xfc\x8f\xc7\x3b\x43\xbf\xd7\xd3\x73\x36\xb6\xcb\x77\x16\x54\x7f\x15\x4e\x30\x39\x2e\xaf\xb4\xce\x9b\xa1\xc6\xe1\x50\xdc\xb7\xc5\xbe\xa4\xb0\x3f\x6e\x77\xc7\x93\xdd\x71\xcf\x8a\x7e\x4a\x95\x93\x56\x9b\xbd\x21\x1f\xf1\x76\xbb\x1b\x8d\xe8\xed\xa4\x9d\xef\x20\x3d\xd8\x88\x1a\x82\xdf\x7b\x4d\xec\x79\xb2\xd9\xd9\xeb\xc2\xa8\xeb\x1f\x34\xdb\xdd\xf6\x61\xb3\xd7\xe9\xec\x37\x0f\x7a\x7b\xdd\xa6\x3f\xda\xdb\xf1\xba\xed\xee\xae\xd7\xdd\x73\x60\x49\x0e\x3d\xa2\xc6\xb0\xd3\xeb\xf9\x87\x87\x9d\x66\xfb\x00\x86\xcd\x5e\x6f\xbf\xdb\x3c\x00\xaf\xd3\x84\x61\x7b\xa7\xe7\xed\x1d\x76\x77\x3a\x43\xbb\x7d\xcc\x83\x3e\x6a\x8c\x18\x6b\xba\xe8\x6d\xdd\x62\xd1\xc2\x5e\x08\x2d\x8f\x85\xfd\x5e\x6f\xa7\x51\x88\xc6\x9c\x15\x99\xd6\xf0\xdb\xb7\x07\x01\x1d\xb7\x77\x3a\x02\x0e\xef\x6a\x0c\x1f\xda\xdd\xdd\xee\xde\x2e\x34\xf1\xc1\x01\x6e\xf6\x7a\xa3\x61\xf3\xa0\xb7\xdb\x6e\x82\xdf\xee\xb4\x61\xb8\x37\xf4\x76\xbd\x79\xc3\xf7\xbd\x5d\x7c\xd0\x3d\x3c\x68\x0e\xc1\xdf\x6f\xf6\xba\x5d\x68\x1e\x1c\xf6\xf6\x9b\xa3\xbd\x91\x8f\xf7\x0e\xbb\x87\xdd\xd1\xa8\x3c\xfc\x21\xe6\xc9\xf0\xbb\xe1\xc8\xc3\xed\x76\x57\x1e\xde\xed\x8b\x71\x4b\xf0\xaa\xe1\xa7\xd5\x89\xc5\xb0\xbb\x5c\xe7\x88\x1a\xee\x98\xdf\x59\x71\xea\x8a\x5c\x67\xb1\x97\x9d\x5a\x32\xbf\x2c\xce\x14\xa5\xb7\x49\xac\xa3\x27\x77\x6b\x88\xf3\xd7\x58\xcd\x82\xee\x7c\x57\xb7\x30\x2d\x6a\x73\x5a\x01\xd7\xb8\xba\xbe\x3c\xbb\x78\x95\x8f\x4d\x9c\x7e\xe8\xac\xc5\xcf\x57\x6f\x2e\x0a\xc7\x02\x93\x98\xbe\x58\x81\x33\x3f\xc0\x48\xb2\x3b\xfa\xad\x32\xab\xe5\xf0\x34\xcd\x85\x69\x10\xed\xb2\x56\x15\x6c\x16\x2a\xbd\x75\x3a\x6f\x90\xd6\xe1\xda\x5d\xfb\x80\xfd\x41\x00\x52\xd9\x80\xbb\x18\x8a\xc3\xd4\xdc\x55\x02\x17\xdc\x99\xae\xaa\xbf\x12\xe1\x48\x35\x35\x3a\x6d\x4b\x96\x12\x63\x54\xb8\x97\x62\x7e\x76\xc6\x7c\x1b\x62\x3b\x87\x47\xdf\x28\x80\x1a\xc7\x6f\x2e\x2e\x4e\x8f\xaf\xdf\x5c\x36\x5f\xbf\x7a\x7d\xdd\xcc\x81\x24\xf7\x08\xa0\xc6\x95\xf5\x2d\x98\xf4\x2b\x31\x02\x51\x26\xb3\xf2\x08\x93\xfd\xd5\x5f\x8d\x79\xae\x64\xab\x7c\x26\xad\x70\xd1\x00\x6a\x74\xc8\xfb\x33\x12\xde\xbd\xf2\xf8\x49\x7c\xbe\xd7\xc1\xef\x3e\x9d\xfd\xe3\xee\xc5\xf5\xdd\xc5\x25\x9e\x71\xe9\xcc\x64\x53\x7f\x8d\x81\x4f\x6b\x70\xaa\xfb\x44\x9c\xea\x2e\x64\x54\xd7\xc1\xa7\xff\x58\x93\xfe\x52\x9f\x00\x30\x5f\xbe\xe3\x02\x72\x7b\x09\x7d\xf4\x8e\x2a\x3b\xa0\xde\xea\x8c\xc1\x2f\xf6\x27\x16\x85\x3e\xa0\x85\x23\x32\x30\x49\xb5\xa4\x38\xbe\x8f\x4a\x14\xf4\x97\xe8\x2f\xab\x63\xf1\x58\x10\x87\xd4\x78\x37\xaa\xa7\x24\x59\x8c\x36\x89\xbf\xd9\x42\x57\x2e\x38\xbd\xab\x62\xf7\xa6\x0c\x39\xa3\x5b\xc9\x5e\xa7\x17\xb0\xd8\x1f\x24\x19\x79\x9e\x3e\x35\xf5\xac\x2d\xf4\xab\xc9\x8c\x9b\x89\xec\x23\xe2\xa3\xe7\xa8\xd3\xdd\xa9\x94\x8a\xe0\xfd\xc9\xab\x78\x3a\x3c\xe3\xa7\xf4\x13\x3f\x82\x70\xbf\xdb\x1b\xdf\xdd\xde\x92\x93\xfb\x54\x2a\x8a\x77\xd3\xb8\x24\xa1\xd7\xee\x3d\x89\x24\xec\x2f\x12\x84\x7d\x87\xbe\xd4\xf9\x8e\xc6\x6c\x30\xce\x7b\xcf\x5c\x43\xda\xff\x76\x03\x3a\xce\xdd\x63\x8b\x88\xff\x7c\xb3\x43\x7e\xd9\xf1\xe3\xdf\x3e\x9c\xdd\xdf\xef\x7e\xb8\x3f\x0f\xa6\x9f\x3b\xe1\xab\xcb\x9d\x9f\xa7\x77\x17\x9b\xda\x34\x8c\x58\x4c\xfd\x39\xca\xff\xe1\xcd\xfe\xb8\x3b\xde\xfb\xe9\xda\x7f\xf7\xcb\x3b\xdc\xbd\x15\x3f\x1d\x74\x6f\x7f\x3d\xd9\x99\xa6\x9c\x29\xde\xd3\xe4\x34\x8d\x9d\xa7\xb1\x8c\x9d\x85\x86\xb1\xe3\x60\x4b\xa6\xc6\xf7\xc0\xc9\x68\x8a\x7e\x7e\x7f\x6d\x6e\x7f\xea\xa3\xcb\xc4\xe1\x45\x38\x96\x13\xc6\xc9\xe7\xf4\x24\xf3\x2d\xd0\x7a\xfc\xd9\x79\x37\x39\x9d\x3c\x84\xbf\xbf\x88\xde\xbf\x1d\x9d\x75\x83\x0b\xb8\x8d\xfc\xde\x3f\x4e\x52\xfe\x1c\xaa\xe5\xed\x98\xd1\x51\x40\x3c\x59\x83\x57\x3b\x7b\x4f\xc2\x2b\x1b\x8d\x9b\x57\x36\x84\x2d\x42\xa6\x6e\xce\x58\x1e\x22\x10\x0e\xf4\xf2\xaa\xcb\xbb\x2a\xf9\xb0\x77\xfb\xa1\xfd\x8e\x9c\xde\x7e\xbe\xfd\xfd\xf8\xf3\xfb\xb7\x70\xd6\x65\x1f\x60\xe2\xef\x9c\x26\x6c\x28\x5f\xb8\xe4\x1a\xfa\xe1\x93\x8c\xfc\x70\xd1\xc0\x0f\x9d\x32\x92\x5d\xd0\x08\xf9\x4e\x4b\x53\x0e\xa7\xe7\xf7\x2f\x0f\x3f\xbe\xfe\xf5\xc3\xde\x87\xf1\x64\xf4\xfa\x70\xfc\xea\x52\xfc\x74\x7f\xfa\x7e\x36\xd6\xda\xc6\xe2\xdb\x8d\xd8\x5e\x05\x75\x9f\xb3\xc3\x6f\x48\x79\x07\x42\xb9\xde\x6f\x8e\x5f\x37\x4f\x7f\x6f\x1e\xf6\x93\x93\x72\x4a\x85\xcc\x79\xb8\x0c\x06\x3e\xc9\x66\xb2\xf6\xe1\x88\x34\x3b\xe4\x53\x7b\x27\xa0\x7e\x10\xde\xb5\xef\x46\xde\xbe\x20\x12\xef\x8a\xe0\xe3\xfd\x81\xed\xc7\x8e\xac\x8b\xc4\x14\x1f\x3a\xe3\x5d\xff\xe0\xe0\xae\x1d\x70\xcf\xbf\xef\x8d\xf7\x71\x30\xdc\x17\xc1\x68\x4c\x3f\xee\xf8\x93\xa1\xf8\xf8\x97\xff\xf7\xd7\xd3\xdf\xaf\x2f\x8f\xd0\x7f\x99\x11\xb7\x34\xc5\xcf\x89\x0f\x54\xaa\x39\xb3\x83\x50\x22\xd0\x66\xaf\xdd\xdb\xdc\xd2\xbc\xd0\x7f\x1e\x9f\xbf\xbb\xba\x3e\xbd\xbc\x32\xcc\x50\x2f\xf5\x5e\xea\x6c\x62\x51\x86\x48\xc3\x77\xc6\xbb\x8c\xef\xb6\xef\x49\xdc\xde\x67\xa0\xa6\x6d\xc2\x6f\xbd\xee\x9e\x3f\x1e\xc9\x8f\x1d\xec\x6d\xda\x8b\x6c\x7a\xb7\xf6\xe6\xa2\x41\x58\xf6\xf6\x6f\x73\xec\xc9\xb5\x78\xcf\xa7\x7b\x54\xdc\x0d\xbb\xe2\x22\x7c\xf9\x71\x77\xf8\x7b\x74\xb2\x7f\x8c\x1b\x1b\xff\x17\x00\x00\xff\xff\x48\xdc\x50\x6f\x24\xa1\x00\x00") +var _connector_mgmtYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7d\x7b\x73\xdb\xb8\xb5\xf8\xff\xfe\x14\xf8\x29\xbf\x8e\xdb\x7b\x23\x59\x92\xe5\x97\xe6\xa6\x33\x4e\xec\x64\xbd\x9b\x38\x59\xdb\xd9\x6c\xda\xe9\xc8\x10\x09\x49\x88\x49\x80\x06\x40\xc5\x4a\x7b\xbf\xfb\x1d\x00\x7c\x80\x24\x48\x51\xb2\x62\x3b\xbb\xe4\x4c\xbb\x31\x89\xc7\x39\x07\x07\xe7\x0d\x88\x06\x88\xc0\x00\x0f\xc1\x6e\xa7\xdb\xe9\x82\x67\x80\x20\xe4\x02\x31\xc3\x1c\x40\x0e\x26\x98\x71\x01\x3c\x4c\x10\x10\x14\x40\xcf\xa3\x5f\x01\xa7\x3e\x02\x67\x27\xa7\x5c\xbe\xba\x21\xf4\xab\x6e\x2d\x3b\x10\x10\x0d\x07\x5c\xea\x84\x3e\x22\xa2\xb3\xf5\x0c\x1c\x7b\x1e\x40\xc4\x0d\x28\x26\x82\x03\x17\x4d\x30\x41\x2e\x98\x21\x86\xc0\x57\xec\x79\x60\x8c\x80\x8b\xb9\x43\xe7\x88\xc1\xb1\x87\xc0\x78\x21\x67\x02\x21\x47\x8c\x77\xc0\xd9\x04\x08\xd5\x56\x4e\x10\x41\x47\xc1\x0d\x42\x81\x86\x24\x19\x79\xeb\x19\x68\x05\x0c\xcf\xa1\x40\xad\xe7\x00\xba\x12\x0b\xe4\xcb\xc6\x62\x86\x40\xcb\xa1\x84\x20\x47\x50\x36\xf2\xa7\xbe\x68\x47\x2d\x3b\x0b\xe8\x7b\x2d\x30\xc1\x1e\xda\xc2\x64\x42\x87\x5b\x00\x08\x2c\x3c\x34\x04\xaf\xe2\x0e\xe0\x12\xb1\x39\x76\x10\x78\xed\x21\x24\xc0\x3b\x48\xe0\x14\xb1\x2d\x00\xe6\x88\x71\x4c\xc9\x10\x74\x3b\xbd\x4e\x77\x0b\x00\x17\x71\x87\xe1\x40\xa8\x97\x4b\xfa\x6b\x7c\x2e\x10\x17\xe0\xf8\xc3\x99\x04\xd3\x57\x1f\x40\x02\x28\xef\x6c\x71\xc4\xe4\x24\x12\xaa\x36\x08\x99\x37\x04\x33\x21\x02\x3e\xdc\xd9\x81\x01\xee\x48\x62\xf3\x19\x9e\x88\x8e\x43\xfd\x2d\x00\x72\x00\xbc\x83\x98\x80\xbf\x06\x8c\xba\xa1\x23\xdf\xfc\x0d\xe8\xe1\xec\x83\x71\x01\xa7\x68\xd9\x90\x97\x02\x4e\x31\x99\x5a\x07\x1a\xee\xec\x78\xd4\x81\xde\x8c\x72\x31\x3c\xec\x76\xbb\xc5\xee\xc9\xf7\xb4\xe7\x4e\xb1\x95\x13\x32\x86\x88\x00\x2e\xf5\x21\x26\x5b\x02\x4e\x23\x02\x10\xe8\x67\xd6\xe5\x6a\x11\x20\x5e\xec\xdf\x6a\xd9\x5a\xd7\x6e\x08\x5e\x79\x21\x17\x68\x85\x0e\xd1\xfa\x5a\xdb\x6f\x05\x50\xcc\x14\xfc\xcf\xe4\xff\x80\xb5\xdb\xb3\xad\x2d\x00\x5a\x72\x19\x76\xb2\x6c\xba\x33\xef\xb5\x86\x6a\xdc\x29\x12\xfa\x1f\x00\xc4\x04\xd1\x4f\xbb\x04\x10\x20\xf7\x22\x83\x12\x90\x33\x77\x28\xfb\xff\xa6\xd9\xf5\x1d\x12\xd0\x85\x02\x46\xad\x78\xe8\xfb\x90\x2d\x86\xe0\x02\x89\x90\x11\xae\x76\x4b\xc4\xd9\xc0\xcf\xb6\xcd\x20\x57\xa3\x3d\x43\x3c\xa0\x84\x23\x03\xdc\x56\xbf\xdb\x6d\xa5\x7f\x02\xc9\xee\x02\x11\x61\xbe\x02\x00\x06\x81\x87\x1d\x05\xfc\xce\x17\x4e\x49\xf6\x2b\x00\xdc\x99\x21\x1f\xe6\xdf\x02\xf0\xff\x19\x9a\x0c\xc1\xf6\xb3\x1d\x87\xfa\x01\x25\x88\x08\xbe\xa3\xdb\xf2\x9d\x1c\xfa\xdb\x46\xe7\x0c\x5e\xbf\xe5\x71\x49\xd6\xae\xc8\x79\x55\x0b\xb7\x73\x03\x27\x37\x70\x94\xbe\x17\xb2\xd3\xce\xbf\xb3\x2f\x46\xd8\xfd\xdf\x88\x1e\x01\x64\xd0\x47\x22\xda\xef\x7a\x6d\x35\xab\x15\xba\x6c\x59\x21\xbf\x9a\x21\x80\x5d\x40\x95\xc4\x4c\x3b\x01\xd9\x69\xab\x9c\x74\xf2\xf3\x10\x70\xc1\x30\x99\x26\xaf\x31\x19\x02\xc9\xba\xc9\x0b\x86\x6e\x43\xcc\x90\x3b\x04\x82\x85\xa8\x3e\x4f\xa6\x9b\x14\x00\x8e\x9c\x90\x61\xb1\x30\x5b\xbe\x44\x90\x21\x36\x04\xff\x04\xff\x2a\xe1\xdb\x64\x2c\x39\xd4\xcb\xc5\xd9\x49\x9e\x73\xdf\x20\x01\x60\x0e\x5f\xa9\x45\x12\x3a\x65\xa8\xb4\xb4\xf5\x23\x71\x6d\xcb\xca\xb5\x19\xe4\x5b\xb9\xae\xe8\x0e\xfa\x81\x67\x02\x1a\x3f\x99\x6e\xa7\xba\x59\xb1\x95\x7d\xea\x78\xd4\x1d\xdb\x20\xad\xb2\x6d\x73\x55\x60\x39\xe0\x43\xe1\xcc\xa4\xba\x90\xec\x28\xf9\x07\x29\xc9\x1f\x91\x74\xd0\xed\x3d\x0e\x49\x4f\x19\xa3\xac\x3e\x29\x07\xdd\xde\xba\x04\x4c\xbb\x96\x92\xed\x38\x14\x33\x20\xe8\x0d\x22\xd2\x20\xc0\x64\x0e\x3d\x63\x7b\xb7\x06\xdd\xc1\x0f\x42\xa4\xc1\xfa\x44\x1a\x2c\x23\xd2\x39\x4d\x79\x29\xc7\x63\xe8\x0e\x73\xc1\x53\x82\xed\x3d\xd6\x46\x5d\x91\x60\x7b\xdd\xee\xba\x04\x4b\xbb\x96\x12\xec\x23\x41\x77\x01\x72\x04\x72\x01\x92\x70\x01\xea\x28\xab\xca\x5d\x59\x5f\xad\x62\x7e\x6c\x58\xd4\xf3\x32\x0b\x05\x02\x0f\x73\x21\xf5\x5c\x96\x19\x78\x95\x99\xb2\xac\x53\x51\xfb\x4a\x90\x6d\x0b\x91\xb6\xdc\x09\xe0\xd4\x58\x84\xa5\xcd\x39\xfe\xb6\x4a\x73\xca\x5c\xc4\x5e\x2e\x56\x99\x00\x41\xe6\xcc\x5a\x4f\x5e\x91\xbd\xc5\x5c\x94\x8b\xc4\x25\x2b\xd5\xe8\x8e\x7a\xba\xa3\x11\x85\x4b\x45\x61\xce\xae\x5f\xd1\xa2\x8f\x85\x63\x20\x3d\xde\x65\xd2\xf1\x1e\x82\xd1\x61\x08\x0a\x64\x42\x09\x4c\xb1\xf8\x4a\x7d\x56\xc1\x91\xaf\xe9\x96\xb1\xc9\xc2\xca\x96\x76\x01\x28\xfd\x80\xdb\x10\xb1\x85\x41\x5f\xed\x94\x40\xbe\x20\x4e\x19\xd5\x3f\x20\x36\xa1\xcc\x57\x96\x1f\x54\xd1\x07\x80\x09\x80\x44\xf7\x9a\x31\x4a\x68\xc8\x81\x0f\x09\x41\x6c\xab\x9a\xdb\xb4\x7b\x32\xa6\xd4\x43\x90\x18\x5f\x2c\x0e\x09\x88\xad\xcc\x97\xd4\x35\x08\x5c\x12\x96\x31\x1c\x55\xeb\xe6\xa8\xde\x1a\xf6\x8d\x51\x4b\x02\x5e\x68\x20\xb3\x3b\xa4\x6c\x7f\x24\xbd\xf4\xe2\x95\xee\x94\x7a\x96\x7c\x66\x90\x56\x95\x73\x57\xa6\x3e\xfa\x8f\xac\x3e\xca\xa5\xa1\xe3\xa0\x40\xa0\x8c\xf1\xfc\x63\x08\xc0\x41\xb7\xab\xd6\x05\x53\xb2\xbe\xb6\xc8\x0f\x51\x4a\xa7\xdf\xa4\x96\x50\x2d\xb5\x40\xe4\xa9\x44\x34\x28\xd7\xe8\xd7\xc6\x37\xab\xe5\x9b\x5d\xa5\xbe\x3d\x72\xa5\xcc\xa0\x21\x73\x10\x70\x29\xe2\x64\x5b\x68\xff\xac\xb1\x49\x72\x8c\x45\x40\x58\x66\x96\x68\x6d\x1f\x47\x4d\xb2\x4a\xba\x8e\x17\x76\x0f\x3b\x43\x9a\xdd\xc5\x71\xfe\x60\xde\xd7\x93\xf6\x8d\x56\xf5\x8b\x1a\x97\xa8\x71\x89\x1e\x27\x3a\xc4\x77\xfe\x5d\x9d\xb9\x58\xb2\x19\xb1\xdb\x7a\x08\x91\x66\xc6\x94\xf2\x02\x2d\x97\x08\xb0\x89\x2f\x7b\x93\xa7\x29\x3b\x6a\x46\xe6\x9b\xa0\x7c\x63\xf8\xd5\x21\x52\x13\x94\x5f\x89\x60\xf7\x12\xbb\xba\xa9\x87\x04\xfa\x9e\xb2\x50\xcf\x50\x2a\x0e\x4f\xd4\xe7\x65\x12\xb1\xb4\x95\x5d\x28\x3e\x95\x8d\x62\xc1\xa1\x71\x77\xff\xb0\x52\x4f\x2f\xf0\x3d\x64\x5f\x66\x80\x2a\x09\xa8\xac\xa2\x58\x8d\x82\xaf\x58\xcc\x00\x0f\x90\x83\x27\x18\xb9\xe0\xec\xe4\x47\x96\x84\xf7\x23\x62\x7e\x80\x35\xa5\x62\x20\x35\xcc\xf7\x14\x8a\x6a\x82\x52\x99\xf8\x41\x7e\x5d\x26\x12\xcb\x1a\x2d\x8f\x45\x9f\x40\x01\x81\xa0\x1a\x88\x5c\xd1\x8e\xe4\xa5\xad\x0a\x36\x31\x99\xc4\x47\x6c\x8a\xda\x6a\x94\xff\xae\x1b\xa9\xd6\x61\x75\x3a\xfe\x82\x1c\x51\x32\xac\x1c\x6a\xc5\x51\x73\x0e\xeb\xcf\x97\xef\xcf\x35\x7d\x9e\x83\x8b\xd7\xaf\xc0\xfe\x51\xb7\x0f\xda\x49\xdd\xa1\xa0\xd4\xe3\x1d\x8c\xc4\xa4\x43\xd9\x74\x67\x26\x7c\x6f\x87\x4d\x1c\xd9\x6a\x3d\x68\x37\x1f\xa2\x4f\x3a\xff\x11\x42\xe4\x8d\x27\xf0\xe7\xd5\x89\x8d\x27\xf0\x23\x78\x02\x96\x5a\xd3\xa8\x1c\x79\xd5\x6a\x53\x27\xaa\x62\x5e\x25\x47\x9d\x2d\x7d\xae\xce\x42\xa7\x60\x81\xda\x9a\x77\x49\xca\x1a\x38\x99\x31\x6b\xa4\xae\x73\x3d\xfe\x74\x29\xec\x08\xfd\xc7\x4b\x65\x47\x5c\xb0\x66\x46\x5b\x77\xde\x4c\x62\xdb\x32\xd6\x0f\x99\xdf\x8e\x10\x69\xd2\xdc\x4d\x9a\xdb\xa0\x5c\x63\xe3\xd4\x20\x52\x93\xe6\x7e\x5a\x66\xce\x1a\x69\xee\x8c\x42\xaf\x55\x74\x9c\x33\x59\xee\x9b\xf6\xce\x0f\x57\x27\xfb\xed\x64\xfb\xd4\x4e\x80\xe7\xfa\x3d\x74\x05\xf2\xd3\x4c\x63\x45\x0b\xb0\x72\x85\x70\x8e\x98\x8d\x74\x6f\x32\xe2\x0f\x7c\x5e\x22\xe6\x40\xf3\x88\x5f\xf4\x6e\xc5\x53\x7e\x69\x2f\xbb\x03\x50\x76\xd0\x2f\xeb\x0d\x3d\xfc\x59\xbf\xfb\xcb\x62\x33\x5f\x9f\xf3\x30\xcb\x4e\xfb\x55\x38\x8d\xd5\x4d\x9f\xb4\xfc\xab\x19\xc3\x8b\x1d\xc0\x26\x96\xd7\xd8\xb9\x75\x88\xb4\x66\x2c\x2f\x66\xb3\x26\xa6\xb7\x6e\x1e\x2b\x7c\x10\xf1\x19\x06\xae\x25\x46\xf7\x72\x71\xe6\xe6\xa5\x68\xe8\x06\x30\x9b\xc7\xaf\x12\xa4\x4b\x5b\xd7\xcf\x75\x69\x10\xdd\x35\x33\x5d\x0f\x12\xbc\x5a\x21\x5a\x94\x15\x19\xd9\x28\x5d\xb4\x67\xb8\x80\x22\x54\xf7\xa3\x44\xa8\x37\x72\xb9\x91\xcb\x1b\x96\xcb\x8d\x48\xb6\xd1\x6c\x23\x05\x57\x1b\x90\xca\xb9\xc2\xab\x12\xbb\xb6\x58\x59\x55\x25\x91\x97\xb6\x6e\xea\xb1\x1a\xb9\xf8\x44\x88\xf4\x80\xf5\x58\x49\x60\xb6\x29\xc5\xda\x64\x29\xd6\xe6\xa2\x20\x3b\xd0\x75\x29\x19\xa5\x51\x90\x26\x2c\xb2\x5e\x58\xe4\x58\xd2\xf1\x43\x42\xb5\xbc\x36\x29\x09\x7d\x6c\x73\xa0\x16\xc0\xa0\xb7\x4d\xbb\xac\xdc\xfb\x49\xc5\x52\xb2\xa4\xa9\x8c\x24\x4b\x96\x49\x91\x01\x62\x06\x05\xe0\x33\x1a\x7a\x2e\x18\x23\x10\x72\x7d\xdb\xa0\x43\xc9\x04\x4f\x43\x86\x14\x63\xe9\x7b\xfa\x4c\x0f\x46\x13\x85\x12\xcd\x77\x9a\x56\x9d\x46\x9d\xfd\x51\xd5\x59\x13\x7e\xf9\x91\x6c\xfd\x0d\xea\x2e\xa9\x90\x78\x00\x1d\xf4\x83\x6b\xad\x15\xf3\x8a\x2b\x65\x15\x57\xbd\xd5\x68\xa5\x3b\x8d\x1e\x4f\xdd\x9e\x27\x4b\x5f\x5f\xd3\x92\x7c\x9f\x9a\x3a\xb6\xd0\xef\x49\x69\xd7\x84\x32\x09\x49\x96\x6a\xd8\x14\x21\x30\xc7\x1c\x8f\x3d\x75\x9d\x70\xc8\x11\x03\xb8\x51\x9a\x8d\xd2\x6c\x94\xe6\x13\x54\x9a\xf9\x3a\xe4\x8c\x04\x5c\xa9\x14\xb9\xa8\x36\x6b\x15\x23\x17\x44\x6e\x55\x39\x72\xd2\x78\x25\xa9\xbf\xac\x20\x99\xe4\x46\xad\x53\x92\x9c\xef\xb3\x4a\x3d\x6f\xd2\xf7\xf1\x2a\x7a\x13\x42\xae\x57\xd3\x9b\x74\xdf\x48\x55\xaf\x7d\xb4\x1f\xb2\xae\x37\x41\xa5\xa9\xec\x6d\x2a\x7b\x0d\xca\x35\xd6\x43\x0d\x22\x35\x95\xbd\x4f\xcb\x70\x58\xa7\xb2\x37\xab\x17\x6b\x79\x72\x45\xa7\xeb\x9e\xd5\xbd\xe5\x5e\x5c\x55\x9d\x6e\xb5\x1f\xb7\x52\xcf\xe6\x96\xe1\xf4\x79\x02\xce\xa9\xad\x90\xb8\xb0\x66\x8d\x3a\x69\x4a\x89\x1f\x38\x10\x99\xf2\xa0\x19\x8a\x4c\xde\xae\x58\x4e\x6c\xf6\xb3\x7b\x20\x65\x31\xc8\xbc\x2f\xf3\xf0\xb9\xb3\x4d\xa8\x00\x33\x9c\x57\xf0\x13\xcb\xc2\x78\x95\xae\xdf\xb2\xc6\x4f\x5c\x26\xd6\x2c\x2e\x4e\xbd\xd1\xa6\xbc\xb8\x31\xb6\xeb\x10\x69\xcd\x50\x5d\xca\x68\x4d\xb0\xee\xbb\x5e\x94\xb3\x11\x71\x9a\x2b\x32\x4e\x86\xac\x59\x66\x5c\x29\x58\x6b\xb4\x5f\xb5\xd4\xd8\xe0\xae\x47\xab\x34\x4e\x68\xa4\xee\xc2\xf9\x4e\x05\xc7\xc9\x24\x4d\xc9\x71\x23\xab\x1f\x40\x56\x37\x62\xda\x46\xb5\xcd\x14\x1d\x6f\x42\x4e\xe7\xca\x8e\x4b\x2d\x5f\x4b\x29\x71\xa5\x8c\xae\xd1\xbe\x29\x3e\x6e\x24\xe4\x13\x21\x52\x53\x7c\xfc\x27\x2a\x3e\x36\xe2\x26\x68\x0e\xbd\x8d\xa7\x9b\x4f\xe7\xd0\x0b\xd5\xbb\x4d\xe6\x9b\xf9\x8c\x32\x01\x3c\x3c\x97\xb8\x27\x33\xac\x95\x86\xae\xd5\xfd\x47\xcd\x48\x4b\xea\xdf\x33\x2b\x2d\x87\xd8\x6c\x66\xba\x30\x62\x93\x9d\xfe\xde\x50\x37\xd9\xe9\x07\xa3\x5c\x63\x62\xd4\x20\x52\x93\x9d\x7e\x5a\x2e\xd8\xfd\xb2\xd3\x5b\xe9\xac\x12\xb8\x08\xc3\xa1\xbe\x09\xf8\x99\xfe\x7f\xf0\x8a\xfa\x3e\x25\xd1\x2b\xf5\x9f\xb7\x38\x35\x32\x12\xc1\x6f\x18\x03\x37\x98\xb8\xc6\x9f\x01\x9c\x22\xe3\x4f\x8e\xbf\x99\x7f\x0a\x2a\xa0\x67\xfc\x8d\x05\xf2\x63\xb3\xc4\x72\x15\x72\xc0\xa4\xad\x22\xb0\x49\x6a\x39\xdf\xd2\x34\x8d\x84\xa2\xd8\x08\x13\x81\xa6\x66\xe5\x39\xfe\x56\xa3\x95\x82\xb9\xbc\x99\xfa\xa0\xd8\x24\x6e\x03\x3d\xef\xfd\xc4\x24\x51\x15\x83\xbd\x57\xf8\x5e\xa0\x09\x62\x88\x38\x99\xfc\x76\xc9\xdd\xd0\x36\xa2\x00\xb5\x27\xdc\x02\xd7\x59\x89\x03\xd4\x4a\x42\xcb\x0e\x29\x6d\x9e\x98\x8c\x23\xec\x56\x76\x52\xdf\x72\x38\x0d\x57\x5b\x60\xbc\x7c\x79\x6b\xf1\xc0\x4c\x52\xbd\xac\x91\x01\xe7\x3b\x24\xe0\x8a\x20\xd2\xaf\x04\xb1\xa5\x00\x68\xd3\xda\x1d\xc1\x8c\x9c\x9a\x50\xe6\x43\x31\x94\x66\x27\x6a\x0b\xec\xa3\x65\xc3\xf8\xd4\x55\xee\xd6\xba\xe3\xa8\xf7\x97\x88\xcd\xb1\x13\x07\x4d\x30\x25\x97\x48\x48\x69\xc1\xab\xb6\x36\x36\x37\x76\xc8\xbc\xfb\x2d\x5a\xc8\x2c\xbb\xc8\x02\xe3\xb1\xe3\xd0\x90\x54\xca\x1c\xc7\xc3\x88\x88\x51\x06\xbe\xe8\x1d\x47\x0e\x43\x55\x6b\x97\xf4\x5d\xbe\x7e\xe6\x88\xd5\xa0\xff\x86\x18\xc7\x94\x48\x56\x92\xee\xc4\x03\x49\x02\x64\x53\x35\x6a\x6f\x80\xd6\xf1\x87\xb3\x08\xa8\xac\xf6\xc2\xf2\xe3\xbc\x97\x7d\x39\xd3\x60\xd9\x9d\xd1\x56\x4e\xca\x78\x9e\xe6\xa0\x82\xfa\x6b\xeb\xc1\x95\xef\xca\xf3\x3a\x73\xc9\x24\xc5\x9f\x20\x2e\xf4\x8f\x10\x2b\xfd\x4d\xb9\x72\xb9\x58\x0a\xb1\xa6\x2b\x64\x0c\x2e\x72\x5f\x94\x62\x2a\xea\xf0\xdc\x82\x9a\xb8\xaf\xb4\xb4\x19\x9d\x1b\xf1\x3d\x37\xb5\xee\x2f\x92\x1c\xe5\xbb\x35\x63\x16\xfc\x44\x3d\x97\xc7\x6a\x5f\x9d\xe7\xd4\x66\xba\x3e\xe0\x29\x47\x90\xff\x84\x7a\x4c\x70\x46\xb8\x80\xc4\x41\x9d\x75\x78\xb4\x54\x8c\xa4\x0b\xf1\x2c\xfa\xed\x90\x28\x4c\xe4\x18\xeb\x92\xb6\x29\x61\xe9\x67\xd9\x55\xd4\x52\x41\x4d\x7d\x81\xa6\x98\x0b\xb6\xd8\x30\x49\xd4\xe0\x20\x1e\xfc\x01\x68\xa3\x1b\x03\x16\xcf\xb8\x29\x2a\xc5\xbc\xa4\x8e\x08\x67\x38\x29\x7b\x68\xd8\x4a\xad\xd6\x71\xfe\xf8\x73\x6b\xe3\x2a\x7b\x0e\xbd\xd0\x62\x6c\x99\x42\xb4\x78\xbc\xb9\x0c\xda\xb8\xb8\x2d\x7f\x68\x3b\x0b\xb6\xb9\xaf\x73\xfb\xb9\xfe\x29\xeb\x56\xde\x3e\x2e\x5e\x5f\x9f\x90\x3a\x7f\xb6\xee\x52\x40\x91\xb3\x7e\x32\x54\x41\x24\xf4\x4d\xee\x72\x31\x8f\xb8\x13\x99\x9a\x8d\x21\xe8\x2e\xec\x33\x44\x51\x23\xd3\x84\xb1\xad\x8f\x2a\x9c\xaa\xa4\x7d\xc9\xc0\xf6\x05\xd0\x5b\x52\x5a\x20\x66\xdd\x8c\x91\x99\x86\x2a\xa8\x06\x02\x0f\x12\x64\x9c\xff\xd3\xe9\xdb\xd6\x3a\x9b\xab\x02\xf1\x96\x1d\x03\x93\x26\x6b\xe8\x61\x3d\xf2\xf7\x02\xee\x52\x51\xa2\x6a\xc9\x78\xa6\x45\xc9\x5e\x2c\xeb\x1c\x0f\x50\xf0\x07\x56\xc1\x42\x71\x6f\x2e\x1e\x69\xba\x39\xf5\x99\x69\xd3\xe6\xd0\x2a\x58\xdc\x67\x1d\x2f\x23\x7e\xb5\x22\x65\xca\xa7\x95\x10\xcb\xda\x2d\x2b\xbb\x79\x56\xcb\x64\x65\x43\x66\xb5\x2b\x3b\xed\x22\xd0\x78\xfb\x6a\x06\x09\x41\x5e\x85\xac\x73\xd1\x04\x86\x9e\x90\x6f\xe1\xd8\x43\x25\x12\x30\xfa\x98\x25\xf8\x09\xe2\xd2\x01\x58\x55\x9a\x6a\xb1\x69\x8e\x4d\x83\x20\x23\x58\xdd\x28\x45\x9a\x9d\x6e\xd5\x79\x20\xe7\x78\x4a\x4c\x5d\x17\xbf\xcb\x4c\xa6\x44\x63\xb6\xd5\x72\x08\x27\x10\x7b\x45\x90\xb3\xa3\xb8\xb9\x44\x6f\x5b\xb2\xce\x1c\x4b\xd3\x3f\xdf\x30\xf3\x21\xc7\xd5\xa6\x9d\x54\x19\xef\x91\xd6\x9d\x09\xb4\x36\x7b\x46\x50\xbb\x6d\xc6\x97\xfc\xef\x86\x59\xa3\x39\x72\x34\x93\x3d\xab\x18\xb3\xc4\x28\x4e\x37\x53\x0e\x96\xe2\xb8\xdb\x55\x96\x5b\xe4\x78\x6e\xa7\xc3\xa9\xef\xa3\xd8\x58\xab\x0b\xe6\x32\x8b\x35\x85\x37\xa1\x90\x39\xf4\x33\x23\x7a\x57\x65\x1e\xca\x96\x4a\xcd\xf2\x19\x0c\x50\xe6\x75\xc0\xa8\x83\x38\x37\x7f\x47\x5b\xbe\xd6\x11\xc3\x19\x24\xae\x97\x0d\xf0\x64\x44\x50\x96\x2f\x2c\x16\x86\x8d\x2b\xa4\x85\x61\x5b\xfa\x91\x1c\x3a\xeb\xa8\xbb\x7a\x3b\x8f\x94\x82\x5a\xd7\x68\x29\x50\x30\x9e\x68\x69\x0f\xb3\x68\x7c\xf9\xf0\x59\xb9\xb6\x6c\xe5\x23\x31\x98\x2e\x70\x06\xd7\xda\xa3\xd8\x04\x5f\x5e\x0b\xe5\x0c\xb4\xf5\x8c\xa9\x8c\xa1\xb2\x62\xdf\x8c\xc0\xc8\x43\xf7\x18\xc6\x57\x09\x32\x2b\xaa\xd7\x38\x13\x31\x9a\xeb\xe8\x89\x5d\xd3\xe6\x63\xc4\xfa\x89\x43\x72\x98\x88\xfd\x81\x45\xab\x3c\x59\x8b\x6f\x03\xa6\xde\xa3\xd8\x78\x9b\x60\xdc\x15\x7b\xdb\x6d\xc2\x3f\x81\x31\xd8\xb2\x1b\x81\xe0\x6a\x11\x64\x43\x57\xc9\x27\xf9\xc5\xea\x40\xfe\xbd\x9d\x80\x70\x81\x02\x86\xb8\x9c\x31\x53\x77\xa7\xca\xe5\x79\x18\x04\x94\x09\xe4\x82\xf1\x42\x39\x9a\xc7\x1f\xce\xa2\x8e\x94\xa0\x2c\x8d\x8b\x3a\x09\x14\xf5\x92\x7e\x15\x6d\xec\xdc\x5b\x8d\xef\x26\x47\xfc\xc2\x29\x19\x65\x86\x7d\xa4\xc4\x50\x5e\x91\x16\xd6\xe3\x1c\xfa\xa8\x78\x46\x4a\xce\xd2\xa9\x12\x00\xe6\x87\x12\x69\x99\xad\x20\xd0\x6d\xee\x39\x53\xa4\x92\x0b\x6c\x9c\xad\xf3\x89\x1a\xad\x32\xd7\xa6\x36\x4c\xde\x06\xc8\x03\x57\x05\xf7\xb1\xf9\x67\x01\xf8\xda\x34\xc2\x0e\x25\xa3\x7c\xfe\xab\x30\xd9\xc7\x8b\xb7\x2a\x0a\x4a\x54\xfb\xf5\x67\xf3\xe0\x78\xd9\x7a\xbc\x55\x4d\xd2\x7b\x07\xa1\x40\x53\xca\xf0\x37\x94\x9d\xf2\xbe\xeb\x52\xce\x34\x30\x80\x63\xec\xe1\xe2\xe6\xb0\x1d\x14\x33\x1a\x17\x85\x90\x23\xd7\xfb\xbb\x02\x6b\xaf\x53\x28\x93\xa0\xf1\x73\xac\x04\x4e\x1c\x60\x56\x17\x3e\x3a\x90\x98\xb7\x3d\xce\x75\x05\x0f\x02\xb0\x60\x46\x16\x46\x4b\x37\xcc\x04\x23\xcf\xb5\xf3\x42\x41\x02\x01\x53\xe8\xfd\x38\x08\x14\xd5\xd6\x9f\x40\x9f\x4b\x34\x4b\x83\xdb\xb9\x9a\xd1\x67\x59\x0a\xe5\xaf\xd2\x59\x23\x83\x5e\xcb\xb9\x83\x84\x50\x01\x0b\x99\x3b\x3b\x3d\x2c\xb4\x28\xe5\xd2\x32\xf2\xdb\x75\x65\xc5\x56\xb5\xe4\x35\x96\xf4\xb0\x9b\x15\x56\xc3\x42\x99\x16\x72\xf8\xad\x12\xf2\x3f\x86\x97\x65\x5b\xfb\xbc\x35\x9c\xb4\xb9\x42\x04\x12\xf1\x8b\x51\xad\x51\x23\x9e\x16\x72\xc3\xb1\x6a\x03\xca\xa6\x90\x60\xae\xd8\xa0\x7a\x9e\x0a\x16\xac\x51\xb8\x94\xc4\x28\xea\x14\x1d\xad\x46\xaa\x94\x0c\x29\xb9\xb3\xc1\x87\x8c\x6c\x3c\xc5\x62\x86\x98\xbe\x46\x90\xb2\x0c\x01\x00\x76\x81\x8b\x02\x44\x5c\x4c\xa6\xf1\xc5\xbc\x8a\x47\xa4\xfa\xce\x60\x54\xe9\xad\xe5\x57\xd1\x6a\xa6\x1f\x5b\x4f\x47\xe9\xaa\x96\xdc\x61\xe9\x3a\x21\xa1\xe2\x75\xa1\x99\x35\x58\x2f\x82\x61\x67\xc7\x74\x90\x55\x6b\xa8\x12\x20\x2b\x5d\x5f\xf3\x43\x9e\x35\xee\xc7\x1e\x25\xcb\x64\x1e\xaf\x5c\x61\xad\xd4\xe1\xd9\xdc\x52\x3d\x00\x9d\x4b\x90\x30\xca\xdc\xed\x38\x90\x25\x65\xfe\x6b\xf1\xde\x26\xf1\x2d\x41\xec\x4f\x60\x2d\x18\x75\xf4\x25\x44\xa8\xcf\x96\x1b\xf6\x86\x57\x83\xff\x9e\x02\xc2\x66\x1f\xac\xea\x14\xd7\x0f\x21\x9a\x5f\xd6\x10\x4d\xe8\x2e\xc0\xd9\x84\x4e\xfc\x3c\xab\xaa\x1e\xac\x1c\x53\x64\x54\x6d\xfc\xac\x23\xf0\x4c\x0f\xb9\x2c\xd8\x82\xdd\xdc\x8b\xda\xd1\x17\x8b\xba\xd1\x1f\x34\x02\x92\x8b\xf3\x57\xcc\xa4\x94\xd5\x17\xcd\x24\x5d\x0b\x1e\xe2\xd9\x89\x54\xb7\x0c\x39\x94\x25\x37\x03\xe4\x7c\x1e\x0b\x01\x73\x77\xc7\x58\x4e\xd1\x98\x65\xcb\x1a\x06\xa3\x9c\x3a\xff\x73\xff\xd9\x1f\xf5\x87\x53\x04\x30\x71\xd1\x5d\x61\xf4\x09\xf4\x38\xaa\x0f\x65\xb1\xb8\x3d\x5f\x4c\xad\x4d\x5e\xd0\x8a\x2a\x03\xcd\x2a\x6a\x0d\xb4\x51\xf4\x5d\x09\xf4\x79\xe8\x8f\xa5\x8d\x33\xd1\xa2\x09\x60\x02\x10\x74\x66\x26\xd2\x1b\x44\x23\x5f\xed\x9d\xa0\xd1\xed\x6a\x44\xa2\x0b\xc3\xac\x82\xec\x3f\xa9\xbf\x7a\x19\x9d\x7f\xd4\xf5\x65\xaa\x13\x18\x2f\x80\xc3\xb0\x40\x0c\xc3\x8e\xe2\x10\xbe\x20\x02\xde\xe9\x98\x0a\xe6\x29\xab\x01\xcc\x0d\x80\x7c\xec\x41\x26\x3d\x5b\x91\xeb\x82\xc0\x75\x3c\xf0\x35\x70\x3c\x18\x72\x65\xe0\x41\x02\x2e\x7f\x7d\xab\x03\xfd\x3e\x22\x22\xf5\x6a\x4f\x25\xdd\x14\xa1\x63\xa7\x59\xf5\xd7\x61\x0b\x48\x16\xc9\xb0\x19\xff\xef\x5a\x3b\xc7\x3c\x1d\xe7\x35\x65\x31\xe9\x9e\x4b\xc0\x98\xba\x04\x4e\xca\x6a\xc3\x3b\x94\xe4\xe6\xe6\x04\x62\x86\xb0\x16\xf0\xcf\xa5\xd9\xaa\x66\x9a\x50\xcf\xa3\x5f\xa5\x99\xaa\x11\x8b\x0a\xd5\xe4\x73\x7d\x7d\xcd\x6f\xbd\x8c\x2f\x08\x20\x77\xcc\xef\x69\xe3\xab\xd5\x81\x00\x23\x48\xdc\x51\x2c\x18\xee\x03\xd2\xf3\x78\x90\x72\xf8\xce\x34\x61\xcd\x15\x26\xdb\x42\xe7\xe0\x5d\xe4\x3e\x97\x26\x3c\x9e\x18\x86\x3a\xe6\x00\xf9\x81\x58\x3c\x97\xef\x52\xb1\xa5\x2b\xa9\x78\xe8\x09\x0e\x20\xcb\xac\x9f\x84\xa6\x93\xf0\x75\xe0\x51\x17\x65\x8e\xde\x15\x79\x3d\xc7\xca\x26\xbb\xc7\xa8\xb5\x4a\x76\xa8\xde\xc2\xd1\x00\xf7\xdd\x85\x5c\x2c\x3c\x34\x54\x5a\x4d\xcb\x0a\x75\xc3\x9e\x7d\x87\xa5\x1b\x4c\x35\x4a\x37\x94\xc1\x0b\xd5\x3b\x6b\xc9\x8e\xfa\x3a\x43\x0c\x65\xb6\x53\x3a\x65\x66\x57\x81\x63\xc9\x27\xc8\x8d\x76\x47\x7c\x97\xbb\x06\x5e\x2d\xce\xb5\xa4\xd2\xf5\x73\x70\x6d\xa0\x20\xff\x8c\xb8\x45\xfe\x53\x45\x45\xaf\x9f\x03\x48\x5c\x70\x1d\x05\xad\xaf\xd3\x8d\x16\x4f\xa1\x4f\x56\x50\xa6\x17\xfd\xfa\x7f\xfe\x2e\xfb\xbe\xb8\x56\x6c\x73\xfd\xf6\xec\x97\x53\x4b\x1f\x87\x92\x2f\x21\x71\x04\x9e\xa3\x7c\xff\xe3\xf3\x93\x6b\x3d\xe5\xfb\x8b\xeb\x0e\xf8\x89\x7e\x45\x73\xc4\x9e\x83\x05\x0d\x95\x60\x90\x98\x43\xe0\xc3\x3b\xec\x87\xbe\xa4\x41\xaf\x9b\x0e\x47\x89\xc2\x15\xc6\x98\x2a\xb6\x30\xc8\x7f\x9a\xf0\x99\x6d\x77\xe6\x72\x42\xfa\xb0\xb8\x88\x6e\xc9\x07\xd7\xf0\x2b\x6f\xf3\x5b\xde\xd6\xe9\x55\x0d\xa4\x8a\xa7\x6a\xd2\x80\x6b\x5d\xfb\x73\x5d\x77\xbb\x66\xf7\xea\x0b\x90\x1d\x5f\x0d\x1f\x0f\xfd\x22\x5b\x74\xa4\xba\xff\x33\x68\xff\xcb\x8e\x86\x2e\x93\xc6\x51\x29\xb0\x46\x03\xea\x59\xf4\xcf\xe8\x08\xc8\x04\xd7\xef\x25\x56\x6b\x42\xec\xe1\x1b\x24\x81\xfe\x4b\x7f\xef\xbb\x08\x16\x25\x2e\xe5\xc7\xec\xb2\x18\xf2\x06\x0a\xf5\x5d\xc5\x19\x66\x90\x83\x00\x31\x1f\x73\x1e\xd5\x49\x73\x84\x14\x4b\x69\xba\x20\xd7\xe0\x83\x73\x2a\x50\x27\x86\x4f\x2b\x9d\xf4\xa0\xa3\xe4\xf8\xa8\xd2\x04\x73\xa3\x77\xb9\xf8\x8a\x8c\x06\xc5\x73\x25\x42\xc9\x2e\x80\x2c\x3a\x3e\x23\x5f\x40\x5e\xec\xd5\xe2\x92\xd6\x7a\xe2\x6d\x2b\x3d\x2b\xaf\x0a\x80\x62\xb0\xa2\xc3\xf2\xe6\xa0\x68\x08\xc6\xea\x6d\xf4\x52\xff\xf1\x3a\x32\xc9\x7f\xfe\x74\xb5\x65\xce\x38\x13\x22\x90\xa3\x67\xb1\xcd\x17\xe9\x59\x0f\x7f\xe7\x82\x93\x9a\xd0\xad\x77\x8b\xcc\x6f\x71\x67\x2c\x82\xea\x01\xb0\x3b\x04\x1e\x9d\x8e\x38\x26\x37\xa3\x6e\xa7\x97\x7c\xd0\x67\x33\x32\x23\x25\xdf\x56\x3a\xf7\xa1\xca\x78\xf8\x8e\x39\x49\x2b\x07\xff\x5b\x3a\x05\x97\x98\xdc\x24\xaf\x63\x37\x0b\xb4\x32\xad\x6d\x89\xc2\x76\x5e\x12\x64\xb3\x54\xf9\x91\xd3\x3c\xda\x9a\xf0\x77\x02\x32\x4d\x21\x2a\x26\xca\xda\x80\x9b\xf3\x95\xa5\xa9\xda\xaa\xd0\x6b\x94\x2f\xf4\x6a\xdb\x0a\xbd\x8a\xc9\x97\xf2\x83\x31\xbe\x5f\x74\x0d\xd3\xad\x96\x5e\xef\x10\x3f\x02\x0b\x4f\xaf\x80\xd5\x5d\xb4\x44\xda\xab\x62\xed\x00\xf8\xa1\x27\xf0\xc8\xc3\xc4\x7a\x5c\x36\x29\x19\x35\xf7\x7c\xb6\x81\xb1\x78\xef\xe4\x58\xe0\x2d\x26\xb6\x96\x11\xe0\xd5\x6d\x14\x0e\x63\x4a\x3d\x04\x89\xe5\xfb\x5d\x7b\xca\x68\x18\x0c\x41\x0b\x11\x37\xa0\x98\x88\xe2\x19\x25\x3e\xa3\x5f\x47\xd0\xf3\xee\x8f\xce\xe5\x8c\x7e\x95\x0a\xbf\x1c\x99\xaa\x16\xf7\x44\x45\xd0\x00\x3b\x4b\x32\xec\xd4\xf7\xa5\xa1\x20\xd5\x93\x40\x6e\x72\x24\x43\x6b\x4f\x35\x80\x8e\xf8\xd8\x59\xe8\xaa\xbc\x41\x79\xae\x25\x05\x5b\xed\xba\x2c\xcc\x5c\xa0\xe0\xfe\xc1\xb0\x5c\x61\x49\xfa\xb4\x2b\x19\x39\x1a\x93\x70\xc4\xc4\x48\x59\x8d\x65\x6d\xca\xfd\xca\xe2\x73\xec\xba\xaa\x2c\x26\xe4\x82\xfa\xda\x18\x8d\xcd\x11\x87\x2a\xfb\x44\x44\xaa\x3f\x32\x78\x7d\xc4\xb9\x0e\x04\x00\xc1\x20\xe1\x58\xe4\xf3\x9e\xe9\xb3\x1c\x1d\xf9\x2c\xc1\xa5\x80\x4f\xfc\xab\x48\xb1\xd1\xad\x81\x16\x54\x7a\xa4\xd0\x75\x91\x5b\x39\x54\xc4\x1c\xaf\x65\xa7\xea\x86\xe5\x4c\x62\x3e\x25\xc9\xb9\x4a\xe8\x93\x6c\x4a\x02\x7e\x1d\x90\x7f\x53\x79\xba\x7b\x83\x5c\x96\x1d\x34\x9f\xf6\x52\xa8\xe2\xb4\xe1\x12\x98\xcf\x14\xbb\x6a\x6a\x83\x63\x65\xff\x97\x77\x29\x17\xf0\xf5\x20\x6f\x67\x76\x87\xb5\xd1\x92\x39\xea\xec\x40\x74\x27\x18\x74\x56\xdb\x82\xa7\xba\x0f\x80\x11\xb3\x4e\x18\xf5\xd5\xe2\x8f\xa9\x9b\x97\x1a\xe9\xf3\xc7\xdf\x3e\x9b\xe0\xc5\x08\xa2\x98\xc4\x0f\xc5\x6a\x19\x36\xf8\x5e\xbc\x36\x83\x7c\x34\x43\xd0\x45\x6c\x34\xc1\x9e\x40\x85\x6a\xd9\xf4\xc9\xac\xf1\x6b\xd5\x18\x8c\x21\x97\xee\xbf\x0e\x2d\xe8\x22\x48\x47\xad\x3b\x25\x08\xe8\x71\xef\xc9\x7c\xf6\x5a\x86\x52\xb8\x24\xef\xe9\x79\x23\x67\x97\xc6\xe9\xb8\x6a\xc1\x16\x9f\x42\x8f\x3a\x9f\x17\xcb\x18\xb2\x4f\xc4\x13\x3f\xe9\xa9\x96\x37\xdf\x1c\xaf\x5a\x2a\x2c\x8a\x60\x41\x1e\x83\x16\x2d\xd4\xf7\x67\xd7\x02\x27\xd5\x63\xd9\xd4\x05\xac\xed\xfb\xbd\x5b\xbc\xa5\x53\x33\xeb\x94\x39\x0d\x01\x5a\x47\x63\x3e\xef\xf2\x03\x41\xd0\xc1\xb4\xdb\x9f\xce\xf6\xa6\x03\xc3\x7f\x29\x1c\xd6\x31\xfa\xec\x8f\xd9\x84\x75\xbb\xfd\x60\x42\x6e\x66\x5d\xd3\x34\x4b\x2f\x5c\x00\x2d\xce\xe6\x4e\x1b\x3a\x8e\x68\xf7\xf6\xfb\x68\xd2\x77\x0f\xdb\xdd\x7e\xf7\xa8\x3d\xe8\xf5\x0e\xda\x87\x83\xfd\x7e\xdb\x9d\xec\xef\x3a\xfd\x6e\x7f\xcf\xe9\xef\x5b\x46\x89\x2e\x63\x00\xad\x71\x6f\x30\x70\x8f\x8e\x7a\xed\xee\x21\x1a\xb7\x07\x83\x83\x7e\xfb\x10\x39\xbd\x36\x1a\x77\x77\x07\xce\xfe\x51\x7f\xb7\x37\x36\xfb\x87\xcc\x1b\x82\xd6\x84\xd2\xb6\x0d\xde\xce\x0d\xe4\x1d\xe8\xf8\xa8\xe3\x50\x7f\x38\x18\xec\xb6\x72\xfe\x94\xf5\x10\x90\x81\x7e\xf7\xe6\xd0\x23\xd3\xee\x6e\x8f\xa3\xa3\xdb\x1a\xe8\xa3\x6e\x7f\xaf\xbf\xbf\x87\xda\xf0\xf0\x10\xb6\x07\x83\xc9\xb8\x7d\x38\xd8\xeb\xb6\x91\xdb\xed\x75\xd1\x78\x7f\xec\xec\x39\x55\xe8\xbb\xce\x1e\x3c\xec\x1f\x1d\xb6\xc7\xc8\x3d\x68\x0f\xfa\x7d\xd4\x3e\x3c\x1a\x1c\xb4\x27\xfb\x13\x17\xee\x1f\xf5\x8f\xfa\x93\x49\x11\xfd\x31\x64\x11\xfa\x7d\x7f\xe2\xc0\x6e\xb7\x2f\x8e\x6e\x0f\xf8\xb4\xc3\x59\x19\xfa\xf1\x09\x98\xbc\xe3\x5c\x3c\x78\x03\x5a\x76\xaf\xdd\x7a\xc8\xc9\xe6\x7b\x26\xce\x93\x19\x1c\xd2\x4f\xea\x28\xf2\xc2\xd7\xc8\x59\x51\x8b\xfb\x7c\x0c\xb3\x37\xa2\x26\x6e\x73\x76\xaa\x1b\xb4\xc8\x6f\xc7\x38\x23\xda\xba\xbc\xba\x38\x3b\x7f\x93\x75\x2e\xac\x86\x64\xd2\xe3\xe7\xcb\xf7\xe7\xb9\x9b\x28\x22\xaf\xbc\x90\xcf\xac\xf4\x10\xa2\xf8\x8c\xfa\x2a\xe5\x62\xd1\xbf\x8c\xa3\x59\xaa\x89\xb2\x39\xcb\xce\x1a\xe5\x0e\x17\xaa\x80\xdc\x28\x3e\xfa\x95\x2d\x3d\x82\xee\xc8\x43\x42\x20\x36\xba\x0d\x51\x1e\x4d\x45\x5d\xc9\x70\xde\x6d\x2e\x5c\x54\xfd\x8b\x78\x65\xa1\x27\xcb\x85\x7c\x46\x66\x7b\x99\x04\x2a\xa9\x08\x8c\x7f\x1a\xa4\x95\x8d\xcf\x74\x60\x80\x3b\x34\x40\x84\xcf\xf0\x44\x48\xde\xde\x09\x18\x9d\x60\x0f\xd9\x56\x17\xb4\x22\x0f\xbd\x9d\x69\xb4\xc2\x5d\x8b\x65\x38\xcb\x0e\x16\xbc\x1f\x01\x99\xf2\x2b\x03\x2d\x91\xbe\x56\xaf\x6b\x08\x82\xe8\xfa\x95\xdc\x85\x68\xd5\xc1\x31\x7d\x51\xe0\x4e\x66\x1c\x75\x4d\x15\x68\xbd\x7a\x7f\x7e\x7e\xfa\xea\xea\xfd\x45\xfb\xdd\x9b\x77\x57\xed\x4c\x93\xe8\x72\x2a\xd0\xba\x5c\x10\x67\xc6\x28\xa1\x21\x07\xd0\xd1\x05\x70\x1c\x10\x2a\xd2\xb2\x6c\x1d\x7c\x87\x7c\x41\x9c\x17\x52\x30\x14\xef\xb0\xc8\xdd\x5e\x05\x5a\x3d\xfc\xe9\x0c\xfb\xb7\x6f\x1c\x76\x12\xbe\xdd\xef\xc1\x8f\x77\x67\xff\xb8\x7d\x79\x75\x7b\x7e\x01\x13\x2a\x9d\xe9\x60\xf6\xaf\x21\x62\x8b\x1a\x94\xea\x6f\x88\x52\xfd\xa5\x84\xea\x5b\xe8\xf4\x1f\x83\x01\x5e\xab\x13\xc3\xd2\x78\x0b\x20\xe3\x28\x93\xca\x19\x82\x8f\x04\x46\x3f\x52\xac\xe2\x35\x3a\x58\x13\x95\x53\x71\x75\xa1\x03\x0c\xf0\x48\xc7\x34\xa3\xc3\xb4\x43\x50\x80\x60\xb8\xc2\x7c\x69\xfd\xbc\x43\xbd\xd0\x27\xda\xb6\x94\x33\x45\xb1\x7a\xb0\x8d\xdd\xed\x0e\xb8\xb4\xb5\x53\x49\x2d\x73\x36\xa9\x85\x29\x79\x1e\xa5\x9a\x1d\x8f\x86\xee\x28\x4a\x88\xb0\xf8\xad\x3e\x47\xd7\x01\xbf\xea\xc4\x84\x5e\xc8\x21\xc0\x2e\x78\x01\x7a\xfd\xdd\x52\xae\xf0\x3e\x9d\xbc\x09\x17\xe3\x33\x76\x4a\xee\xd8\x31\xf2\x0f\xfa\x83\xe9\xed\xcd\x0d\x3e\x99\xc7\x5c\x91\xbf\x0f\xd1\xc6\x09\x83\xee\x60\x23\x9c\x70\xb0\x8c\x11\x0e\x2c\xfb\xa5\xce\xa5\x8a\x09\x32\xd6\xfb\x8f\x6d\x28\x1d\x3c\x1e\x42\x69\xee\x4a\xc5\xbd\xb0\xfb\x62\xbb\x87\x7f\xd9\x75\xc3\xdf\x3e\x9f\xcd\xe7\x7b\x9f\xe7\x6f\xbd\xc5\xb7\x9e\xff\xe6\x62\xf7\xe7\xc5\xed\xf9\xb6\x12\x0d\x13\x1a\x9a\x85\xb6\x85\xcd\xff\xf9\xfd\xc1\xb4\x3f\xdd\xff\xe9\xca\xfd\xf8\xcb\x47\xd8\xbf\xe1\x3f\x1d\xf6\x6f\x7e\x3d\xd9\x5d\xc4\x94\xc9\xdf\x0d\x6a\x15\x8d\xbd\xcd\x48\xc6\xde\x52\xc1\xd8\xb3\x90\x25\xdd\xc6\x73\xc4\xf0\x64\x01\x7e\xfe\x74\xa5\x6f\x1c\x1d\x82\x8b\xc8\xdd\x00\x30\x14\x33\xca\xf0\xb7\xf8\xe6\xa3\x1b\x44\xea\xd1\x67\xf7\xe3\xec\x74\xf6\xd5\xff\xfd\x65\xf0\xe9\xc3\xe4\xac\xef\x9d\xa3\x9b\xc0\x1d\xfc\xe3\x24\xa6\xcf\x91\xd4\x65\xaf\x28\x99\x78\xd8\x11\x35\x68\xb5\xbb\xbf\x11\x5a\x99\xc3\xd8\x69\x65\xb6\x30\x59\x48\x9f\xd7\xd1\x92\x07\x73\x00\x3d\x65\x1b\xa9\x63\x25\xa5\x74\xd8\xbf\xf9\xdc\xfd\x88\x4f\x6f\xbe\xdd\xfc\xfe\xea\xdb\xa7\x0f\xe8\xac\x4f\x3f\xa3\x99\xbb\x7b\x1a\x91\xa1\x78\xd3\xa7\x0d\xf5\xa3\x8d\x60\x7e\xb4\x0c\xf1\x23\x2b\x8f\xa4\x17\xb5\xa3\xec\xa4\x85\x25\x47\xa7\x6f\xe7\xaf\x8f\xbe\xbc\xfb\xf5\xf3\xfe\xe7\xe9\x6c\xf2\xee\x68\xfa\xe6\x82\xff\x34\x3f\xfd\x94\xe0\x5a\x5b\x58\x3c\x1e\xc6\xa6\x16\x54\x73\x26\x97\x65\x00\x69\x1d\x70\xe9\x37\xbd\x7f\xf5\xae\x7d\xfa\x7b\xfb\x68\x18\xdd\xac\x21\xb7\x90\xbe\x3f\x23\x6d\x83\xee\x44\x3b\xd2\x7d\x30\xc0\xed\x1e\xbe\xeb\xee\x7a\xc4\xf5\xfc\xdb\xee\xed\xc4\x39\xe0\x58\xc0\x3d\xee\x7d\x99\x1f\x9a\x4e\xc8\xc4\xf8\x61\x76\x49\x87\xde\x74\xcf\x3d\x3c\xbc\xed\x7a\xcc\x71\xe7\x83\xe9\x01\xf4\xc6\x07\xdc\x9b\x4c\xc9\x97\x5d\x77\x36\xe6\x5f\xfe\xf2\xff\xfe\x7a\xfa\xfb\xd5\xc5\x31\xf8\x2f\x8d\x71\x47\x41\xfc\x02\xbb\x88\x08\xb9\x66\x66\x08\x00\x73\xb0\x3d\xe8\x0e\xb6\x9f\x2b\x5a\xa8\x3f\x5f\xbd\xfd\x78\x79\x75\x7a\x71\xa9\x89\x21\x3f\xaa\x54\x76\xb2\xb0\x20\x1d\x48\xb5\xef\x4d\xf7\x28\xdb\xeb\xce\x71\xd8\x3d\xa0\x48\x2e\xdb\x8c\xdd\x38\xfd\x7d\x77\x3a\x11\x5f\x7a\xd0\xd9\x36\x95\x6c\x94\x1d\x56\xbd\x2a\x91\x30\xe4\xed\xdf\x2a\xe4\xc9\x15\xff\xc4\x16\xfb\x84\xdf\x8e\xfb\xfc\xdc\x7f\xfd\x65\x6f\xfc\x7b\x70\x72\xf0\x0a\xb6\xb6\xfe\x2f\x00\x00\xff\xff\x3e\xa2\xe6\x96\xa5\xd9\x00\x00") func connector_mgmtYamlBytes() ([]byte, error) { return bindataRead( @@ -93,7 +93,7 @@ func connector_mgmtYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "connector_mgmt.yaml", size: 41252, mode: os.FileMode(420), modTime: time.Unix(1, 0)} + info := bindataFileInfo{name: "connector_mgmt.yaml", size: 55717, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/internal/connector/internal/handlers/connector_admin.go b/internal/connector/internal/handlers/connector_admin.go index aaf8ad868..7daddb282 100644 --- a/internal/connector/internal/handlers/connector_admin.go +++ b/internal/connector/internal/handlers/connector_admin.go @@ -6,7 +6,6 @@ import ( "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/auth" "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/client/keycloak" "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/server" - "net/http" "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/api/dbapi" @@ -23,13 +22,14 @@ import ( type ConnectorAdminHandler struct { di.Inject - Bus signalbus.SignalBus - Service services.ConnectorClusterService - Keycloak coreservices.KafkaKeycloakService - ConnectorTypes services.ConnectorTypesService - Vault vault.VaultService - KeycloakConfig *keycloak.KeycloakConfig - ServerConfig *server.ServerConfig + Bus signalbus.SignalBus + Service services.ConnectorClusterService + NamespaceService services.ConnectorNamespaceService + Keycloak coreservices.KafkaKeycloakService + ConnectorTypes services.ConnectorTypesService + Vault vault.VaultService + KeycloakConfig *keycloak.KeycloakConfig + ServerConfig *server.ServerConfig } func NewConnectorAdminHandler(handler ConnectorAdminHandler) *ConnectorAdminHandler { @@ -57,9 +57,9 @@ func (h *ConnectorAdminHandler) ListConnectorClusters(w http.ResponseWriter, r * Total: int32(paging.Total), } - for _, resource := range resources { - converted := presenters.PresentPrivateConnectorCluster(resource) - resourceList.Items = append(resourceList.Items, converted) + resourceList.Items = make([]private.ConnectorCluster, len(resources)) + for i, resource := range resources { + resourceList.Items[i] = presenters.PresentPrivateConnectorCluster(resource) } return resourceList, nil @@ -86,8 +86,8 @@ func (h *ConnectorAdminHandler) GetConnectorUpgradesByType(writer http.ResponseW return nil, serviceError } result := make([]private.ConnectorAvailableTypeUpgrade, len(upgrades)) - for i, upgrade := range upgrades { - result[i] = *presenters.PresentConnectorAvailableTypeUpgrade(&upgrade) + for j, upgrade := range upgrades { + result[j] = *presenters.PresentConnectorAvailableTypeUpgrade(&upgrade) } i = private.ConnectorAvailableTypeUpgradeList{ @@ -103,19 +103,6 @@ func (h *ConnectorAdminHandler) GetConnectorUpgradesByType(writer http.ResponseW handlers.HandleGet(writer, request, &cfg) } -func isAdmin(request *http.Request) *errors.ServiceError { - ctx := request.Context() - _, err := auth.GetClaimsFromContext(ctx) - if err != nil { - return errors.NewWithCause(errors.ErrorUnauthenticated, err, "user not authenticated") - } - - if !auth.GetIsAdminFromContext(ctx) { - return errors.NewWithCause(errors.ErrorUnauthenticated, err, "user not authorized") - } - return nil -} - func (h *ConnectorAdminHandler) UpgradeConnectorsByType(writer http.ResponseWriter, request *http.Request) { resource := make([]private.ConnectorAvailableTypeUpgrade, 0) id := mux.Vars(request)["connector_cluster_id"] @@ -196,3 +183,138 @@ func (h *ConnectorAdminHandler) UpgradeConnectorsByOperator(writer http.Response handlers.Handle(writer, request, &cfg, http.StatusNoContent) } + +func (h *ConnectorAdminHandler) GetClusterNamespaces(writer http.ResponseWriter, request *http.Request) { + id := mux.Vars(request)["connector_cluster_id"] + listArgs := coreservices.NewListArguments(request.URL.Query()) + cfg := handlers.HandlerConfig{ + Validate: []handlers.Validate{ + handlers.Validation("connector_cluster_id", &id, handlers.MinLen(1), handlers.MaxLen(maxConnectorClusterIdLength)), + }, + Action: func() (interface{}, *errors.ServiceError) { + if err := isAdmin(request); err != nil { + return nil, err + } + + namespaces, paging, err := h.NamespaceService.List(request.Context(), []string{id}, listArgs) + if err != nil { + return nil, err + } + + result := private.ConnectorNamespaceList{ + Kind: "ConnectorNamespaceList", + Page: int32(paging.Page), + Size: int32(paging.Size), + Total: int32(paging.Total), + } + + result.Items = make([]private.ConnectorNamespace, len(namespaces)) + for i, namespace := range namespaces { + result.Items[i] = presenters.PresentPrivateConnectorNamespace(namespace) + } + + return result, nil + }, + } + + handlers.HandleGet(writer, request, &cfg) +} + +func (h *ConnectorAdminHandler) GetConnectorNamespaces(writer http.ResponseWriter, request *http.Request) { + listArgs := coreservices.NewListArguments(request.URL.Query()) + cfg := handlers.HandlerConfig{ + Action: func() (interface{}, *errors.ServiceError) { + if err := isAdmin(request); err != nil { + return nil, err + } + + namespaces, paging, err := h.NamespaceService.List(request.Context(), []string{}, listArgs) + if err != nil { + return nil, err + } + + result := private.ConnectorNamespaceList{ + Kind: "ConnectorNamespaceList", + Page: int32(paging.Page), + Size: int32(paging.Size), + Total: int32(paging.Total), + } + + result.Items = make([]private.ConnectorNamespace, len(namespaces)) + for i, namespace := range namespaces { + result.Items[i] = presenters.PresentPrivateConnectorNamespace(namespace) + } + + return result, nil + }, + } + + handlers.HandleGet(writer, request, &cfg) +} + +func (h *ConnectorAdminHandler) CreateConnectorNamespace(writer http.ResponseWriter, request *http.Request) { + var resource private.ConnectorNamespaceWithTenantRequest + cfg := handlers.HandlerConfig{ + MarshalInto: &resource, + Action: func() (i interface{}, serviceError *errors.ServiceError) { + if serviceError = isAdmin(request); serviceError != nil { + return nil, serviceError + } + + ctx := request.Context() + connectorNamespace, serviceError := presenters.ConvertConnectorNamespaceWithTenantRequest(&resource) + if serviceError != nil { + return nil, serviceError + } + if connectorNamespace.TenantUser != nil { + connectorNamespace.Owner = connectorNamespace.TenantUser.ID + } else { + // NOTE: admin user is owner + claims, err := auth.GetClaimsFromContext(ctx) + if err != nil { + return nil, errors.NewWithCause(errors.ErrorUnauthenticated, err, "user not authenticated") + } + connectorNamespace.Owner = auth.GetUsernameFromClaims(claims) + } + if err := h.NamespaceService.Create(ctx, connectorNamespace); err != nil { + return nil, err + } + i = presenters.PresentPrivateConnectorNamespace(connectorNamespace) + return + }, + } + + handlers.Handle(writer, request, &cfg, http.StatusCreated) +} + +func (h *ConnectorAdminHandler) DeleteConnectorNamespace(writer http.ResponseWriter, request *http.Request) { + namespaceId := mux.Vars(request)["namespace_id"] + cfg := handlers.HandlerConfig{ + Validate: []handlers.Validate{ + handlers.Validation("namespace_id", &namespaceId, handlers.MinLen(1), handlers.MaxLen(maxConnectorNamespaceIdLength)), + }, + Action: func() (i interface{}, serviceError *errors.ServiceError) { + if serviceError = isAdmin(request); serviceError != nil { + return nil, serviceError + } + + serviceError = h.NamespaceService.Delete(namespaceId) + return nil, serviceError + }, + } + + handlers.HandleDelete(writer, request, &cfg, http.StatusNoContent) +} + +func isAdmin(request *http.Request) *errors.ServiceError { + ctx := request.Context() + _, err := auth.GetClaimsFromContext(ctx) + if err != nil { + return errors.NewWithCause(errors.ErrorUnauthenticated, err, "user not authenticated") + } + + if !auth.GetIsAdminFromContext(ctx) { + return errors.NewWithCause(errors.ErrorUnauthenticated, err, "user not authorized") + } + return nil +} diff --git a/internal/connector/internal/handlers/connector_agent.go b/internal/connector/internal/handlers/connector_agent.go index ecb9c7cad..d332ef33a 100644 --- a/internal/connector/internal/handlers/connector_agent.go +++ b/internal/connector/internal/handlers/connector_agent.go @@ -232,6 +232,7 @@ func (h *ConnectorClusterHandler) GetDeployment(w http.ResponseWriter, r *http.R handlers.Validation("deployment_id", &deploymentId, handlers.MinLen(1), handlers.MaxLen(maxConnectorIdLength)), }, Action: func() (i interface{}, serviceError *errors.ServiceError) { + var resource dbapi.ConnectorDeployment resource, err := h.Service.GetDeployment(r.Context(), deploymentId) if err != nil { return nil, err diff --git a/internal/connector/internal/handlers/connector_cluster.go b/internal/connector/internal/handlers/connector_cluster.go index 649a1ae2f..5bdd5d788 100644 --- a/internal/connector/internal/handlers/connector_cluster.go +++ b/internal/connector/internal/handlers/connector_cluster.go @@ -32,13 +32,14 @@ var ( type ConnectorClusterHandler struct { di.Inject - Bus signalbus.SignalBus - Service services.ConnectorClusterService - Keycloak coreservices.KafkaKeycloakService - ConnectorTypes services.ConnectorTypesService - Vault vault.VaultService - KeycloakConfig *keycloak.KeycloakConfig - ServerConfig *server.ServerConfig + Bus signalbus.SignalBus + Service services.ConnectorClusterService + Keycloak coreservices.KafkaKeycloakService + ConnectorTypes services.ConnectorTypesService + ConnectorNamespace services.ConnectorNamespaceService + Vault vault.VaultService + KeycloakConfig *keycloak.KeycloakConfig + ServerConfig *server.ServerConfig } func NewConnectorClusterHandler(handler ConnectorClusterHandler) *ConnectorClusterHandler { @@ -245,3 +246,36 @@ func (o *ConnectorClusterHandler) buildTokenURL(serviceAccount *api.ServiceAccou u.User = url.UserPassword(serviceAccount.ClientID, serviceAccount.ClientSecret) return u.String(), nil } + +func (h *ConnectorClusterHandler) GetNamespaces(writer http.ResponseWriter, request *http.Request) { + connectorClusterId := mux.Vars(request)["connector_cluster_id"] + cfg := &handlers.HandlerConfig{ + Validate: []handlers.Validate{ + handlers.Validation("connector_cluster_id", &connectorClusterId, handlers.MinLen(1), handlers.MaxLen(maxConnectorClusterIdLength)), + }, + Action: func() (interface{}, *errors.ServiceError) { + ctx := request.Context() + listArgs := coreservices.NewListArguments(request.URL.Query()) + resources, paging, err := h.ConnectorNamespace.List(ctx, []string{connectorClusterId} ,listArgs) + if err != nil { + return nil, err + } + + resourceList := public.ConnectorNamespaceList{ + Kind: "ConnectorNamespaceList", + Page: int32(paging.Page), + Size: int32(paging.Size), + Total: int32(paging.Total), + } + + for _, resource := range resources { + converted := presenters.PresentConnectorNamespace(resource) + resourceList.Items = append(resourceList.Items, converted) + } + + return resourceList, nil + }, + } + + handlers.HandleList(writer, request, cfg) +} diff --git a/internal/connector/internal/handlers/connector_namespace.go b/internal/connector/internal/handlers/connector_namespace.go new file mode 100644 index 000000000..1b2a3a6f4 --- /dev/null +++ b/internal/connector/internal/handlers/connector_namespace.go @@ -0,0 +1,268 @@ +package handlers + +import ( + "fmt" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/api/dbapi" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/api/public" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/presenters" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/services" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/db" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/handlers" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/services/signalbus" + "github.com/goava/di" + "net/http" + + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/auth" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/errors" + coreservices "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/services" + "github.com/gorilla/mux" +) + +var ( + maxConnectorNamespaceIdLength = 32 +) + +type ConnectorNamespaceHandler struct { + di.Inject + Bus signalbus.SignalBus + Service services.ConnectorNamespaceService +} + +func NewConnectorNamespaceHandler(handler ConnectorNamespaceHandler) *ConnectorNamespaceHandler { + return &handler +} + +func (h *ConnectorNamespaceHandler) Create(w http.ResponseWriter, r *http.Request) { + var resource public.ConnectorNamespaceRequest + cfg := &handlers.HandlerConfig{ + MarshalInto: &resource, + Validate: []handlers.Validate{ + handlers.Validation("name", &resource.Name, handlers.MinLen(1)), + handlers.Validation("cluster_id", &resource.ClusterId, handlers.MinLen(1), handlers.MaxLen(maxConnectorClusterIdLength)), + }, + Action: func() (interface{}, *errors.ServiceError) { + + // validate tenant kind + if _, ok := presenters.AllNamespaceTenantKinds[string(resource.Kind)]; !ok { + return nil, coreservices.HandleCreateError("connector namespace", + errors.MinimumFieldLengthNotReached("%s is not valid. Must be one of: [%s, %s]", "namespace_id", + public.CONNECTORNAMESPACETENANTKIND_USER, public.CONNECTORNAMESPACETENANTKIND_ORGANISATION)) + } + ctx := r.Context() + claims, err := auth.GetClaimsFromContext(ctx) + if err != nil { + return nil, errors.Unauthenticated("user not authenticated") + } + userID := auth.GetUsernameFromClaims(claims) + organisationId := auth.GetOrgIdFromClaims(claims) + + convResource, serr := presenters.ConvertConnectorNamespaceRequest(&resource, userID, organisationId) + if serr != nil { + return nil, serr + } + if err := h.checkAuthorizedClusterAccess(userID, convResource, organisationId); err != nil { + return nil, err + } + + // set tenant to org if there is one, or set it to user + if auth.GetFilterByOrganisationFromContext(ctx) { + convResource.TenantOrganisationId = &organisationId + convResource.TenantOrganisation = &dbapi.ConnectorTenantOrganisation{ + Model: db.Model{ + ID: organisationId, + }, + } + } else { + convResource.TenantUserId = &userID + convResource.TenantUser = &dbapi.ConnectorTenantUser{ + Model: db.Model{ + ID: userID, + }, + } + } + + if err := h.Service.Create(ctx, convResource); err != nil { + return nil, err + } + return presenters.PresentConnectorNamespace(convResource), nil + }, + } + + // return 201 status created + handlers.Handle(w, r, cfg, http.StatusCreated) +} + +func (h *ConnectorNamespaceHandler) checkAuthorizedClusterAccess(userID string, convResource *dbapi.ConnectorNamespace, + organisationId string) *errors.ServiceError { + + var ownerClusterIds []string + if err := h.Service.GetOwnerClusterIds(userID, &ownerClusterIds); err != nil { + return err + } + authorized := false + for _, id := range ownerClusterIds { + if id == convResource.ClusterId { + authorized = true + break + } + } + if !authorized { + var orgClusterIds []string + if err := h.Service.GetOrgClusterIds(userID, organisationId, &orgClusterIds); err != nil { + return err + } + for _, id := range orgClusterIds { + if id == convResource.ClusterId { + authorized = true + break + } + } + if !authorized { + return errors.Unauthorized( + "user %v is not authorized to create namespace in cluster %v", + userID, convResource.ClusterId) + } + } + + return nil +} + +func (h *ConnectorNamespaceHandler) CreateEvaluation(w http.ResponseWriter, r *http.Request) { + var resource public.ConnectorNamespaceEvalRequest + cfg := &handlers.HandlerConfig{ + MarshalInto: &resource, + Validate: []handlers.Validate{ + handlers.Validation("name", &resource.Name, handlers.MinLen(1)), + }, + Action: func() (interface{}, *errors.ServiceError) { + + claims, err := auth.GetClaimsFromContext(r.Context()) + if err != nil { + return nil, errors.Unauthenticated("user not authenticated") + } + userId := auth.GetUsernameFromClaims(claims) + + convResource := presenters.ConvertConnectorNamespaceEvalRequest(&resource, userId) + if err := h.Service.SetEvalClusterId(convResource); err != nil { + return nil, err + } + + if err := h.Service.Create(r.Context(), convResource); err != nil { + return nil, err + } + return presenters.PresentConnectorNamespace(convResource), nil + }, + } + + // return 201 status created + handlers.Handle(w, r, cfg, http.StatusCreated) +} + +func (h *ConnectorNamespaceHandler) Get(w http.ResponseWriter, r *http.Request) { + connectorNamespaceId := mux.Vars(r)["connector_namespace_id"] + cfg := &handlers.HandlerConfig{ + Validate: []handlers.Validate{ + handlers.Validation("connector_namespace_id", &connectorNamespaceId, + handlers.MinLen(1), handlers.MaxLen(maxConnectorNamespaceIdLength)), + }, + Action: func() (i interface{}, serviceError *errors.ServiceError) { + resource, err := h.Service.Get(r.Context(), connectorNamespaceId) + if err != nil { + return nil, err + } + return presenters.PresentConnectorNamespace(resource), nil + }, + } + handlers.HandleGet(w, r, cfg) +} + +func (h *ConnectorNamespaceHandler) Update(w http.ResponseWriter, r *http.Request) { + var resource public.ConnectorNamespacePatchRequest + + connectorNamespaceId := mux.Vars(r)["connector_namespace_id"] + cfg := &handlers.HandlerConfig{ + MarshalInto: &resource, + Validate: []handlers.Validate{ + handlers.Validation("connector_namespace_id", &connectorNamespaceId, + handlers.MinLen(1), handlers.MaxLen(maxConnectorNamespaceIdLength)), + }, + Action: func() (i interface{}, serviceError *errors.ServiceError) { + existing, err := h.Service.Get(r.Context(), connectorNamespaceId) + if err != nil { + return nil, err + } + + // Copy over the fields that support being updated... + if len(resource.Name) != 0 { + existing.Name = resource.Name + } else { + // name is the only updatable field for now + return nil, nil + } + + return nil, h.Service.Update(r.Context(), existing) + }, + } + handlers.Handle(w, r, cfg, http.StatusNoContent) +} + +func (h *ConnectorNamespaceHandler) Delete(w http.ResponseWriter, r *http.Request) { + connectorNamespaceId := mux.Vars(r)["connector_namespace_id"] + cfg := &handlers.HandlerConfig{ + Validate: []handlers.Validate{ + handlers.Validation("connector_namespace_id", &connectorNamespaceId, + handlers.MinLen(1), handlers.MaxLen(maxConnectorNamespaceIdLength)), + }, + Action: func() (i interface{}, serviceError *errors.ServiceError) { + err := h.Service.Delete(connectorNamespaceId) + return nil, err + }, + } + handlers.HandleDelete(w, r, cfg, http.StatusNoContent) +} + +func (h *ConnectorNamespaceHandler) List(w http.ResponseWriter, r *http.Request) { + cfg := &handlers.HandlerConfig{ + Action: func() (i interface{}, serviceError *errors.ServiceError) { + + ctx := r.Context() + listArgs := coreservices.NewListArguments(r.URL.Query()) + + claims, err := auth.GetClaimsFromContext(r.Context()) + if err != nil { + return nil, errors.Unauthenticated("user not authenticated") + } + userId := auth.GetUsernameFromClaims(claims) + organisationId := auth.GetOrgIdFromClaims(claims) + + userQuery := fmt.Sprintf("owner = '%s' or tenant_user_id = '%s' or tenant_organisation_id = '%s'", + userId, userId, organisationId) + if len(listArgs.Search) == 0 { + listArgs.Search = userQuery + } else { + listArgs.Search = fmt.Sprintf("%s AND %s", listArgs.Search, userQuery) + } + + resources, paging, serviceError := h.Service.List(ctx, nil, listArgs) + if serviceError != nil { + return nil, serviceError + } + + items := make([]public.ConnectorNamespace, len(resources)) + for j, resource := range resources { + items[j] = presenters.PresentConnectorNamespace(resource) + } + resourceList := public.ConnectorNamespaceList{ + Kind: "ConnectorNamespaceList", + Page: int32(paging.Page), + Size: int32(paging.Size), + Total: int32(paging.Total), + Items: items, + } + + return resourceList, nil + }, + } + + handlers.HandleList(w, r, cfg) +} diff --git a/internal/connector/internal/handlers/connector_validation.go b/internal/connector/internal/handlers/connector_validation.go index 3572ffed1..9e9fffe72 100644 --- a/internal/connector/internal/handlers/connector_validation.go +++ b/internal/connector/internal/handlers/connector_validation.go @@ -14,6 +14,7 @@ import ( func validateConnectorRequest(connectorTypesService services.ConnectorTypesService, resource *public.ConnectorRequest, tid string) handlers.Validate { return connectorValidationFunction(connectorTypesService, &resource.ConnectorTypeId, &resource.Channel, &resource.Connector, tid) } + func validateConnector(connectorTypesService services.ConnectorTypesService, resource *public.Connector, tid string) handlers.Validate { return connectorValidationFunction(connectorTypesService, &resource.ConnectorTypeId, &resource.Channel, &resource.Connector, tid) } diff --git a/internal/connector/internal/handlers/connectors.go b/internal/connector/internal/handlers/connectors.go index bd89a9eff..3868b851f 100644 --- a/internal/connector/internal/handlers/connectors.go +++ b/internal/connector/internal/handlers/connectors.go @@ -35,13 +35,16 @@ var ( type ConnectorsHandler struct { connectorsService services.ConnectorsService connectorTypesService services.ConnectorTypesService + namespaceService services.ConnectorNamespaceService vaultService vault.VaultService } -func NewConnectorsHandler(connectorsService services.ConnectorsService, connectorTypesService services.ConnectorTypesService, vaultService vault.VaultService) *ConnectorsHandler { +func NewConnectorsHandler(connectorsService services.ConnectorsService, connectorTypesService services.ConnectorTypesService, + namespaceService services.ConnectorNamespaceService, vaultService vault.VaultService) *ConnectorsHandler { return &ConnectorsHandler{ connectorsService: connectorsService, connectorTypesService: connectorTypesService, + namespaceService: namespaceService, vaultService: vaultService, } } @@ -63,8 +66,9 @@ func (h ConnectorsHandler) Create(w http.ResponseWriter, r *http.Request) { handlers.Validation("service_account.client_secret", &resource.ServiceAccount.ClientSecret, handlers.MinLen(1)), handlers.Validation("connector_type_id", &resource.ConnectorTypeId, handlers.MinLen(1), handlers.MaxLen(maxConnectorTypeIdLength)), handlers.Validation("desired_state", (*string)(&resource.DesiredState), handlers.WithDefault("ready"), handlers.IsOneOf(dbapi.ValidDesiredStates...)), - handlers.Validation("deployment_location.kind", &resource.DeploymentLocation.Kind, handlers.IsOneOf("addon")), validateConnectorRequest(h.connectorTypesService, &resource, tid), + handlers.Validation("namespace_id", &resource.NamespaceId, + handlers.MaxLen(maxConnectorNamespaceIdLength), validateNamespaceID(h.namespaceService, r.Context())), }, Action: func() (interface{}, *errors.ServiceError) { @@ -179,7 +183,7 @@ func (h ConnectorsHandler) Patch(w http.ResponseWriter, r *http.Request) { resource.Kafka = patch.Kafka resource.ServiceAccount = patch.ServiceAccount resource.SchemaRegistry = patch.SchemaRegistry - resource.DeploymentLocation = patch.DeploymentLocation + resource.NamespaceId = patch.NamespaceId // If we didn't change anything, then just skip the update... originalResource, _ := presenters.PresentConnector(dbresource) @@ -193,9 +197,9 @@ func (h ConnectorsHandler) Patch(w http.ResponseWriter, r *http.Request) { handlers.Validation("connector_type_id", &resource.ConnectorTypeId, handlers.MinLen(1), handlers.MaxLen(maxKafkaNameLength)), // handlers.Validation("kafka_id", &resource.Metadata.KafkaId, handlers.MinLen(1), handlers.MaxLen(maxKafkaNameLength)), handlers.Validation("service_account.client_id", &resource.ServiceAccount.ClientId, handlers.MinLen(1)), - handlers.Validation("deployment_location.kind", &resource.DeploymentLocation.Kind, handlers.IsOneOf("addon")), handlers.Validation("desired_state", (*string)(&resource.DesiredState), handlers.IsOneOf(dbapi.ValidDesiredStates...)), validateConnector(h.connectorTypesService, &resource, connectorTypeId), + handlers.Validation("namespace_id", &resource.NamespaceId, handlers.MaxLen(maxConnectorNamespaceIdLength)), } for _, v := range validates { diff --git a/internal/connector/internal/handlers/namespace_validation.go b/internal/connector/internal/handlers/namespace_validation.go new file mode 100644 index 000000000..7d8bbf728 --- /dev/null +++ b/internal/connector/internal/handlers/namespace_validation.go @@ -0,0 +1,20 @@ +package handlers + +import ( + "context" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/services" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/errors" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/handlers" +) + +func validateNamespaceID(namespaceService services.ConnectorNamespaceService, ctx context.Context) handlers.ValidateOption { + + return func(name string, value *string) *errors.ServiceError { + if value != nil && *value != "" { + if _, err := namespaceService.Get(ctx, *value); err != nil { + return errors.BadRequest("%s is not valid: %s", name, err) + } + } + return nil + } +} diff --git a/internal/connector/internal/migrations/202202070000_add_connector_namespace_tables.go b/internal/connector/internal/migrations/202202070000_add_connector_namespace_tables.go new file mode 100644 index 000000000..40b5b170a --- /dev/null +++ b/internal/connector/internal/migrations/202202070000_add_connector_namespace_tables.go @@ -0,0 +1,91 @@ +package migrations + +// Migrations should NEVER use types from other packages. Types can change +// and then migrations run on a _new_ database will fail or behave unexpectedly. +// Instead of importing types, always re-create the type in the migration, as +// is done here, even though the same type is defined in pkg/api + +import ( + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/db" + "github.com/go-gormigrate/gormigrate/v2" + "time" +) + +func addConnectorNamespaceTables(migrationId string) *gormigrate.Migration { + + type ConnectorTenantUser struct { + db.Model // user id in Id, required for references and data consistency + } + + type ConnectorTenantOrganisation struct { + db.Model // org id in Id, required for references and data consistency + } + + type ConnectorNamespaceAnnotation struct { + NamespaceId string `gorm:"primaryKey;index"` + Name string `gorm:"primaryKey;not null"` + Value string `gorm:"not null"` + } + + type ConnectorNamespace struct { + db.Model + Name string `gorm:"not null;uniqueIndex:idx_connector_namespaces_name_cluster_id"` + ClusterId string `gorm:"not null;uniqueIndex:idx_connector_namespaces_name_cluster_id;index"` + + Owner string `gorm:"not null;index"` + Version int64 `gorm:"type:bigserial;index"` + Expiration *time.Time + + // metadata + Annotations []ConnectorNamespaceAnnotation `gorm:"foreignKey:NamespaceId;references:ID"` + + // tenant, only one of the below fields can be not null + TenantUserId *string `gorm:"index:connector_namespaces_user_organisation_idx;index:,where:tenant_user_id is not null"` + TenantOrganisationId *string `gorm:"index:connector_namespaces_user_organisation_idx;index:,where:tenant_organisation_id is not null"` + TenantUser *ConnectorTenantUser `gorm:"foreignKey:TenantUserId"` + TenantOrganisation *ConnectorTenantOrganisation `gorm:"foreignKey:TenantOrganisationId"` + } + + return db.CreateMigrationFromActions(migrationId, + db.CreateTableAction(&ConnectorTenantUser{}), + db.CreateTableAction(&ConnectorTenantOrganisation{}), + db.CreateTableAction(&ConnectorNamespaceAnnotation{}), + db.CreateTableAction(&ConnectorNamespace{}), + + // connector namespace version + db.ExecAction(` + CREATE OR REPLACE FUNCTION connector_namespaces_version_trigger() RETURNS TRIGGER LANGUAGE plpgsql AS ' + BEGIN + NEW.version := nextval(''connector_namespaces_version_seq''); + RETURN NEW; + END;' + `, ` + DROP FUNCTION IF EXISTS connector_namespaces_version_trigger + `), + db.ExecAction(`DROP TRIGGER IF EXISTS connector_namespaces_version_trigger ON connector_namespaces`, ``), + db.ExecAction(` + CREATE TRIGGER connector_namespaces_version_trigger BEFORE INSERT OR UPDATE ON connector_namespaces + FOR EACH ROW EXECUTE PROCEDURE connector_namespaces_version_trigger(); + `, ` + DROP TRIGGER IF EXISTS connector_namespaces_version_trigger ON connector_namespaces + `), + + // foreign key relationship between namespaces and clusters + db.ExecAction( + "ALTER TABLE connector_namespaces DROP CONSTRAINT IF EXISTS fk_connector_namespaces_cluster_id", + ""), + db.ExecAction( + "ALTER TABLE connector_namespaces ADD CONSTRAINT fk_connector_namespaces_cluster_id "+ + "FOREIGN KEY (cluster_id) REFERENCES connector_clusters(id)", + "ALTER TABLE connector_namespaces DROP CONSTRAINT IF EXISTS fk_connector_namespaces_cluster_id"), + + // only one of tenantUser or tenantOrg in ConnectorNamespace can be not null + db.ExecAction( + "ALTER TABLE connector_namespaces DROP CONSTRAINT IF EXISTS connector_namespaces_tenant_check", + ""), + db.ExecAction( + "ALTER TABLE connector_namespaces ADD CONSTRAINT connector_namespaces_tenant_check "+ + "CHECK (((tenant_user_id is not null)::integer + (tenant_organisation_id is not null)::integer) = 1)", + "ALTER TABLE connector_namespaces DROP CONSTRAINT IF EXISTS connector_namespaces_tenant_check"), + ) +} diff --git a/internal/connector/internal/migrations/202202220000_add_connector_namespace_deployment.go b/internal/connector/internal/migrations/202202220000_add_connector_namespace_deployment.go new file mode 100644 index 000000000..a1220f971 --- /dev/null +++ b/internal/connector/internal/migrations/202202220000_add_connector_namespace_deployment.go @@ -0,0 +1,56 @@ +package migrations + +// Migrations should NEVER use types from other packages. Types can change +// and then migrations run on a _new_ database will fail or behave unexpectedly. +// Instead of importing types, always re-create the type in the migration, as +// is done here, even though the same type is defined in pkg/api + +import ( + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/db" + "github.com/go-gormigrate/gormigrate/v2" +) + +func addConnectorNamespaceDeployment(migrationId string) *gormigrate.Migration { + + return db.CreateMigrationFromActions(migrationId, + // replace column cluster_id with namespace_id in connectors + db.ExecAction(`ALTER TABLE connectors DROP COLUMN addon_cluster_id`, + `ALTER TABLE connectors ADD addon_cluster_id text`), + db.ExecAction(`ALTER TABLE connectors ADD namespace_id text`, + `ALTER TABLE connectors DROP COLUMN namespace_id`), + db.ExecAction( + "ALTER TABLE connectors ADD CONSTRAINT fk_connectors_namespace_id "+ + "FOREIGN KEY (namespace_id) REFERENCES connector_namespaces(id)", + "ALTER TABLE connectors DROP CONSTRAINT IF EXISTS fk_connectors_namespace_id"), + db.ExecAction( + "CREATE INDEX ON connectors(namespace_id)", + "DROP INDEX IF EXISTS connectors_namespace_id_idx"), + + // replace column cluster_id with namespace_id in connector_statuses + db.ExecAction(`ALTER TABLE connector_statuses DROP COLUMN cluster_id`, + `ALTER TABLE connector_statuses ADD cluster_id text`), + db.ExecAction(`ALTER TABLE connector_statuses ADD namespace_id text`, + `ALTER TABLE connector_statuses DROP COLUMN namespace_id`), + db.ExecAction( + "ALTER TABLE connector_statuses ADD CONSTRAINT fk_connector_statuses_namespace_id "+ + "FOREIGN KEY (namespace_id) REFERENCES connector_namespaces(id)", + "ALTER TABLE connector_statuses DROP CONSTRAINT IF EXISTS fk_connector_statuses_namespace_id"), + db.ExecAction( + "CREATE INDEX ON connector_statuses(namespace_id)", + "DROP INDEX IF EXISTS connector_statuses_namespace_id_idx"), + + // add column namespace_id in connector_deployments, keeping cluster_id for agent API + db.ExecAction(`ALTER TABLE connector_deployments ADD namespace_id text not null`, + `ALTER TABLE connector_deployments DROP COLUMN namespace_id`), + db.ExecAction( + "ALTER TABLE connector_deployments DROP CONSTRAINT IF EXISTS fk_connector_deployments_namespace_id", + ""), + db.ExecAction( + "ALTER TABLE connector_deployments ADD CONSTRAINT fk_connector_deployments_namespace_id "+ + "FOREIGN KEY (namespace_id) REFERENCES connector_namespaces(id)", + "ALTER TABLE connector_deployments DROP CONSTRAINT IF EXISTS fk_connector_deployments_namespace_id"), + db.ExecAction( + "CREATE INDEX ON connector_deployments(namespace_id)", + "DROP INDEX IF EXISTS connector_deployments_namespace_id_idx"), + ) +} diff --git a/internal/connector/internal/migrations/202202280000_add_deleted_at_index.go b/internal/connector/internal/migrations/202202280000_add_deleted_at_index.go new file mode 100644 index 000000000..875656ee6 --- /dev/null +++ b/internal/connector/internal/migrations/202202280000_add_deleted_at_index.go @@ -0,0 +1,28 @@ +package migrations + +// Migrations should NEVER use types from other packages. Types can change +// and then migrations run on a _new_ database will fail or behave unexpectedly. +// Instead of importing types, always re-create the type in the migration, as +// is done here, even though the same type is defined in pkg/api + +import ( + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/db" + "github.com/go-gormigrate/gormigrate/v2" +) + +func addDeletedAtIndex(migrationId string) *gormigrate.Migration { + + // add missing deleted_at index caused by incorrectly using api.Meta instead of db.Model in dbapi.* structs + return db.CreateMigrationFromActions(migrationId, + db.ExecAction(`CREATE INDEX on connectors(deleted_at)`, + `DROP INDEX IF EXISTS connectors_deleted_at_idx`), + db.ExecAction(`CREATE INDEX on connector_statuses(deleted_at)`, + `DROP INDEX IF EXISTS connector_statuses_deleted_at_idx`), + db.ExecAction(`CREATE INDEX on connector_channels(deleted_at)`, + `DROP INDEX IF EXISTS connector_channels_deleted_at_idx`), + db.ExecAction(`CREATE INDEX on connector_types(deleted_at)`, + `DROP INDEX IF EXISTS connector_types_deleted_at_idx`), + db.ExecAction(`CREATE INDEX on connector_clusters(deleted_at)`, + `DROP INDEX IF EXISTS connector_clusters_deleted_at_idx`), + ) +} diff --git a/internal/connector/internal/migrations/202203020000_delete_connector_target_kind.go b/internal/connector/internal/migrations/202203020000_delete_connector_target_kind.go new file mode 100644 index 000000000..8053fc43c --- /dev/null +++ b/internal/connector/internal/migrations/202203020000_delete_connector_target_kind.go @@ -0,0 +1,19 @@ +package migrations + +// Migrations should NEVER use types from other packages. Types can change +// and then migrations run on a _new_ database will fail or behave unexpectedly. +// Instead of importing types, always re-create the type in the migration, as +// is done here, even though the same type is defined in pkg/api + +import ( + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/db" + "github.com/go-gormigrate/gormigrate/v2" +) + +func deleteConnectorTargetKind(migrationId string) *gormigrate.Migration { + + return db.CreateMigrationFromActions(migrationId, + db.ExecAction(`ALTER TABLE connectors DROP COLUMN target_kind`, + `ALTER TABLE connectors ADD COLUMN target_kind text`), + ) +} diff --git a/internal/connector/internal/migrations/202203030000_add_connector_namespace_lease.go b/internal/connector/internal/migrations/202203030000_add_connector_namespace_lease.go new file mode 100644 index 000000000..645257e02 --- /dev/null +++ b/internal/connector/internal/migrations/202203030000_add_connector_namespace_lease.go @@ -0,0 +1,45 @@ +package migrations + +// Migrations should NEVER use types from other packages. Types can change +// and then migrations run on a _new_ database will fail or behave unexpectedly. +// Instead of importing types, always re-create the type in the migration, as +// is done here, even though the same type is defined in pkg/api + +import ( + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/api" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/db" + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" + "time" +) + +func addConnectorNamespaceLease(migrationId string) *gormigrate.Migration { + + type LeaderLease struct { + db.Model + Leader string + LeaseType string + Expires *time.Time + } + + // add missing deleted_at index caused by incorrectly using api.Meta instead of db.Model in dbapi.* structs + return db.CreateMigrationFromActions(migrationId, + db.FuncAction(func(tx *gorm.DB) error { + // We don't want to delete the leader lease table on rollback because it's shared with the kas-fleet-manager + // so we just create it here if it does not exist yet.. but we don't drop it on rollback. + err := tx.Migrator().AutoMigrate(&LeaderLease{}) + if err != nil { + return err + } + now := time.Now().Add(-time.Minute) //set to a expired time + return tx.Create(&api.LeaderLease{ + Expires: &now, + LeaseType: "connector_namespace", + }).Error + }, func(tx *gorm.DB) error { + // The leader lease table may have already been dropped, by the kafka migration rollback, ignore error + _ = tx.Where("lease_type = ?", "connector_namespace").Delete(&api.LeaderLease{}) + return nil + }), + ) +} diff --git a/internal/connector/internal/migrations/migrations.go b/internal/connector/internal/migrations/migrations.go index 7631faee9..9fdae8748 100644 --- a/internal/connector/internal/migrations/migrations.go +++ b/internal/connector/internal/migrations/migrations.go @@ -28,6 +28,11 @@ var migrations = []*gormigrate.Migration{ connectorRefactor("202201310000"), addConnectorTypeCapabilitiesTable("202202040000"), addClientId("202202030000"), + addConnectorNamespaceTables("202202070000"), + addConnectorNamespaceDeployment("202202220000"), + addDeletedAtIndex("202202280000"), + deleteConnectorTargetKind("202203020000"), + addConnectorNamespaceLease("202203030000"), } func New(dbConfig *db.DatabaseConfig) (*db.Migration, func(), error) { diff --git a/internal/connector/internal/presenters/connector.go b/internal/connector/internal/presenters/connector.go index 18fff8cab..f772f2442 100644 --- a/internal/connector/internal/presenters/connector.go +++ b/internal/connector/internal/presenters/connector.go @@ -16,12 +16,15 @@ func ConvertConnector(from public.Connector) (*dbapi.Connector, *errors.ServiceE return nil, errors.BadRequest("invalid connector spec: %v", err) } + var namespaceId *string + if from.NamespaceId != "" { + namespaceId = &from.NamespaceId + } return &dbapi.Connector{ Meta: api.Meta{ ID: from.Id, }, - TargetKind: from.DeploymentLocation.Kind, - AddonClusterId: from.DeploymentLocation.ClusterId, + NamespaceId: namespaceId, Name: from.Name, Owner: from.Owner, Version: from.ResourceVersion, @@ -54,6 +57,11 @@ func PresentConnector(from *dbapi.Connector) (public.Connector, *errors.ServiceE return public.Connector{}, errors.BadRequest("invalid connector spec: %v", err) } + namespaceId := "" + if from.NamespaceId != nil { + namespaceId = *from.NamespaceId + } + reference := PresentReference(from.ID, from) return public.Connector{ Id: reference.Id, @@ -65,11 +73,7 @@ func PresentConnector(from *dbapi.Connector) (public.Connector, *errors.ServiceE CreatedAt: from.CreatedAt, ModifiedAt: from.UpdatedAt, ResourceVersion: from.Version, - - DeploymentLocation: public.DeploymentLocation{ - Kind: from.TargetKind, - ClusterId: from.AddonClusterId, - }, + NamespaceId: namespaceId, ConnectorTypeId: from.ConnectorTypeId, Connector: spec, Status: public.ConnectorStatusStatus{ diff --git a/internal/connector/internal/presenters/connector_available_operator_upgrade.go b/internal/connector/internal/presenters/connector_available_operator_upgrade.go index 37a82ba44..ddf7027b4 100644 --- a/internal/connector/internal/presenters/connector_available_operator_upgrade.go +++ b/internal/connector/internal/presenters/connector_available_operator_upgrade.go @@ -9,6 +9,7 @@ func PresentConnectorAvailableOperatorUpgrade(req *dbapi.ConnectorDeploymentOper return &private.ConnectorAvailableOperatorUpgrade{ ConnectorId: req.ConnectorID, ConnectorTypeId: req.ConnectorTypeId, + NamespaceId: req.NamespaceID, Channel: req.Channel, Operator: private.ConnectorAvailableOperatorUpgradeOperator{ AssignedId: req.Operator.Assigned.Id, @@ -21,6 +22,7 @@ func ConvertConnectorAvailableOperatorUpgrade(req *private.ConnectorAvailableOpe return &dbapi.ConnectorDeploymentOperatorUpgrade{ ConnectorID: req.ConnectorId, ConnectorTypeId: req.ConnectorTypeId, + NamespaceID: req.NamespaceId, Channel: req.Channel, Operator: &dbapi.ConnectorOperatorUpgrade{ Assigned: dbapi.ConnectorOperator{ diff --git a/internal/connector/internal/presenters/connector_available_type_upgrade.go b/internal/connector/internal/presenters/connector_available_type_upgrade.go index 64f9f106f..b39d7f5db 100644 --- a/internal/connector/internal/presenters/connector_available_type_upgrade.go +++ b/internal/connector/internal/presenters/connector_available_type_upgrade.go @@ -9,6 +9,7 @@ func PresentConnectorAvailableTypeUpgrade(req *dbapi.ConnectorDeploymentTypeUpgr return &private.ConnectorAvailableTypeUpgrade{ ConnectorId: req.ConnectorID, ConnectorTypeId: req.ConnectorTypeId, + NamespaceId: req.NamespaceID, Channel: req.Channel, ShardMetadata: private.ConnectorAvailableTypeUpgradeShardMetadata{ AssignedId: req.ShardMetadata.AssignedId, @@ -21,6 +22,7 @@ func ConvertConnectorAvailableTypeUpgrade(req *private.ConnectorAvailableTypeUpg return &dbapi.ConnectorDeploymentTypeUpgrade{ ConnectorID: req.ConnectorId, ConnectorTypeId: req.ConnectorTypeId, + NamespaceID: req.NamespaceId, Channel: req.Channel, ShardMetadata: &dbapi.ConnectorTypeUpgrade{ AssignedId: req.ShardMetadata.AssignedId, diff --git a/internal/connector/internal/presenters/connector_deployment.go b/internal/connector/internal/presenters/connector_deployment.go index a0fb1d741..d6b7e1ab3 100644 --- a/internal/connector/internal/presenters/connector_deployment.go +++ b/internal/connector/internal/presenters/connector_deployment.go @@ -38,6 +38,8 @@ func PresentConnectorDeployment(from dbapi.ConnectorDeployment) (private.Connect Spec: private.ConnectorDeploymentSpec{ ConnectorId: from.ConnectorID, OperatorId: from.OperatorID, + NamespaceId: from.NamespaceID, + NamespaceName: from.NamespaceName, ConnectorResourceVersion: from.ConnectorVersion, }, Status: private.ConnectorDeploymentStatus{ diff --git a/internal/connector/internal/presenters/connector_namespace.go b/internal/connector/internal/presenters/connector_namespace.go new file mode 100644 index 000000000..454381750 --- /dev/null +++ b/internal/connector/internal/presenters/connector_namespace.go @@ -0,0 +1,200 @@ +package presenters + +import ( + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/api/admin/private" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/api/dbapi" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/api/public" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/api" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/db" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/errors" + "k8s.io/apimachinery/pkg/util/json" + "strings" + "time" +) + +var AllNamespaceTenantKinds = map[string]public.ConnectorNamespaceTenantKind{ + string(public.CONNECTORNAMESPACETENANTKIND_USER): public.CONNECTORNAMESPACETENANTKIND_USER, + string(public.CONNECTORNAMESPACETENANTKIND_ORGANISATION): public.CONNECTORNAMESPACETENANTKIND_ORGANISATION, +} + +func ConvertConnectorNamespaceRequest(namespaceRequest *public.ConnectorNamespaceRequest, + userID string, organisationID string) (*dbapi.ConnectorNamespace, *errors.ServiceError) { + result := &dbapi.ConnectorNamespace{ + Model: db.Model{ + ID: api.NewID(), + }, + Name: namespaceRequest.Name, + ClusterId: namespaceRequest.ClusterId, + Owner: userID, + } + result.Annotations = make([]dbapi.ConnectorNamespaceAnnotation, len(namespaceRequest.Annotations)) + for i, annotation := range namespaceRequest.Annotations { + result.Annotations[i].NamespaceId = result.ID + result.Annotations[i].Name = annotation.Name + result.Annotations[i].Value = annotation.Value + } + switch namespaceRequest.Kind { + case public.CONNECTORNAMESPACETENANTKIND_USER: + result.TenantUserId = &userID + result.TenantUser = &dbapi.ConnectorTenantUser{ + Model: db.Model{ + ID: userID, + }, + } + case public.CONNECTORNAMESPACETENANTKIND_ORGANISATION: + if organisationID == "" { + return nil, errors.BadRequest("missing organization for tenant organisation namespace") + } + result.TenantOrganisationId = &organisationID + result.TenantOrganisation = &dbapi.ConnectorTenantOrganisation{ + Model: db.Model{ + ID: organisationID, + }, + } + default: + return nil, errors.BadRequest("invalid tenant kind: %s", namespaceRequest.Kind) + } + + return result, nil +} + +func ConvertConnectorNamespaceEvalRequest(namespaceRequest *public.ConnectorNamespaceEvalRequest, userID string) *dbapi.ConnectorNamespace { + result := &dbapi.ConnectorNamespace{ + Model: db.Model{ + ID: api.NewID(), + }, + Name: namespaceRequest.Name, + Owner: userID, + } + result.Annotations = make([]dbapi.ConnectorNamespaceAnnotation, len(namespaceRequest.Annotations)) + for i, annotation := range namespaceRequest.Annotations { + result.Annotations[i].NamespaceId = result.ID + result.Annotations[i].Name = annotation.Name + result.Annotations[i].Value = annotation.Value + } + result.TenantUserId = &result.Owner + result.TenantUser = &dbapi.ConnectorTenantUser{ + Model: db.Model{ + ID: userID, + }, + } + + return result +} + +func ConvertConnectorNamespaceWithTenantRequest(namespaceRequest *private.ConnectorNamespaceWithTenantRequest) (*dbapi.ConnectorNamespace, *errors.ServiceError) { + result := &dbapi.ConnectorNamespace{ + Model: db.Model{ + ID: api.NewID(), + }, + Name: namespaceRequest.Name, + ClusterId: namespaceRequest.ClusterId, + } + switch namespaceRequest.Tenant.Kind { + case private.USER: + result.TenantUserId = &namespaceRequest.Tenant.Id + result.TenantUser = &dbapi.ConnectorTenantUser{ + Model: db.Model{ + ID: *result.TenantUserId, + }, + } + case private.ORGANISATION: + result.TenantOrganisationId = &namespaceRequest.Tenant.Id + result.TenantOrganisation = &dbapi.ConnectorTenantOrganisation{ + Model: db.Model{ + ID: *result.TenantOrganisationId, + }, + } + default: + return nil, errors.BadRequest("invalid kind %s", namespaceRequest.Tenant.Kind) + } + result.Annotations = make([]dbapi.ConnectorNamespaceAnnotation, len(namespaceRequest.Annotations)) + for i, annotation := range namespaceRequest.Annotations { + result.Annotations[i].NamespaceId = result.ID + result.Annotations[i].Name = annotation.Name + result.Annotations[i].Value = annotation.Value + } + + return result, nil +} + +func PresentConnectorNamespace(namespace *dbapi.ConnectorNamespace) public.ConnectorNamespace { + annotations := make([]public.ConnectorNamespaceRequestMetaAnnotations, len(namespace.Annotations)) + for i, anno := range namespace.Annotations { + annotations[i].Name = anno.Name + annotations[i].Value = anno.Value + } + + reference := PresentReference(namespace.ID, namespace) + result := public.ConnectorNamespace{ + Id: reference.Id, + Kind: reference.Kind, + Href: reference.Href, + + CreatedAt: namespace.CreatedAt, + ModifiedAt: namespace.UpdatedAt, + Owner: namespace.Owner, + Version: namespace.Version, + + Name: namespace.Name, + ClusterId: namespace.ClusterId, + Tenant: public.ConnectorNamespaceTenant{}, + Annotations: annotations, + } + if namespace.TenantUser != nil { + result.Tenant.Kind = public.CONNECTORNAMESPACETENANTKIND_USER + result.Tenant.Id = namespace.TenantUser.ID + } + if namespace.TenantOrganisation != nil { + result.Tenant.Kind = public.CONNECTORNAMESPACETENANTKIND_ORGANISATION + result.Tenant.Id = namespace.TenantOrganisation.ID + } + if namespace.Expiration != nil { + result.Expiration = getTimestamp(*namespace.Expiration) + } + + return result +} + +func PresentPrivateConnectorNamespace(namespace *dbapi.ConnectorNamespace) private.ConnectorNamespace { + annotations := make([]private.ConnectorNamespaceRequestMetaAnnotations, len(namespace.Annotations)) + for i, anno := range namespace.Annotations { + annotations[i].Name = anno.Name + annotations[i].Value = anno.Value + } + + reference := PresentReference(namespace.ID, namespace) + result := private.ConnectorNamespace{ + Id: reference.Id, + Kind: reference.Kind, + Href: reference.Href, + + CreatedAt: namespace.CreatedAt, + ModifiedAt: namespace.UpdatedAt, + Owner: namespace.Owner, + Version: namespace.Version, + + Name: namespace.Name, + ClusterId: namespace.ClusterId, + Tenant: private.ConnectorNamespaceTenant{}, + Annotations: annotations, + } + if namespace.TenantUser != nil { + result.Tenant.Kind = private.USER + result.Tenant.Id = namespace.TenantUser.ID + } + if namespace.TenantOrganisationId != nil { + result.Tenant.Kind = private.ORGANISATION + result.Tenant.Id = namespace.TenantOrganisation.ID + } + if namespace.Expiration != nil { + result.Expiration = getTimestamp(*namespace.Expiration) + } + + return result +} + +func getTimestamp(expiration time.Time) string { + bytes, _ := json.Marshal(expiration) + return strings.Trim(string(bytes), "\"") +} diff --git a/internal/connector/internal/presenters/connector_request.go b/internal/connector/internal/presenters/connector_request.go index fada7c723..dbd1b8526 100644 --- a/internal/connector/internal/presenters/connector_request.go +++ b/internal/connector/internal/presenters/connector_request.go @@ -15,9 +15,12 @@ func ConvertConnectorRequest(from public.ConnectorRequest) (*dbapi.Connector, *e return nil, errors.BadRequest("invalid connector spec: %v", err) } + namespaceId := &from.NamespaceId + if *namespaceId == "" { + namespaceId = nil + } return &dbapi.Connector{ - TargetKind: from.DeploymentLocation.Kind, - AddonClusterId: from.DeploymentLocation.ClusterId, + NamespaceId: namespaceId, Name: from.Name, ConnectorTypeId: from.ConnectorTypeId, ConnectorSpec: spec, diff --git a/internal/connector/internal/presenters/object_reference.go b/internal/connector/internal/presenters/object_reference.go index 174ac4038..e759f9a22 100644 --- a/internal/connector/internal/presenters/object_reference.go +++ b/internal/connector/internal/presenters/object_reference.go @@ -15,6 +15,8 @@ const ( KindConnectorCluster = "ConnectorCluster" // KindConnectorDeployment is a string identifier for the type dbapi.ConnectorDeployment KindConnectorDeployment = "ConnectorDeployment" + // KindConnectorNamespace is a string identifier for the type dbapi.ConnectorNamespace + KindConnectorNamespace = "ConnectorNamespace" // KindConnectorType is a string identifier for the type dbapi.ConnectorType KindConnectorType = "ConnectorType" // KindError is a string identifier for the type api.ServiceError @@ -33,6 +35,8 @@ func objectKind(i interface{}) string { return KindConnectorCluster case dbapi.ConnectorDeployment, *dbapi.ConnectorDeployment: return KindConnectorDeployment + case dbapi.ConnectorNamespace, *dbapi.ConnectorNamespace: + return KindConnectorNamespace case dbapi.ConnectorType, *dbapi.ConnectorType: return KindConnectorType case errors.ServiceError, *errors.ServiceError: @@ -54,6 +58,8 @@ func objectPath(id string, obj interface{}) string { return fmt.Sprintf("/api/connector_mgmt/v1/kafka_connector_clusters/%s/deployments/%s", obj.ClusterID, id) case *dbapi.ConnectorDeployment: return fmt.Sprintf("/api/connector_mgmt/v1/kafka_connector_clusters/%s/deployments/%s", obj.ClusterID, id) + case dbapi.ConnectorNamespace, *dbapi.ConnectorNamespace: + return fmt.Sprintf("/api/connector_mgmt/v1/kafka_connector_namespaces/%s", id) default: return "" } diff --git a/internal/connector/internal/routes/route_loader.go b/internal/connector/internal/routes/route_loader.go index 0a8c9f284..694ad6f30 100644 --- a/internal/connector/internal/routes/route_loader.go +++ b/internal/connector/internal/routes/route_loader.go @@ -24,17 +24,18 @@ import ( type options struct { di.Inject - ConnectorsConfig *config.ConnectorsConfig - ServerConfig *server.ServerConfig - ErrorsHandler *coreHandlers.ErrorHandler - AuthorizeMiddleware *acl.AccessControlListMiddleware - KeycloakService services.KafkaKeycloakService - AuthAgentService auth.AuthAgentService - ConnectorAdminHandler *handlers.ConnectorAdminHandler - ConnectorTypesHandler *handlers.ConnectorTypesHandler - ConnectorsHandler *handlers.ConnectorsHandler - ConnectorClusterHandler *handlers.ConnectorClusterHandler - DB *db.ConnectionFactory + ConnectorsConfig *config.ConnectorsConfig + ServerConfig *server.ServerConfig + ErrorsHandler *coreHandlers.ErrorHandler + AuthorizeMiddleware *acl.AccessControlListMiddleware + KeycloakService services.KafkaKeycloakService + AuthAgentService auth.AuthAgentService + ConnectorAdminHandler *handlers.ConnectorAdminHandler + ConnectorTypesHandler *handlers.ConnectorTypesHandler + ConnectorsHandler *handlers.ConnectorsHandler + ConnectorClusterHandler *handlers.ConnectorClusterHandler + ConnectorNamespaceHandler *handlers.ConnectorNamespaceHandler + DB *db.ConnectionFactory } func NewRouteLoader(s options) environments.RouteLoader { @@ -109,8 +110,24 @@ func (s *options) AddRoutes(mainRouter *mux.Router) error { apiV1ConnectorClustersRouter.HandleFunc("/{connector_cluster_id}", s.ConnectorClusterHandler.Update).Methods(http.MethodPut) apiV1ConnectorClustersRouter.HandleFunc("/{connector_cluster_id}", s.ConnectorClusterHandler.Delete).Methods(http.MethodDelete) apiV1ConnectorClustersRouter.HandleFunc("/{connector_cluster_id}/{_:addon[-_]parameters}", s.ConnectorClusterHandler.GetAddonParameters).Methods(http.MethodGet) + apiV1ConnectorClustersRouter.HandleFunc("/{connector_cluster_id}/namespaces", s.ConnectorClusterHandler.GetNamespaces).Methods(http.MethodGet) apiV1ConnectorClustersRouter.Use(s.AuthorizeMiddleware.Authorize) + // /api/connector_mgmt/v1/kafka_connector_namespaces + v1Collections = append(v1Collections, api.CollectionMetadata{ + ID: "kafka_connector_namespaces", + Kind: "ConnectorNamespaceList", + }) + + apiV1ConnectorNamespacesRouter := apiV1Router.PathPrefix("/{_:kafka[-_]connector[-_]namespaces}").Subrouter() + apiV1ConnectorNamespacesRouter.HandleFunc("", s.ConnectorNamespaceHandler.Create).Methods(http.MethodPost) + apiV1ConnectorNamespacesRouter.HandleFunc("/eval", s.ConnectorNamespaceHandler.CreateEvaluation).Methods(http.MethodPost) + apiV1ConnectorNamespacesRouter.HandleFunc("", s.ConnectorNamespaceHandler.List).Methods(http.MethodGet) + apiV1ConnectorNamespacesRouter.HandleFunc("/{connector_namespace_id}", s.ConnectorNamespaceHandler.Get).Methods(http.MethodGet) + apiV1ConnectorNamespacesRouter.HandleFunc("/{connector_namespace_id}", s.ConnectorNamespaceHandler.Update).Methods(http.MethodPatch) + apiV1ConnectorNamespacesRouter.HandleFunc("/{connector_namespace_id}", s.ConnectorNamespaceHandler.Delete).Methods(http.MethodDelete) + apiV1ConnectorNamespacesRouter.Use(s.AuthorizeMiddleware.Authorize) + // This section adds the API's accessed by the connector agent... { // /api/connector_mgmt/v1/kafka_connector_clusters/{id} @@ -123,19 +140,25 @@ func (s *options) AddRoutes(mainRouter *mux.Router) error { } // This section adds APIs accessed by connector admins - adminRouter := apiV1Router.PathPrefix("/admin/{_:kafka[-_]connector[-_]clusters}").Subrouter() + adminRouter := apiV1Router.PathPrefix("/admin").Subrouter() rolesMapping := map[string][]string{ - http.MethodGet: {auth.ConnectorFleetManagerAdminReadRole, auth.ConnectorFleetManagerAdminWriteRole, auth.ConnectorFleetManagerAdminFullRole}, - http.MethodPut: {auth.ConnectorFleetManagerAdminWriteRole, auth.ConnectorFleetManagerAdminFullRole}, + http.MethodDelete: {auth.ConnectorFleetManagerAdminWriteRole, auth.ConnectorFleetManagerAdminFullRole}, + http.MethodGet: {auth.ConnectorFleetManagerAdminReadRole, auth.ConnectorFleetManagerAdminWriteRole, auth.ConnectorFleetManagerAdminFullRole}, + http.MethodPost: {auth.ConnectorFleetManagerAdminWriteRole, auth.ConnectorFleetManagerAdminFullRole}, + http.MethodPut: {auth.ConnectorFleetManagerAdminWriteRole, auth.ConnectorFleetManagerAdminFullRole}, } adminRouter.Use(auth.NewRequireIssuerMiddleware().RequireIssuer([]string{s.KeycloakService.GetConfig().OSDClusterIDPRealm.ValidIssuerURI}, kerrors.ErrorNotFound)) adminRouter.Use(auth.NewRolesAuhzMiddleware().RequireRolesForMethods(rolesMapping, kerrors.ErrorNotFound)) adminRouter.Use(auth.NewAuditLogMiddleware().AuditLog(kerrors.ErrorNotFound)) - adminRouter.HandleFunc("", s.ConnectorAdminHandler.ListConnectorClusters).Methods(http.MethodGet) - adminRouter.HandleFunc("/{connector_cluster_id}/upgrades/type", s.ConnectorAdminHandler.GetConnectorUpgradesByType).Methods(http.MethodGet) - adminRouter.HandleFunc("/{connector_cluster_id}/upgrades/type", s.ConnectorAdminHandler.UpgradeConnectorsByType).Methods(http.MethodPut) - adminRouter.HandleFunc("/{connector_cluster_id}/upgrades/operator", s.ConnectorAdminHandler.GetConnectorUpgradesByOperator).Methods(http.MethodGet) - adminRouter.HandleFunc("/{connector_cluster_id}/upgrades/operator", s.ConnectorAdminHandler.UpgradeConnectorsByOperator).Methods(http.MethodPut) + adminRouter.HandleFunc("/{_:kafka[-_]connector[-_]clusters}", s.ConnectorAdminHandler.ListConnectorClusters).Methods(http.MethodGet) + adminRouter.HandleFunc("/{_:kafka[-_]connector[-_]clusters}/{connector_cluster_id}/namespaces", s.ConnectorAdminHandler.GetClusterNamespaces).Methods(http.MethodGet) + adminRouter.HandleFunc("/{_:kafka[-_]connector[-_]clusters}/{connector_cluster_id}/upgrades/type", s.ConnectorAdminHandler.GetConnectorUpgradesByType).Methods(http.MethodGet) + adminRouter.HandleFunc("/{_:kafka[-_]connector[-_]clusters}/{connector_cluster_id}/upgrades/type", s.ConnectorAdminHandler.UpgradeConnectorsByType).Methods(http.MethodPut) + adminRouter.HandleFunc("/{_:kafka[-_]connector[-_]clusters}/{connector_cluster_id}/upgrades/operator", s.ConnectorAdminHandler.GetConnectorUpgradesByOperator).Methods(http.MethodGet) + adminRouter.HandleFunc("/{_:kafka[-_]connector[-_]clusters}/{connector_cluster_id}/upgrades/operator", s.ConnectorAdminHandler.UpgradeConnectorsByOperator).Methods(http.MethodPut) + adminRouter.HandleFunc("/{_:kafka[-_]connector[-_]namespaces}", s.ConnectorAdminHandler.GetConnectorNamespaces).Methods(http.MethodGet) + adminRouter.HandleFunc("/{_:kafka[-_]connector[-_]namespaces}", s.ConnectorAdminHandler.CreateConnectorNamespace).Methods(http.MethodPost) + adminRouter.HandleFunc("/{_:kafka[-_]connector[-_]namespaces}/{namespace_id}", s.ConnectorAdminHandler.DeleteConnectorNamespace).Methods(http.MethodDelete) v1Metadata := api.VersionMetadata{ ID: "v1", diff --git a/internal/connector/internal/services/connector_cluster.go b/internal/connector/internal/services/connector_cluster.go index 328735e8a..a4d530c17 100644 --- a/internal/connector/internal/services/connector_cluster.go +++ b/internal/connector/internal/services/connector_cluster.go @@ -8,6 +8,7 @@ import ( goerrors "errors" "fmt" "github.com/golang/glog" + "math/rand" "reflect" "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/api/dbapi" @@ -37,7 +38,7 @@ type ConnectorClusterService interface { GetConnectorWithBase64Secrets(ctx context.Context, resource dbapi.ConnectorDeployment) (dbapi.Connector, *errors.ServiceError) ListConnectorDeployments(ctx context.Context, id string, listArgs *services.ListArguments, gtVersion int64) (dbapi.ConnectorDeploymentList, *api.PagingMeta, *errors.ServiceError) UpdateConnectorDeploymentStatus(ctx context.Context, status dbapi.ConnectorDeploymentStatus) *errors.ServiceError - FindReadyCluster(owner string, orgId string, group string) (*dbapi.ConnectorCluster, *errors.ServiceError) + FindReadyNamespace(owner string, orgId string, namespaceId *string) (*dbapi.ConnectorNamespace, *errors.ServiceError) GetDeploymentByConnectorId(ctx context.Context, connectorID string) (dbapi.ConnectorDeployment, *errors.ServiceError) GetDeployment(ctx context.Context, id string) (dbapi.ConnectorDeployment, *errors.ServiceError) GetAvailableDeploymentTypeUpgrades(listArgs *services.ListArguments) (dbapi.ConnectorDeploymentTypeUpgradeList, *api.PagingMeta, *errors.ServiceError) @@ -52,23 +53,35 @@ var _ ConnectorClusterService = &connectorClusterService{} var _ auth.AuthAgentService = &connectorClusterService{} type connectorClusterService struct { - connectionFactory *db.ConnectionFactory - bus signalbus.SignalBus - connectorTypesService ConnectorTypesService - vaultService vault.VaultService - keycloakService services.KafkaKeycloakService - connectorsService ConnectorsService + connectionFactory *db.ConnectionFactory + bus signalbus.SignalBus + connectorTypesService ConnectorTypesService + vaultService vault.VaultService + keycloakService services.KafkaKeycloakService + connectorsService ConnectorsService + connectorNamespaceService ConnectorNamespaceService + deploymentColumns []string } func NewConnectorClusterService(connectionFactory *db.ConnectionFactory, bus signalbus.SignalBus, vaultService vault.VaultService, - connectorTypesService ConnectorTypesService, connectorsService ConnectorsService, keycloakService services.KafkaKeycloakService) *connectorClusterService { + connectorTypesService ConnectorTypesService, connectorsService ConnectorsService, + keycloakService services.KafkaKeycloakService, connectorNamespaceService ConnectorNamespaceService) *connectorClusterService { + types, _ := connectionFactory.New().Migrator().ColumnTypes(&dbapi.ConnectorDeployment{}) + nTypes := len(types) + columnNames := make([]string, nTypes+1) + for i, columnType := range types { + columnNames[i] = "connector_deployments." + columnType.Name() + } + columnNames[nTypes] = "connector_namespaces.name AS namespace_name" return &connectorClusterService{ - connectionFactory: connectionFactory, - bus: bus, - connectorTypesService: connectorTypesService, - vaultService: vaultService, - connectorsService: connectorsService, - keycloakService: keycloakService, + connectionFactory: connectionFactory, + bus: bus, + connectorTypesService: connectorTypesService, + vaultService: vaultService, + connectorsService: connectorsService, + keycloakService: keycloakService, + connectorNamespaceService: connectorNamespaceService, + deploymentColumns: columnNames, } } @@ -118,11 +131,40 @@ func (k *connectorClusterService) Create(ctx context.Context, resource *dbapi.Co if err := dbConn.Save(resource).Error; err != nil { return errors.GeneralError("failed to create connector: %v", err) } + + // create a default namespace for the new cluster + if err := k.connectorNamespaceService.CreateDefaultNamespace(ctx, resource); err != nil { + return err + } // TODO: increment connector cluster metrics // metrics.IncreaseStatusCountMetric(constants.KafkaRequestStatusAccepted.String()) return nil } +func filterClusterToOwnerOrOrg(ctx context.Context, dbConn *gorm.DB) (*gorm.DB, *errors.ServiceError) { + + claims, err := auth.GetClaimsFromContext(ctx) + if err != nil { + return dbConn, errors.Unauthenticated("user not authenticated") + } + owner := auth.GetUsernameFromClaims(claims) + if owner == "" { + return dbConn, errors.Unauthenticated("user not authenticated") + } + + orgId := auth.GetOrgIdFromClaims(claims) + filterByOrganisationId := auth.GetFilterByOrganisationFromContext(ctx) + + // filter by organisationId if a user is part of an organisation and is not allowed as a service account + if filterByOrganisationId { + // include namespaces where either user or organisation is the tenant + dbConn = dbConn.Where("organisation_id = ?", orgId) + } else { + dbConn = dbConn.Where("owner = ?", owner) + } + return dbConn, nil +} + // Get gets a connector by id from the database func (k *connectorClusterService) Get(ctx context.Context, id string) (dbapi.ConnectorCluster, *errors.ServiceError) { @@ -131,7 +173,7 @@ func (k *connectorClusterService) Get(ctx context.Context, id string) (dbapi.Con dbConn = dbConn.Where("id = ?", id) var err *errors.ServiceError - dbConn, err = filterToOwnerOrOrg(ctx, dbConn) + dbConn, err = filterClusterToOwnerOrOrg(ctx, dbConn) if err != nil { return resource, err } @@ -153,76 +195,40 @@ func (k *connectorClusterService) Delete(ctx context.Context, id string) *errors return errors.Unauthenticated("user not authenticated") } - dbConn := k.connectionFactory.New() - var resource dbapi.ConnectorCluster - if err := dbConn.Where("owner = ? AND id = ?", owner, id).First(&resource).Error; err != nil { - return services.HandleGetError("Connector cluster", "id", id, err) - } + if err := k.connectionFactory.New().Transaction(func(dbConn *gorm.DB) error { - if err := dbConn.Delete(&resource).Error; err != nil { - return errors.GeneralError("unable to delete connector with id %s: %s", resource.ID, err) - } + var resource dbapi.ConnectorCluster + if err := dbConn.Where("owner = ? AND id = ?", owner, id).First(&resource).Error; err != nil { + return services.HandleGetError("Connector cluster", "id", id, err) + } - // Delete all deployments assigned to the cluster.. - dbConn = k.connectionFactory.New() - { - rows, err := dbConn. - Model(&dbapi.ConnectorDeployment{}). - Select("id"). - Where("cluster_id = ?", id). - Rows() - if err != nil { - return errors.GeneralError("unable find deployments of cluster %s: %s", id, err) - } - defer rows.Close() - for rows.Next() { - deployment := dbapi.ConnectorDeployment{} - err := dbConn.ScanRows(rows, &deployment) - if err != nil { - return errors.GeneralError("Unable to scan connector deployment: %s", err) - } - deploymentStatus := dbapi.ConnectorDeploymentStatus{ - Meta: api.Meta{ - ID: deployment.ID, - }, - } - if err := dbConn.Delete(&deploymentStatus).Error; err != nil { - return errors.GeneralError("failed to delete connector deployment status: %s", err) - } - if err := dbConn.Delete(&deployment).Error; err != nil { - return errors.GeneralError("failed to delete connector deployment: %s", err) - } + // delete cluster + if err := dbConn.Delete(&dbapi.ConnectorCluster{}, "id = ?", id).Error; err != nil { + return services.HandleDeleteError("Connector cluster", "id", id, err) } - } - // Clear the cluster from any connectors that were using it. - { - rows, err := dbConn. - Model(&dbapi.Connector{}). - Select("id"). - Where("addon_cluster_id = ?", id). - Rows() - if err != nil { - return errors.GeneralError("unable find connector using cluster %s: %s", id, err) - } - defer rows.Close() - for rows.Next() { - connector := dbapi.Connector{} - err := dbConn.ScanRows(rows, &connector) - if err != nil { - return errors.GeneralError("Unable to scan connector: %s", err) - } + // Delete all deployments assigned to the cluster.. + if err := dbConn.Delete(&dbapi.ConnectorDeploymentStatus{}, "id IN (?)", + dbConn.Table("connector_deployments").Select("id").Where("cluster_id = ?", id)).Error; err != nil { + return services.HandleDeleteError("Connector deployment status", "cluster_id", id, err) + } + if err := dbConn.Delete(&dbapi.ConnectorDeployment{}, "cluster_id = ?", id).Error; err != nil { + return services.HandleDeleteError("Connector deployment", "cluster_id", id, err) + } - if err := dbConn.Model(&connector).Update("addon_cluster_id", "").Error; err != nil { - return errors.GeneralError("failed to update connector: %s", err) - } + // Delete all namespaces assigned to the cluster.. + if err := dbConn.Delete(&dbapi.ConnectorNamespace{}, "cluster_id = ?", id).Error; err != nil { + return services.HandleDeleteError("Connector namespace", "cluster_id", id, err) + } - status := dbapi.ConnectorStatus{} - status.ID = connector.ID - if err := dbConn.Model(&status).Update("phase", "assigning").Error; err != nil { - return errors.GeneralError("failed to update connector status: %s", err) - } + // Clear the cluster from any connectors that were using it. + if err := removeConnectorsFromNamespace(dbConn, "connector_namespaces.cluster_id = ?", id); err != nil { + return err } + + return nil + }); err != nil { + return services.HandleDeleteError("Connector cluster", "id", id, err) } glog.V(5).Infof("Removing agent service account for connector cluster %s", id) @@ -288,7 +294,7 @@ func (k *connectorClusterService) List(ctx context.Context, listArgs *services.L return nil, nil, err } if !admin { - dbConn, err = filterToOwnerOrOrg(ctx, dbConn) + dbConn, err = filterClusterToOwnerOrOrg(ctx, dbConn) if err != nil { return nil, nil, err } @@ -437,9 +443,10 @@ func (k *connectorClusterService) ListConnectorDeployments(ctx context.Context, Size: listArgs.Size, } - dbConn = dbConn.Where("cluster_id = ?", id) + dbConn = dbConn. + Where("connector_deployments.cluster_id = ?", id) if gtVersion != 0 { - dbConn = dbConn.Where("version > ?", gtVersion) + dbConn = dbConn.Where("connector_deployments.version > ?", gtVersion) } // set total, limit and paging (based on https://gitlab.cee.redhat.com/service/api-guidelines#user-content-paging) @@ -453,7 +460,10 @@ func (k *connectorClusterService) ListConnectorDeployments(ctx context.Context, dbConn = dbConn.Offset((pagingMeta.Page - 1) * pagingMeta.Size).Limit(pagingMeta.Size) // default the order by version - dbConn = dbConn.Order("version") + dbConn = dbConn.Order("connector_deployments.version") + + // add namespace_name column + dbConn = k.selectDeploymentColumns(dbConn) // execute query if err := dbConn.Find(&resourceList).Error; err != nil { @@ -463,6 +473,12 @@ func (k *connectorClusterService) ListConnectorDeployments(ctx context.Context, return resourceList, pagingMeta, nil } +func (k *connectorClusterService) selectDeploymentColumns(dbConn *gorm.DB) *gorm.DB { + // join connector_namespaces for namespace name + return dbConn.Select(k.deploymentColumns). + Joins("INNER JOIN connector_namespaces ON connector_namespaces.id = namespace_id") +} + func (k *connectorClusterService) UpdateConnectorDeploymentStatus(ctx context.Context, deploymentStatus dbapi.ConnectorDeploymentStatus) *errors.ServiceError { dbConn := k.connectionFactory.New() @@ -516,25 +532,52 @@ func (k *connectorClusterService) UpdateConnectorDeploymentStatus(ctx context.Co return nil } -func (k *connectorClusterService) FindReadyCluster(owner string, orgId string, connectorClusterId string) (*dbapi.ConnectorCluster, *errors.ServiceError) { +func (k *connectorClusterService) FindReadyNamespace(owner string, orgID string, namespaceID *string) (*dbapi.ConnectorNamespace, *errors.ServiceError) { dbConn := k.connectionFactory.New() - var resource dbapi.ConnectorCluster + var namespaces dbapi.ConnectorNamespaceList - dbConn = dbConn.Where("id = ? AND status_phase = ?", connectorClusterId, dbapi.ConnectorClusterPhaseReady) + if namespaceID != nil { + dbConn = dbConn.Where("connector_namespaces.id = ?", namespaceID) + } + dbConn = dbConn.Joins("INNER JOIN connector_clusters"+ + " ON cluster_id = connector_clusters.id and connector_clusters.status_phase = ?", dbapi.ConnectorClusterPhaseReady) - if orgId != "" { - dbConn = dbConn.Where("organisation_id = ?", orgId) + if orgID != "" { + dbConn = dbConn.Where("tenant_organisation_id = ? or tenant_user_id = ?", orgID, owner) } else { - dbConn = dbConn.Where("owner = ?", owner) + dbConn = dbConn.Where("tenant_owner_id = ?", owner) } - if err := dbConn.First(&resource).Error; err != nil { - if goerrors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil + if err := dbConn.Find(&namespaces).Error; err != nil { + return nil, errors.GeneralError("failed to query ready connector namespace: %v", err.Error()) + } + + n := len(namespaces) + if n == 1 { + return namespaces[0], nil + } else if n > 1 { + // prioritise and select from multiple namespaces + ownerNamespaces := dbapi.ConnectorNamespaceList{} + orgNamespaces := dbapi.ConnectorNamespaceList{} + for _, namespace := range namespaces { + if namespace.TenantUserId != nil { + ownerNamespaces = append(ownerNamespaces, namespace) + } else { + orgNamespaces = append(orgNamespaces, namespace) + } + } + + // TODO replace with more sophisticated load balancing logic in the future + // prefer owner tenant + n := len(ownerNamespaces) + if n > 0 { + return ownerNamespaces[rand.Intn(n)], nil + } else { + return orgNamespaces[rand.Intn(len(orgNamespaces))], nil } - return nil, errors.GeneralError("failed to query ready addon connector cluster: %v", err.Error()) } - return &resource, nil + + return nil, nil } func Checksum(spec interface{}) (string, error) { @@ -622,17 +665,18 @@ func getSecretsFromVaultAsBase64(resource *dbapi.Connector, cts ConnectorTypesSe func (k *connectorClusterService) GetDeploymentByConnectorId(ctx context.Context, connectorID string) (resource dbapi.ConnectorDeployment, serr *errors.ServiceError) { dbConn := k.connectionFactory.New() - dbConn = dbConn.Where("connector_id = ?", connectorID) + dbConn = k.selectDeploymentColumns(dbConn).Where("connector_id = ?", connectorID) if err := dbConn.First(&resource).Error; err != nil { return resource, services.HandleGetError("Connector deployment", "connector_id", connectorID, err) } return } + func (k *connectorClusterService) GetDeployment(ctx context.Context, id string) (resource dbapi.ConnectorDeployment, serr *errors.ServiceError) { dbConn := k.connectionFactory.New() - dbConn = dbConn.Unscoped().Where("id = ?", id) - if err := dbConn.First(&resource).Error; err != nil { + dbConn = dbConn.Unscoped().Where("connector_deployments.id = ?", id) + if err := k.selectDeploymentColumns(dbConn).First(&resource).Error; err != nil { return resource, services.HandleGetError("Connector deployment", "id", id, err) } @@ -651,6 +695,7 @@ func (k *connectorClusterService) GetAvailableDeploymentTypeUpgrades(listArgs *s ConnectorTypeUpgradeFrom int64 ConnectorTypeUpgradeTo int64 ConnectorTypeID string + NamespaceID string Channel string } @@ -660,6 +705,7 @@ func (k *connectorClusterService) GetAvailableDeploymentTypeUpgrades(listArgs *s dbConn = dbConn.Select( "connector_deployments.connector_id AS connector_id", "connector_deployments.id AS deployment_id", + "connector_deployments.namespace_id AS namespace_id", "connector_shard_metadata.id AS connector_type_upgrade_from", "connector_shard_metadata.latest_id AS connector_type_upgrade_to", "connector_shard_metadata.connector_type_id", @@ -687,6 +733,7 @@ func (k *connectorClusterService) GetAvailableDeploymentTypeUpgrades(listArgs *s ConnectorID: r.ConnectorID, DeploymentID: r.DeploymentID, ConnectorTypeId: r.ConnectorTypeID, + NamespaceID: r.NamespaceID, Channel: r.Channel, } @@ -768,6 +815,7 @@ func (k *connectorClusterService) GetAvailableDeploymentOperatorUpgrades(listArg ConnectorID string DeploymentID string ConnectorTypeID string + NamespaceID string Channel string ConnectorOperators api.JSON } @@ -778,6 +826,7 @@ func (k *connectorClusterService) GetAvailableDeploymentOperatorUpgrades(listArg dbConn = dbConn.Select( "connector_deployments.connector_id AS connector_id", "connector_deployments.id AS deployment_id", + "connector_deployments.namespace_id AS namespace_id", "connector_shard_metadata.connector_type_id", "connector_shard_metadata.channel", "connector_deployment_statuses.operators AS connector_operators", @@ -802,6 +851,7 @@ func (k *connectorClusterService) GetAvailableDeploymentOperatorUpgrades(listArg ConnectorID: r.ConnectorID, DeploymentID: r.DeploymentID, ConnectorTypeId: r.ConnectorTypeID, + NamespaceID: r.NamespaceID, Channel: r.Channel, } diff --git a/internal/connector/internal/services/connector_namespaces.go b/internal/connector/internal/services/connector_namespaces.go new file mode 100644 index 000000000..e0d48b64e --- /dev/null +++ b/internal/connector/internal/services/connector_namespaces.go @@ -0,0 +1,284 @@ +package services + +import ( + "context" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/api/dbapi" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/api/public" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/config" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/presenters" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/api" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/db" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/errors" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/services" + coreServices "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/services/queryparser" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/services/signalbus" + "gorm.io/gorm" + "math/rand" + "time" +) + +type ConnectorNamespaceService interface { + Create(ctx context.Context, request *dbapi.ConnectorNamespace) *errors.ServiceError + Update(ctx context.Context, request *dbapi.ConnectorNamespace) *errors.ServiceError + Get(ctx context.Context, namespaceID string) (*dbapi.ConnectorNamespace, *errors.ServiceError) + List(ctx context.Context, clusterIDs []string, listArguments *services.ListArguments) (dbapi.ConnectorNamespaceList, *api.PagingMeta, *errors.ServiceError) + Delete(namespaceId string) *errors.ServiceError + SetEvalClusterId(request *dbapi.ConnectorNamespace) *errors.ServiceError + GetOwnerClusterIds(userId string, ownerClusterIds *[]string) *errors.ServiceError + GetOrgClusterIds(userId string, organisationId string, orgClusterIds *[]string) *errors.ServiceError + CreateDefaultNamespace(ctx context.Context, connectorCluster *dbapi.ConnectorCluster) *errors.ServiceError + GetExpiredNamespaces() (dbapi.ConnectorNamespaceList, *errors.ServiceError) +} + +var _ ConnectorNamespaceService = &connectorNamespaceService{} + +type connectorNamespaceService struct { + connectionFactory *db.ConnectionFactory + connectorsConfig *config.ConnectorsConfig + bus signalbus.SignalBus +} + +func init() { + // random seed + rand.Seed(time.Now().UnixNano()) +} + +func NewConnectorNamespaceService(factory *db.ConnectionFactory, config *config.ConnectorsConfig, bus signalbus.SignalBus) *connectorNamespaceService { + return &connectorNamespaceService{ + connectionFactory: factory, + connectorsConfig: config, + bus: bus, + } +} + +func (k *connectorNamespaceService) GetOrgClusterIds(userId, organisationId string, orgClusterIds *[]string) *errors.ServiceError { + dbConn := k.connectionFactory.New() + if err := dbConn.Raw("SELECT id FROM connector_clusters WHERE deleted_at is null AND owner <> ? AND organisation_id = ?", userId, organisationId). + Scan(orgClusterIds).Error; err != nil { + return errors.Unauthorized("failed to get cluster id for organisation %v: %v", organisationId, err) + } + return nil +} + +func (k *connectorNamespaceService) GetOwnerClusterIds(userId string, ownerClusterIds *[]string) *errors.ServiceError { + dbConn := k.connectionFactory.New() + if err := dbConn.Raw("SELECT id FROM connector_clusters WHERE deleted_at is null AND owner = ?", userId). + Scan(ownerClusterIds).Error; err != nil { + return errors.Unauthorized("failed to get cluster id for user %v: %v", userId, err) + } + return nil +} + +func (k *connectorNamespaceService) SetEvalClusterId(request *dbapi.ConnectorNamespace) *errors.ServiceError { + var availableClusters []string + + // get eval clusters + dbConn := k.connectionFactory.New() + if err := dbConn.Raw("SELECT id FROM connector_clusters WHERE deleted_at is null AND organisation_id IN ?", + k.connectorsConfig.ConnectorEvalOrganizations). + Scan(&availableClusters).Error; err != nil { + return errors.Unauthorized("failed to get eval cluster id: %v", err) + } + + numOrgClusters := len(availableClusters) + if numOrgClusters == 0 { + return errors.Unauthorized("no eval clusters") + } else if numOrgClusters == 1 { + request.ClusterId = availableClusters[0] + } else { + // TODO add support for load balancing strategies + // pick a cluster at random + request.ClusterId = availableClusters[rand.Intn(numOrgClusters)] + } + + // also set expiration duration + expiry := time.Now().Add(k.connectorsConfig.ConnectorEvalDuration) + request.Expiration = &expiry + return nil +} + +func (k *connectorNamespaceService) Create(ctx context.Context, request *dbapi.ConnectorNamespace) *errors.ServiceError { + + dbConn := k.connectionFactory.New() + if err := dbConn.Create(request).Error; err != nil { + return errors.GeneralError("failed to create connector namespace: %v", err) + } + // reload namespace to get version update + if err := dbConn.Select("version").First(request).Error; err != nil { + return errors.GeneralError("failed to read new connector namespace: %v", err) + } + + // TODO: increment connector namespace metrics + // metrics.IncreaseStatusCountMetric(constants.KafkaRequestStatusAccepted.String()) + return nil +} + +func (k *connectorNamespaceService) Update(ctx context.Context, request *dbapi.ConnectorNamespace) *errors.ServiceError { + + dbConn := k.connectionFactory.New() + if err := dbConn.Save(request).Error; err != nil { + return errors.GeneralError("failed to update connector namespace: %v", err) + } + // reload namespace to get version update + if err := dbConn.Select("version").First(request).Error; err != nil { + return errors.GeneralError("failed to read updated connector namespace: %v", err) + } + + // TODO: increment connector namespace metrics + // metrics.IncreaseStatusCountMetric(constants.KafkaRequestStatusAccepted.String()) + return nil +} + +func (k *connectorNamespaceService) Get(ctx context.Context, namespaceID string) (*dbapi.ConnectorNamespace, *errors.ServiceError) { + dbConn := k.connectionFactory.New() + result := &dbapi.ConnectorNamespace{ + Model: db.Model{ + ID: namespaceID, + }, + } + if err := dbConn.First(result).Error; err != nil { + return nil, errors.GeneralError("failed to get connector namespace: %v", err) + } + + // TODO: increment connector namespace metrics + // metrics.IncreaseStatusCountMetric(constants.KafkaRequestStatusAccepted.String()) + return result, nil +} + +var validNamespaceColumns = []string{"name", "cluster_id", "owner", "expiration", "tenant_user_id", "tenant_organisation_id"} + +func (k *connectorNamespaceService) List(ctx context.Context, clusterIDs []string, + listArguments *services.ListArguments) (dbapi.ConnectorNamespaceList, *api.PagingMeta, *errors.ServiceError) { + var resourceList dbapi.ConnectorNamespaceList + pagingMeta := api.PagingMeta{ + Page: listArguments.Page, + Size: listArguments.Size, + Total: 0, + } + dbConn := k.connectionFactory.New().Model(&resourceList) + if len(clusterIDs) != 0 { + dbConn = dbConn.Where("cluster_id IN ?", clusterIDs) + } + + // Apply search query + if len(listArguments.Search) > 0 { + queryParser := coreServices.NewQueryParser(validNamespaceColumns...) + searchDbQuery, err := queryParser.Parse(listArguments.Search) + if err != nil { + return resourceList, &pagingMeta, errors.NewWithCause(errors.ErrorFailedToParseSearch, err, "Unable to list connector namespace requests: %s", err.Error()) + } + dbConn = dbConn.Where(searchDbQuery.Query, searchDbQuery.Values...) + } + + // set total, limit and paging (based on https://gitlab.cee.redhat.com/service/api-guidelines#user-content-paging) + total := int64(pagingMeta.Total) + dbConn.Count(&total) + pagingMeta.Total = int(total) + if pagingMeta.Size > pagingMeta.Total { + pagingMeta.Size = pagingMeta.Total + } + dbConn = dbConn.Offset((pagingMeta.Page - 1) * pagingMeta.Size).Limit(pagingMeta.Size) + + if len(listArguments.OrderBy) == 0 { + // default orderBy name + dbConn = dbConn.Order("name ASC") + } + + // Set the order by arguments if any + for _, orderByArg := range listArguments.OrderBy { + dbConn = dbConn.Order(orderByArg) + } + + // execute query + result := dbConn. + Preload("Annotations"). + Preload("TenantUser"). + Preload("TenantOrganisation"). + Find(&resourceList) + if result.Error != nil { + return nil, nil, errors.GeneralError("failed to get connector namespaces: %v", result.Error) + } + + // TODO: increment connector namespace metrics + // metrics.IncreaseStatusCountMetric(constants.KafkaRequestStatusAccepted.String()) + return resourceList, &pagingMeta, nil +} + +func (k *connectorNamespaceService) Delete(namespaceId string) *errors.ServiceError { + if err := k.connectionFactory.New().Transaction(func(dbConn *gorm.DB) error { + + if err := dbConn.Where("id = ?", namespaceId). + First(&dbapi.ConnectorNamespace{}).Error; err != nil { + return services.HandleGetError("Connector namespace", "id", namespaceId, err) + } + + // TODO do this asynchronously in a namespace reconciler + if err := dbConn.Where("id = ?", namespaceId). + Delete(&dbapi.ConnectorNamespace{}).Error; err != nil { + return services.HandleDeleteError("Connector namespace", "id", namespaceId, err) + } + + // Clear the namespace from any connectors that were using it. + return removeConnectorsFromNamespace(dbConn, "connector_namespaces.id = ?", namespaceId) + }); err != nil { + return services.HandleDeleteError("Connector namespace", "id", namespaceId, err) + } + + // TODO: increment connector namespace metrics + // metrics.IncreaseStatusCountMetric(constants.KafkaRequestStatusAccepted.String()) + return nil +} + +// TODO make this a configurable property in the future +const defaultNamespaceName = "default-connector-namespace" + +func (k *connectorNamespaceService) CreateDefaultNamespace(ctx context.Context, connectorCluster *dbapi.ConnectorCluster) *errors.ServiceError { + owner := connectorCluster.Owner + organisationId := connectorCluster.OrganisationId + + kind := public.CONNECTORNAMESPACETENANTKIND_USER + if organisationId != "" { + kind = public.CONNECTORNAMESPACETENANTKIND_ORGANISATION + } + + namespaceRequest, err := presenters.ConvertConnectorNamespaceRequest(&public.ConnectorNamespaceRequest{ + Name: defaultNamespaceName, + Annotations: []public.ConnectorNamespaceRequestMetaAnnotations{ + { + Name: "connector_mgmt.api.openshift.com/profile", + Value: "default-profile", + }, + }, + ClusterId: connectorCluster.ID, + Kind: kind, + }, owner, organisationId) + + if err != nil { + return err + } + return k.Create(ctx, namespaceRequest) +} + +func (k *connectorNamespaceService) GetExpiredNamespaces() (dbapi.ConnectorNamespaceList, *errors.ServiceError) { + var result dbapi.ConnectorNamespaceList + dbConn := k.connectionFactory.New() + if err := dbConn.Where("expiration < ?", time.Now()).Find(&result).Error; err != nil { + return nil, errors.GeneralError("Error retrieving expired namespaces: %s", err) + } + return result, nil +} + +func removeConnectorsFromNamespace(dbConn *gorm.DB, query interface{}, values ...interface{}) error { + namespaceSubQuery :=dbConn.Table("connector_namespaces").Select("id").Where(query, values) + if err := dbConn.Select("namespace_id"). + Where("namespace_id IN (?)", namespaceSubQuery). + Updates(&dbapi.Connector{ NamespaceId: nil}).Error; err != nil { + return services.HandleUpdateError("Connector", err) + } + if err := dbConn.Select("namespace_id", "phase"). + Where("namespace_id IN (?)", namespaceSubQuery). + Updates(&dbapi.ConnectorStatus{NamespaceID: nil, Phase: "assigning"}).Error; err != nil { + return services.HandleUpdateError("Connector", err) + } + return nil +} diff --git a/internal/connector/internal/services/connectors.go b/internal/connector/internal/services/connectors.go index b27036316..e5d38a630 100644 --- a/internal/connector/internal/services/connectors.go +++ b/internal/connector/internal/services/connectors.go @@ -39,7 +39,8 @@ type connectorsService struct { connectorTypesService ConnectorTypesService } -func NewConnectorsService(connectionFactory *db.ConnectionFactory, bus signalbus.SignalBus, vaultService vault.VaultService, connectorTypesService ConnectorTypesService) *connectorsService { +func NewConnectorsService(connectionFactory *db.ConnectionFactory, bus signalbus.SignalBus, + vaultService vault.VaultService, connectorTypesService ConnectorTypesService) *connectorsService { return &connectorsService{ connectionFactory: connectionFactory, bus: bus, @@ -93,7 +94,7 @@ func (k *connectorsService) Get(ctx context.Context, id string, tid string) (*db dbConn = dbConn.Preload("Status") var err *errors.ServiceError - dbConn, err = filterToOwnerOrOrg(ctx, dbConn) + dbConn, err = filterConnectorsToOwnerOrOrg(ctx, dbConn, k.connectionFactory) if err != nil { return nil, err } @@ -108,7 +109,8 @@ func (k *connectorsService) Get(ctx context.Context, id string, tid string) (*db return &resource, nil } -func filterToOwnerOrOrg(ctx context.Context, dbConn *gorm.DB) (*gorm.DB, *errors.ServiceError) { +func filterConnectorsToOwnerOrOrg(ctx context.Context, dbConn *gorm.DB, factory *db.ConnectionFactory) (*gorm.DB, *errors.ServiceError) { + claims, err := auth.GetClaimsFromContext(ctx) if err != nil { return dbConn, errors.Unauthenticated("user not authenticated") @@ -123,7 +125,13 @@ func filterToOwnerOrOrg(ctx context.Context, dbConn *gorm.DB) (*gorm.DB, *errors // filter by organisationId if a user is part of an organisation and is not allowed as a service account if filterByOrganisationId { - dbConn = dbConn.Where("organisation_id = ?", orgId) + // unassigned connectors with no namespace_id use owner and org + // assigned connectors use tenant user or organisation + dbConn = dbConn.Where("(namespace_id is null AND (owner = ? or organisation_id = ?)) OR (namespace_id is not null AND namespace_id IN (?))", + owner, + orgId, + factory.New().Table("connector_namespaces").Select("id"). + Where("deleted_at is null AND (tenant_user_id = ? OR tenant_organisation_id = ?)", owner, orgId)) } else { dbConn = dbConn.Where("owner = ?", owner) } @@ -202,7 +210,7 @@ func (k *connectorsService) List(ctx context.Context, kafka_id string, listArgs } var err *errors.ServiceError - dbConn, err = filterToOwnerOrOrg(ctx, dbConn) + dbConn, err = filterConnectorsToOwnerOrOrg(ctx, dbConn, k.connectionFactory) if err != nil { return nil, nil, err } diff --git a/internal/connector/internal/workers/connector_mgr.go b/internal/connector/internal/workers/connector_mgr.go index d364dcea5..5bd636276 100644 --- a/internal/connector/internal/workers/connector_mgr.go +++ b/internal/connector/internal/workers/connector_mgr.go @@ -199,49 +199,45 @@ func (k *ConnectorManager) ReconcileConnectorCatalogEntry(id string, channel str return nil } +//goland:noinspection VacuumSwitchStatement func (k *ConnectorManager) reconcileAssigning(ctx context.Context, connector *dbapi.Connector) error { - switch connector.TargetKind { - case dbapi.AddonTargetKind: - - cluster, err := k.connectorClusterService.FindReadyCluster(connector.Owner, connector.OrganisationId, connector.AddonClusterId) - if err != nil { - return errors.Wrapf(err, "failed to find cluster for connector request %s", connector.ID) - } - if cluster == nil { - // we will try to find a ready cluster again in the next reconcile - return nil - } - - channelVersion, err := k.connectorTypesService.GetLatestConnectorShardMetadataID(connector.ConnectorTypeId, connector.Channel) - if err != nil { - return errors.Wrapf(err, "failed to get latest channel version for connector request %s", connector.ID) - } + var namespace *dbapi.ConnectorNamespace + namespace, err := k.connectorClusterService.FindReadyNamespace(connector.Owner, connector.OrganisationId, connector.NamespaceId) + if err != nil { + return errors.Wrapf(err, "failed to find namespace for connector request %s", connector.ID) + } + if namespace == nil { + // we will try to find a ready cluster again in the next reconcile + return nil + } - var status = dbapi.ConnectorStatus{} - status.ID = connector.ID - status.ClusterID = cluster.ID - status.Phase = dbapi.ConnectorStatusPhaseAssigned - if err = k.connectorService.SaveStatus(ctx, status); err != nil { - return errors.Wrapf(err, "failed to update connector status %s with cluster details", connector.ID) - } + channelVersion, err := k.connectorTypesService.GetLatestConnectorShardMetadataID(connector.ConnectorTypeId, connector.Channel) + if err != nil { + return errors.Wrapf(err, "failed to get latest channel version for connector request %s", connector.ID) + } - deployment := dbapi.ConnectorDeployment{ - Meta: api.Meta{ - ID: api.NewID(), - }, - ConnectorID: connector.ID, - ClusterID: cluster.ID, - ConnectorVersion: connector.Version, - ConnectorTypeChannelId: channelVersion, - Status: dbapi.ConnectorDeploymentStatus{}, - } + var status = dbapi.ConnectorStatus{} + status.ID = connector.ID + status.NamespaceID = &namespace.ID + status.Phase = dbapi.ConnectorStatusPhaseAssigned + if err = k.connectorService.SaveStatus(ctx, status); err != nil { + return errors.Wrapf(err, "failed to update connector status %s with namespace details", status.ID) + } - if err = k.connectorClusterService.SaveDeployment(ctx, &deployment); err != nil { - return errors.Wrapf(err, "failed to create connector deployment for connector %s", connector.ID) - } + deployment := dbapi.ConnectorDeployment{ + Meta: api.Meta{ + ID: api.NewID(), + }, + ConnectorID: connector.ID, + ClusterID: namespace.ClusterId, + NamespaceID: namespace.ID, + ConnectorVersion: connector.Version, + ConnectorTypeChannelId: channelVersion, + Status: dbapi.ConnectorDeploymentStatus{}, + } - default: - return errors.Errorf("target kind not supported: %s", connector.TargetKind) + if err = k.connectorClusterService.SaveDeployment(ctx, &deployment); err != nil { + return errors.Wrapf(err, "failed to create connector deployment for connector %s", connector.ID) } return nil } diff --git a/internal/connector/internal/workers/namespace_mgr.go b/internal/connector/internal/workers/namespace_mgr.go new file mode 100644 index 000000000..0f57ff911 --- /dev/null +++ b/internal/connector/internal/workers/namespace_mgr.go @@ -0,0 +1,65 @@ +package workers + +import ( + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/services" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/services/signalbus" + "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/workers" + "github.com/golang/glog" + "github.com/google/uuid" +) + +var _ workers.Worker = &NamespaceManager{} + +type NamespaceManager struct { + workers.BaseWorker + namespaceService services.ConnectorNamespaceService +} + +func (m *NamespaceManager) Start() { + m.StartWorker(m) +} + +func (m *NamespaceManager) Stop() { + m.StopWorker(m) +} + +func NewNamespaceManager(bus signalbus.SignalBus, namespaceService services.ConnectorNamespaceService) *NamespaceManager { + return &NamespaceManager{ + BaseWorker: workers.BaseWorker{ + Id: uuid.New().String(), + WorkerType: "connector_namespace", + Reconciler: workers.Reconciler{ + SignalBus: bus, + }, + }, + namespaceService: namespaceService, + } +} + +func (m *NamespaceManager) Reconcile() []error { + glog.V(5).Infof("Deleting expired namespaces...") + namespaces, err := m.namespaceService.GetExpiredNamespaces() + if err != nil { + return []error{err} + } + + n := len(namespaces) + if n == 0 { + glog.V(5).Infof("No expired namespaces") + return nil + } + + glog.V(5).Infof("Deleting %d namespaces...", n) + success := n + var errs []error + for _, namespace := range namespaces { + id := namespace.ID + if err := m.namespaceService.Delete(id); err != nil { + errs = append(errs, err) + success-- + } + } + + glog.V(5).Infof("Deleted %d expired namespaces with %d errors", success, len(errs)) + return errs +} diff --git a/internal/connector/providers.go b/internal/connector/providers.go index 117597014..af21bd428 100644 --- a/internal/connector/providers.go +++ b/internal/connector/providers.go @@ -49,14 +49,16 @@ func serviceProviders() di.Option { return di.Options( di.Provide(services.NewConnectorsService, di.As(new(services.ConnectorsService))), di.Provide(services.NewConnectorTypesService, di.As(new(services.ConnectorTypesService))), - di.Provide(services.NewConnectorClusterService, di.As(new(services.ConnectorClusterService))), - di.Provide(services.NewConnectorClusterService, di.As(new(auth.AuthAgentService))), + di.Provide(services.NewConnectorClusterService, di.As(new(services.ConnectorClusterService)), di.As(new(auth.AuthAgentService))), + di.Provide(services.NewConnectorNamespaceService, di.As(new(services.ConnectorNamespaceService))), + di.Provide(handlers.NewConnectorNamespaceHandler), di.Provide(handlers.NewConnectorAdminHandler), di.Provide(handlers.NewConnectorTypesHandler), di.Provide(handlers.NewConnectorsHandler), di.Provide(handlers.NewConnectorClusterHandler), di.Provide(routes.NewRouteLoader), di.Provide(workers.NewConnectorManager, di.As(new(coreWorkers.Worker))), + di.Provide(workers.NewNamespaceManager, di.As(new(coreWorkers.Worker))), di.Provide(workers.NewApiServerReadyCondition), ) } diff --git a/internal/connector/test/integration/feature_test.go b/internal/connector/test/integration/feature_test.go index 07cee6441..230d71f22 100644 --- a/internal/connector/test/integration/feature_test.go +++ b/internal/connector/test/integration/feature_test.go @@ -3,6 +3,7 @@ package integration import ( "os" "testing" + "time" "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/internal/connector/internal/config" "github.com/bf2fc6cc711aee1a0c2a/kas-fleet-manager/pkg/client/keycloak" @@ -23,6 +24,8 @@ func TestMain(m *testing.M) { h, teardown := test.NewHelperWithHooks(t, ocmServer, func(c *config.ConnectorsConfig, kc *keycloak.KeycloakConfig) { c.ConnectorCatalogDirs = []string{"./internal/connector/test/integration/connector-catalog"} + c.ConnectorEvalDuration, _ = time.ParseDuration("2s") + c.ConnectorEvalOrganizations = []string{"13640210"} kc.KeycloakClientExpire = true }, diff --git a/internal/connector/test/integration/features/connector-agent-api.feature b/internal/connector/test/integration/features/connector-agent-api.feature index 60a9dbdf6..e516dc564 100644 --- a/internal/connector/test/integration/features/connector-agent-api.feature +++ b/internal/connector/test/integration/features/connector-agent-api.feature @@ -28,6 +28,10 @@ Feature: connector agent API And the ".status.state" selection from the response should match "disconnected" Given I store the ".id" selection from the response as ${connector_cluster_id} + When I GET path "/v1/kafka_connector_clusters/${connector_cluster_id}/namespaces" + Then the response code should be 200 + Given I store the ".items[0].id" selection from the response as ${connector_namespace_id} + When I GET path "/v1/kafka_connector_clusters/${connector_cluster_id}/addon_parameters" Then the response code should be 200 And get and store access token using the addon parameter response as ${shard_token} and clientID as ${clientID} @@ -38,10 +42,7 @@ Feature: connector agent API { "kind": "Connector", "name": "example 1", - "deployment_location": { - "kind": "addon", - "cluster_id": "${connector_cluster_id}" - }, + "namespace_id": "${connector_namespace_id}", "channel":"stable", "connector_type_id": "aws-sqs-source-v1alpha1", "kafka": { @@ -209,6 +210,8 @@ Feature: connector agent API ] }, "connector_id": "${connector_id}", + "namespace_id": "${connector_namespace_id}", + "namespace_name": "default-connector-namespace", "connector_resource_version": ${response.object.spec.connector_resource_version}, "connector_type_id": "aws-sqs-source-v1alpha1", "connector_spec": { @@ -305,6 +308,8 @@ Feature: connector agent API ] }, "connector_id": "${connector_id}", + "namespace_id": "${connector_namespace_id}", + "namespace_name": "default-connector-namespace", "connector_resource_version": ${response.items[0].spec.connector_resource_version}, "connector_type_id": "aws-sqs-source-v1alpha1", "connector_spec": { @@ -390,6 +395,8 @@ Feature: connector agent API ] }, "connector_id": "${connector_id}", + "namespace_id": "${connector_namespace_id}", + "namespace_name": "default-connector-namespace", "connector_resource_version": ${response.spec.connector_resource_version}, "connector_type_id": "aws-sqs-source-v1alpha1", "connector_spec": { @@ -476,10 +483,7 @@ Feature: connector agent API "kafka_topic": "test" }, "connector_type_id": "aws-sqs-source-v1alpha1", - "deployment_location": { - "kind": "addon", - "cluster_id": "${connector_cluster_id}" - }, + "namespace_id": "${connector_namespace_id}", "href": "/api/connector_mgmt/v1/kafka_connectors/${connector_id}", "id": "${connector_id}", "kafka": { @@ -570,6 +574,8 @@ Feature: connector agent API ] }, "connector_id": "${connector_id}", + "namespace_id": "${connector_namespace_id}", + "namespace_name": "default-connector-namespace", "connector_resource_version": ${response.object.spec.connector_resource_version}, "connector_type_id": "aws-sqs-source-v1alpha1", "connector_spec": { @@ -616,7 +622,7 @@ Feature: connector agent API # Now lets verify connector upgrades due to catalog updates Given I am logged in as "Ricky Bobby" - And I GET path "/v1/admin/kafka_connector_clusters/" + And I GET path "/v1/admin/kafka_connector_clusters" And the response code should be 200 And the ".items[0]" selection from the response should match json: """ @@ -625,7 +631,7 @@ Feature: connector agent API "id": "${response.items[0].id}", "kind": "ConnectorCluster", "created_at": "${response.items[0].created_at}", - "name": "New Cluster", + "name": "${response.items[0].name}", "owner": "${response.items[0].owner}", "modified_at": "${response.items[0].modified_at}", "status": { @@ -680,6 +686,7 @@ Feature: connector agent API "items": [{ "connector_id": "${connector_id}", + "namespace_id": "${connector_namespace_id}", "connector_type_id": "aws-sqs-source-v1alpha1", "channel": "stable", "shard_metadata": { @@ -785,6 +792,7 @@ Feature: connector agent API "items": [{ "connector_id": "${connector_id}", + "namespace_id": "${connector_namespace_id}", "connector_type_id": "aws-sqs-source-v1alpha1", "channel": "stable", "operator": { @@ -871,9 +879,9 @@ Feature: connector agent API When I GET path "/v1/kafka_connectors/${connector_id}" Then the response code should be 200 And the ".status.state" selection from the response should match "assigning" - And the ".deployment_location" selection from the response should match json: + And the ".namespace_id" selection from the response should match json: """ - {"kind": "addon"} + null """ @@ -891,6 +899,10 @@ Feature: connector agent API And the ".status.state" selection from the response should match "disconnected" Given I store the ".id" selection from the response as ${connector_cluster_id} + When I GET path "/v1/kafka_connector_namespaces/?search=cluster_id=${connector_cluster_id}" + Then the response code should be 200 + Given I store the ".items[0].id" selection from the response as ${connector_namespace_id} + When I GET path "/v1/kafka_connector_clusters/${connector_cluster_id}/addon_parameters" Then the response code should be 200 And get and store access token using the addon parameter response as ${shard_token} and clientID as ${clientID} @@ -928,10 +940,7 @@ Feature: connector agent API { "kind": "Connector", "name": "example 1", - "deployment_location": { - "kind": "addon", - "cluster_id": "${connector_cluster_id}" - }, + "namespace_id": "${connector_namespace_id}", "channel":"stable", "connector_type_id": "log_sink_0.1", "kafka": { diff --git a/internal/connector/test/integration/features/connector-api.feature b/internal/connector/test/integration/features/connector-api.feature index c6b77a985..04af88838 100644 --- a/internal/connector/test/integration/features/connector-api.feature +++ b/internal/connector/test/integration/features/connector-api.feature @@ -1102,10 +1102,6 @@ Feature: create a connector { "kind": "Connector", "name": "example 1", - "deployment_location": { - "kind": "addon", - "cluster_id": "default" - }, "kafka": { "id":"mykafka", "url": "kafka.hostname" @@ -1136,17 +1132,14 @@ Feature: create a connector } """ - Scenario: Greg tries to create a connector with an invalid deployment_location.kind + Scenario: Greg tries to create a connector with an invalid namespace_id Given I am logged in as "Greg" When I POST path "/v1/kafka_connectors?async=true" with json body: """ { "kind": "Connector", "name": "example 1", - "deployment_location": { - "kind": "WRONG", - "cluster_id": "default" - }, + "namespace_id": "default", "connector_type_id": "aws-sqs-source-v1alpha1", "kafka": { "id":"mykafka", @@ -1160,7 +1153,8 @@ Feature: create a connector "aws_queue_name_or_arn": "test", "aws_access_key": "test", "aws_secret_key": "test", - "aws_region": "east" + "aws_region": "east", + "kafka_topic": "test" } } """ @@ -1168,12 +1162,12 @@ Feature: create a connector And the response should match json: """ { - "code": "CONNECTOR-MGMT-33", - "href": "/api/connector_mgmt/v1/errors/33", - "id": "33", + "code": "CONNECTOR-MGMT-21", + "href": "/api/connector_mgmt/v1/errors/21", + "id": "21", "kind": "Error", "operation_id": "${response.operation_id}", - "reason": "deployment_location.kind is not valid. Must be one of: addon" + "reason": "namespace_id is not valid: KAFKAS-MGMT-9: failed to get connector namespace: record not found" } """ @@ -1185,10 +1179,6 @@ Feature: create a connector { "kind": "Connector", "name": "example 1", - "deployment_location": { - "kind": "addon", - "cluster_id": "default" - }, "connector_type_id": "aws-sqs-source-v1alpha1", "kafka": { "id":"mykafka", @@ -1243,10 +1233,6 @@ Feature: create a connector "kafka_topic": "test" }, "connector_type_id": "aws-sqs-source-v1alpha1", - "deployment_location": { - "cluster_id": "default", - "kind": "addon" - }, "href": "/api/connector_mgmt/v1/kafka_connectors/${connector_id}", "id": "${connector_id}", "kind": "Connector", @@ -1295,10 +1281,6 @@ Feature: create a connector "client_secret": "", "client_id": "myclient" }, - "deployment_location": { - "kind": "addon", - "cluster_id": "default" - }, "connector_type_id": "aws-sqs-source-v1alpha1", "channel": "stable", "connector": { @@ -1449,6 +1431,11 @@ Feature: create a connector "href": "/api/connector_mgmt/v1/kafka_connector_clusters", "id": "kafka_connector_clusters", "kind": "ConnectorClusterList" + }, + { + "href": "/api/connector_mgmt/v1/kafka_connector_namespaces", + "id": "kafka_connector_namespaces", + "kind": "ConnectorNamespaceList" } ] } @@ -1477,6 +1464,11 @@ Feature: create a connector "href": "/api/connector_mgmt/v1/kafka_connector_clusters", "id": "kafka_connector_clusters", "kind": "ConnectorClusterList" + }, + { + "href": "/api/connector_mgmt/v1/kafka_connector_namespaces", + "id": "kafka_connector_namespaces", + "kind": "ConnectorNamespaceList" } ] } @@ -1789,10 +1781,6 @@ Feature: create a connector { "kind": "Connector", "name": "example 1", - "deployment_location": { - "kind": "addon", - "cluster_id": "default" - }, "connector_type_id": "aws-sqs-source-v1alpha1", "kafka": { "id":"mykafka", @@ -1839,10 +1827,6 @@ Feature: create a connector }, "connector": {}, "connector_type_id": "foo", - "deployment_location": { - "cluster_id": "default", - "kind": "addon" - }, "href": "/api/connector_mgmt/v1/kafka_connectors/${connector_id}", "id": "${connector_id}", "kind": "Connector", @@ -1889,10 +1873,6 @@ Feature: create a connector "client_secret": "", "client_id": "myclient" }, - "deployment_location": { - "kind": "addon", - "cluster_id": "default" - }, "connector": {}, "connector_type_id": "foo", "channel": "stable", diff --git a/internal/connector/test/integration/features/connector-multitenancy-api.feature b/internal/connector/test/integration/features/connector-multitenancy-api.feature new file mode 100644 index 000000000..808ba23cb --- /dev/null +++ b/internal/connector/test/integration/features/connector-multitenancy-api.feature @@ -0,0 +1,678 @@ +Feature: connector namespaces API + In order to deploy connectors to an addon OSD cluster + As a regular user, I need to be able to create and manage namespaces in both evaluation and non-evaluation clusters + As an admin user, I need to be able to create and manage namespaces for other users and clusters + + Background: + Given the path prefix is "/api/connector_mgmt" + + # User for eval organization id 13640210 configured in internal/connector/test/integration/feature_test.go:27 + Given a user named "Gru" in organization "13640210" + + # eval users used in public API + Given a user named "Stuart" in organization "13640221" + Given I store userid for "Stuart" as ${stuart_user_id} + Given a user named "Kevin" in organization "13640222" + Given I store userid for "Kevin" as ${kevin_user_id} + Given a user named "Carl" in organization "13640223" + Given I store userid for "Carl" as ${carl_user_id} + + # eval users used in admin API + Given a user named "Dave" in organization "13640224" + Given I store userid for "Dave" as ${dave_user_id} + Given a user named "Phil" in organization "13640225" + Given I store userid for "Phil" as ${phil_user_id} + Given a user named "Tim" in organization "13640226" + Given I store userid for "Tim" as ${tim_user_id} + + # users in organization 13640230 + Given a user named "Dusty" in organization "13640230" + Given I store userid for "Dusty" as ${dusty_user_id} + Given a user named "Lucky" in organization "13640230" + Given I store userid for "Lucky" as ${lucky_user_id} + Given a user named "Ned" in organization "13640230" + Given I store userid for "Ned" as ${ned_user_id} + + # users in organization 13640231 + Given a user named "El Guapo" in organization "13640231" + Given I store userid for "El Guapo" as ${guapo_user_id} + + Scenario Outline: Create eval namespace + Given I am logged in as "Gru" + When I POST path "/v1/kafka_connector_clusters" with json body: + """ + { + "name": "Evaluation Cluster" + } + """ + Then the response code should be 202 + And the ".status.state" selection from the response should match "disconnected" + + Given I store the ".id" selection from the response as ${connector_cluster_id} + When I GET path "/v1/kafka_connector_clusters/${connector_cluster_id}/addon_parameters" + Then the response code should be 200 + And get and store access token using the addon parameter response as ${shard_token} and clientID as ${clientID} + And I remember keycloak client for cleanup with clientID: ${clientID} + + # There should be no namespaces at first for user + Given I am logged in as "" + When I GET path "/v1/kafka_connector_namespaces/" + Then the response code should be 200 + And the response should match json: + """ + { + "items": [], + "kind": "ConnectorNamespaceList", + "page": 1, + "size": 0, + "total": 0 + } + """ + + # Create an eval namespace + Given I am logged in as "" + When I POST path "/v1/kafka_connector_namespaces/eval" with json body: + """ + { + "name": "_namespace", + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ] + } + """ + Then the response code should be 201 + And the response should match json: + """ + { + "id": "${response.id}", + "kind": "ConnectorNamespace", + "href": "/api/connector_mgmt/v1/kafka_connector_namespaces/${response.id}", + "name": "_namespace", + "owner": "${}", + "version": ${response.version}, + "cluster_id": "${response.cluster_id}", + "created_at": "${response.created_at}", + "modified_at": "${response.modified_at}", + "expiration": "${response.expiration}", + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "tenant": { + "kind": "user", + "id": "${}" + } + } + """ + And I store the ".id" selection from the response as ${namespace_id} + + # eval namespace MUST be in list of user's namespaces + Given I GET path "/v1/kafka_connector_namespaces/" + Then the response code should be 200 + """ + { + "items": [ + { + "cluster_id": "${connector_cluster_id}", + "href": "${response.items[0].href}", + "id": "${namespace_id}", + "kind": "ConnectorNamespace", + "name": "_namespace", + "owner": "${}", + "version": ${response.items[0].version} + "created_at": "${response.items[0].created_at}", + "modified_at": "${response.items[0].modified_at}", + "expiration": "${response.expiration}", + "tenant": { + "kind": "user", + "id": "${}" + }, + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + } + ], + "kind": "ConnectorNamespaceList", + "page": 1, + "size": 1, + "total": 1 + } + """ + + # Eval namespace should expire and get deleted after 2 seconds as configured in internal/connector/test/integration/feature_test.go:27 + Given I sleep for 3 seconds + And I GET path "/v1/kafka_connector_namespaces/" + Then the response code should be 200 + And the ".total" selection from the response should match "0" + + # cleanup eval cluster + Given I am logged in as "Gru" + When I DELETE path "/v1/kafka_connector_clusters/${connector_cluster_id}" + Then the response code should be 204 + And the response should match "" + + Examples: + | user | user_id | + | Stuart | stuart_user_id | + | Kevin | kevin_user_id | + | Carl | carl_user_id | + + Scenario: Create namespaces in cluster for organization 13640230 + Given I am logged in as "Dusty" + When I POST path "/v1/kafka_connector_clusters" with json body: + """ + { + "name": "Dusty's Cluster" + } + """ + Then the response code should be 202 + And the ".status.state" selection from the response should match "disconnected" + + Given I store the ".id" selection from the response as ${connector_cluster_id} + When I GET path "/v1/kafka_connector_clusters/${connector_cluster_id}/addon_parameters" + Then the response code should be 200 + And get and store access token using the addon parameter response as ${shard_token} and clientID as ${clientID} + And I remember keycloak client for cleanup with clientID: ${clientID} + + # There should be default namespace at first for organization 13640230 + Given I am logged in as "Lucky" + When I GET path "/v1/kafka_connector_namespaces" + Then the response code should be 200 + And the response should match json: + """ + { + "items": [ + { + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.items[0].created_at}", + "href": "${response.items[0].href}", + "id": "${response.items[0].id}", + "kind": "ConnectorNamespace", + "modified_at": "${response.items[0].modified_at}", + "name": "default-connector-namespace", + "owner": "${dusty_user_id}", + "tenant": { + "kind": "organisation", + "id": "13640230" + }, + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "version": ${response.items[0].version} + } + ], + "kind": "ConnectorNamespaceList", + "page": 1, + "size": 1, + "total": 1 + } + """ + + # Create a namespace + Given I am logged in as "Lucky" + When I POST path "/v1/kafka_connector_namespaces/" with json body: + """ + { + "name": "shared_namespace", + "cluster_id": "${connector_cluster_id}", + "kind": "organisation", + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ] + } + """ + Then the response code should be 201 + And the response should match json: + """ + { + "id": "${response.id}", + "kind": "ConnectorNamespace", + "href": "/api/connector_mgmt/v1/kafka_connector_namespaces/${response.id}", + "name": "shared_namespace", + "owner": "${lucky_user_id}", + "version": ${response.version}, + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.created_at}", + "modified_at": "${response.modified_at}", + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "tenant": { + "kind": "organisation", + "id": "13640230" + } + } + """ + And I store the ".id" selection from the response as ${namespace_id} + + # All organization members MUST be able to see the org tenant namespace + Given I am logged in as "Ned" + When I GET path "/v1/kafka_connector_namespaces/?orderBy=name" + Then the response code should be 200 + And the response should match json: + """ + { + "items": [ + { + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.items[0].created_at}", + "href": "${response.items[0].href}", + "id": "${response.items[0].id}", + "kind": "ConnectorNamespace", + "modified_at": "${response.items[0].modified_at}", + "name": "default-connector-namespace", + "owner": "${dusty_user_id}", + "tenant": { + "kind": "organisation", + "id": "13640230" + }, + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "version": ${response.items[0].version} + }, + { + "id": "${namespace_id}", + "kind": "ConnectorNamespace", + "href": "/api/connector_mgmt/v1/kafka_connector_namespaces/${namespace_id}", + "name": "shared_namespace", + "owner": "${lucky_user_id}", + "version": ${response.items[1].version}, + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.items[1].created_at}", + "modified_at": "${response.items[1].modified_at}", + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "tenant": { + "kind": "organisation", + "id": "13640230" + } + } + ], + "kind": "ConnectorNamespaceList", + "page": 1, + "size": 2, + "total": 2 + } + """ + + # Delete namespace + Given I am logged in as "Lucky" + When I DELETE path "/v1/kafka_connector_namespaces/${namespace_id}" + Then the response code should be 204 + And I GET path "/v1/kafka_connector_namespaces" + And the response code should be 200 + And the response should match json: + """ + { + "items": [ + { + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.items[0].created_at}", + "href": "${response.items[0].href}", + "id": "${response.items[0].id}", + "kind": "ConnectorNamespace", + "modified_at": "${response.items[0].modified_at}", + "name": "default-connector-namespace", + "owner": "${dusty_user_id}", + "tenant": { + "kind": "organisation", + "id": "13640230" + }, + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "version": ${response.items[0].version} + } + ], + "kind": "ConnectorNamespaceList", + "page": 1, + "size": 1, + "total": 1 + } + """ + + # cleanup cluster + Given I am logged in as "Dusty" + When I DELETE path "/v1/kafka_connector_clusters/${connector_cluster_id}" + Then the response code should be 204 + And the response should match "" + + Scenario Outline: Use Admin API to create namespaces for end users + Given I am logged in as "" + + #----------------------------------------------------------------------------------- + # Create a target cluster, and get the shard access token. + # ----------------------------------------------------------------------------------- + When I POST path "/v1/kafka_connector_clusters" with json body: + """ + { + "name": "User 's Cluster" + } + """ + Then the response code should be 202 + And the ".status.state" selection from the response should match "disconnected" + + Given I store the ".id" selection from the response as ${connector_cluster_id} + When I GET path "/v1/kafka_connector_clusters/${connector_cluster_id}/addon_parameters" + Then the response code should be 200 + And get and store access token using the addon parameter response as ${shard_token} and clientID as ${clientID} + And I remember keycloak client for cleanup with clientID: ${clientID} + + #----------------------------------------------------------------------------------------------------------------- + # In this part of the Scenario we create connector namespaces + #----------------------------------------------------------------------------------------------------------------- + + # There should be default namespace first + Given I am logged in as "Ricky Bobby" + When I GET path "/v1/admin/kafka_connector_namespaces/?search=cluster_id=${connector_cluster_id}" + Then the response code should be 200 + And the response should match json: + """ + { + "items": [ + { + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.items[0].created_at}", + "href": "${response.items[0].href}", + "id": "${response.items[0].id}", + "kind": "ConnectorNamespace", + "modified_at": "${response.items[0].modified_at}", + "name": "default-connector-namespace", + "owner": "${}", + "tenant": { + "kind": "organisation", + "id": "${response.items[0].tenant.id}" + }, + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "version": ${response.items[0].version} + } + ], + "kind": "ConnectorNamespaceList", + "page": 1, + "size": 1, + "total": 1 + } + """ + + # Create a namespace + Given I am logged in as "Ricky Bobby" + When I POST path "/v1/admin/kafka_connector_namespaces/" with json body: + """ + { + "name": "_namespace", + "cluster_id": "${connector_cluster_id}", + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "tenant": { + "kind": "user", + "id": "${}" + } + } + """ + Then the response code should be 201 + And the response should match json: + """ + { + "id": "${response.id}", + "kind": "ConnectorNamespace", + "href": "/api/connector_mgmt/v1/kafka_connector_namespaces/${response.id}", + "name": "_namespace", + "owner": "${}", + "version": ${response.version}, + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.created_at}", + "modified_at": "${response.modified_at}", + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "tenant": { + "kind": "user", + "id": "${}" + } + } + """ + And I store the ".id" selection from the response as ${namespace_id} + + # Delete namespace + Given I am logged in as "Ricky Bobby" + When I DELETE path "/v1/admin/kafka_connector_namespaces/${namespace_id}" + Then the response code should be 204 + And I GET path "/v1/admin/kafka_connector_clusters/${connector_cluster_id}/namespaces" + And the response code should be 200 + And the response should match json: + """ + { + "items": [ + { + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.items[0].created_at}", + "href": "${response.items[0].href}", + "id": "${response.items[0].id}", + "kind": "ConnectorNamespace", + "modified_at": "${response.items[0].modified_at}", + "name": "default-connector-namespace", + "owner": "${}", + "tenant": { + "kind": "organisation", + "id": "${response.items[0].tenant.id}" + }, + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "version": ${response.items[0].version} + } + ], + "kind": "ConnectorNamespaceList", + "page": 1, + "size": 1, + "total": 1 + } + """ + + #cleanup + Given I am logged in as "" + When I DELETE path "/v1/kafka_connector_clusters/${connector_cluster_id}" + Then the response code should be 204 + And the response should match "" + + Examples: + | user | user_id | + | Dave | dave_user_id | + | Phil | phil_user_id | + | Tim | tim_user_id | + + Scenario: Use Admin API to create namespace for organization 13640231. + Given I am logged in as "El Guapo" + + #----------------------------------------------------------------------------------- + # Create a target cluster, and get the shard access token. + # ----------------------------------------------------------------------------------- + When I POST path "/v1/kafka_connector_clusters" with json body: + """ + { + "name": "El Guapo's Cluster" + } + """ + Then the response code should be 202 + And the ".status.state" selection from the response should match "disconnected" + + Given I store the ".id" selection from the response as ${connector_cluster_id} + When I GET path "/v1/kafka_connector_clusters/${connector_cluster_id}/addon_parameters" + Then the response code should be 200 + And get and store access token using the addon parameter response as ${shard_token} and clientID as ${clientID} + And I remember keycloak client for cleanup with clientID: ${clientID} + + #----------------------------------------------------------------------------------------------------------------- + # In this part of the Scenario we create connector namespace + #----------------------------------------------------------------------------------------------------------------- + + # There should be default namespace in cluster ${connector_cluster_id} + Given I am logged in as "Ricky Bobby" + When I GET path "/v1/admin/kafka_connector_namespaces/?search=cluster_id=${connector_cluster_id}" + Then the response code should be 200 + And the response should match json: + """ + { + "items": [ + { + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.items[0].created_at}", + "href": "${response.items[0].href}", + "id": "${response.items[0].id}", + "kind": "ConnectorNamespace", + "modified_at": "${response.items[0].modified_at}", + "name": "default-connector-namespace", + "owner": "${guapo_user_id}", + "tenant": { + "kind": "organisation", + "id": "13640231" + }, + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "version": ${response.items[0].version} + } + ], + "kind": "ConnectorNamespaceList", + "page": 1, + "size": 1, + "total": 1 + } + """ + + # Create a namespace + Given I am logged in as "Ricky Bobby" + When I POST path "/v1/admin/kafka_connector_namespaces/" with json body: + """ + { + "name": "amigos_namespace", + "cluster_id": "${connector_cluster_id}", + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "tenant": { + "kind": "organisation", + "id": "13640231" + } + } + """ + Then the response code should be 201 + And the response should match json: + """ + { + "id": "${response.id}", + "kind": "ConnectorNamespace", + "href": "/api/connector_mgmt/v1/kafka_connector_namespaces/${response.id}", + "name": "amigos_namespace", + "owner": "Ricky Bobby", + "version": ${response.version}, + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.created_at}", + "modified_at": "${response.modified_at}", + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "tenant": { + "kind": "organisation", + "id": "13640231" + } + } + """ + And I store the ".id" selection from the response as ${namespace_id} + + # Delete namespace + Given I am logged in as "Ricky Bobby" + When I DELETE path "/v1/admin/kafka_connector_namespaces/${namespace_id}" + Then the response code should be 204 + And I GET path "/v1/admin/kafka_connector_namespaces?search=cluster_id=${connector_cluster_id}" + And the response code should be 200 + And the response should match json: + """ + { + "items": [ + { + "cluster_id": "${connector_cluster_id}", + "created_at": "${response.items[0].created_at}", + "href": "${response.items[0].href}", + "id": "${response.items[0].id}", + "kind": "ConnectorNamespace", + "modified_at": "${response.items[0].modified_at}", + "name": "default-connector-namespace", + "owner": "${guapo_user_id}", + "tenant": { + "kind": "organisation", + "id": "13640231" + }, + "annotations": [ + { + "name": "connector_mgmt.api.openshift.com/profile", + "value": "default-profile" + } + ], + "version": ${response.items[0].version} + } + ], + "kind": "ConnectorNamespaceList", + "page": 1, + "size": 1, + "total": 1 + } + """ + + #cleanup + Given I am logged in as "El Guapo" + When I DELETE path "/v1/kafka_connector_clusters/${connector_cluster_id}" + Then the response code should be 204 + And the response should match "" diff --git a/internal/connector/test/integration/features/connector-old-path.feature b/internal/connector/test/integration/features/connector-old-path.feature index f15839cac..56e462ea8 100644 --- a/internal/connector/test/integration/features/connector-old-path.feature +++ b/internal/connector/test/integration/features/connector-old-path.feature @@ -21,10 +21,6 @@ Feature: the old connectors path are still valid "name": "example 1", "kafka_id":"mykafka" }, - "deployment_location": { - "kind": "addon", - "cluster_id": "default" - }, "kafka": { "bootstrap_server": "kafka.hostname", "client_id": "myclient", diff --git a/openapi/connector_mgmt-private-admin.yaml b/openapi/connector_mgmt-private-admin.yaml index 87ec647c0..da93cdc61 100644 --- a/openapi/connector_mgmt-private-admin.yaml +++ b/openapi/connector_mgmt-private-admin.yaml @@ -15,12 +15,14 @@ servers: tags: - name: Connector Clusters Admin description: "" + - name: Connector Namespaces Admin + description: "" paths: # # These are the connector related admin APIs # - /api/connector_mgmt/v1/admin/kafka_connector_clusters/: + /api/connector_mgmt/v1/admin/kafka_connector_clusters: get: tags: - Connector Clusters Admin @@ -55,10 +57,64 @@ paths: $ref: "connector_mgmt.yaml#/components/examples/500Example" description: Unexpected error occurred security: - - Bearer: [] + - Bearer: [ ] operationId: listConnectorClusters summary: Returns a list of connector clusters + /api/connector_mgmt/v1/admin/kafka_connector_clusters/{connector_cluster_id}/namespaces: + get: + tags: + - Connector Clusters Admin + parameters: + - name: connector_cluster_id + description: The id of the connector cluster + schema: + type: string + in: path + required: true + - $ref: "connector_mgmt.yaml#/components/parameters/page" + - $ref: "connector_mgmt.yaml#/components/parameters/size" + - $ref: "connector_mgmt.yaml#/components/parameters/orderBy" + - $ref: "connector_mgmt.yaml#/components/parameters/search" + responses: + "200": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/ConnectorNamespaceList" + description: Connector namespaces + "401": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 401Example: + $ref: "connector_mgmt.yaml#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 404Example: + $ref: "connector_mgmt.yaml#/components/examples/404Example" + description: No matching connector namespace exists + "500": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 500Example: + $ref: "connector_mgmt.yaml#/components/examples/500Example" + description: Unexpected error occurred + security: + - Bearer: [ ] + operationId: getClusterNamespaces + summary: Get a list of available connector namespaces in cluster + /api/connector_mgmt/v1/admin/kafka_connector_clusters/{connector_cluster_id}/upgrades/type: parameters: - name: connector_cluster_id @@ -107,7 +163,7 @@ paths: $ref: "connector_mgmt.yaml#/components/examples/500Example" description: Unexpected error occurred security: - - Bearer: [] + - Bearer: [ ] operationId: getConnectorUpgradesByType summary: Get a list of available connector type upgrades @@ -145,7 +201,7 @@ paths: $ref: "connector_mgmt.yaml#/components/examples/500Example" description: Unexpected error occurred security: - - Bearer: [] + - Bearer: [ ] operationId: upgradeConnectorsByType summary: upgrade a connector cluster requestBody: @@ -206,7 +262,7 @@ paths: $ref: "connector_mgmt.yaml#/components/examples/500Example" description: Unexpected error occurred security: - - Bearer: [] + - Bearer: [ ] operationId: getConnectorUpgradesByOperator summary: Get a list of available connector operator upgrades @@ -244,7 +300,7 @@ paths: $ref: "connector_mgmt.yaml#/components/examples/500Example" description: Unexpected error occurred security: - - Bearer: [] + - Bearer: [ ] operationId: upgradeConnectorsByOperator summary: upgrade a connector cluster requestBody: @@ -257,6 +313,153 @@ paths: $ref: "#/components/schemas/ConnectorAvailableOperatorUpgrade" required: true + /api/connector_mgmt/v1/admin/kafka_connector_namespaces: + get: + tags: + - Connector Namespaces Admin + parameters: + - $ref: "connector_mgmt.yaml#/components/parameters/page" + - $ref: "connector_mgmt.yaml#/components/parameters/size" + - $ref: "connector_mgmt.yaml#/components/parameters/orderBy" + - $ref: "connector_mgmt.yaml#/components/parameters/search" + responses: + "200": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/ConnectorNamespaceList" + description: Connector namespaces + "401": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 401Example: + $ref: "connector_mgmt.yaml#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 404Example: + $ref: "connector_mgmt.yaml#/components/examples/404Example" + description: No matching connector namespace exists + "500": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 500Example: + $ref: "connector_mgmt.yaml#/components/examples/500Example" + description: Unexpected error occurred + security: + - Bearer: [ ] + operationId: getConnectorNamespaces + summary: Get a list of available connector namespaces + + post: + tags: + - Connector Namespaces Admin + responses: + "202": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/ConnectorNamespace" + description: Accepted + "401": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 401Example: + $ref: "connector_mgmt.yaml#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 404Example: + $ref: "connector_mgmt.yaml#/components/examples/404Example" + description: No matching connector namespace exists + "500": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 500Example: + $ref: "connector_mgmt.yaml#/components/examples/500Example" + description: Unexpected error occurred + security: + - Bearer: [ ] + operationId: createConnectorNamespace + summary: Create a connector namespace + requestBody: + description: Namespace to create + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectorNamespaceWithTenantRequest" + required: true + + /api/connector_mgmt/v1/admin/kafka_connector_namespaces/${namespace_id}: + parameters: + - name: namespace_id + description: The name of the namespace to delete + schema: + type: string + in: path + required: true + delete: + tags: + - Connector Clusters Admin + responses: + "204": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + description: Deleted + "401": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 401Example: + $ref: "connector_mgmt.yaml#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 404Example: + $ref: "connector_mgmt.yaml#/components/examples/404Example" + description: No matching connector cluster exists + "500": + content: + application/json: + schema: + $ref: "connector_mgmt.yaml#/components/schemas/Error" + examples: + 500Example: + $ref: "connector_mgmt.yaml#/components/examples/500Example" + description: Unexpected error occurred + security: + - Bearer: [ ] + operationId: deleteConnectorNamespace + summary: Delete a connector namespace + components: schemas: ConnectorAvailableTypeUpgradeList: @@ -275,6 +478,8 @@ components: properties: connector_id: type: string + namespace_id: + type: string connector_type_id: type: string channel: @@ -305,6 +510,8 @@ components: properties: connector_id: type: string + namespace_id: + type: string connector_type_id: type: string channel: @@ -317,6 +524,20 @@ components: available_id: type: string + ConnectorNamespaceWithTenantRequest: + required: + - name + - cluster_id + - tenant + allOf: + - $ref: "connector_mgmt.yaml#/components/schemas/ConnectorNamespaceEvalRequest" + - type: object + properties: + cluster_id: + type: string + tenant: + $ref: "connector_mgmt.yaml#/components/schemas/ConnectorNamespaceTenant" + securitySchemes: Bearer: scheme: bearer diff --git a/openapi/connector_mgmt-private.yaml b/openapi/connector_mgmt-private.yaml index f4b18dd45..7ca854986 100644 --- a/openapi/connector_mgmt-private.yaml +++ b/openapi/connector_mgmt-private.yaml @@ -313,6 +313,10 @@ components: format: int64 connector_type_id: type: string + namespace_id: + type: string + namespace_name: + type: string connector_spec: type: object allow_upgrade: diff --git a/openapi/connector_mgmt.yaml b/openapi/connector_mgmt.yaml index 4498335a4..1fec4bdb2 100644 --- a/openapi/connector_mgmt.yaml +++ b/openapi/connector_mgmt.yaml @@ -35,7 +35,7 @@ paths: - Connector Service operationId: getVersionMetadata summary: Returns the version metadata - description: Returns the version metadata + description: Returns the version metadata responses: "200": content: @@ -63,7 +63,7 @@ paths: - Bearer: [ ] operationId: getConnectorTypeByID summary: Get a connector type by id - description: Get a connector type by id + description: Get a connector type by id responses: "200": content: @@ -361,7 +361,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ConnectorRequest" - + required: true responses: "202": @@ -697,6 +697,367 @@ paths: $ref: "#/components/examples/500Example" description: Unexpected error occurred + "/api/connector_mgmt/v1/kafka_connector_clusters/{connector_cluster_id}/namespaces": + parameters: + - name: connector_cluster_id + description: The id of the connector cluster + schema: + type: string + in: path + required: true + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/size" + - $ref: "#/components/parameters/orderBy" + - $ref: "#/components/parameters/search" + get: + tags: + - Connector Clusters + security: + - Bearer: [ ] + operationId: getConnectorClusterNamespaces + summary: Get a connector cluster's namespaces + description: Get a connector cluster's namespaces + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectorNamespaceList" + description: The namespaces visible to user in the cluster. + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 401Example: + $ref: "#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 404Example: + $ref: "#/components/examples/404Example" + description: No matching connector cluster type exists + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 500Example: + $ref: "#/components/examples/500Example" + description: Unexpected error occurred + + # + # Connector Namespaces + # + + "/api/connector_mgmt/v1/kafka_connector_namespaces": + post: + tags: + - Connector Namespaces + operationId: createConnectorNamespace + security: + - Bearer: [ ] + summary: Create a new connector namespace + description: Create a new connector namespace + requestBody: + description: Connector namespace data + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectorNamespaceRequest" + examples: + ConnectorNamespaceCreateExample: + $ref: "#/components/examples/ConnectorNamespaceCreateExample" + required: true + responses: + "202": + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectorNamespace" + description: Accepted + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 400CreationExample: + $ref: "#/components/examples/400CreationExample" + description: Validation errors occurred + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 401Example: + $ref: "#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 404Example: + $ref: "#/components/examples/404Example" + description: The requested resource doesn't exist + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 500Example: + $ref: "#/components/examples/500Example" + description: An unexpected error occurred creating the connector namespace + get: + tags: + - Connector Namespaces + security: + - Bearer: [ ] + operationId: listConnectorNamespaces + summary: Returns a list of connector namespaces + description: Returns a list of connector namespaces + parameters: + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/size" + - $ref: "#/components/parameters/orderBy" + - $ref: "#/components/parameters/search" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectorNamespaceList" + description: A list of connector namespaces + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 401Example: + $ref: "#/components/examples/401Example" + description: Auth token is invalid + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 500Example: + $ref: "#/components/examples/500Example" + description: Unexpected error occurred + + "/api/connector_mgmt/v1/kafka_connector_namespaces/{connector_namespace_id}": + parameters: + - name: connector_namespace_id + description: The id of the connector namespace + schema: + type: string + in: path + required: true + get: + tags: + - Connector Namespaces + security: + - Bearer: [ ] + operationId: getConnectorNamespace + summary: Get a connector namespace + description: Get a connector namespace + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectorNamespace" + description: The connector namespace matching the request + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 401Example: + $ref: "#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 404Example: + $ref: "#/components/examples/404Example" + description: No matching connector namespace type exists + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 500Example: + $ref: "#/components/examples/500Example" + description: Unexpected error occurred + patch: + tags: + - Connector Namespaces + security: + - Bearer: [ ] + operationId: updateConnectorNamespaceById + summary: udpate a connector namespace + description: udpate a connector namespace + requestBody: + description: Data to update namespace with + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectorNamespacePatchRequest" + required: true + responses: + "204": + description: Namespace status is updated + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 401Example: + $ref: "#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 404Example: + $ref: "#/components/examples/404Example" + description: No matching connector namespace exists + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 500Example: + $ref: "#/components/examples/500Example" + description: Unexpected error occurred + delete: + tags: + - Connector Namespaces + security: + - Bearer: [ ] + operationId: deleteConnectorNamespace + summary: Delete a connector namespace + description: Delete a connector namespace + responses: + "204": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + description: Deleted + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 401Example: + $ref: "#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 404DeleteExample: + $ref: "#/components/examples/404DeleteExample" + description: No resource with specified ID exists + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 500DeleteExample: + $ref: "#/components/examples/500DeleteExample" + description: Unexpected error occurred + + "/api/connector_mgmt/v1/kafka_connector_namespaces/eval": + post: + tags: + - Connector Namespaces + operationId: createEvaluationNamespace + security: + - Bearer: [ ] + summary: Create a new short lived evaluation connector namespace + description: Create a new evaluation connector namespace + requestBody: + description: Connector namespace data + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectorNamespaceEvalRequest" + examples: + ConnectorNamespaceEvalCreateExample: + $ref: "#/components/examples/ConnectorNamespaceEvalCreateExample" + required: true + responses: + "202": + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectorNamespace" + description: Accepted + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 400CreationExample: + $ref: "#/components/examples/400CreationExample" + description: Validation errors occurred + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 401Example: + $ref: "#/components/examples/401Example" + description: Auth token is invalid + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 404Example: + $ref: "#/components/examples/404Example" + description: The requested resource doesn't exist + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + examples: + 500Example: + $ref: "#/components/examples/500Example" + description: An unexpected error occurred creating the connector namespace + components: schemas: @@ -776,25 +1137,6 @@ components: client_secret: type: string - DeploymentLocation: - discriminator: - propertyName: kind - mapping: - ConnectorCluster: "#/components/schemas/ConnectorClusterTarget" - oneOf: - - $ref: "#/components/schemas/ConnectorClusterTarget" - - ConnectorClusterTarget: - description: "Targets workloads to an addon cluster" - type: object - required: - - kind - properties: - kind: - type: string - cluster_id: - type: string - VersionMetadata: allOf: - $ref: "#/components/schemas/ObjectReference" @@ -957,17 +1299,16 @@ components: required: - name - connector_type_id - - deployment_location - desired_state properties: name: type: string connector_type_id: type: string + namespace_id: + type: string channel: $ref: "#/components/schemas/Channel" - deployment_location: - $ref: "#/components/schemas/DeploymentLocation" desired_state: $ref: "#/components/schemas/ConnectorDesiredState" @@ -1080,6 +1421,114 @@ components: items: $ref: "#/components/schemas/ConnectorType" + # + # Connector Namespaces + # + ConnectorNamespaceRequestMeta: + type: object + properties: + name: + type: string + annotations: + type: array + items: + type: object + properties: + name: + type: string + value: + type: string + required: + - name + - value + + ConnectorNamespaceMeta: + allOf: + - $ref: "#/components/schemas/ObjectMeta" + - $ref: "#/components/schemas/ConnectorNamespaceRequestMeta" + + ConnectorNamespaceTenantKind: + type: string + enum: + - user + - organisation + + ConnectorNamespaceTenant: + type: object + required: + - kind + - id + properties: + kind: + $ref: "#/components/schemas/ConnectorNamespaceTenantKind" + id: + description: Either user or organisation id depending on the value of kind + type: string + + ConnectorNamespaceRequest: + description: A connector namespace create request + required: + - name + - cluster_id + - kind + allOf: + - $ref: "#/components/schemas/ConnectorNamespaceRequestMeta" + - type: object + properties: + cluster_id: + type: string + kind: + $ref: "#/components/schemas/ConnectorNamespaceTenantKind" + + ConnectorNamespacePatchRequest: + description: A connector namespace patch request + allOf: + - $ref: "#/components/schemas/ConnectorNamespaceRequestMeta" + - type: object + + ConnectorNamespaceEvalRequest: + description: An evaluation connector namespace create request + required: + - name + allOf: + - $ref: "#/components/schemas/ConnectorNamespaceRequestMeta" + + ConnectorNamespaceList: + allOf: + - $ref: "#/components/schemas/List" + - type: object + properties: + items: + type: array + items: + $ref: "#/components/schemas/ConnectorNamespace" + + ConnectorNamespace: + description: A connector namespace + allOf: + - $ref: "#/components/schemas/ObjectReference" + - $ref: "#/components/schemas/ConnectorNamespaceMeta" + - type: object + properties: + name: + type: string + version: + type: integer + format: int64 + cluster_id: + type: string + expiration: + #format: date-time + type: string + tenant: + $ref: "#/components/schemas/ConnectorNamespaceTenant" + required: + - id + - name + - version + - cluster_id + - tenant + parameters: id: name: id @@ -1204,7 +1653,7 @@ components: schema: properties: common: - required: [] + required: [ ] title: Log type: object properties: @@ -1284,9 +1733,7 @@ components: ConnectorCreateExample: value: name: MyLogger - deployment_location: - kind: "ConnectoCluster" - cluster_id: "9bsv0s7tne7g02gh5g4g" + namespace_id: "9bsv0s7tne7g02gh5g4g" kafka: id: "9bsv0s6brfr002pfnkh0" client_id: "srvc-acct-162ef2d8-0209-4117-8462-df63c2025c26" @@ -1317,6 +1764,19 @@ components: error_handling: dead_letter_queue: topic: "dlq" + ConnectorNamespaceCreateExample: + value: + name: "MyNamespace" + cluster_id: "9bsv0s7tne7g02gh5g4g" + annotations: + - name: "connector_mgmt.api.openshift.com/profile" + value: "default-profile" + ConnectorNamespaceEvalCreateExample: + value: + name: "MyEvalNamespace" + annotations: + - name: "connector_mgmt.api.openshift.com/profile" + value: "default-profile" 400CreationExample: value: id: "103" diff --git a/pkg/handlers/validation_builder.go b/pkg/handlers/validation_builder.go index 17e9a062c..17dc8a90e 100644 --- a/pkg/handlers/validation_builder.go +++ b/pkg/handlers/validation_builder.go @@ -54,6 +54,6 @@ func IsOneOf(options ...string) ValidateOption { } } } - return errors.MinimumFieldLengthNotReached("%s is not valid. Must be one of: %s", field, strings.Join(options, ", ")) + return errors.BadRequest("%s is not valid. Must be one of: %s", field, strings.Join(options, ", ")) } } diff --git a/test/cucumber/cucumber.go b/test/cucumber/cucumber.go index 3171d0534..c3b15cc38 100644 --- a/test/cucumber/cucumber.go +++ b/test/cucumber/cucumber.go @@ -59,10 +59,11 @@ type TestSuite struct { // TestUser represents a user that can login to the system. The same users are shared by // the different test scenarios. type TestUser struct { - Name string - Token string - Ctx context.Context - Mu sync.Mutex + Name string + Token string + UserName string + Ctx context.Context + Mu sync.Mutex } // TestScenario holds that state of single scenario. It is not accessed @@ -168,10 +169,10 @@ func (s *TestScenario) Expand(value string, skippedVars []string) (result string switch next := next.(type) { case string: return next - case float64: - return fmt.Sprintf("%f", next) case float32: - return fmt.Sprintf("%f", next) + case float64: + // handle int64 returned as float in json + return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", next), "0"), ".") case nil: rerr = fmt.Errorf("field ${%s} not found in json response:\n%s\n", name, string(session.RespBytes)) return "" diff --git a/test/cucumber/http_response.go b/test/cucumber/http_response.go index 8443d74d9..41e2d5555 100644 --- a/test/cucumber/http_response.go +++ b/test/cucumber/http_response.go @@ -31,7 +31,7 @@ // Then the ".deployment_location" selection from the response should match json: // """ // { -// "kind": "addon", +// "namespace_id": "default" // } // """ package cucumber diff --git a/test/cucumber/user.go b/test/cucumber/user.go index 94fc1a363..78d2174d4 100644 --- a/test/cucumber/user.go +++ b/test/cucumber/user.go @@ -27,6 +27,7 @@ func init() { ctx.Step(`^I am logged in as "([^"]*)"$`, s.iAmLoggedInAs) ctx.Step(`^I set the "([^"]*)" header to "([^"]*)"$`, s.iSetTheHeaderTo) ctx.Step(`^an admin user named "([^"]+)" with roles "([^"]+)"$`, s.Suite.createAdminUserNamed) + ctx.Step(`^I store userid for "([^"]+)" as \${([^"]*)}$`, s.storeUserId) }) } @@ -58,6 +59,7 @@ func (s *TestSuite) createUserNamedInOrganization(name string, orgid string) err s.users[name] = &TestUser{ Name: name, Token: token, + UserName: account.Username(), Ctx: context.WithValue(context.Background(), compat.ContextAccessToken, token), } return nil @@ -92,11 +94,24 @@ func (s *TestSuite) createAdminUserNamed(name, roles string) error { s.users[name] = &TestUser{ Name: name, Token: token, + UserName: account.Username(), Ctx: context.WithValue(context.Background(), compat.ContextAccessToken, token), } return nil } +func (s *TestScenario) storeUserId(name, varName string) error { + s.Suite.Mu.Lock() + defer s.Suite.Mu.Unlock() + + user := s.Suite.users[name] + if user != nil { + s.Variables[varName] = user.UserName + } + + return nil +} + func (s *TestScenario) iAmLoggedInAs(name string) error { s.Session().Header.Del("Authorization") s.CurrentUser = name