|
|
@ -19,7 +19,9 @@ import (
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
|
|
|
|
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
|
|
|
|
|
|
|
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
|
|
|
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1"
|
|
|
|
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
|
|
|
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
|
|
|
@ -28,12 +30,25 @@ import (
|
|
|
|
var bootstrapGitHubCmd = &cobra.Command{
|
|
|
|
var bootstrapGitHubCmd = &cobra.Command{
|
|
|
|
Use: "github",
|
|
|
|
Use: "github",
|
|
|
|
Short: "Bootstrap GitHub repository",
|
|
|
|
Short: "Bootstrap GitHub repository",
|
|
|
|
RunE: bootstrapGitHubCmdRun,
|
|
|
|
Long: `
|
|
|
|
|
|
|
|
The bootstrap command creates the GitHub repository if it doesn't exists and
|
|
|
|
|
|
|
|
commits the toolkit components manifests to the master branch.
|
|
|
|
|
|
|
|
Then it configure the target cluster to synchronize with the repository.
|
|
|
|
|
|
|
|
If the toolkit components are present on the cluster,
|
|
|
|
|
|
|
|
the bootstrap command will perform an upgrade if needed.`,
|
|
|
|
|
|
|
|
Example: ` # Create a GitHub personal access token and export it as an env var
|
|
|
|
|
|
|
|
export GITHUB_TOKEN=<my-token>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Run bootstrap
|
|
|
|
|
|
|
|
tk bootstrap github --owner=<org or user> --repository=<repo name>
|
|
|
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
RunE: bootstrapGitHubCmdRun,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
var (
|
|
|
|
ghOwner string
|
|
|
|
ghOwner string
|
|
|
|
ghRepository string
|
|
|
|
ghRepository string
|
|
|
|
|
|
|
|
ghInterval time.Duration
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const ghTokenName = "GITHUB_TOKEN"
|
|
|
|
const ghTokenName = "GITHUB_TOKEN"
|
|
|
@ -41,6 +56,7 @@ const ghTokenName = "GITHUB_TOKEN"
|
|
|
|
func init() {
|
|
|
|
func init() {
|
|
|
|
bootstrapGitHubCmd.Flags().StringVar(&ghOwner, "owner", "", "GitHub user or organization name")
|
|
|
|
bootstrapGitHubCmd.Flags().StringVar(&ghOwner, "owner", "", "GitHub user or organization name")
|
|
|
|
bootstrapGitHubCmd.Flags().StringVar(&ghRepository, "repository", "", "GitHub repository name")
|
|
|
|
bootstrapGitHubCmd.Flags().StringVar(&ghRepository, "repository", "", "GitHub repository name")
|
|
|
|
|
|
|
|
bootstrapGitHubCmd.Flags().DurationVar(&ghInterval, "interval", time.Minute, "sync interval")
|
|
|
|
bootstrapCmd.AddCommand(bootstrapGitHubCmd)
|
|
|
|
bootstrapCmd.AddCommand(bootstrapGitHubCmd)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -55,6 +71,11 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|
|
|
return fmt.Errorf("owner and repository are required")
|
|
|
|
return fmt.Errorf("owner and repository are required")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
kubeClient, err := utils.kubeClient(kubeconfig)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tmpDir, err := ioutil.TempDir("", namespace)
|
|
|
|
tmpDir, err := ioutil.TempDir("", namespace)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
@ -75,70 +96,37 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
w, err := repo.Worktree()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
logSuccess("repository cloned")
|
|
|
|
logSuccess("repository cloned")
|
|
|
|
|
|
|
|
|
|
|
|
// generate install manifests
|
|
|
|
// generate install manifests
|
|
|
|
logGenerate("generating manifests")
|
|
|
|
logGenerate("generating manifests")
|
|
|
|
kDir := path.Join(tmpDir, ".kustomization")
|
|
|
|
manifest, err := generateGitHubInstall(namespace, tmpDir)
|
|
|
|
if err := os.MkdirAll(kDir, os.ModePerm); err != nil {
|
|
|
|
|
|
|
|
return fmt.Errorf("generating manifests failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := genInstallManifests(bootstrapVersion, namespace, components, kDir); err != nil {
|
|
|
|
|
|
|
|
return fmt.Errorf("generating manifests failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
manifestsDir := path.Join(tmpDir, namespace)
|
|
|
|
|
|
|
|
if err := os.MkdirAll(manifestsDir, os.ModePerm); err != nil {
|
|
|
|
|
|
|
|
return fmt.Errorf("generating manifests failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
manifest := path.Join(manifestsDir, "toolkit.yaml")
|
|
|
|
|
|
|
|
if err := buildKustomization(kDir, manifest); err != nil {
|
|
|
|
|
|
|
|
return fmt.Errorf("build kustomization failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
os.RemoveAll(kDir)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// stage install manifests
|
|
|
|
|
|
|
|
_, err = w.Add(fmt.Sprintf("%s/toolkit.yaml", namespace))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
status, err := w.Status()
|
|
|
|
// stage install manifests
|
|
|
|
|
|
|
|
changed, err := commitGitHubManifests(repo, namespace)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isInstall := !status.IsClean()
|
|
|
|
if changed {
|
|
|
|
|
|
|
|
|
|
|
|
if isInstall {
|
|
|
|
|
|
|
|
// commit install manifests
|
|
|
|
|
|
|
|
logGenerate("pushing manifests")
|
|
|
|
|
|
|
|
_, err = w.Commit("Add bootstrap manifests", &git.CommitOptions{
|
|
|
|
|
|
|
|
Author: &object.Signature{
|
|
|
|
|
|
|
|
Name: "tk",
|
|
|
|
|
|
|
|
Email: "tk@@users.noreply.github.com",
|
|
|
|
|
|
|
|
When: time.Now(),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// push install manifests
|
|
|
|
// push install manifests
|
|
|
|
if err := pushGitHubRepository(ctx, repo, ghToken); err != nil {
|
|
|
|
if err := pushGitHubRepository(ctx, repo, ghToken); err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
logSuccess("manifests pushed")
|
|
|
|
logSuccess("components manifests pushed")
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
logSuccess("components are up to date")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isInstall := shouldInstallGitHub(ctx, kubeClient, namespace)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if isInstall {
|
|
|
|
// apply install manifests
|
|
|
|
// apply install manifests
|
|
|
|
logAction("installing components in %s namespace", namespace)
|
|
|
|
logAction("installing components in %s namespace", namespace)
|
|
|
|
command := fmt.Sprintf("cat %s | kubectl apply -f-", manifest)
|
|
|
|
command := fmt.Sprintf("kubectl apply -f %s", manifest)
|
|
|
|
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
|
|
|
|
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
|
|
|
|
return fmt.Errorf("install failed")
|
|
|
|
return fmt.Errorf("install failed")
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -166,37 +154,26 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
|
|
|
|
// generate and push source kustomization
|
|
|
|
// generate and push source kustomization
|
|
|
|
if isInstall {
|
|
|
|
if isInstall {
|
|
|
|
logAction("generating kustomization manifests")
|
|
|
|
logAction("generating kustomization manifests")
|
|
|
|
if err := generateGitHubKustomization(ghURL, namespace, namespace, tmpDir); err != nil {
|
|
|
|
if err := generateGitHubKustomization(ghURL, namespace, namespace, tmpDir, ghInterval); err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// stage manifests
|
|
|
|
// stage manifests
|
|
|
|
_, err = w.Add(fmt.Sprintf("%s", namespace))
|
|
|
|
changed, err = commitGitHubManifests(repo, namespace)
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// commit manifests
|
|
|
|
|
|
|
|
logAction("pushing kustomization manifests")
|
|
|
|
|
|
|
|
_, err = w.Commit("Add kustomization manifests", &git.CommitOptions{
|
|
|
|
|
|
|
|
Author: &object.Signature{
|
|
|
|
|
|
|
|
Name: "tk",
|
|
|
|
|
|
|
|
Email: "tk@@users.noreply.github.com",
|
|
|
|
|
|
|
|
When: time.Now(),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// push install manifests
|
|
|
|
// push install manifests
|
|
|
|
if err := pushGitHubRepository(ctx, repo, ghToken); err != nil {
|
|
|
|
if changed {
|
|
|
|
return err
|
|
|
|
if err := pushGitHubRepository(ctx, repo, ghToken); err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
logSuccess("kustomization manifests pushed")
|
|
|
|
logSuccess("kustomization manifests pushed")
|
|
|
|
|
|
|
|
|
|
|
|
logAction("applying kustomization manifests")
|
|
|
|
logAction("applying kustomization manifests")
|
|
|
|
if err := applyGitHubKustomization(ctx, namespace, namespace, tmpDir); err != nil {
|
|
|
|
if err := applyGitHubKustomization(ctx, kubeClient, namespace, namespace, tmpDir); err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -257,6 +234,63 @@ func checkoutGitHubRepository(ctx context.Context, path, url, token string) (*gi
|
|
|
|
return repo, nil
|
|
|
|
return repo, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func generateGitHubInstall(namespace, tmpDir string) (string, error) {
|
|
|
|
|
|
|
|
kDir := path.Join(tmpDir, ".kustomization")
|
|
|
|
|
|
|
|
defer os.RemoveAll(kDir)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if err := os.MkdirAll(kDir, os.ModePerm); err != nil {
|
|
|
|
|
|
|
|
return "", fmt.Errorf("generating manifests failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if err := genInstallManifests(bootstrapVersion, namespace, components, kDir); err != nil {
|
|
|
|
|
|
|
|
return "", fmt.Errorf("generating manifests failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
manifestsDir := path.Join(tmpDir, namespace)
|
|
|
|
|
|
|
|
if err := os.MkdirAll(manifestsDir, os.ModePerm); err != nil {
|
|
|
|
|
|
|
|
return "", fmt.Errorf("generating manifests failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
manifest := path.Join(manifestsDir, "toolkit.yaml")
|
|
|
|
|
|
|
|
if err := buildKustomization(kDir, manifest); err != nil {
|
|
|
|
|
|
|
|
return "", fmt.Errorf("build kustomization failed: %w", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return manifest, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func commitGitHubManifests(repo *git.Repository, namespace string) (bool, error) {
|
|
|
|
|
|
|
|
w, err := repo.Worktree()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_, err = w.Add(namespace)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
status, err := w.Status()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if !status.IsClean() {
|
|
|
|
|
|
|
|
if _, err := w.Commit("Add manifests", &git.CommitOptions{
|
|
|
|
|
|
|
|
Author: &object.Signature{
|
|
|
|
|
|
|
|
Name: "tk",
|
|
|
|
|
|
|
|
Email: "tk@@users.noreply.github.com",
|
|
|
|
|
|
|
|
When: time.Now(),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}); err != nil {
|
|
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func pushGitHubRepository(ctx context.Context, repo *git.Repository, token string) error {
|
|
|
|
func pushGitHubRepository(ctx context.Context, repo *git.Repository, token string) error {
|
|
|
|
auth := &http.BasicAuth{
|
|
|
|
auth := &http.BasicAuth{
|
|
|
|
Username: "git",
|
|
|
|
Username: "git",
|
|
|
@ -272,7 +306,7 @@ func pushGitHubRepository(ctx context.Context, repo *git.Repository, token strin
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func generateGitHubKustomization(url, name, namespace, tmpDir string) error {
|
|
|
|
func generateGitHubKustomization(url, name, namespace, tmpDir string, interval time.Duration) error {
|
|
|
|
gvk := sourcev1.GroupVersion.WithKind("GitRepository")
|
|
|
|
gvk := sourcev1.GroupVersion.WithKind("GitRepository")
|
|
|
|
gitRepository := sourcev1.GitRepository{
|
|
|
|
gitRepository := sourcev1.GitRepository{
|
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
|
TypeMeta: metav1.TypeMeta{
|
|
|
@ -286,7 +320,7 @@ func generateGitHubKustomization(url, name, namespace, tmpDir string) error {
|
|
|
|
Spec: sourcev1.GitRepositorySpec{
|
|
|
|
Spec: sourcev1.GitRepositorySpec{
|
|
|
|
URL: url,
|
|
|
|
URL: url,
|
|
|
|
Interval: metav1.Duration{
|
|
|
|
Interval: metav1.Duration{
|
|
|
|
Duration: 1 * time.Minute,
|
|
|
|
Duration: interval,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Reference: &sourcev1.GitRepositoryRef{
|
|
|
|
Reference: &sourcev1.GitRepositoryRef{
|
|
|
|
Branch: "master",
|
|
|
|
Branch: "master",
|
|
|
@ -343,12 +377,7 @@ func generateGitHubKustomization(url, name, namespace, tmpDir string) error {
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func applyGitHubKustomization(ctx context.Context, name, namespace, tmpDir string) error {
|
|
|
|
func applyGitHubKustomization(ctx context.Context, kubeClient client.Client, name, namespace, tmpDir string) error {
|
|
|
|
kubeClient, err := utils.kubeClient(kubeconfig)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
command := fmt.Sprintf("kubectl apply -f %s", filepath.Join(tmpDir, namespace))
|
|
|
|
command := fmt.Sprintf("kubectl apply -f %s", filepath.Join(tmpDir, namespace))
|
|
|
|
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
|
|
|
|
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
@ -362,3 +391,16 @@ func applyGitHubKustomization(ctx context.Context, name, namespace, tmpDir strin
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func shouldInstallGitHub(ctx context.Context, kubeClient client.Client, namespace string) bool {
|
|
|
|
|
|
|
|
namespacedName := types.NamespacedName{
|
|
|
|
|
|
|
|
Namespace: namespace,
|
|
|
|
|
|
|
|
Name: namespace,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
var kustomization kustomizev1.Kustomization
|
|
|
|
|
|
|
|
if err := kubeClient.Get(ctx, namespacedName, &kustomization); err != nil {
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return kustomization.Status.LastAppliedRevision == ""
|
|
|
|
|
|
|
|
}
|
|
|
|