mirror of https://github.com/knative/func.git
180 lines
5.5 KiB
Go
180 lines
5.5 KiB
Go
package k8s
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"net"
|
|
"syscall"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/equality"
|
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
k8sclientcmd "k8s.io/client-go/tools/clientcmd"
|
|
)
|
|
|
|
func GetSecret(ctx context.Context, name, namespaceOverride string) (*corev1.Secret, error) {
|
|
client, namespace, err := NewClientAndResolvedNamespace(namespaceOverride)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
|
|
}
|
|
|
|
// ListSecretsNamesIfConnected lists names of Secrets present and the current k8s context
|
|
// returns empty list, if not connected to any cluster
|
|
func ListSecretsNamesIfConnected(ctx context.Context, namespaceOverride string) (names []string, err error) {
|
|
names, err = listSecretsNames(ctx, namespaceOverride)
|
|
if err != nil {
|
|
// not logged our authorized to access resources
|
|
if k8serrors.IsForbidden(err) || k8serrors.IsUnauthorized(err) || k8serrors.IsInvalid(err) || k8serrors.IsTimeout(err) {
|
|
return []string{}, nil
|
|
}
|
|
|
|
// non existent k8s cluster
|
|
var dnsErr *net.DNSError
|
|
if errors.As(err, &dnsErr) {
|
|
if dnsErr.IsNotFound || dnsErr.IsTemporary || dnsErr.IsTimeout {
|
|
return []string{}, nil
|
|
}
|
|
}
|
|
|
|
// connection refused
|
|
if errors.Is(err, syscall.ECONNREFUSED) {
|
|
return []string{}, nil
|
|
}
|
|
|
|
// invalid configuration: no configuration has been provided
|
|
if k8sclientcmd.IsEmptyConfig(err) {
|
|
return []string{}, nil
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func listSecretsNames(ctx context.Context, namespaceOverride string) (names []string, err error) {
|
|
client, namespace, err := NewClientAndResolvedNamespace(namespaceOverride)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
secrets, err := client.CoreV1().Secrets(namespace).List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, s := range secrets.Items {
|
|
names = append(names, s.Name)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func DeleteSecrets(ctx context.Context, namespaceOverride string, listOptions metav1.ListOptions) (err error) {
|
|
client, namespace, err := NewClientAndResolvedNamespace(namespaceOverride)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return client.CoreV1().Secrets(namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, listOptions)
|
|
}
|
|
|
|
func EnsureDockerRegistrySecretExist(ctx context.Context, name, namespaceOverride string, labels map[string]string, annotations map[string]string, username, password, server string) (err error) {
|
|
dockerConfigJSONContent, err := HandleDockerCfgJSONContent(username, password, "", server)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Check whether we need to create or update the Secret
|
|
secret := corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Labels: labels,
|
|
Annotations: annotations,
|
|
},
|
|
Type: corev1.SecretTypeOpaque,
|
|
Data: map[string][]byte{},
|
|
}
|
|
secret.Data["config.json"] = dockerConfigJSONContent
|
|
|
|
return EnsureSecretExist(ctx, secret, namespaceOverride)
|
|
}
|
|
|
|
func EnsureSecretExist(ctx context.Context, secret corev1.Secret, namespaceOverride string) (err error) {
|
|
client, namespace, err := NewClientAndResolvedNamespace(namespaceOverride)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Check whether Secret with specified name exist
|
|
secretNotFound := false
|
|
existingSecret, err := GetSecret(ctx, secret.Name, namespace)
|
|
if err != nil {
|
|
if !k8serrors.IsNotFound(err) {
|
|
return
|
|
}
|
|
secretNotFound = true
|
|
}
|
|
|
|
// TODO we should also compare labels and annotations
|
|
if secretNotFound || !equality.Semantic.DeepDerivative(existingSecret.Data, secret.Data) {
|
|
// Decide whether create or update
|
|
if secretNotFound {
|
|
_, err = client.CoreV1().Secrets(namespace).Create(ctx, &secret, metav1.CreateOptions{})
|
|
} else {
|
|
_, err = client.CoreV1().Secrets(namespace).Update(ctx, &secret, metav1.UpdateOptions{})
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// --- Helper methods for DockerConfigJson type of Secret
|
|
// Taken from (and converted to private):
|
|
// https://github.com/kubernetes/kubectl/blob/10c4667470db41ce138b9aae4e9590dbd7f1930d/pkg/cmd/create/create_secret_docker.go#L290
|
|
|
|
// DockerConfigJSON represents a local docker auth config file
|
|
// for pulling images.
|
|
type dockerConfigJSON struct {
|
|
Auths dockerConfig `json:"auths" datapolicy:"token"`
|
|
// +optional
|
|
HttpHeaders map[string]string `json:"HttpHeaders,omitempty" datapolicy:"token"`
|
|
}
|
|
|
|
// DockerConfig represents the config file used by the docker CLI.
|
|
// This config that represents the credentials that should be used
|
|
// when pulling images from specific image repositories.
|
|
type dockerConfig map[string]dockerConfigEntry
|
|
|
|
// dockerConfigEntry holds the user information that grant the access to docker registry
|
|
type dockerConfigEntry struct {
|
|
Username string `json:"username,omitempty"`
|
|
Password string `json:"password,omitempty" datapolicy:"password"`
|
|
Email string `json:"email,omitempty"`
|
|
Auth string `json:"auth,omitempty" datapolicy:"token"`
|
|
}
|
|
|
|
func HandleDockerCfgJSONContent(username, password, email, server string) ([]byte, error) {
|
|
dockerConfigAuth := dockerConfigEntry{
|
|
Username: username,
|
|
Password: password,
|
|
Email: email,
|
|
Auth: encodeDockerConfigFieldAuth(username, password),
|
|
}
|
|
dockerConfigJSON := dockerConfigJSON{
|
|
Auths: map[string]dockerConfigEntry{server: dockerConfigAuth},
|
|
}
|
|
|
|
return json.Marshal(dockerConfigJSON)
|
|
}
|
|
|
|
// encodeDockerConfigFieldAuth returns base64 encoding of the username and password string
|
|
func encodeDockerConfigFieldAuth(username, password string) string {
|
|
fieldValue := username + ":" + password
|
|
return base64.StdEncoding.EncodeToString([]byte(fieldValue))
|
|
}
|