Skip to content

Commit

Permalink
feat: support version contract for Talos config generation
Browse files Browse the repository at this point in the history
This allows to generating current version Talos configs (by default) or
backwards compatible configuration (e.g. for Talos 0.8).

`talosctl gen config` defaults to current version, but explicit version
can be passed to the command via flags.

`talosctl cluster create` defaults to install/container image version,
but that can be overridden. This makes `talosctl cluster create` now
compatible with 0.8.1 images out of the box.

Upgrade tests use contract based on source version in the test.

When used as a library, `VersionContract` can be omitted (defaults to
current version) or passed explicitly. `VersionContract` can be
convienietly parsed from Talos version string or specified as one of the
constants.

Fixes #3130

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
  • Loading branch information
smira authored and talos-bot committed Feb 10, 2021
1 parent f989677 commit daea9d3
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 31 deletions.
25 changes: 25 additions & 0 deletions cmd/talosctl/cmd/mgmt/cluster/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ var (
crashdumpOnFailure bool
skipKubeconfig bool
skipInjectingConfig bool
talosVersion string
)

// createCmd represents the cluster up command.
Expand Down Expand Up @@ -280,6 +281,29 @@ func create(ctx context.Context) (err error) {
genOptions = append(genOptions, generate.WithUserDisks(machineDisks))
}

if talosVersion == "" {
if provisionerName == "docker" {
parts := strings.Split(nodeImage, ":")

talosVersion = parts[len(parts)-1]
} else {
parts := strings.Split(nodeInstallImage, ":")

talosVersion = parts[len(parts)-1]
}
}

if talosVersion != "latest" {
var versionContract *config.VersionContract

versionContract, err = config.ParseContractFromVersion(talosVersion)
if err != nil {
return fmt.Errorf("error parsing Talos version %q: %w", talosVersion, err)
}

genOptions = append(genOptions, generate.WithVersionContract(versionContract))
}

defaultInternalLB, defaultEndpoint := provisioner.GetLoadBalancers(request.Network)

if defaultInternalLB == "" {
Expand Down Expand Up @@ -677,5 +701,6 @@ func init() {
createCmd.Flags().BoolVar(&crashdumpOnFailure, "crashdump", false, "print debug crashdump to stderr when cluster startup fails")
createCmd.Flags().BoolVar(&skipKubeconfig, "skip-kubeconfig", false, "skip merging kubeconfig from the created cluster")
createCmd.Flags().BoolVar(&skipInjectingConfig, "skip-injecting-config", false, "skip injecting config from embedded metadata server, write config files to current directory")
createCmd.Flags().StringVar(&talosVersion, "talos-version", "", "the desired Talos version to generate config for (if not set, defaults to image version)")
Cmd.AddCommand(createCmd)
}
14 changes: 14 additions & 0 deletions cmd/talosctl/cmd/mgmt/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/talos-systems/talos/cmd/talosctl/pkg/mgmt/helpers"
"github.com/talos-systems/talos/pkg/images"
"github.com/talos-systems/talos/pkg/machinery/config"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/bundle"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/generate"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
Expand All @@ -29,6 +30,7 @@ var (
configVersion string
dnsDomain string
kubernetesVersion string
talosVersion string
installDisk string
installImage string
outputDir string
Expand Down Expand Up @@ -127,6 +129,17 @@ func genV1Alpha1Config(args []string) error {
genOptions = append(genOptions, generate.WithRegistryMirror(components[0], components[1]))
}

if talosVersion != "" {
var versionContract *config.VersionContract

versionContract, err = config.ParseContractFromVersion(talosVersion)
if err != nil {
return fmt.Errorf("invalid talos-version: %w", err)
}

genOptions = append(genOptions, generate.WithVersionContract(versionContract))
}

configBundle, err := bundle.NewConfigBundle(
bundle.WithInputOptions(
&bundle.InputOptions{
Expand Down Expand Up @@ -177,6 +190,7 @@ func init() {
genConfigCmd.Flags().StringSliceVar(&additionalSANs, "additional-sans", []string{}, "additional Subject-Alt-Names for the APIServer certificate")
genConfigCmd.Flags().StringVar(&dnsDomain, "dns-domain", "cluster.local", "the dns domain to use for cluster")
genConfigCmd.Flags().StringVar(&configVersion, "version", "v1alpha1", "the desired machine config version to generate")
genConfigCmd.Flags().StringVar(&talosVersion, "talos-version", "", "the desired Talos version to generate config for (backwards compatibility, e.g. v0.8)")
genConfigCmd.Flags().StringVar(&kubernetesVersion, "kubernetes-version", "", "desired kubernetes version to run")
genConfigCmd.Flags().StringVarP(&outputDir, "output-dir", "o", "", "destination to output generated files")
genConfigCmd.Flags().StringSliceVar(&registryMirrors, "registry-mirror", []string{}, "list of registry mirrors to use in format: <registry host>=<mirror URL>")
Expand Down
11 changes: 5 additions & 6 deletions internal/integration/provision/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
talosclient "github.com/talos-systems/talos/pkg/machinery/client"
clientconfig "github.com/talos-systems/talos/pkg/machinery/client/config"
"github.com/talos-systems/talos/pkg/machinery/config"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/bundle"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/generate"
Expand Down Expand Up @@ -62,7 +63,6 @@ type upgradeSpec struct {

UpgradePreserve bool
UpgradeStage bool
UseRSAKeys bool
}

const (
Expand Down Expand Up @@ -106,7 +106,6 @@ func upgradeBetweenTwoLastReleases() upgradeSpec {

MasterNodes: DefaultSettings.MasterNodes,
WorkerNodes: DefaultSettings.WorkerNodes,
UseRSAKeys: true, // ECDSA is supported with Talos >= 0.9
}
}

Expand All @@ -127,7 +126,6 @@ func upgradeStableReleaseToCurrent() upgradeSpec {

MasterNodes: DefaultSettings.MasterNodes,
WorkerNodes: DefaultSettings.WorkerNodes,
UseRSAKeys: true, // ECDSA is supported with Talos >= 0.9
}
}

Expand All @@ -149,7 +147,6 @@ func upgradeSingeNodePreserve() upgradeSpec {
MasterNodes: 1,
WorkerNodes: 0,
UpgradePreserve: true,
UseRSAKeys: true, // ECDSA is supported with Talos >= 0.9
}
}

Expand All @@ -172,7 +169,6 @@ func upgradeSingeNodeStage() upgradeSpec {
WorkerNodes: 0,
UpgradePreserve: true,
UpgradeStage: true,
UseRSAKeys: true, // ECDSA is supported with Talos >= 0.9
}
}

Expand Down Expand Up @@ -319,17 +315,20 @@ func (suite *UpgradeSuite) setupCluster() {
}))
}

versionContract, err := config.ParseContractFromVersion(suite.spec.SourceVersion)
suite.Require().NoError(err)

suite.configBundle, err = bundle.NewConfigBundle(bundle.WithInputOptions(
&bundle.InputOptions{
ClusterName: clusterName,
Endpoint: suite.controlPlaneEndpoint,
KubeVersion: "", // keep empty so that default version is used per Talos version
UseRSAKeys: suite.spec.UseRSAKeys,
GenOptions: append(
genOptions,
generate.WithEndpointList(masterEndpoints),
generate.WithInstallImage(suite.spec.SourceInstallerImage),
generate.WithDNSDomain("cluster.local"),
generate.WithVersionContract(versionContract),
),
}))
suite.Require().NoError(err)
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func Generate(ctx context.Context, in *machine.GenerateConfigurationRequest) (re

switch {
case os.IsNotExist(err):
secrets, err = generate.NewSecretsBundle(clock, false)
secrets, err = generate.NewSecretsBundle(clock)
if err != nil {
return nil, err
}
Expand Down
74 changes: 74 additions & 0 deletions pkg/machinery/config/contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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 config

import (
"fmt"
"regexp"
"strconv"
)

// VersionContract describes Talos version to generate config for.
//
// Config generation only supports backwards compatibility (e.g. Talos 0.9 can generate configs for Talos 0.9 and 0.8).
// Matching version of the machinery package is required to generate configs for the current version of Talos.
//
// Nil value of *VersionContract always describes current version of Talos.
type VersionContract struct {
Major int
Minor int
}

// Well-known Talos version contracts.
var (
TalosVersionCurrent = (*VersionContract)(nil)
TalosVersion0_9 = &VersionContract{0, 9}
TalosVersion0_8 = &VersionContract{0, 8}
)

var versionRegexp = regexp.MustCompile(`^v(\d+)\.(\d+)($|\.)`)

// ParseContractFromVersion parses Talos version into VersionContract.
func ParseContractFromVersion(version string) (*VersionContract, error) {
matches := versionRegexp.FindStringSubmatch(version)
if len(matches) < 3 {
return nil, fmt.Errorf("error parsing version %q", version)
}

var contract VersionContract

contract.Major, _ = strconv.Atoi(matches[1]) //nolint: errcheck
contract.Minor, _ = strconv.Atoi(matches[2]) //nolint: errcheck

return &contract, nil
}

// Greater compares contract to another contract.
func (contract *VersionContract) Greater(other *VersionContract) bool {
if contract == nil {
return other != nil
}

if other == nil {
return false
}

return contract.Major > other.Major || (contract.Major == other.Major && contract.Minor > other.Minor)
}

// SupportsECDSAKeys returns true if version of Talos supports ECDSA keys (vs. RSA keys).
func (contract *VersionContract) SupportsECDSAKeys() bool {
return contract.Greater(TalosVersion0_8)
}

// SupportsAggregatorCA returns true if version of Talos supports AggregatorCA in the config.
func (contract *VersionContract) SupportsAggregatorCA() bool {
return contract.Greater(TalosVersion0_8)
}

// SupportsServiceAccount returns true if version of Talos supports ServiceAccount in the config.
func (contract *VersionContract) SupportsServiceAccount() bool {
return contract.Greater(TalosVersion0_8)
}
59 changes: 59 additions & 0 deletions pkg/machinery/config/contract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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 config_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/talos-systems/talos/pkg/machinery/config"
)

func TestContractGreater(t *testing.T) {
assert.True(t, config.TalosVersion0_9.Greater(config.TalosVersion0_8))
assert.True(t, config.TalosVersionCurrent.Greater(config.TalosVersion0_8))
assert.True(t, config.TalosVersionCurrent.Greater(config.TalosVersion0_9))

assert.False(t, config.TalosVersion0_8.Greater(config.TalosVersion0_9))
assert.False(t, config.TalosVersion0_8.Greater(config.TalosVersion0_8))
assert.False(t, config.TalosVersionCurrent.Greater(config.TalosVersionCurrent))
}

func TestContractParseVersion(t *testing.T) {
contract, err := config.ParseContractFromVersion("v0.8")
assert.NoError(t, err)
assert.Equal(t, config.TalosVersion0_8, contract)

contract, err = config.ParseContractFromVersion("v0.8.1")
assert.NoError(t, err)
assert.Equal(t, config.TalosVersion0_8, contract)

contract, err = config.ParseContractFromVersion("v0.88")
assert.NoError(t, err)
assert.NotEqual(t, config.TalosVersion0_8, contract)

contract, err = config.ParseContractFromVersion("v0.8.3-alpha.4")
assert.NoError(t, err)
assert.Equal(t, config.TalosVersion0_8, contract)
}

func TestContractCurrent(t *testing.T) {
assert.True(t, config.TalosVersionCurrent.SupportsAggregatorCA())
assert.True(t, config.TalosVersionCurrent.SupportsECDSAKeys())
assert.True(t, config.TalosVersionCurrent.SupportsServiceAccount())
}

func TestContract0_9(t *testing.T) {
assert.True(t, config.TalosVersion0_9.SupportsAggregatorCA())
assert.True(t, config.TalosVersion0_9.SupportsECDSAKeys())
assert.True(t, config.TalosVersion0_9.SupportsServiceAccount())
}

func TestContract0_8(t *testing.T) {
assert.False(t, config.TalosVersion0_8.SupportsAggregatorCA())
assert.False(t, config.TalosVersion0_8.SupportsECDSAKeys())
assert.False(t, config.TalosVersion0_8.SupportsServiceAccount())
}
2 changes: 1 addition & 1 deletion pkg/machinery/config/types/v1alpha1/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func NewConfigBundle(opts ...Option) (*v1alpha1.ConfigBundle, error) {
fmt.Println("generating PKI and tokens")
}

secrets, err := generate.NewSecretsBundle(generate.NewClock(), options.InputOptions.UseRSAKeys)
secrets, err := generate.NewSecretsBundle(generate.NewClock(), options.InputOptions.GenOptions...)
if err != nil {
return bundle, err
}
Expand Down
1 change: 0 additions & 1 deletion pkg/machinery/config/types/v1alpha1/bundle/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ type InputOptions struct {
Endpoint string
KubeVersion string
GenOptions []generate.GenOption
UseRSAKeys bool
}

// Options describes generate parameters.
Expand Down
Loading

0 comments on commit daea9d3

Please sign in to comment.