feat: generate and use v1 machine configs
This PR will implement the v1 machine config proposal. This will allow for a streamlined config for talos nodes. Signed-off-by: Spencer Smith <robertspencersmith@gmail.com>
This commit is contained in:
committed by
Spencer Smith
parent
15cfd42168
commit
f85750cdca
@ -24,7 +24,7 @@ import (
|
||||
"github.com/talos-systems/talos/cmd/osctl/cmd/cluster/pkg/node"
|
||||
"github.com/talos-systems/talos/cmd/osctl/pkg/client/config"
|
||||
"github.com/talos-systems/talos/cmd/osctl/pkg/helpers"
|
||||
"github.com/talos-systems/talos/pkg/userdata/generate"
|
||||
"github.com/talos-systems/talos/pkg/userdata/v1/generate"
|
||||
"github.com/talos-systems/talos/pkg/version"
|
||||
)
|
||||
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/talos-systems/talos/pkg/userdata/generate"
|
||||
"github.com/talos-systems/talos/pkg/userdata/v1/generate"
|
||||
)
|
||||
|
||||
// Request represents the set of options available for configuring a node.
|
||||
|
@ -17,11 +17,16 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/talos-systems/talos/cmd/osctl/pkg/client/config"
|
||||
"github.com/talos-systems/talos/cmd/osctl/pkg/helpers"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"github.com/talos-systems/talos/pkg/userdata/generate"
|
||||
udv0 "github.com/talos-systems/talos/pkg/userdata"
|
||||
udgenv0 "github.com/talos-systems/talos/pkg/userdata/generate"
|
||||
"github.com/talos-systems/talos/pkg/userdata/translate"
|
||||
udgenv1 "github.com/talos-systems/talos/pkg/userdata/v1/generate"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var genVersion string
|
||||
|
||||
// configCmd represents the config command.
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
@ -127,57 +132,66 @@ var configGenerateCmd = &cobra.Command{
|
||||
if len(args) != 2 {
|
||||
log.Fatal("expected a cluster name and comma delimited list of IP addresses")
|
||||
}
|
||||
input, err := generate.NewInput(args[0], strings.Split(args[1], ","))
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to generate PKI and tokens: %v", err)
|
||||
switch genVersion {
|
||||
case "v0":
|
||||
genV0Userdata(args)
|
||||
case "v1":
|
||||
genV1Userdata(args)
|
||||
}
|
||||
input.AdditionalSubjectAltNames = additionalSANs
|
||||
|
||||
workingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to fetch current working dir: %v", err)
|
||||
}
|
||||
|
||||
var udType generate.Type
|
||||
for idx, master := range strings.Split(args[1], ",") {
|
||||
input.Index = idx
|
||||
input.IP = net.ParseIP(master)
|
||||
if input.Index == 0 {
|
||||
udType = generate.TypeInit
|
||||
} else {
|
||||
udType = generate.TypeControlPlane
|
||||
}
|
||||
|
||||
if err = writeUserdata(input, udType, "master-"+strconv.Itoa(idx+1)); err != nil {
|
||||
helpers.Fatalf("failed to generate userdata for %s: %v", "master-"+strconv.Itoa(idx+1), err)
|
||||
}
|
||||
fmt.Println("created file", workingDir+"/master-"+strconv.Itoa(idx+1)+".yaml")
|
||||
}
|
||||
input.IP = nil
|
||||
|
||||
if err = writeUserdata(input, generate.TypeJoin, "worker"); err != nil {
|
||||
helpers.Fatalf("failed to generate userdata for %s: %v", "worker", err)
|
||||
}
|
||||
fmt.Println("created file", workingDir+"/worker.yaml")
|
||||
|
||||
data, err := generate.Talosconfig(input)
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to generate talosconfig: %v", err)
|
||||
}
|
||||
if err = ioutil.WriteFile("talosconfig", []byte(data), 0644); err != nil {
|
||||
helpers.Fatalf("%v", err)
|
||||
}
|
||||
fmt.Println("created file", workingDir+"/talosconfig")
|
||||
},
|
||||
}
|
||||
|
||||
func writeUserdata(input *generate.Input, t generate.Type, name string) (err error) {
|
||||
func genV0Userdata(args []string) {
|
||||
input, err := udgenv0.NewInput(args[0], strings.Split(args[1], ","))
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to generate PKI and tokens: %v", err)
|
||||
}
|
||||
input.AdditionalSubjectAltNames = additionalSANs
|
||||
|
||||
workingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to fetch current working dir: %v", err)
|
||||
}
|
||||
|
||||
var udType udgenv0.Type
|
||||
for idx, master := range strings.Split(args[1], ",") {
|
||||
input.Index = idx
|
||||
input.IP = net.ParseIP(master)
|
||||
if input.Index == 0 {
|
||||
udType = udgenv0.TypeInit
|
||||
} else {
|
||||
udType = udgenv0.TypeControlPlane
|
||||
}
|
||||
|
||||
if err = writeV0Userdata(input, udType, "master-"+strconv.Itoa(idx+1)); err != nil {
|
||||
helpers.Fatalf("failed to generate userdata for %s: %v", "master-"+strconv.Itoa(idx+1), err)
|
||||
}
|
||||
fmt.Println("created file", workingDir+"/master-"+strconv.Itoa(idx+1)+".yaml")
|
||||
}
|
||||
input.IP = nil
|
||||
|
||||
if err = writeV0Userdata(input, udgenv0.TypeJoin, "worker"); err != nil {
|
||||
helpers.Fatalf("failed to generate userdata for %s: %v", "worker", err)
|
||||
}
|
||||
fmt.Println("created file", workingDir+"/worker.yaml")
|
||||
|
||||
data, err := udgenv0.Talosconfig(input)
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to generate talosconfig: %v", err)
|
||||
}
|
||||
if err = ioutil.WriteFile("talosconfig", []byte(data), 0644); err != nil {
|
||||
helpers.Fatalf("%v", err)
|
||||
}
|
||||
fmt.Println("created file", workingDir+"/talosconfig")
|
||||
}
|
||||
|
||||
func writeV0Userdata(input *udgenv0.Input, t udgenv0.Type, name string) (err error) {
|
||||
var data string
|
||||
data, err = generate.Userdata(t, input)
|
||||
data, err = udgenv0.Userdata(t, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ud := &userdata.UserData{}
|
||||
ud := &udv0.UserData{}
|
||||
if err = yaml.Unmarshal([]byte(data), ud); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -191,12 +205,84 @@ func writeUserdata(input *generate.Input, t generate.Type, name string) (err err
|
||||
return nil
|
||||
}
|
||||
|
||||
func genV1Userdata(args []string) {
|
||||
input, err := udgenv1.NewInput(args[0], strings.Split(args[1], ","))
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to generate PKI and tokens: %v", err)
|
||||
}
|
||||
input.AdditionalSubjectAltNames = additionalSANs
|
||||
|
||||
workingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to fetch current working dir: %v", err)
|
||||
}
|
||||
|
||||
var udType udgenv1.Type
|
||||
for idx, master := range strings.Split(args[1], ",") {
|
||||
input.Index = idx
|
||||
input.IP = net.ParseIP(master)
|
||||
if input.Index == 0 {
|
||||
udType = udgenv1.TypeInit
|
||||
} else {
|
||||
udType = udgenv1.TypeControlPlane
|
||||
}
|
||||
|
||||
if err = writeV1Userdata(input, udType, "master-"+strconv.Itoa(idx+1)); err != nil {
|
||||
helpers.Fatalf("failed to generate userdata for %s: %v", "master-"+strconv.Itoa(idx+1), err)
|
||||
}
|
||||
fmt.Println("created file", workingDir+"/master-"+strconv.Itoa(idx+1)+".yaml")
|
||||
}
|
||||
input.IP = nil
|
||||
|
||||
if err = writeV1Userdata(input, udgenv1.TypeJoin, "worker"); err != nil {
|
||||
helpers.Fatalf("failed to generate userdata for %s: %v", "worker", err)
|
||||
}
|
||||
fmt.Println("created file", workingDir+"/worker.yaml")
|
||||
|
||||
data, err := udgenv1.Talosconfig(input)
|
||||
if err != nil {
|
||||
helpers.Fatalf("failed to generate talosconfig: %v", err)
|
||||
}
|
||||
if err = ioutil.WriteFile("talosconfig", []byte(data), 0644); err != nil {
|
||||
helpers.Fatalf("%v", err)
|
||||
}
|
||||
fmt.Println("created file", workingDir+"/talosconfig")
|
||||
}
|
||||
|
||||
func writeV1Userdata(input *udgenv1.Input, t udgenv1.Type, name string) (err error) {
|
||||
var data string
|
||||
data, err = udgenv1.Userdata(t, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trans, err := translate.NewTranslator("v1", data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ud, err := trans.Translate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ud.Validate(); err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
if err = ioutil.WriteFile(strings.ToLower(name)+".yaml", []byte(data), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configContextCmd, configTargetCmd, configAddCmd, configGenerateCmd)
|
||||
configAddCmd.Flags().StringVar(&ca, "ca", "", "the path to the CA certificate")
|
||||
configAddCmd.Flags().StringVar(&crt, "crt", "", "the path to the certificate")
|
||||
configAddCmd.Flags().StringVar(&key, "key", "", "the path to the key")
|
||||
configGenerateCmd.Flags().StringSliceVar(&additionalSANs, "additionalSANs", []string{}, "additional Subject-Alt-Names for the APIServer certificate")
|
||||
configGenerateCmd.Flags().StringVar(&genVersion, "genversion", "v1", "desired machine configs to generate")
|
||||
helpers.Should(configAddCmd.MarkFlagRequired("ca"))
|
||||
helpers.Should(configAddCmd.MarkFlagRequired("crt"))
|
||||
helpers.Should(configAddCmd.MarkFlagRequired("key"))
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
ud "github.com/talos-systems/talos/pkg/userdata"
|
||||
"github.com/talos-systems/talos/pkg/userdata/download"
|
||||
)
|
||||
|
||||
// UserData provides an abstraction to call the appropriate method to
|
||||
@ -15,7 +16,7 @@ import (
|
||||
// TODO: Merge this in to internal/pkg/userdata
|
||||
func UserData(location string) (userData *ud.UserData, err error) {
|
||||
if strings.HasPrefix(location, "http") {
|
||||
userData, err = ud.Download(location, nil)
|
||||
userData, err = download.Download(location, nil)
|
||||
} else {
|
||||
userData, err = ud.Open(location)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/fullsailor/pkcs7"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"github.com/talos-systems/talos/pkg/userdata/download"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -118,7 +119,7 @@ func (a *AWS) Name() string {
|
||||
|
||||
// UserData implements the platform.Platform interface.
|
||||
func (a *AWS) UserData() (*userdata.UserData, error) {
|
||||
return userdata.Download(AWSUserDataEndpoint)
|
||||
return download.Download(AWSUserDataEndpoint)
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"github.com/talos-systems/talos/pkg/userdata/download"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -39,7 +40,7 @@ func (a *Azure) UserData() (*userdata.UserData, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return userdata.Download(AzureUserDataEndpoint, userdata.WithHeaders(map[string]string{"Metadata": "true"}), userdata.WithFormat("base64"))
|
||||
return download.Download(AzureUserDataEndpoint, download.WithHeaders(map[string]string{"Metadata": "true"}), download.WithFormat("base64"))
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
|
@ -11,8 +11,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"github.com/talos-systems/talos/pkg/userdata/translate"
|
||||
)
|
||||
|
||||
// Container is a platform for installing Talos via an Container image.
|
||||
@ -33,11 +32,14 @@ func (c *Container) UserData() (data *userdata.UserData, err error) {
|
||||
if decoded, err = base64.StdEncoding.DecodeString(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = &userdata.UserData{}
|
||||
if err = yaml.Unmarshal(decoded, data); err != nil {
|
||||
trans, err := translate.NewTranslator("v1", string(decoded))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err = trans.Translate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ package gcp
|
||||
import (
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"github.com/talos-systems/talos/pkg/userdata/download"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -24,7 +25,7 @@ func (gc *GCP) Name() string {
|
||||
|
||||
// UserData implements the platform.Platform interface.
|
||||
func (gc *GCP) UserData() (data *userdata.UserData, err error) {
|
||||
return userdata.Download(GCUserDataEndpoint, userdata.WithHeaders(map[string]string{"Metadata-Flavor": "Google"}))
|
||||
return download.Download(GCUserDataEndpoint, download.WithHeaders(map[string]string{"Metadata-Flavor": "Google"}))
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/talos-systems/talos/pkg/blockdevice/probe"
|
||||
"github.com/talos-systems/talos/pkg/constants"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"github.com/talos-systems/talos/pkg/userdata/download"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
@ -67,7 +68,7 @@ func (b *Metal) UserData() (data *userdata.UserData, err error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
return userdata.Download(*option)
|
||||
return download.Download(*option)
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
|
@ -7,6 +7,7 @@ package packet
|
||||
import (
|
||||
"github.com/talos-systems/talos/internal/app/machined/internal/runtime"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"github.com/talos-systems/talos/pkg/userdata/download"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -24,7 +25,7 @@ func (p *Packet) Name() string {
|
||||
|
||||
// UserData implements the platform.Platform interface.
|
||||
func (p *Packet) UserData() (data *userdata.UserData, err error) {
|
||||
return userdata.Download(PacketUserDataEndpoint)
|
||||
return download.Download(PacketUserDataEndpoint)
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
|
@ -2,7 +2,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package userdata
|
||||
package download
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
@ -14,6 +14,8 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
"github.com/talos-systems/talos/pkg/userdata/translate"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@ -26,6 +28,10 @@ type downloadOptions struct {
|
||||
Wait float64
|
||||
}
|
||||
|
||||
type version struct {
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
// Option configures the download options
|
||||
type Option func(*downloadOptions)
|
||||
|
||||
@ -76,7 +82,7 @@ func WithMaxWait(wait float64) Option {
|
||||
|
||||
// Download initializes a UserData struct from a remote URL.
|
||||
// nolint: gocyclo
|
||||
func Download(udURL string, opts ...Option) (data *UserData, err error) {
|
||||
func Download(udURL string, opts ...Option) (data *userdata.UserData, err error) {
|
||||
u, err := url.Parse(udURL)
|
||||
if err != nil {
|
||||
return data, err
|
||||
@ -98,6 +104,7 @@ func Download(udURL string, opts ...Option) (data *UserData, err error) {
|
||||
|
||||
var dataBytes []byte
|
||||
for attempt := 0; attempt < dlOpts.Retries; attempt++ {
|
||||
|
||||
dataBytes, err = download(req)
|
||||
if err != nil {
|
||||
log.Printf("download failed: %+v", err)
|
||||
@ -117,11 +124,30 @@ func Download(udURL string, opts ...Option) (data *UserData, err error) {
|
||||
dataBytes = baseBytes
|
||||
}
|
||||
|
||||
data = &UserData{}
|
||||
version := &version{}
|
||||
if err = yaml.Unmarshal(dataBytes, version); err != nil {
|
||||
return data, fmt.Errorf("failed to parse version: %s", err.Error())
|
||||
}
|
||||
|
||||
data = &userdata.UserData{}
|
||||
if version.Version != "" {
|
||||
trans, err := translate.NewTranslator(version.Version, string(dataBytes))
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
data, err = trans.Translate()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
return data, data.Validate()
|
||||
}
|
||||
|
||||
// No version specified, just unmarshal and return
|
||||
if err := yaml.Unmarshal(dataBytes, data); err != nil {
|
||||
return data, fmt.Errorf("unmarshal user data: %s", err.Error())
|
||||
return data, fmt.Errorf("unmarshal v0 user data: %s", err.Error())
|
||||
}
|
||||
return data, data.Validate()
|
||||
|
||||
}
|
||||
|
||||
return data, fmt.Errorf("failed to download userdata from: %s", u.String())
|
200
pkg/userdata/download/download_test.go
Normal file
200
pkg/userdata/download/download_test.go
Normal file
@ -0,0 +1,200 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package download
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type downloadSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestDownloadSuite(t *testing.T) {
|
||||
suite.Run(t, new(downloadSuite))
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (suite *downloadSuite) TestV0Download() {
|
||||
// Disable logging for test
|
||||
log.SetOutput(ioutil.Discard)
|
||||
ts := testUDServer()
|
||||
defer ts.Close()
|
||||
|
||||
var err error
|
||||
|
||||
// Download plain-text string
|
||||
_, err = Download(ts.URL, WithMaxWait(0.1), WithHeaders(map[string]string{"configVersion": "v0"}))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// Download b64 string
|
||||
_, err = Download(
|
||||
ts.URL,
|
||||
WithFormat(b64),
|
||||
WithRetries(1),
|
||||
WithHeaders(map[string]string{"Metadata": "true", "format": b64, "configVersion": "v0"}),
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
log.SetOutput(os.Stderr)
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (suite *downloadSuite) TestV1Download() {
|
||||
// Disable logging for test
|
||||
log.SetOutput(ioutil.Discard)
|
||||
ts := testUDServer()
|
||||
defer ts.Close()
|
||||
|
||||
var err error
|
||||
|
||||
_, err = Download(ts.URL, WithMaxWait(0.1), WithHeaders(map[string]string{"configVersion": "v1"}))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
_, err = Download(
|
||||
ts.URL,
|
||||
WithFormat(b64),
|
||||
WithRetries(1),
|
||||
WithHeaders(map[string]string{"Metadata": "true", "format": b64, "configVersion": "v1"}),
|
||||
)
|
||||
suite.Require().NoError(err)
|
||||
log.SetOutput(os.Stderr)
|
||||
}
|
||||
|
||||
func testUDServer() *httptest.Server {
|
||||
var count int
|
||||
|
||||
testMap := map[string]string{
|
||||
"v0": testV0Config,
|
||||
"v1": testV1Config,
|
||||
}
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
count++
|
||||
log.Printf("Request %d\n", count)
|
||||
if count < 2 {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if r.Header.Get("format") == b64 {
|
||||
// nolint: errcheck
|
||||
w.Write([]byte(base64.StdEncoding.EncodeToString([]byte(testMap[r.Header.Get("configVersion")]))))
|
||||
} else {
|
||||
// nolint: errcheck
|
||||
w.Write([]byte(testMap[r.Header.Get("configVersion")]))
|
||||
}
|
||||
}))
|
||||
|
||||
return ts
|
||||
}
|
||||
|
||||
// nolint: lll
|
||||
const testV1Config = `version: v1
|
||||
machine:
|
||||
type: init
|
||||
token: 57dn7x.k5jc6dum97cotlqb
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
kubelet: {}
|
||||
network: {}
|
||||
install: {}
|
||||
cluster:
|
||||
controlPlane:
|
||||
ips:
|
||||
- 10.254.0.10
|
||||
clusterName: spencer-test
|
||||
network:
|
||||
dnsDomain: cluster.local
|
||||
podSubnets:
|
||||
- 10.244.0.0/16
|
||||
serviceSubnets:
|
||||
- 10.96.0.0/12
|
||||
token: 4iysc6.t3bsjbrd74v91wpv
|
||||
initToken: 22c11be4-c413-11e9-b8e8-309c23e4bd47
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
apiServer: {}
|
||||
controllerManager: {}
|
||||
scheduler: {}
|
||||
etcd: {}
|
||||
`
|
||||
|
||||
// nolint: lll
|
||||
const testV0Config = `version: ""
|
||||
security:
|
||||
os:
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
identity:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
kubernetes:
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
sa:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
frontproxy:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
etcd:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
networking:
|
||||
os: {}
|
||||
kubernetes: {}
|
||||
services:
|
||||
init:
|
||||
cni: flannel
|
||||
kubeadm:
|
||||
initToken: 528d1ad6-3485-49ad-94cd-0f44a35877ac
|
||||
certificateKey: 'test'
|
||||
configuration: |
|
||||
apiVersion: kubeadm.k8s.io/v1beta2
|
||||
kind: InitConfiguration
|
||||
localAPIEndpoint:
|
||||
bindPort: 6443
|
||||
bootstrapTokens:
|
||||
- token: '1qbsj9.3oz5hsk6grdfp98b'
|
||||
ttl: 0s
|
||||
---
|
||||
apiVersion: kubeadm.k8s.io/v1beta2
|
||||
kind: ClusterConfiguration
|
||||
clusterName: test
|
||||
kubernetesVersion: v1.16.0-alpha.3
|
||||
---
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
mode: ipvs
|
||||
ipvs:
|
||||
scheduler: lc
|
||||
trustd:
|
||||
username: 'test'
|
||||
password: 'test'
|
||||
endpoints: [ "1.2.3.4" ]
|
||||
certSANs: []
|
||||
install:
|
||||
wipe: true
|
||||
force: true
|
||||
boot:
|
||||
force: true
|
||||
device: /dev/sda
|
||||
size: 1024000000
|
||||
ephemeral:
|
||||
force: true
|
||||
device: /dev/sda
|
||||
size: 1024000000
|
||||
`
|
@ -34,7 +34,7 @@ type Kubeadm struct {
|
||||
CertificateKey string `yaml:"certificateKey,omitempty"`
|
||||
IgnorePreflightErrors []string `yaml:"ignorePreflightErrors,omitempty"`
|
||||
Token *token.Token `yaml:"initToken,omitempty"`
|
||||
controlPlane bool
|
||||
ControlPlane bool
|
||||
}
|
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface.
|
||||
@ -112,7 +112,7 @@ func (kdm *Kubeadm) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
return err
|
||||
}
|
||||
kdm.InitConfiguration = cfg
|
||||
kdm.controlPlane = true
|
||||
kdm.ControlPlane = true
|
||||
case kubeadmutil.GroupVersionKindsHasKind(gvks, "JoinConfiguration"):
|
||||
cfg, err := kubeadmutil.UnmarshalFromYamlForCodecs(config, kubeadmv1beta2.SchemeGroupVersion, kubeadmscheme.Codecs)
|
||||
if err != nil {
|
||||
@ -123,7 +123,7 @@ func (kdm *Kubeadm) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
return errors.New("expected JoinConfiguration")
|
||||
}
|
||||
if joinCfg.ControlPlane != nil {
|
||||
kdm.controlPlane = true
|
||||
kdm.ControlPlane = true
|
||||
}
|
||||
kdm.JoinConfiguration = cfg
|
||||
case kubeadmutil.GroupVersionKindsHasKind(gvks, "ClusterConfiguration"):
|
||||
@ -161,7 +161,7 @@ func (kdm *Kubeadm) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
// IsControlPlane indicates if the current kubeadm configuration is a worker
|
||||
// acting as a master.
|
||||
func (kdm *Kubeadm) IsControlPlane() bool {
|
||||
return kdm.controlPlane
|
||||
return kdm.ControlPlane
|
||||
}
|
||||
|
||||
// IsBootstrap indicates if the current kubeadm configuration is a master init
|
||||
|
@ -2,6 +2,8 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Package userdata provides internal representation of machine configs
|
||||
// nolint: dupl
|
||||
package userdata
|
||||
|
||||
import (
|
||||
|
26
pkg/userdata/translate/translate.go
Normal file
26
pkg/userdata/translate/translate.go
Normal file
@ -0,0 +1,26 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package translate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
)
|
||||
|
||||
// Translator is the interface that will be implemented by all future machine config versions
|
||||
type Translator interface {
|
||||
Translate() (*userdata.UserData, error)
|
||||
}
|
||||
|
||||
// NewTranslator returns an instance of the translator depending on version
|
||||
func NewTranslator(apiVersion string, nodeConfig string) (Translator, error) {
|
||||
switch apiVersion {
|
||||
case "v1":
|
||||
return &V1Translator{nodeConfig: nodeConfig}, nil
|
||||
default:
|
||||
return nil, errors.New("unknown translator")
|
||||
}
|
||||
}
|
64
pkg/userdata/translate/translate_test.go
Normal file
64
pkg/userdata/translate/translate_test.go
Normal file
@ -0,0 +1,64 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package translate
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type translatorSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestTranslatorSuite(t *testing.T) {
|
||||
suite.Run(t, new(translatorSuite))
|
||||
}
|
||||
|
||||
func (suite *translatorSuite) TestTranslation() {
|
||||
tv1, err := NewTranslator("v1", testV1Config)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
ud, err := tv1.Translate()
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().Equal(string(ud.Version), "v1")
|
||||
err = ud.Validate()
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
// nolint: lll
|
||||
const testV1Config = `version: v1
|
||||
machine:
|
||||
type: init
|
||||
token: 57dn7x.k5jc6dum97cotlqb
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
kubelet: {}
|
||||
network: {}
|
||||
install: {}
|
||||
cluster:
|
||||
controlPlane:
|
||||
ips:
|
||||
- 10.254.0.10
|
||||
clusterName: spencer-test
|
||||
network:
|
||||
dnsDomain: cluster.local
|
||||
podSubnets:
|
||||
- 10.244.0.0/16
|
||||
serviceSubnets:
|
||||
- 10.96.0.0/12
|
||||
token: 4iysc6.t3bsjbrd74v91wpv
|
||||
initToken: 22c11be4-c413-11e9-b8e8-309c23e4bd47
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
apiServer: {}
|
||||
controllerManager: {}
|
||||
scheduler: {}
|
||||
etcd: {}
|
||||
`
|
312
pkg/userdata/translate/translate_v1.go
Normal file
312
pkg/userdata/translate/translate_v1.go
Normal file
@ -0,0 +1,312 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package translate
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/constants"
|
||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||
"github.com/talos-systems/talos/pkg/userdata"
|
||||
v1 "github.com/talos-systems/talos/pkg/userdata/v1"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kubeproxyconfig "k8s.io/kube-proxy/config/v1alpha1"
|
||||
kubeletconfig "k8s.io/kubelet/config/v1beta1"
|
||||
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
|
||||
)
|
||||
|
||||
// V1Translator holds info about a v1 machine config translation layer
|
||||
type V1Translator struct {
|
||||
nodeConfig string
|
||||
}
|
||||
|
||||
// Translate takes a v1 NodeConfig and translates it to a UserData struct
|
||||
func (tv1 *V1Translator) Translate() (*userdata.UserData, error) {
|
||||
nc := &v1.NodeConfig{}
|
||||
|
||||
err := yaml.Unmarshal([]byte(tv1.nodeConfig), nc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Lay down the absolute minimum for all node types
|
||||
ud := &userdata.UserData{
|
||||
Version: "v1",
|
||||
Security: &userdata.Security{},
|
||||
Services: &userdata.Services{
|
||||
Init: &userdata.Init{
|
||||
CNI: "flannel",
|
||||
},
|
||||
Kubeadm: &userdata.Kubeadm{},
|
||||
Trustd: &userdata.Trustd{
|
||||
Token: nc.Machine.Token,
|
||||
Endpoints: nc.Cluster.ControlPlane.IPs,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if nc.Machine.Install != nil {
|
||||
translateV1Install(nc, ud)
|
||||
}
|
||||
|
||||
switch nc.Machine.Type {
|
||||
case "init":
|
||||
err = translateV1Init(nc, ud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "controlplane":
|
||||
err = translateV1ControlPlane(nc, ud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "worker":
|
||||
translateV1Worker(nc, ud)
|
||||
}
|
||||
return ud, nil
|
||||
}
|
||||
|
||||
func translateV1Install(nc *v1.NodeConfig, ud *userdata.UserData) {
|
||||
|
||||
ud.Install = &userdata.Install{
|
||||
Wipe: nc.Machine.Install.Wipe,
|
||||
Force: nc.Machine.Install.Force,
|
||||
}
|
||||
|
||||
if nc.Machine.Install.Boot != nil {
|
||||
ud.Install.Boot = &userdata.BootDevice{
|
||||
InstallDevice: userdata.InstallDevice{
|
||||
Device: nc.Machine.Install.Boot.InstallDisk.Disk,
|
||||
Size: nc.Machine.Install.Boot.InstallDisk.Size,
|
||||
},
|
||||
Kernel: nc.Machine.Install.Boot.Kernel,
|
||||
Initramfs: nc.Machine.Install.Boot.Initramfs,
|
||||
}
|
||||
}
|
||||
|
||||
if nc.Machine.Install.Ephemeral != nil {
|
||||
ud.Install.Ephemeral = &userdata.InstallDevice{
|
||||
Device: nc.Machine.Install.Ephemeral.Disk,
|
||||
Size: nc.Machine.Install.Ephemeral.Size,
|
||||
}
|
||||
}
|
||||
|
||||
if nc.Machine.Install.ExtraDisks != nil {
|
||||
ud.Install.ExtraDevices = []*userdata.ExtraDevice{}
|
||||
for _, device := range nc.Machine.Install.ExtraDisks {
|
||||
ed := &userdata.ExtraDevice{
|
||||
Device: device.Disk,
|
||||
Partitions: []*userdata.ExtraDevicePartition{},
|
||||
}
|
||||
|
||||
for _, partition := range device.Partitions {
|
||||
partToAppend := &userdata.ExtraDevicePartition{
|
||||
Size: partition.Size,
|
||||
MountPoint: partition.MountPoint,
|
||||
}
|
||||
ed.Partitions = append(ed.Partitions, partToAppend)
|
||||
}
|
||||
ud.Install.ExtraDevices = append(ud.Install.ExtraDevices, ed)
|
||||
}
|
||||
}
|
||||
|
||||
if nc.Machine.Install.ExtraKernelArgs != nil {
|
||||
ud.Install.ExtraKernelArgs = nc.Machine.Install.ExtraKernelArgs
|
||||
}
|
||||
}
|
||||
|
||||
func translateV1Init(nc *v1.NodeConfig, ud *userdata.UserData) error {
|
||||
// Convert and decode certs back to byte slices
|
||||
osCert, err := base64.StdEncoding.DecodeString(nc.Machine.CA.Crt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
osKey, err := base64.StdEncoding.DecodeString(nc.Machine.CA.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubeCert, err := base64.StdEncoding.DecodeString(nc.Cluster.CA.Crt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kubeKey, err := base64.StdEncoding.DecodeString(nc.Cluster.CA.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Inject certs and SANs
|
||||
ud.Security.OS = &userdata.OSSecurity{
|
||||
CA: &x509.PEMEncodedCertificateAndKey{
|
||||
Crt: osCert,
|
||||
Key: osKey,
|
||||
},
|
||||
}
|
||||
ud.Security.Kubernetes = &userdata.KubernetesSecurity{
|
||||
CA: &x509.PEMEncodedCertificateAndKey{
|
||||
Crt: kubeCert,
|
||||
Key: kubeKey,
|
||||
},
|
||||
}
|
||||
|
||||
ud.Services.Trustd.CertSANs = []string{nc.Cluster.ControlPlane.IPs[nc.Cluster.ControlPlane.Index], "127.0.0.1", "::1"}
|
||||
|
||||
ud.Services.Kubeadm.Token = nc.Cluster.InitToken
|
||||
ud.Services.Kubeadm.ControlPlane = true
|
||||
|
||||
kubeadmToken := strings.Split(nc.Cluster.Token, ".")
|
||||
|
||||
// Craft an init kubeadm config
|
||||
initConfig := &kubeadm.InitConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "InitConfiguration",
|
||||
APIVersion: "kubeadm.k8s.io/v1beta2",
|
||||
},
|
||||
BootstrapTokens: []kubeadm.BootstrapToken{
|
||||
{
|
||||
Token: &kubeadm.BootstrapTokenString{
|
||||
ID: kubeadmToken[0],
|
||||
Secret: kubeadmToken[1],
|
||||
},
|
||||
TTL: &metav1.Duration{
|
||||
Duration: time.Duration(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
NodeRegistration: kubeadm.NodeRegistrationOptions{
|
||||
KubeletExtraArgs: nc.Machine.Kubelet.ExtraArgs,
|
||||
},
|
||||
}
|
||||
|
||||
clusterConfig := &kubeadm.ClusterConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "ClusterConfiguration",
|
||||
APIVersion: "kubeadm.k8s.io/v1beta2",
|
||||
},
|
||||
ClusterName: nc.Cluster.ClusterName,
|
||||
KubernetesVersion: constants.KubernetesVersion,
|
||||
ControlPlaneEndpoint: nc.Cluster.ControlPlane.IPs[0] + ":443",
|
||||
Networking: kubeadm.Networking{
|
||||
DNSDomain: nc.Cluster.Network.DNSDomain,
|
||||
PodSubnet: nc.Cluster.Network.PodSubnet[0],
|
||||
ServiceSubnet: nc.Cluster.Network.ServiceSubnet[0],
|
||||
},
|
||||
APIServer: kubeadm.APIServer{
|
||||
ControlPlaneComponent: kubeadm.ControlPlaneComponent{
|
||||
ExtraArgs: nc.Cluster.APIServer.ExtraArgs,
|
||||
},
|
||||
CertSANs: nc.Cluster.APIServer.CertSANs,
|
||||
TimeoutForControlPlane: &metav1.Duration{
|
||||
Duration: time.Duration(0),
|
||||
},
|
||||
},
|
||||
ControllerManager: kubeadm.ControlPlaneComponent{
|
||||
ExtraArgs: nc.Cluster.ControllerManager.ExtraArgs,
|
||||
},
|
||||
Scheduler: kubeadm.ControlPlaneComponent{
|
||||
ExtraArgs: nc.Cluster.Scheduler.ExtraArgs,
|
||||
},
|
||||
}
|
||||
|
||||
kubeletConfig := &kubeletconfig.KubeletConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "KubeletConfiguration",
|
||||
APIVersion: "kubelet.config.k8s.io/v1beta1",
|
||||
},
|
||||
FeatureGates: map[string]bool{
|
||||
"ExperimentalCriticalPodAnnotation": true,
|
||||
},
|
||||
}
|
||||
|
||||
proxyConfig := &kubeproxyconfig.KubeProxyConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "KubeProxyConfiguration",
|
||||
APIVersion: "kubeproxy.config.k8s.io/v1alpha1",
|
||||
},
|
||||
Mode: "ipvs",
|
||||
IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{
|
||||
Scheduler: "lc",
|
||||
},
|
||||
}
|
||||
|
||||
ud.Services.Kubeadm.InitConfiguration = initConfig
|
||||
ud.Services.Kubeadm.ClusterConfiguration = clusterConfig
|
||||
ud.Services.Kubeadm.KubeletConfiguration = kubeletConfig
|
||||
ud.Services.Kubeadm.KubeProxyConfiguration = proxyConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func translateV1ControlPlane(nc *v1.NodeConfig, ud *userdata.UserData) error {
|
||||
// Convert and decode certs back to byte slices
|
||||
osCert, err := base64.StdEncoding.DecodeString(nc.Machine.CA.Crt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
osKey, err := base64.StdEncoding.DecodeString(nc.Machine.CA.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Inject certs and SANs
|
||||
ud.Security.OS = &userdata.OSSecurity{
|
||||
CA: &x509.PEMEncodedCertificateAndKey{
|
||||
Crt: osCert,
|
||||
Key: osKey,
|
||||
},
|
||||
}
|
||||
ud.Services.Trustd.CertSANs = []string{nc.Cluster.ControlPlane.IPs[nc.Cluster.ControlPlane.Index], "127.0.0.1", "::1"}
|
||||
ud.Services.Kubeadm.ControlPlane = true
|
||||
|
||||
// Craft a control plane kubeadm config
|
||||
controlPlaneConfig := &kubeadm.JoinConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "JoinConfiguration",
|
||||
APIVersion: "kubeadm.k8s.io/v1beta2",
|
||||
},
|
||||
ControlPlane: &kubeadm.JoinControlPlane{},
|
||||
Discovery: kubeadm.Discovery{
|
||||
BootstrapToken: &kubeadm.BootstrapTokenDiscovery{
|
||||
Token: nc.Cluster.Token,
|
||||
APIServerEndpoint: nc.Cluster.ControlPlane.IPs[nc.Cluster.ControlPlane.Index-1] + ":6443",
|
||||
UnsafeSkipCAVerification: true,
|
||||
},
|
||||
},
|
||||
NodeRegistration: kubeadm.NodeRegistrationOptions{
|
||||
KubeletExtraArgs: nc.Machine.Kubelet.ExtraArgs,
|
||||
},
|
||||
}
|
||||
|
||||
ud.Services.Kubeadm.JoinConfiguration = controlPlaneConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func translateV1Worker(nc *v1.NodeConfig, ud *userdata.UserData) {
|
||||
//Craft a worker kubeadm config
|
||||
workerConfig := &kubeadm.JoinConfiguration{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "JoinConfiguration",
|
||||
APIVersion: "kubeadm.k8s.io/v1beta2",
|
||||
},
|
||||
Discovery: kubeadm.Discovery{
|
||||
BootstrapToken: &kubeadm.BootstrapTokenDiscovery{
|
||||
Token: nc.Cluster.Token,
|
||||
APIServerEndpoint: nc.Cluster.ControlPlane.IPs[0] + ":443",
|
||||
UnsafeSkipCAVerification: true,
|
||||
},
|
||||
},
|
||||
NodeRegistration: kubeadm.NodeRegistrationOptions{
|
||||
KubeletExtraArgs: nc.Machine.Kubelet.ExtraArgs,
|
||||
},
|
||||
}
|
||||
|
||||
ud.Services.Kubeadm.JoinConfiguration = workerConfig
|
||||
}
|
@ -5,12 +5,6 @@
|
||||
package userdata
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -26,22 +20,6 @@ func TestValidateSuite(t *testing.T) {
|
||||
suite.Run(t, new(validateSuite))
|
||||
}
|
||||
|
||||
func (suite *validateSuite) TestDownloadRetry() {
|
||||
// Disable logging for test
|
||||
log.SetOutput(ioutil.Discard)
|
||||
ts := testUDServer()
|
||||
defer ts.Close()
|
||||
|
||||
var err error
|
||||
|
||||
_, err = Download(ts.URL, WithMaxWait(0.1))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
_, err = Download(ts.URL, WithFormat(b64), WithRetries(1), WithHeaders(map[string]string{"Metadata": "true", "format": b64}))
|
||||
suite.Require().NoError(err)
|
||||
log.SetOutput(os.Stderr)
|
||||
}
|
||||
|
||||
func (suite *validateSuite) TestKubeadmMarshal() {
|
||||
var kubeadm Kubeadm
|
||||
|
||||
@ -56,97 +34,6 @@ func (suite *validateSuite) TestKubeadmMarshal() {
|
||||
assert.Equal(suite.T(), kubeadmConfig, string(out))
|
||||
}
|
||||
|
||||
func testUDServer() *httptest.Server {
|
||||
var count int
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
count++
|
||||
log.Printf("Request %d\n", count)
|
||||
if count < 2 {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if r.Header.Get("format") == b64 {
|
||||
// nolint: errcheck
|
||||
w.Write([]byte(base64.StdEncoding.EncodeToString([]byte(testConfig))))
|
||||
} else {
|
||||
// nolint: errcheck
|
||||
w.Write([]byte(testConfig))
|
||||
}
|
||||
}))
|
||||
|
||||
return ts
|
||||
}
|
||||
|
||||
// nolint: lll
|
||||
const testConfig = `version: "1"
|
||||
security:
|
||||
os:
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
identity:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
kubernetes:
|
||||
ca:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
sa:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
frontproxy:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
etcd:
|
||||
crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
networking:
|
||||
os: {}
|
||||
kubernetes: {}
|
||||
services:
|
||||
init:
|
||||
cni: flannel
|
||||
kubeadm:
|
||||
initToken: 528d1ad6-3485-49ad-94cd-0f44a35877ac
|
||||
certificateKey: 'test'
|
||||
configuration: |
|
||||
apiVersion: kubeadm.k8s.io/v1beta2
|
||||
kind: InitConfiguration
|
||||
localAPIEndpoint:
|
||||
bindPort: 6443
|
||||
bootstrapTokens:
|
||||
- token: '1qbsj9.3oz5hsk6grdfp98b'
|
||||
ttl: 0s
|
||||
---
|
||||
apiVersion: kubeadm.k8s.io/v1beta2
|
||||
kind: ClusterConfiguration
|
||||
clusterName: test
|
||||
kubernetesVersion: v1.16.0-alpha.3
|
||||
---
|
||||
apiVersion: kubeproxy.config.k8s.io/v1alpha1
|
||||
kind: KubeProxyConfiguration
|
||||
mode: ipvs
|
||||
ipvs:
|
||||
scheduler: lc
|
||||
trustd:
|
||||
username: 'test'
|
||||
password: 'test'
|
||||
endpoints: [ "1.2.3.4" ]
|
||||
certSANs: []
|
||||
install:
|
||||
wipe: true
|
||||
force: true
|
||||
boot:
|
||||
force: true
|
||||
device: /dev/sda
|
||||
size: 1024000000
|
||||
ephemeral:
|
||||
force: true
|
||||
device: /dev/sda
|
||||
size: 1024000000
|
||||
`
|
||||
|
||||
// nolint: lll
|
||||
const kubeadmConfig = `configuration: |
|
||||
apiVersion: kubeadm.k8s.io/v1beta2
|
||||
@ -189,4 +76,5 @@ const kubeadmConfig = `configuration: |
|
||||
scheduler: {}
|
||||
certificateKey: test
|
||||
initToken: 528d1ad6-3485-49ad-94cd-0f44a35877ac
|
||||
controlplane: true
|
||||
`
|
||||
|
64
pkg/userdata/v1/cluster_config.go
Normal file
64
pkg/userdata/v1/cluster_config.go
Normal file
@ -0,0 +1,64 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package v1
|
||||
|
||||
import "github.com/talos-systems/talos/pkg/userdata/token"
|
||||
|
||||
// ClusterConfig reperesents the cluster-wide config values
|
||||
type ClusterConfig struct {
|
||||
ControlPlane *ControlPlaneConfig `yaml:"controlPlane"`
|
||||
ClusterName string `yaml:"clusterName,omitempty"`
|
||||
Network *ClusterNetworkConfig `yaml:"network,omitempty"`
|
||||
Token string `yaml:"token,omitempty"`
|
||||
InitToken *token.Token `yaml:"initToken,omitempty"`
|
||||
CA *ClusterCAConfig `yaml:"ca,omitempty"`
|
||||
APIServer *APIServerConfig `yaml:"apiServer,omitempty"`
|
||||
ControllerManager *ControllerManagerConfig `yaml:"controllerManager,omitempty"`
|
||||
Scheduler *SchedulerConfig `yaml:"scheduler,omitempty"`
|
||||
Etcd *EtcdConfig `yaml:"etcd,omitempty"`
|
||||
}
|
||||
|
||||
// ControlPlaneConfig represents control plane config vals
|
||||
type ControlPlaneConfig struct {
|
||||
IPs []string `yaml:"ips"`
|
||||
Index int `yaml:"index,omitempty"`
|
||||
}
|
||||
|
||||
// APIServerConfig represents kube apiserver config vals
|
||||
type APIServerConfig struct {
|
||||
Image string `yaml:"image,omitempty"`
|
||||
ExtraArgs map[string]string `yaml:"extraArgs,omitempty"`
|
||||
CertSANs []string `yaml:"certSANs,omitempty"`
|
||||
}
|
||||
|
||||
// ControllerManagerConfig represents kube controller manager config vals
|
||||
type ControllerManagerConfig struct {
|
||||
Image string `yaml:"image,omitempty"`
|
||||
ExtraArgs map[string]string `yaml:"extraArgs,omitempty"`
|
||||
}
|
||||
|
||||
// SchedulerConfig represents kube scheduler config vals
|
||||
type SchedulerConfig struct {
|
||||
Image string `yaml:"image,omitempty"`
|
||||
ExtraArgs map[string]string `yaml:"extraArgs,omitempty"`
|
||||
}
|
||||
|
||||
// EtcdConfig represents etcd config vals
|
||||
type EtcdConfig struct {
|
||||
Image string `yaml:"image,omitempty"`
|
||||
}
|
||||
|
||||
// ClusterNetworkConfig represents kube networking config vals
|
||||
type ClusterNetworkConfig struct {
|
||||
DNSDomain string `yaml:"dnsDomain"`
|
||||
PodSubnet []string `yaml:"podSubnets"`
|
||||
ServiceSubnet []string `yaml:"serviceSubnets"`
|
||||
}
|
||||
|
||||
// ClusterCAConfig represents kube cert config vals
|
||||
type ClusterCAConfig struct {
|
||||
Crt string `yaml:"crt"`
|
||||
Key string `yaml:"key"`
|
||||
}
|
40
pkg/userdata/v1/errors.go
Normal file
40
pkg/userdata/v1/errors.go
Normal file
@ -0,0 +1,40 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Package v1 provides user-facing v1 machine configs
|
||||
// nolint: dupl
|
||||
package v1
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// General
|
||||
|
||||
// ErrRequiredSection denotes a section is required
|
||||
ErrRequiredSection = errors.New("required userdata section")
|
||||
// ErrInvalidVersion denotes that the config file version is invalid
|
||||
ErrInvalidVersion = errors.New("invalid config version")
|
||||
|
||||
// Security
|
||||
|
||||
// ErrInvalidCert denotes that the certificate specified is invalid
|
||||
ErrInvalidCert = errors.New("certificate is invalid")
|
||||
// ErrInvalidCertType denotes that the certificate type is invalid
|
||||
ErrInvalidCertType = errors.New("certificate type is invalid")
|
||||
|
||||
// Services
|
||||
|
||||
// ErrUnsupportedCNI denotes that the specified CNI is invalid
|
||||
ErrUnsupportedCNI = errors.New("unsupported CNI driver")
|
||||
// ErrInvalidTrustdToken denotes that a trustd token has not been specified
|
||||
ErrInvalidTrustdToken = errors.New("trustd token is invalid")
|
||||
|
||||
// Networking
|
||||
|
||||
// ErrBadAddressing denotes that an incorrect combination of network
|
||||
// address methods have been specified
|
||||
ErrBadAddressing = errors.New("invalid network device addressing method")
|
||||
// ErrInvalidAddress denotes that a bad address was provided
|
||||
ErrInvalidAddress = errors.New("invalid network address")
|
||||
)
|
45
pkg/userdata/v1/generate/controlplane.go
Normal file
45
pkg/userdata/v1/generate/controlplane.go
Normal file
@ -0,0 +1,45 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package generate
|
||||
|
||||
import (
|
||||
v1 "github.com/talos-systems/talos/pkg/userdata/v1"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func controlPlaneUd(in *Input) (string, error) {
|
||||
|
||||
machine := &v1.MachineConfig{
|
||||
Type: "controlplane",
|
||||
Token: in.TrustdInfo.Token,
|
||||
CA: &v1.MachineCAConfig{
|
||||
Crt: in.Certs.OsCert,
|
||||
Key: in.Certs.OsKey,
|
||||
},
|
||||
Kubelet: &v1.KubeletConfig{},
|
||||
Network: &v1.NetworkConfig{},
|
||||
}
|
||||
|
||||
cluster := &v1.ClusterConfig{
|
||||
Token: in.KubeadmTokens.BootstrapToken,
|
||||
ControlPlane: &v1.ControlPlaneConfig{
|
||||
IPs: in.MasterIPs,
|
||||
Index: in.Index,
|
||||
},
|
||||
}
|
||||
|
||||
ud := v1.NodeConfig{
|
||||
Version: "v1",
|
||||
Machine: machine,
|
||||
Cluster: cluster,
|
||||
}
|
||||
|
||||
udMarshal, err := yaml.Marshal(ud)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(udMarshal), nil
|
||||
}
|
367
pkg/userdata/v1/generate/generate.go
Normal file
367
pkg/userdata/v1/generate/generate.go
Normal file
@ -0,0 +1,367 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package generate
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
stdlibx509 "crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/constants"
|
||||
"github.com/talos-systems/talos/pkg/crypto/x509"
|
||||
tnet "github.com/talos-systems/talos/pkg/net"
|
||||
"github.com/talos-systems/talos/pkg/userdata/token"
|
||||
)
|
||||
|
||||
// DefaultIPv4PodNet is the network to be used for kubernetes Pods when using IPv4-based master nodes
|
||||
const DefaultIPv4PodNet = "10.244.0.0/16"
|
||||
|
||||
// DefaultIPv4ServiceNet is the network to be used for kubernetes Services when using IPv4-based master nodes
|
||||
const DefaultIPv4ServiceNet = "10.96.0.0/12"
|
||||
|
||||
// DefaultIPv6PodNet is the network to be used for kubernetes Pods when using IPv6-based master nodes
|
||||
const DefaultIPv6PodNet = "fc00:db8:10::/56"
|
||||
|
||||
// DefaultIPv6ServiceNet is the network to be used for kubernetes Services when using IPv6-based master nodes
|
||||
const DefaultIPv6ServiceNet = "fc00:db8:20::/112"
|
||||
|
||||
// CertStrings holds the string representation of a certificate and key.
|
||||
type CertStrings struct {
|
||||
Crt string
|
||||
Key string
|
||||
}
|
||||
|
||||
// Input holds info about certs, ips, and node type.
|
||||
type Input struct {
|
||||
Certs *Certs
|
||||
MasterIPs []string
|
||||
AdditionalSubjectAltNames []string
|
||||
|
||||
ClusterName string
|
||||
ServiceDomain string
|
||||
PodNet []string
|
||||
ServiceNet []string
|
||||
KubernetesVersion string
|
||||
KubeadmTokens *KubeadmTokens
|
||||
TrustdInfo *TrustdInfo
|
||||
InitToken *token.Token
|
||||
|
||||
//
|
||||
// Runtime variables
|
||||
//
|
||||
|
||||
// Index is the index of the current master
|
||||
Index int
|
||||
|
||||
// IP is the IP address of the current master
|
||||
IP net.IP
|
||||
}
|
||||
|
||||
// Endpoints returns the formatted set of Master IP addresses
|
||||
func (i *Input) Endpoints() (out string) {
|
||||
if i == nil || len(i.MasterIPs) < 1 {
|
||||
panic("cannot Endpoints without any Master IPs")
|
||||
}
|
||||
for index, addr := range i.MasterIPs {
|
||||
if index > 0 {
|
||||
out += ", "
|
||||
}
|
||||
out += fmt.Sprintf(`"%s"`, addr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetControlPlaneEndpoint returns the formatted host:port of the first master node
|
||||
func (i *Input) GetControlPlaneEndpoint(port string) string {
|
||||
|
||||
if i == nil || len(i.MasterIPs) < 1 {
|
||||
panic("cannot GetControlPlaneEndpoint without any Master IPs")
|
||||
}
|
||||
|
||||
// Each master after the first should reference the next-lower master index.
|
||||
// Thus, master-2 references master-1 and master-3 references master-2.
|
||||
refMaster := 0
|
||||
if i.Index > 1 {
|
||||
refMaster = i.Index - 1
|
||||
}
|
||||
|
||||
if port == "" {
|
||||
return tnet.FormatAddress(i.MasterIPs[refMaster])
|
||||
}
|
||||
return net.JoinHostPort(i.MasterIPs[refMaster], port)
|
||||
}
|
||||
|
||||
// GetAPIServerSANs returns the formatted list of Subject Alt Name addresses for the API Server
|
||||
func (i *Input) GetAPIServerSANs() []string {
|
||||
|
||||
var list = []string{"127.0.0.1", "::1"}
|
||||
list = append(list, i.MasterIPs...)
|
||||
list = append(list, i.AdditionalSubjectAltNames...)
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
// Certs holds the base64 encoded keys and certificates.
|
||||
type Certs struct {
|
||||
AdminCert string
|
||||
AdminKey string
|
||||
OsCert string
|
||||
OsKey string
|
||||
K8sCert string
|
||||
K8sKey string
|
||||
}
|
||||
|
||||
// KubeadmTokens holds the senesitve kubeadm data.
|
||||
type KubeadmTokens struct {
|
||||
BootstrapToken string
|
||||
CertKey string
|
||||
}
|
||||
|
||||
// TrustdInfo holds the trustd credentials.
|
||||
type TrustdInfo struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// randBytes returns a random string consisting of the characters in
|
||||
// validBootstrapTokenChars, with the length customized by the parameter
|
||||
func randBytes(length int) (string, error) {
|
||||
|
||||
// validBootstrapTokenChars defines the characters a bootstrap token can consist of
|
||||
const validBootstrapTokenChars = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
// len("0123456789abcdefghijklmnopqrstuvwxyz") = 36 which doesn't evenly divide
|
||||
// the possible values of a byte: 256 mod 36 = 4. Discard any random bytes we
|
||||
// read that are >= 252 so the bytes we evenly divide the character set.
|
||||
const maxByteValue = 252
|
||||
|
||||
var (
|
||||
b byte
|
||||
err error
|
||||
token = make([]byte, length)
|
||||
)
|
||||
|
||||
reader := bufio.NewReaderSize(rand.Reader, length*2)
|
||||
for i := range token {
|
||||
for {
|
||||
if b, err = reader.ReadByte(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if b < maxByteValue {
|
||||
break
|
||||
}
|
||||
}
|
||||
token[i] = validBootstrapTokenChars[int(b)%len(validBootstrapTokenChars)]
|
||||
}
|
||||
|
||||
return string(token), err
|
||||
}
|
||||
|
||||
//genToken will generate a token of the format abc.123 (like kubeadm/trustd), where the length of the first string (before the dot)
|
||||
//and length of the second string (after dot) are specified as inputs
|
||||
func genToken(lenFirst int, lenSecond int) (string, error) {
|
||||
|
||||
var err error
|
||||
var tokenTemp = make([]string, 2)
|
||||
|
||||
tokenTemp[0], err = randBytes(lenFirst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tokenTemp[1], err = randBytes(lenSecond)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenTemp[0] + "." + tokenTemp[1], nil
|
||||
}
|
||||
|
||||
func isIPv6(addrs ...string) bool {
|
||||
for _, a := range addrs {
|
||||
if ip := net.ParseIP(a); ip != nil {
|
||||
if ip.To4() == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewInput generates the sensitive data required to generate all userdata
|
||||
// types.
|
||||
// nolint: dupl,gocyclo
|
||||
func NewInput(clustername string, masterIPs []string) (input *Input, err error) {
|
||||
|
||||
var loopbackIP, podNet, serviceNet string
|
||||
|
||||
if isIPv6(masterIPs...) {
|
||||
loopbackIP = "::1"
|
||||
podNet = DefaultIPv6PodNet
|
||||
serviceNet = DefaultIPv6ServiceNet
|
||||
} else {
|
||||
loopbackIP = "127.0.0.1"
|
||||
podNet = DefaultIPv4PodNet
|
||||
serviceNet = DefaultIPv4ServiceNet
|
||||
}
|
||||
|
||||
//Gen trustd token strings
|
||||
kubeadmBootstrapToken, err := genToken(6, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: Can be dropped
|
||||
//Gen kubeadm cert key
|
||||
kubeadmCertKey, err := randBytes(26)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Gen trustd token strings
|
||||
trustdToken, err := genToken(6, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kubeadmTokens := &KubeadmTokens{
|
||||
BootstrapToken: kubeadmBootstrapToken,
|
||||
CertKey: kubeadmCertKey,
|
||||
}
|
||||
|
||||
trustdInfo := &TrustdInfo{
|
||||
Token: trustdToken,
|
||||
}
|
||||
|
||||
// Generate Kubernetes CA.
|
||||
opts := []x509.Option{x509.RSA(true), x509.Organization("talos-k8s")}
|
||||
k8sCert, err := x509.NewSelfSignedCertificateAuthority(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate Talos CA.
|
||||
opts = []x509.Option{x509.RSA(false), x509.Organization("talos-os")}
|
||||
osCert, err := x509.NewSelfSignedCertificateAuthority(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the init token
|
||||
tok, err := token.NewToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate the admin talosconfig.
|
||||
adminKey, err := x509.NewKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pemBlock, _ := pem.Decode(adminKey.KeyPEM)
|
||||
if pemBlock == nil {
|
||||
return nil, errors.New("failed to decode admin key pem")
|
||||
}
|
||||
adminKeyEC, err := stdlibx509.ParseECPrivateKey(pemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips := []net.IP{net.ParseIP(loopbackIP)}
|
||||
opts = []x509.Option{x509.IPAddresses(ips)}
|
||||
csr, err := x509.NewCertificateSigningRequest(adminKeyEC, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csrPemBlock, _ := pem.Decode(csr.X509CertificateRequestPEM)
|
||||
if csrPemBlock == nil {
|
||||
return nil, errors.New("failed to decode csr pem")
|
||||
}
|
||||
ccsr, err := stdlibx509.ParseCertificateRequest(csrPemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caPemBlock, _ := pem.Decode(osCert.CrtPEM)
|
||||
if caPemBlock == nil {
|
||||
return nil, errors.New("failed to decode ca cert pem")
|
||||
}
|
||||
caCrt, err := stdlibx509.ParseCertificate(caPemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caKeyPemBlock, _ := pem.Decode(osCert.KeyPEM)
|
||||
if caKeyPemBlock == nil {
|
||||
return nil, errors.New("failed to decode ca key pem")
|
||||
}
|
||||
caKey, err := stdlibx509.ParseECPrivateKey(caKeyPemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
adminCrt, err := x509.NewCertificateFromCSR(caCrt, caKey, ccsr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certs := &Certs{
|
||||
AdminCert: base64.StdEncoding.EncodeToString(adminCrt.X509CertificatePEM),
|
||||
AdminKey: base64.StdEncoding.EncodeToString(adminKey.KeyPEM),
|
||||
OsCert: base64.StdEncoding.EncodeToString(osCert.CrtPEM),
|
||||
OsKey: base64.StdEncoding.EncodeToString(osCert.KeyPEM),
|
||||
K8sCert: base64.StdEncoding.EncodeToString(k8sCert.CrtPEM),
|
||||
K8sKey: base64.StdEncoding.EncodeToString(k8sCert.KeyPEM),
|
||||
}
|
||||
|
||||
input = &Input{
|
||||
Certs: certs,
|
||||
MasterIPs: masterIPs,
|
||||
PodNet: []string{podNet},
|
||||
ServiceNet: []string{serviceNet},
|
||||
ServiceDomain: "cluster.local",
|
||||
ClusterName: clustername,
|
||||
KubernetesVersion: constants.KubernetesVersion,
|
||||
KubeadmTokens: kubeadmTokens,
|
||||
TrustdInfo: trustdInfo,
|
||||
InitToken: tok,
|
||||
}
|
||||
|
||||
return input, nil
|
||||
}
|
||||
|
||||
// Type represents a userdata type.
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// TypeInit indicates a userdata type should correspond to the kubeadm
|
||||
// InitConfiguration type.
|
||||
TypeInit Type = iota
|
||||
// TypeControlPlane indicates a userdata type should correspond to the
|
||||
// kubeadm JoinConfiguration type that has the ControlPlane field
|
||||
// defined.
|
||||
TypeControlPlane
|
||||
// TypeJoin indicates a userdata type should correspond to the kubeadm
|
||||
// JoinConfiguration type.
|
||||
TypeJoin
|
||||
)
|
||||
|
||||
// Sring returns the string representation of Type.
|
||||
func (t Type) String() string {
|
||||
return [...]string{"Init", "ControlPlane", "Join"}[t]
|
||||
}
|
||||
|
||||
// Userdata returns the talos userdata for a given node type.
|
||||
func Userdata(t Type, in *Input) (string, error) {
|
||||
switch t {
|
||||
case TypeInit:
|
||||
return initUd(in)
|
||||
case TypeControlPlane:
|
||||
return controlPlaneUd(in)
|
||||
case TypeJoin:
|
||||
return workerUd(in)
|
||||
default:
|
||||
}
|
||||
return "", errors.New("failed to determine userdata type to generate")
|
||||
}
|
64
pkg/userdata/v1/generate/generate_test.go
Normal file
64
pkg/userdata/v1/generate/generate_test.go
Normal file
@ -0,0 +1,64 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package generate_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
v1 "github.com/talos-systems/talos/pkg/userdata/v1"
|
||||
udgenv1 "github.com/talos-systems/talos/pkg/userdata/v1/generate"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
input *udgenv1.Input
|
||||
)
|
||||
|
||||
type GenerateSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestGenerateSuite(t *testing.T) {
|
||||
suite.Run(t, new(GenerateSuite))
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) SetupSuite() {
|
||||
var err error
|
||||
input, err = udgenv1.NewInput("test", []string{"10.0.1.5", "10.0.1.6", "10.0.1.7"})
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateInitSuccess() {
|
||||
input.IP = net.ParseIP("10.0.1.5")
|
||||
dataString, err := udgenv1.Userdata(udgenv1.TypeInit, input)
|
||||
suite.Require().NoError(err)
|
||||
data := &v1.NodeConfig{}
|
||||
err = yaml.Unmarshal([]byte(dataString), data)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateControlPlaneSuccess() {
|
||||
input.IP = net.ParseIP("10.0.1.6")
|
||||
dataString, err := udgenv1.Userdata(udgenv1.TypeControlPlane, input)
|
||||
suite.Require().NoError(err)
|
||||
data := &v1.NodeConfig{}
|
||||
err = yaml.Unmarshal([]byte(dataString), data)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateWorkerSuccess() {
|
||||
dataString, err := udgenv1.Userdata(udgenv1.TypeJoin, input)
|
||||
suite.Require().NoError(err)
|
||||
data := &v1.NodeConfig{}
|
||||
err = yaml.Unmarshal([]byte(dataString), data)
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (suite *GenerateSuite) TestGenerateTalosconfigSuccess() {
|
||||
_, err := udgenv1.Talosconfig(input)
|
||||
suite.Require().NoError(err)
|
||||
}
|
64
pkg/userdata/v1/generate/init.go
Normal file
64
pkg/userdata/v1/generate/init.go
Normal file
@ -0,0 +1,64 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package generate
|
||||
|
||||
import (
|
||||
v1 "github.com/talos-systems/talos/pkg/userdata/v1"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func initUd(in *Input) (string, error) {
|
||||
|
||||
machine := &v1.MachineConfig{
|
||||
Type: "init",
|
||||
Kubelet: &v1.KubeletConfig{},
|
||||
Network: &v1.NetworkConfig{},
|
||||
CA: &v1.MachineCAConfig{
|
||||
Crt: in.Certs.OsCert,
|
||||
Key: in.Certs.OsKey,
|
||||
},
|
||||
Token: in.TrustdInfo.Token,
|
||||
}
|
||||
|
||||
certSANs := in.GetAPIServerSANs()
|
||||
|
||||
cluster := &v1.ClusterConfig{
|
||||
ClusterName: in.ClusterName,
|
||||
ControlPlane: &v1.ControlPlaneConfig{
|
||||
IPs: in.MasterIPs,
|
||||
Index: in.Index,
|
||||
},
|
||||
APIServer: &v1.APIServerConfig{
|
||||
CertSANs: certSANs,
|
||||
},
|
||||
ControllerManager: &v1.ControllerManagerConfig{},
|
||||
Scheduler: &v1.SchedulerConfig{},
|
||||
Etcd: &v1.EtcdConfig{},
|
||||
Network: &v1.ClusterNetworkConfig{
|
||||
DNSDomain: in.ServiceDomain,
|
||||
PodSubnet: in.PodNet,
|
||||
ServiceSubnet: in.ServiceNet,
|
||||
},
|
||||
CA: &v1.ClusterCAConfig{
|
||||
Crt: in.Certs.K8sCert,
|
||||
Key: in.Certs.K8sKey,
|
||||
},
|
||||
Token: in.KubeadmTokens.BootstrapToken,
|
||||
InitToken: in.InitToken,
|
||||
}
|
||||
|
||||
ud := v1.NodeConfig{
|
||||
Version: "v1",
|
||||
Machine: machine,
|
||||
Cluster: cluster,
|
||||
}
|
||||
|
||||
udMarshal, err := yaml.Marshal(ud)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(udMarshal), nil
|
||||
}
|
40
pkg/userdata/v1/generate/join.go
Normal file
40
pkg/userdata/v1/generate/join.go
Normal file
@ -0,0 +1,40 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package generate
|
||||
|
||||
import (
|
||||
v1 "github.com/talos-systems/talos/pkg/userdata/v1"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func workerUd(in *Input) (string, error) {
|
||||
|
||||
machine := &v1.MachineConfig{
|
||||
Type: "worker",
|
||||
Token: in.TrustdInfo.Token,
|
||||
Kubelet: &v1.KubeletConfig{},
|
||||
Network: &v1.NetworkConfig{},
|
||||
}
|
||||
|
||||
cluster := &v1.ClusterConfig{
|
||||
Token: in.KubeadmTokens.BootstrapToken,
|
||||
ControlPlane: &v1.ControlPlaneConfig{
|
||||
IPs: in.MasterIPs,
|
||||
},
|
||||
}
|
||||
|
||||
ud := v1.NodeConfig{
|
||||
Version: "v1",
|
||||
Machine: machine,
|
||||
Cluster: cluster,
|
||||
}
|
||||
|
||||
udMarshal, err := yaml.Marshal(ud)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(udMarshal), nil
|
||||
}
|
41
pkg/userdata/v1/generate/talosconfig.go
Normal file
41
pkg/userdata/v1/generate/talosconfig.go
Normal file
@ -0,0 +1,41 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package generate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
// Talosconfig returns the talos admin Talos config.
|
||||
func Talosconfig(in *Input) (string, error) {
|
||||
return renderTemplate(in, talosconfigTempl)
|
||||
}
|
||||
|
||||
const talosconfigTempl = `context: {{ .ClusterName }}
|
||||
contexts:
|
||||
{{ .ClusterName }}:
|
||||
target: {{ index .MasterIPs 0 }}
|
||||
ca: {{ .Certs.OsCert }}
|
||||
crt: {{ .Certs.AdminCert }}
|
||||
key: {{ .Certs.AdminKey }}
|
||||
`
|
||||
|
||||
// renderTemplate will output a templated string.
|
||||
func renderTemplate(in *Input, udTemplate string) (string, error) {
|
||||
// So we can have a simple add func
|
||||
funcs := template.FuncMap{"add": add}
|
||||
|
||||
templ := template.Must(template.New("udTemplate").Funcs(funcs).Parse(udTemplate))
|
||||
var buf bytes.Buffer
|
||||
if err := templ.Execute(&buf, in); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func add(a, b int) int {
|
||||
return a + b
|
||||
}
|
51
pkg/userdata/v1/install.go
Normal file
51
pkg/userdata/v1/install.go
Normal file
@ -0,0 +1,51 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Package v1 provides user-facing v1 machine configs
|
||||
//nolint: dupl
|
||||
package v1
|
||||
|
||||
// Install represents the installation options for preparing a node.
|
||||
type Install struct {
|
||||
Boot *BootDisk `yaml:"boot,omitempty"`
|
||||
Ephemeral *InstallDisk `yaml:"ephemeral,omitempty"`
|
||||
ExtraDisks []*ExtraDisk `yaml:"extraDisks,omitempty"`
|
||||
ExtraKernelArgs []string `yaml:"extraKernelArgs,omitempty"`
|
||||
Wipe bool `yaml:"wipe"`
|
||||
Force bool `yaml:"force"`
|
||||
}
|
||||
|
||||
// BootDisk represents the install options specific to the boot partition.
|
||||
type BootDisk struct {
|
||||
InstallDisk `yaml:",inline"`
|
||||
|
||||
Kernel string `yaml:"kernel"`
|
||||
Initramfs string `yaml:"initramfs"`
|
||||
}
|
||||
|
||||
// RootDisk represents the install options specific to the root partition.
|
||||
type RootDisk struct {
|
||||
InstallDisk `yaml:",inline"`
|
||||
|
||||
Rootfs string `yaml:"rootfs"`
|
||||
}
|
||||
|
||||
// InstallDisk represents the specific directions for each partition.
|
||||
type InstallDisk struct {
|
||||
Disk string `yaml:"disk,omitempty"`
|
||||
Size uint `yaml:"size,omitempty"`
|
||||
}
|
||||
|
||||
// ExtraDisk represents the options available for partitioning, formatting,
|
||||
// and mounting extra disks.
|
||||
type ExtraDisk struct {
|
||||
Disk string `yaml:"disk,omitempty"`
|
||||
Partitions []*ExtraDiskPartition `yaml:"partitions,omitempty"`
|
||||
}
|
||||
|
||||
// ExtraDiskPartition represents the options for a device partition.
|
||||
type ExtraDiskPartition struct {
|
||||
Size uint `yaml:"size,omitempty"`
|
||||
MountPoint string `yaml:"mountpoint,omitempty"`
|
||||
}
|
33
pkg/userdata/v1/machine_config.go
Normal file
33
pkg/userdata/v1/machine_config.go
Normal file
@ -0,0 +1,33 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package v1
|
||||
|
||||
// MachineConfig reperesents the machine-specific config values
|
||||
type MachineConfig struct {
|
||||
Type string `yaml:"type"`
|
||||
Token string `yaml:"token"`
|
||||
CA *MachineCAConfig `yaml:"ca,omitempty"`
|
||||
Kubelet *KubeletConfig `yaml:"kubelet,omitempty"`
|
||||
Network *NetworkConfig `yaml:"network,omitempty"`
|
||||
Install *Install `yaml:"install,omitempty"`
|
||||
}
|
||||
|
||||
// KubeletConfig reperesents the kubelet config values
|
||||
type KubeletConfig struct {
|
||||
Image string `yaml:"image,omitempty"`
|
||||
ExtraArgs map[string]string `yaml:"extraArgs,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkConfig reperesents the machine's networking config values
|
||||
type NetworkConfig struct {
|
||||
Hostname string `yaml:"hostname,omitempty"`
|
||||
Interfaces []*Device `yaml:"interfaces,omitempty"`
|
||||
}
|
||||
|
||||
// MachineCAConfig reperesents the machine's talos cert config values
|
||||
type MachineCAConfig struct {
|
||||
Crt string `yaml:"crt,omitempty"`
|
||||
Key string `yaml:"key,omitempty"`
|
||||
}
|
124
pkg/userdata/v1/networking.go
Normal file
124
pkg/userdata/v1/networking.go
Normal file
@ -0,0 +1,124 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Package v1 provides user-facing v1 machine configs
|
||||
// nolint: dupl
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Device represents a network interface
|
||||
// nolint: dupl
|
||||
type Device struct {
|
||||
Interface string `yaml:"interface"`
|
||||
CIDR string `yaml:"cidr"`
|
||||
DHCP bool `yaml:"dhcp"`
|
||||
Routes []Route `yaml:"routes"`
|
||||
Bond *Bond `yaml:"bond"`
|
||||
MTU int `yaml:"mtu"`
|
||||
}
|
||||
|
||||
// NetworkDeviceCheck defines the function type for checks
|
||||
// nolint: dupl
|
||||
type NetworkDeviceCheck func(*Device) error
|
||||
|
||||
// Validate triggers the specified validation checks to run
|
||||
// nolint: dupl
|
||||
func (d *Device) Validate(checks ...NetworkDeviceCheck) error {
|
||||
var result *multierror.Error
|
||||
|
||||
for _, check := range checks {
|
||||
result = multierror.Append(result, check(d))
|
||||
}
|
||||
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
// CheckDeviceInterface ensures that the interface has been specified
|
||||
// nolint: dupl
|
||||
func CheckDeviceInterface() NetworkDeviceCheck {
|
||||
return func(d *Device) error {
|
||||
var result *multierror.Error
|
||||
|
||||
if d.Interface == "" {
|
||||
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "networking.os.device.interface", "", ErrRequiredSection))
|
||||
}
|
||||
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
}
|
||||
|
||||
// CheckDeviceAddressing ensures that an appropriate addressing method
|
||||
// has been specified
|
||||
// nolint: dupl
|
||||
func CheckDeviceAddressing() NetworkDeviceCheck {
|
||||
return func(d *Device) error {
|
||||
var result *multierror.Error
|
||||
|
||||
// Test for both dhcp and cidr specified
|
||||
if d.DHCP && d.CIDR != "" {
|
||||
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "networking.os.device", "", ErrBadAddressing))
|
||||
}
|
||||
|
||||
// test for neither dhcp nor cidr specified
|
||||
if !d.DHCP && d.CIDR == "" {
|
||||
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "networking.os.device", "", ErrBadAddressing))
|
||||
}
|
||||
|
||||
// ensure cidr is a valid address
|
||||
if d.CIDR != "" {
|
||||
if _, _, err := net.ParseCIDR(d.CIDR); err != nil {
|
||||
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "networking.os.device.CIDR", "", err))
|
||||
}
|
||||
}
|
||||
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
}
|
||||
|
||||
// CheckDeviceRoutes ensures that the specified routes are valid
|
||||
// nolint: dupl
|
||||
func CheckDeviceRoutes() NetworkDeviceCheck {
|
||||
return func(d *Device) error {
|
||||
var result *multierror.Error
|
||||
|
||||
if len(d.Routes) == 0 {
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
for idx, route := range d.Routes {
|
||||
if _, _, err := net.ParseCIDR(route.Network); err != nil {
|
||||
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "networking.os.device.route["+strconv.Itoa(idx)+"].Network", route.Network, ErrInvalidAddress))
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(route.Gateway); ip == nil {
|
||||
result = multierror.Append(result, xerrors.Errorf("[%s] %q: %w", "networking.os.device.route["+strconv.Itoa(idx)+"].Gateway", route.Gateway, ErrInvalidAddress))
|
||||
}
|
||||
}
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
}
|
||||
|
||||
// Bond contains the various options for configuring a
|
||||
// bonded interface
|
||||
// nolint: dupl
|
||||
type Bond struct {
|
||||
Mode string `yaml:"mode"`
|
||||
HashPolicy string `yaml:"hashpolicy"`
|
||||
LACPRate string `yaml:"lacprate"`
|
||||
Interfaces []string `yaml:"interfaces"`
|
||||
}
|
||||
|
||||
// Route represents a network route
|
||||
// nolint: dupl
|
||||
type Route struct {
|
||||
Network string `yaml:"network"`
|
||||
Gateway string `yaml:"gateway"`
|
||||
}
|
12
pkg/userdata/v1/node_config.go
Normal file
12
pkg/userdata/v1/node_config.go
Normal file
@ -0,0 +1,12 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package v1
|
||||
|
||||
// NodeConfig holds the full representation of the node config
|
||||
type NodeConfig struct {
|
||||
Version string
|
||||
Machine *MachineConfig
|
||||
Cluster *ClusterConfig
|
||||
}
|
Reference in New Issue
Block a user