|
|
|
@ -4,6 +4,7 @@ import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"net/url"
|
|
|
|
|
"os"
|
|
|
|
|
"path"
|
|
|
|
|
"path/filepath"
|
|
|
|
@ -40,13 +41,13 @@ the bootstrap command will perform an upgrade if needed.`,
|
|
|
|
|
export GITHUB_TOKEN=<my-token>
|
|
|
|
|
|
|
|
|
|
# Run bootstrap for a private repo owned by a GitHub organization
|
|
|
|
|
tk bootstrap github --owner=<organization> --repository=<repo name>
|
|
|
|
|
bootstrap github --owner=<organization> --repository=<repo name>
|
|
|
|
|
|
|
|
|
|
# Run bootstrap for a public repository on a personal account
|
|
|
|
|
tk bootstrap github --owner=<user> --repository=<repo name> --private=false --personal=true
|
|
|
|
|
bootstrap github --owner=<user> --repository=<repo name> --private=false --personal=true
|
|
|
|
|
|
|
|
|
|
# Run bootstrap for a private repo hosted on GitHub Enterprise
|
|
|
|
|
tk bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain>
|
|
|
|
|
bootstrap github --owner=<organization> --repository=<repo name> --hostname=<domain>
|
|
|
|
|
`,
|
|
|
|
|
RunE: bootstrapGitHubCmdRun,
|
|
|
|
|
}
|
|
|
|
@ -86,6 +87,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ghURL := fmt.Sprintf("https://%s/%s/%s", ghHostname, ghOwner, ghRepository)
|
|
|
|
|
sshURL := fmt.Sprintf("ssh://git@%s/%s/%s", ghHostname, ghOwner, ghRepository)
|
|
|
|
|
if ghOwner == "" || ghRepository == "" {
|
|
|
|
|
return fmt.Errorf("owner and repository are required")
|
|
|
|
|
}
|
|
|
|
@ -165,18 +167,30 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// create or update auth secret
|
|
|
|
|
// TODO: replace this with SSH deploy key
|
|
|
|
|
if err := generateBasicAuth(ctx, namespace, namespace, "git", ghToken); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
// setup SSH deploy key
|
|
|
|
|
if shouldCreateGitHubDeployKey(ctx, kubeClient, namespace) {
|
|
|
|
|
logAction("configuring deploy key")
|
|
|
|
|
u, err := url.Parse(sshURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("git URL parse failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key, err := generateGitHubDeployKey(ctx, kubeClient, u, namespace)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("generating deploy key failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := createGitHubDeployKey(ctx, key, ghHostname, ghOwner, ghRepository, ghToken, ghPersonal); err != nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
logSuccess("deploy key configured")
|
|
|
|
|
}
|
|
|
|
|
logSuccess("authentication configured")
|
|
|
|
|
|
|
|
|
|
// configure repo synchronization
|
|
|
|
|
if isInstall {
|
|
|
|
|
// generate source and kustomization manifests
|
|
|
|
|
logAction("generating sync manifests")
|
|
|
|
|
if err := generateGitHubKustomization(ghURL, namespace, namespace, tmpDir, ghInterval); err != nil {
|
|
|
|
|
if err := generateGitHubKustomization(sshURL, namespace, namespace, tmpDir, ghInterval); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -205,12 +219,12 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createGitHubRepository(ctx context.Context, hostname, owner, name, token string, isPrivate, isPersonal bool) error {
|
|
|
|
|
autoInit := true
|
|
|
|
|
func makeGitHubClient(hostname, token string) (*github.Client, error) {
|
|
|
|
|
auth := github.BasicAuthTransport{
|
|
|
|
|
Username: "git",
|
|
|
|
|
Password: token,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gh := github.NewClient(auth.Client())
|
|
|
|
|
if hostname != ghDefaultHostname {
|
|
|
|
|
baseURL := fmt.Sprintf("https://%s/api/v3/", hostname)
|
|
|
|
@ -218,9 +232,18 @@ func createGitHubRepository(ctx context.Context, hostname, owner, name, token st
|
|
|
|
|
if g, err := github.NewEnterpriseClient(baseURL, uploadURL, auth.Client()); err == nil {
|
|
|
|
|
gh = g
|
|
|
|
|
} else {
|
|
|
|
|
return fmt.Errorf("github client error: %w", err)
|
|
|
|
|
return nil, fmt.Errorf("github client error: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return gh, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createGitHubRepository(ctx context.Context, hostname, owner, name, token string, isPrivate, isPersonal bool) error {
|
|
|
|
|
gh, err := makeGitHubClient(hostname, token)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
org := ""
|
|
|
|
|
if !isPersonal {
|
|
|
|
|
org = owner
|
|
|
|
@ -230,7 +253,8 @@ func createGitHubRepository(ctx context.Context, hostname, owner, name, token st
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, _, err := gh.Repositories.Create(ctx, org, &github.Repository{
|
|
|
|
|
autoInit := true
|
|
|
|
|
_, _, err = gh.Repositories.Create(ctx, org, &github.Repository{
|
|
|
|
|
AutoInit: &autoInit,
|
|
|
|
|
Name: &name,
|
|
|
|
|
Private: &isPrivate,
|
|
|
|
@ -442,3 +466,67 @@ func shouldInstallGitHub(ctx context.Context, kubeClient client.Client, namespac
|
|
|
|
|
|
|
|
|
|
return kustomization.Status.LastAppliedRevision == ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func shouldCreateGitHubDeployKey(ctx context.Context, kubeClient client.Client, namespace string) bool {
|
|
|
|
|
namespacedName := types.NamespacedName{
|
|
|
|
|
Namespace: namespace,
|
|
|
|
|
Name: namespace,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var existing corev1.Secret
|
|
|
|
|
if err := kubeClient.Get(ctx, namespacedName, &existing); err != nil {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func generateGitHubDeployKey(ctx context.Context, kubeClient client.Client, url *url.URL, namespace string) (string, error) {
|
|
|
|
|
pair, err := generateKeyPair(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hostKey, err := scanHostKey(ctx, url)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
secret := corev1.Secret{
|
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
|
Name: namespace,
|
|
|
|
|
Namespace: namespace,
|
|
|
|
|
},
|
|
|
|
|
StringData: map[string]string{
|
|
|
|
|
"identity": string(pair.PrivateKey),
|
|
|
|
|
"identity.pub": string(pair.PublicKey),
|
|
|
|
|
"known_hosts": string(hostKey),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
if err := upsertSecret(ctx, kubeClient, secret); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string(pair.PublicKey), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createGitHubDeployKey(ctx context.Context, key, hostname, owner, name, token string, isPersonal bool) error {
|
|
|
|
|
gh, err := makeGitHubClient(hostname, token)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
keyName := fmt.Sprintf("tk-%s", namespace)
|
|
|
|
|
org := ""
|
|
|
|
|
if !isPersonal {
|
|
|
|
|
org = owner
|
|
|
|
|
}
|
|
|
|
|
isReadOnly := true
|
|
|
|
|
_, _, err = gh.Repositories.CreateKey(ctx, org, name, &github.Key{
|
|
|
|
|
Title: &keyName,
|
|
|
|
|
Key: &key,
|
|
|
|
|
ReadOnly: &isReadOnly,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("github create deploy key error: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|