Skip to content

Commit

Permalink
Extract unrelated code fixes/features out of k8s native apply work (#196
Browse files Browse the repository at this point in the history
)

### Breaking Changes:
* `localhost` is no longer a valid option for cluster ingress when initializing a zarf cluster. Instead you have to use a `127.0.0.1` or some other local ip found via `ifconfig`

### Fixes:
* No longer depends on 127.0.0.1 local bindings for the registry / gitops service
    * should fix #193 
* Resolve outstanding issues with image hostname swapping and
    * fixes #18
    * fixes #44
    * fixes #194

### Features:
* Adds `before` and `after` script options when defining a `zarf.yaml` with an optional retry flag
* Add symlink to ZarfFile for creating links to places files
* Add template boolean to ZarfFile to allow injection of zarf variables into text files
* Adds a new `zarf tool` command to print out config schema and commit the output to the repo (will need to make a git hook or something later on)
* Changes `zarf destroy` command to run any script that starts with `zarf-clean` instead of only running the k3s-remove script
* Add new ZarfState and `.zarf-state.yaml` for persisting host information from `zarf init` to `zarf package deploy`
* Remove all hard-coded logic for k3s install, now uses only standard zarf component features like everything else
* Add user prompt with host/IP address suggestions for ingress

#### Misc:
* Upgrades k3s from v1.21.2 to v1.21.6
* Adds optional regex filter for when performing RecursiveFileList()
* Adds more description to the components in zarf.yaml
* Renames type ZarfConfig to ZarfPackage in the config pkg
* Handful of general code organizing changes (moving yaml related functions to the `...../utils/yaml.go`, etc.)
* Expose execCommand() with stdout control
* Move traefik to standalone component and drop the internal k3s install of traefik
* Use the airgap tarball of K3s instead of manually listing images
* Cleanup init prompt logic
  • Loading branch information
jeff-mccoy authored Dec 11, 2021
1 parent 2a1a876 commit b2c7a26
Show file tree
Hide file tree
Showing 40 changed files with 1,254 additions and 603 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ build-cli-mac: ## Build the Mac CLI

build-cli: clean build-cli-linux build-cli-mac ## Build the CLI

init-package: ## Create the zarf init package
init-package: ## Create the zarf init package, macos "brew install coreutils" first
$(ZARF_BIN) package create --confirm
mv zarf-init.tar.zst build
cd build && sha256sum -b zarf* > zarf.sha256
Expand Down
File renamed without changes.
36 changes: 36 additions & 0 deletions assets/manifests/traefik/traefik.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
name: traefik
namespace: kube-system
spec:
chart: https://%{KUBERNETES_API}%/static/charts/traefik-9.18.2.tgz
targetNamespace: kube-system
valuesContent: |-
rbac:
enabled: true
ports:
websecure:
tls:
enabled: true
podAnnotations:
prometheus.io/port: "8082"
prometheus.io/scrape: "true"
providers:
kubernetesIngress:
publishedService:
enabled: true
priorityClassName: "system-cluster-critical"
image:
name: "rancher/library-traefik"
tolerations:
- key: "CriticalAddonsOnly"
operator: "Exists"
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
Empty file added assets/misc/empty-file
Empty file.
12 changes: 6 additions & 6 deletions assets/misc/registries.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
mirrors:
registry.dso.mil:
endpoint:
- "https://127.0.0.1"
- "https://###ZARF_TARGET_ENDPOINT###"
registry1.dso.mil:
endpoint:
- "https://127.0.0.1"
- "https://###ZARF_TARGET_ENDPOINT###"
docker.io:
endpoint:
- "https://127.0.0.1"
- "https://###ZARF_TARGET_ENDPOINT###"
registry-1.docker.io:
endpoint:
- "https://127.0.0.1"
- "https://###ZARF_TARGET_ENDPOINT###"
ghcr.io:
endpoint:
- "https://127.0.0.1"
- "https://###ZARF_TARGET_ENDPOINT###"
registry.opensource.zalan.do:
endpoint:
- "https://127.0.0.1"
- "https://###ZARF_TARGET_ENDPOINT###"
File renamed without changes.
14 changes: 13 additions & 1 deletion cli/cmd/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package cmd

import (
"fmt"
"os"
"regexp"

"github.com/defenseunicorns/zarf/cli/config"
"github.com/defenseunicorns/zarf/cli/internal/utils"

"github.com/spf13/cobra"
Expand All @@ -15,7 +18,16 @@ var destroyCmd = &cobra.Command{
Short: "Tear it all down, we'll miss you Zarf...",
Run: func(cmd *cobra.Command, args []string) {
burn()
_, _ = utils.ExecCommand(nil, "/usr/local/bin/k3s-remove.sh")
_ = os.Remove(config.ZarfStatePath)
pattern := regexp.MustCompile(`(?mi)zarf-clean-.+\.sh$`)
scripts := utils.RecursiveFileList("/usr/local/bin", pattern)
// Iterate over al matching zarf-clean scripts and exec them
for _, script := range scripts {
// Run the matched script
_, _ = utils.ExecCommand(true, nil, script)
// Try to remove the script, but ignore any errors
_ = os.Remove(script)
}
burn()
},
}
Expand Down
177 changes: 137 additions & 40 deletions cli/cmd/initialize.go
Original file line number Diff line number Diff line change
@@ -1,79 +1,176 @@
package cmd

import (
"net"
"os"
"path/filepath"

"github.com/defenseunicorns/zarf/cli/internal/k3s"
"github.com/defenseunicorns/zarf/cli/config"
"github.com/defenseunicorns/zarf/cli/internal/packager"

"github.com/defenseunicorns/zarf/cli/internal/pki"
"github.com/defenseunicorns/zarf/cli/internal/utils"

"github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var initOptions = k3s.InstallOptions{}
const invalidHostMessage = "The hostname provided (%v) was not a valid hostname. The hostname can only contain: 'a-z', 'A-Z', '0-9', '-', and '.' characters as defined by RFC-1035. If using localhost, you must use the 127.0.0.1.\n"

var initOptions = packager.InstallOptions{}
var state = config.ZarfState{
Kind: "ZarfState",
}

// initCmd represents the init command
var initCmd = &cobra.Command{
Use: "init",
Short: "Deploys the gitops service or appliance cluster on a clean linux box",
Long: "Flags are only required if running via automation, otherwise the init command will prompt you for your configuration choices",
Run: func(cmd *cobra.Command, args []string) {

if !initOptions.Confirmed {
var confirm bool
prompt := &survey.Confirm{
Message: "⚠️ This will initialize a new Zarf deployment on this machine which will make changes to your filesystem. You should not run zarf init more than once without first running zarf destroy. Do you want to continue?",
}
_ = survey.AskOne(prompt, &confirm)
if !confirm {
// Gracefully exit because they didn't want to play after all :-/
os.Exit(0)
}
}

handleTLSOptions()
k3s.Install(initOptions)
pki.HandlePKI()
packager.Install(&initOptions)
},
}

func handleTLSOptions() {
// Check to see if the certpaths or host entries are set as flags first
if initOptions.PKI.CertPublicPath == "" && initOptions.PKI.Host == "" {
// Check for cert paths provided via automation (both required)
func hasCertPaths() bool {
return state.TLS.CertPrivatePath != "" && state.TLS.CertPublicPath != ""
}

const Generate = 0
// Ask user if they will be importing or generating certs, return true if importing certs
func promptIsImportCerts() bool {
var mode int

var tlsMode int
if hasCertPaths() {
return true
}

// Determine flow for generate or import
modePrompt := &survey.Select{
Message: "Will Zarf be generating a TLS chain or importing an existing ingress cert?",
Options: []string{
"Generate TLS chain with an ephemeral CA",
"Import user-provided cert keypair",
},
if initOptions.Confirmed {
// Assume generate on confirmed without cert paths
return false
}

// Determine flow for generate or import
modePrompt := &survey.Select{
Message: "Will Zarf be generating a TLS chain or importing an existing ingress cert?",
Options: []string{
"Generate TLS chain with an ephemeral CA",
"Import user-provided cert keypair",
},
}
_ = survey.AskOne(modePrompt, &mode)

return mode == 1
}

// Ask user for the public and private key paths to import into the cluster
func promptCertPaths() {
prompt := &survey.Input{
Message: "Enter a file path to the ingress public key",
Suggest: func(toComplete string) []string {
// Give some suggestions to users
files, _ := filepath.Glob(toComplete + "*")
return files
},
}
_ = survey.AskOne(prompt, &state.TLS.CertPublicPath, survey.WithValidator(survey.Required))

prompt.Message = "Enter a file path to the ingress private key"
_ = survey.AskOne(prompt, &state.TLS.CertPrivatePath, survey.WithValidator(survey.Required))
}

// Ask user for the hostname or ip if not provided via automation and validate the input
func promptAndValidateHost() {
if state.TLS.Host == "" {
if initOptions.Confirmed {
// Fail if host is not provided on confirm
logrus.Fatalf(invalidHostMessage, state.TLS.Host)
}
_ = survey.AskOne(modePrompt, &tlsMode)

if tlsMode == Generate {
// Generate mode requires a host entry
prompt := &survey.Input{
Message: "Enter a host DNS entry or IP Address for the cluster ingress",
}
_ = survey.AskOne(prompt, &initOptions.PKI.Host, survey.WithValidator(survey.Required))
} else {
// Import mode requires the public and private key paths
prompt := &survey.Input{
Message: "Enter a file path to the ingress public key",
Suggest: func(toComplete string) []string {
// Give some suggestions to users
files, _ := filepath.Glob(toComplete + "*")
return files
},
}
_ = survey.AskOne(prompt, &initOptions.PKI.CertPublicPath, survey.WithValidator(survey.Required))
// If not provided, always ask for a host entry to avoid having to guess which entry in a cert if provided
prompt := &survey.Input{
Message: "Enter a host DNS entry or IP Address for the cluster ingress. If using localhost, use 127.0.0.1",
Suggest: func(toComplete string) []string {
var suggestions []string
// Create a list of IPs to add to the suggestion box
interfaces, err := net.InterfaceAddrs()
if err == nil {
for _, iface := range interfaces {
// Conver the CIRD to the IP string if valid
ip, _, _ := net.ParseCIDR(iface.String())
if iface.String() != "" {
suggestions = append(suggestions, ip.String())
}
}
}
// Add the localhost hostname as well
hostname, _ := os.Hostname()
if hostname != "" {
suggestions = append(suggestions, hostname)
}

return suggestions
},
}
err := survey.AskOne(prompt, &state.TLS.Host, survey.WithValidator(survey.Required))
if err != nil && err.Error() == os.Interrupt.String() {
// Handle CTRL+C
os.Exit(0)
}
}

prompt.Message = "Enter a file path to the ingress private key"
_ = survey.AskOne(prompt, &initOptions.PKI.CertPrivatePath, survey.WithValidator(survey.Required))
if !utils.ValidHostname(state.TLS.Host) {
// When hitting an invalid hostname...
if initOptions.Confirmed {
// ...if using automation end it all
logrus.Fatalf(invalidHostMessage, state.TLS.Host)
}
// ...otherwise, warn user, reset the field, and cycle the function
logrus.Warnf(invalidHostMessage, state.TLS.Host)
state.TLS.Host = ""
promptAndValidateHost()
}
if !utils.CheckHostName(initOptions.PKI.Host) {
logrus.Fatalf("The hostname provided (%v) was not a valid hostname. The hostname can only contain: 'a-z', 'A-Z', '0-9', '-', and '.' characters.\n", initOptions.PKI.Host)
}

func handleTLSOptions() {

// Get and validate host
promptAndValidateHost()

// Get the cert path if this is an import
if promptIsImportCerts() && !hasCertPaths() {
promptCertPaths()
}

// Persist the config the ZarfState
if err := config.WriteState(state); err != nil {
logrus.Debug(err)
logrus.Fatal("Unable to save the zarf state file.")
}
}

func init() {

rootCmd.AddCommand(initCmd)
initCmd.Flags().BoolVar(&initOptions.Confirmed, "confirm", false, "Confirm the install without prompting")
initCmd.Flags().StringVar(&initOptions.PKI.Host, "host", "", "Specify the host or IP for the gitops service ingress. E.g. host=10.10.10.5 or host=gitops.domain.com")
initCmd.Flags().StringVar(&initOptions.PKI.CertPublicPath, "server-crt", "", "Path to the server public key if not generating unique PKI")
initCmd.Flags().StringVar(&initOptions.PKI.CertPrivatePath, "server-key", "", "Path to the server private key if not generating unique PKI")
initCmd.Flags().StringVar(&state.TLS.Host, "host", "", "Specify the host or IP for the gitops service ingress. E.g. host=10.10.10.5 or host=gitops.domain.com")
initCmd.Flags().StringVar(&state.TLS.CertPublicPath, "server-crt", "", "Path to the server public key if not generating unique PKI")
initCmd.Flags().StringVar(&state.TLS.CertPrivatePath, "server-key", "", "Path to the server private key if not generating unique PKI")
initCmd.Flags().StringVar(&initOptions.Components, "components", "", "Comma-separated list of components to install. Adding this flag will skip the init prompts for which components to install")
}
32 changes: 21 additions & 11 deletions cli/cmd/pki.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package cmd

import (
"github.com/AlecAivazis/survey/v2"
"github.com/defenseunicorns/zarf/cli/config"
"github.com/defenseunicorns/zarf/cli/internal/pki"
"github.com/defenseunicorns/zarf/cli/internal/utils"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var pkiOptions = utils.PKIConfig{}
var tempState config.ZarfState

var pkiCmd = &cobra.Command{
Use: "pki",
Expand All @@ -19,27 +21,35 @@ var pkiRegenerate = &cobra.Command{
Short: "Regenerate the pki certs for the cluster ingress",
Run: func(cmd *cobra.Command, args []string) {
// Prompt for a hostname if it wasn't provided as a command flag
if pkiOptions.Host == "" {
if tempState.TLS.Host == "" {
prompt := &survey.Input{
Message: "Enter a host DNS entry or IP Address for the gitops service ingress",
Message: "Enter a host DNS entry or IP Address for the gitops service ingress. If using localhost, use 127.0.0.1",
}
_ = survey.AskOne(prompt, &pkiOptions.Host, survey.WithValidator(survey.Required))
_ = survey.AskOne(prompt, &tempState.TLS.Host, survey.WithValidator(survey.Required))
}

// Verify the hostname provided is valid
if !utils.CheckHostName(pkiOptions.Host) {
logrus.Fatalf("The hostname provided (%v) was not a valid hostname. The hostname can only contain: 'a-z', 'A-Z', '0-9', '-', and '.' characters.\n", pkiOptions.Host)
if !utils.ValidHostname(tempState.TLS.Host) {
logrus.Fatalf(invalidHostMessage, tempState.TLS.Host)
}

utils.GeneratePKI(pkiOptions)
pki.GeneratePKI()
if err := config.WriteState(state); err != nil {
logrus.Debug(err)
logrus.Fatal("Unable to save the zarf state file.")
}
},
}

var pkiImport = &cobra.Command{
Use: "import",
Short: "Import an existing key pair for the cluster ingress",
Run: func(cmd *cobra.Command, args []string) {
utils.HandlePKI(pkiOptions)
pki.HandlePKI()
if err := config.WriteState(state); err != nil {
logrus.Debug(err)
logrus.Fatal("Unable to save the zarf state file.")
}
},
}

Expand All @@ -48,8 +58,8 @@ func init() {
pkiCmd.AddCommand(pkiRegenerate)
pkiCmd.AddCommand(pkiImport)

pkiRegenerate.Flags().StringVar(&pkiOptions.Host, "host", "", "Specify the host or IP for the gitops service ingress")
pkiRegenerate.Flags().StringVar(&tempState.TLS.Host, "host", "", "Specify the host or IP for the gitops service ingress")

pkiImport.Flags().StringVar(&pkiOptions.CertPublicPath, "server-crt", "", "Path to the server public key if not generating unique PKI")
pkiImport.Flags().StringVar(&pkiOptions.CertPrivatePath, "server-key", "", "Path to the server private key if not generating unique PKI")
pkiImport.Flags().StringVar(&tempState.TLS.CertPublicPath, "server-crt", "", "Path to the server public key if not generating unique PKI")
pkiImport.Flags().StringVar(&tempState.TLS.CertPrivatePath, "server-key", "", "Path to the server private key if not generating unique PKI")
}
Loading

0 comments on commit b2c7a26

Please sign in to comment.