func/cmd/client.go

151 lines
5.5 KiB
Go

package cmd
import (
"fmt"
"net/http"
"os"
"knative.dev/func/cmd/prompt"
"knative.dev/func/pkg/builders/buildpacks"
"knative.dev/func/pkg/config"
"knative.dev/func/pkg/docker"
"knative.dev/func/pkg/docker/creds"
fn "knative.dev/func/pkg/functions"
fnhttp "knative.dev/func/pkg/http"
"knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/knative"
"knative.dev/func/pkg/pipelines/tekton"
)
// ClientConfig settings for use with NewClient
// These are the minimum settings necessary to create the default client
// instance which has most subsystems initialized.
type ClientConfig struct {
// Verbose logging. By default, logging output is kept to the bare minimum.
// Use this flag to configure verbose logging throughout.
Verbose bool
// Allow insecure server connections when using SSL
InsecureSkipVerify bool
}
// ClientFactory defines a constructor which assists in the creation of a Client
// for use by commands.
// See the NewClient constructor which is the fully populated ClientFactory used
// by commands by default.
// See NewClientFactory which constructs a minimal ClientFactory for use
// during testing.
type ClientFactory func(ClientConfig, ...fn.Option) (*fn.Client, func())
// NewTestClient returns a client factory which will ignore options used,
// instead using those provided when creating the factory. This allows
// for tests to create an entirely default client but with N mocks.
func NewTestClient(options ...fn.Option) ClientFactory {
return func(_ ClientConfig, _ ...fn.Option) (*fn.Client, func()) {
return fn.New(options...), func() {}
}
}
// NewClient constructs an fn.Client with the majority of
// the concrete implementations set. Provide additional Options to this constructor
// to override or augment as needed, or override the ClientFactory passed to
// commands entirely to mock for testing. Note the returned cleanup function.
// 'Namespace' is optional. If not provided (see DefaultNamespace commentary),
// the currently configured is used.
// 'Verbose' indicates the system should write out a higher amount of logging.
func NewClient(cfg ClientConfig, options ...fn.Option) (*fn.Client, func()) {
var (
t = newTransport(cfg.InsecureSkipVerify) // may provide a custom impl which proxies
c = newCredentialsProvider(config.Dir(), t) // for accessing registries
d = newKnativeDeployer(cfg.Verbose)
pp = newTektonPipelinesProvider(c, cfg.Verbose)
o = []fn.Option{ // standard (shared) options for all commands
fn.WithVerbose(cfg.Verbose),
fn.WithTransport(t),
fn.WithRepositoriesPath(config.RepositoriesPath()),
fn.WithBuilder(buildpacks.NewBuilder(buildpacks.WithVerbose(cfg.Verbose))),
fn.WithRemover(knative.NewRemover(cfg.Verbose)),
fn.WithDescriber(knative.NewDescriber(cfg.Verbose)),
fn.WithLister(knative.NewLister(cfg.Verbose)),
fn.WithDeployer(d),
fn.WithPipelinesProvider(pp),
fn.WithPusher(docker.NewPusher(
docker.WithCredentialsProvider(c),
docker.WithTransport(t),
docker.WithVerbose(cfg.Verbose))),
}
)
// Client is constructed with standard options plus any additional options
// which either augment or override the defaults.
client := fn.New(append(o, options...)...)
// A deferrable cleanup function which is used to perform any cleanup, such
// as closing the transport
cleanup := func() {
if err := t.Close(); err != nil {
fmt.Fprintf(os.Stderr, "error closing http transport. %v", err)
}
}
return client, cleanup
}
// newTransport returns a transport with cluster-flavor-specific variations
// which take advantage of additional features offered by cluster variants.
func newTransport(insecureSkipVerify bool) fnhttp.RoundTripCloser {
return fnhttp.NewRoundTripper(fnhttp.WithInsecureSkipVerify(insecureSkipVerify), fnhttp.WithOpenShiftServiceCA())
}
// newCredentialsProvider returns a credentials provider which possibly
// has cluster-flavor specific additional credential loaders to take advantage
// of features or configuration nuances of cluster variants.
func newCredentialsProvider(configPath string, t http.RoundTripper) docker.CredentialsProvider {
options := []creds.Opt{
creds.WithPromptForCredentials(prompt.NewPromptForCredentials(os.Stdin, os.Stdout, os.Stderr)),
creds.WithPromptForCredentialStore(prompt.NewPromptForCredentialStore()),
creds.WithTransport(t),
creds.WithAdditionalCredentialLoaders(k8s.GetOpenShiftDockerCredentialLoaders()...),
}
// Other cluster variants can be supported here
return creds.NewCredentialsProvider(configPath, options...)
}
func newTektonPipelinesProvider(creds docker.CredentialsProvider, verbose bool) *tekton.PipelinesProvider {
options := []tekton.Opt{
tekton.WithCredentialsProvider(creds),
tekton.WithVerbose(verbose),
tekton.WithPipelineDecorator(deployDecorator{}),
}
return tekton.NewPipelinesProvider(options...)
}
func newKnativeDeployer(verbose bool) fn.Deployer {
options := []knative.DeployerOpt{
knative.WithDeployerVerbose(verbose),
knative.WithDeployerDecorator(deployDecorator{}),
}
return knative.NewDeployer(options...)
}
type deployDecorator struct {
oshDec k8s.OpenshiftMetadataDecorator
}
func (d deployDecorator) UpdateAnnotations(function fn.Function, annotations map[string]string) map[string]string {
if k8s.IsOpenShift() {
return d.oshDec.UpdateAnnotations(function, annotations)
}
return annotations
}
func (d deployDecorator) UpdateLabels(function fn.Function, labels map[string]string) map[string]string {
if k8s.IsOpenShift() {
return d.oshDec.UpdateLabels(function, labels)
}
return labels
}