github.com/openshift-online/ocm-sdk-go@v0.1.473/connection.go (about)

     1  /*
     2  Copyright (c) 2018 Red Hat, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8    http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // This file contains the implementations of the Builder and Connection objects.
    18  
    19  package sdk
    20  
    21  import (
    22  	"context"
    23  	"crypto/x509"
    24  	"fmt"
    25  	"net/http"
    26  	"net/url"
    27  	"regexp"
    28  	"sort"
    29  	"time"
    30  
    31  	"github.com/prometheus/client_golang/prometheus"
    32  
    33  	"github.com/openshift-online/ocm-sdk-go/accesstransparency"
    34  	"github.com/openshift-online/ocm-sdk-go/accountsmgmt"
    35  	"github.com/openshift-online/ocm-sdk-go/addonsmgmt"
    36  	"github.com/openshift-online/ocm-sdk-go/arohcp"
    37  	"github.com/openshift-online/ocm-sdk-go/authentication"
    38  	"github.com/openshift-online/ocm-sdk-go/authorizations"
    39  	"github.com/openshift-online/ocm-sdk-go/clustersmgmt"
    40  	"github.com/openshift-online/ocm-sdk-go/configuration"
    41  	"github.com/openshift-online/ocm-sdk-go/internal"
    42  	"github.com/openshift-online/ocm-sdk-go/jobqueue"
    43  	"github.com/openshift-online/ocm-sdk-go/logging"
    44  	"github.com/openshift-online/ocm-sdk-go/metrics"
    45  	"github.com/openshift-online/ocm-sdk-go/osdfleetmgmt"
    46  	"github.com/openshift-online/ocm-sdk-go/retry"
    47  	"github.com/openshift-online/ocm-sdk-go/servicelogs"
    48  	"github.com/openshift-online/ocm-sdk-go/servicemgmt"
    49  	"github.com/openshift-online/ocm-sdk-go/statusboard"
    50  	"github.com/openshift-online/ocm-sdk-go/webrca"
    51  )
    52  
    53  // Default values:
    54  const (
    55  	// #nosec G101
    56  	DefaultTokenURL     = authentication.DefaultTokenURL
    57  	DefaultClientID     = authentication.DefaultClientID
    58  	DefaultClientSecret = authentication.DefaultClientSecret
    59  	DefaultURL          = "https://api.openshift.com"
    60  	DefaultAgent        = "OCM-SDK/" + Version
    61  	FedRAMPURL          = "https://api.openshiftusgov.com"
    62  )
    63  
    64  // DefaultScopes is the ser of scopes used by default:
    65  var DefaultScopes = []string{
    66  	"openid",
    67  }
    68  
    69  // ConnectionBuilder contains the configuration and logic needed to create connections to
    70  // `api.openshift.com`. Don't create instances of this type directly, use the NewConnectionBuilder
    71  // function instead.
    72  type ConnectionBuilder struct {
    73  	// Basic attributes:
    74  	logger            logging.Logger
    75  	trustedCAs        []interface{}
    76  	insecure          bool
    77  	disableKeepAlives bool
    78  	tokenURL          string
    79  	clientID          string
    80  	clientSecret      string
    81  	urlTable          map[string]string
    82  	agent             string
    83  	user              string
    84  	password          string
    85  	tokens            []string
    86  	scopes            []string
    87  	retryLimit        int
    88  	retryInterval     time.Duration
    89  	retryJitter       float64
    90  	transportWrappers []func(http.RoundTripper) http.RoundTripper
    91  
    92  	includeDefaultAuthnTransportWrapper bool
    93  
    94  	// Metrics:
    95  	metricsSubsystem  string
    96  	metricsRegisterer prometheus.Registerer
    97  
    98  	// Error detected while populating the builder. Once set calls to methods to
    99  	// set other builder parameters will be ignored and the Build method will
   100  	// exit inmediately returning this error.
   101  	err error
   102  }
   103  
   104  // TransportWrapper is a wrapper for a transport of type http.RoundTripper. Creating a transport
   105  // wrapper, enables to preform actions and manipulations on the transport request and response.
   106  type TransportWrapper func(http.RoundTripper) http.RoundTripper
   107  
   108  // Connection contains the data needed to connect to the `api.openshift.com`. Don't create instances
   109  // of this type directly, use the builder instead.
   110  type Connection struct {
   111  	// Basic attributes:
   112  	closed         bool
   113  	logger         logging.Logger
   114  	authnWrapper   *authentication.TransportWrapper
   115  	retryWrapper   *retry.TransportWrapper
   116  	clientSelector *internal.ClientSelector
   117  	urlTable       []urlTableEntry
   118  	agent          string
   119  
   120  	// Metrics:
   121  	metricsSubsystem  string
   122  	metricsRegisterer prometheus.Registerer
   123  }
   124  
   125  // urlTableEntry is used to store one entry of the table that contains the correspondence between
   126  // path prefixes and base URLs.
   127  type urlTableEntry struct {
   128  	prefix string
   129  	re     *regexp.Regexp
   130  	url    *internal.ServerAddress
   131  }
   132  
   133  // NewConnectionBuilder creates an builder that knows how to create connections with the default
   134  // configuration.
   135  func NewConnectionBuilder() *ConnectionBuilder {
   136  	return &ConnectionBuilder{
   137  		urlTable: map[string]string{
   138  			"": DefaultURL,
   139  		},
   140  		retryLimit:                          retry.DefaultLimit,
   141  		retryInterval:                       retry.DefaultInterval,
   142  		retryJitter:                         retry.DefaultJitter,
   143  		metricsRegisterer:                   prometheus.DefaultRegisterer,
   144  		includeDefaultAuthnTransportWrapper: true,
   145  	}
   146  }
   147  
   148  // NewConnectionBuilder creates a Builder that knows how to create connections
   149  // without authentication
   150  func NewUnauthenticatedConnectionBuilder() *ConnectionBuilder {
   151  	connectionBuilder := NewConnectionBuilder()
   152  	connectionBuilder.includeDefaultAuthnTransportWrapper = false
   153  	return connectionBuilder
   154  }
   155  
   156  // Logger sets the logger that will be used by the connection. By default it uses the Go `log`
   157  // package, and with the debug level disabled and the rest enabled. If you need to change that you
   158  // can create a logger and pass it to this method. For example:
   159  //
   160  //	// Create a logger with the debug level enabled:
   161  //	logger, err := logging.NewGoLoggerBuilder().
   162  //		Debug(true).
   163  //		Build()
   164  //	if err != nil {
   165  //		panic(err)
   166  //	}
   167  //
   168  //	// Create the connection:
   169  //	cl, err := client.NewConnectionBuilder().
   170  //		Logger(logger).
   171  //		Build()
   172  //	if err != nil {
   173  //		panic(err)
   174  //	}
   175  //
   176  // You can also build your own logger, implementing the Logger interface.
   177  func (b *ConnectionBuilder) Logger(logger logging.Logger) *ConnectionBuilder {
   178  	if b.err != nil {
   179  		return b
   180  	}
   181  	b.logger = logger
   182  	return b
   183  }
   184  
   185  // TokenURL sets the URL that will be used to request OpenID access tokens. The default is
   186  // `https://sso.redhat.com/auth/realms/cloud-services/protocol/openid-connect/token`.
   187  func (b *ConnectionBuilder) TokenURL(url string) *ConnectionBuilder {
   188  	if b.err != nil {
   189  		return b
   190  	}
   191  	b.tokenURL = url
   192  	return b
   193  }
   194  
   195  // Client sets OpenID client identifier and secret that will be used to request OpenID tokens. The
   196  // default identifier is `cloud-services`. The default secret is the empty string. When these two
   197  // values are provided and no user name and password is provided, the connection will use the client
   198  // credentials grant to obtain the token. For example, to create a connection using the client
   199  // credentials grant do the following:
   200  //
   201  //	// Use the client credentials grant:
   202  //	connection, err := sdk.NewConnectionBuilder().
   203  //		Client("myclientid", "myclientsecret").
   204  //		Build()
   205  //
   206  // Note that some OpenID providers (Keycloak, for example) require the client identifier also for
   207  // the resource owner password grant. In that case use the set only the identifier, and let the
   208  // secret blank. For example:
   209  //
   210  //	// Use the resource owner password grant:
   211  //	connection, err := sdk.NewConnectionBuilder().
   212  //		User("myuser", "mypassword").
   213  //		Client("myclientid", "").
   214  //		Build()
   215  //
   216  // Note the empty client secret.
   217  func (b *ConnectionBuilder) Client(id string, secret string) *ConnectionBuilder {
   218  	if b.err != nil {
   219  		return b
   220  	}
   221  	b.clientID = id
   222  	b.clientSecret = secret
   223  	return b
   224  }
   225  
   226  // URL sets the base URL of the API gateway. The default is `https://api.openshift.com`.
   227  //
   228  // To connect using a Unix sockets and HTTP use the `unix` URL scheme and put the name of socket file
   229  // in the URL path:
   230  //
   231  //	connection, err := sdk.NewConnectionBuilder().
   232  //		URL("unix://my.server.com/tmp/api.socket").
   233  //		Build()
   234  //
   235  // To connect using Unix sockets and HTTPS use `unix+https://my.server.com/tmp/api.socket`.
   236  //
   237  // To force use of HTTP/2 without TLS use `h2c://...`. This can also be combined with Unix sockets,
   238  // for example `unix+h2c://...`.
   239  //
   240  // Note that the host name is mandatory even when using Unix sockets because it is used to populate
   241  // the `Host` header sent to the server.
   242  func (b *ConnectionBuilder) URL(url string) *ConnectionBuilder {
   243  	if b.err != nil {
   244  		return b
   245  	}
   246  	return b.AlternativeURL("", url)
   247  }
   248  
   249  // AlternativeURL sets an alternative base URL for the given path prefix. For example, to configure
   250  // the connection so that it sends the requests for the clusters management service to
   251  // `https://my.server.com`:
   252  //
   253  //	connection, err := client.NewConnectionBuilder().
   254  //		URL("https://api.example.com").
   255  //		AlternativeURL("/api/clusters_mgmt", "https://my.server.com").
   256  //		Build()
   257  //
   258  // Requests for other paths that don't start with the given prefix will still be sent to the default
   259  // base URL.
   260  //
   261  // This method can be called multiple times to set alternative URLs for multiple prefixes.
   262  func (b *ConnectionBuilder) AlternativeURL(prefix, base string) *ConnectionBuilder {
   263  	if b.err != nil {
   264  		return b
   265  	}
   266  	b.urlTable[prefix] = base
   267  	return b
   268  }
   269  
   270  // AlternativeURLs sets an collection of alternative base URLs. For example, to configure the
   271  // connection so that it sends the requests for the clusters management service to
   272  // `https://my.server.com` and the requests for the accounts management service to
   273  // `https://your.server.com`:
   274  //
   275  //	connection, err := client.NewConnectionBuilder().
   276  //		URL("https://api.example.com").
   277  //		AlternativeURLs(map[string]string{
   278  //			"/api/clusters_mgmt": "https://my.server.com",
   279  //			"/api/accounts_mgmt": "https://your.server.com",
   280  //		}).
   281  //		Build()
   282  //
   283  // The effect is the same as calling the AlternativeURL multiple times.
   284  func (b *ConnectionBuilder) AlternativeURLs(entries map[string]string) *ConnectionBuilder {
   285  	if b.err != nil {
   286  		return b
   287  	}
   288  	for prefix, base := range entries {
   289  		b.urlTable[prefix] = base
   290  	}
   291  	return b
   292  }
   293  
   294  // Agent sets the `User-Agent` header that the client will use in all the HTTP requests. The default
   295  // is `OCM` followed by an slash and the version of the client, for example `OCM/0.0.0`.
   296  func (b *ConnectionBuilder) Agent(agent string) *ConnectionBuilder {
   297  	if b.err != nil {
   298  		return b
   299  	}
   300  	b.agent = agent
   301  	return b
   302  }
   303  
   304  // User sets the user name and password that will be used to request OpenID access tokens. When
   305  // these two values are provided the connection will use the resource owner password grant type to
   306  // obtain the token. For example:
   307  //
   308  //	// Use the resource owner password grant:
   309  //	connection, err := sdk.NewConnectionBuilder().
   310  //		User("myuser", "mypassword").
   311  //		Build()
   312  //
   313  // Note that some OpenID providers (Keycloak, for example) require the client identifier also for
   314  // the resource owner password grant. In that case use the set only the identifier, and let the
   315  // secret blank. For example:
   316  //
   317  //	// Use the resource owner password grant:
   318  //	connection, err := sdk.NewConnectionBuilder().
   319  //		User("myuser", "mypassword").
   320  //		Client("myclientid", "").
   321  //		Build()
   322  //
   323  // Note the empty client secret.
   324  func (b *ConnectionBuilder) User(name string, password string) *ConnectionBuilder {
   325  	if b.err != nil {
   326  		return b
   327  	}
   328  	b.user = name
   329  	b.password = password
   330  	return b
   331  }
   332  
   333  // Scopes sets the OpenID scopes that will be included in the token request. The default is to use
   334  // the `openid` scope. If this method is used then that default will be completely replaced, so you
   335  // will need to specify it explicitly if you want to use it. For example, if you want to add the
   336  // scope 'myscope' without loosing the default you will have to do something like this:
   337  //
   338  //	// Create a connection with the default 'openid' scope and some additional scopes:
   339  //	connection, err := sdk.NewConnectionBuilder().
   340  //		User("myuser", "mypassword").
   341  //		Scopes("openid", "myscope", "yourscope").
   342  //		Build()
   343  //
   344  // If you just want to use the default 'openid' then there is no need to use this method.
   345  func (b *ConnectionBuilder) Scopes(values ...string) *ConnectionBuilder {
   346  	if b.err != nil {
   347  		return b
   348  	}
   349  	b.scopes = make([]string, len(values))
   350  	copy(b.scopes, values)
   351  	return b
   352  }
   353  
   354  // Tokens sets the OpenID tokens that will be used to authenticate. Multiple types of tokens are
   355  // accepted, and used according to their type. For example, you can pass a single access token, or
   356  // an access token and a refresh token, or just a refresh token. If no token is provided then the
   357  // connection will the user name and password or the client identifier and client secret (see the
   358  // User and Client methods) to request new ones.
   359  //
   360  // If the connection is created with these tokens and no user or client credentials, it will
   361  // stop working when both tokens expire. That can happen, for example, if the connection isn't used
   362  // for a period of time longer than the life of the refresh token.
   363  func (b *ConnectionBuilder) Tokens(tokens ...string) *ConnectionBuilder {
   364  	if b.err != nil {
   365  		return b
   366  	}
   367  	b.tokens = append(b.tokens, tokens...)
   368  	return b
   369  }
   370  
   371  // TrustedCAs sets the certificate pool that contains the certificate authorities that will be
   372  // trusted by the connection. If this isn't explicitly specified then the client will trust the
   373  // certificate authorities trusted by default by the system.
   374  func (b *ConnectionBuilder) TrustedCAs(value *x509.CertPool) *ConnectionBuilder {
   375  	if b.err != nil {
   376  		return b
   377  	}
   378  	b.trustedCAs = append(b.trustedCAs, value)
   379  	return b
   380  }
   381  
   382  // TrustedCAFile sets the name of a file that contains the certificate authorities that will be
   383  // trusted by the connection. If this isn't explicitly specified then the client will trust the
   384  // certificate authorities trusted by default by the system.
   385  func (b *ConnectionBuilder) TrustedCAFile(value string) *ConnectionBuilder {
   386  	if b.err != nil {
   387  		return b
   388  	}
   389  	b.trustedCAs = append(b.trustedCAs, value)
   390  	return b
   391  }
   392  
   393  // Insecure enables insecure communication with the server. This disables verification of TLS
   394  // certificates and host names and it isn't recommended for a production environment.
   395  func (b *ConnectionBuilder) Insecure(flag bool) *ConnectionBuilder {
   396  	if b.err != nil {
   397  		return b
   398  	}
   399  	b.insecure = flag
   400  	return b
   401  }
   402  
   403  // DisableKeepAlives disables HTTP keep-alives with the server. This is unrelated to similarly
   404  // named TCP keep-alives.
   405  func (b *ConnectionBuilder) DisableKeepAlives(flag bool) *ConnectionBuilder {
   406  	if b.err != nil {
   407  		return b
   408  	}
   409  	b.disableKeepAlives = flag
   410  	return b
   411  }
   412  
   413  // RetryLimit sets the maximum number of retries for a request. When this is zero no retries will be
   414  // performed. The default value is two.
   415  func (b *ConnectionBuilder) RetryLimit(value int) *ConnectionBuilder {
   416  	if b.err != nil {
   417  		return b
   418  	}
   419  	b.retryLimit = value
   420  	return b
   421  }
   422  
   423  // RetryInterval sets the time to wait before the first retry. The interval time will be doubled for
   424  // each retry. For example, if this is set to one second then the first retry will happen
   425  // approximately one second after the failure of the initial request, the second retry will happen
   426  // affer four seconds, the third will happen after eitght seconds, so on.
   427  func (b *ConnectionBuilder) RetryInterval(value time.Duration) *ConnectionBuilder {
   428  	if b.err != nil {
   429  		return b
   430  	}
   431  	b.retryInterval = value
   432  	return b
   433  }
   434  
   435  // RetryJitter sets a factor that will be used to randomize the retry intervals. For example, if
   436  // this is set to 0.1 then a random adjustment between -10% and +10% will be done to the interval
   437  // for each retry.  This is intended to reduce simultaneous retries by clients when a server starts
   438  // failing.  The default value is 0.2.
   439  func (b *ConnectionBuilder) RetryJitter(value float64) *ConnectionBuilder {
   440  	if b.err != nil {
   441  		return b
   442  	}
   443  	b.retryJitter = value
   444  	return b
   445  }
   446  
   447  // TransportWrapper allows setting a transport layer into the connection for capturing and
   448  // manipulating the request or response.
   449  func (b *ConnectionBuilder) TransportWrapper(value TransportWrapper) *ConnectionBuilder {
   450  	if b.err != nil {
   451  		return b
   452  	}
   453  	b.transportWrappers = append(b.transportWrappers, value)
   454  	return b
   455  }
   456  
   457  // MetricsSubsystem sets the name of the subsystem that will be used by the connection to register
   458  // metrics with Prometheus. If this isn't explicitly specified, or if it is an empty string, then no
   459  // metrics will be registered.  For example, if the value is `api_outbound` then the following
   460  // metrics will be registered:
   461  //
   462  //	api_outbound_request_count - Number of API requests sent.
   463  //	api_outbound_request_duration_sum - Total time to send API requests, in seconds.
   464  //	api_outbound_request_duration_count - Total number of API requests measured.
   465  //	api_outbound_request_duration_bucket - Number of API requests organized in buckets.
   466  //	api_outbound_token_request_count - Number of token requests sent.
   467  //	api_outbound_token_request_duration_sum - Total time to send token requests, in seconds.
   468  //	api_outbound_token_request_duration_count - Total number of token requests measured.
   469  //	api_outbound_token_request_duration_bucket - Number of token requests organized in buckets.
   470  //
   471  // The duration buckets metrics contain an `le` label that indicates the upper bound. For example if
   472  // the `le` label is `1` then the value will be the number of requests that were processed in less
   473  // than one second.
   474  //
   475  // The API request metrics have the following labels:
   476  //
   477  //	method - Name of the HTTP method, for example GET or POST.
   478  //	path - Request path, for example /api/clusters_mgmt/v1/clusters.
   479  //	code - HTTP response code, for example 200 or 500.
   480  //
   481  // To calculate the average request duration during the last 10 minutes, for example, use a
   482  // Prometheus expression like this:
   483  //
   484  //	rate(api_outbound_request_duration_sum[10m]) / rate(api_outbound_request_duration_count[10m])
   485  //
   486  // In order to reduce the cardinality of the metrics the path label is modified to remove the
   487  // identifiers of the objects. For example, if the original path is .../clusters/123 then it will
   488  // be replaced by .../clusters/-, and the values will be accumulated. The line returned by the
   489  // metrics server will be like this:
   490  //
   491  //	api_outbound_request_count{code="200",method="GET",path="/api/clusters_mgmt/v1/clusters/-"} 56
   492  //
   493  // The meaning of that is that there were a total of 56 requests to get specific clusters,
   494  // independently of the specific identifier of the cluster.
   495  //
   496  // The token request metrics will contain the following labels:
   497  //
   498  //	code - HTTP response code, for example 200 or 500.
   499  //
   500  // The value of the `code` label will be zero when sending the request failed without a response
   501  // code, for example if it wasn't possible to open the connection, or if there was a timeout waiting
   502  // for the response.
   503  //
   504  // Note that setting this attribute is not enough to have metrics published, you also need to
   505  // create and start a metrics server, as described in the documentation of the Prometheus library.
   506  func (b *ConnectionBuilder) MetricsSubsystem(value string) *ConnectionBuilder {
   507  	if b.err != nil {
   508  		return b
   509  	}
   510  	b.metricsSubsystem = value
   511  	return b
   512  }
   513  
   514  // MetricsRegisterer sets the Prometheus registerer that will be used to register the metrics. The
   515  // default is to use the default Prometheus registerer and there is usually no need to change that.
   516  // This is intended for unit tests, where it is convenient to have a registerer that doesn't
   517  // interfere with the rest of the system.
   518  func (b *ConnectionBuilder) MetricsRegisterer(value prometheus.Registerer) *ConnectionBuilder {
   519  	if b.err != nil {
   520  		return b
   521  	}
   522  	if value == nil {
   523  		value = prometheus.DefaultRegisterer
   524  	}
   525  	b.metricsRegisterer = value
   526  	return b
   527  }
   528  
   529  // Metrics sets the name of the subsystem that will be used by the connection to register metrics
   530  // with Prometheus.
   531  //
   532  // Deprecated: has been replaced by MetricsSubsystem.
   533  func (b *ConnectionBuilder) Metrics(value string) *ConnectionBuilder {
   534  	return b.MetricsSubsystem(value)
   535  }
   536  
   537  // Load loads the connection configuration from the given source. The source must be a YAML
   538  // document with content similar to this:
   539  //
   540  //	url: https://my.server.com
   541  //	alternative_urls:
   542  //	- /api/clusters_mgmt: https://your.server.com
   543  //	- /api/accounts_mgmt: https://her.server.com
   544  //	token_url: https://openid.server.com
   545  //	user: myuser
   546  //	password: mypassword
   547  //	client_id: myclient
   548  //	client_secret: mysecret
   549  //	tokens:
   550  //	- eY...
   551  //	- eY...
   552  //	scopes:
   553  //	- openid
   554  //	insecure: false
   555  //	trusted_cas:
   556  //	- /my/ca.pem
   557  //	- /your/ca.pem
   558  //	agent: myagent
   559  //	retry: true
   560  //	retry_limit: 1
   561  //
   562  // Setting any of these fields in the file has the same effect that calling the corresponding method
   563  // of the builder.
   564  //
   565  // For details of the supported syntax see the documentation of the configuration package.
   566  func (b *ConnectionBuilder) Load(source interface{}) *ConnectionBuilder {
   567  	if b.err != nil {
   568  		return b
   569  	}
   570  
   571  	// Load the configuration:
   572  	var config *configuration.Object
   573  	config, b.err = configuration.New().
   574  		Load(source).
   575  		Build()
   576  	if b.err != nil {
   577  		return b
   578  	}
   579  	var view struct {
   580  		URL              *string           `yaml:"url"`
   581  		AlternativeURLs  map[string]string `yaml:"alternative_urls"`
   582  		TokenURL         *string           `yaml:"token_url"`
   583  		User             *string           `yaml:"user"`
   584  		Password         *string           `yaml:"password"`
   585  		ClientID         *string           `yaml:"client_id"`
   586  		ClientSecret     *string           `yaml:"client_secret"`
   587  		Tokens           []string          `yaml:"tokens"`
   588  		Insecure         *bool             `yaml:"insecure"`
   589  		TrustedCAs       []string          `yaml:"trusted_cas"`
   590  		Scopes           []string          `yaml:"scopes"`
   591  		Agent            *string           `yaml:"agent"`
   592  		Retry            *bool             `yaml:"retry"`
   593  		RetryLimit       *int              `yaml:"retry_limit"`
   594  		MetricsSubsystem *string           `yaml:"metrics_subsystem"`
   595  	}
   596  	b.err = config.Populate(&view)
   597  	if b.err != nil {
   598  		return b
   599  	}
   600  
   601  	// URL:
   602  	if view.URL != nil {
   603  		b.URL(*view.URL)
   604  	}
   605  	if view.TokenURL != nil {
   606  		b.TokenURL(*view.TokenURL)
   607  	}
   608  
   609  	// Alternative URLs:
   610  	if view.AlternativeURLs != nil {
   611  		for prefix, base := range view.AlternativeURLs {
   612  			b.AlternativeURL(prefix, base)
   613  		}
   614  	}
   615  
   616  	// User and password:
   617  	var user string
   618  	var password string
   619  	if view.User != nil {
   620  		user = *view.User
   621  	}
   622  	if view.Password != nil {
   623  		password = *view.Password
   624  	}
   625  	if user != "" || password != "" {
   626  		b.User(user, password)
   627  	}
   628  
   629  	// Client identifier and secret:
   630  	var clientID string
   631  	var clientSecret string
   632  	if view.ClientID != nil {
   633  		clientID = *view.ClientID
   634  	}
   635  	if view.ClientSecret != nil {
   636  		clientSecret = *view.ClientSecret
   637  	}
   638  	if clientID != "" || clientSecret != "" {
   639  		b.Client(clientID, clientSecret)
   640  	}
   641  
   642  	// Tokens:
   643  	if view.Tokens != nil {
   644  		b.Tokens(view.Tokens...)
   645  	}
   646  
   647  	// Scopes:
   648  	if view.Scopes != nil {
   649  		b.Scopes(view.Scopes...)
   650  	}
   651  
   652  	// Insecure:
   653  	if view.Insecure != nil {
   654  		b.Insecure(*view.Insecure)
   655  	}
   656  
   657  	// Trusted CAs:
   658  	for _, trustedCA := range view.TrustedCAs {
   659  		b.TrustedCAFile(trustedCA)
   660  	}
   661  
   662  	// Agent:
   663  	if view.Agent != nil {
   664  		b.Agent(*view.Agent)
   665  	}
   666  
   667  	// Retry:
   668  	if view.RetryLimit != nil {
   669  		b.RetryLimit(*view.RetryLimit)
   670  	}
   671  
   672  	// Metrics subsystem:
   673  	if view.MetricsSubsystem != nil {
   674  		b.MetricsSubsystem(*view.MetricsSubsystem)
   675  	}
   676  
   677  	return b
   678  }
   679  
   680  // Build uses the configuration stored in the builder to create a new connection. The builder can be
   681  // reused to create multiple connections with the same configuration. It returns a pointer to the
   682  // connection, and an error if something fails when trying to create it.
   683  //
   684  // This operation is potentially lengthy, as it may require network communications. Consider using a
   685  // context and the BuildContext method.
   686  func (b *ConnectionBuilder) Build() (connection *Connection, err error) {
   687  	return b.BuildContext(context.Background())
   688  }
   689  
   690  // BuildContext uses the configuration stored in the builder to create a new connection. The builder
   691  // can be reused to create multiple connections with the same configuration. It returns a pointer to
   692  // the connection, and an error if something fails when trying to create it.
   693  func (b *ConnectionBuilder) BuildContext(ctx context.Context) (connection *Connection, err error) {
   694  	// If an error has been detected while populating the builder then return it and finish:
   695  	if b.err != nil {
   696  		err = b.err
   697  		return
   698  	}
   699  
   700  	// Create the default logger, if needed:
   701  	if b.logger == nil {
   702  		b.logger, err = logging.NewGoLoggerBuilder().
   703  			Debug(false).
   704  			Info(true).
   705  			Warn(true).
   706  			Error(true).
   707  			Build()
   708  		if err != nil {
   709  			err = fmt.Errorf("can't create default logger: %w", err)
   710  			return
   711  		}
   712  		b.logger.Debug(ctx, "Logger wasn't provided, will use Go log")
   713  	}
   714  
   715  	// Create the URL table:
   716  	urlTable, err := b.createURLTable(ctx)
   717  	if err != nil {
   718  		return
   719  	}
   720  
   721  	// Set the default agent, if needed:
   722  	agent := b.agent
   723  	if b.agent == "" {
   724  		agent = DefaultAgent
   725  	}
   726  
   727  	// Create the metrics wrapper:
   728  	var metricsWrapper func(http.RoundTripper) http.RoundTripper
   729  	if b.metricsSubsystem != "" {
   730  		var parsed *url.URL
   731  		parsed, err = url.Parse(b.tokenURL)
   732  		if err != nil {
   733  			return
   734  		}
   735  		var wrapper *metrics.TransportWrapper
   736  		wrapper, err = metrics.NewTransportWrapper().
   737  			Path(parsed.Path).
   738  			Subsystem(b.metricsSubsystem).
   739  			Registerer(b.metricsRegisterer).
   740  			Build()
   741  		if err != nil {
   742  			return
   743  		}
   744  		metricsWrapper = wrapper.Wrap
   745  	}
   746  
   747  	// Create the logging wrapper:
   748  	var loggingWrapper func(http.RoundTripper) http.RoundTripper
   749  	if b.logger.DebugEnabled() {
   750  		wrapper := &dumpTransportWrapper{
   751  			logger: b.logger,
   752  		}
   753  		loggingWrapper = wrapper.Wrap
   754  	}
   755  
   756  	// Initialize the client selector builder:
   757  	clientSelectorBuilder := internal.NewClientSelector().
   758  		Logger(b.logger).
   759  		TrustedCAs(b.trustedCAs...).
   760  		Insecure(b.insecure)
   761  
   762  	var authnWrapper *authentication.TransportWrapper
   763  	if b.includeDefaultAuthnTransportWrapper {
   764  		// Create the authentication wrapper:
   765  		authnWrapper, err = authentication.NewTransportWrapper().
   766  			Logger(b.logger).
   767  			TokenURL(b.tokenURL).
   768  			User(b.user, b.password).
   769  			Client(b.clientID, b.clientSecret).
   770  			Tokens(b.tokens...).
   771  			Scopes(b.scopes...).
   772  			TrustedCAs(b.trustedCAs...).
   773  			Insecure(b.insecure).
   774  			TransportWrapper(metricsWrapper).
   775  			TransportWrapper(loggingWrapper).
   776  			TransportWrappers(b.transportWrappers...).
   777  			MetricsSubsystem(b.metricsSubsystem).
   778  			MetricsRegisterer(b.metricsRegisterer).
   779  			Build(ctx)
   780  		if err != nil {
   781  			return
   782  		}
   783  		clientSelectorBuilder.TransportWrapper(authnWrapper.Wrap)
   784  	}
   785  
   786  	// Create the retry wrapper:
   787  	retryWrapper, err := retry.NewTransportWrapper().
   788  		Logger(b.logger).
   789  		Limit(b.retryLimit).
   790  		Interval(b.retryInterval).
   791  		Jitter(b.retryJitter).
   792  		Build(ctx)
   793  	if err != nil {
   794  		return
   795  	}
   796  
   797  	// Create the client selector:
   798  	clientSelector, err := clientSelectorBuilder.
   799  		TransportWrapper(metricsWrapper).
   800  		TransportWrapper(retryWrapper.Wrap).
   801  		TransportWrapper(loggingWrapper).
   802  		TransportWrappers(b.transportWrappers...).
   803  		Build(ctx)
   804  	if err != nil {
   805  		return
   806  	}
   807  
   808  	// Allocate and populate the connection object:
   809  	connection = &Connection{
   810  		logger:            b.logger,
   811  		authnWrapper:      authnWrapper,
   812  		retryWrapper:      retryWrapper,
   813  		clientSelector:    clientSelector,
   814  		urlTable:          urlTable,
   815  		agent:             agent,
   816  		metricsSubsystem:  b.metricsSubsystem,
   817  		metricsRegisterer: b.metricsRegisterer,
   818  	}
   819  
   820  	return
   821  }
   822  
   823  func (b *ConnectionBuilder) createURLTable(ctx context.Context) (table []urlTableEntry, err error) {
   824  	// Check that all the prefixes are acceptable:
   825  	for prefix, base := range b.urlTable {
   826  		if !validPrefixRE.MatchString(prefix) {
   827  			err = fmt.Errorf(
   828  				"prefix '%s' for URL '%s' isn't valid; it must start with a "+
   829  					"slash and be composed of slash separated segments "+
   830  					"containing only digits, letters, dashes and undercores",
   831  				prefix, base,
   832  			)
   833  			return
   834  		}
   835  	}
   836  
   837  	// Allocate space for the table:
   838  	table = make([]urlTableEntry, len(b.urlTable))
   839  
   840  	// For each alternative URL create the regular expression that will be used to check if
   841  	// paths match it, and parse the base URL:
   842  	i := 0
   843  	for prefix, base := range b.urlTable {
   844  		entry := &table[i]
   845  		entry.prefix = prefix
   846  		pattern := fmt.Sprintf("^%s(/.*)?$", regexp.QuoteMeta(prefix))
   847  		entry.re, err = regexp.Compile(pattern)
   848  		if err != nil {
   849  			err = fmt.Errorf(
   850  				"can't compile regular expression '%s' for URL with "+
   851  					"prefix '%s' and URL '%s': %v",
   852  				pattern, prefix, base, err,
   853  			)
   854  			return
   855  		}
   856  		entry.url, err = internal.ParseServerAddress(ctx, base)
   857  		if err != nil {
   858  			err = fmt.Errorf(
   859  				"can't parse URL '%s' for prefix '%s': %w",
   860  				base, prefix, err,
   861  			)
   862  			return
   863  		}
   864  		i++
   865  	}
   866  
   867  	// Sort the entries in descending order of the length of the prefix, so that later
   868  	// when matching it will be easier to select the longest prefix that matches:
   869  	sort.Slice(table, func(i, j int) bool {
   870  		lenI := len(table[i].prefix)
   871  		lenJ := len(table[j].prefix)
   872  		return lenI > lenJ
   873  	})
   874  
   875  	// Write to the log the resulting table:
   876  	if b.logger.DebugEnabled() {
   877  		for _, entry := range table {
   878  			b.logger.Debug(
   879  				ctx,
   880  				"Added URL with prefix '%s', regular expression "+
   881  					"'%s' and URL '%s'",
   882  				entry.prefix, entry.re, entry.url.Text,
   883  			)
   884  		}
   885  	}
   886  
   887  	return
   888  }
   889  
   890  // Logger returns the logger that is used by the connection.
   891  func (c *Connection) Logger() logging.Logger {
   892  	return c.logger
   893  }
   894  
   895  // TokenURL returns the URL that the connection is using request OpenID access tokens.
   896  // An empty string is returned if the connection does not use authentication.
   897  func (c *Connection) TokenURL() string {
   898  	if c.authnWrapper == nil {
   899  		return ""
   900  	}
   901  	return c.authnWrapper.TokenURL()
   902  }
   903  
   904  // Client returns OpenID client identifier and secret that the connection is using to request OpenID
   905  // access tokens.
   906  // Empty strings are returned if the connection does not use authentication.
   907  func (c *Connection) Client() (id, secret string) {
   908  	if c.authnWrapper != nil {
   909  		id, secret = c.authnWrapper.Client()
   910  	}
   911  	return
   912  }
   913  
   914  // User returns the user name and password that the is using to request OpenID access tokens.
   915  // Empty strings are returned if the connection does not use authentication.
   916  func (c *Connection) User() (user, password string) {
   917  	if c.authnWrapper != nil {
   918  		user, password = c.authnWrapper.User()
   919  	}
   920  	return
   921  }
   922  
   923  // Scopes returns the OpenID scopes that the connection is using to request OpenID access tokens.
   924  // An empty slice is returned if the connection does not use authentication.
   925  func (c *Connection) Scopes() []string {
   926  	if c.authnWrapper == nil {
   927  		return []string{}
   928  	}
   929  	return c.authnWrapper.Scopes()
   930  }
   931  
   932  // URL returns the base URL of the API gateway.
   933  func (c *Connection) URL() string {
   934  	// The base URL will most likely be the last in the URL table because it is sorted in
   935  	// descending order of the prefix length, so it is faster to traverse the table in
   936  	// reverse order.
   937  	for i := len(c.urlTable) - 1; i >= 0; i-- {
   938  		entry := &c.urlTable[i]
   939  		if entry.prefix == "" {
   940  			return entry.url.Text
   941  		}
   942  	}
   943  	return ""
   944  }
   945  
   946  // Agent returns the `User-Agent` header that the client is using for all HTTP requests.
   947  func (c *Connection) Agent() string {
   948  	return c.agent
   949  }
   950  
   951  // TrustedCAs sets returns the certificate pool that contains the certificate authorities that are
   952  // trusted by the connection.
   953  func (c *Connection) TrustedCAs() *x509.CertPool {
   954  	return c.clientSelector.TrustedCAs()
   955  }
   956  
   957  // Insecure returns the flag that indicates if insecure communication with the server is enabled.
   958  func (c *Connection) Insecure() bool {
   959  	return c.clientSelector.Insecure()
   960  }
   961  
   962  // DisableKeepAlives returns the flag that indicates if HTTP keep alive is disabled.
   963  func (c *Connection) DisableKeepAlives() bool {
   964  	return c.clientSelector.DisableKeepAlives()
   965  }
   966  
   967  // RetryLimit gets the maximum number of retries for a request.
   968  func (c *Connection) RetryLimit() int {
   969  	return c.retryWrapper.Limit()
   970  }
   971  
   972  // RetryInteval returns the initial retry interval.
   973  func (c *Connection) RetryInterval() time.Duration {
   974  	return c.retryWrapper.Interval()
   975  }
   976  
   977  // RetryJitter returns the retry interval jitter factor.
   978  func (c *Connection) RetryJitter() float64 {
   979  	return c.retryWrapper.Jitter()
   980  }
   981  
   982  // MetricsSubsystem returns the name of the subsystem that is used by the connection to register
   983  // metrics with Prometheus. An empty string means that no metrics are registered.
   984  func (c *Connection) MetricsSubsystem() string {
   985  	return c.metricsSubsystem
   986  }
   987  
   988  // AlternativeURLs returns the alternative URLs in use by the connection. Note that the map returned
   989  // is a copy of the data used internally, so changing it will have no effect on the connection.
   990  func (c *Connection) AlternativeURLs() map[string]string {
   991  	// Copy all the entries of the URL table except the one corresponding to the empty prefix, as
   992  	// that isn't usually set via the alternative URLs mechanism:
   993  	result := map[string]string{}
   994  	for _, entry := range c.urlTable {
   995  		if entry.prefix != "" {
   996  			result[entry.prefix] = entry.url.Text
   997  		}
   998  	}
   999  	return result
  1000  }
  1001  
  1002  // AccessTransparency returns the client for the access transparency service.
  1003  func (c *Connection) AccessTransparency() *accesstransparency.Client {
  1004  	return accesstransparency.NewClient(c, "/api/access_transparency")
  1005  }
  1006  
  1007  // AccountsMgmt returns the client for the accounts management service.
  1008  func (c *Connection) AccountsMgmt() *accountsmgmt.Client {
  1009  	return accountsmgmt.NewClient(c, "/api/accounts_mgmt")
  1010  }
  1011  
  1012  // AccountsMgmt returns the client for the accounts management service.
  1013  func (c *Connection) AddonsMgmt() *addonsmgmt.Client {
  1014  	return addonsmgmt.NewClient(c, "/api/addons_mgmt")
  1015  }
  1016  
  1017  // ClustersMgmt returns the client for the clusters management service.
  1018  func (c *Connection) ClustersMgmt() *clustersmgmt.Client {
  1019  	return clustersmgmt.NewClient(c, "/api/clusters_mgmt")
  1020  }
  1021  
  1022  // AroHCP returns the client for the ARO-HCP clusters management service.
  1023  func (c *Connection) AroHCP() *arohcp.Client {
  1024  	return arohcp.NewClient(c, "/api/aro_hcp")
  1025  }
  1026  
  1027  // OSDFleetMgmt returns the client for the OSD management service.
  1028  func (c *Connection) OSDFleetMgmt() *osdfleetmgmt.Client {
  1029  
  1030  	return osdfleetmgmt.NewClient(c, "/api/osd_fleet_mgmt")
  1031  }
  1032  
  1033  // Authorizations returns the client for the authorizations service.
  1034  func (c *Connection) Authorizations() *authorizations.Client {
  1035  	return authorizations.NewClient(c, "/api/authorizations")
  1036  }
  1037  
  1038  // ServiceLogs returns the client for the logs service.
  1039  func (c *Connection) ServiceLogs() *servicelogs.Client {
  1040  	return servicelogs.NewClient(c, "/api/service_logs")
  1041  }
  1042  
  1043  // JobQueue returns the client for the Job Queues service.
  1044  func (c *Connection) JobQueue() *jobqueue.Client {
  1045  	return jobqueue.NewClient(c, "/api/job_queue")
  1046  }
  1047  
  1048  // Status board returns the client for the status board service.
  1049  func (c *Connection) StatusBoard() *statusboard.Client {
  1050  	return statusboard.NewClient(c, "/api/status-board")
  1051  }
  1052  
  1053  // ServiceMgmt returns the client for the service management service.
  1054  func (c *Connection) ServiceMgmt() *servicemgmt.Client {
  1055  	return servicemgmt.NewClient(c, "/api/service_mgmt")
  1056  }
  1057  
  1058  // WebRCA returns the client for the web RCA service.
  1059  func (c *Connection) WebRCA() *webrca.Client {
  1060  	return webrca.NewClient(c, "/api/web-rca")
  1061  }
  1062  
  1063  // Close releases all the resources used by the connection. It is very important to always close it
  1064  // once it is no longer needed, as otherwise those resources may be leaked. Trying to use a
  1065  // connection that has been closed will result in a error.
  1066  func (c *Connection) Close() error {
  1067  	var err error
  1068  
  1069  	// in case the connection is already closed, return instead of printing an error message
  1070  	if c.closed {
  1071  		return nil
  1072  	}
  1073  
  1074  	// Close the HTTP clients:
  1075  	err = c.clientSelector.Close()
  1076  	if err != nil {
  1077  		return err
  1078  	}
  1079  
  1080  	// If the default authentication wrapper is set close it
  1081  	if c.authnWrapper != nil {
  1082  		// Close the authentication wrapper:
  1083  		err = c.authnWrapper.Close()
  1084  		if err != nil {
  1085  			return err
  1086  		}
  1087  	}
  1088  
  1089  	// Mark the connection as closed, so that further attempts to use it will fail:
  1090  	c.closed = true
  1091  	return nil
  1092  }
  1093  
  1094  func (c *Connection) checkClosed() error {
  1095  	if c.closed {
  1096  		return fmt.Errorf("connection is closed")
  1097  	}
  1098  	return nil
  1099  }
  1100  
  1101  // validPrefixRE is the regular expression used to check patch prefixes.
  1102  var validPrefixRE = regexp.MustCompile(`^((/\w+)*)?$`)