Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: types/functions for images and Postgres versions #29

Merged
merged 4 commits into from
Oct 1, 2024
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
19 changes: 19 additions & 0 deletions pkg/image/reference/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
Copyright The CloudNativePG Contributors

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 reference contains an image reference
// parser
package reference
78 changes: 78 additions & 0 deletions pkg/image/reference/imagename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
Copyright The CloudNativePG Contributors

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 reference

import (
"fmt"
"regexp"
"strings"
)

var (
digestRegex = regexp.MustCompile(`@sha256:(?P<sha256>[a-fA-F0-9]+)$`)
tagRegex = regexp.MustCompile(`:(?P<tag>[^/]+)$`)
hostRegex = regexp.MustCompile(`^[^./:]+((\.[^./:]+)+(:[0-9]+)?|:[0-9]+)/`)
)

// Data is the main data type
type Data struct {
Name string
Tag string
Digest string
}

// GetNormalizedName returns the normalized name of a reference
func (r *Data) GetNormalizedName() (name string) {
name = r.Name
if r.Tag != "" {
name = fmt.Sprintf("%s:%s", name, r.Tag)
}
if r.Digest != "" {
name = fmt.Sprintf("%s@sha256:%s", name, r.Digest)
}
return name
}

// New parses the image name and returns an error if the name is invalid.
func New(name string) *Data {
reference := &Data{}

if !strings.Contains(name, "/") {
name = "docker.io/library/" + name
} else if !hostRegex.MatchString(name) {
name = "docker.io/" + name
}

if digestRegex.MatchString(name) {
res := digestRegex.FindStringSubmatch(name)
reference.Digest = res[1] // digest capture group index
name = strings.TrimSuffix(name, res[0])
}

if tagRegex.MatchString(name) {
res := tagRegex.FindStringSubmatch(name)
reference.Tag = res[1] // tag capture group index
name = strings.TrimSuffix(name, res[0])
} else if reference.Digest == "" {
reference.Tag = "latest"
}

// everything else is the name
reference.Name = name

return reference
}
49 changes: 49 additions & 0 deletions pkg/image/reference/imagename_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright The CloudNativePG Contributors

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 reference

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("image name management", func() {
It("should normalize image names", func() {
Expect(New("postgres").GetNormalizedName()).To(
Equal("docker.io/library/postgres:latest"))
Expect(New("myimage/postgres").GetNormalizedName()).To(
Equal("docker.io/myimage/postgres:latest"))
Expect(New("localhost:5000/postgres").GetNormalizedName()).To(
Equal("localhost:5000/postgres:latest"))
Expect(New("registry.localhost:5000/postgres:14.4").GetNormalizedName()).To(
Equal("registry.localhost:5000/postgres:14.4"))
Expect(New("ghcr.io/cloudnative-pg/postgresql:34").GetNormalizedName()).To(
Equal("ghcr.io/cloudnative-pg/postgresql:34"))
})

It("should extract tag names", func() {
Expect(New("postgres").Tag).To(Equal("latest"))
Expect(New("postgres:34.3").Tag).To(Equal("34.3"))
Expect(New("postgres:13@sha256:cff94de382ca538861622bbe84cfe03f44f307a9846a5c5eda672cf4dc692866").Tag).
To(Equal("13"))
})

It("should not extract a tag name", func() {
Expect(New("postgres@sha256:cff94dd382ca538861622bbe84cfe03f44f307a9846a5c5eda672cf4dc692866").Tag).
To(BeEmpty())
})
})
33 changes: 33 additions & 0 deletions pkg/image/reference/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright The CloudNativePG Contributors

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 reference

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.

func TestPostgresVersion(t *testing.T) {
RegisterFailHandler(Fail)

RunSpecs(t, "PostgreSQL Version")
}
19 changes: 19 additions & 0 deletions pkg/postgres/version/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
Copyright The CloudNativePG Contributors

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 version contains a PostgreSQL image version parser with
// the corresponding data type and operations on it
package version
33 changes: 33 additions & 0 deletions pkg/postgres/version/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
Copyright The CloudNativePG Contributors

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 version

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.

func TestPostgresVersion(t *testing.T) {
RegisterFailHandler(Fail)

RunSpecs(t, "PostgreSQL Version")
}
122 changes: 122 additions & 0 deletions pkg/postgres/version/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
Copyright The CloudNativePG Contributors

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 version

import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
)

var semanticVersionRegex = regexp.MustCompile(`^(\d\.?)+`)

// Data is a structure used to manage a PostgreSQL version,
// consisting of two parts: major and minor. For instance,
// version 10.0 corresponds to a major of 10 and a minor of 0.
// See: https://www.postgresql.org/support/versioning/
type Data struct {
major uint64
minor uint64
}

// Major gets the major version (i.e. 10)
func (d Data) Major() uint64 {
return d.major
}

// Minor gets the minor version (i.e. 0)
func (d Data) Minor() uint64 {
return d.minor
}

// Less is the implementation of the "less than" operator
func (d Data) Less(other Data) bool {
if d.major < other.major {
return true
} else if d.major > other.major {
return false
}

return d.minor < other.minor
}

// New constructs a version from its components
func New(major, minor uint64) Data {
return Data{
major: major,
minor: minor,
}
}

// FromTag parse a PostgreSQL version string returning
// a major version ID. Example:
//
// FromTag("11.2") == (11,2)
// FromTag("12.1") == (12,1)
// FromTag("13.3.2.1-1") == (13,3)
// FromTag("13.4") == (13,4)
// FromTag("14") == (14,0)
// FromTag("15.5-10") == (15,5)
// FromTag("16.0") == (16,0)
// FromTag("17beta1") == (17,0)
// FromTag("17rc1") == (17,0)
func FromTag(version string) (Data, error) {
if !semanticVersionRegex.MatchString(version) {
return Data{},
fmt.Errorf("version not starting with a semantic version regex (%v): %s", semanticVersionRegex, version)
}

if versionOnly := semanticVersionRegex.FindString(version); versionOnly != "" {
version = versionOnly
}

splitVersion := strings.Split(version, ".")

majorVersion, err := strconv.Atoi(splitVersion[0])
if err != nil {
return Data{}, fmt.Errorf("wrong PostgreSQL major in version %v", version)
}

minorVersion := 0
if len(splitVersion) > 1 {
minorVersion, err = strconv.Atoi(splitVersion[1])
if err != nil {
return Data{}, fmt.Errorf("wrong PostgreSQL minor in version %v", version)
}
}

if majorVersion < 0 || majorVersion > math.MaxInt {
return Data{}, fmt.Errorf("wrong PostgreSQL major in version %v", version)
}

if minorVersion < 0 || minorVersion > math.MaxInt {
return Data{}, fmt.Errorf("wrong PostgreSQL minor in version %v", version)
}

return Data{
major: uint64(majorVersion),
minor: uint64(minorVersion),
}, nil
}

// IsUpgradePossible detect if it's possible to upgrade from fromVersion to
// toVersion
func IsUpgradePossible(fromVersion, toVersion Data) bool {
return fromVersion.major == toVersion.major
}
Loading