Add create oci secret command
Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
This commit is contained in:
committed by
Stefan Prodan
parent
70d30fd52e
commit
4c576bf599
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
117
cmd/flux/create_secret_oci.go
Normal file
117
cmd/flux/create_secret_oci.go
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/sourcesecret"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/spf13/cobra"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
var createSecretOCICmd = &cobra.Command{
|
||||
Use: "oci [name]",
|
||||
Short: "Create or update a Kubernetes secret for docker authentication",
|
||||
Long: `The create secret oci command generates a Kubernetes secret with `,
|
||||
Example: ` # Create a secret for a OCI repository using basic authentication
|
||||
flux create secret oci podinfo-auth \
|
||||
--url=ghcr.io/stefanprodan/charts \
|
||||
--username=username \
|
||||
--password=password
|
||||
`,
|
||||
RunE: createSecretOCICmdRun,
|
||||
}
|
||||
|
||||
type secretOCIFlags struct {
|
||||
url string
|
||||
password string
|
||||
username string
|
||||
}
|
||||
|
||||
var secretOCIArgs = secretOCIFlags{}
|
||||
|
||||
func init() {
|
||||
createSecretOCICmd.Flags().StringVar(&secretOCIArgs.url, "url", "", "oci repository address e.g ghcr.io/stefanprodan/charts")
|
||||
createSecretOCICmd.Flags().StringVarP(&secretOCIArgs.username, "username", "u", "", "basic authentication username")
|
||||
createSecretOCICmd.Flags().StringVarP(&secretOCIArgs.password, "password", "p", "", "basic authentication")
|
||||
|
||||
createSecretCmd.AddCommand(createSecretOCICmd)
|
||||
}
|
||||
|
||||
func createSecretOCICmdRun(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("name is required")
|
||||
}
|
||||
|
||||
secretName := args[0]
|
||||
|
||||
if secretOCIArgs.url == "" {
|
||||
return fmt.Errorf("--url is required")
|
||||
}
|
||||
|
||||
if secretOCIArgs.username == "" {
|
||||
return fmt.Errorf("--username is required")
|
||||
}
|
||||
|
||||
if secretOCIArgs.password == "" {
|
||||
return fmt.Errorf("--password is required")
|
||||
}
|
||||
|
||||
if _, err := name.ParseReference(secretOCIArgs.url); err != nil {
|
||||
return fmt.Errorf("error parsing url: '%s'", err)
|
||||
}
|
||||
|
||||
opts := sourcesecret.Options{
|
||||
Name: secretName,
|
||||
Namespace: *kubeconfigArgs.Namespace,
|
||||
Registry: secretOCIArgs.url,
|
||||
Password: secretOCIArgs.password,
|
||||
Username: secretOCIArgs.username,
|
||||
}
|
||||
|
||||
secret, err := sourcesecret.Generate(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createArgs.export {
|
||||
rootCmd.Println(secret.Content)
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var s corev1.Secret
|
||||
if err := yaml.Unmarshal([]byte(secret.Content), &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertSecret(ctx, kubeClient, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Actionf("oci secret '%s' created in '%s' namespace", secretName, *kubeconfigArgs.Namespace)
|
||||
return nil
|
||||
}
|
||||
51
cmd/flux/create_secret_oci_test.go
Normal file
51
cmd/flux/create_secret_oci_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateSecretOCI(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
assert assertFunc
|
||||
}{
|
||||
{
|
||||
args: "create secret oci",
|
||||
assert: assertError("name is required"),
|
||||
},
|
||||
{
|
||||
args: "create secret oci ghcr",
|
||||
assert: assertError("--url is required"),
|
||||
},
|
||||
{
|
||||
args: "create secret oci ghcr --namespace=my-namespace --url ghcr.io --username stefanprodan --password=password --export",
|
||||
assert: assertGoldenFile("testdata/create_secret/oci/create-secret.yaml"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := cmdTestCase{
|
||||
args: tt.args,
|
||||
assert: tt.assert,
|
||||
}
|
||||
cmd.runTestCmd(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
10
cmd/flux/testdata/create_secret/oci/create-secret.yaml
vendored
Normal file
10
cmd/flux/testdata/create_secret/oci/create-secret.yaml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ghcr
|
||||
namespace: my-namespace
|
||||
stringData:
|
||||
.dockerconfigjson: '{"auths":{"ghcr.io":{"username":"stefanprodan","password":"password","auth":"c3RlZmFucHJvZGFuOnBhc3N3b3Jk"}}}'
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
|
||||
@@ -43,6 +43,7 @@ type Options struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Labels map[string]string
|
||||
Registry string
|
||||
SSHHostname string
|
||||
PrivateKeyAlgorithm PrivateKeyAlgorithm
|
||||
RSAKeyBits int
|
||||
|
||||
@@ -18,6 +18,8 @@ package sourcesecret
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
@@ -36,6 +38,18 @@ import (
|
||||
|
||||
const defaultSSHPort = 22
|
||||
|
||||
type DockerConfigJson struct {
|
||||
Auths DockerConfig `json:"auths"`
|
||||
}
|
||||
|
||||
type DockerConfig map[string]DockerConfigEntry
|
||||
|
||||
type DockerConfigEntry struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Auth string `json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
func Generate(options Options) (*manifestgen.Manifest, error) {
|
||||
var err error
|
||||
|
||||
@@ -77,7 +91,15 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
|
||||
}
|
||||
}
|
||||
|
||||
secret := buildSecret(keypair, hostKey, caFile, certFile, keyFile, options)
|
||||
var dockerCfgJson []byte
|
||||
if options.Registry != "" {
|
||||
dockerCfgJson, err = generateDockerConfigJson(options.Registry, options.Username, options.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate json for docker config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
secret := buildSecret(keypair, hostKey, caFile, certFile, keyFile, dockerCfgJson, options)
|
||||
b, err := yaml.Marshal(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -89,7 +111,7 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile []byte, options Options) (secret corev1.Secret) {
|
||||
func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile, dockerCfg []byte, options Options) (secret corev1.Secret) {
|
||||
secret.TypeMeta = metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Secret",
|
||||
@@ -101,6 +123,12 @@ func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile []byte
|
||||
secret.Labels = options.Labels
|
||||
secret.StringData = map[string]string{}
|
||||
|
||||
if dockerCfg != nil {
|
||||
secret.Type = corev1.SecretTypeDockerConfigJson
|
||||
secret.StringData[corev1.DockerConfigJsonKey] = string(dockerCfg)
|
||||
return
|
||||
}
|
||||
|
||||
if options.Username != "" && options.Password != "" {
|
||||
secret.StringData[UsernameSecretKey] = options.Username
|
||||
secret.StringData[PasswordSecretKey] = options.Password
|
||||
@@ -189,3 +217,19 @@ func resourceToString(data []byte) string {
|
||||
data = bytes.Replace(data, []byte("status: {}\n"), []byte(""), 1)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func generateDockerConfigJson(url, username, password string) ([]byte, error) {
|
||||
cred := fmt.Sprintf("%s:%s", username, password)
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(cred))
|
||||
cfg := DockerConfigJson{
|
||||
Auths: map[string]DockerConfigEntry{
|
||||
url: {
|
||||
Username: username,
|
||||
Password: password,
|
||||
Auth: auth,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user