Skip to content

Commit

Permalink
added license provisioning
Browse files Browse the repository at this point in the history
  • Loading branch information
hellt committed Jul 31, 2022
1 parent d70aa3b commit 9c05fff
Show file tree
Hide file tree
Showing 11 changed files with 588 additions and 50 deletions.
39 changes: 39 additions & 0 deletions api/types/v1alpha1/srl_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package v1alpha1

import (
"errors"
"regexp"
"strings"
)

// ErrVersionParse is an error which is raised when srlinux version is failed to parse.
var ErrVersionParse = errors.New("version parsing failed")

// SrlVersion represents an sr linux version as a set of fields.
type SrlVersion struct {
Major string `json:"major,omitempty"`
Minor string `json:"minor,omitempty"`
Patch string `json:"patch,omitempty"`
Build string `json:"build,omitempty"`
Commit string `json:"commit,omitempty"`
}

func parseVersionString(s string) (*SrlVersion, error) {
// for latest or missing tag we consider the version to be an engineering build
// with major = 0
if strings.ToLower(s) == "latest" || s == "" {
return &SrlVersion{"0", "", "", "", ""}, nil
}

// https://regex101.com/r/eWS6Ms/1
re := regexp.MustCompile(
`^v?(?P<major>\d{1,3})\.(?P<minor>\d{1,2})\.?(?P<patch>\d{1,2})?-?(?P<build>\d{1,10})?-?(?P<commit>\S+)?`,
)

v := re.FindStringSubmatch(s)
if v == nil {
return nil, ErrVersionParse
}

return &SrlVersion{v[1], v[2], v[3], v[4], v[5]}, nil
}
98 changes: 98 additions & 0 deletions api/types/v1alpha1/srl_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package v1alpha1

import (
"errors"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestParseVersionString(t *testing.T) {
tests := []struct {
desc string
got string
want *SrlVersion
err error
}{
{
desc: "maj, minor, patch",
got: "21.6.4",
want: &SrlVersion{"21", "6", "4", "", ""},
},
{
desc: "maj, minor",
got: "21.6",
want: &SrlVersion{"21", "6", "", "", ""},
},
{
desc: "maj, minor and extra string",
got: "21.6-test",
want: &SrlVersion{"21", "6", "", "", "test"},
},
{
desc: "maj, minor, patch and extra string",
got: "21.6.11-test",
want: &SrlVersion{"21", "6", "11", "", "test"},
},
{
desc: "maj, minor, patch, build and extra string",
got: "21.6.11-235-test",
want: &SrlVersion{"21", "6", "11", "235", "test"},
},
{
desc: "maj, minor, patch and build",
got: "21.6.11-235",
want: &SrlVersion{"21", "6", "11", "235", ""},
},
{
desc: "0.0",
got: "0.0",
want: &SrlVersion{"0", "0", "", "", ""},
},
{
desc: "0.0.0",
got: "0.0.0",
want: &SrlVersion{"0", "0", "0", "", ""},
},
{
desc: "0.0.0-34652",
got: "0.0.0-34652",
want: &SrlVersion{"0", "0", "0", "34652", ""},
},
{
desc: "latest",
got: "latest",
want: &SrlVersion{"0", "", "", "", ""},
},
{
desc: "empty",
got: "",
want: &SrlVersion{"0", "", "", "", ""},
},
{
desc: "invalid1",
got: "abcd",
want: nil,
err: ErrVersionParse,
},
}

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
ver, err := parseVersionString(tt.got)
if !errors.Is(err, tt.err) {
t.Fatalf("got error '%v' but expected '%v'", err, tt.err)
}

if !cmp.Equal(ver, tt.want) {
t.Fatalf(
"%s: actual and expected inputs do not match\nactual: %+v\nexpected:%+v",
tt.desc,
ver,
tt.want,
)
}
},
)
}
}
62 changes: 61 additions & 1 deletion api/types/v1alpha1/srlinux_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ limitations under the License.
package v1alpha1

import (
"context"
"fmt"
"strings"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// key value for a combined license file stored in a secret.
const allSecretKey = "all.key"

// SrlinuxSpec defines the desired state of Srlinux.
type SrlinuxSpec struct {
Config *NodeConfig `json:"config,omitempty"`
Expand Down Expand Up @@ -50,9 +58,14 @@ type Srlinux struct {

Spec SrlinuxSpec `json:"spec,omitempty"`
Status SrlinuxStatus `json:"status,omitempty"`

// parsed NOS version
NOSVersion *SrlVersion `json:"nos-version,omitempty"`
// license key from license secret that contains a license file for this Srlinux
LicenseKey string `json:"license_key,omitempty"`
}

//+kubebuilder:object:root=true
// +kubebuilder:object:root=true

// SrlinuxList contains a list of Srlinux.
type SrlinuxList struct {
Expand Down Expand Up @@ -112,3 +125,50 @@ func (s *SrlinuxSpec) GetImage() string {

return img
}

// GetImageVersion finds an srlinux image version by looking at the Image field of the spec
// as well as at Version field.
// When Version field is set it is returned.
// In other cases, Image string is evaluated and it's tag substring is parsed.
// If no tag is present, or tag is latest, the 0.0 version is assumed to be in use.
func (s *SrlinuxSpec) GetImageVersion() (*SrlVersion, error) {
if s.Version != "" {
return parseVersionString(s.Version)
}

var tag string

split := strings.Split(s.GetImage(), ":")
if len(split) == 2 { // nolint: gomnd
tag = split[1]
}

return parseVersionString(tag)
}

// InitLicenseKey sets the Srlinux.LicenseKey to a value of a key
// that matches MAJOR-MINOR.key of a passed secret.
// Where MAJOR-MINOR is retrieved from the image version.
// If such key doesn't exist, it checks if a wildcard `all.key` is found in that secret,
// if nothing found, LicenseKey stays empty, which denotes that no license was found for Srlinux.
func (s *Srlinux) InitLicenseKey(
_ context.Context,
secret *corev1.Secret,
) {
if secret == nil {
return
}

versionSecretKey := fmt.Sprintf("%s-%s.key", s.NOSVersion.Major, s.NOSVersion.Minor)
if _, ok := secret.Data[versionSecretKey]; ok {
s.LicenseKey = versionSecretKey

return
}

if _, ok := secret.Data[allSecretKey]; ok {
s.LicenseKey = allSecretKey

return
}
}
141 changes: 140 additions & 1 deletion api/types/v1alpha1/srlinux_types_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package v1alpha1

import (
"context"
"errors"
"testing"

"github.com/google/go-cmp/cmp"
corev1 "k8s.io/api/core/v1"
)

func TestGetImage(t *testing.T) {

tests := []struct {
desc string
spec *SrlinuxSpec
Expand All @@ -34,6 +36,24 @@ func TestGetImage(t *testing.T) {
},
want: defaultSRLinuxImageName + ":21.11.1",
},
{
desc: "image without tag",
spec: &SrlinuxSpec{
Config: &NodeConfig{
Image: "ghcr.io/nokia/srlinux",
},
},
want: "ghcr.io/nokia/srlinux",
},
{
desc: "image with latest tag",
spec: &SrlinuxSpec{
Config: &NodeConfig{
Image: "ghcr.io/nokia/srlinux:latest",
},
},
want: "ghcr.io/nokia/srlinux:latest",
},
}

for _, tt := range tests {
Expand All @@ -51,5 +71,124 @@ func TestGetImage(t *testing.T) {
},
)
}
}

func TestGetImageVersion(t *testing.T) {
tests := []struct {
desc string
spec *SrlinuxSpec
want *SrlVersion
err error
}{
{
desc: "valid version is present",
spec: &SrlinuxSpec{
Version: "21.11.1",
Config: &NodeConfig{Image: "ghcr.io/nokia/srlinux:somever"},
},
want: &SrlVersion{"21", "11", "1", "", ""},
},
{
desc: "invalid version is present",
spec: &SrlinuxSpec{
Version: "abc21.11.1",
Config: &NodeConfig{Image: "ghcr.io/nokia/srlinux:somever"},
},
err: ErrVersionParse,
},
{
desc: "version is not present, valid image tag is given",
spec: &SrlinuxSpec{
Config: &NodeConfig{Image: "ghcr.io/nokia/srlinux:21.11.1"},
},
want: &SrlVersion{"21", "11", "1", "", ""},
},
{
desc: "version is not present, invalid image tag is given",
spec: &SrlinuxSpec{
Config: &NodeConfig{Image: "ghcr.io/nokia/srlinux:21"},
},
err: ErrVersionParse,
},
}

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
v, err := tt.spec.GetImageVersion()

if !errors.Is(err, tt.err) {
t.Fatalf("got error '%v' but expected '%v'", err, tt.err)
}

if !cmp.Equal(v, tt.want) {
t.Fatalf(
"%s: actual and expected inputs do not match\nactual: %+v\nexpected:%+v",
tt.desc,
v,
tt.want,
)
}
},
)
}
}

func TestInitVersion(t *testing.T) {
tests := []struct {
desc string
srl *Srlinux
secret *corev1.Secret
want string
}{
{
desc: "secret key matches srl version",
srl: &Srlinux{
NOSVersion: &SrlVersion{"22", "3", "", "", ""},
},
secret: &corev1.Secret{
Data: map[string][]byte{
"22-3.key": nil,
"all.key": nil,
},
},
want: "22-3.key",
},
{
desc: "wildcard secret key matches srl version",
srl: &Srlinux{
NOSVersion: &SrlVersion{"22", "3", "", "", ""},
},
secret: &corev1.Secret{
Data: map[string][]byte{
"22-6.key": nil,
"all.key": nil,
},
},
want: "all.key",
},
{
desc: "secret does not exist",
srl: &Srlinux{
NOSVersion: &SrlVersion{"22", "3", "", "", ""},
},
secret: nil,
want: "",
},
}

for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
tt.srl.InitLicenseKey(context.TODO(), tt.secret)

if !cmp.Equal(tt.srl.LicenseKey, tt.want) {
t.Fatalf(
"%s: actual and expected inputs do not match\nactual: %+v\nexpected:%+v",
tt.desc,
tt.srl.LicenseKey,
tt.want,
)
}
},
)
}
}
Loading

0 comments on commit 9c05fff

Please sign in to comment.