Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cmd/talosctl/cmd/constants/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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 constants

const (
// ImageFactoryEmptySchematicID is the ID of an empty image factory schematic.
ImageFactoryEmptySchematicID = "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba"

// ImageFactoryURL is the url of the Sidero hosted image factory.
ImageFactoryURL = "https://factory.talos.dev/"
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/netip"
"os"
"slices"
"strconv"
"strings"

"github.com/google/uuid"
Expand All @@ -23,8 +24,10 @@ import (
"github.com/siderolabs/talos/pkg/machinery/config"
"github.com/siderolabs/talos/pkg/machinery/config/bundle"
"github.com/siderolabs/talos/pkg/machinery/config/configpatcher"
"github.com/siderolabs/talos/pkg/machinery/config/container"
"github.com/siderolabs/talos/pkg/machinery/config/generate"
"github.com/siderolabs/talos/pkg/machinery/config/machine"
"github.com/siderolabs/talos/pkg/machinery/config/types/siderolink"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/provision"
Expand Down Expand Up @@ -67,6 +70,7 @@ type Maker[ExtraOps any] struct {
Cidrs []netip.Prefix
InClusterEndpoint string
Endpoints []string
WithOmni bool

ProvisionOps []provision.Option
GenOps []generate.Option
Expand Down Expand Up @@ -101,15 +105,18 @@ func (m *Maker[T]) InitExtra() error {
return err
}

if err := m.extraOptionsProvider.AddExtraGenOps(); err != nil {
return err
}
// skip generating machine config if nodes are to be used with omni
if !m.WithOmni {
if err := m.extraOptionsProvider.AddExtraGenOps(); err != nil {
return err
}

if err := m.extraOptionsProvider.AddExtraProvisionOpts(); err != nil {
return err
if err := m.extraOptionsProvider.AddExtraConfigBundleOpts(); err != nil {
return err
}
}

if err := m.extraOptionsProvider.AddExtraConfigBundleOpts(); err != nil {
if err := m.extraOptionsProvider.AddExtraProvisionOpts(); err != nil {
return err
}

Expand All @@ -125,7 +132,13 @@ func (m *Maker[T]) InitExtra() error {
}

// InitCommon initializes the common fields.
//
//nolint:gocyclo
func (m *Maker[T]) InitCommon() error {
if m.Ops.OmniAPIEndpoint != "" {
m.WithOmni = true
}

if err := m.initVersionContract(); err != nil {
return err
}
Expand All @@ -152,15 +165,18 @@ func (m *Maker[T]) InitCommon() error {
return err
}

if err := m.initGenOps(); err != nil {
return err
}
// skip generating machine config if nodes are to be used with omni
if !m.WithOmni {
if err := m.initGenOps(); err != nil {
return err
}

if err := m.initProvisionOps(); err != nil {
return err
if err := m.initConfigBundleOps(); err != nil {
return err
}
}

if err := m.initConfigBundleOps(); err != nil {
if err := m.initProvisionOps(); err != nil {
return err
}

Expand Down Expand Up @@ -210,6 +226,53 @@ func (m *Maker[T]) initVersionContract() error {
// GetClusterConfigs prepares and returns the cluster create request data. This method is ment to be called after the implemeting maker
// logic has been run.
func (m *Maker[T]) GetClusterConfigs() (clusterops.ClusterConfigs, error) {
var configBundle *bundle.Bundle

if !m.WithOmni {
cfgBundle, err := m.finalizeMachineConfigs()
if err != nil {
return clusterops.ClusterConfigs{}, err
}

configBundle = cfgBundle
} else {
err := m.applyOmniConfigs()
if err != nil {
return clusterops.ClusterConfigs{}, err
}
}

return clusterops.ClusterConfigs{
ClusterRequest: m.ClusterRequest,
ProvisionOptions: m.ProvisionOps,
ConfigBundle: configBundle,
}, nil
}

func (m *Maker[T]) applyOmniConfigs() error {
cfg := siderolink.NewConfigV1Alpha1()

parsedURL, err := ParseOmniAPIUrl(m.Ops.OmniAPIEndpoint)
if err != nil {
return fmt.Errorf("error parsing omni api url: %w", err)
}

cfg.APIUrlConfig.URL = parsedURL

ctr, err := container.New(cfg)
if err != nil {
return err
}

m.ForEachNode(func(i int, node *provision.NodeRequest) {
node.Config = ctr
node.Name = m.Ops.RootOps.ClusterName + "-machine-" + strconv.Itoa(i+1)
})

return nil
}

func (m *Maker[T]) finalizeMachineConfigs() (*bundle.Bundle, error) {
// These options needs to be generated after the implementing maker has made changes to the cluster request.
m.GenOps = slices.Concat(m.GenOps, m.Provisioner.GenOptions(m.ClusterRequest.Network, m.VersionContract))
m.GenOps = slices.Concat(m.GenOps, []generate.Option{generate.WithEndpointList(m.Endpoints)})
Expand All @@ -226,7 +289,7 @@ func (m *Maker[T]) GetClusterConfigs() (clusterops.ClusterConfigs, error) {

configBundle, err := bundle.NewBundle(m.ConfigBundleOps...)
if err != nil {
return clusterops.ClusterConfigs{}, err
return nil, err
}

if m.ClusterRequest.Nodes[0].Type == machine.TypeInit {
Expand All @@ -243,15 +306,15 @@ func (m *Maker[T]) GetClusterConfigs() (clusterops.ClusterConfigs, error) {
if m.Ops.WireguardCIDR != "" {
wireguardConfigBundle, err := helpers.NewWireguardConfigBundle(m.IPs[0], m.Ops.WireguardCIDR, 51111, m.Ops.Controlplanes)
if err != nil {
return clusterops.ClusterConfigs{}, err
return nil, err
}

for i := range m.ClusterRequest.Nodes {
node := &m.ClusterRequest.Nodes[i]

patchedCfg, err := wireguardConfigBundle.PatchConfig(node.IPs[0], node.Config)
if err != nil {
return clusterops.ClusterConfigs{}, err
return nil, err
}

node.Config = patchedCfg
Expand All @@ -260,11 +323,7 @@ func (m *Maker[T]) GetClusterConfigs() (clusterops.ClusterConfigs, error) {

m.ProvisionOps = append(m.ProvisionOps, provision.WithTalosConfig(configBundle.TalosConfig()))

return clusterops.ClusterConfigs{
ClusterRequest: m.ClusterRequest,
ProvisionOptions: m.ProvisionOps,
ConfigBundle: configBundle,
}, nil
return configBundle, nil
}

// ForEachNode iterates over all nodes allowing modification of each node.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ func TestCommonMaker(t *testing.T) {

_, err = m.GetClusterConfigs()
assert.NoError(t, err)

m.Ops.OmniAPIEndpoint = "grpc://10.5.0.1:8090?jointoken=my-token"
err = m.Init()
assert.NoError(t, err)

clusterCfgs, err := m.GetClusterConfigs()
assert.NoError(t, err)

req := clusterCfgs.ClusterRequest
assert.Equal(t, "test-cluster-machine-1", req.Nodes[0].Name)
assert.Equal(t, "test-cluster-machine-2", req.Nodes[1].Name)

cfgBytes, err := req.Nodes[0].Config.Bytes()
assert.NoError(t, err)

assert.Contains(t, string(cfgBytes), "apiVersion: v1alpha1")
assert.Contains(t, string(cfgBytes), "kind: SideroLinkConfig")
assert.Contains(t, string(cfgBytes), "apiUrl: grpc://10.5.0.1:8090?jointoken=my-token")
}

func TestCommonMaker_MachineConfig(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 makers

import (
"fmt"
"net/url"
"strings"
)

// ParseOmniAPIUrl validates and parses the omni api url.
func ParseOmniAPIUrl(urlIn string) (*url.URL, error) {
if !strings.HasPrefix(urlIn, "grpc://") && !strings.HasPrefix(urlIn, "https://") {
return nil, fmt.Errorf("invalid url scheme: must be either 'grpc://' or 'https://'")
}

if !strings.Contains(urlIn, "?jointoken=") {
return nil, fmt.Errorf("invalid url: must contain a jointoken query parameter")
}

url, err := url.Parse(urlIn)
if err != nil {
return nil, err
}

if url.Port() == "" {
return nil, fmt.Errorf("invalid url: must contain a port")
}

return url, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 makers_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/create/clusterops/configmaker/internal/makers"
)

func TestParseOmniAPIUrl(t *testing.T) {
t.Run("valid grpc", func(t *testing.T) {
u, err := makers.ParseOmniAPIUrl("grpc://10.5.0.1:8090?jointoken=abc")
assert.NoError(t, err)

if assert.NotNil(t, u) {
assert.Equal(t, "grpc", u.Scheme)
assert.Equal(t, "10.5.0.1:8090", u.Host)
assert.Equal(t, "abc", u.Query().Get("jointoken"))
}
})

t.Run("valid https", func(t *testing.T) {
u, err := makers.ParseOmniAPIUrl("https://example.com:443?jointoken=token123")
assert.NoError(t, err)

if assert.NotNil(t, u) {
assert.Equal(t, "https", u.Scheme)
assert.Equal(t, "example.com:443", u.Host)
assert.Equal(t, "token123", u.Query().Get("jointoken"))
}
})

t.Run("invalid scheme", func(t *testing.T) {
u, err := makers.ParseOmniAPIUrl("http://10.5.0.1:8090?jointoken=abc")
assert.Error(t, err)
assert.Nil(t, u)
})

t.Run("missing jointoken", func(t *testing.T) {
u, err := makers.ParseOmniAPIUrl("grpc://10.5.0.1:8090")
assert.Error(t, err)
assert.Nil(t, u)
})

t.Run("missing port", func(t *testing.T) {
u, err := makers.ParseOmniAPIUrl("grpc://10.5.0.1?jointoken=abc")
assert.Error(t, err)
assert.Nil(t, u)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,30 @@ func (m *Qemu) InitExtra() error {
m.SideroLinkBuilder = slb
}

m.initEndpoints()

if m.Ops.WithJSONLogs {
m.initJSONLogs()
}

return nil
}

func (m *Qemu) initEndpoints() {
switch {
case m.Ops.ForceEndpoint != "":
// using non-default endpoints, provision additional cert SANs and fix endpoint list
m.Endpoints = []string{m.Ops.ForceEndpoint}
case m.Ops.ForceInitNodeAsEndpoint:
m.Endpoints = []string{m.IPs[0][0].String()}
case m.Endpoints == nil:
// use control plane nodes as endpoints, client-side load-balancing
for i := range m.Ops.Controlplanes {
m.Endpoints = slices.Concat(m.Endpoints, []string{m.IPs[0][i].String()})
}
}
}

// AddExtraGenOps implements ExtraOptionsProvider.
func (m *Qemu) AddExtraGenOps() error {
m.GenOps = slices.Concat(m.GenOps, []generate.Option{generate.WithInstallImage(m.EOps.NodeInstallImage)})
Expand Down Expand Up @@ -134,18 +151,8 @@ func (m *Qemu) AddExtraGenOps() error {
})
}

switch {
case m.Ops.ForceEndpoint != "":
// using non-default endpoints, provision additional cert SANs and fix endpoint list
m.Endpoints = []string{m.Ops.ForceEndpoint}
if m.Ops.ForceEndpoint != "" {
m.GenOps = slices.Concat(m.GenOps, []generate.Option{generate.WithAdditionalSubjectAltNames(m.Endpoints)})
case m.Ops.ForceInitNodeAsEndpoint:
m.Endpoints = []string{m.IPs[0][0].String()}
case m.Endpoints == nil:
// use control plane nodes as endpoints, client-side load-balancing
for i := range m.Ops.Controlplanes {
m.Endpoints = slices.Concat(m.Endpoints, []string{m.IPs[0][i].String()})
}
}

return nil
Expand Down
Loading
Loading