mirror of https://github.com/knative/func.git
Custom improvements for OpenShift (#825)
* Custom improvements for OpenShift Signed-off-by: Matej Vasek <mvasek@redhat.com> * fixup: osh cred loader Signed-off-by: Matej Vasek <mvasek@redhat.com> * fixup: style Signed-off-by: Matej Vasek <mvasek@redhat.com> * fixup: dns-err detec for fallback in cluster dial Signed-off-by: Matej Vasek <mvasek@redhat.com>
This commit is contained in:
parent
fba018962e
commit
4fec4afca1
|
@ -53,7 +53,7 @@ kn func build --builder cnbs/sample-builder:bionic
|
|||
cmd.Flags().StringP("builder", "b", "", "Buildpack builder, either an as a an image name or a mapping name.\nSpecified value is stored in func.yaml for subsequent builds.")
|
||||
cmd.Flags().BoolP("confirm", "c", false, "Prompt to confirm all configuration options (Env: $FUNC_CONFIRM)")
|
||||
cmd.Flags().StringP("image", "i", "", "Full image name in the form [registry]/[namespace]/[name]:[tag] (optional). This option takes precedence over --registry (Env: $FUNC_IMAGE)")
|
||||
cmd.Flags().StringP("registry", "r", "", "Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined based on the local directory name. If not provided the registry will be taken from func.yaml (Env: $FUNC_REGISTRY)")
|
||||
cmd.Flags().StringP("registry", "r", GetDefaultRegistry(), "Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined based on the local directory name. If not provided the registry will be taken from func.yaml (Env: $FUNC_REGISTRY)")
|
||||
cmd.Flags().BoolP("push", "u", false, "Attempt to push the function image after being successfully built")
|
||||
setPathFlag(cmd)
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
fn "knative.dev/kn-plugin-func"
|
||||
"knative.dev/kn-plugin-func/buildpacks"
|
||||
"knative.dev/kn-plugin-func/docker"
|
||||
"knative.dev/kn-plugin-func/docker/creds"
|
||||
fnhttp "knative.dev/kn-plugin-func/http"
|
||||
"knative.dev/kn-plugin-func/knative"
|
||||
"knative.dev/kn-plugin-func/openshift"
|
||||
"knative.dev/kn-plugin-func/pipelines/tekton"
|
||||
"knative.dev/kn-plugin-func/progress"
|
||||
)
|
||||
|
@ -22,8 +22,36 @@ type ClientOptions struct {
|
|||
|
||||
type ClientFactory func(opts ClientOptions) *fn.Client
|
||||
|
||||
func NewClientFactory(transport http.RoundTripper) ClientFactory {
|
||||
return func(clientOptions ClientOptions) *fn.Client {
|
||||
// NewDefaultClientFactory returns a function that creates instances of function.Client and a cleanup routine.
|
||||
//
|
||||
// This function may allocate resources that are used by produced instances of function.Client.
|
||||
//
|
||||
// To free these resources (after instances of function.Client are no longer in use)
|
||||
// caller of this function has to invoke the cleanup routine.
|
||||
//
|
||||
// Usage:
|
||||
// newClient, cleanUp := NewDefaultClientFactory()
|
||||
// defer cleanUp()
|
||||
// fnClient := newClient()
|
||||
// // use your fnClient here...
|
||||
func NewDefaultClientFactory() (newClient ClientFactory, cleanUp func() error) {
|
||||
|
||||
var transportOpts []fnhttp.Option
|
||||
var additionalCredLoaders []creds.CredentialsCallback
|
||||
|
||||
switch {
|
||||
case openshift.IsOpenShift():
|
||||
transportOpts = append(transportOpts, openshift.WithOpenShiftServiceCA())
|
||||
additionalCredLoaders = openshift.GetDockerCredentialLoaders()
|
||||
default:
|
||||
}
|
||||
|
||||
transport := fnhttp.NewRoundTripper(transportOpts...)
|
||||
cleanUp = func() error {
|
||||
return transport.Close()
|
||||
}
|
||||
|
||||
newClient = func(clientOptions ClientOptions) *fn.Client {
|
||||
builder := buildpacks.NewBuilder()
|
||||
builder.Verbose = clientOptions.Verbose
|
||||
|
||||
|
@ -33,7 +61,8 @@ func NewClientFactory(transport http.RoundTripper) ClientFactory {
|
|||
credentialsProvider := creds.NewCredentialsProvider(
|
||||
creds.WithPromptForCredentials(newPromptForCredentials()),
|
||||
creds.WithPromptForCredentialStore(newPromptForCredentialStore()),
|
||||
creds.WithTransport(transport))
|
||||
creds.WithTransport(transport),
|
||||
creds.WithAdditionalCredentialLoaders(additionalCredLoaders...))
|
||||
|
||||
pusher := docker.NewPusher(
|
||||
docker.WithCredentialsProvider(credentialsProvider),
|
||||
|
@ -84,4 +113,15 @@ func NewClientFactory(transport http.RoundTripper) ClientFactory {
|
|||
|
||||
return fn.New(opts...)
|
||||
}
|
||||
|
||||
return newClient, cleanUp
|
||||
}
|
||||
|
||||
func GetDefaultRegistry() string {
|
||||
switch {
|
||||
case openshift.IsOpenShift():
|
||||
return openshift.GetDefaultRegistry()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ kn func deploy --image quay.io/myuser/myfunc -n myns
|
|||
"You may provide this flag multiple times for setting multiple environment variables. "+
|
||||
"To unset, specify the environment variable name followed by a \"-\" (e.g., NAME-).")
|
||||
cmd.Flags().StringP("image", "i", "", "Full image name in the form [registry]/[namespace]/[name]:[tag] (optional). This option takes precedence over --registry (Env: $FUNC_IMAGE)")
|
||||
cmd.Flags().StringP("registry", "r", "", "Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined based on the local directory name. If not provided the registry will be taken from func.yaml (Env: $FUNC_REGISTRY)")
|
||||
cmd.Flags().StringP("registry", "r", GetDefaultRegistry(), "Registry + namespace part of the image to build, ex 'quay.io/myuser'. The full image name is automatically determined based on the local directory name. If not provided the registry will be taken from func.yaml (Env: $FUNC_REGISTRY)")
|
||||
cmd.Flags().StringP("build", "b", fn.DefaultBuildType, fmt.Sprintf("Build specifies the way the function should be built. Supported types are %s (Env: $FUNC_BUILD)", fn.SupportedBuildTypes(true)))
|
||||
cmd.Flags().BoolP("push", "u", true, "Attempt to push the function image to registry before deploying (Env: $FUNC_PUSH)")
|
||||
setPathFlag(cmd)
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
"knative.dev/client/pkg/util"
|
||||
|
||||
fn "knative.dev/kn-plugin-func"
|
||||
fnhttp "knative.dev/kn-plugin-func/http"
|
||||
)
|
||||
|
||||
var exampleTemplate = template.Must(template.New("example").Parse(`
|
||||
|
@ -90,11 +89,11 @@ Create, build and deploy Functions in serverless containers for multiple runtime
|
|||
newClient := config.NewClient
|
||||
|
||||
if newClient == nil {
|
||||
transport := fnhttp.NewRoundTripper()
|
||||
root.PostRun = func(cmd *cobra.Command, args []string) {
|
||||
transport.Close()
|
||||
var cleanUp func() error
|
||||
newClient, cleanUp = NewDefaultClientFactory()
|
||||
root.PersistentPostRunE = func(cmd *cobra.Command, args []string) error {
|
||||
return cleanUp()
|
||||
}
|
||||
newClient = NewClientFactory(transport)
|
||||
}
|
||||
|
||||
root.AddCommand(NewVersionCmd(version))
|
||||
|
|
|
@ -119,7 +119,7 @@ func (d *dialerWithFallback) DialContext(ctx context.Context, network, address s
|
|||
}
|
||||
|
||||
var dnsErr *net.DNSError
|
||||
if !(errors.As(err, &dnsErr) && dnsErr.IsNotFound) {
|
||||
if !errors.As(err, &dnsErr) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
package openshift
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
|
||||
"knative.dev/kn-plugin-func/docker"
|
||||
"knative.dev/kn-plugin-func/docker/creds"
|
||||
fnhttp "knative.dev/kn-plugin-func/http"
|
||||
"knative.dev/kn-plugin-func/k8s"
|
||||
)
|
||||
|
||||
const (
|
||||
registryHost = "image-registry.openshift-image-registry.svc"
|
||||
registryHostPort = registryHost + ":5000"
|
||||
)
|
||||
|
||||
func GetServiceCA(ctx context.Context) (*x509.Certificate, error) {
|
||||
client, ns, err := k8s.NewClientAndResolvedNamespace("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfgMapName := "service-ca-config-" + rand.String(5)
|
||||
|
||||
cfgMap := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: cfgMapName,
|
||||
Annotations: map[string]string{"service.beta.openshift.io/inject-cabundle": "true"},
|
||||
},
|
||||
}
|
||||
|
||||
configMaps := client.CoreV1().ConfigMaps(ns)
|
||||
|
||||
nameSelector := fields.OneTermEqualSelector("metadata.name", cfgMapName).String()
|
||||
listOpts := metav1.ListOptions{
|
||||
Watch: true,
|
||||
FieldSelector: nameSelector,
|
||||
}
|
||||
|
||||
watch, err := configMaps.Watch(ctx, listOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer watch.Stop()
|
||||
|
||||
crtChan := make(chan string)
|
||||
go func() {
|
||||
for event := range watch.ResultChan() {
|
||||
cm, ok := event.Object.(*v1.ConfigMap)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if crt, ok := cm.Data["service-ca.crt"]; ok {
|
||||
crtChan <- crt
|
||||
close(crtChan)
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = configMaps.Create(ctx, cfgMap, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = configMaps.Delete(ctx, cfgMapName, metav1.DeleteOptions{})
|
||||
}()
|
||||
|
||||
select {
|
||||
case crt := <-crtChan:
|
||||
blk, _ := pem.Decode([]byte(crt))
|
||||
return x509.ParseCertificate(blk.Bytes)
|
||||
case <-time.After(time.Second * 5):
|
||||
return nil, errors.New("failed to get OpenShift's service CA in time")
|
||||
}
|
||||
}
|
||||
|
||||
// WithOpenShiftServiceCA enables trust to OpenShift's service CA for internal image registry
|
||||
func WithOpenShiftServiceCA() fnhttp.Option {
|
||||
var selectCA func(ctx context.Context, serverName string) (*x509.Certificate, error)
|
||||
ca, err := GetServiceCA(context.TODO())
|
||||
if err == nil {
|
||||
selectCA = func(ctx context.Context, serverName string) (*x509.Certificate, error) {
|
||||
if strings.HasPrefix(serverName, registryHost) {
|
||||
return ca, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
return fnhttp.WithSelectCA(selectCA)
|
||||
}
|
||||
|
||||
func GetDefaultRegistry() string {
|
||||
ns, _ := k8s.GetNamespace("")
|
||||
if ns == "" {
|
||||
ns = "default"
|
||||
}
|
||||
|
||||
return registryHostPort + "/" + ns
|
||||
}
|
||||
|
||||
func GetDockerCredentialLoaders() []creds.CredentialsCallback {
|
||||
conf := k8s.GetClientConfig()
|
||||
|
||||
rawConf, err := conf.RawConfig()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cc := rawConf.Contexts[rawConf.CurrentContext]
|
||||
authInfo := rawConf.AuthInfos[cc.AuthInfo]
|
||||
|
||||
var user string
|
||||
parts := strings.SplitN(cc.AuthInfo, "/", 2)
|
||||
if len(parts) >= 1 {
|
||||
user = parts[0]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
credentials := docker.Credentials{
|
||||
Username: user,
|
||||
Password: authInfo.Token,
|
||||
}
|
||||
|
||||
return []creds.CredentialsCallback{
|
||||
func(registry string) (docker.Credentials, error) {
|
||||
if registry == registryHostPort {
|
||||
return credentials, nil
|
||||
}
|
||||
return docker.Credentials{}, nil
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var isOpenShift bool
|
||||
var checkOpenShiftOnce sync.Once
|
||||
|
||||
func IsOpenShift() bool {
|
||||
checkOpenShiftOnce.Do(func() {
|
||||
client, err := k8s.NewKubernetesClientset()
|
||||
if err != nil {
|
||||
isOpenShift = false
|
||||
return
|
||||
}
|
||||
_, err = client.CoreV1().Services("openshift-image-registry").Get(context.TODO(), "image-registry", metav1.GetOptions{})
|
||||
if k8sErrors.IsNotFound(err) {
|
||||
isOpenShift = false
|
||||
return
|
||||
}
|
||||
isOpenShift = true
|
||||
})
|
||||
return isOpenShift
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package openshift_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"testing"
|
||||
|
||||
fnhttp "knative.dev/kn-plugin-func/http"
|
||||
"knative.dev/kn-plugin-func/openshift"
|
||||
)
|
||||
|
||||
func TestRoundTripper(t *testing.T) {
|
||||
if !openshift.IsOpenShift() {
|
||||
t.Skip("The cluster in not an instance of OpenShift.")
|
||||
return
|
||||
}
|
||||
|
||||
transport := fnhttp.NewRoundTripper(openshift.WithOpenShiftServiceCA())
|
||||
defer transport.Close()
|
||||
|
||||
client := http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
resp, err := client.Get("https://image-registry.openshift-image-registry.svc.cluster.local:5000/v2/")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
Loading…
Reference in New Issue