Skip to content

Commit

Permalink
refactor: create abstraction over cluster/instancegroup for building …
Browse files Browse the repository at this point in the history
…assets

This abstraction should let us change the version on an instance group level.
  • Loading branch information
justinsb committed Nov 25, 2024
1 parent 9561ae6 commit c1276c0
Show file tree
Hide file tree
Showing 17 changed files with 220 additions and 76 deletions.
2 changes: 1 addition & 1 deletion nodeup/pkg/model/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func (b *ContainerdBuilder) installContainerd(c *fi.NodeupModelBuilderContext) e
return nil
}

func (b *ContainerdBuilder) buildSystemdService(sv semver.Version) *nodetasks.Service {
func (b *ContainerdBuilder) buildSystemdService(containerdVersion semver.Version) *nodetasks.Service {
// Based on https://github.com/containerd/containerd/blob/master/containerd.service

manifest := &systemd.Manifest{}
Expand Down
30 changes: 14 additions & 16 deletions nodeup/pkg/model/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@ import (

awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/blang/semver/v4"
hcloudmetadata "github.com/hetznercloud/hcloud-go/hcloud/metadata"
"k8s.io/klog/v2"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/model"
"k8s.io/kops/pkg/apis/kops/util"
kopsmodel "k8s.io/kops/pkg/apis/kops/model"
"k8s.io/kops/pkg/apis/nodeup"
"k8s.io/kops/pkg/systemd"
"k8s.io/kops/upup/pkg/fi"
Expand Down Expand Up @@ -74,7 +72,7 @@ type NodeupModelContext struct {
// usesNoneDNS is true if the cluster runs with dns=none (which uses fixed IPs, for example a load balancer, instead of DNS)
usesNoneDNS bool

kubernetesVersion semver.Version
kubernetesVersion *kopsmodel.KubernetesVersion
bootstrapCerts map[string]*nodetasks.BootstrapCert
bootstrapKeypairIDs map[string]string

Expand All @@ -86,11 +84,11 @@ type NodeupModelContext struct {

// Init completes initialization of the object, for example pre-parsing the kubernetes version
func (c *NodeupModelContext) Init() error {
k8sVersion, err := util.ParseKubernetesVersion(c.NodeupConfig.KubernetesVersion)
if err != nil || k8sVersion == nil {
return fmt.Errorf("unable to parse KubernetesVersion %q", c.NodeupConfig.KubernetesVersion)
k8sVersion, err := kopsmodel.ParseKubernetesVersion(c.NodeupConfig.KubernetesVersion)
if err != nil {
return fmt.Errorf("unable to parse KubernetesVersion %q: %w", c.NodeupConfig.KubernetesVersion, err)
}
c.kubernetesVersion = *k8sVersion
c.kubernetesVersion = k8sVersion
c.bootstrapCerts = map[string]*nodetasks.BootstrapCert{}
c.bootstrapKeypairIDs = map[string]string{}

Expand Down Expand Up @@ -303,20 +301,20 @@ func (c *NodeupModelContext) RemapImage(image string) string {
return image
}

// IsKubernetesGTE checks if the version is greater-than-or-equal
// IsKubernetesGTE checks if the kubernetes version is greater-than-or-equal-to version
func (c *NodeupModelContext) IsKubernetesGTE(version string) bool {
if c.kubernetesVersion.Major == 0 {
if c.kubernetesVersion == nil {
klog.Fatalf("kubernetesVersion not set (%s); Init not called", c.kubernetesVersion)
}
return util.IsKubernetesGTE(version, c.kubernetesVersion)
return c.kubernetesVersion.IsGTE(version)
}

// IsKubernetesLT checks if the version is less-than
// IsKubernetesLT checks if the kubernetes version is less-than version
func (c *NodeupModelContext) IsKubernetesLT(version string) bool {
if c.kubernetesVersion.Major == 0 {
if c.kubernetesVersion == nil {
klog.Fatalf("kubernetesVersion not set (%s); Init not called", c.kubernetesVersion)
}
return !c.IsKubernetesGTE(version)
return c.kubernetesVersion.IsLT(version)
}

// UseVolumeMounts is used to check if we have volume mounts enabled as we need to
Expand All @@ -327,11 +325,11 @@ func (c *NodeupModelContext) UseVolumeMounts() bool {

// UseChallengeCallback is true if we should use a callback challenge during node provisioning with kops-controller.
func (c *NodeupModelContext) UseChallengeCallback(cloudProvider kops.CloudProviderID) bool {
return model.UseChallengeCallback(cloudProvider)
return kopsmodel.UseChallengeCallback(cloudProvider)
}

func (c *NodeupModelContext) UseExternalKubeletCredentialProvider() bool {
return model.UseExternalKubeletCredentialProvider(c.kubernetesVersion, c.CloudProvider())
return kopsmodel.UseExternalKubeletCredentialProvider(c.kubernetesVersion, c.CloudProvider())
}

// UsesSecondaryIP checks if the CNI in use attaches secondary interfaces to the host.
Expand Down
4 changes: 2 additions & 2 deletions nodeup/pkg/model/kube_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import (
"testing"

"k8s.io/kops/pkg/apis/kops"
kopsmodel "k8s.io/kops/pkg/apis/kops/model"
"k8s.io/kops/pkg/apis/nodeup"
"k8s.io/kops/pkg/flagbuilder"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/util/pkg/architectures"
"k8s.io/kops/util/pkg/exec"

"github.com/blang/semver/v4"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -70,7 +70,7 @@ func TestKubeProxyBuilder_buildPod(t *testing.T) {
fields{
&NodeupModelContext{
NodeupConfig: nodeupConfig,
kubernetesVersion: semver.Version{Major: 1, Minor: 20},
kubernetesVersion: kopsmodel.MustParseKubernetesVersion("1.20"),
},
},
&v1.Pod{
Expand Down
8 changes: 3 additions & 5 deletions pkg/apis/kops/model/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ limitations under the License.
package model

import (
"github.com/blang/semver/v4"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/util"
)

// UseChallengeCallback is true if we should use a callback challenge during node provisioning with kops-controller.
Expand Down Expand Up @@ -70,12 +68,12 @@ func UseCiliumEtcd(cluster *kops.Cluster) bool {
}

// Configures a Kubelet Credential Provider if Kubernetes is newer than a specific version
func UseExternalKubeletCredentialProvider(k8sVersion semver.Version, cloudProvider kops.CloudProviderID) bool {
func UseExternalKubeletCredentialProvider(k8sVersion *KubernetesVersion, cloudProvider kops.CloudProviderID) bool {
switch cloudProvider {
case kops.CloudProviderGCE:
return util.IsKubernetesGTE("1.29", k8sVersion)
return k8sVersion.IsGTE("1.29")
case kops.CloudProviderAWS:
return util.IsKubernetesGTE("1.27", k8sVersion)
return k8sVersion.IsGTE("1.27")
default:
return false
}
Expand Down
51 changes: 51 additions & 0 deletions pkg/apis/kops/model/instance_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package model

import (
"fmt"

"k8s.io/kops/pkg/apis/kops"
)

// InstanceGroup is a subset of the full Cluster and InstanceGroup functionality,
// that gives us some abstraction over the raw types.
type InstanceGroup interface {
// KubernetesVersion returns the Kubernetes version for the instance group
KubernetesVersion() *KubernetesVersion
// GetCloudProvider returns the cloud provider for the instance group
GetCloudProvider() kops.CloudProviderID

// ClusterSpec returns the cluster spec for the instance group.
// If possible, prefer abstracted methods over accessing this data directly.
ClusterSpec() *kops.ClusterSpec
}

// ForInstanceGroup creates an InstanceGroup model for the given cluster and instance group.
func ForInstanceGroup(cluster *kops.Cluster, ig *kops.InstanceGroup) (InstanceGroup, error) {
kubernetesVersionString := cluster.Spec.KubernetesVersion
kubernetesVersion, err := ParseKubernetesVersion(kubernetesVersionString)
if err != nil {
return nil, fmt.Errorf("error parsing Kubernetes version %q: %v", kubernetesVersionString, err)
}

return &instanceGroupModel{cluster: cluster, ig: ig, kubernetesVersion: kubernetesVersion}, nil
}

type instanceGroupModel struct {
cluster *kops.Cluster
ig *kops.InstanceGroup
kubernetesVersion *KubernetesVersion
}

var _ InstanceGroup = &instanceGroupModel{}

func (m *instanceGroupModel) KubernetesVersion() *KubernetesVersion {
return m.kubernetesVersion
}

func (m *instanceGroupModel) GetCloudProvider() kops.CloudProviderID {
return m.cluster.GetCloudProvider()
}

func (m *instanceGroupModel) ClusterSpec() *kops.ClusterSpec {
return &m.cluster.Spec
}
73 changes: 73 additions & 0 deletions pkg/apis/kops/model/kubernetes_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package model

import (
"fmt"
"strings"

"github.com/blang/semver/v4"
"k8s.io/kops/pkg/apis/kops/util"
)

// KubernetesVersion is a wrapper over semver functionality,
// that offers some functionality particularly useful for kubernetes version semantics.
type KubernetesVersion struct {
versionString string
version semver.Version
}

// ParseKubernetesVersion parses a Kubernetes version string and returns a KubernetesVersion object.
func ParseKubernetesVersion(versionString string) (*KubernetesVersion, error) {
parsedVersion, err := util.ParseKubernetesVersion(versionString)
if err != nil {
return nil, fmt.Errorf("error parsing version %q: %v", versionString, err)
}

return &KubernetesVersion{versionString: versionString, version: *parsedVersion}, nil
}

// MustParseKubernetesVersion parses a Kubernetes version string and panics if it fails.
func MustParseKubernetesVersion(versionString string) *KubernetesVersion {
kubernetesVersion, err := ParseKubernetesVersion(versionString)
if err != nil || kubernetesVersion == nil {
panic(err)
}
return kubernetesVersion
}

func (v *KubernetesVersion) String() string {
return v.versionString
}

// IsBaseURL checks if the version string is a URL, rather than a version identifier.
// URLs are typically used for CI builds and during development.
func IsBaseURL(kubernetesVersion string) bool {
return strings.HasPrefix(kubernetesVersion, "http:") || strings.HasPrefix(kubernetesVersion, "https:") || strings.HasPrefix(kubernetesVersion, "memfs:")
}

// IsBaseURL checks if the version string is a URL, rather than a version identifier.
// URLs are typically used for CI builds and during development.
func (v *KubernetesVersion) IsBaseURL() bool {
return IsBaseURL(v.versionString)
}

// IsGTE checks if the version is greater than or equal (>=) to the specified version.
// It panics if the kubernetes version in the cluster is invalid, or if the version is invalid.
func (v *KubernetesVersion) IsGTE(version string) bool {
parsedVersion, err := util.ParseKubernetesVersion(version)
if err != nil || parsedVersion == nil {
panic(fmt.Sprintf("error parsing version %q: %v", version, err))
}

// Ignore Pre & Build fields
clusterVersion := v.version
clusterVersion.Pre = nil
clusterVersion.Build = nil

return clusterVersion.GTE(*parsedVersion)
}

// IsLT checks if the version is strictly less (<) than the specified version.
// It panics if the kubernetes version in the cluster is invalid, or if the version is invalid.
func (v *KubernetesVersion) IsLT(version string) bool {
return !v.IsGTE(version)
}
3 changes: 1 addition & 2 deletions pkg/apis/kops/util/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ func ParseKubernetesVersion(version string) (*semver.Version, error) {
return &sv, nil
}

// TODO: Convert to our own KubernetesVersion type?

// Deprecated: prefer using KubernetesVersion.IsGTE()
func IsKubernetesGTE(version string, k8sVersion semver.Version) bool {
parsedVersion, err := ParseKubernetesVersion(version)
if err != nil {
Expand Down
7 changes: 2 additions & 5 deletions pkg/model/components/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"

"k8s.io/kops/pkg/apis/kops"
kopsmodel "k8s.io/kops/pkg/apis/kops/model"
"k8s.io/kops/pkg/apis/kops/util"
"k8s.io/kops/pkg/assets"
"k8s.io/kops/pkg/k8sversion"
Expand Down Expand Up @@ -89,10 +90,6 @@ func WellKnownServiceIP(networkingSpec *kops.NetworkingSpec, id int) (net.IP, er
return nil, fmt.Errorf("unexpected IP address type for ServiceClusterIPRange: %s", networkingSpec.ServiceClusterIPRange)
}

func IsBaseURL(kubernetesVersion string) bool {
return strings.HasPrefix(kubernetesVersion, "http:") || strings.HasPrefix(kubernetesVersion, "https:") || strings.HasPrefix(kubernetesVersion, "memfs:")
}

// Image returns the docker image name for the specified component
func Image(component string, clusterSpec *kops.ClusterSpec, assetsBuilder *assets.AssetBuilder) (string, error) {
if assetsBuilder == nil {
Expand All @@ -106,7 +103,7 @@ func Image(component string, clusterSpec *kops.ClusterSpec, assetsBuilder *asset

imageName := component

if !IsBaseURL(clusterSpec.KubernetesVersion) {
if !kopsmodel.IsBaseURL(clusterSpec.KubernetesVersion) {
image := "registry.k8s.io/" + imageName + ":" + "v" + kubernetesVersion.String()

image, err := assetsBuilder.RemapImage(image)
Expand Down
Loading

0 comments on commit c1276c0

Please sign in to comment.