diff --git a/.vscode/launch.json b/.vscode/launch.json index 7210fae..5311871 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "--webhook-bind-address=:2443", "--webhook-tls-directory=${workspaceFolder}/.local/ssl", "--enableWebhooks=false", - "--cluster-resource-namespace=default", + "--refresh-token-auto-renewal-interval=1h", "--sap-binding-metadata" ] }, diff --git a/Dockerfile b/Dockerfile index 9566cbb..fc0a62e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ # Build the manager binary -FROM --platform=$BUILDPLATFORM golang:1.23.0 as builder +FROM --platform=$BUILDPLATFORM golang:1.22.5 AS builder + ARG TARGETOS ARG TARGETARCH diff --git a/go.mod b/go.mod index 7506b26..a1bf850 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/sap/cf-service-operator go 1.22.5 require ( - github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5 + github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.9 github.com/go-logr/logr v1.4.1 github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 github.com/onsi/ginkgo/v2 v2.15.0 @@ -49,26 +49,25 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.8.4 // indirect + github.com/stretchr/testify v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.20.0 // indirect - golang.org/x/oauth2 v0.12.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.17.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 5e90dbd..abc2391 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5 h1:D6Kc4/ockmFHKNKUSeMNt3G82rddwZV6xWbjIXv8oaE= -github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5/go.mod h1:hFja9UPzLkfNxTF8EM0sqs7K+J2BCoLcjNmrMbP24xY= +github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.9 h1:HK3+nJEPgwlhc5H74aw/V4mVowqWaTKGjHONdVQQ2Vw= +github.com/cloudfoundry/go-cfclient/v3 v3.0.0-alpha.9/go.mod h1:eUjFfpsU3lRv388wKlXMmkQfsJ9pveUHZEia7AoBCPY= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -40,7 +40,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -92,8 +91,8 @@ github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= -github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= -github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -115,13 +114,15 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -140,14 +141,13 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -162,7 +162,6 @@ golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= @@ -180,8 +179,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= diff --git a/internal/cf/binding.go b/internal/cf/binding.go index f7f66c2..fa8cbdb 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -11,8 +11,8 @@ import ( "fmt" "strconv" - cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" - cfresource "github.com/cloudfoundry-community/go-cfclient/v3/resource" + cfclient "github.com/cloudfoundry/go-cfclient/v3/client" + cfresource "github.com/cloudfoundry/go-cfclient/v3/resource" "github.com/pkg/errors" "github.com/sap/cf-service-operator/internal/facade" @@ -37,7 +37,8 @@ func (bn *bindingFilterName) getListOptions() *cfclient.ServiceCredentialBinding func (bo *bindingFilterOwner) getListOptions() *cfclient.ServiceCredentialBindingListOptions { listOpts := cfclient.NewServiceCredentialBindingListOptions() - listOpts.LabelSelector.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, bo.owner)) + listOpts.LabelSel = make(map[string]cfclient.ExclusionFilter) + listOpts.LabelSel.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, bo.owner)) return listOpts } @@ -113,7 +114,7 @@ func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]str return &facade.Binding{ Guid: guid, - Name: name, + Name: *name, Owner: bindingOpts["owner"], Generation: generation, ParameterHash: parameterHash, @@ -161,5 +162,6 @@ func (c *spaceClient) UpdateBinding(ctx context.Context, guid string, generation } func (c *spaceClient) DeleteBinding(ctx context.Context, guid string) error { - return c.client.ServiceCredentialBindings.Delete(ctx, guid) + _ /*jobId*/, err := c.client.ServiceCredentialBindings.Delete(ctx, guid) + return err } diff --git a/internal/cf/client.go b/internal/cf/client.go index 4285f36..2f8bdda 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -7,12 +7,15 @@ package cf import ( "fmt" + "net/http" "sync" + "time" - cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" - cfconfig "github.com/cloudfoundry-community/go-cfclient/v3/config" + cfclient "github.com/cloudfoundry/go-cfclient/v3/client" + cfconfig "github.com/cloudfoundry/go-cfclient/v3/config" "sigs.k8s.io/controller-runtime/pkg/metrics" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/facade" cfmetrics "github.com/sap/cf-service-operator/pkg/metrics" ) @@ -44,10 +47,11 @@ type clientIdentifier struct { } type clientCacheEntry struct { - url string - username string - password string - client cfclient.Client + url string + username string + password string + client cfclient.Client + expiresAt time.Time } var ( @@ -68,17 +72,22 @@ func newOrganizationClient(organizationName string, url string, username string, if password == "" { return nil, fmt.Errorf("missing or empty password") } - config, err := cfconfig.NewUserPassword(url, username, password) + + // prepare HTTP client with metrics + httpClient := &http.Client{} + transport, err := cfmetrics.AddMetricsToTransport(httpClient.Transport, metrics.Registry, "cf-api", url) if err != nil { return nil, err } - httpClient := config.HTTPClient() - transport, err := cfmetrics.AddMetricsToTransport(httpClient.Transport, metrics.Registry, "cf-api", url) + httpClient.Transport = transport + + // create CF client + config, err := cfconfig.New(url, + cfconfig.UserPassword(username, password), + cfconfig.HttpClient(httpClient)) if err != nil { return nil, err } - httpClient.Transport = transport - config.WithHTTPClient(httpClient) c, err := cfclient.New(config) if err != nil { return nil, err @@ -99,17 +108,22 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri if password == "" { return nil, fmt.Errorf("missing or empty password") } - config, err := cfconfig.NewUserPassword(url, username, password) + + // add metrics to HTTP client + httpClient := &http.Client{} + transport, err := cfmetrics.AddMetricsToTransport(httpClient.Transport, metrics.Registry, "cf-api", url) if err != nil { return nil, err } - httpClient := config.HTTPClient() - transport, err := cfmetrics.AddMetricsToTransport(httpClient.Transport, metrics.Registry, "cf-api", url) + httpClient.Transport = transport + + // create CF client + config, err := cfconfig.New(url, + cfconfig.UserPassword(username, password), + cfconfig.HttpClient(httpClient)) if err != nil { return nil, err } - httpClient.Transport = transport - config.WithHTTPClient(httpClient) c, err := cfclient.New(config) if err != nil { return nil, err @@ -117,7 +131,7 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri return &spaceClient{spaceGuid: spaceGuid, client: *c}, nil } -func NewOrganizationClient(organizationName string, url string, username string, password string) (facade.OrganizationClient, error) { +func NewOrganizationClient(organizationName string, url string, username string, password string, config *config.Config) (facade.OrganizationClient, error) { cacheMutex.Lock() defer cacheMutex.Unlock() @@ -135,6 +149,10 @@ func NewOrganizationClient(organizationName string, url string, username string, delete(clientCache, identifier) isInCache = false } + if cacheEntry.expiresAt.Before(time.Now()) { + delete(clientCache, identifier) + isInCache = false + } } if !isInCache { @@ -142,14 +160,21 @@ func NewOrganizationClient(organizationName string, url string, username string, client, err = newOrganizationClient(organizationName, url, username, password) if err == nil { // add CF client to cache - clientCache[identifier] = &clientCacheEntry{url: url, username: username, password: password, client: client.client} + clientCache[identifier] = &clientCacheEntry{ + url: url, + username: username, + password: password, + client: client.client, + expiresAt: time.Now().Add(config.RefreshTokenAutoRenewalInterval), + } + } } return client, err } -func NewSpaceClient(spaceGuid string, url string, username string, password string) (facade.SpaceClient, error) { +func NewSpaceClient(spaceGuid string, url string, username string, password string, config *config.Config) (facade.SpaceClient, error) { cacheMutex.Lock() defer cacheMutex.Unlock() @@ -167,6 +192,10 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri delete(clientCache, identifier) isInCache = false } + if cacheEntry.expiresAt.Before(time.Now()) { + delete(clientCache, identifier) + isInCache = false + } } if !isInCache { @@ -174,14 +203,20 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri client, err = newSpaceClient(spaceGuid, url, username, password) if err == nil { // add CF client to cache - clientCache[identifier] = &clientCacheEntry{url: url, username: username, password: password, client: client.client} + clientCache[identifier] = &clientCacheEntry{ + url: url, + username: username, + password: password, + client: client.client, + expiresAt: time.Now().Add(config.RefreshTokenAutoRenewalInterval), + } } } return client, err } -func NewSpaceHealthChecker(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { +func NewSpaceHealthChecker(spaceGuid string, url string, username string, password string, config *config.Config) (facade.SpaceHealthChecker, error) { cacheMutex.Lock() defer cacheMutex.Unlock() @@ -199,6 +234,10 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo delete(clientCache, identifier) isInCache = false } + if cacheEntry.expiresAt.Before(time.Now()) { + delete(clientCache, identifier) + isInCache = false + } } if !isInCache { @@ -206,7 +245,13 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo client, err = newSpaceClient(spaceGuid, url, username, password) if err == nil { // add CF client to cache - clientCache[identifier] = &clientCacheEntry{url: url, username: username, password: password, client: client.client} + clientCache[identifier] = &clientCacheEntry{ + url: url, + username: username, + password: password, + client: client.client, + expiresAt: time.Now().Add(config.RefreshTokenAutoRenewalInterval), + } } } diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index eafc2aa..34e014a 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -9,11 +9,12 @@ import ( "testing" "time" - cfResource "github.com/cloudfoundry-community/go-cfclient/v3/resource" + cfResource "github.com/cloudfoundry/go-cfclient/v3/resource" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/ghttp" "github.com/prometheus/client_golang/prometheus" + "github.com/sap/cf-service-operator/internal/config" "sigs.k8s.io/controller-runtime/pkg/metrics" ) @@ -32,9 +33,9 @@ const ( Owner = "testOwner" Owner2 = "testOwner2" - spacesURI = "/v3/spaces" - serviceInstancesURI = "/v3/service_instances" - uaaURI = "/uaa/oauth/token" + spacesURI = "/v3/spaces" + instancesURI = "/v3/service_instances" + uaaURI = "/uaa/oauth/token" ) type Token struct { @@ -60,6 +61,7 @@ var _ = Describe("CF Client tests", Ordered, func() { var statusCode int var ctx context.Context var tokenResult Token + var cfg config.Config BeforeAll(func() { ctx = context.Background() @@ -67,6 +69,9 @@ var _ = Describe("CF Client tests", Ordered, func() { server = ghttp.NewServer() url = "http://" + server.Addr() statusCode = 200 + cfg = config.Config{ + RefreshTokenAutoRenewalInterval: time.Duration(time.Hour * 1), + } rootResult = cfResource.Root{ Links: cfResource.RootLinks{ Uaa: cfResource.Link{ @@ -85,12 +90,13 @@ var _ = Describe("CF Client tests", Ordered, func() { } By("creating space CR") }) + AfterAll(func() { // Shutdown the server after tests server.Close() }) - Describe("NewOrganizationClient", func() { + Describe("OrganizationClient", func() { BeforeEach(func() { // Reset some entities to enable tests to run independently clientCache = make(map[clientIdentifier]*clientCacheEntry) @@ -110,21 +116,27 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should create OrgClient", func() { - NewOrganizationClient(OrgName, url, Username, Password) + NewOrganizationClient(OrgName, url, Username, Password, &cfg) + + Expect(server.ReceivedRequests()).To(HaveLen(2)) // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - Expect(server.ReceivedRequests()).To(HaveLen(1)) + // Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) }) It("should be able to query some org", func() { - orgClient, err := NewOrganizationClient(OrgName, url, Username, Password) + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, &cfg) Expect(err).To(BeNil()) orgClient.GetSpace(ctx, Owner) + Expect(server.ReceivedRequests()).To(HaveLen(3)) + // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) @@ -135,8 +147,6 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[2].URL.Path).To(Equal(spacesURI)) - Expect(server.ReceivedRequests()).To(HaveLen(3)) - // verify metrics metricsList, err := metrics.Registry.Gather() Expect(err).To(BeNil()) @@ -152,14 +162,16 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some org twice", func() { - orgClient, err := NewOrganizationClient(OrgName, url, Username, Password) + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, &cfg) Expect(err).To(BeNil()) orgClient.GetSpace(ctx, Owner) - orgClient, err = NewOrganizationClient(OrgName, url, Username, Password) + orgClient, err = NewOrganizationClient(OrgName, url, Username, Password, &cfg) Expect(err).To(BeNil()) orgClient.GetSpace(ctx, Owner) + Expect(server.ReceivedRequests()).To(HaveLen(4)) + // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) @@ -173,13 +185,46 @@ var _ = Describe("CF Client tests", Ordered, func() { // Get space Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[3].URL.Path).To(Equal(spacesURI)) + }) - Expect(server.ReceivedRequests()).To(HaveLen(4)) + It("should re-create the refresh token after configured RefreshTokenAutoRenewalInterval", func() { + cfgZero := config.Config{ + RefreshTokenAutoRenewalInterval: time.Duration(time.Second * 0), + } + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, &cfgZero) + Expect(err).To(BeNil()) + orgClient.GetSpace(ctx, Owner) + + orgClient, err = NewOrganizationClient(OrgName, url, Username, Password, &cfgZero) + Expect(err).To(BeNil()) + orgClient.GetSpace(ctx, Owner) + + Expect(server.ReceivedRequests()).To(HaveLen(6)) + + // Discover UAA endpoint + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // Get space + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].URL.Path).To(Equal(spacesURI)) + + // Discover UAA endpoint + Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[3].URL.Path).To(Equal("/")) + // Get new oAuth token + Expect(server.ReceivedRequests()[4].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[4].URL.Path).To(Equal(uaaURI)) + // Get space + Expect(server.ReceivedRequests()[5].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[5].URL.Path).To(Equal(spacesURI)) }) It("should be able to query two different orgs", func() { // test org 1 - orgClient1, err1 := NewOrganizationClient(OrgName, url, Username, Password) + orgClient1, err1 := NewOrganizationClient(OrgName, url, Username, Password, &cfg) Expect(err1).To(BeNil()) orgClient1.GetSpace(ctx, Owner) // Discover UAA endpoint @@ -193,7 +238,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) // test org 2 - orgClient2, err2 := NewOrganizationClient(OrgName2, url, Username, Password) + orgClient2, err2 := NewOrganizationClient(OrgName2, url, Username, Password, &cfg) Expect(err2).To(BeNil()) orgClient2.GetSpace(ctx, Owner2) // no discovery of UAA endpoint or oAuth token here due to caching @@ -204,7 +249,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) - Describe("NewSpaceClient", func() { + Describe("SpaceClient", func() { BeforeEach(func() { // Reset some entities to enable tests to run independently clientCache = make(map[clientIdentifier]*clientCacheEntry) @@ -215,7 +260,7 @@ var _ = Describe("CF Client tests", Ordered, func() { server.RouteToHandler("GET", "/", ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &rootResult), )) - server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( + server.RouteToHandler("GET", instancesURI, ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &rootResult), )) server.RouteToHandler("POST", uaaURI, ghttp.CombineHandlers( @@ -224,21 +269,27 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should create SpaceClient", func() { - NewSpaceClient(OrgName, url, Username, Password) + NewSpaceClient(OrgName, url, Username, Password, &cfg) + + Expect(server.ReceivedRequests()).To(HaveLen(2)) // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - Expect(server.ReceivedRequests()).To(HaveLen(1)) + // Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) }) It("should be able to query some space", func() { - spaceClient, err := NewSpaceClient(OrgName, url, Username, Password) + spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, &cfg) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(3)) + // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) @@ -247,9 +298,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) // Get instance Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[2].URL.Path).To(Equal(serviceInstancesURI)) - - Expect(server.ReceivedRequests()).To(HaveLen(3)) + Expect(server.ReceivedRequests()[2].URL.Path).To(Equal(instancesURI)) // verify metrics metricsList, err := metrics.Registry.Gather() @@ -266,14 +315,16 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some space twice", func() { - spaceClient, err := NewSpaceClient(OrgName, url, Username, Password) + spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, &cfg) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - spaceClient, err = NewSpaceClient(OrgName, url, Username, Password) + spaceClient, err = NewSpaceClient(OrgName, url, Username, Password, &cfg) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(4)) + // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) @@ -282,18 +333,51 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) // Get instance Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[2].URL.Path).To(Equal(serviceInstancesURI)) + Expect(server.ReceivedRequests()[2].URL.Path).To(Equal(instancesURI)) // Get instance Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[3].URL.Path).To(Equal(serviceInstancesURI)) + Expect(server.ReceivedRequests()[3].URL.Path).To(Equal(instancesURI)) + }) - Expect(server.ReceivedRequests()).To(HaveLen(4)) + It("should re-create the refresh token after configured RefreshTokenAutoRenewalInterval", func() { + cfgZero := config.Config{ + RefreshTokenAutoRenewalInterval: time.Duration(time.Second * 0), + } + spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, &cfgZero) + Expect(err).To(BeNil()) + + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + spaceClient, err = NewSpaceClient(OrgName, url, Username, Password, &cfgZero) + Expect(err).To(BeNil()) + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + + Expect(server.ReceivedRequests()).To(HaveLen(6)) + + // Discover UAA endpoint + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // Get instance + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].URL.Path).To(Equal(instancesURI)) + + // Discover UAA endpoint + Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[3].URL.Path).To(Equal("/")) + // Get new oAuth token + Expect(server.ReceivedRequests()[4].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[4].URL.Path).To(Equal(uaaURI)) + // Get instance + Expect(server.ReceivedRequests()[5].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[5].URL.Path).To(Equal(instancesURI)) }) It("should be able to query two different spaces", func() { // test space 1 - spaceClient1, err1 := NewSpaceClient(SpaceName, url, Username, Password) + spaceClient1, err1 := NewSpaceClient(SpaceName, url, Username, Password, &cfg) Expect(err1).To(BeNil()) spaceClient1.GetInstance(ctx, map[string]string{"owner": Owner}) // Discover UAA endpoint @@ -307,7 +391,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) // test space 2 - spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password) + spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password, &cfg) Expect(err2).To(BeNil()) spaceClient2.GetInstance(ctx, map[string]string{"owner": Owner2}) // no discovery of UAA endpoint or oAuth token here due to caching @@ -317,7 +401,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should register prometheus metrics for OrgClient", func() { - orgClient, err := NewOrganizationClient(OrgName, url, Username, Password) + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, &cfg) Expect(err).To(BeNil()) Expect(orgClient).ToNot(BeNil()) @@ -336,7 +420,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should register prometheus metrics for SpaceClient", func() { - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password) + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, &cfg) Expect(err).To(BeNil()) Expect(spaceClient).ToNot(BeNil()) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 5da95ff..84622bc 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -11,8 +11,8 @@ import ( "fmt" "strconv" - cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" - cfresource "github.com/cloudfoundry-community/go-cfclient/v3/resource" + cfclient "github.com/cloudfoundry/go-cfclient/v3/client" + cfresource "github.com/cloudfoundry/go-cfclient/v3/resource" "github.com/pkg/errors" "github.com/sap/cf-service-operator/internal/facade" @@ -37,7 +37,8 @@ func (in *instanceFilterName) getListOptions() *cfclient.ServiceInstanceListOpti func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOptions { listOpts := cfclient.NewServiceInstanceListOptions() - listOpts.LabelSelector.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, io.owner)) + listOpts.LabelSel = make(map[string]cfclient.ExclusionFilter) + listOpts.LabelSel.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, io.owner)) return listOpts } diff --git a/internal/cf/offering.go b/internal/cf/offering.go index 68f6099..e6aad57 100644 --- a/internal/cf/offering.go +++ b/internal/cf/offering.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" + cfclient "github.com/cloudfoundry/go-cfclient/v3/client" ) func (c *spaceClient) FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) { diff --git a/internal/cf/space.go b/internal/cf/space.go index 909e818..7f19cf4 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -10,8 +10,8 @@ import ( "fmt" "strconv" - cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" - cfresource "github.com/cloudfoundry-community/go-cfclient/v3/resource" + cfclient "github.com/cloudfoundry/go-cfclient/v3/client" + cfresource "github.com/cloudfoundry/go-cfclient/v3/resource" "github.com/pkg/errors" "github.com/sap/cf-service-operator/internal/facade" @@ -19,7 +19,8 @@ import ( func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facade.Space, error) { listOpts := cfclient.NewSpaceListOptions() - listOpts.LabelSelector.EqualTo(labelPrefix + "/" + labelKeyOwner + "=" + owner) + listOpts.LabelSel = make(map[string]cfclient.ExclusionFilter) + listOpts.LabelSel.EqualTo(labelPrefix + "/" + labelKeyOwner + "=" + owner) spaces, err := c.client.Spaces.ListAll(ctx, listOpts) if err != nil { return nil, err diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..4863a14 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,13 @@ +/* +SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and cf-service-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package config + +import "time" + +// Config defines the configuration keys +type Config struct { + RefreshTokenAutoRenewalInterval time.Duration +} diff --git a/internal/controllers/servicebinding_controller.go b/internal/controllers/servicebinding_controller.go index 12ff006..215611f 100644 --- a/internal/controllers/servicebinding_controller.go +++ b/internal/controllers/servicebinding_controller.go @@ -24,6 +24,7 @@ import ( cfv1alpha1 "github.com/sap/cf-service-operator/api/v1alpha1" "github.com/sap/cf-service-operator/internal/binding" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/facade" ) @@ -43,6 +44,7 @@ const ( // ServiceBindingReconciler reconciles a ServiceBinding object type ServiceBindingReconciler struct { client.Client + Config *config.Config Scheme *runtime.Scheme ClusterResourceNamespace string EnableBindingMetadata bool @@ -168,7 +170,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque // Build cloud foundry client var client facade.SpaceClient if spaceGuid != "" { - client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"])) + client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"]), r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the client from secret %s", spaceSecretName) } diff --git a/internal/controllers/serviceinstance_controller.go b/internal/controllers/serviceinstance_controller.go index a4e51d2..4d0ae53 100644 --- a/internal/controllers/serviceinstance_controller.go +++ b/internal/controllers/serviceinstance_controller.go @@ -25,6 +25,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" cfv1alpha1 "github.com/sap/cf-service-operator/api/v1alpha1" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/facade" ) @@ -51,6 +52,7 @@ const ( // ServiceInstanceReconciler reconciles a ServiceInstance object type ServiceInstanceReconciler struct { client.Client + Config *config.Config Scheme *runtime.Scheme ClusterResourceNamespace string ClientBuilder facade.SpaceClientBuilder @@ -182,7 +184,7 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ // Build cloud foundry client var client facade.SpaceClient if spaceGuid != "" { - client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"])) + client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"]), r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the client from secret %s", spaceSecretName) } diff --git a/internal/controllers/space_controller.go b/internal/controllers/space_controller.go index 161036b..a13f002 100644 --- a/internal/controllers/space_controller.go +++ b/internal/controllers/space_controller.go @@ -11,6 +11,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/sap/cf-service-operator/internal/config" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -40,8 +41,9 @@ const ( // SpaceReconciler reconciles a (Cluster)Space object type SpaceReconciler struct { - Kind string - client.Client + Kind string + Config *config.Config + client.Client // K8s client Scheme *runtime.Scheme ClusterResourceNamespace string ClientBuilder facade.OrganizationClientBuilder @@ -147,7 +149,7 @@ func (r *SpaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu password = string(secret.Data["password"]) } - client, err = r.ClientBuilder(spec.OrganizationName, url, username, password) + client, err = r.ClientBuilder(spec.OrganizationName, url, username, password, r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the client from secret %s", secretName) } @@ -232,7 +234,7 @@ func (r *SpaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu url := string(secret.Data["url"]) username := string(secret.Data["username"]) password := string(secret.Data["password"]) - checker, err := r.HealthCheckerBuilder(status.SpaceGuid, url, username, password) + checker, err := r.HealthCheckerBuilder(status.SpaceGuid, url, username, password, r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the healthchecker from secret %s", secretName) } diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index f6895ec..d82f491 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -16,6 +16,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sap/cf-service-operator/api/v1alpha1" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/facade" "github.com/sap/cf-service-operator/internal/facade/facadefakes" @@ -204,10 +205,10 @@ func addControllers(k8sManager ctrl.Manager) { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), ClusterResourceNamespace: testK8sNamespace, - ClientBuilder: func(organizationName string, url string, username string, password string) (facade.OrganizationClient, error) { + ClientBuilder: func(organizationName string, url string, username string, password string, config *config.Config) (facade.OrganizationClient, error) { return fakeOrgClient, nil }, - HealthCheckerBuilder: func(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { + HealthCheckerBuilder: func(spaceGuid string, url string, username string, password string, config *config.Config) (facade.SpaceHealthChecker, error) { return fakeSpaceHealthChecker, nil }, } @@ -218,7 +219,7 @@ func addControllers(k8sManager ctrl.Manager) { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), ClusterResourceNamespace: testK8sNamespace, - ClientBuilder: func(organizationName string, url string, username string, password string) (facade.SpaceClient, error) { + ClientBuilder: func(organizationName string, url string, username string, password string, config *config.Config) (facade.SpaceClient, error) { return fakeSpaceClient, nil }, } diff --git a/internal/facade/client.go b/internal/facade/client.go index 6990788..4622e88 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -5,7 +5,11 @@ SPDX-License-Identifier: Apache-2.0 package facade -import "context" +import ( + "context" + + "github.com/sap/cf-service-operator/internal/config" +) type Space struct { Guid string @@ -73,7 +77,7 @@ type OrganizationClient interface { AddManager(ctx context.Context, guid string, username string) error } -type OrganizationClientBuilder func(string, string, string, string) (OrganizationClient, error) +type OrganizationClientBuilder func(string, string, string, string, *config.Config) (OrganizationClient, error) //counterfeiter:generate . SpaceClient type SpaceClient interface { @@ -90,4 +94,4 @@ type SpaceClient interface { FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) } -type SpaceClientBuilder func(string, string, string, string) (SpaceClient, error) +type SpaceClientBuilder func(string, string, string, string, *config.Config) (SpaceClient, error) diff --git a/internal/facade/health.go b/internal/facade/health.go index 394ba6d..f844d35 100644 --- a/internal/facade/health.go +++ b/internal/facade/health.go @@ -5,11 +5,15 @@ SPDX-License-Identifier: Apache-2.0 package facade -import "context" +import ( + "context" + + "github.com/sap/cf-service-operator/internal/config" +) //counterfeiter:generate . SpaceHealthChecker type SpaceHealthChecker interface { Check(ctx context.Context) error } -type SpaceHealthCheckerBuilder func(string, string, string, string) (SpaceHealthChecker, error) +type SpaceHealthCheckerBuilder func(string, string, string, string, *config.Config) (SpaceHealthChecker, error) diff --git a/main.go b/main.go index 825f142..19abb6e 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "net" "os" "strconv" + "time" "github.com/pkg/errors" @@ -28,6 +29,7 @@ import ( cfv1alpha1 "github.com/sap/cf-service-operator/api/v1alpha1" "github.com/sap/cf-service-operator/internal/cf" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/controllers" // +kubebuilder:scaffold:imports ) @@ -56,14 +58,16 @@ func main() { var enableWebhooks bool var clusterResourceNamespace string var enableBindingMetadata bool + var refreshTokenAutoRenewalInterval string + flag.StringVar(&clusterResourceNamespace, "cluster-resource-namespace", "", "The namespace for secrets in which cluster-scoped resources are found.") + flag.BoolVar(&enableBindingMetadata, "sap-binding-metadata", false, "Enhance binding secrets by SAP binding metadata by default.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") + flag.BoolVar(&enableWebhooks, "enableWebhooks", true, "Enable webhooks in controller. May be disabled for local development.") flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.StringVar(&refreshTokenAutoRenewalInterval, "refresh-token-auto-renewal-interval", "12h", "The interval after which the CF client should automatically renew its refresh token (e.g. 12h, 30m).") flag.StringVar(&webhookAddr, "webhook-bind-address", ":9443", "The address the webhook endpoint binds to.") flag.StringVar(&webhookCertDir, "webhook-tls-directory", "", "The directory containing TLS server key and certificate, as tls.key and tls.crt; defaults to $TMPDIR/k8s-webhook-server/serving-certs.") - flag.BoolVar(&enableWebhooks, "enableWebhooks", true, "Enable webhooks in controller. May be disabled for local development.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") - flag.StringVar(&clusterResourceNamespace, "cluster-resource-namespace", "", "The namespace for secrets in which cluster-scoped resources are found.") - flag.BoolVar(&enableBindingMetadata, "sap-binding-metadata", false, "Enhance binding secrets by SAP binding metadata by default.") opts := zap.Options{ Development: false, @@ -73,6 +77,16 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + // Create a Config instance with the parsed flag values + refreshTokenAutoRenewal, err := time.ParseDuration(refreshTokenAutoRenewalInterval) + if err != nil { + setupLog.Error(err, "please provide valid --refresh-token-auto-renewal-interval e.g. 12h, 30m") + os.Exit(1) + } + cfg := config.Config{ + RefreshTokenAutoRenewalInterval: refreshTokenAutoRenewal, + } + if clusterResourceNamespace == "" { var err error clusterResourceNamespace, err = getInClusterNamespace() @@ -91,6 +105,7 @@ func main() { "enable-leader-election", enableLeaderElection, "metrics-addr", metricsAddr, "cluster-resource-namespace", clusterResourceNamespace, + "refresh-token-auto-renewal-interval", refreshTokenAutoRenewal, ) webhookHost, webhookPort, err := parseAddress(webhookAddr) @@ -136,6 +151,7 @@ func main() { if err = (&controllers.SpaceReconciler{ Kind: "Space", Client: mgr.GetClient(), + Config: &cfg, Scheme: mgr.GetScheme(), ClusterResourceNamespace: clusterResourceNamespace, ClientBuilder: cf.NewOrganizationClient, @@ -147,6 +163,7 @@ func main() { if err = (&controllers.SpaceReconciler{ Kind: "ClusterSpace", Client: mgr.GetClient(), + Config: &cfg, Scheme: mgr.GetScheme(), ClusterResourceNamespace: clusterResourceNamespace, ClientBuilder: cf.NewOrganizationClient, @@ -157,6 +174,7 @@ func main() { } if err = (&controllers.ServiceInstanceReconciler{ Client: mgr.GetClient(), + Config: &cfg, Scheme: mgr.GetScheme(), ClusterResourceNamespace: clusterResourceNamespace, ClientBuilder: cf.NewSpaceClient, @@ -166,6 +184,7 @@ func main() { } if err = (&controllers.ServiceBindingReconciler{ Client: mgr.GetClient(), + Config: &cfg, Scheme: mgr.GetScheme(), ClusterResourceNamespace: clusterResourceNamespace, EnableBindingMetadata: enableBindingMetadata, diff --git a/website/content/en/docs/configuration/operator.md b/website/content/en/docs/configuration/operator.md index f6f44f0..c83605e 100644 --- a/website/content/en/docs/configuration/operator.md +++ b/website/content/en/docs/configuration/operator.md @@ -15,6 +15,8 @@ cf-service-operator accepts the following command line flags: Usage of manager: -cluster-resource-namespace string The namespace for secrets in which cluster-scoped resources are found. + -enableWebhooks bool + Enable webhooks in controller. May be disabled for local development. -health-probe-bind-address string The address the probe endpoint binds to. (default ":8081") -kubeconfig string @@ -24,6 +26,8 @@ Usage of manager: Enabling this will ensure there is only one active controller manager. -metrics-bind-address string The address the metric endpoint binds to. (default ":8080") + -refresh-token-auto-renewal-interval duration + The interval after which the CF client should automatically renew its refresh token. (default 12h) -sap-binding-metadata Enhance binding secrets by SAP binding metadata by default. -webhook-bind-address string