github.com/opendevstack/tailor@v1.3.5-0.20220119161809-cab064e60a67/cmd/tailor/main.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"runtime/debug"
     8  
     9  	"github.com/alecthomas/kingpin"
    10  	"github.com/opendevstack/tailor/pkg/cli"
    11  	"github.com/opendevstack/tailor/pkg/commands"
    12  )
    13  
    14  var (
    15  	app = kingpin.New(
    16  		"tailor",
    17  		"Tailor - Infrastructure as Code for OpenShift",
    18  	).DefaultEnvars()
    19  	// App-wide flags
    20  	verboseFlag = app.Flag(
    21  		"verbose",
    22  		"Enable verbose output.",
    23  	).Short('v').Bool()
    24  	debugFlag = app.Flag(
    25  		"debug",
    26  		"Enable debug output (implies verbose).",
    27  	).Short('d').Bool()
    28  	nonInteractiveFlag = app.Flag(
    29  		"non-interactive",
    30  		"Disable interactive mode.",
    31  	).Bool()
    32  	ocBinaryFlag = app.Flag(
    33  		"oc-binary",
    34  		"oc binary to use",
    35  	).Default("oc").String()
    36  	fileFlag = app.Flag(
    37  		"file",
    38  		"Tailorfile with flags.",
    39  	).Short('f').Default("Tailorfile").String()
    40  	forceFlag = app.Flag(
    41  		"force",
    42  		"Force to continue despite warning (e.g. deleting all resources).",
    43  	).Bool()
    44  	namespaceFlag = app.Flag(
    45  		"namespace",
    46  		"Namespace (omit to use current)",
    47  	).Short('n').String()
    48  	selectorFlag = app.Flag(
    49  		"selector",
    50  		"Selector (label query) to filter on. When using multiple labels (comma-separated), all need to be present (AND condition).",
    51  	).Short('l').String()
    52  	excludeFlag = app.Flag(
    53  		"exclude",
    54  		"Exclude kinds, names and labels (repeatable or comma-separated)",
    55  	).Short('e').Strings()
    56  	templateDirFlag = app.Flag(
    57  		"template-dir",
    58  		"Path to local templates",
    59  	).Short('t').Default(".").String()
    60  	paramDirFlag = app.Flag(
    61  		"param-dir",
    62  		"Path to parameter files for local templates (defaults to <NAMESPACE> or working directory)",
    63  	).Short('p').Default(".").String()
    64  	publicKeyDirFlag = app.Flag(
    65  		"public-key-dir",
    66  		"Path to public key files",
    67  	).Default(".").String()
    68  	privateKeyFlag = app.Flag(
    69  		"private-key",
    70  		"Path to private key file",
    71  	).Default("private.key").String()
    72  	passphraseFlag = app.Flag(
    73  		"passphrase",
    74  		"Passphrase to unlock key",
    75  	).String()
    76  
    77  	versionCommand = app.Command(
    78  		"version",
    79  		"Show version",
    80  	)
    81  
    82  	diffCommand = app.Command(
    83  		"diff",
    84  		"Show diff between remote and local",
    85  	).Alias("status")
    86  	diffLabelsFlag = diffCommand.Flag(
    87  		"labels",
    88  		"Label to set in all resources for this template.",
    89  	).String()
    90  	diffParamFlag = diffCommand.Flag(
    91  		"param",
    92  		"Specify a key-value pair (eg. -p FOO=BAR) to set/override a parameter value in the template.",
    93  	).Strings()
    94  	diffParamFileFlag = diffCommand.Flag(
    95  		"param-file",
    96  		"File(s) containing template parameter values to set/override in the template.",
    97  	).Strings()
    98  	diffIgnorePathFlag = diffCommand.Flag(
    99  		"ignore-path",
   100  		"DEPRECATED! Use --preserve instead.",
   101  	).PlaceHolder("bc:foobar:/spec/output/to/name").Strings()
   102  	diffPreservePathFlag = diffCommand.Flag(
   103  		"preserve",
   104  		"Path(s) per kind/name for which to preserve current state (e.g. because they are externally modified) in RFC 6901 format.",
   105  	).PlaceHolder("bc:foobar:/spec/output/to/name").Strings()
   106  	diffPreserveImmutableFieldsFlag = diffCommand.Flag(
   107  		"preserve-immutable-fields",
   108  		"Preserve current state of all immutable fields (such as host of a route, or storageClassName of a PVC).",
   109  	).Bool()
   110  	diffIgnoreUnknownParametersFlag = diffCommand.Flag(
   111  		"ignore-unknown-parameters",
   112  		"If true, will not stop processing if a provided parameter does not exist in the template.",
   113  	).Bool()
   114  	diffUpsertOnlyFlag = diffCommand.Flag(
   115  		"upsert-only",
   116  		"Don't delete resource, only create / update.",
   117  	).Short('u').Bool()
   118  	diffAllowRecreateFlag = diffCommand.Flag(
   119  		"allow-recreate",
   120  		"Allow to recreate the whole resource when an immutable field is changed.",
   121  	).Bool()
   122  	diffRevealSecretsFlag = diffCommand.Flag(
   123  		"reveal-secrets",
   124  		"Reveal drift of Secret resources (might show secret values in clear text).",
   125  	).Bool()
   126  	diffResourceArg = diffCommand.Arg(
   127  		"resource", "Remote resource (defaults to all)",
   128  	).String()
   129  
   130  	applyCommand = app.Command(
   131  		"apply",
   132  		"Update remote with local",
   133  	).Alias("update")
   134  	applyLabelsFlag = applyCommand.Flag(
   135  		"labels",
   136  		"Label to set in all resources for this template.",
   137  	).String()
   138  	applyParamFlag = applyCommand.Flag(
   139  		"param",
   140  		"Specify a key-value pair (eg. -p FOO=BAR) to set/override a parameter value in the template.",
   141  	).Strings()
   142  	applyParamFileFlag = applyCommand.Flag(
   143  		"param-file",
   144  		"File(s) containing template parameter values to set/override in the template.",
   145  	).Strings()
   146  	applyIgnorePathFlag = applyCommand.Flag(
   147  		"ignore-path",
   148  		"DEPRECATED! Use --preserve instead.",
   149  	).PlaceHolder("bc:foobar:/spec/output/to/name").Strings()
   150  	applyPreservePathFlag = applyCommand.Flag(
   151  		"preserve",
   152  		"Path(s) per kind for which to preserve current state (e.g. because they are externally modified) in RFC 6901 format.",
   153  	).PlaceHolder("bc:foobar:/spec/output/to/name").Strings()
   154  	applyPreserveImmutableFieldsFlag = applyCommand.Flag(
   155  		"preserve-immutable-fields",
   156  		"Preserve current state of all immutable fields (such as host of a route, or storageClassName of a PVC).",
   157  	).Bool()
   158  	applyIgnoreUnknownParametersFlag = applyCommand.Flag(
   159  		"ignore-unknown-parameters",
   160  		"If true, will not stop processing if a provided parameter does not exist in the template.",
   161  	).Bool()
   162  	applyUpsertOnlyFlag = applyCommand.Flag(
   163  		"upsert-only",
   164  		"Don't delete resource, only create / apply.",
   165  	).Short('u').Bool()
   166  	applyAllowRecreateFlag = applyCommand.Flag(
   167  		"allow-recreate",
   168  		"Allow to recreate the whole resource when an immutable field is changed.",
   169  	).Bool()
   170  	applyRevealSecretsFlag = applyCommand.Flag(
   171  		"reveal-secrets",
   172  		"Reveal drift of Secret resources (might show secret values in clear text).",
   173  	).Bool()
   174  	applyVerifyFlag = applyCommand.Flag(
   175  		"verify",
   176  		"Verify if resources are in sync after changes are applied.",
   177  	).Bool()
   178  	applyResourceArg = applyCommand.Arg(
   179  		"resource", "Remote resource (defaults to all)",
   180  	).String()
   181  
   182  	exportCommand = app.Command(
   183  		"export",
   184  		"Export remote state as template",
   185  	)
   186  	exportWithAnnotationsFlag = exportCommand.Flag(
   187  		"with-annotations",
   188  		"Export annotations as well.",
   189  	).Bool()
   190  	exportWithHardcodedNamespaceFlag = exportCommand.Flag(
   191  		"with-hardcoded-namespace",
   192  		"Keep any occurences of hardcoded namespace instead of replacing with ${TAILOR_NAMESPACE} in template.",
   193  	).Bool()
   194  	exportTrimAnnotationFlag = exportCommand.Flag(
   195  		"trim-annotation",
   196  		"Annotation (prefix) to trim on top of annotations trimmed by default. ",
   197  	).PlaceHolder("template.openshift.io/").Strings()
   198  	exportResourceArg = exportCommand.Arg(
   199  		"resource", "Remote resource (defaults to all)",
   200  	).String()
   201  
   202  	secretsCommand = app.Command(
   203  		"secrets",
   204  		"Work with secrets",
   205  	)
   206  	editCommand = secretsCommand.Command(
   207  		"edit",
   208  		"Edit param file",
   209  	)
   210  	editFileArg = editCommand.Arg(
   211  		"file", "File to edit",
   212  	).Required().String()
   213  
   214  	reEncryptCommand = secretsCommand.Command(
   215  		"re-encrypt",
   216  		"Re-Encrypt param file(s)",
   217  	)
   218  	reEncryptFileArg = reEncryptCommand.Arg(
   219  		"file", "File to re-encrypt",
   220  	).String()
   221  
   222  	revealCommand = secretsCommand.Command(
   223  		"reveal",
   224  		"Show param file contents with revealed secrets",
   225  	)
   226  	revealFileArg = revealCommand.Arg(
   227  		"file", "File to show",
   228  	).Required().String()
   229  
   230  	generateKeyCommand = secretsCommand.Command(
   231  		"generate-key",
   232  		"Generate new keypair",
   233  	)
   234  	generateKeyNameFlag = generateKeyCommand.Flag(
   235  		"name",
   236  		"Name for keypair",
   237  	).String()
   238  	generateKeyEmailArg = generateKeyCommand.Arg(
   239  		"email", "Emil of keypair",
   240  	).Required().String()
   241  )
   242  
   243  func main() {
   244  	log.SetFlags(0)
   245  
   246  	defer func() {
   247  		err := recover()
   248  		if err != nil {
   249  			log.Fatalf("ERROR: An unexpected error occured. Please file a bug on GitHub (https://github.com/opendevstack/tailor/issues/new) with the following stack trace:\n\n%s\n\n%s", err, debug.Stack())
   250  		}
   251  	}()
   252  
   253  	command := kingpin.MustParse(app.Parse(os.Args[1:]))
   254  
   255  	if command == versionCommand.FullCommand() {
   256  		fmt.Println("1.3.4+master")
   257  		return
   258  	}
   259  
   260  	clusterRequired := true
   261  	if command == editCommand.FullCommand() ||
   262  		command == revealCommand.FullCommand() ||
   263  		command == reEncryptCommand.FullCommand() ||
   264  		command == generateKeyCommand.FullCommand() {
   265  		clusterRequired = false
   266  	}
   267  
   268  	globalOptions, err := cli.NewGlobalOptions(
   269  		clusterRequired,
   270  		*fileFlag,
   271  		*verboseFlag,
   272  		*debugFlag,
   273  		*nonInteractiveFlag,
   274  		*ocBinaryFlag,
   275  		*forceFlag,
   276  	)
   277  	if err != nil {
   278  		log.Fatalln("Options could not be processed:", err)
   279  	}
   280  
   281  	switch command {
   282  	case editCommand.FullCommand():
   283  		secretsOptions, err := cli.NewSecretsOptions(
   284  			globalOptions,
   285  			*paramDirFlag,
   286  			*publicKeyDirFlag,
   287  			*privateKeyFlag,
   288  			*passphraseFlag,
   289  		)
   290  		if err != nil {
   291  			log.Fatalln("Options could not be processed:", err)
   292  		}
   293  		err = commands.Edit(secretsOptions, *editFileArg)
   294  		if err != nil {
   295  			log.Fatalf("Failed to edit file: %s.", err)
   296  		}
   297  
   298  	case reEncryptCommand.FullCommand():
   299  		secretsOptions, err := cli.NewSecretsOptions(
   300  			globalOptions,
   301  			*paramDirFlag,
   302  			*publicKeyDirFlag,
   303  			*privateKeyFlag,
   304  			*passphraseFlag,
   305  		)
   306  		if err != nil {
   307  			log.Fatalln("Options could not be processed:", err)
   308  		}
   309  		err = commands.ReEncrypt(secretsOptions, *reEncryptFileArg)
   310  		if err != nil {
   311  			log.Fatalf("Failed to re-encrypt: %s.", err)
   312  		}
   313  
   314  	case revealCommand.FullCommand():
   315  		secretsOptions, err := cli.NewSecretsOptions(
   316  			globalOptions,
   317  			*paramDirFlag,
   318  			*publicKeyDirFlag,
   319  			*privateKeyFlag,
   320  			*passphraseFlag,
   321  		)
   322  		if err != nil {
   323  			log.Fatalln("Options could not be processed:", err)
   324  		}
   325  		err = commands.Reveal(secretsOptions, *revealFileArg)
   326  		if err != nil {
   327  			log.Fatalf("Failed to reveal file: %s.", err)
   328  		}
   329  
   330  	case generateKeyCommand.FullCommand():
   331  		secretsOptions, err := cli.NewSecretsOptions(
   332  			globalOptions,
   333  			*paramDirFlag,
   334  			*publicKeyDirFlag,
   335  			*privateKeyFlag,
   336  			*passphraseFlag,
   337  		)
   338  		if err != nil {
   339  			log.Fatalln("Options could not be processed:", err)
   340  		}
   341  		err = commands.GenerateKey(secretsOptions, *generateKeyEmailArg, *generateKeyNameFlag)
   342  		if err != nil {
   343  			log.Fatalf("Failed to generate keypair: %s.", err)
   344  		}
   345  
   346  	case diffCommand.FullCommand():
   347  		preservePathFlag := *diffPreservePathFlag
   348  		preservePathFlag = append(preservePathFlag, *diffIgnorePathFlag...)
   349  		compareOptions, err := cli.NewCompareOptions(
   350  			globalOptions,
   351  			*namespaceFlag,
   352  			*selectorFlag,
   353  			*excludeFlag,
   354  			*templateDirFlag,
   355  			*paramDirFlag,
   356  			*publicKeyDirFlag,
   357  			*privateKeyFlag,
   358  			*passphraseFlag,
   359  			*diffLabelsFlag,
   360  			*diffParamFlag,
   361  			*diffParamFileFlag,
   362  			preservePathFlag,
   363  			*diffPreserveImmutableFieldsFlag,
   364  			*diffIgnoreUnknownParametersFlag,
   365  			*diffUpsertOnlyFlag,
   366  			*diffAllowRecreateFlag,
   367  			*diffRevealSecretsFlag,
   368  			false, // verification only when changes are applied
   369  			*diffResourceArg,
   370  		)
   371  		if err != nil {
   372  			log.Fatalln("Options could not be processed:", err)
   373  		}
   374  
   375  		driftDectected, err := commands.Diff(compareOptions)
   376  		if err != nil {
   377  			log.Fatalln(err)
   378  		}
   379  		if driftDectected {
   380  			os.Exit(3)
   381  		}
   382  
   383  	case applyCommand.FullCommand():
   384  		preservePathFlag := *applyPreservePathFlag
   385  		preservePathFlag = append(preservePathFlag, *applyIgnorePathFlag...)
   386  		compareOptions, err := cli.NewCompareOptions(
   387  			globalOptions,
   388  			*namespaceFlag,
   389  			*selectorFlag,
   390  			*excludeFlag,
   391  			*templateDirFlag,
   392  			*paramDirFlag,
   393  			*publicKeyDirFlag,
   394  			*privateKeyFlag,
   395  			*passphraseFlag,
   396  			*applyLabelsFlag,
   397  			*applyParamFlag,
   398  			*applyParamFileFlag,
   399  			preservePathFlag,
   400  			*applyPreserveImmutableFieldsFlag,
   401  			*applyIgnoreUnknownParametersFlag,
   402  			*applyUpsertOnlyFlag,
   403  			*applyAllowRecreateFlag,
   404  			*applyRevealSecretsFlag,
   405  			*applyVerifyFlag,
   406  			*applyResourceArg,
   407  		)
   408  		if err != nil {
   409  			log.Fatalln("Options could not be processed:", err)
   410  		}
   411  
   412  		ocClient := cli.NewOcClient(compareOptions.Namespace)
   413  		driftDectected, err := commands.Apply(
   414  			globalOptions.NonInteractive,
   415  			compareOptions,
   416  			ocClient,
   417  			os.Stdin,
   418  		)
   419  		if err != nil {
   420  			log.Fatalln(err)
   421  		}
   422  		if driftDectected {
   423  			os.Exit(3)
   424  		}
   425  
   426  	case exportCommand.FullCommand():
   427  		exportOptions, err := cli.NewExportOptions(
   428  			globalOptions,
   429  			*namespaceFlag,
   430  			*selectorFlag,
   431  			*excludeFlag,
   432  			*templateDirFlag,
   433  			*paramDirFlag,
   434  			*exportWithAnnotationsFlag,
   435  			*exportWithHardcodedNamespaceFlag,
   436  			*exportTrimAnnotationFlag,
   437  			*exportResourceArg,
   438  		)
   439  		if err != nil {
   440  			log.Fatalln("Options could not be processed:", err)
   441  		}
   442  		err = commands.Export(exportOptions)
   443  		if err != nil {
   444  			log.Fatalln(err)
   445  		}
   446  	}
   447  }