1
0
mirror of synced 2026-03-02 03:26:57 +00:00

Compare commits

..

17 Commits

Author SHA1 Message Date
Stefan Prodan
b7727e2659 Merge pull request #74 from fluxcd/docs-notifications
Add notifications guide
2020-07-02 15:13:03 +03:00
stefanprodan
229d1d8c6e Add notifications guide 2020-07-02 14:51:28 +03:00
Stefan Prodan
c0b18f85aa Merge pull request #73 from fluxcd/notification-controller
Add notification component
2020-07-02 13:43:15 +03:00
stefanprodan
b11b9588f8 Update tk docs 2020-07-02 13:35:30 +03:00
stefanprodan
633d028841 Update kustomize controller and enable events 2020-07-02 13:14:56 +03:00
stefanprodan
a744b304a0 Add notification controller to docs 2020-07-02 12:58:01 +03:00
stefanprodan
e594350307 Add notification controller to tk components 2020-07-02 12:52:55 +03:00
Stefan Prodan
ecf7a1eac1 Merge pull request #70 from fluxcd/fluxcd-pkg
Migrate to fluxcd/pkg
2020-06-30 17:32:38 +03:00
stefanprodan
4621afcb31 Migrate to fluxcd/pkg 2020-06-30 17:11:04 +03:00
Stefan Prodan
006c949941 Merge pull request #69 from fluxcd/docs-meta
Add metadata to docs website
2020-06-26 16:53:09 +03:00
stefanprodan
72e06ab337 Add metadata to docs website 2020-06-26 16:41:04 +03:00
Hidde Beydals
f4b2a32a23 Merge pull request #67 from fluxcd/log-interface
Create logger interface
2020-06-25 18:20:17 +02:00
Hidde Beydals
329e21fd78 Create logger interface 2020-06-25 18:05:15 +02:00
Hidde Beydals
5499d15402 Merge pull request #68 from fluxcd/sort-ecdsa-curves
Sort supported ECDSA curves names
2020-06-25 18:03:29 +02:00
Hidde Beydals
d7f8a05612 Sort supported ECDSA curves names
To ensure options are always printed in the same order.
2020-06-25 17:53:41 +02:00
Hidde Beydals
1f99a75049 Merge pull request #66 from fluxcd/docs/cli-examples
Improve CLI command descriptions
2020-06-25 15:58:33 +02:00
Hidde Beydals
21fd436621 Add descriptions to all CLI commands 2020-06-25 15:49:31 +02:00
94 changed files with 601 additions and 1940 deletions

View File

@@ -20,6 +20,10 @@ jobs:
curl https://raw.githubusercontent.com/fluxcd/source-controller/master/docs/spec/v1alpha1/helmrepositories.md > docs/components/source/helmrepositories.md curl https://raw.githubusercontent.com/fluxcd/source-controller/master/docs/spec/v1alpha1/helmrepositories.md > docs/components/source/helmrepositories.md
curl https://raw.githubusercontent.com/fluxcd/kustomize-controller/master/docs/api/kustomize.md > docs/components/kustomize/api.md curl https://raw.githubusercontent.com/fluxcd/kustomize-controller/master/docs/api/kustomize.md > docs/components/kustomize/api.md
curl https://raw.githubusercontent.com/fluxcd/kustomize-controller/master/docs/spec/v1alpha1/kustomization.md > docs/components/kustomize/kustomization.md curl https://raw.githubusercontent.com/fluxcd/kustomize-controller/master/docs/spec/v1alpha1/kustomization.md > docs/components/kustomize/kustomization.md
curl https://raw.githubusercontent.com/fluxcd/notification-controller/master/docs/api/notification.md > docs/components/notification/api.md
curl https://raw.githubusercontent.com/fluxcd/notification-controller/master/docs/spec/v1alpha1/event.md > docs/components/notification/event.md
curl https://raw.githubusercontent.com/fluxcd/notification-controller/master/docs/spec/v1alpha1/alert.md > docs/components/notification/alert.md
curl https://raw.githubusercontent.com/fluxcd/notification-controller/master/docs/spec/v1alpha1/provider.md > docs/components/notification/provider.md
- name: Deploy docs - name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master uses: mhausenblas/mkdocs-deploy-gh-pages@master
env: env:

View File

@@ -30,7 +30,7 @@ jobs:
- name: Check if working tree is dirty - name: Check if working tree is dirty
run: | run: |
if [[ $(git diff --stat) != '' ]]; then if [[ $(git diff --stat) != '' ]]; then
git diff --stat git diff
echo 'run make test and commit changes' echo 'run make test and commit changes'
exit 1 exit 1
fi fi
@@ -41,7 +41,7 @@ jobs:
./bin/tk check --pre ./bin/tk check --pre
- name: tk install --version - name: tk install --version
run: | run: |
./bin/tk install --version=master --namespace=test --verbose ./bin/tk install --version=master --namespace=test --verbose --components="source-controller,kustomize-controller"
- name: tk uninstall - name: tk uninstall
run: | run: |
./bin/tk uninstall --namespace=test --crds --silent ./bin/tk uninstall --namespace=test --crds --silent

View File

@@ -40,7 +40,8 @@ import (
var bootstrapCmd = &cobra.Command{ var bootstrapCmd = &cobra.Command{
Use: "bootstrap", Use: "bootstrap",
Short: "Bootstrap commands", Short: "Bootstrap toolkit components",
Long: "The bootstrap sub-commands bootstrap the toolkit components on the targeted Git provider.",
} }
var ( var (
@@ -178,7 +179,7 @@ func applySyncManifests(ctx context.Context, kubeClient client.Client, name, nam
return err return err
} }
logWaiting("waiting for cluster sync") logger.Waitingf("waiting for cluster sync")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil { isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {

View File

@@ -27,16 +27,15 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/toolkit/pkg/git" "github.com/fluxcd/pkg/git"
) )
var bootstrapGitHubCmd = &cobra.Command{ var bootstrapGitHubCmd = &cobra.Command{
Use: "github", Use: "github",
Short: "Bootstrap GitHub repository", Short: "Bootstrap toolkit components in a GitHub repository",
Long: ` Long: `The bootstrap github command creates the GitHub repository if it doesn't exists and
The bootstrap command creates the GitHub repository if it doesn't exists and
commits the toolkit components manifests to the master branch. commits the toolkit components manifests to the master branch.
Then it configure the target cluster to synchronize with the repository. Then it configures the target cluster to synchronize with the repository.
If the toolkit components are present on the cluster, If the toolkit components are present on the cluster,
the bootstrap command will perform an upgrade if needed.`, the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a GitHub personal access token and export it as an env var Example: ` # Create a GitHub personal access token and export it as an env var
@@ -119,13 +118,13 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
defer cancel() defer cancel()
// create GitHub repository if doesn't exists // create GitHub repository if doesn't exists
logAction("connecting to %s", ghHostname) logger.Actionf("connecting to %s", ghHostname)
changed, err := provider.CreateRepository(ctx, repository) changed, err := provider.CreateRepository(ctx, repository)
if err != nil { if err != nil {
return err return err
} }
if changed { if changed {
logSuccess("repository created") logger.Successf("repository created")
} }
withErrors := false withErrors := false
@@ -133,10 +132,10 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
if !ghPersonal { if !ghPersonal {
for _, team := range ghTeams { for _, team := range ghTeams {
if changed, err := provider.AddTeam(ctx, repository, team, ghDefaultPermission); err != nil { if changed, err := provider.AddTeam(ctx, repository, team, ghDefaultPermission); err != nil {
logFailure(err.Error()) logger.Failuref(err.Error())
withErrors = true withErrors = true
} else if changed { } else if changed {
logSuccess("%s team access granted", team) logger.Successf("%s team access granted", team)
} }
} }
} }
@@ -145,10 +144,10 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
if err := repository.Checkout(ctx, bootstrapBranch, tmpDir); err != nil { if err := repository.Checkout(ctx, bootstrapBranch, tmpDir); err != nil {
return err return err
} }
logSuccess("repository cloned") logger.Successf("repository cloned")
// generate install manifests // generate install manifests
logGenerate("generating manifests") logger.Generatef("generating manifests")
manifest, err := generateInstallManifests(ghPath, namespace, tmpDir) manifest, err := generateInstallManifests(ghPath, namespace, tmpDir)
if err != nil { if err != nil {
return err return err
@@ -165,9 +164,9 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
if err := repository.Push(ctx); err != nil { if err := repository.Push(ctx); err != nil {
return err return err
} }
logSuccess("components manifests pushed") logger.Successf("components manifests pushed")
} else { } else {
logSuccess("components are up to date") logger.Successf("components are up to date")
} }
// determine if repo synchronization is working // determine if repo synchronization is working
@@ -175,16 +174,16 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
if isInstall { if isInstall {
// apply install manifests // apply install manifests
logAction("installing components in %s namespace", namespace) logger.Actionf("installing components in %s namespace", namespace)
if err := applyInstallManifests(ctx, manifest, components); err != nil { if err := applyInstallManifests(ctx, manifest, components); err != nil {
return err return err
} }
logSuccess("install completed") logger.Successf("install completed")
} }
// setup SSH deploy key // setup SSH deploy key
if shouldCreateDeployKey(ctx, kubeClient, namespace) { if shouldCreateDeployKey(ctx, kubeClient, namespace) {
logAction("configuring deploy key") logger.Actionf("configuring deploy key")
u, err := url.Parse(repository.GetSSH()) u, err := url.Parse(repository.GetSSH())
if err != nil { if err != nil {
return fmt.Errorf("git URL parse failed: %w", err) return fmt.Errorf("git URL parse failed: %w", err)
@@ -203,14 +202,14 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil { if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
return err return err
} else if changed { } else if changed {
logSuccess("deploy key configured") logger.Successf("deploy key configured")
} }
} }
// configure repo synchronization // configure repo synchronization
if isInstall { if isInstall {
// generate source and kustomization manifests // generate source and kustomization manifests
logAction("generating sync manifests") logger.Actionf("generating sync manifests")
if err := generateSyncManifests(repository.GetSSH(), namespace, namespace, ghPath, tmpDir, ghInterval); err != nil { if err := generateSyncManifests(repository.GetSSH(), namespace, namespace, ghPath, tmpDir, ghInterval); err != nil {
return err return err
} }
@@ -222,11 +221,11 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
if err := repository.Push(ctx); err != nil { if err := repository.Push(ctx); err != nil {
return err return err
} }
logSuccess("sync manifests pushed") logger.Successf("sync manifests pushed")
} }
// apply manifests and waiting for sync // apply manifests and waiting for sync
logAction("applying sync manifests") logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, namespace, namespace, ghPath, tmpDir); err != nil { if err := applySyncManifests(ctx, kubeClient, namespace, namespace, ghPath, tmpDir); err != nil {
return err return err
} }
@@ -236,6 +235,6 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("bootstrap completed with errors") return fmt.Errorf("bootstrap completed with errors")
} }
logSuccess("bootstrap finished") logger.Successf("bootstrap finished")
return nil return nil
} }

View File

@@ -27,16 +27,15 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/fluxcd/toolkit/pkg/git" "github.com/fluxcd/pkg/git"
) )
var bootstrapGitLabCmd = &cobra.Command{ var bootstrapGitLabCmd = &cobra.Command{
Use: "gitlab", Use: "gitlab",
Short: "Bootstrap GitLab repository", Short: "Bootstrap toolkit components in a GitLab repository",
Long: ` Long: `The bootstrap gitlab command creates the GitLab repository if it doesn't exists and
The bootstrap command creates the GitLab repository if it doesn't exists and
commits the toolkit components manifests to the master branch. commits the toolkit components manifests to the master branch.
Then it configure the target cluster to synchronize with the repository. Then it configures the target cluster to synchronize with the repository.
If the toolkit components are present on the cluster, If the toolkit components are present on the cluster,
the bootstrap command will perform an upgrade if needed.`, the bootstrap command will perform an upgrade if needed.`,
Example: ` # Create a GitLab API token and export it as an env var Example: ` # Create a GitLab API token and export it as an env var
@@ -110,23 +109,23 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
defer cancel() defer cancel()
// create GitLab project if doesn't exists // create GitLab project if doesn't exists
logAction("connecting to %s", glHostname) logger.Actionf("connecting to %s", glHostname)
changed, err := provider.CreateRepository(ctx, repository) changed, err := provider.CreateRepository(ctx, repository)
if err != nil { if err != nil {
return err return err
} }
if changed { if changed {
logSuccess("repository created") logger.Successf("repository created")
} }
// clone repository and checkout the master branch // clone repository and checkout the master branch
if err := repository.Checkout(ctx, bootstrapBranch, tmpDir); err != nil { if err := repository.Checkout(ctx, bootstrapBranch, tmpDir); err != nil {
return err return err
} }
logSuccess("repository cloned") logger.Successf("repository cloned")
// generate install manifests // generate install manifests
logGenerate("generating manifests") logger.Generatef("generating manifests")
manifest, err := generateInstallManifests(glPath, namespace, tmpDir) manifest, err := generateInstallManifests(glPath, namespace, tmpDir)
if err != nil { if err != nil {
return err return err
@@ -143,9 +142,9 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if err := repository.Push(ctx); err != nil { if err := repository.Push(ctx); err != nil {
return err return err
} }
logSuccess("components manifests pushed") logger.Successf("components manifests pushed")
} else { } else {
logSuccess("components are up to date") logger.Successf("components are up to date")
} }
// determine if repo synchronization is working // determine if repo synchronization is working
@@ -153,16 +152,16 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if isInstall { if isInstall {
// apply install manifests // apply install manifests
logAction("installing components in %s namespace", namespace) logger.Actionf("installing components in %s namespace", namespace)
if err := applyInstallManifests(ctx, manifest, components); err != nil { if err := applyInstallManifests(ctx, manifest, components); err != nil {
return err return err
} }
logSuccess("install completed") logger.Successf("install completed")
} }
// setup SSH deploy key // setup SSH deploy key
if shouldCreateDeployKey(ctx, kubeClient, namespace) { if shouldCreateDeployKey(ctx, kubeClient, namespace) {
logAction("configuring deploy key") logger.Actionf("configuring deploy key")
u, err := url.Parse(repository.GetSSH()) u, err := url.Parse(repository.GetSSH())
if err != nil { if err != nil {
return fmt.Errorf("git URL parse failed: %w", err) return fmt.Errorf("git URL parse failed: %w", err)
@@ -181,14 +180,14 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil { if changed, err := provider.AddDeployKey(ctx, repository, key, keyName); err != nil {
return err return err
} else if changed { } else if changed {
logSuccess("deploy key configured") logger.Successf("deploy key configured")
} }
} }
// configure repo synchronization // configure repo synchronization
if isInstall { if isInstall {
// generate source and kustomization manifests // generate source and kustomization manifests
logAction("generating sync manifests") logger.Actionf("generating sync manifests")
if err := generateSyncManifests(repository.GetSSH(), namespace, namespace, glPath, tmpDir, glInterval); err != nil { if err := generateSyncManifests(repository.GetSSH(), namespace, namespace, glPath, tmpDir, glInterval); err != nil {
return err return err
} }
@@ -200,16 +199,16 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
if err := repository.Push(ctx); err != nil { if err := repository.Push(ctx); err != nil {
return err return err
} }
logSuccess("sync manifests pushed") logger.Successf("sync manifests pushed")
} }
// apply manifests and waiting for sync // apply manifests and waiting for sync
logAction("applying sync manifests") logger.Actionf("applying sync manifests")
if err := applySyncManifests(ctx, kubeClient, namespace, namespace, glPath, tmpDir); err != nil { if err := applySyncManifests(ctx, kubeClient, namespace, namespace, glPath, tmpDir); err != nil {
return err return err
} }
} }
logSuccess("bootstrap finished") logger.Successf("bootstrap finished")
return nil return nil
} }

View File

@@ -32,8 +32,7 @@ import (
var checkCmd = &cobra.Command{ var checkCmd = &cobra.Command{
Use: "check", Use: "check",
Short: "Check requirements and installation", Short: "Check requirements and installation",
Long: ` Long: `The check command will perform a series of checks to validate that
The check command will perform a series of checks to validate that
the local environment is configured correctly and if the installed components are healthy.`, the local environment is configured correctly and if the installed components are healthy.`,
Example: ` # Run pre-installation checks Example: ` # Run pre-installation checks
check --pre check --pre
@@ -59,7 +58,7 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel() defer cancel()
logAction("checking prerequisites") logger.Actionf("checking prerequisites")
checkFailed := false checkFailed := false
if !kubectlCheck(ctx, ">=1.18.0") { if !kubectlCheck(ctx, ">=1.18.0") {
@@ -74,83 +73,83 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
if checkFailed { if checkFailed {
os.Exit(1) os.Exit(1)
} }
logSuccess("prerequisites checks passed") logger.Successf("prerequisites checks passed")
return nil return nil
} }
logAction("checking controllers") logger.Actionf("checking controllers")
if !componentsCheck() { if !componentsCheck() {
checkFailed = true checkFailed = true
} }
if checkFailed { if checkFailed {
os.Exit(1) os.Exit(1)
} }
logSuccess("all checks passed") logger.Successf("all checks passed")
return nil return nil
} }
func kubectlCheck(ctx context.Context, version string) bool { func kubectlCheck(ctx context.Context, version string) bool {
_, err := exec.LookPath("kubectl") _, err := exec.LookPath("kubectl")
if err != nil { if err != nil {
logFailure("kubectl not found") logger.Failuref("kubectl not found")
return false return false
} }
command := "kubectl version --client --short | awk '{ print $3 }'" command := "kubectl version --client --short | awk '{ print $3 }'"
output, err := utils.execCommand(ctx, ModeCapture, command) output, err := utils.execCommand(ctx, ModeCapture, command)
if err != nil { if err != nil {
logFailure("kubectl version can't be determined") logger.Failuref("kubectl version can't be determined")
return false return false
} }
v, err := semver.ParseTolerant(output) v, err := semver.ParseTolerant(output)
if err != nil { if err != nil {
logFailure("kubectl version can't be parsed") logger.Failuref("kubectl version can't be parsed")
return false return false
} }
rng, _ := semver.ParseRange(version) rng, _ := semver.ParseRange(version)
if !rng(v) { if !rng(v) {
logFailure("kubectl version must be %s", version) logger.Failuref("kubectl version must be %s", version)
return false return false
} }
logSuccess("kubectl %s %s", v.String(), version) logger.Successf("kubectl %s %s", v.String(), version)
return true return true
} }
func kubernetesCheck(version string) bool { func kubernetesCheck(version string) bool {
cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil { if err != nil {
logFailure("Kubernetes client initialization failed: %s", err.Error()) logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
return false return false
} }
client, err := kubernetes.NewForConfig(cfg) client, err := kubernetes.NewForConfig(cfg)
if err != nil { if err != nil {
logFailure("Kubernetes client initialization failed: %s", err.Error()) logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
return false return false
} }
ver, err := client.Discovery().ServerVersion() ver, err := client.Discovery().ServerVersion()
if err != nil { if err != nil {
logFailure("Kubernetes API call failed: %s", err.Error()) logger.Failuref("Kubernetes API call failed: %s", err.Error())
return false return false
} }
v, err := semver.ParseTolerant(ver.String()) v, err := semver.ParseTolerant(ver.String())
if err != nil { if err != nil {
logFailure("Kubernetes version can't be determined") logger.Failuref("Kubernetes version can't be determined")
return false return false
} }
rng, _ := semver.ParseRange(version) rng, _ := semver.ParseRange(version)
if !rng(v) { if !rng(v) {
logFailure("Kubernetes version must be %s", version) logger.Failuref("Kubernetes version must be %s", version)
return false return false
} }
logSuccess("Kubernetes %s %s", v.String(), version) logger.Successf("Kubernetes %s %s", v.String(), version)
return true return true
} }
@@ -163,10 +162,10 @@ func componentsCheck() bool {
command := fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s", command := fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
namespace, deployment, timeout.String()) namespace, deployment, timeout.String())
if output, err := utils.execCommand(ctx, ModeCapture, command); err != nil { if output, err := utils.execCommand(ctx, ModeCapture, command); err != nil {
logFailure("%s: %s", deployment, strings.TrimSuffix(output, "\n")) logger.Failuref("%s: %s", deployment, strings.TrimSuffix(output, "\n"))
ok = false ok = false
} else { } else {
logSuccess("%s is healthy", deployment) logger.Successf("%s is healthy", deployment)
} }
} }
return ok return ok

View File

@@ -24,7 +24,8 @@ import (
var createCmd = &cobra.Command{ var createCmd = &cobra.Command{
Use: "create", Use: "create",
Short: "Create commands", Short: "Create or update sources and resources",
Long: "The create sub-commands generate sources and resources.",
} }
var ( var (
@@ -34,6 +35,6 @@ var (
func init() { func init() {
createCmd.PersistentFlags().DurationVarP(&interval, "interval", "", time.Minute, "source sync interval") createCmd.PersistentFlags().DurationVarP(&interval, "interval", "", time.Minute, "source sync interval")
createCmd.PersistentFlags().BoolVar(&export, "export", false, "export in yaml format to stdout") createCmd.PersistentFlags().BoolVar(&export, "export", false, "export in YAML format to stdout")
rootCmd.AddCommand(createCmd) rootCmd.AddCommand(createCmd)
} }

View File

@@ -37,11 +37,9 @@ import (
var createKsCmd = &cobra.Command{ var createKsCmd = &cobra.Command{
Use: "kustomization [name]", Use: "kustomization [name]",
Aliases: []string{"ks"}, Aliases: []string{"ks"},
Short: "Create or update a kustomization resource", Short: "Create or update a Kustomization resource",
Long: ` Long: "The kustomization source create command generates a Kustomize resource for a given GitRepository source.",
The kustomization source command generates a kustomization.kustomize.fluxcd.io resource for a given GitRepository source. Example: ` # Create a Kustomization resource from a source at a given path
API spec: https://github.com/fluxcd/kustomize-controller/tree/master/docs/spec/v1alpha1`,
Example: ` # Create a kustomization from a source at a given path
create kustomization contour \ create kustomization contour \
--source=contour \ --source=contour \
--path="./examples/contour/" \ --path="./examples/contour/" \
@@ -52,7 +50,7 @@ API spec: https://github.com/fluxcd/kustomize-controller/tree/master/docs/spec/v
--health-check="DaemonSet/envoy.projectcontour" \ --health-check="DaemonSet/envoy.projectcontour" \
--health-check-timeout=3m --health-check-timeout=3m
# Create a kustomization that depends on the previous one # Create a Kustomization resource that depends on the previous one
create kustomization webapp \ create kustomization webapp \
--depends-on=contour \ --depends-on=contour \
--source=webapp \ --source=webapp \
@@ -61,7 +59,7 @@ API spec: https://github.com/fluxcd/kustomize-controller/tree/master/docs/spec/v
--interval=5m \ --interval=5m \
--validate=client --validate=client
# Create a kustomization that runs under a service account # Create a Kustomization resource that runs under a service account
create kustomization webapp \ create kustomization webapp \
--source=webapp \ --source=webapp \
--path="./deploy/overlays/staging" \ --path="./deploy/overlays/staging" \
@@ -88,12 +86,12 @@ var (
func init() { func init() {
createKsCmd.Flags().StringVar(&ksSource, "source", "", "GitRepository name") createKsCmd.Flags().StringVar(&ksSource, "source", "", "GitRepository name")
createKsCmd.Flags().StringVar(&ksPath, "path", "./", "path to the directory containing the kustomization file") createKsCmd.Flags().StringVar(&ksPath, "path", "./", "path to the directory containing the Kustomization file")
createKsCmd.Flags().BoolVar(&ksPrune, "prune", false, "enable garbage collection") createKsCmd.Flags().BoolVar(&ksPrune, "prune", false, "enable garbage collection")
createKsCmd.Flags().StringArrayVar(&ksHealthCheck, "health-check", nil, "workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'") createKsCmd.Flags().StringArrayVar(&ksHealthCheck, "health-check", nil, "workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'")
createKsCmd.Flags().DurationVar(&ksHealthTimeout, "health-check-timeout", 2*time.Minute, "timeout of health checking operations") createKsCmd.Flags().DurationVar(&ksHealthTimeout, "health-check-timeout", 2*time.Minute, "timeout of health checking operations")
createKsCmd.Flags().StringVar(&ksValidate, "validate", "", "validate the manifests before applying them on the cluster, can be 'client' or 'server'") createKsCmd.Flags().StringVar(&ksValidate, "validate", "", "validate the manifests before applying them on the cluster, can be 'client' or 'server'")
createKsCmd.Flags().StringArrayVar(&ksDependsOn, "depends-on", nil, "kustomization that must be ready before this kustomization can be applied") createKsCmd.Flags().StringArrayVar(&ksDependsOn, "depends-on", nil, "Kustomization that must be ready before this Kustomization can be applied")
createKsCmd.Flags().StringVar(&ksSAName, "sa-name", "", "service account name") createKsCmd.Flags().StringVar(&ksSAName, "sa-name", "", "service account name")
createKsCmd.Flags().StringVar(&ksSANamespace, "sa-namespace", "", "service account namespace") createKsCmd.Flags().StringVar(&ksSANamespace, "sa-namespace", "", "service account namespace")
createCmd.AddCommand(createKsCmd) createCmd.AddCommand(createKsCmd)
@@ -124,7 +122,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
} }
if !export { if !export {
logGenerate("generating kustomization") logger.Generatef("generating kustomization")
} }
emptyAPIGroup := "" emptyAPIGroup := ""
@@ -194,18 +192,18 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
return exportKs(kustomization) return exportKs(kustomization)
} }
logAction("applying kustomization") logger.Actionf("applying kustomization")
if err := upsertKustomization(ctx, kubeClient, kustomization); err != nil { if err := upsertKustomization(ctx, kubeClient, kustomization); err != nil {
return err return err
} }
logWaiting("waiting for kustomization sync") logger.Waitingf("waiting for kustomization sync")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil { isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil {
return err return err
} }
logSuccess("kustomization %s is ready", name) logger.Successf("kustomization %s is ready", name)
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: namespace,
@@ -217,7 +215,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
} }
if kustomization.Status.LastAppliedRevision != "" { if kustomization.Status.LastAppliedRevision != "" {
logSuccess("applied revision %s", kustomization.Status.LastAppliedRevision) logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
} else { } else {
return fmt.Errorf("kustomization sync failed") return fmt.Errorf("kustomization sync failed")
} }
@@ -238,7 +236,7 @@ func upsertKustomization(ctx context.Context, kubeClient client.Client, kustomiz
if err := kubeClient.Create(ctx, &kustomization); err != nil { if err := kubeClient.Create(ctx, &kustomization); err != nil {
return err return err
} else { } else {
logSuccess("kustomization created") logger.Successf("kustomization created")
return nil return nil
} }
} }
@@ -250,7 +248,7 @@ func upsertKustomization(ctx context.Context, kubeClient client.Client, kustomiz
return err return err
} }
logSuccess("kustomization updated") logger.Successf("kustomization updated")
return nil return nil
} }

View File

@@ -22,7 +22,8 @@ import (
var createSourceCmd = &cobra.Command{ var createSourceCmd = &cobra.Command{
Use: "source", Use: "source",
Short: "Create source commands", Short: "Create or update sources",
Long: "The create source sub-commands generate sources.",
} }
func init() { func init() {

View File

@@ -35,14 +35,14 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/toolkit/pkg/ssh" "github.com/fluxcd/pkg/ssh"
) )
var createSourceGitCmd = &cobra.Command{ var createSourceGitCmd = &cobra.Command{
Use: "git [name]", Use: "git [name]",
Short: "Create or update a git source", Short: "Create or update a GitRepository source",
Long: ` Long: `
The create source command generates a GitRepository resource and waits for it to sync. The create source git command generates a GitRepository resource and waits for it to sync.
For Git over SSH, host and SSH keys are automatically generated and stored in a Kubernetes secret. For Git over SSH, host and SSH keys are automatically generated and stored in a Kubernetes secret.
For private Git repositories, the basic authentication credentials are stored in a Kubernetes secret.`, For private Git repositories, the basic authentication credentials are stored in a Kubernetes secret.`,
Example: ` # Create a source from a public Git repository master branch Example: ` # Create a source from a public Git repository master branch
@@ -166,7 +166,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
withAuth := false withAuth := false
// TODO(hidde): move all auth prep to separate func? // TODO(hidde): move all auth prep to separate func?
if u.Scheme == "ssh" { if u.Scheme == "ssh" {
logAction("generating deploy key pair") logger.Actionf("generating deploy key pair")
pair, err := generateKeyPair(ctx) pair, err := generateKeyPair(ctx)
if err != nil { if err != nil {
return err return err
@@ -181,15 +181,15 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("aborting") return fmt.Errorf("aborting")
} }
logAction("collecting preferred public key from SSH server") logger.Actionf("collecting preferred public key from SSH server")
hostKey, err := scanHostKey(ctx, u) hostKey, err := scanHostKey(ctx, u)
if err != nil { if err != nil {
return err return err
} }
logSuccess("collected public key from SSH server:") logger.Successf("collected public key from SSH server:")
fmt.Printf("%s", hostKey) fmt.Printf("%s", hostKey)
logAction("applying secret with keys") logger.Actionf("applying secret with keys")
secret := corev1.Secret{ secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
@@ -206,7 +206,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
withAuth = true withAuth = true
} else if sourceGitUsername != "" && sourceGitPassword != "" { } else if sourceGitUsername != "" && sourceGitPassword != "" {
logAction("applying secret with basic auth credentials") logger.Actionf("applying secret with basic auth credentials")
secret := corev1.Secret{ secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
@@ -224,10 +224,10 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
if withAuth { if withAuth {
logSuccess("authentication configured") logger.Successf("authentication configured")
} }
logGenerate("generating source") logger.Generatef("generating source")
if withAuth { if withAuth {
gitRepository.Spec.SecretRef = &corev1.LocalObjectReference{ gitRepository.Spec.SecretRef = &corev1.LocalObjectReference{
@@ -235,18 +235,18 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
logAction("applying source") logger.Actionf("applying source")
if err := upsertGitRepository(ctx, kubeClient, gitRepository); err != nil { if err := upsertGitRepository(ctx, kubeClient, gitRepository); err != nil {
return err return err
} }
logWaiting("waiting for git sync") logger.Waitingf("waiting for git sync")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil { isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
return err return err
} }
logSuccess("git sync completed") logger.Successf("git sync completed")
namespacedName := types.NamespacedName{ namespacedName := types.NamespacedName{
Namespace: namespace, Namespace: namespace,
@@ -258,7 +258,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
if gitRepository.Status.Artifact != nil { if gitRepository.Status.Artifact != nil {
logSuccess("fetched revision: %s", gitRepository.Status.Artifact.Revision) logger.Successf("fetched revision: %s", gitRepository.Status.Artifact.Revision)
} else { } else {
return fmt.Errorf("git sync failed, artifact not found") return fmt.Errorf("git sync failed, artifact not found")
} }
@@ -336,7 +336,7 @@ func upsertGitRepository(ctx context.Context, kubeClient client.Client, gitRepos
if err := kubeClient.Create(ctx, &gitRepository); err != nil { if err := kubeClient.Create(ctx, &gitRepository); err != nil {
return err return err
} else { } else {
logSuccess("source created") logger.Successf("source created")
return nil return nil
} }
} }
@@ -348,7 +348,7 @@ func upsertGitRepository(ctx context.Context, kubeClient client.Client, gitRepos
return err return err
} }
logSuccess("source updated") logger.Successf("source updated")
return nil return nil
} }

View File

@@ -22,7 +22,8 @@ import (
var deleteCmd = &cobra.Command{ var deleteCmd = &cobra.Command{
Use: "delete", Use: "delete",
Short: "Delete commands", Short: "Delete sources and resources",
Long: "The delete sub-commands delete sources and resources.",
} }
var ( var (

View File

@@ -29,7 +29,8 @@ import (
var deleteKsCmd = &cobra.Command{ var deleteKsCmd = &cobra.Command{
Use: "kustomization [name]", Use: "kustomization [name]",
Aliases: []string{"ks"}, Aliases: []string{"ks"},
Short: "Delete kustomization", Short: "Delete a Kustomization resource",
Long: "The delete kustomization command deletes the given Kustomization from the cluster.",
RunE: deleteKsCmdRun, RunE: deleteKsCmdRun,
} }
@@ -64,7 +65,7 @@ func deleteKsCmdRun(cmd *cobra.Command, args []string) error {
if !deleteSilent { if !deleteSilent {
if !kustomization.Spec.Suspend { if !kustomization.Spec.Suspend {
logWaiting("This action will remove the Kubernetes objects previously applied by the %s kustomization!", name) logger.Waitingf("This action will remove the Kubernetes objects previously applied by the %s kustomization!", name)
} }
prompt := promptui.Prompt{ prompt := promptui.Prompt{
Label: "Are you sure you want to delete this kustomization", Label: "Are you sure you want to delete this kustomization",
@@ -75,12 +76,12 @@ func deleteKsCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
logAction("deleting kustomization %s in %s namespace", name, namespace) logger.Actionf("deleting kustomization %s in %s namespace", name, namespace)
err = kubeClient.Delete(ctx, &kustomization) err = kubeClient.Delete(ctx, &kustomization)
if err != nil { if err != nil {
return err return err
} }
logSuccess("kustomization deleted") logger.Successf("kustomization deleted")
return nil return nil
} }

View File

@@ -22,7 +22,8 @@ import (
var deleteSourceCmd = &cobra.Command{ var deleteSourceCmd = &cobra.Command{
Use: "source", Use: "source",
Short: "Delete sources commands", Short: "Delete sources",
Long: "The delete source sub-commands delete sources.",
} }
func init() { func init() {

View File

@@ -28,7 +28,8 @@ import (
var deleteSourceGitCmd = &cobra.Command{ var deleteSourceGitCmd = &cobra.Command{
Use: "git [name]", Use: "git [name]",
Short: "Delete git source", Short: "Delete a GitRepository source",
Long: "The delete source git command deletes the given GitRepository from the cluster.",
RunE: deleteSourceGitCmdRun, RunE: deleteSourceGitCmdRun,
} }
@@ -71,12 +72,12 @@ func deleteSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
} }
logAction("deleting source %s in %s namespace", name, namespace) logger.Actionf("deleting source %s in %s namespace", name, namespace)
err = kubeClient.Delete(ctx, &git) err = kubeClient.Delete(ctx, &git)
if err != nil { if err != nil {
return err return err
} }
logSuccess("source deleted") logger.Successf("source deleted")
return nil return nil
} }

View File

@@ -22,7 +22,8 @@ import (
var exportCmd = &cobra.Command{ var exportCmd = &cobra.Command{
Use: "export", Use: "export",
Short: "Export commands", Short: "Export resources in YAML format",
Long: "The export sub-commands export resources in YAML format.",
} }
var ( var (

View File

@@ -31,11 +31,12 @@ import (
var exportKsCmd = &cobra.Command{ var exportKsCmd = &cobra.Command{
Use: "kustomization [name]", Use: "kustomization [name]",
Aliases: []string{"ks"}, Aliases: []string{"ks"},
Short: "Export kustomization in YAML format", Short: "Export Kustomization resources in YAML format",
Example: ` # Export all kustomizations Long: "The export kustomization command exports one or all Kustomization resources in YAML format.",
Example: ` # Export all Kustomization resources
export kustomization --all > kustomizations.yaml export kustomization --all > kustomizations.yaml
# Export a kustomization # Export a Kustomization
export kustomization my-app > kustomization.yaml export kustomization my-app > kustomization.yaml
`, `,
RunE: exportKsCmdRun, RunE: exportKsCmdRun,
@@ -66,7 +67,7 @@ func exportKsCmdRun(cmd *cobra.Command, args []string) error {
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logFailure("no kustomizations found in %s namespace", namespace) logger.Failuref("no kustomizations found in %s namespace", namespace)
return nil return nil
} }

View File

@@ -22,7 +22,8 @@ import (
var exportSourceCmd = &cobra.Command{ var exportSourceCmd = &cobra.Command{
Use: "source", Use: "source",
Short: "Export source commands", Short: "Export sources",
Long: "The export source sub-commands export sources in YAML format.",
} }
var ( var (

View File

@@ -31,11 +31,12 @@ import (
var exportSourceGitCmd = &cobra.Command{ var exportSourceGitCmd = &cobra.Command{
Use: "git [name]", Use: "git [name]",
Short: "Export git sources in YAML format", Short: "Export GitRepository sources in YAML format",
Example: ` # Export all git sources Long: "The export source git command exports on or all GitRepository sources in YAML format.",
Example: ` # Export all GitRepository sources
export source git --all > sources.yaml export source git --all > sources.yaml
# Export a git source including the SSH keys or basic auth credentials # Export a GitRepository source including the SSH key pair or basic auth credentials
export source git my-private-repo --with-credentials > source.yaml export source git my-private-repo --with-credentials > source.yaml
`, `,
RunE: exportSourceGitCmdRun, RunE: exportSourceGitCmdRun,
@@ -66,7 +67,7 @@ func exportSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logFailure("no source found in %s namespace", namespace) logger.Failuref("no source found in %s namespace", namespace)
return nil return nil
} }

View File

@@ -19,6 +19,7 @@ package main
import ( import (
"crypto/elliptic" "crypto/elliptic"
"fmt" "fmt"
"sort"
"strconv" "strconv"
"strings" "strings"
) )
@@ -124,5 +125,6 @@ func ecdsaCurves() []string {
for k := range supportedECDSACurves { for k := range supportedECDSACurves {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys)
return keys return keys
} }

View File

@@ -22,7 +22,8 @@ import (
var getCmd = &cobra.Command{ var getCmd = &cobra.Command{
Use: "get", Use: "get",
Short: "Get commands", Short: "Get sources and resources",
Long: "The get sub-commands print the statuses of sources and resources.",
} }
func init() { func init() {

View File

@@ -28,10 +28,9 @@ import (
var getKsCmd = &cobra.Command{ var getKsCmd = &cobra.Command{
Use: "kustomizations", Use: "kustomizations",
Aliases: []string{"ks"}, Aliases: []string{"ks"},
Short: "Get kustomizations status", Short: "Get Kustomization source statuses",
Long: ` Long: "The get kustomizations command prints the statuses of the resources.",
The get kustomizations command prints the status of the resources.`, RunE: getKsCmdRun,
RunE: getKsCmdRun,
} }
func init() { func init() {
@@ -54,13 +53,13 @@ func getKsCmdRun(cmd *cobra.Command, args []string) error {
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logFailure("no kustomizations found in %s namespace", namespace) logger.Failuref("no kustomizations found in %s namespace", namespace)
return nil return nil
} }
for _, kustomization := range list.Items { for _, kustomization := range list.Items {
if kustomization.Spec.Suspend { if kustomization.Spec.Suspend {
logSuccess("%s is suspended", kustomization.GetName()) logger.Successf("%s is suspended", kustomization.GetName())
continue continue
} }
isInitialized := false isInitialized := false
@@ -68,19 +67,19 @@ func getKsCmdRun(cmd *cobra.Command, args []string) error {
if condition.Type == kustomizev1.ReadyCondition { if condition.Type == kustomizev1.ReadyCondition {
if condition.Status != corev1.ConditionFalse { if condition.Status != corev1.ConditionFalse {
if kustomization.Status.LastAppliedRevision != "" { if kustomization.Status.LastAppliedRevision != "" {
logSuccess("%s last applied revision %s", kustomization.GetName(), kustomization.Status.LastAppliedRevision) logger.Successf("%s last applied revision %s", kustomization.GetName(), kustomization.Status.LastAppliedRevision)
} else { } else {
logSuccess("%s reconciling", kustomization.GetName()) logger.Successf("%s reconciling", kustomization.GetName())
} }
} else { } else {
logFailure("%s %s", kustomization.GetName(), condition.Message) logger.Failuref("%s %s", kustomization.GetName(), condition.Message)
} }
isInitialized = true isInitialized = true
break break
} }
} }
if !isInitialized { if !isInitialized {
logFailure("%s is not ready", kustomization.GetName()) logger.Failuref("%s is not ready", kustomization.GetName())
} }
} }
return nil return nil

View File

@@ -22,7 +22,8 @@ import (
var getSourceCmd = &cobra.Command{ var getSourceCmd = &cobra.Command{
Use: "sources", Use: "sources",
Short: "Get sources commands", Short: "Get source statuses",
Long: "The get source sub-commands print the statuses of the sources.",
} }
func init() { func init() {

View File

@@ -27,10 +27,9 @@ import (
var getSourceGitCmd = &cobra.Command{ var getSourceGitCmd = &cobra.Command{
Use: "git", Use: "git",
Short: "Get git sources status", Short: "Get GitRepository source statuses",
Long: ` Long: "The get sources git command prints the status of the GitRepository sources.",
The get sources command prints the status of the git resources.`, RunE: getSourceGitCmdRun,
RunE: getSourceGitCmdRun,
} }
func init() { func init() {
@@ -53,7 +52,7 @@ func getSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
if len(list.Items) == 0 { if len(list.Items) == 0 {
logFailure("no sources found in %s namespace", namespace) logger.Failuref("no sources found in %s namespace", namespace)
return nil return nil
} }
@@ -62,16 +61,16 @@ func getSourceGitCmdRun(cmd *cobra.Command, args []string) error {
for _, condition := range source.Status.Conditions { for _, condition := range source.Status.Conditions {
if condition.Type == sourcev1.ReadyCondition { if condition.Type == sourcev1.ReadyCondition {
if condition.Status != corev1.ConditionFalse { if condition.Status != corev1.ConditionFalse {
logSuccess("%s last fetched revision: %s", source.GetName(), source.Status.Artifact.Revision) logger.Successf("%s last fetched revision: %s", source.GetName(), source.Status.Artifact.Revision)
} else { } else {
logFailure("%s %s", source.GetName(), condition.Message) logger.Failuref("%s %s", source.GetName(), condition.Message)
} }
isInitialized = true isInitialized = true
break break
} }
} }
if !isInitialized { if !isInitialized {
logFailure("%s is not ready", source.GetName()) logger.Failuref("%s is not ready", source.GetName())
} }
} }
return nil return nil

View File

@@ -33,8 +33,7 @@ import (
var installCmd = &cobra.Command{ var installCmd = &cobra.Command{
Use: "install", Use: "install",
Short: "Install the toolkit components", Short: "Install the toolkit components",
Long: ` Long: `The install command deploys the toolkit components in the specified namespace.
The install command deploys the toolkit components in the specified namespace.
If a previous version is installed, then an in-place upgrade will be performed.`, If a previous version is installed, then an in-place upgrade will be performed.`,
Example: ` # Install the latest version in the gitops-systems namespace Example: ` # Install the latest version in the gitops-systems namespace
install --version=master --namespace=gitops-systems install --version=master --namespace=gitops-systems
@@ -82,7 +81,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
} }
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
logGenerate("generating manifests") logger.Generatef("generating manifests")
if kustomizePath == "" { if kustomizePath == "" {
err = genInstallManifests(installVersion, namespace, components, tmpDir) err = genInstallManifests(installVersion, namespace, components, tmpDir)
if err != nil { if err != nil {
@@ -104,9 +103,9 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
fmt.Print(yaml) fmt.Print(yaml)
} }
} }
logSuccess("manifests build completed") logger.Successf("manifests build completed")
logAction("installing components in %s namespace", namespace) logger.Actionf("installing components in %s namespace", namespace)
applyOutput := ModeStderrOS applyOutput := ModeStderrOS
if verbose { if verbose {
applyOutput = ModeOS applyOutput = ModeOS
@@ -122,24 +121,24 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
} }
if installDryRun { if installDryRun {
logSuccess("install dry-run finished") logger.Successf("install dry-run finished")
return nil return nil
} else { } else {
logSuccess("install completed") logger.Successf("install completed")
} }
logWaiting("verifying installation") logger.Waitingf("verifying installation")
for _, deployment := range components { for _, deployment := range components {
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s", command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
namespace, deployment, timeout.String()) namespace, deployment, timeout.String())
if _, err := utils.execCommand(ctx, applyOutput, command); err != nil { if _, err := utils.execCommand(ctx, applyOutput, command); err != nil {
return fmt.Errorf("install failed") return fmt.Errorf("install failed")
} else { } else {
logSuccess("%s ready", deployment) logger.Successf("%s ready", deployment)
} }
} }
logSuccess("install finished") logger.Successf("install finished")
return nil return nil
} }

View File

@@ -18,22 +18,24 @@ package main
import "fmt" import "fmt"
func logAction(format string, a ...interface{}) { type printLogger struct{}
func (l printLogger) Actionf(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...)) fmt.Println(``, fmt.Sprintf(format, a...))
} }
func logGenerate(format string, a ...interface{}) { func (l printLogger) Generatef(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...)) fmt.Println(``, fmt.Sprintf(format, a...))
} }
func logWaiting(format string, a ...interface{}) { func (l printLogger) Waitingf(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...)) fmt.Println(``, fmt.Sprintf(format, a...))
} }
func logSuccess(format string, a ...interface{}) { func (l printLogger) Successf(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...)) fmt.Println(``, fmt.Sprintf(format, a...))
} }
func logFailure(format string, a ...interface{}) { func (l printLogger) Failuref(format string, a ...interface{}) {
fmt.Println(``, fmt.Sprintf(format, a...)) fmt.Println(``, fmt.Sprintf(format, a...))
} }

View File

@@ -25,6 +25,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
tklog "github.com/fluxcd/toolkit/pkg/log"
) )
var VERSION = "0.0.0-dev.0" var VERSION = "0.0.0-dev.0"
@@ -48,16 +50,16 @@ var rootCmd = &cobra.Command{
--branch=master \ --branch=master \
--interval=3m --interval=3m
# List git sources and their status # List GitRepository sources and their status
tk get sources git tk get sources git
# Trigger a git sync # Trigger a GitRepository source sync
tk sync source git webapp-latest tk sync source git webapp-latest
# Export git sources in YAML format # Export GitRepository sources in YAML format
tk export source git --all > sources.yaml tk export source git --all > sources.yaml
# Create a kustomization for deploying a series of microservices # Create a Kustomization for deploying a series of microservices
tk create kustomization webapp-dev \ tk create kustomization webapp-dev \
--source=webapp-latest \ --source=webapp-latest \
--path="./deploy/webapp/" \ --path="./deploy/webapp/" \
@@ -68,22 +70,22 @@ var rootCmd = &cobra.Command{
--health-check="Deployment/frontend.webapp" \ --health-check="Deployment/frontend.webapp" \
--health-check-timeout=2m --health-check-timeout=2m
# Trigger a git sync and apply changes if any # Trigger a git sync of the Kustomization's source and apply changes
tk sync kustomization webapp-dev --with-source tk sync kustomization webapp-dev --with-source
# Suspend a kustomization reconciliation # Suspend a Kustomization reconciliation
tk suspend kustomization webapp-dev tk suspend kustomization webapp-dev
# Export kustomizations in YAML format # Export Kustomizations in YAML format
tk export kustomization --all > kustomizations.yaml tk export kustomization --all > kustomizations.yaml
# Resume a kustomization reconciliation # Resume a Kustomization reconciliation
tk resume kustomization webapp-dev tk resume kustomization webapp-dev
# Delete a kustomization # Delete a Kustomization
tk delete kustomization webapp-dev tk delete kustomization webapp-dev
# Delete a git source # Delete a GitRepository source
tk delete source git webapp-latest tk delete source git webapp-latest
# Uninstall the toolkit and delete CRDs # Uninstall the toolkit and delete CRDs
@@ -98,7 +100,8 @@ var (
verbose bool verbose bool
components []string components []string
utils Utils utils Utils
pollInterval = 2 * time.Second pollInterval = 2 * time.Second
logger tklog.Logger = printLogger{}
) )
func init() { func init() {
@@ -109,7 +112,7 @@ func init() {
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "", false, rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "", false,
"print generated objects") "print generated objects")
rootCmd.PersistentFlags().StringSliceVar(&components, "components", rootCmd.PersistentFlags().StringSliceVar(&components, "components",
[]string{"source-controller", "kustomize-controller"}, []string{"source-controller", "kustomize-controller", "notification-controller"},
"list of components, accepts comma-separated values") "list of components, accepts comma-separated values")
} }
@@ -118,7 +121,7 @@ func main() {
generateDocs() generateDocs()
kubeconfigFlag() kubeconfigFlag()
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
logFailure("%v", err) logger.Failuref("%v", err)
os.Exit(1) os.Exit(1)
} }
} }

View File

@@ -22,7 +22,8 @@ import (
var resumeCmd = &cobra.Command{ var resumeCmd = &cobra.Command{
Use: "resume", Use: "resume",
Short: "Resume commands", Short: "Resume suspended resources",
Long: "The resume sub-commands resume a suspended resource.",
} }
func init() { func init() {

View File

@@ -32,9 +32,10 @@ import (
var resumeKsCmd = &cobra.Command{ var resumeKsCmd = &cobra.Command{
Use: "kustomization [name]", Use: "kustomization [name]",
Aliases: []string{"ks"}, Aliases: []string{"ks"},
Short: "Resume kustomization", Short: "Resume a suspended Kustomization",
Long: "The resume command marks a previously suspended Kustomization resource for reconciliation and waits for it to finish the apply.", Long: `The resume command marks a previously suspended Kustomization resource for reconciliation and waits for it to
RunE: resumeKsCmdRun, finish the apply.`,
RunE: resumeKsCmdRun,
} }
func init() { func init() {
@@ -65,20 +66,20 @@ func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logAction("resuming kustomization %s in %s namespace", name, namespace) logger.Actionf("resuming kustomization %s in %s namespace", name, namespace)
kustomization.Spec.Suspend = false kustomization.Spec.Suspend = false
if err := kubeClient.Update(ctx, &kustomization); err != nil { if err := kubeClient.Update(ctx, &kustomization); err != nil {
return err return err
} }
logSuccess("kustomization resumed") logger.Successf("kustomization resumed")
logWaiting("waiting for kustomization sync") logger.Waitingf("waiting for kustomization sync")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationResumed(ctx, kubeClient, name, namespace)); err != nil { isKustomizationResumed(ctx, kubeClient, name, namespace)); err != nil {
return err return err
} }
logSuccess("kustomization sync completed") logger.Successf("kustomization sync completed")
err = kubeClient.Get(ctx, namespacedName, &kustomization) err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil { if err != nil {
@@ -86,7 +87,7 @@ func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
} }
if kustomization.Status.LastAppliedRevision != "" { if kustomization.Status.LastAppliedRevision != "" {
logSuccess("applied revision %s", kustomization.Status.LastAppliedRevision) logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
} else { } else {
return fmt.Errorf("kustomization sync failed") return fmt.Errorf("kustomization sync failed")
} }

View File

@@ -22,7 +22,8 @@ import (
var suspendCmd = &cobra.Command{ var suspendCmd = &cobra.Command{
Use: "suspend", Use: "suspend",
Short: "Suspend commands", Short: "Suspend resources",
Long: "The suspend sub-commands suspend the reconciliation of a resource.",
} }
func init() { func init() {

View File

@@ -27,7 +27,7 @@ import (
var suspendKsCmd = &cobra.Command{ var suspendKsCmd = &cobra.Command{
Use: "kustomization [name]", Use: "kustomization [name]",
Aliases: []string{"ks"}, Aliases: []string{"ks"},
Short: "Suspend kustomization", Short: "Suspend reconciliation of Kustomization",
Long: "The suspend command disables the reconciliation of a Kustomization resource.", Long: "The suspend command disables the reconciliation of a Kustomization resource.",
RunE: suspendKsCmdRun, RunE: suspendKsCmdRun,
} }
@@ -60,12 +60,12 @@ func suspendKsCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
logAction("suspending kustomization %s in %s namespace", name, namespace) logger.Actionf("suspending kustomization %s in %s namespace", name, namespace)
kustomization.Spec.Suspend = true kustomization.Spec.Suspend = true
if err := kubeClient.Update(ctx, &kustomization); err != nil { if err := kubeClient.Update(ctx, &kustomization); err != nil {
return err return err
} }
logSuccess("kustomization suspended") logger.Successf("kustomization suspended")
return nil return nil
} }

View File

@@ -22,7 +22,8 @@ import (
var syncCmd = &cobra.Command{ var syncCmd = &cobra.Command{
Use: "sync", Use: "sync",
Short: "Synchronize commands", Short: "Synchronize sources and resources",
Long: "The sync sub-commands trigger a reconciliation of sources and resources.",
} }
func init() { func init() {

View File

@@ -30,13 +30,13 @@ import (
var syncKsCmd = &cobra.Command{ var syncKsCmd = &cobra.Command{
Use: "kustomization [name]", Use: "kustomization [name]",
Aliases: []string{"ks"}, Aliases: []string{"ks"},
Short: "Synchronize kustomization", Short: "Synchronize a Kustomization resource",
Long: ` Long: `
The sync kustomization command triggers a reconciliation of a Kustomization resource and waits for it to finish.`, The sync kustomization command triggers a reconciliation of a Kustomization resource and waits for it to finish.`,
Example: ` # Trigger a kustomization apply outside of the reconciliation interval Example: ` # Trigger a Kustomization apply outside of the reconciliation interval
sync kustomization podinfo sync kustomization podinfo
# Trigger a git sync of the kustomization source and apply changes # Trigger a sync of the Kustomization's source and apply changes
sync kustomization podinfo --with-source sync kustomization podinfo --with-source
`, `,
RunE: syncKsCmdRun, RunE: syncKsCmdRun,
@@ -83,7 +83,7 @@ func syncKsCmdRun(cmd *cobra.Command, args []string) error {
return err return err
} }
} else { } else {
logAction("annotating kustomization %s in %s namespace", name, namespace) logger.Actionf("annotating kustomization %s in %s namespace", name, namespace)
if kustomization.Annotations == nil { if kustomization.Annotations == nil {
kustomization.Annotations = map[string]string{ kustomization.Annotations = map[string]string{
kustomizev1.SyncAtAnnotation: time.Now().String(), kustomizev1.SyncAtAnnotation: time.Now().String(),
@@ -94,16 +94,16 @@ func syncKsCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &kustomization); err != nil { if err := kubeClient.Update(ctx, &kustomization); err != nil {
return err return err
} }
logSuccess("kustomization annotated") logger.Successf("kustomization annotated")
} }
logWaiting("waiting for kustomization sync") logger.Waitingf("waiting for kustomization sync")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil { isKustomizationReady(ctx, kubeClient, name, namespace)); err != nil {
return err return err
} }
logSuccess("kustomization sync completed") logger.Successf("kustomization sync completed")
err = kubeClient.Get(ctx, namespacedName, &kustomization) err = kubeClient.Get(ctx, namespacedName, &kustomization)
if err != nil { if err != nil {
@@ -111,7 +111,7 @@ func syncKsCmdRun(cmd *cobra.Command, args []string) error {
} }
if kustomization.Status.LastAppliedRevision != "" { if kustomization.Status.LastAppliedRevision != "" {
logSuccess("applied revision %s", kustomization.Status.LastAppliedRevision) logger.Successf("applied revision %s", kustomization.Status.LastAppliedRevision)
} else { } else {
return fmt.Errorf("kustomization sync failed") return fmt.Errorf("kustomization sync failed")
} }

View File

@@ -22,7 +22,8 @@ import (
var syncSourceCmd = &cobra.Command{ var syncSourceCmd = &cobra.Command{
Use: "source", Use: "source",
Short: "Synchronize source commands", Short: "Synchronize sources",
Long: "The sync source sub-commands trigger a reconciliation of sources.",
} }
func init() { func init() {

View File

@@ -28,9 +28,8 @@ import (
var syncSourceGitCmd = &cobra.Command{ var syncSourceGitCmd = &cobra.Command{
Use: "git [name]", Use: "git [name]",
Short: "Synchronize git source", Short: "Synchronize a GitRepository source",
Long: ` Long: `The sync source command triggers a reconciliation of a GitRepository resource and waits for it to finish.`,
The sync source command triggers a reconciliation of a GitRepository resource and waits for it to finish.`,
Example: ` # Trigger a git pull for an existing source Example: ` # Trigger a git pull for an existing source
sync source git podinfo sync source git podinfo
`, `,
@@ -60,7 +59,7 @@ func syncSourceGitCmdRun(cmd *cobra.Command, args []string) error {
Name: name, Name: name,
} }
logAction("annotating source %s in %s namespace", name, namespace) logger.Actionf("annotating source %s in %s namespace", name, namespace)
var gitRepository sourcev1.GitRepository var gitRepository sourcev1.GitRepository
err = kubeClient.Get(ctx, namespacedName, &gitRepository) err = kubeClient.Get(ctx, namespacedName, &gitRepository)
if err != nil { if err != nil {
@@ -77,15 +76,15 @@ func syncSourceGitCmdRun(cmd *cobra.Command, args []string) error {
if err := kubeClient.Update(ctx, &gitRepository); err != nil { if err := kubeClient.Update(ctx, &gitRepository); err != nil {
return err return err
} }
logSuccess("source annotated") logger.Successf("source annotated")
logWaiting("waiting for git sync") logger.Waitingf("waiting for git sync")
if err := wait.PollImmediate(pollInterval, timeout, if err := wait.PollImmediate(pollInterval, timeout,
isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil { isGitRepositoryReady(ctx, kubeClient, name, namespace)); err != nil {
return err return err
} }
logSuccess("git sync completed") logger.Successf("git sync completed")
err = kubeClient.Get(ctx, namespacedName, &gitRepository) err = kubeClient.Get(ctx, namespacedName, &gitRepository)
if err != nil { if err != nil {
@@ -93,7 +92,7 @@ func syncSourceGitCmdRun(cmd *cobra.Command, args []string) error {
} }
if gitRepository.Status.Artifact != nil { if gitRepository.Status.Artifact != nil {
logSuccess("fetched revision: %s", gitRepository.Status.Artifact.Revision) logger.Successf("fetched revision %s", gitRepository.Status.Artifact.Revision)
} else { } else {
return fmt.Errorf("git sync failed, artifact not found") return fmt.Errorf("git sync failed, artifact not found")
} }

View File

@@ -28,9 +28,7 @@ import (
var uninstallCmd = &cobra.Command{ var uninstallCmd = &cobra.Command{
Use: "uninstall", Use: "uninstall",
Short: "Uninstall the toolkit components", Short: "Uninstall the toolkit components",
Long: ` Long: "The uninstall command removes the namespace, cluster roles, cluster role bindings and CRDs from the cluster.",
The uninstall command removes the namespace, cluster roles,
cluster role bindings and CRDs.`,
Example: ` # Dry-run uninstall of all components Example: ` # Dry-run uninstall of all components
uninstall --dry-run --namespace=gitops-system uninstall --dry-run --namespace=gitops-system
@@ -49,7 +47,7 @@ var (
func init() { func init() {
uninstallCmd.Flags().BoolVarP(&uninstallKustomizations, "kustomizations", "", false, uninstallCmd.Flags().BoolVarP(&uninstallKustomizations, "kustomizations", "", false,
"removes all kustomizations previously installed") "removes all Kustomizations previously installed")
uninstallCmd.Flags().BoolVarP(&uninstallCRDs, "crds", "", false, uninstallCmd.Flags().BoolVarP(&uninstallCRDs, "crds", "", false,
"removes all CRDs previously installed") "removes all CRDs previously installed")
uninstallCmd.Flags().BoolVarP(&uninstallDryRun, "dry-run", "", false, uninstallCmd.Flags().BoolVarP(&uninstallDryRun, "dry-run", "", false,
@@ -78,7 +76,7 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
} }
if uninstallKustomizations { if uninstallKustomizations {
logAction("uninstalling kustomizations") logger.Actionf("uninstalling kustomizations")
command := fmt.Sprintf("kubectl -n %s delete kustomizations --all --timeout=%s %s", command := fmt.Sprintf("kubectl -n %s delete kustomizations --all --timeout=%s %s",
namespace, timeout.String(), dryRun) namespace, timeout.String(), dryRun)
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil { if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
@@ -87,7 +85,7 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
// TODO: use the kustomizations snapshots to create a list of objects // TODO: use the kustomizations snapshots to create a list of objects
// that are subject to deletion and wait for all of them to be terminated // that are subject to deletion and wait for all of them to be terminated
logWaiting("waiting on GC") logger.Waitingf("waiting on GC")
time.Sleep(30 * time.Second) time.Sleep(30 * time.Second)
} }
@@ -96,13 +94,13 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
kinds += ",crds" kinds += ",crds"
} }
logAction("uninstalling components") logger.Actionf("uninstalling components")
command := fmt.Sprintf("kubectl delete %s -l app.kubernetes.io/instance=%s --timeout=%s %s", command := fmt.Sprintf("kubectl delete %s -l app.kubernetes.io/instance=%s --timeout=%s %s",
kinds, namespace, timeout.String(), dryRun) kinds, namespace, timeout.String(), dryRun)
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil { if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
return fmt.Errorf("uninstall failed") return fmt.Errorf("uninstall failed")
} }
logSuccess("uninstall finished") logger.Successf("uninstall finished")
return nil return nil
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -21,16 +21,16 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
--branch=master \ --branch=master \
--interval=3m --interval=3m
# List git sources and their status # List GitRepository sources and their status
tk get sources git tk get sources git
# Trigger a git sync # Trigger a GitRepository source sync
tk sync source git webapp-latest tk sync source git webapp-latest
# Export git sources in YAML format # Export GitRepository sources in YAML format
tk export source git --all > sources.yaml tk export source git --all > sources.yaml
# Create a kustomization for deploying a series of microservices # Create a Kustomization for deploying a series of microservices
tk create kustomization webapp-dev \ tk create kustomization webapp-dev \
--source=webapp-latest \ --source=webapp-latest \
--path="./deploy/webapp/" \ --path="./deploy/webapp/" \
@@ -41,22 +41,22 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
--health-check="Deployment/frontend.webapp" \ --health-check="Deployment/frontend.webapp" \
--health-check-timeout=2m --health-check-timeout=2m
# Trigger a git sync and apply changes if any # Trigger a git sync of the Kustomization's source and apply changes
tk sync kustomization webapp-dev --with-source tk sync kustomization webapp-dev --with-source
# Suspend a kustomization reconciliation # Suspend a Kustomization reconciliation
tk suspend kustomization webapp-dev tk suspend kustomization webapp-dev
# Export kustomizations in YAML format # Export Kustomizations in YAML format
tk export kustomization --all > kustomizations.yaml tk export kustomization --all > kustomizations.yaml
# Resume a kustomization reconciliation # Resume a Kustomization reconciliation
tk resume kustomization webapp-dev tk resume kustomization webapp-dev
# Delete a kustomization # Delete a Kustomization
tk delete kustomization webapp-dev tk delete kustomization webapp-dev
# Delete a git source # Delete a GitRepository source
tk delete source git webapp-latest tk delete source git webapp-latest
# Uninstall the toolkit and delete CRDs # Uninstall the toolkit and delete CRDs
@@ -67,7 +67,7 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
### Options ### Options
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
-h, --help help for tk -h, --help help for tk
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
@@ -77,16 +77,16 @@ Command line utility for assembling Kubernetes CD pipelines the GitOps way.
### SEE ALSO ### SEE ALSO
* [tk bootstrap](tk_bootstrap.md) - Bootstrap commands * [tk bootstrap](tk_bootstrap.md) - Bootstrap toolkit components
* [tk check](tk_check.md) - Check requirements and installation * [tk check](tk_check.md) - Check requirements and installation
* [tk completion](tk_completion.md) - Generates bash completion scripts * [tk completion](tk_completion.md) - Generates bash completion scripts
* [tk create](tk_create.md) - Create commands * [tk create](tk_create.md) - Create or update sources and resources
* [tk delete](tk_delete.md) - Delete commands * [tk delete](tk_delete.md) - Delete sources and resources
* [tk export](tk_export.md) - Export commands * [tk export](tk_export.md) - Export resources in YAML format
* [tk get](tk_get.md) - Get commands * [tk get](tk_get.md) - Get sources and resources
* [tk install](tk_install.md) - Install the toolkit components * [tk install](tk_install.md) - Install the toolkit components
* [tk resume](tk_resume.md) - Resume commands * [tk resume](tk_resume.md) - Resume suspended resources
* [tk suspend](tk_suspend.md) - Suspend commands * [tk suspend](tk_suspend.md) - Suspend resources
* [tk sync](tk_sync.md) - Synchronize commands * [tk sync](tk_sync.md) - Synchronize sources and resources
* [tk uninstall](tk_uninstall.md) - Uninstall the toolkit components * [tk uninstall](tk_uninstall.md) - Uninstall the toolkit components

View File

@@ -1,10 +1,10 @@
## tk bootstrap ## tk bootstrap
Bootstrap commands Bootstrap toolkit components
### Synopsis ### Synopsis
Bootstrap commands The bootstrap sub-commands bootstrap the toolkit components on the targeted Git provider.
### Options ### Options
@@ -16,7 +16,7 @@ Bootstrap commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -26,6 +26,6 @@ Bootstrap commands
### SEE ALSO ### SEE ALSO
* [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines
* [tk bootstrap github](tk_bootstrap_github.md) - Bootstrap GitHub repository * [tk bootstrap github](tk_bootstrap_github.md) - Bootstrap toolkit components in a GitHub repository
* [tk bootstrap gitlab](tk_bootstrap_gitlab.md) - Bootstrap GitLab repository * [tk bootstrap gitlab](tk_bootstrap_gitlab.md) - Bootstrap toolkit components in a GitLab repository

View File

@@ -1,13 +1,12 @@
## tk bootstrap github ## tk bootstrap github
Bootstrap GitHub repository Bootstrap toolkit components in a GitHub repository
### Synopsis ### Synopsis
The bootstrap github command creates the GitHub repository if it doesn't exists and
The bootstrap command creates the GitHub repository if it doesn't exists and
commits the toolkit components manifests to the master branch. commits the toolkit components manifests to the master branch.
Then it configure the target cluster to synchronize with the repository. Then it configures the target cluster to synchronize with the repository.
If the toolkit components are present on the cluster, If the toolkit components are present on the cluster,
the bootstrap command will perform an upgrade if needed. the bootstrap command will perform an upgrade if needed.
@@ -55,7 +54,7 @@ tk bootstrap github [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -65,5 +64,5 @@ tk bootstrap github [flags]
### SEE ALSO ### SEE ALSO
* [tk bootstrap](tk_bootstrap.md) - Bootstrap commands * [tk bootstrap](tk_bootstrap.md) - Bootstrap toolkit components

View File

@@ -1,13 +1,12 @@
## tk bootstrap gitlab ## tk bootstrap gitlab
Bootstrap GitLab repository Bootstrap toolkit components in a GitLab repository
### Synopsis ### Synopsis
The bootstrap gitlab command creates the GitLab repository if it doesn't exists and
The bootstrap command creates the GitLab repository if it doesn't exists and
commits the toolkit components manifests to the master branch. commits the toolkit components manifests to the master branch.
Then it configure the target cluster to synchronize with the repository. Then it configures the target cluster to synchronize with the repository.
If the toolkit components are present on the cluster, If the toolkit components are present on the cluster,
the bootstrap command will perform an upgrade if needed. the bootstrap command will perform an upgrade if needed.
@@ -51,7 +50,7 @@ tk bootstrap gitlab [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -61,5 +60,5 @@ tk bootstrap gitlab [flags]
### SEE ALSO ### SEE ALSO
* [tk bootstrap](tk_bootstrap.md) - Bootstrap commands * [tk bootstrap](tk_bootstrap.md) - Bootstrap toolkit components

View File

@@ -4,7 +4,6 @@ Check requirements and installation
### Synopsis ### Synopsis
The check command will perform a series of checks to validate that The check command will perform a series of checks to validate that
the local environment is configured correctly and if the installed components are healthy. the local environment is configured correctly and if the installed components are healthy.
@@ -33,7 +32,7 @@ tk check [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)

View File

@@ -33,7 +33,7 @@ To configure your bash shell to load completions for each session add to your ba
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)

View File

@@ -1,15 +1,15 @@
## tk create ## tk create
Create commands Create or update sources and resources
### Synopsis ### Synopsis
Create commands The create sub-commands generate sources and resources.
### Options ### Options
``` ```
--export export in yaml format to stdout --export export in YAML format to stdout
-h, --help help for create -h, --help help for create
--interval duration source sync interval (default 1m0s) --interval duration source sync interval (default 1m0s)
``` ```
@@ -17,7 +17,7 @@ Create commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -27,6 +27,6 @@ Create commands
### SEE ALSO ### SEE ALSO
* [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines
* [tk create kustomization](tk_create_kustomization.md) - Create or update a kustomization resource * [tk create kustomization](tk_create_kustomization.md) - Create or update a Kustomization resource
* [tk create source](tk_create_source.md) - Create source commands * [tk create source](tk_create_source.md) - Create or update sources

View File

@@ -1,12 +1,10 @@
## tk create kustomization ## tk create kustomization
Create or update a kustomization resource Create or update a Kustomization resource
### Synopsis ### Synopsis
The kustomization source create command generates a Kustomize resource for a given GitRepository source.
The kustomization source command generates a kustomization.kustomize.fluxcd.io resource for a given GitRepository source.
API spec: https://github.com/fluxcd/kustomize-controller/tree/master/docs/spec/v1alpha1
``` ```
tk create kustomization [name] [flags] tk create kustomization [name] [flags]
@@ -15,7 +13,7 @@ tk create kustomization [name] [flags]
### Examples ### Examples
``` ```
# Create a kustomization from a source at a given path # Create a Kustomization resource from a source at a given path
create kustomization contour \ create kustomization contour \
--source=contour \ --source=contour \
--path="./examples/contour/" \ --path="./examples/contour/" \
@@ -26,7 +24,7 @@ tk create kustomization [name] [flags]
--health-check="DaemonSet/envoy.projectcontour" \ --health-check="DaemonSet/envoy.projectcontour" \
--health-check-timeout=3m --health-check-timeout=3m
# Create a kustomization that depends on the previous one # Create a Kustomization resource that depends on the previous one
create kustomization webapp \ create kustomization webapp \
--depends-on=contour \ --depends-on=contour \
--source=webapp \ --source=webapp \
@@ -35,7 +33,7 @@ tk create kustomization [name] [flags]
--interval=5m \ --interval=5m \
--validate=client --validate=client
# Create a kustomization that runs under a service account # Create a Kustomization resource that runs under a service account
create kustomization webapp \ create kustomization webapp \
--source=webapp \ --source=webapp \
--path="./deploy/overlays/staging" \ --path="./deploy/overlays/staging" \
@@ -50,11 +48,11 @@ tk create kustomization [name] [flags]
### Options ### Options
``` ```
--depends-on stringArray kustomization that must be ready before this kustomization can be applied --depends-on stringArray Kustomization that must be ready before this Kustomization can be applied
--health-check stringArray workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>' --health-check stringArray workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'
--health-check-timeout duration timeout of health checking operations (default 2m0s) --health-check-timeout duration timeout of health checking operations (default 2m0s)
-h, --help help for kustomization -h, --help help for kustomization
--path string path to the directory containing the kustomization file (default "./") --path string path to the directory containing the Kustomization file (default "./")
--prune enable garbage collection --prune enable garbage collection
--sa-name string service account name --sa-name string service account name
--sa-namespace string service account namespace --sa-namespace string service account namespace
@@ -65,8 +63,8 @@ tk create kustomization [name] [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--export export in yaml format to stdout --export export in YAML format to stdout
--interval duration source sync interval (default 1m0s) --interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
@@ -76,5 +74,5 @@ tk create kustomization [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk create](tk_create.md) - Create commands * [tk create](tk_create.md) - Create or update sources and resources

View File

@@ -1,10 +1,10 @@
## tk create source ## tk create source
Create source commands Create or update sources
### Synopsis ### Synopsis
Create source commands The create source sub-commands generate sources.
### Options ### Options
@@ -15,8 +15,8 @@ Create source commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--export export in yaml format to stdout --export export in YAML format to stdout
--interval duration source sync interval (default 1m0s) --interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
@@ -26,6 +26,6 @@ Create source commands
### SEE ALSO ### SEE ALSO
* [tk create](tk_create.md) - Create commands * [tk create](tk_create.md) - Create or update sources and resources
* [tk create source git](tk_create_source_git.md) - Create or update a git source * [tk create source git](tk_create_source_git.md) - Create or update a GitRepository source

View File

@@ -1,11 +1,11 @@
## tk create source git ## tk create source git
Create or update a git source Create or update a GitRepository source
### Synopsis ### Synopsis
The create source command generates a GitRepository resource and waits for it to sync. The create source git command generates a GitRepository resource and waits for it to sync.
For Git over SSH, host and SSH keys are automatically generated and stored in a Kubernetes secret. For Git over SSH, host and SSH keys are automatically generated and stored in a Kubernetes secret.
For private Git repositories, the basic authentication credentials are stored in a Kubernetes secret. For private Git repositories, the basic authentication credentials are stored in a Kubernetes secret.
@@ -70,8 +70,8 @@ tk create source git [name] [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--export export in yaml format to stdout --export export in YAML format to stdout
--interval duration source sync interval (default 1m0s) --interval duration source sync interval (default 1m0s)
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
@@ -81,5 +81,5 @@ tk create source git [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk create source](tk_create_source.md) - Create source commands * [tk create source](tk_create_source.md) - Create or update sources

View File

@@ -1,10 +1,10 @@
## tk delete ## tk delete
Delete commands Delete sources and resources
### Synopsis ### Synopsis
Delete commands The delete sub-commands delete sources and resources.
### Options ### Options
@@ -16,7 +16,7 @@ Delete commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -26,6 +26,6 @@ Delete commands
### SEE ALSO ### SEE ALSO
* [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines
* [tk delete kustomization](tk_delete_kustomization.md) - Delete kustomization * [tk delete kustomization](tk_delete_kustomization.md) - Delete a Kustomization resource
* [tk delete source](tk_delete_source.md) - Delete sources commands * [tk delete source](tk_delete_source.md) - Delete sources

View File

@@ -1,10 +1,10 @@
## tk delete kustomization ## tk delete kustomization
Delete kustomization Delete a Kustomization resource
### Synopsis ### Synopsis
Delete kustomization The delete kustomization command deletes the given Kustomization from the cluster.
``` ```
tk delete kustomization [name] [flags] tk delete kustomization [name] [flags]
@@ -19,7 +19,7 @@ tk delete kustomization [name] [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
-s, --silent delete resource without asking for confirmation -s, --silent delete resource without asking for confirmation
@@ -29,5 +29,5 @@ tk delete kustomization [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk delete](tk_delete.md) - Delete commands * [tk delete](tk_delete.md) - Delete sources and resources

View File

@@ -1,10 +1,10 @@
## tk delete source ## tk delete source
Delete sources commands Delete sources
### Synopsis ### Synopsis
Delete sources commands The delete source sub-commands delete sources.
### Options ### Options
@@ -15,7 +15,7 @@ Delete sources commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
-s, --silent delete resource without asking for confirmation -s, --silent delete resource without asking for confirmation
@@ -25,6 +25,6 @@ Delete sources commands
### SEE ALSO ### SEE ALSO
* [tk delete](tk_delete.md) - Delete commands * [tk delete](tk_delete.md) - Delete sources and resources
* [tk delete source git](tk_delete_source_git.md) - Delete git source * [tk delete source git](tk_delete_source_git.md) - Delete a GitRepository source

View File

@@ -1,10 +1,10 @@
## tk delete source git ## tk delete source git
Delete git source Delete a GitRepository source
### Synopsis ### Synopsis
Delete git source The delete source git command deletes the given GitRepository from the cluster.
``` ```
tk delete source git [name] [flags] tk delete source git [name] [flags]
@@ -19,7 +19,7 @@ tk delete source git [name] [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
-s, --silent delete resource without asking for confirmation -s, --silent delete resource without asking for confirmation
@@ -29,5 +29,5 @@ tk delete source git [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk delete source](tk_delete_source.md) - Delete sources commands * [tk delete source](tk_delete_source.md) - Delete sources

View File

@@ -1,10 +1,10 @@
## tk export ## tk export
Export commands Export resources in YAML format
### Synopsis ### Synopsis
Export commands The export sub-commands export resources in YAML format.
### Options ### Options
@@ -16,7 +16,7 @@ Export commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -26,6 +26,6 @@ Export commands
### SEE ALSO ### SEE ALSO
* [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines
* [tk export kustomization](tk_export_kustomization.md) - Export kustomization in YAML format * [tk export kustomization](tk_export_kustomization.md) - Export Kustomization resources in YAML format
* [tk export source](tk_export_source.md) - Export source commands * [tk export source](tk_export_source.md) - Export sources

View File

@@ -1,10 +1,10 @@
## tk export kustomization ## tk export kustomization
Export kustomization in YAML format Export Kustomization resources in YAML format
### Synopsis ### Synopsis
Export kustomization in YAML format The export kustomization command exports one or all Kustomization resources in YAML format.
``` ```
tk export kustomization [name] [flags] tk export kustomization [name] [flags]
@@ -13,10 +13,10 @@ tk export kustomization [name] [flags]
### Examples ### Examples
``` ```
# Export all kustomizations # Export all Kustomization resources
export kustomization --all > kustomizations.yaml export kustomization --all > kustomizations.yaml
# Export a kustomization # Export a Kustomization
export kustomization my-app > kustomization.yaml export kustomization my-app > kustomization.yaml
``` ```
@@ -31,7 +31,7 @@ tk export kustomization [name] [flags]
``` ```
--all select all resources --all select all resources
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -40,5 +40,5 @@ tk export kustomization [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk export](tk_export.md) - Export commands * [tk export](tk_export.md) - Export resources in YAML format

View File

@@ -1,10 +1,10 @@
## tk export source ## tk export source
Export source commands Export sources
### Synopsis ### Synopsis
Export source commands The export source sub-commands export sources in YAML format.
### Options ### Options
@@ -17,7 +17,7 @@ Export source commands
``` ```
--all select all resources --all select all resources
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -26,6 +26,6 @@ Export source commands
### SEE ALSO ### SEE ALSO
* [tk export](tk_export.md) - Export commands * [tk export](tk_export.md) - Export resources in YAML format
* [tk export source git](tk_export_source_git.md) - Export git sources in YAML format * [tk export source git](tk_export_source_git.md) - Export GitRepository sources in YAML format

View File

@@ -1,10 +1,10 @@
## tk export source git ## tk export source git
Export git sources in YAML format Export GitRepository sources in YAML format
### Synopsis ### Synopsis
Export git sources in YAML format The export source git command exports on or all GitRepository sources in YAML format.
``` ```
tk export source git [name] [flags] tk export source git [name] [flags]
@@ -13,10 +13,10 @@ tk export source git [name] [flags]
### Examples ### Examples
``` ```
# Export all git sources # Export all GitRepository sources
export source git --all > sources.yaml export source git --all > sources.yaml
# Export a git source including the SSH keys or basic auth credentials # Export a GitRepository source including the SSH key pair or basic auth credentials
export source git my-private-repo --with-credentials > source.yaml export source git my-private-repo --with-credentials > source.yaml
``` ```
@@ -31,7 +31,7 @@ tk export source git [name] [flags]
``` ```
--all select all resources --all select all resources
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -41,5 +41,5 @@ tk export source git [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk export source](tk_export_source.md) - Export source commands * [tk export source](tk_export_source.md) - Export sources

View File

@@ -1,10 +1,10 @@
## tk get ## tk get
Get commands Get sources and resources
### Synopsis ### Synopsis
Get commands The get sub-commands print the statuses of sources and resources.
### Options ### Options
@@ -15,7 +15,7 @@ Get commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -25,6 +25,6 @@ Get commands
### SEE ALSO ### SEE ALSO
* [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines
* [tk get kustomizations](tk_get_kustomizations.md) - Get kustomizations status * [tk get kustomizations](tk_get_kustomizations.md) - Get Kustomization source statuses
* [tk get sources](tk_get_sources.md) - Get sources commands * [tk get sources](tk_get_sources.md) - Get source statuses

View File

@@ -1,11 +1,10 @@
## tk get kustomizations ## tk get kustomizations
Get kustomizations status Get Kustomization source statuses
### Synopsis ### Synopsis
The get kustomizations command prints the statuses of the resources.
The get kustomizations command prints the status of the resources.
``` ```
tk get kustomizations [flags] tk get kustomizations [flags]
@@ -20,7 +19,7 @@ tk get kustomizations [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -29,5 +28,5 @@ tk get kustomizations [flags]
### SEE ALSO ### SEE ALSO
* [tk get](tk_get.md) - Get commands * [tk get](tk_get.md) - Get sources and resources

View File

@@ -1,10 +1,10 @@
## tk get sources ## tk get sources
Get sources commands Get source statuses
### Synopsis ### Synopsis
Get sources commands The get source sub-commands print the statuses of the sources.
### Options ### Options
@@ -15,7 +15,7 @@ Get sources commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -24,6 +24,6 @@ Get sources commands
### SEE ALSO ### SEE ALSO
* [tk get](tk_get.md) - Get commands * [tk get](tk_get.md) - Get sources and resources
* [tk get sources git](tk_get_sources_git.md) - Get git sources status * [tk get sources git](tk_get_sources_git.md) - Get GitRepository source statuses

View File

@@ -1,11 +1,10 @@
## tk get sources git ## tk get sources git
Get git sources status Get GitRepository source statuses
### Synopsis ### Synopsis
The get sources git command prints the status of the GitRepository sources.
The get sources command prints the status of the git resources.
``` ```
tk get sources git [flags] tk get sources git [flags]
@@ -20,7 +19,7 @@ tk get sources git [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -29,5 +28,5 @@ tk get sources git [flags]
### SEE ALSO ### SEE ALSO
* [tk get sources](tk_get_sources.md) - Get sources commands * [tk get sources](tk_get_sources.md) - Get source statuses

View File

@@ -4,7 +4,6 @@ Install the toolkit components
### Synopsis ### Synopsis
The install command deploys the toolkit components in the specified namespace. The install command deploys the toolkit components in the specified namespace.
If a previous version is installed, then an in-place upgrade will be performed. If a previous version is installed, then an in-place upgrade will be performed.
@@ -38,7 +37,7 @@ tk install [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)

View File

@@ -1,10 +1,10 @@
## tk resume ## tk resume
Resume commands Resume suspended resources
### Synopsis ### Synopsis
Resume commands The resume sub-commands resume a suspended resource.
### Options ### Options
@@ -15,7 +15,7 @@ Resume commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -25,5 +25,5 @@ Resume commands
### SEE ALSO ### SEE ALSO
* [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines
* [tk resume kustomization](tk_resume_kustomization.md) - Resume kustomization * [tk resume kustomization](tk_resume_kustomization.md) - Resume a suspended Kustomization

View File

@@ -1,10 +1,11 @@
## tk resume kustomization ## tk resume kustomization
Resume kustomization Resume a suspended Kustomization
### Synopsis ### Synopsis
The resume command marks a previously suspended Kustomization resource for reconciliation and waits for it to finish the apply. The resume command marks a previously suspended Kustomization resource for reconciliation and waits for it to
finish the apply.
``` ```
tk resume kustomization [name] [flags] tk resume kustomization [name] [flags]
@@ -19,7 +20,7 @@ tk resume kustomization [name] [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -28,5 +29,5 @@ tk resume kustomization [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk resume](tk_resume.md) - Resume commands * [tk resume](tk_resume.md) - Resume suspended resources

View File

@@ -1,10 +1,10 @@
## tk suspend ## tk suspend
Suspend commands Suspend resources
### Synopsis ### Synopsis
Suspend commands The suspend sub-commands suspend the reconciliation of a resource.
### Options ### Options
@@ -15,7 +15,7 @@ Suspend commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -25,5 +25,5 @@ Suspend commands
### SEE ALSO ### SEE ALSO
* [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines
* [tk suspend kustomization](tk_suspend_kustomization.md) - Suspend kustomization * [tk suspend kustomization](tk_suspend_kustomization.md) - Suspend reconciliation of Kustomization

View File

@@ -1,6 +1,6 @@
## tk suspend kustomization ## tk suspend kustomization
Suspend kustomization Suspend reconciliation of Kustomization
### Synopsis ### Synopsis
@@ -19,7 +19,7 @@ tk suspend kustomization [name] [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -28,5 +28,5 @@ tk suspend kustomization [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk suspend](tk_suspend.md) - Suspend commands * [tk suspend](tk_suspend.md) - Suspend resources

View File

@@ -1,10 +1,10 @@
## tk sync ## tk sync
Synchronize commands Synchronize sources and resources
### Synopsis ### Synopsis
Synchronize commands The sync sub-commands trigger a reconciliation of sources and resources.
### Options ### Options
@@ -15,7 +15,7 @@ Synchronize commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -25,6 +25,6 @@ Synchronize commands
### SEE ALSO ### SEE ALSO
* [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines * [tk](tk.md) - Command line utility for assembling Kubernetes CD pipelines
* [tk sync kustomization](tk_sync_kustomization.md) - Synchronize kustomization * [tk sync kustomization](tk_sync_kustomization.md) - Synchronize a Kustomization resource
* [tk sync source](tk_sync_source.md) - Synchronize source commands * [tk sync source](tk_sync_source.md) - Synchronize sources

View File

@@ -1,6 +1,6 @@
## tk sync kustomization ## tk sync kustomization
Synchronize kustomization Synchronize a Kustomization resource
### Synopsis ### Synopsis
@@ -14,10 +14,10 @@ tk sync kustomization [name] [flags]
### Examples ### Examples
``` ```
# Trigger a kustomization apply outside of the reconciliation interval # Trigger a Kustomization apply outside of the reconciliation interval
sync kustomization podinfo sync kustomization podinfo
# Trigger a git sync of the kustomization source and apply changes # Trigger a sync of the Kustomization's source and apply changes
sync kustomization podinfo --with-source sync kustomization podinfo --with-source
``` ```
@@ -32,7 +32,7 @@ tk sync kustomization [name] [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -41,5 +41,5 @@ tk sync kustomization [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk sync](tk_sync.md) - Synchronize commands * [tk sync](tk_sync.md) - Synchronize sources and resources

View File

@@ -1,10 +1,10 @@
## tk sync source ## tk sync source
Synchronize source commands Synchronize sources
### Synopsis ### Synopsis
Synchronize source commands The sync source sub-commands trigger a reconciliation of sources.
### Options ### Options
@@ -15,7 +15,7 @@ Synchronize source commands
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -24,6 +24,6 @@ Synchronize source commands
### SEE ALSO ### SEE ALSO
* [tk sync](tk_sync.md) - Synchronize commands * [tk sync](tk_sync.md) - Synchronize sources and resources
* [tk sync source git](tk_sync_source_git.md) - Synchronize git source * [tk sync source git](tk_sync_source_git.md) - Synchronize a GitRepository source

View File

@@ -1,10 +1,9 @@
## tk sync source git ## tk sync source git
Synchronize git source Synchronize a GitRepository source
### Synopsis ### Synopsis
The sync source command triggers a reconciliation of a GitRepository resource and waits for it to finish. The sync source command triggers a reconciliation of a GitRepository resource and waits for it to finish.
``` ```
@@ -28,7 +27,7 @@ tk sync source git [name] [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)
@@ -37,5 +36,5 @@ tk sync source git [name] [flags]
### SEE ALSO ### SEE ALSO
* [tk sync source](tk_sync_source.md) - Synchronize source commands * [tk sync source](tk_sync_source.md) - Synchronize sources

View File

@@ -4,9 +4,7 @@ Uninstall the toolkit components
### Synopsis ### Synopsis
The uninstall command removes the namespace, cluster roles, cluster role bindings and CRDs from the cluster.
The uninstall command removes the namespace, cluster roles,
cluster role bindings and CRDs.
``` ```
tk uninstall [flags] tk uninstall [flags]
@@ -29,14 +27,14 @@ tk uninstall [flags]
--crds removes all CRDs previously installed --crds removes all CRDs previously installed
--dry-run only print the object that would be deleted --dry-run only print the object that would be deleted
-h, --help help for uninstall -h, --help help for uninstall
--kustomizations removes all kustomizations previously installed --kustomizations removes all Kustomizations previously installed
-s, --silent delete components without asking for confirmation -s, --silent delete components without asking for confirmation
``` ```
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller]) --components strings list of components, accepts comma-separated values (default [source-controller,kustomize-controller,notification-controller])
--kubeconfig string path to the kubeconfig file (default "~/.kube/config") --kubeconfig string path to the kubeconfig file (default "~/.kube/config")
--namespace string the namespace scope for this operation (default "gitops-system") --namespace string the namespace scope for this operation (default "gitops-system")
--timeout duration timeout for this operation (default 5m0s) --timeout duration timeout for this operation (default 5m0s)

View File

@@ -15,7 +15,7 @@ Features:
- Health assessment of the deployed workloads - Health assessment of the deployed workloads
- Runs pipelines in a specific order (depends-on relationship) - Runs pipelines in a specific order (depends-on relationship)
- Prunes objects removed from source (garbage collection) - Prunes objects removed from source (garbage collection)
- Reports cluster state changes (Slack/Discord) - Reports cluster state changes (alerting provided by notification-controller)
Links: Links:

View File

@@ -0,0 +1,16 @@
# Notification Controller
The Notification Controller is a Kubernetes operator,
specialized in dispatching events to external systems such as
Slack, Microsoft Teams, Discord and Rocket chat.
The controller receives events via HTTP and dispatch them to external
webhooks based on event severity and involved objects.
The controller can be configured with Kubernetes custom resources that
define how events are processed and where to dispatch them.
Links:
- Source code [fluxcd/notification-controller](https://github.com/fluxcd/notification-controller)
- Specification [docs](https://github.com/fluxcd/notification-controller/tree/master/docs/spec)

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

@@ -21,7 +21,7 @@ export GITHUB_USER=<your-username>
To install the latest `tk` release run: To install the latest `tk` release run:
```bash ```sh
curl -s https://toolkit.fluxcd.io/install.sh | sudo bash curl -s https://toolkit.fluxcd.io/install.sh | sudo bash
``` ```
@@ -58,7 +58,7 @@ kubectl cluster-info --context kind-staging
Verify that your staging cluster satisfies the prerequisites with: Verify that your staging cluster satisfies the prerequisites with:
```text ```console
$ tk check --pre $ tk check --pre
► checking prerequisites ► checking prerequisites
@@ -104,22 +104,9 @@ $ tk bootstrap github --owner=gitopsrun --repository=fleet-infra --path=staging-
✚ generating manifests ✚ generating manifests
✔ components manifests pushed ✔ components manifests pushed
► installing components in gitops-system namespace ► installing components in gitops-system namespace
namespace/gitops-system created
customresourcedefinition.apiextensions.k8s.io/gitrepositories.source.fluxcd.io created
customresourcedefinition.apiextensions.k8s.io/helmcharts.source.fluxcd.io created
customresourcedefinition.apiextensions.k8s.io/helmrepositories.source.fluxcd.io created
customresourcedefinition.apiextensions.k8s.io/kustomizations.kustomize.fluxcd.io created
customresourcedefinition.apiextensions.k8s.io/profiles.kustomize.fluxcd.io created
role.rbac.authorization.k8s.io/crd-controller-gitops-system created
rolebinding.rbac.authorization.k8s.io/crd-controller-gitops-system created
clusterrolebinding.rbac.authorization.k8s.io/cluster-reconciler-gitops-system created
service/source-controller created
deployment.apps/kustomize-controller created
deployment.apps/source-controller created
networkpolicy.networking.k8s.io/deny-ingress created
Waiting for deployment "source-controller" rollout to finish: 0 of 1 updated replicas are available...
deployment "source-controller" successfully rolled out deployment "source-controller" successfully rolled out
deployment "kustomize-controller" successfully rolled out deployment "kustomize-controller" successfully rolled out
deployment "notification-controller" successfully rolled out
✔ install completed ✔ install completed
► configuring deploy key ► configuring deploy key
✔ deploy key configured ✔ deploy key configured
@@ -208,7 +195,7 @@ git add -A && git commit -m "add staging webapp" && git push
In about 30s the synchronization should start: In about 30s the synchronization should start:
```text ```console
$ watch tk get kustomizations $ watch tk get kustomizations
✔ gitops-system last applied revision master/35d5765a1acb9e9ce66cad7274c6fe03eee1e8eb ✔ gitops-system last applied revision master/35d5765a1acb9e9ce66cad7274c6fe03eee1e8eb
@@ -219,7 +206,7 @@ $ watch tk get kustomizations
When the synchronization finishes you can check that the webapp services are running: When the synchronization finishes you can check that the webapp services are running:
```text ```console
$ kubectl -n webapp get deployments,services $ kubectl -n webapp get deployments,services
NAME READY UP-TO-DATE AVAILABLE AGE NAME READY UP-TO-DATE AVAILABLE AGE
@@ -307,7 +294,7 @@ git add -A && git commit -m "add prod webapp" && git push
List git sources: List git sources:
```text ```console
$ tk get sources git $ tk get sources git
✔ gitops-system last fetched revision master/99072ee132abdead8b7799d7891eae2f524eb73d ✔ gitops-system last fetched revision master/99072ee132abdead8b7799d7891eae2f524eb73d
@@ -318,7 +305,7 @@ The kubectl equivalent is `kubectl -n gitops-system get gitrepositories`.
List kustomization: List kustomization:
```text ```console
$ tk get kustomizations $ tk get kustomizations
✔ gitops-system last applied revision master/99072ee132abdead8b7799d7891eae2f524eb73d ✔ gitops-system last applied revision master/99072ee132abdead8b7799d7891eae2f524eb73d
@@ -341,7 +328,7 @@ git add -A && git commit -m "update prod webapp" && git push
Trigger a git sync: Trigger a git sync:
```text ```console
$ tk sync ks gitops-system --with-source $ tk sync ks gitops-system --with-source
► annotating source gitops-system ► annotating source gitops-system
@@ -358,7 +345,7 @@ The kubectl equivalent is `kubectl -n gitops-system annotate gitrepository/gitop
Wait for the webapp to be upgraded: Wait for the webapp to be upgraded:
```text ```console
$ watch tk get kustomizations $ watch tk get kustomizations
✔ gitops-system last applied revision master/d751ea264d48bf0db8b588d1d08184834ac8fec9 ✔ gitops-system last applied revision master/d751ea264d48bf0db8b588d1d08184834ac8fec9

View File

@@ -0,0 +1,101 @@
# Setup Notifications
When operating a cluster, different teams may wish to receive notifications about
the status of their GitOps pipelines.
For example, the on-call team would receive alerts about reconciliation
failures in the cluster, while the dev team may wish to be alerted when a new version
of an app was deployed and if the deployment is healthy.
## Prerequisites
* [Get started guide](../get-started/index.md)
The GitOps toolkit controllers emit Kubernetes events whenever a resource status changes.
You can use the [notification-controller](../components/notification/controller.md)
to forward these events to Slack, Microsoft Teams, Discord or Rocket chart.
The notification controller is part of the default toolkit installation.
## Define a provider
First create a secret with your Slack incoming webhook:
```sh
kubectl -n gitops-system create secret generic slack-url \
--from-literal=address=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK
```
Note that the secret must contain an `address` field,
it can be a Slack, Microsoft Teams, Discord or Rocket webhook URL.
Create a notification provider for Slack by referencing the above secret:
```yaml
apiVersion: notification.fluxcd.io/v1alpha1
kind: Provider
metadata:
name: slack
namespace: gitops-system
spec:
type: slack
channel: general
secretRef:
name: slack-url
```
The provider type can be `slack`, `msteams`, `discord`, `rocket` or `webhook`.
When type `webhook` is specified, the notification controller will post the incoming
[event](../components/notification/event.md) in JSON format to the webhook address.
This way you can create custom handlers that can store the events in
Elasticsearch, CloudWatch, Stackdriver, etc.
## Define an alert
Create an alert definition for the webapp kustomizations:
```yaml
apiVersion: notification.fluxcd.io/v1alpha1
kind: Alert
metadata:
name: on-call-webapp
namespace: gitops-system
spec:
providerRef:
name: slack
eventSeverity: info
eventSources:
- kind: Kustomization
name: webapp-backend
- kind: Kustomization
name: webapp-frontend
```
Apply the above files or commit them to the `fleet-infra` repository.
To verify that the alert has been acknowledge by the notification controller do:
```console
$ kubectl -n gitops-system get alerts
NAME READY STATUS AGE
on-call-webapp True Initialized 1m
```
Multiple alerts can be used to send notifications to different channels or Slack organizations.
The event severity can be set to `info` or `error`.
When the severity is set to `error`, the kustomize controller will alert on any error
encountered during the reconciliation process.
This includes kustomize build and validation errors,
apply errors and health check failures.
![error alert](../diagrams/slack-error-alert.png)
When the verbosity is set to `info`, the controller will alert if:
* a Kubernetes object was created, updated or deleted
* heath checks are passing
* a dependency is delaying the execution
* an error occurs
![info alert](../diagrams/slack-info-alert.png)

View File

@@ -41,6 +41,9 @@ Components:
- [HelmRepository CRD](components/source/helmrepositories.md) - [HelmRepository CRD](components/source/helmrepositories.md)
- [Kustomize Controller](components/kustomize/controller.md) - [Kustomize Controller](components/kustomize/controller.md)
- [Kustomization CRD](components/kustomize/kustomization.md) - [Kustomization CRD](components/kustomize/kustomization.md)
- [Notification Controller](components/notification/controller.md)
- [Provider CRD](components/notification/provider.md)
- [Alert CRD](components/notification/alert.md)
- Helm Controller (TBA) - Helm Controller (TBA)
To get started with the toolkit please follow this [guide](get-started/index.md). To get started with the toolkit please follow this [guide](get-started/index.md).

View File

@@ -28,12 +28,12 @@ Non-Goals
Tasks Tasks
- Review the git source and kustomize APIs - Review the git source and kustomize APIs
- Design the events API - ~~Design the events API~~
- Implement events in source and kustomize controllers - Implement events in source and kustomize controllers
- Implement Prometheus metrics in source and kustomize controllers - Implement Prometheus metrics in source and kustomize controllers
- Make the kustomize-controller apply/gc events on-par with Flux v1 apply events - Make the kustomize-controller apply/gc events on-par with Flux v1 apply events
- Design the notifications and events filtering API - ~~Design the notifications and events filtering API~~
- Implement a notification controller for Slack, MS Teams, Discord, Rocket - ~~Implement a notification controller for Slack, MS Teams, Discord, Rocket~~
- Implement the migration command in tk - Implement the migration command in tk
- Create a migration guide for `flux.yaml` kustomize users - Create a migration guide for `flux.yaml` kustomize users

5
go.mod
View File

@@ -5,15 +5,12 @@ go 1.14
require ( require (
github.com/blang/semver v3.5.1+incompatible github.com/blang/semver v3.5.1+incompatible
github.com/fluxcd/kustomize-controller v0.0.1 github.com/fluxcd/kustomize-controller v0.0.1
github.com/fluxcd/pkg v0.0.1
github.com/fluxcd/source-controller v0.0.1 github.com/fluxcd/source-controller v0.0.1
github.com/go-git/go-git/v5 v5.0.0
github.com/golang/protobuf v1.4.2 // indirect github.com/golang/protobuf v1.4.2 // indirect
github.com/google/go-github/v32 v32.0.0
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect github.com/hashicorp/go-retryablehttp v0.6.6 // indirect
github.com/manifoldco/promptui v0.7.0 github.com/manifoldco/promptui v0.7.0
github.com/spf13/cobra v1.0.0 github.com/spf13/cobra v1.0.0
github.com/xanzy/go-gitlab v0.32.1
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect

8
go.sum
View File

@@ -170,6 +170,8 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fluxcd/kustomize-controller v0.0.1 h1:F2wg9c5nMUEnPHgs44HMY1/2UAXXaYcmpj7WeOzf9p0= github.com/fluxcd/kustomize-controller v0.0.1 h1:F2wg9c5nMUEnPHgs44HMY1/2UAXXaYcmpj7WeOzf9p0=
github.com/fluxcd/kustomize-controller v0.0.1/go.mod h1:sSIy+Y924OGHW2anzZvD53BbgjSOO4mONTTG2+UTEhM= github.com/fluxcd/kustomize-controller v0.0.1/go.mod h1:sSIy+Y924OGHW2anzZvD53BbgjSOO4mONTTG2+UTEhM=
github.com/fluxcd/pkg v0.0.1 h1:yECp5SBjX7vUBOjd3KYBoVQwt22A0u1SZJjYV4PduAk=
github.com/fluxcd/pkg v0.0.1/go.mod h1:3DgEcVmkVYrA/BDb/fyDIJllxK++c/ovLCMPRlkAp9Y=
github.com/fluxcd/source-controller v0.0.1 h1:17/b/Zcb3OUkUoo03W+L7TGwkCKG23K9HrgL+d5WMXE= github.com/fluxcd/source-controller v0.0.1 h1:17/b/Zcb3OUkUoo03W+L7TGwkCKG23K9HrgL+d5WMXE=
github.com/fluxcd/source-controller v0.0.1/go.mod h1:tmscNdCxEt7+Xt2g1+bI38hMPw2leYMFAaCn4UlMGuw= github.com/fluxcd/source-controller v0.0.1/go.mod h1:tmscNdCxEt7+Xt2g1+bI38hMPw2leYMFAaCn4UlMGuw=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
@@ -195,6 +197,8 @@ github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg= github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA= github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
@@ -394,6 +398,8 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -688,6 +694,8 @@ golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=

View File

@@ -1,5 +1,12 @@
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
- github.com/fluxcd/kustomize-controller/config//crd?ref=v0.0.1 - github.com/fluxcd/kustomize-controller/config//crd?ref=v0.0.2
- github.com/fluxcd/kustomize-controller/config//manager?ref=v0.0.1 - github.com/fluxcd/kustomize-controller/config//manager?ref=v0.0.2
patchesJson6902:
- target:
group: apps
version: v1
kind: Deployment
name: kustomize-controller
path: patch.yaml

View File

@@ -0,0 +1,3 @@
- op: add
path: /spec/template/spec/containers/0/args/0
value: --events-addr=http://notification-controller/

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- github.com/fluxcd/notification-controller/config//crd?ref=v0.0.1-alpha.2
- github.com/fluxcd/notification-controller/config//manager?ref=v0.0.1-alpha.2

View File

@@ -5,6 +5,7 @@ resources:
- namespace.yaml - namespace.yaml
- ../bases/source-controller - ../bases/source-controller
- ../bases/kustomize-controller - ../bases/kustomize-controller
- ../bases/notification-controller
- ../rbac - ../rbac
- ../policies - ../policies
transformers: transformers:

View File

@@ -9,6 +9,9 @@ rules:
- apiGroups: ['kustomize.fluxcd.io'] - apiGroups: ['kustomize.fluxcd.io']
resources: ['*'] resources: ['*']
verbs: ['*'] verbs: ['*']
- apiGroups: ['notification.fluxcd.io']
resources: ['*']
verbs: ['*']
- apiGroups: - apiGroups:
- "" - ""
resources: resources:

View File

@@ -1,7 +1,7 @@
site_name: GitOps Toolkit site_name: GitOps Toolkit
site_description: Documentation for GitOps Toolkit. site_description: A toolkit for assembling GitOps pipelines on Kubernetes
site_author: The Flux CD contributors site_author: The Flux CD contributors
site_url: https://fluxcd.github.io/toolkit/ site_url: https://toolkit.fluxcd.io
repo_name: fluxcd/toolkit repo_name: fluxcd/toolkit
repo_url: https://github.com/fluxcd/toolkit repo_url: https://github.com/fluxcd/toolkit
@@ -14,6 +14,7 @@ theme:
palette: palette:
primary: blue primary: blue
accent: indigo accent: indigo
custom_dir: mkdocs/
docs_dir: docs docs_dir: docs
@@ -25,6 +26,7 @@ plugins:
markdown_extensions: markdown_extensions:
- admonition - admonition
- meta
- codehilite: - codehilite:
guess_lang: false guess_lang: false
- toc: - toc:
@@ -32,10 +34,13 @@ markdown_extensions:
- pymdownx.superfences: - pymdownx.superfences:
highlight_code: true highlight_code: true
- pymdownx.tabbed - pymdownx.tabbed
- pymdownx.tilde
nav: nav:
- Introduction: index.md - Introduction: index.md
- Get Started: get-started/index.md - Get Started: get-started/index.md
- Guides:
- Setup Notifications: guides/notifications.md
- Toolkit Components: - Toolkit Components:
- Source Controller: - Source Controller:
- Overview: components/source/controller.md - Overview: components/source/controller.md
@@ -46,6 +51,12 @@ nav:
- Overview: components/kustomize/controller.md - Overview: components/kustomize/controller.md
- Kustomization CRD: components/kustomize/kustomization.md - Kustomization CRD: components/kustomize/kustomization.md
- Kustomize API Reference: components/kustomize/api.md - Kustomize API Reference: components/kustomize/api.md
- Notification Controller:
- Overview: components/notification/controller.md
- Provider CRD: components/notification/provider.md
- Alert CRD: components/notification/alert.md
- Event: components/notification/event.md
- Notification API Reference: components/notification/api.md
- Toolkit CLI: - Toolkit CLI:
- Overview: cmd/tk.md - Overview: cmd/tk.md
- Bootstrap: cmd/tk_bootstrap.md - Bootstrap: cmd/tk_bootstrap.md

32
mkdocs/main.html Normal file
View File

@@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block extrahead %}
<meta property="og:url" content="{{ page.canonical_url }}">
{% if page and page.meta and page.meta.title %}
<meta property="og:title" content="{{ page.meta.title }}">
{% elif page and page.title and not page.is_homepage %}
<meta property="og:title" content="{{ page.title }} - {{ config.site_name }}">
{% else %}
<meta property="og:title" content="{{ config.site_name }}">
{% endif %}
<meta property="og:description" content="{{ config.site_description }}">
<meta property="og:image" content="https://toolkit.fluxcd.io/_files/toolkit-icon.png">
<meta property="og:image:alt" content="GitOps Toolkit">
<meta property="og:image:type" content="image/png">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@stefanprodan">
<meta name="twitter:creator" content="@stefanprodan">
{% if page and page.meta and page.meta.title %}
<meta property="twitter:title" content="{{ page.meta.title }}">
{% elif page and page.title and not page.is_homepage %}
<meta property="twitter:title" content="{{ page.title }} - {{ config.site_name }}">
{% else %}
<meta property="twitter:title" content="{{ config.site_name }}">
{% endif %}
<meta name="twitter:description" content="{{ config.site_description }}">
<meta name="twitter:image" content="https://toolkit.fluxcd.io/_files/toolkit-icon.png">
<meta name="twitter:image:alt" content="GitOps Toolkit">
{% endblock %}

View File

@@ -1,177 +0,0 @@
/*
Copyright 2020 The Flux CD contributors.
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 git
import (
"context"
"fmt"
"github.com/google/go-github/v32/github"
"strings"
)
// GithubProvider represents a GitHub API wrapper
type GithubProvider struct {
IsPrivate bool
IsPersonal bool
}
const (
GitHubTokenName = "GITHUB_TOKEN"
GitHubDefaultHostname = "github.com"
)
func (p *GithubProvider) newClient(r *Repository) (*github.Client, error) {
auth := github.BasicAuthTransport{
Username: "git",
Password: r.Token,
}
gh := github.NewClient(auth.Client())
if r.Host != GitHubDefaultHostname {
baseURL := fmt.Sprintf("https://%s/api/v3/", r.Host)
uploadURL := fmt.Sprintf("https://%s/api/uploads/", r.Host)
if g, err := github.NewEnterpriseClient(baseURL, uploadURL, auth.Client()); err == nil {
gh = g
} else {
return nil, err
}
}
return gh, nil
}
// CreateRepository returns false if the repository exists
func (p *GithubProvider) CreateRepository(ctx context.Context, r *Repository) (bool, error) {
gh, err := p.newClient(r)
if err != nil {
return false, fmt.Errorf("client error: %w", err)
}
org := ""
if !p.IsPersonal {
org = r.Owner
}
if _, _, err := gh.Repositories.Get(ctx, org, r.Name); err == nil {
return false, nil
}
autoInit := true
_, _, err = gh.Repositories.Create(ctx, org, &github.Repository{
AutoInit: &autoInit,
Name: &r.Name,
Private: &p.IsPrivate,
})
if err != nil {
if !strings.Contains(err.Error(), "name already exists on this account") {
return false, fmt.Errorf("failed to create repository, error: %w", err)
}
} else {
return true, nil
}
return false, nil
}
// AddTeam returns false if the team is already assigned to the repository
func (p *GithubProvider) AddTeam(ctx context.Context, r *Repository, name, permission string) (bool, error) {
gh, err := p.newClient(r)
if err != nil {
return false, fmt.Errorf("client error: %w", err)
}
// check team exists
_, _, err = gh.Teams.GetTeamBySlug(ctx, r.Owner, name)
if err != nil {
return false, fmt.Errorf("failed to retrieve team '%s', error: %w", name, err)
}
// check if team is assigned to the repo
_, resp, err := gh.Teams.IsTeamRepoBySlug(ctx, r.Owner, name, r.Owner, r.Name)
if resp == nil && err != nil {
return false, fmt.Errorf("failed to determine if team '%s' is assigned to the repository, error: %w", name, err)
}
// add team to the repo
if resp.StatusCode == 404 {
_, err = gh.Teams.AddTeamRepoBySlug(ctx, r.Owner, name, r.Owner, r.Name, &github.TeamAddTeamRepoOptions{
Permission: permission,
})
if err != nil {
return false, fmt.Errorf("failed to add team '%s' to the repository, error: %w", name, err)
}
return true, nil
}
return false, nil
}
// AddDeployKey returns false if the key exists and the content is the same
func (p *GithubProvider) AddDeployKey(ctx context.Context, r *Repository, key, keyName string) (bool, error) {
gh, err := p.newClient(r)
if err != nil {
return false, fmt.Errorf("client error: %w", err)
}
// list deploy keys
keys, resp, err := gh.Repositories.ListKeys(ctx, r.Owner, r.Name, nil)
if err != nil {
return false, fmt.Errorf("failed to list deploy keys, error: %w", err)
}
if resp.StatusCode >= 300 {
return false, fmt.Errorf("failed to list deploy keys (status code: %s)", resp.Status)
}
// check if the key exists
shouldCreateKey := true
var existingKey *github.Key
for _, k := range keys {
if k.Title != nil && k.Key != nil && *k.Title == keyName {
if *k.Key != key {
existingKey = k
} else {
shouldCreateKey = false
}
break
}
}
// delete existing key if the value differs
if existingKey != nil {
resp, err := gh.Repositories.DeleteKey(ctx, r.Owner, r.Name, *existingKey.ID)
if err != nil {
return false, fmt.Errorf("failed to delete deploy key '%s', error: %w", keyName, err)
}
if resp.StatusCode >= 300 {
return false, fmt.Errorf("failed to delete deploy key '%s' (status code: %s)", keyName, resp.Status)
}
}
// create key
if shouldCreateKey {
isReadOnly := true
_, _, err = gh.Repositories.CreateKey(ctx, r.Owner, r.Name, &github.Key{
Title: &keyName,
Key: &key,
ReadOnly: &isReadOnly,
})
if err != nil {
return false, fmt.Errorf("failed to create deploy key '%s', error: %w", keyName, err)
}
return true, nil
}
return false, nil
}

View File

@@ -1,163 +0,0 @@
/*
Copyright 2020 The Flux CD contributors.
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 git
import (
"context"
"fmt"
"github.com/xanzy/go-gitlab"
)
// GitLabProvider represents a GitLab API wrapper
type GitLabProvider struct {
IsPrivate bool
IsPersonal bool
}
const (
GitLabTokenName = "GITLAB_TOKEN"
GitLabDefaultHostname = "gitlab.com"
)
func (p *GitLabProvider) newClient(r *Repository) (*gitlab.Client, error) {
gl, err := gitlab.NewClient(r.Token)
if err != nil {
return nil, err
}
if r.Host != GitLabDefaultHostname {
gl, err = gitlab.NewClient(r.Token, gitlab.WithBaseURL(fmt.Sprintf("https://%s/api/v4", r.Host)))
if err != nil {
return nil, err
}
}
return gl, nil
}
// CreateRepository returns false if the repository already exists
func (p *GitLabProvider) CreateRepository(ctx context.Context, r *Repository) (bool, error) {
gl, err := p.newClient(r)
if err != nil {
return false, fmt.Errorf("client error: %w", err)
}
var id *int
if !p.IsPersonal {
groups, _, err := gl.Groups.ListGroups(&gitlab.ListGroupsOptions{Search: gitlab.String(r.Owner)}, gitlab.WithContext(ctx))
if err != nil {
return false, fmt.Errorf("failed to list groups, error: %w", err)
}
if len(groups) > 0 {
id = &groups[0].ID
}
}
visibility := gitlab.PublicVisibility
if p.IsPrivate {
visibility = gitlab.PrivateVisibility
}
projects, _, err := gl.Projects.ListProjects(&gitlab.ListProjectsOptions{Search: gitlab.String(r.Name)}, gitlab.WithContext(ctx))
if err != nil {
return false, fmt.Errorf("failed to list projects, error: %w", err)
}
if len(projects) == 0 {
p := &gitlab.CreateProjectOptions{
Name: gitlab.String(r.Name),
NamespaceID: id,
Visibility: &visibility,
InitializeWithReadme: gitlab.Bool(true),
}
_, _, err := gl.Projects.CreateProject(p)
if err != nil {
return false, fmt.Errorf("failed to create project, error: %w", err)
}
return true, nil
}
return false, nil
}
// AddTeam returns false if the team is already assigned to the repository
func (p *GitLabProvider) AddTeam(ctx context.Context, r *Repository, name, permission string) (bool, error) {
return false, nil
}
// AddDeployKey returns false if the key exists and the content is the same
func (p *GitLabProvider) AddDeployKey(ctx context.Context, r *Repository, key, keyName string) (bool, error) {
gl, err := p.newClient(r)
if err != nil {
return false, fmt.Errorf("client error: %w", err)
}
// list deploy keys
var projId int
projects, _, err := gl.Projects.ListProjects(&gitlab.ListProjectsOptions{Search: gitlab.String(r.Name)}, gitlab.WithContext(ctx))
if err != nil {
return false, fmt.Errorf("failed to list projects, error: %w", err)
}
if len(projects) > 0 {
projId = projects[0].ID
} else {
return false, fmt.Errorf("no project found")
}
// check if the key exists
keys, _, err := gl.DeployKeys.ListProjectDeployKeys(projId, &gitlab.ListProjectDeployKeysOptions{})
if err != nil {
return false, fmt.Errorf("failed to list deploy keys, error: %w", err)
}
shouldCreateKey := true
var existingKey *gitlab.DeployKey
for _, k := range keys {
if k.Title == keyName {
if k.Key != key {
existingKey = k
} else {
shouldCreateKey = false
}
break
}
}
// delete existing key if the value differs
if existingKey != nil {
_, err := gl.DeployKeys.DeleteDeployKey(projId, existingKey.ID, gitlab.WithContext(ctx))
if err != nil {
return false, fmt.Errorf("failed to delete deploy key '%s', error: %w", keyName, err)
}
}
// create key
if shouldCreateKey {
_, _, err := gl.DeployKeys.AddDeployKey(projId, &gitlab.AddDeployKeyOptions{
Title: gitlab.String(keyName),
Key: gitlab.String(key),
CanPush: gitlab.Bool(false),
}, gitlab.WithContext(ctx))
if err != nil {
return false, fmt.Errorf("failed to create deploy key '%s', error: %w", keyName, err)
}
return true, nil
}
return false, nil
}

View File

@@ -1,167 +0,0 @@
/*
Copyright 2020 The Flux CD contributors.
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 git
import (
"context"
"fmt"
"time"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
// Repository represents a git repository wrapper
type Repository struct {
Name string
Owner string
Host string
Token string
AuthorName string
AuthorEmail string
repo *git.Repository
}
// NewRepository returns a git repository wrapper
func NewRepository(name, owner, host, token, authorName, authorEmail string) (*Repository, error) {
if name == "" {
return nil, fmt.Errorf("name required")
}
if owner == "" {
return nil, fmt.Errorf("owner required")
}
if host == "" {
return nil, fmt.Errorf("host required")
}
if token == "" {
return nil, fmt.Errorf("token required")
}
if authorName == "" {
return nil, fmt.Errorf("author name required")
}
if authorEmail == "" {
return nil, fmt.Errorf("author email required")
}
return &Repository{
Name: name,
Owner: owner,
Host: host,
Token: token,
AuthorName: authorName,
AuthorEmail: authorEmail,
}, nil
}
// GetURL returns the repository HTTPS address
func (r *Repository) GetURL() string {
return fmt.Sprintf("https://%s/%s/%s", r.Host, r.Owner, r.Name)
}
// GetSSH returns the repository SSH address
func (r *Repository) GetSSH() string {
return fmt.Sprintf("ssh://git@%s/%s/%s", r.Host, r.Owner, r.Name)
}
func (r *Repository) auth() transport.AuthMethod {
return &http.BasicAuth{
Username: "git",
Password: r.Token,
}
}
// Checkout repository branch at specified path
func (r *Repository) Checkout(ctx context.Context, branch, path string) error {
repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
URL: r.GetURL(),
Auth: r.auth(),
RemoteName: git.DefaultRemoteName,
ReferenceName: plumbing.NewBranchReferenceName(branch),
SingleBranch: true,
NoCheckout: false,
Progress: nil,
Tags: git.NoTags,
})
if err != nil {
return fmt.Errorf("git clone error: %w", err)
}
_, err = repo.Head()
if err != nil {
return fmt.Errorf("git resolve HEAD error: %w", err)
}
r.repo = repo
return nil
}
// Commit changes for the specified path, returns false if no changes are detected
func (r *Repository) Commit(ctx context.Context, path, message string) (bool, error) {
if r.repo == nil {
return false, fmt.Errorf("repository hasn't been cloned")
}
w, err := r.repo.Worktree()
if err != nil {
return false, err
}
_, err = w.Add(path)
if err != nil {
return false, err
}
status, err := w.Status()
if err != nil {
return false, err
}
if !status.IsClean() {
if _, err := w.Commit(message, &git.CommitOptions{
Author: &object.Signature{
Name: r.AuthorName,
Email: r.AuthorEmail,
When: time.Now(),
},
}); err != nil {
return false, err
}
return true, nil
}
return false, nil
}
// Push commits to origin
func (r *Repository) Push(ctx context.Context) error {
if r.repo == nil {
return fmt.Errorf("repository hasn't been cloned")
}
err := r.repo.PushContext(ctx, &git.PushOptions{
Auth: r.auth(),
Progress: nil,
})
if err != nil {
return fmt.Errorf("git push error: %w", err)
}
return nil
}

View File

@@ -13,14 +13,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package log
package git type Logger interface {
// Actionf logs a formatted action message.
import "context" Actionf(format string, a ...interface{})
// Generatef logs a formatted generate message.
// Provider is the interface that a git provider should implement Generatef(format string, a ...interface{})
type Provider interface { // Waitingf logs a formatted waiting message.
CreateRepository(ctx context.Context, r *Repository) (bool, error) Waitingf(format string, a ...interface{})
AddTeam(ctx context.Context, r *Repository, name, permission string) (bool, error) // Waitingf logs a formatted success message.
AddDeployKey(ctx context.Context, r *Repository, key, keyName string) (bool, error) Successf(format string, a ...interface{})
// Failuref logs a formatted failure message.
Failuref(format string, a ...interface{})
} }

View File

@@ -1,71 +0,0 @@
/*
Copyright 2020 The Flux CD contributors.
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 ssh
import (
"encoding/base64"
"fmt"
"net"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
// ScanHostKey collects the given host's preferred public key for the
// Any errors (e.g. authentication failures) are ignored, except if
// no key could be collected from the host.
func ScanHostKey(host string, timeout time.Duration) ([]byte, error) {
col := &HostKeyCollector{}
config := &ssh.ClientConfig{
HostKeyCallback: col.StoreKey(),
Timeout: timeout,
}
client, err := ssh.Dial("tcp", host, config)
if err == nil {
defer client.Close()
}
if len(col.knownKeys) > 0 {
return col.knownKeys, nil
}
return col.knownKeys, err
}
// HostKeyCollector offers a StoreKey method which provides an
// HostKeyCallBack to collect public keys from an SSH server.
type HostKeyCollector struct {
knownKeys []byte
}
// StoreKey stores the public key in bytes as returned by the host.
// To collect multiple public key types from the host, multiple
// SSH dials need with the ClientConfig HostKeyAlgorithms set to
// the algorithm you want to collect.
func (c *HostKeyCollector) StoreKey() ssh.HostKeyCallback {
return func(hostname string, remote net.Addr, key ssh.PublicKey) error {
c.knownKeys = append(
c.knownKeys,
fmt.Sprintf("%s %s %s\n", knownhosts.Normalize(hostname), key.Type(), base64.StdEncoding.EncodeToString(key.Marshal()))...,
)
return nil
}
}
// GetKnownKeys returns the collected public keys in bytes.
func (c *HostKeyCollector) GetKnownKeys() []byte {
return c.knownKeys
}

View File

@@ -1,146 +0,0 @@
/*
Copyright 2020 The Flux CD contributors.
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 ssh
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"golang.org/x/crypto/ssh"
)
// KeyPair holds the public and private key PEM block bytes.
type KeyPair struct {
PublicKey []byte
PrivateKey []byte
}
type KeyPairGenerator interface {
Generate() (*KeyPair, error)
}
type RSAGenerator struct {
bits int
}
func NewRSAGenerator(bits int) KeyPairGenerator {
return &RSAGenerator{bits}
}
func (g *RSAGenerator) Generate() (*KeyPair, error) {
pk, err := rsa.GenerateKey(rand.Reader, g.bits)
if err != nil {
return nil, err
}
err = pk.Validate()
if err != nil {
return nil, err
}
pub, err := generatePublicKey(&pk.PublicKey)
if err != nil {
return nil, err
}
priv, err := encodePrivateKeyToPEM(pk)
if err != nil {
return nil, err
}
return &KeyPair{
PublicKey: pub,
PrivateKey: priv,
}, nil
}
type ECDSAGenerator struct {
c elliptic.Curve
}
func NewECDSAGenerator(c elliptic.Curve) KeyPairGenerator {
return &ECDSAGenerator{c}
}
func (g *ECDSAGenerator) Generate() (*KeyPair, error) {
pk, err := ecdsa.GenerateKey(g.c, rand.Reader)
if err != nil {
return nil, err
}
pub, err := generatePublicKey(&pk.PublicKey)
if err != nil {
return nil, err
}
priv, err := encodePrivateKeyToPEM(pk)
if err != nil {
return nil, err
}
return &KeyPair{
PublicKey: pub,
PrivateKey: priv,
}, nil
}
type Ed25519Generator struct{}
func NewEd25519Generator() KeyPairGenerator {
return &Ed25519Generator{}
}
func (g *Ed25519Generator) Generate() (*KeyPair, error) {
pk, pv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
pub, err := generatePublicKey(pk)
if err != nil {
return nil, err
}
priv, err := encodePrivateKeyToPEM(pv)
if err != nil {
return nil, err
}
return &KeyPair{
PublicKey: pub,
PrivateKey: priv,
}, nil
}
func generatePublicKey(pk interface{}) ([]byte, error) {
b, err := ssh.NewPublicKey(pk)
if err != nil {
return nil, err
}
k := ssh.MarshalAuthorizedKey(b)
return k, nil
}
// encodePrivateKeyToPEM encodes the given private key to a PEM block.
// The encoded format is PKCS#8 for universal support of the most
// common key types (rsa, ecdsa, ed25519).
func encodePrivateKeyToPEM(pk interface{}) ([]byte, error) {
b, err := x509.MarshalPKCS8PrivateKey(pk)
if err != nil {
return nil, err
}
block := pem.Block{
Type: "PRIVATE KEY",
Bytes: b,
}
return pem.EncodeToMemory(&block), nil
}

View File

@@ -1,27 +0,0 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,446 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright 2020 The FluxCD contributors. All rights reserved.
// This package provides an in-memory known hosts database
// derived from the golang.org/x/crypto/ssh/knownhosts
// package.
// It has been slightly modified and adapted to work with
// in-memory host keys not related to any known_hosts files
// on disk, and the database can be initialized with just a
// known_hosts byte blob.
// https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts
package knownhosts
import (
"bufio"
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"strings"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
// See the sshd manpage
// (http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) for
// background.
type addr struct{ host, port string }
func (a *addr) String() string {
h := a.host
if strings.Contains(h, ":") {
h = "[" + h + "]"
}
return h + ":" + a.port
}
type matcher interface {
match(addr) bool
}
type hostPattern struct {
negate bool
addr addr
}
func (p *hostPattern) String() string {
n := ""
if p.negate {
n = "!"
}
return n + p.addr.String()
}
type hostPatterns []hostPattern
func (ps hostPatterns) match(a addr) bool {
matched := false
for _, p := range ps {
if !p.match(a) {
continue
}
if p.negate {
return false
}
matched = true
}
return matched
}
// See
// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/addrmatch.c
// The matching of * has no regard for separators, unlike filesystem globs
func wildcardMatch(pat []byte, str []byte) bool {
for {
if len(pat) == 0 {
return len(str) == 0
}
if len(str) == 0 {
return false
}
if pat[0] == '*' {
if len(pat) == 1 {
return true
}
for j := range str {
if wildcardMatch(pat[1:], str[j:]) {
return true
}
}
return false
}
if pat[0] == '?' || pat[0] == str[0] {
pat = pat[1:]
str = str[1:]
} else {
return false
}
}
}
func (p *hostPattern) match(a addr) bool {
return wildcardMatch([]byte(p.addr.host), []byte(a.host)) && p.addr.port == a.port
}
type inMemoryHostKeyDB struct {
hostKeys []hostKey
revoked map[string]*ssh.PublicKey
}
func newInMemoryHostKeyDB() *inMemoryHostKeyDB {
db := &inMemoryHostKeyDB{
revoked: make(map[string]*ssh.PublicKey),
}
return db
}
func keyEq(a, b ssh.PublicKey) bool {
return bytes.Equal(a.Marshal(), b.Marshal())
}
type hostKey struct {
matcher matcher
cert bool
key ssh.PublicKey
}
func (l *hostKey) match(a addr) bool {
return l.matcher.match(a)
}
// IsAuthorityForHost can be used as a callback in ssh.CertChecker
func (db *inMemoryHostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
h, p, err := net.SplitHostPort(address)
if err != nil {
return false
}
a := addr{host: h, port: p}
for _, l := range db.hostKeys {
if l.cert && keyEq(l.key, remote) && l.match(a) {
return true
}
}
return false
}
// IsRevoked can be used as a callback in ssh.CertChecker
func (db *inMemoryHostKeyDB) IsRevoked(key *ssh.Certificate) bool {
_, ok := db.revoked[string(key.Marshal())]
return ok
}
const markerCert = "@cert-authority"
const markerRevoked = "@revoked"
func nextWord(line []byte) (string, []byte) {
i := bytes.IndexAny(line, "\t ")
if i == -1 {
return string(line), nil
}
return string(line[:i]), bytes.TrimSpace(line[i:])
}
func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
if w, next := nextWord(line); w == markerCert || w == markerRevoked {
marker = w
line = next
}
host, line = nextWord(line)
if len(line) == 0 {
return "", "", nil, errors.New("knownhosts: missing host pattern")
}
// ignore the keytype as it's in the key blob anyway.
_, line = nextWord(line)
if len(line) == 0 {
return "", "", nil, errors.New("knownhosts: missing key type pattern")
}
keyBlob, _ := nextWord(line)
keyBytes, err := base64.StdEncoding.DecodeString(keyBlob)
if err != nil {
return "", "", nil, err
}
key, err = ssh.ParsePublicKey(keyBytes)
if err != nil {
return "", "", nil, err
}
return marker, host, key, nil
}
func (db *inMemoryHostKeyDB) parseLine(line []byte) error {
marker, pattern, key, err := parseLine(line)
if err != nil {
return err
}
if marker == markerRevoked {
db.revoked[string(key.Marshal())] = &key
return nil
}
entry := hostKey{
key: key,
cert: marker == markerCert,
}
if pattern[0] == '|' {
entry.matcher, err = newHashedHost(pattern)
} else {
entry.matcher, err = newHostnameMatcher(pattern)
}
if err != nil {
return err
}
db.hostKeys = append(db.hostKeys, entry)
return nil
}
func newHostnameMatcher(pattern string) (matcher, error) {
var hps hostPatterns
for _, p := range strings.Split(pattern, ",") {
if len(p) == 0 {
continue
}
var a addr
var negate bool
if p[0] == '!' {
negate = true
p = p[1:]
}
if len(p) == 0 {
return nil, errors.New("knownhosts: negation without following hostname")
}
var err error
if p[0] == '[' {
a.host, a.port, err = net.SplitHostPort(p)
if err != nil {
return nil, err
}
} else {
a.host, a.port, err = net.SplitHostPort(p)
if err != nil {
a.host = p
a.port = "22"
}
}
hps = append(hps, hostPattern{
negate: negate,
addr: a,
})
}
return hps, nil
}
// check checks a key against the host database. This should not be
// used for verifying certificates.
func (db *inMemoryHostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
return &knownhosts.RevokedError{Revoked: knownhosts.KnownKey{Key: *revoked}}
}
host, port, err := net.SplitHostPort(remote.String())
if err != nil {
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", remote, err)
}
hostToCheck := addr{host, port}
if address != "" {
// Give preference to the hostname if available.
host, port, err := net.SplitHostPort(address)
if err != nil {
return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", address, err)
}
hostToCheck = addr{host, port}
}
return db.checkAddr(hostToCheck, remoteKey)
}
// checkAddr checks if we can find the given public key for the
// given address. If we only find an entry for the IP address,
// or only the hostname, then this still succeeds.
func (db *inMemoryHostKeyDB) checkAddr(a addr, remoteKey ssh.PublicKey) error {
// TODO(hanwen): are these the right semantics? What if there
// is just a key for the IP address, but not for the
// hostname?
// Algorithm => key.
knownKeys := map[string]ssh.PublicKey{}
for _, l := range db.hostKeys {
if l.match(a) {
typ := l.key.Type()
if _, ok := knownKeys[typ]; !ok {
knownKeys[typ] = l.key
}
}
}
keyErr := &knownhosts.KeyError{}
for _, v := range knownKeys {
keyErr.Want = append(keyErr.Want, knownhosts.KnownKey{Key: v})
}
// Unknown remote host.
if len(knownKeys) == 0 {
return keyErr
}
// If the remote host starts using a different, unknown key type, we
// also interpret that as a mismatch.
if known, ok := knownKeys[remoteKey.Type()]; !ok || !keyEq(known, remoteKey) {
return keyErr
}
return nil
}
// The Read function parses file contents.
func (db *inMemoryHostKeyDB) Read(r io.Reader) error {
scanner := bufio.NewScanner(r)
lineNum := 0
for scanner.Scan() {
lineNum++
line := scanner.Bytes()
line = bytes.TrimSpace(line)
if len(line) == 0 || line[0] == '#' {
continue
}
if err := db.parseLine(line); err != nil {
return fmt.Errorf("knownhosts: %v", err)
}
}
return scanner.Err()
}
// New creates a host key callback from the given OpenSSH host key
// file bytes. The returned callback is for use in
// ssh.ClientConfig.HostKeyCallback. By preference, the key check
// operates on the hostname if available, i.e. if a server changes its
// IP address, the host key check will still succeed, even though a
// record of the new IP address is not available.
func New(b []byte) (ssh.HostKeyCallback, error) {
db := newInMemoryHostKeyDB()
r := bytes.NewReader(b)
if err := db.Read(r); err != nil {
return nil, err
}
var certChecker ssh.CertChecker
certChecker.IsHostAuthority = db.IsHostAuthority
certChecker.IsRevoked = db.IsRevoked
certChecker.HostKeyFallback = db.check
return certChecker.CheckHostKey, nil
}
func decodeHash(encoded string) (hashType string, salt, hash []byte, err error) {
if len(encoded) == 0 || encoded[0] != '|' {
err = errors.New("knownhosts: hashed host must start with '|'")
return
}
components := strings.Split(encoded, "|")
if len(components) != 4 {
err = fmt.Errorf("knownhosts: got %d components, want 3", len(components))
return
}
hashType = components[1]
if salt, err = base64.StdEncoding.DecodeString(components[2]); err != nil {
return
}
if hash, err = base64.StdEncoding.DecodeString(components[3]); err != nil {
return
}
return
}
func encodeHash(typ string, salt []byte, hash []byte) string {
return strings.Join([]string{"",
typ,
base64.StdEncoding.EncodeToString(salt),
base64.StdEncoding.EncodeToString(hash),
}, "|")
}
// See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
func hashHost(hostname string, salt []byte) []byte {
mac := hmac.New(sha1.New, salt)
mac.Write([]byte(hostname))
return mac.Sum(nil)
}
type hashedHost struct {
salt []byte
hash []byte
}
const sha1HashType = "1"
func newHashedHost(encoded string) (*hashedHost, error) {
typ, salt, hash, err := decodeHash(encoded)
if err != nil {
return nil, err
}
// The type field seems for future algorithm agility, but it's
// actually hardcoded in openssh currently, see
// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
if typ != sha1HashType {
return nil, fmt.Errorf("knownhosts: got hash type %s, must be '1'", typ)
}
return &hashedHost{salt: salt, hash: hash}, nil
}
func (h *hashedHost) match(a addr) bool {
return bytes.Equal(hashHost(knownhosts.Normalize(a.String()), h.salt), h.hash)
}

View File

@@ -1,327 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright 2020 The FluxCD contributors. All rights reserved.
// This package provides an in-memory known hosts database
// derived from the golang.org/x/crypto/ssh/knownhosts
// package.
// It has been slightly modified and adapted to work with
// in-memory host keys not related to any known_hosts files
// on disk, and the database can be initialized with just a
// known_hosts byte blob.
// https://pkg.go.dev/golang.org/x/crypto/ssh/knownhosts
package knownhosts
import (
"bytes"
"fmt"
"net"
"reflect"
"testing"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
const edKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGBAarftlLeoyf+v+nVchEZII/vna2PCV8FaX4vsF5BX"
const alternateEdKeyStr = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIXffBYeYL+WVzVru8npl5JHt2cjlr4ornFTWzoij9sx"
const ecKeyStr = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNLCu01+wpXe3xB5olXCN4SqU2rQu0qjSRKJO4Bg+JRCPU+ENcgdA5srTU8xYDz/GEa4dzK5ldPw4J/gZgSXCMs="
var ecKey, alternateEdKey, edKey ssh.PublicKey
var testAddr = &net.TCPAddr{
IP: net.IP{198, 41, 30, 196},
Port: 22,
}
var testAddr6 = &net.TCPAddr{
IP: net.IP{198, 41, 30, 196,
1, 2, 3, 4,
1, 2, 3, 4,
1, 2, 3, 4,
},
Port: 22,
}
func init() {
var err error
ecKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(ecKeyStr))
if err != nil {
panic(err)
}
edKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(edKeyStr))
if err != nil {
panic(err)
}
alternateEdKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(alternateEdKeyStr))
if err != nil {
panic(err)
}
}
func testDB(t *testing.T, s string) *inMemoryHostKeyDB {
db := newInMemoryHostKeyDB()
if err := db.Read(bytes.NewBufferString(s)); err != nil {
t.Fatalf("Read: %v", err)
}
return db
}
func TestRevoked(t *testing.T) {
db := testDB(t, "\n\n@revoked * "+edKeyStr+"\n")
want := &knownhosts.RevokedError{
Revoked: knownhosts.KnownKey{
Key: edKey,
},
}
if err := db.check("", &net.TCPAddr{
Port: 42,
}, edKey); err == nil {
t.Fatal("no error for revoked key")
} else if !reflect.DeepEqual(want, err) {
t.Fatalf("got %#v, want %#v", want, err)
}
}
func TestHostAuthority(t *testing.T) {
for _, m := range []struct {
authorityFor string
address string
good bool
}{
{authorityFor: "localhost", address: "localhost:22", good: true},
{authorityFor: "localhost", address: "localhost", good: false},
{authorityFor: "localhost", address: "localhost:1234", good: false},
{authorityFor: "[localhost]:1234", address: "localhost:1234", good: true},
{authorityFor: "[localhost]:1234", address: "localhost:22", good: false},
{authorityFor: "[localhost]:1234", address: "localhost", good: false},
} {
db := testDB(t, `@cert-authority `+m.authorityFor+` `+edKeyStr)
if ok := db.IsHostAuthority(db.hostKeys[0].key, m.address); ok != m.good {
t.Errorf("IsHostAuthority: authority %s, address %s, wanted good = %v, got good = %v",
m.authorityFor, m.address, m.good, ok)
}
}
}
func TestBracket(t *testing.T) {
db := testDB(t, `[git.eclipse.org]:29418,[198.41.30.196]:29418 `+edKeyStr)
if err := db.check("git.eclipse.org:29418", &net.TCPAddr{
IP: net.IP{198, 41, 30, 196},
Port: 29418,
}, edKey); err != nil {
t.Errorf("got error %v, want none", err)
}
if err := db.check("git.eclipse.org:29419", &net.TCPAddr{
Port: 42,
}, edKey); err == nil {
t.Fatalf("no error for unknown address")
} else if ke, ok := err.(*knownhosts.KeyError); !ok {
t.Fatalf("got type %T, want *KeyError", err)
} else if len(ke.Want) > 0 {
t.Fatalf("got Want %v, want []", ke.Want)
}
}
func TestNewKeyType(t *testing.T) {
str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
db := testDB(t, str)
if err := db.check("", testAddr, ecKey); err == nil {
t.Fatalf("no error for unknown address")
} else if ke, ok := err.(*knownhosts.KeyError); !ok {
t.Fatalf("got type %T, want *KeyError", err)
} else if len(ke.Want) == 0 {
t.Fatalf("got empty KeyError.Want")
}
}
func TestSameKeyType(t *testing.T) {
str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
db := testDB(t, str)
if err := db.check("", testAddr, alternateEdKey); err == nil {
t.Fatalf("no error for unknown address")
} else if ke, ok := err.(*knownhosts.KeyError); !ok {
t.Fatalf("got type %T, want *KeyError", err)
} else if len(ke.Want) == 0 {
t.Fatalf("got empty KeyError.Want")
} else if got, want := ke.Want[0].Key.Marshal(), edKey.Marshal(); !bytes.Equal(got, want) {
t.Fatalf("got key %q, want %q", got, want)
}
}
func TestIPAddress(t *testing.T) {
str := fmt.Sprintf("%s %s", testAddr, edKeyStr)
db := testDB(t, str)
if err := db.check("", testAddr, edKey); err != nil {
t.Errorf("got error %q, want none", err)
}
}
func TestIPv6Address(t *testing.T) {
str := fmt.Sprintf("%s %s", testAddr6, edKeyStr)
db := testDB(t, str)
if err := db.check("", testAddr6, edKey); err != nil {
t.Errorf("got error %q, want none", err)
}
}
func TestBasic(t *testing.T) {
str := fmt.Sprintf("#comment\n\nserver.org,%s %s\notherhost %s", testAddr, edKeyStr, ecKeyStr)
db := testDB(t, str)
if err := db.check("server.org:22", testAddr, edKey); err != nil {
t.Errorf("got error %v, want none", err)
}
want := knownhosts.KnownKey{
Key: edKey,
}
if err := db.check("server.org:22", testAddr, ecKey); err == nil {
t.Errorf("succeeded, want KeyError")
} else if ke, ok := err.(*knownhosts.KeyError); !ok {
t.Errorf("got %T, want *KeyError", err)
} else if len(ke.Want) != 1 {
t.Errorf("got %v, want 1 entry", ke)
} else if !reflect.DeepEqual(ke.Want[0], want) {
t.Errorf("got %v, want %v", ke.Want[0], want)
}
}
func TestHostNamePrecedence(t *testing.T) {
var evilAddr = &net.TCPAddr{
IP: net.IP{66, 66, 66, 66},
Port: 22,
}
str := fmt.Sprintf("server.org,%s %s\nevil.org,%s %s", testAddr, edKeyStr, evilAddr, ecKeyStr)
db := testDB(t, str)
if err := db.check("server.org:22", evilAddr, ecKey); err == nil {
t.Errorf("check succeeded")
} else if _, ok := err.(*knownhosts.KeyError); !ok {
t.Errorf("got %T, want *KeyError", err)
}
}
func TestDBOrderingPrecedenceKeyType(t *testing.T) {
str := fmt.Sprintf("server.org,%s %s\nserver.org,%s %s", testAddr, edKeyStr, testAddr, alternateEdKeyStr)
db := testDB(t, str)
if err := db.check("server.org:22", testAddr, alternateEdKey); err == nil {
t.Errorf("check succeeded")
} else if _, ok := err.(*knownhosts.KeyError); !ok {
t.Errorf("got %T, want *KeyError", err)
}
}
func TestNegate(t *testing.T) {
str := fmt.Sprintf("%s,!server.org %s", testAddr, edKeyStr)
db := testDB(t, str)
if err := db.check("server.org:22", testAddr, ecKey); err == nil {
t.Errorf("succeeded")
} else if ke, ok := err.(*knownhosts.KeyError); !ok {
t.Errorf("got error type %T, want *KeyError", err)
} else if len(ke.Want) != 0 {
t.Errorf("got expected keys %d (first of type %s), want []", len(ke.Want), ke.Want[0].Key.Type())
}
}
func TestWildcard(t *testing.T) {
str := fmt.Sprintf("server*.domain %s", edKeyStr)
db := testDB(t, str)
want := &knownhosts.KeyError{
Want: []knownhosts.KnownKey{{
Key: edKey,
}},
}
got := db.check("server.domain:22", &net.TCPAddr{}, ecKey)
if !reflect.DeepEqual(got, want) {
t.Errorf("got %s, want %s", got, want)
}
}
func TestWildcardMatch(t *testing.T) {
for _, c := range []struct {
pat, str string
want bool
}{
{"a?b", "abb", true},
{"ab", "abc", false},
{"abc", "ab", false},
{"a*b", "axxxb", true},
{"a*b", "axbxb", true},
{"a*b", "axbxbc", false},
{"a*?", "axbxc", true},
{"a*b*", "axxbxxxxxx", true},
{"a*b*c", "axxbxxxxxxc", true},
{"a*b*?", "axxbxxxxxxc", true},
{"a*b*z", "axxbxxbxxxz", true},
{"a*b*z", "axxbxxzxxxz", true},
{"a*b*z", "axxbxxzxxx", false},
} {
got := wildcardMatch([]byte(c.pat), []byte(c.str))
if got != c.want {
t.Errorf("wildcardMatch(%q, %q) = %v, want %v", c.pat, c.str, got, c.want)
}
}
}
// TODO(hanwen): test coverage for certificates.
const testHostname = "hostname"
// generated with keygen -H -f
const encodedTestHostnameHash = "|1|IHXZvQMvTcZTUU29+2vXFgx8Frs=|UGccIWfRVDwilMBnA3WJoRAC75Y="
func TestHostHash(t *testing.T) {
testHostHash(t, testHostname, encodedTestHostnameHash)
}
func TestHashList(t *testing.T) {
encoded := knownhosts.HashHostname(testHostname)
testHostHash(t, testHostname, encoded)
}
func testHostHash(t *testing.T, hostname, encoded string) {
typ, salt, hash, err := decodeHash(encoded)
if err != nil {
t.Fatalf("decodeHash: %v", err)
}
if got := encodeHash(typ, salt, hash); got != encoded {
t.Errorf("got encoding %s want %s", got, encoded)
}
if typ != sha1HashType {
t.Fatalf("got hash type %q, want %q", typ, sha1HashType)
}
got := hashHost(hostname, salt)
if !bytes.Equal(got, hash) {
t.Errorf("got hash %x want %x", got, hash)
}
}
func TestHashedHostkeyCheck(t *testing.T) {
str := fmt.Sprintf("%s %s", knownhosts.HashHostname(testHostname), edKeyStr)
db := testDB(t, str)
if err := db.check(testHostname+":22", testAddr, edKey); err != nil {
t.Errorf("check(%s): %v", testHostname, err)
}
want := &knownhosts.KeyError{
Want: []knownhosts.KnownKey{{
Key: edKey,
}},
}
if got := db.check(testHostname+":22", testAddr, alternateEdKey); !reflect.DeepEqual(got, want) {
t.Errorf("got error %v, want %v", got, want)
}
}