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 27, 2024
1 parent 59e06d8 commit 9448e5b
Show file tree
Hide file tree
Showing 17 changed files with 254 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
69 changes: 69 additions & 0 deletions pkg/apis/kops/model/instance_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

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
}

// instanceGroupModel is a concrete implementation of InstanceGroup.
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
}
89 changes: 89 additions & 0 deletions pkg/apis/kops/model/kubernetes_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

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 9448e5b

Please sign in to comment.