diff --git a/cmd/argocd-util/main.go b/cmd/argocd-util/main.go index 73cfcce599..be1db06296 100644 --- a/cmd/argocd-util/main.go +++ b/cmd/argocd-util/main.go @@ -6,14 +6,20 @@ import ( "io/ioutil" "os" "os/exec" + "strings" "syscall" "github.com/argoproj/argo-cd/errors" + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned" "github.com/argoproj/argo-cd/util/cli" + "github.com/argoproj/argo-cd/util/db" "github.com/argoproj/argo-cd/util/dex" "github.com/argoproj/argo-cd/util/settings" + "github.com/ghodss/yaml" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" @@ -26,6 +32,9 @@ import ( const ( // CLIName is the name of the CLI cliName = "argocd-util" + + // YamlSeparator separates sections of a YAML file + yamlSeparator = "\n---\n" ) // NewCommand returns a new instance of an argocd command @@ -45,6 +54,8 @@ func NewCommand() *cobra.Command { command.AddCommand(cli.NewVersionCmd(cliName)) command.AddCommand(NewRunDexCommand()) command.AddCommand(NewGenDexConfigCommand()) + command.AddCommand(NewImportCommand()) + command.AddCommand(NewExportCommand()) command.Flags().StringVar(&logLevel, "loglevel", "info", "Set the logging level. One of: debug|info|warn|error") return command @@ -154,6 +165,164 @@ func NewGenDexConfigCommand() *cobra.Command { return &command } +// NewImportCommand defines a new command for exporting Kubernetes and Argo CD resources. +func NewImportCommand() *cobra.Command { + var ( + clientConfig clientcmd.ClientConfig + in string + ) + var command = cobra.Command{ + Use: "import", + Short: "Import Argo CD data", + RunE: func(c *cobra.Command, args []string) error { + var ( + input []byte + err error + newSettings *settings.ArgoCDSettings + newRepos []*v1alpha1.Repository + newClusters []*v1alpha1.Cluster + newApps []*v1alpha1.Application + ) + if in == "-" { + input, err = ioutil.ReadAll(os.Stdin) + errors.CheckError(err) + } else { + input, err = ioutil.ReadFile(in) + errors.CheckError(err) + } + inputStrings := strings.Split(string(input), yamlSeparator) + + err = yaml.Unmarshal([]byte(inputStrings[0]), &newSettings) + errors.CheckError(err) + + err = yaml.Unmarshal([]byte(inputStrings[1]), &newRepos) + errors.CheckError(err) + + err = yaml.Unmarshal([]byte(inputStrings[2]), &newClusters) + errors.CheckError(err) + + err = yaml.Unmarshal([]byte(inputStrings[3]), &newApps) + errors.CheckError(err) + + config, err := clientConfig.ClientConfig() + errors.CheckError(err) + namespace, _, err := clientConfig.Namespace() + errors.CheckError(err) + kubeClientset := kubernetes.NewForConfigOrDie(config) + + settingsMgr := settings.NewSettingsManager(kubeClientset, namespace) + err = settingsMgr.SaveSettings(newSettings) + errors.CheckError(err) + db := db.NewDB(namespace, kubeClientset) + + for _, repo := range newRepos { + _, err := db.CreateRepository(context.Background(), repo) + if err != nil { + log.Warn(err) + } + } + + for _, cluster := range newClusters { + _, err := db.CreateCluster(context.Background(), cluster) + if err != nil { + log.Warn(err) + } + } + + appClientset := appclientset.NewForConfigOrDie(config) + for _, app := range newApps { + out, err := appClientset.ArgoprojV1alpha1().Applications(namespace).Create(app) + errors.CheckError(err) + log.Println(out) + } + + return nil + }, + } + + clientConfig = cli.AddKubectlFlagsToCmd(&command) + command.Flags().StringVarP(&in, "in", "i", "-", "Input from the specified file instead of stdin") + + return &command +} + +// NewExportCommand defines a new command for exporting Kubernetes and Argo CD resources. +func NewExportCommand() *cobra.Command { + var ( + clientConfig clientcmd.ClientConfig + out string + ) + var command = cobra.Command{ + Use: "export", + Short: "Export all Argo CD data", + RunE: func(c *cobra.Command, args []string) error { + config, err := clientConfig.ClientConfig() + errors.CheckError(err) + namespace, _, err := clientConfig.Namespace() + errors.CheckError(err) + kubeClientset := kubernetes.NewForConfigOrDie(config) + + settingsMgr := settings.NewSettingsManager(kubeClientset, namespace) + settings, err := settingsMgr.GetSettings() + errors.CheckError(err) + // certificate data is included in secrets that are exported alongside + settings.Certificate = nil + settingsData, err := yaml.Marshal(settings) + errors.CheckError(err) + + db := db.NewDB(namespace, kubeClientset) + clusters, err := db.ListClusters(context.Background()) + errors.CheckError(err) + clusterData, err := yaml.Marshal(clusters.Items) + errors.CheckError(err) + + repos, err := db.ListRepositories(context.Background()) + errors.CheckError(err) + repoData, err := yaml.Marshal(repos.Items) + errors.CheckError(err) + + appClientset := appclientset.NewForConfigOrDie(config) + apps, err := appClientset.ArgoprojV1alpha1().Applications(namespace).List(metav1.ListOptions{}) + errors.CheckError(err) + + // remove extraneous cruft from output + for idx, app := range apps.Items { + apps.Items[idx].ObjectMeta = metav1.ObjectMeta{ + Name: app.ObjectMeta.Name, + Finalizers: app.ObjectMeta.Finalizers, + } + apps.Items[idx].Status = v1alpha1.ApplicationStatus{ + History: app.Status.History, + } + apps.Items[idx].Operation = nil + } + appsData, err := yaml.Marshal(apps.Items) + errors.CheckError(err) + + outputStrings := []string{ + string(settingsData), + string(repoData), + string(clusterData), + string(appsData), + } + output := strings.Join(outputStrings, yamlSeparator) + + if out == "-" { + fmt.Println(output) + } else { + err = ioutil.WriteFile(out, []byte(output), 0644) + errors.CheckError(err) + } + return nil + }, + } + + clientConfig = cli.AddKubectlFlagsToCmd(&command) + command.Flags().StringVarP(&out, "out", "o", "-", "Output to the specified file instead of stdout") + + return &command +} + func main() { if err := NewCommand().Execute(); err != nil { fmt.Println(err) diff --git a/util/settings/settings.go b/util/settings/settings.go index 4a9d4b25bb..87299f47f5 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -27,24 +27,24 @@ import ( type ArgoCDSettings struct { // URL is the externally facing URL users will visit to reach ArgoCD. // The value here is used when configuring SSO. Omitting this value will disable SSO. - URL string + URL string `json:"url,omitempty"` // DexConfig is contains portions of a dex config yaml - DexConfig string + DexConfig string `json:"dexConfig,omitempty"` // LocalUsers holds users local to (stored on) the server. This is to be distinguished from any potential alternative future login providers (LDAP, SAML, etc.) that might ever be added. - LocalUsers map[string]string + LocalUsers map[string]string `json:"localUsers,omitempty"` // ServerSignature holds the key used to generate JWT tokens. - ServerSignature []byte + ServerSignature []byte `json:"serverSignature,omitempty"` // Certificate holds the certificate/private key for the ArgoCD API server. // If nil, will run insecure without TLS. - Certificate *tls.Certificate + Certificate *tls.Certificate `json:"-"` // WebhookGitLabSecret holds the shared secret for authenticating GitHub webhook events - WebhookGitHubSecret string + WebhookGitHubSecret string `json:"webhookGitHubSecret,omitempty"` // WebhookGitLabSecret holds the shared secret for authenticating GitLab webhook events - WebhookGitLabSecret string + WebhookGitLabSecret string `json:"webhookGitLabSecret,omitempty"` // WebhookBitbucketUUID holds the UUID for authenticating Bitbucket webhook events - WebhookBitbucketUUID string + WebhookBitbucketUUID string `json:"webhookBitbucketUUID,omitempty"` // Secrets holds all secrets in argocd-secret as a map[string]string - Secrets map[string]string + Secrets map[string]string `json:"secrets,omitempty"` } const (