Initial QSfera import
This commit is contained in:
+187
@@ -0,0 +1,187 @@
|
||||
package wait
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Validate we implement interface.
|
||||
var _ Strategy = (*TLSStrategy)(nil)
|
||||
|
||||
// TLSStrategy is a strategy for handling TLS.
|
||||
type TLSStrategy struct {
|
||||
// General Settings.
|
||||
timeout *time.Duration
|
||||
pollInterval time.Duration
|
||||
|
||||
// Custom Settings.
|
||||
certFiles *x509KeyPair
|
||||
rootFiles []string
|
||||
|
||||
// State.
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
// x509KeyPair is a pair of certificate and key files.
|
||||
type x509KeyPair struct {
|
||||
certPEMFile string
|
||||
keyPEMFile string
|
||||
}
|
||||
|
||||
// ForTLSCert returns a CertStrategy that will add a Certificate to the [tls.Config]
|
||||
// constructed from PEM formatted certificate key file pair in the container.
|
||||
func ForTLSCert(certPEMFile, keyPEMFile string) *TLSStrategy {
|
||||
return &TLSStrategy{
|
||||
certFiles: &x509KeyPair{
|
||||
certPEMFile: certPEMFile,
|
||||
keyPEMFile: keyPEMFile,
|
||||
},
|
||||
tlsConfig: &tls.Config{},
|
||||
pollInterval: defaultPollInterval(),
|
||||
}
|
||||
}
|
||||
|
||||
// ForTLSRootCAs returns a CertStrategy that sets the root CAs for the [tls.Config]
|
||||
// using the given PEM formatted files from the container.
|
||||
func ForTLSRootCAs(pemFiles ...string) *TLSStrategy {
|
||||
return &TLSStrategy{
|
||||
rootFiles: pemFiles,
|
||||
tlsConfig: &tls.Config{},
|
||||
pollInterval: defaultPollInterval(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithRootCAs sets the root CAs for the [tls.Config] using the given files from
|
||||
// the container.
|
||||
func (ws *TLSStrategy) WithRootCAs(files ...string) *TLSStrategy {
|
||||
ws.rootFiles = files
|
||||
return ws
|
||||
}
|
||||
|
||||
// WithCert sets the [tls.Config] Certificates using the given files from the container.
|
||||
func (ws *TLSStrategy) WithCert(certPEMFile, keyPEMFile string) *TLSStrategy {
|
||||
ws.certFiles = &x509KeyPair{
|
||||
certPEMFile: certPEMFile,
|
||||
keyPEMFile: keyPEMFile,
|
||||
}
|
||||
return ws
|
||||
}
|
||||
|
||||
// WithServerName sets the server for the [tls.Config].
|
||||
func (ws *TLSStrategy) WithServerName(serverName string) *TLSStrategy {
|
||||
ws.tlsConfig.ServerName = serverName
|
||||
return ws
|
||||
}
|
||||
|
||||
// WithStartupTimeout can be used to change the default startup timeout.
|
||||
func (ws *TLSStrategy) WithStartupTimeout(startupTimeout time.Duration) *TLSStrategy {
|
||||
ws.timeout = &startupTimeout
|
||||
return ws
|
||||
}
|
||||
|
||||
// WithPollInterval can be used to override the default polling interval of 100 milliseconds.
|
||||
func (ws *TLSStrategy) WithPollInterval(pollInterval time.Duration) *TLSStrategy {
|
||||
ws.pollInterval = pollInterval
|
||||
return ws
|
||||
}
|
||||
|
||||
// TLSConfig returns the TLS config once the strategy is ready.
|
||||
// If the strategy is nil, it returns nil.
|
||||
func (ws *TLSStrategy) TLSConfig() *tls.Config {
|
||||
if ws == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ws.tlsConfig
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the wait strategy.
|
||||
func (ws *TLSStrategy) String() string {
|
||||
var parts []string
|
||||
|
||||
if len(ws.rootFiles) > 0 {
|
||||
parts = append(parts, fmt.Sprintf("root CAs %v", ws.rootFiles))
|
||||
}
|
||||
|
||||
if ws.certFiles != nil {
|
||||
parts = append(parts, fmt.Sprintf("cert %q and key %q", ws.certFiles.certPEMFile, ws.certFiles.keyPEMFile))
|
||||
}
|
||||
|
||||
if len(parts) == 0 {
|
||||
return "TLS certificates"
|
||||
}
|
||||
|
||||
return strings.Join(parts, " and ")
|
||||
}
|
||||
|
||||
// WaitUntilReady implements the [Strategy] interface.
|
||||
// It waits for the CA, client cert and key files to be available in the container and
|
||||
// uses them to setup the TLS config.
|
||||
func (ws *TLSStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) error {
|
||||
size := len(ws.rootFiles)
|
||||
if ws.certFiles != nil {
|
||||
size += 2
|
||||
}
|
||||
strategies := make([]Strategy, 0, size)
|
||||
for _, file := range ws.rootFiles {
|
||||
strategies = append(strategies,
|
||||
ForFile(file).WithMatcher(func(r io.Reader) error {
|
||||
buf, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read CA cert file %q: %w", file, err)
|
||||
}
|
||||
|
||||
if ws.tlsConfig.RootCAs == nil {
|
||||
ws.tlsConfig.RootCAs = x509.NewCertPool()
|
||||
}
|
||||
|
||||
if !ws.tlsConfig.RootCAs.AppendCertsFromPEM(buf) {
|
||||
return fmt.Errorf("invalid CA cert file %q", file)
|
||||
}
|
||||
|
||||
return nil
|
||||
}).WithPollInterval(ws.pollInterval),
|
||||
)
|
||||
}
|
||||
|
||||
if ws.certFiles != nil {
|
||||
var certPEMBlock []byte
|
||||
strategies = append(strategies,
|
||||
ForFile(ws.certFiles.certPEMFile).WithMatcher(func(r io.Reader) error {
|
||||
var err error
|
||||
if certPEMBlock, err = io.ReadAll(r); err != nil {
|
||||
return fmt.Errorf("read certificate cert %q: %w", ws.certFiles.certPEMFile, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}).WithPollInterval(ws.pollInterval),
|
||||
ForFile(ws.certFiles.keyPEMFile).WithMatcher(func(r io.Reader) error {
|
||||
keyPEMBlock, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read certificate key %q: %w", ws.certFiles.keyPEMFile, err)
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||
if err != nil {
|
||||
return fmt.Errorf("x509 key pair %q %q: %w", ws.certFiles.certPEMFile, ws.certFiles.keyPEMFile, err)
|
||||
}
|
||||
|
||||
ws.tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
|
||||
return nil
|
||||
}).WithPollInterval(ws.pollInterval),
|
||||
)
|
||||
}
|
||||
|
||||
strategy := ForAll(strategies...)
|
||||
if ws.timeout != nil {
|
||||
strategy.WithStartupTimeout(*ws.timeout)
|
||||
}
|
||||
|
||||
return strategy.WaitUntilReady(ctx, target)
|
||||
}
|
||||
Reference in New Issue
Block a user