diff --git a/.gitignore b/.gitignore index 2b99b32..6b67c57 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ tmp/ dist/ .vscode vendor/ +*.csv diff --git a/README.md b/README.md index 15078e6..7c74c0d 100644 --- a/README.md +++ b/README.md @@ -1,62 +1 @@ -# Colony Scout - -## Report - -```bash -go build -o colony-scout - -./colony-scout report \ - --validate=k8s,cloud-init \ - --type=server \ # server, node, agent - # --colony-api="http://localhost:8080" \ - --token=123456 \ - --cluster-id=00000000-0000-0000-0000-000000000000 \ - --workflow-id=00000000-0000-0000-0000-000000000000 \ - --hardware-id=00000000-0000-0000-0000-000000000000 \ - --host-ip-port=192.168.0.43:6443 \ - --kubeconfig=~/.kube/config \ - --k3s-token=00000000-0000-0000-0000-000000000000 - -``` - -## Discovery - -```bash -go build -o colony-scout - -./colony-scout discovery \ - # --colony-api="http://localhost:8080" \ - --token=123456 \ - --hardware-id=00000000-0000-0000-0000-000000000000 - -``` - -## Running test - -Validate and install [kwok](https://kwok.sigs.k8s.io/) - -### Run tests in CI - -```bash -make test -``` - -### Run tests locally - -```bash -make start_kwok && make test -``` - -## Development with Docker - -### Build - -```bash -docker compose build -``` - -### Run - -```bash -docker compose up -``` +# Colony diff --git a/cmd/addipmi.go b/cmd/addipmi.go new file mode 100644 index 0000000..4aeec05 --- /dev/null +++ b/cmd/addipmi.go @@ -0,0 +1,151 @@ +package cmd + +import ( + "encoding/base64" + "encoding/csv" + "fmt" + "html/template" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/konstructio/colony/internal/constants" + "github.com/konstructio/colony/internal/exec" + "github.com/konstructio/colony/internal/k8s" + "github.com/konstructio/colony/internal/logger" + "github.com/konstructio/colony/manifests" + "github.com/spf13/cobra" +) + +type IPMIAuth struct { + HardwareID string + IP string + Password string + Username string + InsecureTLS bool +} + +func getAddIPMICommand() *cobra.Command { + var ipmiAuthFile string + + getAddIPMICmd := &cobra.Command{ + Use: "add-ipmi", + Short: "adds an IPMI auth to the cluster", + RunE: func(cmd *cobra.Command, _ []string) error { + log := logger.New(logger.Debug) + + ctx := cmd.Context() + + homeDir, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("error getting user home directory: %w", err) + } + + err = exec.CreateDirIfNotExist(filepath.Join(homeDir, constants.ColonyDir, "ipmi")) + if err != nil { + return fmt.Errorf("error creating directory templates: %w", err) + } + + ipmiEntries, err := parseCSV(ipmiAuthFile) + if err != nil { + return fmt.Errorf("failed to parse csv: %w", err) + } + + fileTypes := []string{"machine", "secret"} + + for _, entry := range ipmiEntries { + log.Infof("found entry for host ip: %q\n", entry.IP) + + for _, t := range fileTypes { + file, err := manifests.IPMI.ReadFile(fmt.Sprintf("ipmi/ipmi-%s.yaml.tmpl", t)) + if err != nil { + return fmt.Errorf("error reading templates file: %w", err) + } + + tmpl, err := template.New("ipmi").Funcs(template.FuncMap{ + "dotsToHyphens": func(s string) string { + return strings.ReplaceAll(s, ".", "-") + }, + "base64Encode": func(s string) string { + return base64.StdEncoding.EncodeToString([]byte(s)) + }, + }).Parse(string(file)) + if err != nil { + return fmt.Errorf("error parsing template: %w", err) + } + + outputFile, err := os.Create(filepath.Join(homeDir, constants.ColonyDir, "ipmi", fmt.Sprintf("%s-%s.yaml", entry.HardwareID, t))) + if err != nil { + return fmt.Errorf("error creating output file: %w", err) + } + defer outputFile.Close() + + err = tmpl.Execute(outputFile, entry) + if err != nil { + return fmt.Errorf("error executing template: %w", err) + } + } + + var templateFiles []string + + files, err := os.ReadDir(filepath.Join(homeDir, constants.ColonyDir, "ipmi")) + if err != nil { + return fmt.Errorf("failed to open directory: %w", err) + } + + for _, file := range files { + content, err := os.ReadFile(filepath.Join(homeDir, constants.ColonyDir, "ipmi", file.Name())) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + templateFiles = append(templateFiles, string(content)) + } + + k8sClient, err := k8s.New(log, filepath.Join(homeDir, constants.ColonyDir, constants.KubeconfigHostPath)) + if err != nil { + return fmt.Errorf("failed to create k8s client: %w", err) + } + if err := k8sClient.ApplyManifests(ctx, templateFiles); err != nil { + return fmt.Errorf("error applying templates: %w", err) + } + } + + return nil + }, + } + getAddIPMICmd.Flags().StringVar(&ipmiAuthFile, "ipmi-auth-file", "", "path to csv file containging IPMI auth data") + + getAddIPMICmd.MarkFlagRequired("ipmi-auth-file") + + return getAddIPMICmd +} + +func parseCSV(filename string) ([]IPMIAuth, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + reader := csv.NewReader(file) + records, err := reader.ReadAll() + if err != nil { + return nil, err + } + + var ipmiEntries []IPMIAuth + for _, record := range records { + enabled, _ := strconv.ParseBool(record[4]) + entry := IPMIAuth{ + HardwareID: record[0], + IP: record[1], + Username: record[2], + Password: record[3], + InsecureTLS: enabled, + } + ipmiEntries = append(ipmiEntries, entry) + } + + return ipmiEntries, nil +} diff --git a/cmd/root.go b/cmd/root.go index 1bac9d3..ba6ea4e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,6 +14,6 @@ func GetRootCommand() *cobra.Command { SilenceErrors: true, // we print the errors ourselves on main } - cmd.AddCommand(getDestroyCommand(), getInitCommand(), getVersionCommand()) + cmd.AddCommand(getDestroyCommand(), getInitCommand(), getAddIPMICommand(), getVersionCommand()) return cmd } diff --git a/manifests/ipmi/ipmi-machine.yaml.tmpl b/manifests/ipmi/ipmi-machine.yaml.tmpl new file mode 100644 index 0000000..973cbab --- /dev/null +++ b/manifests/ipmi/ipmi-machine.yaml.tmpl @@ -0,0 +1,14 @@ +apiVersion: bmc.tinkerbell.org/v1alpha1 +kind: Machine +metadata: + name: "{{ .IP | dotsToHyphens }}-ipmi-auth" + namespace: tink-system + labels: + colony.konstruct.io/hardware-id: "{{ .HardwareID }}" +spec: + connection: + host: "{{ .IP }}" + authSecretRef: + name: "{{ .IP | dotsToHyphens }}-ipmi-auth" + namespace: tink-system + insecureTLS: {{ .InsecureTLS }} diff --git a/manifests/ipmi/ipmi-secret.yaml.tmpl b/manifests/ipmi/ipmi-secret.yaml.tmpl new file mode 100644 index 0000000..fb6adff --- /dev/null +++ b/manifests/ipmi/ipmi-secret.yaml.tmpl @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: "{{ .IP | dotsToHyphens }}-ipmi-auth" + namespace: tink-system + labels: + colony.konstruct.io/hardware-id: "{{ .HardwareID }}" +type: Opaque +data: + username: "{{ .Username | base64Encode }}" + password: "{{ .Password | base64Encode }}" diff --git a/manifests/templates.go b/manifests/templates.go index 0046f0a..a5d62a1 100644 --- a/manifests/templates.go +++ b/manifests/templates.go @@ -10,5 +10,8 @@ var Colony embed.FS //go:embed downloads/*.yaml var Downloads embed.FS +//go:embed ipmi/*.yaml.tmpl +var IPMI embed.FS + //go:embed templates/*.yaml var Templates embed.FS