mirror of
https://github.com/fluxcd/flux2.git
synced 2026-02-08 16:57:29 +00:00
Merge pull request #922 from fluxcd/check-improvements
This commit is contained in:
commit
298d6a1a15
8 changed files with 200 additions and 83 deletions
|
|
@ -132,6 +132,17 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
|
|||
return "", err
|
||||
}
|
||||
bootstrapArgs.version = version
|
||||
} else {
|
||||
if ok, err := install.ExistingVersion(bootstrapArgs.version); err != nil || !ok {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("targeted version '%s' does not exist", bootstrapArgs.version)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if !utils.CompatibleVersion(VERSION, bootstrapArgs.version) {
|
||||
return "", fmt.Errorf("targeted version '%s' is not compatible with your current version of flux (%s)", bootstrapArgs.version, VERSION)
|
||||
}
|
||||
|
||||
opts := install.Options{
|
||||
|
|
|
|||
|
|
@ -21,14 +21,19 @@ import (
|
|||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/spf13/cobra"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/fluxcd/pkg/version"
|
||||
|
||||
"github.com/fluxcd/flux2/internal/utils"
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
)
|
||||
|
||||
var checkCmd = &cobra.Command{
|
||||
|
|
@ -74,6 +79,8 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
|
|||
logger.Actionf("checking prerequisites")
|
||||
checkFailed := false
|
||||
|
||||
fluxCheck()
|
||||
|
||||
if !kubectlCheck(ctx, ">=1.18.0") {
|
||||
checkFailed = true
|
||||
}
|
||||
|
|
@ -101,7 +108,29 @@ func runCheckCmd(cmd *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func kubectlCheck(ctx context.Context, version string) bool {
|
||||
func fluxCheck() {
|
||||
curSv, err := version.ParseVersion(VERSION)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Exclude development builds.
|
||||
if curSv.Prerelease() != "" {
|
||||
return
|
||||
}
|
||||
latest, err := install.GetLatestVersion()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
latestSv, err := version.ParseVersion(latest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if latestSv.GreaterThan(curSv) {
|
||||
logger.Failuref("flux %s <%s (new version is available, please upgrade)", curSv, latestSv)
|
||||
}
|
||||
}
|
||||
|
||||
func kubectlCheck(ctx context.Context, constraint string) bool {
|
||||
_, err := exec.LookPath("kubectl")
|
||||
if err != nil {
|
||||
logger.Failuref("kubectl not found")
|
||||
|
|
@ -117,58 +146,58 @@ func kubectlCheck(ctx context.Context, version string) bool {
|
|||
|
||||
kv := &kubectlVersion{}
|
||||
if err = json.Unmarshal([]byte(output), kv); err != nil {
|
||||
logger.Failuref("kubectl version output can't be unmarshaled")
|
||||
logger.Failuref("kubectl version output can't be unmarshalled")
|
||||
return false
|
||||
}
|
||||
|
||||
v, err := semver.ParseTolerant(kv.ClientVersion.GitVersion)
|
||||
v, err := version.ParseVersion(kv.ClientVersion.GitVersion)
|
||||
if err != nil {
|
||||
logger.Failuref("kubectl version can't be parsed")
|
||||
return false
|
||||
}
|
||||
|
||||
rng, _ := semver.ParseRange(version)
|
||||
if !rng(v) {
|
||||
logger.Failuref("kubectl version must be %s", version)
|
||||
c, _ := semver.NewConstraint(constraint)
|
||||
if !c.Check(v) {
|
||||
logger.Failuref("kubectl version must be %s", constraint)
|
||||
return false
|
||||
}
|
||||
|
||||
logger.Successf("kubectl %s %s", v.String(), version)
|
||||
logger.Successf("kubectl %s %s", v.String(), constraint)
|
||||
return true
|
||||
}
|
||||
|
||||
func kubernetesCheck(version string) bool {
|
||||
func kubernetesCheck(constraint string) bool {
|
||||
cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(cfg)
|
||||
clientSet, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
logger.Failuref("Kubernetes client initialization failed: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
ver, err := client.Discovery().ServerVersion()
|
||||
kv, err := clientSet.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
logger.Failuref("Kubernetes API call failed: %s", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
v, err := semver.ParseTolerant(ver.String())
|
||||
v, err := version.ParseVersion(kv.String())
|
||||
if err != nil {
|
||||
logger.Failuref("Kubernetes version can't be determined")
|
||||
return false
|
||||
}
|
||||
|
||||
rng, _ := semver.ParseRange(version)
|
||||
if !rng(v) {
|
||||
logger.Failuref("Kubernetes version must be %s", version)
|
||||
c, _ := semver.NewConstraint(constraint)
|
||||
if !c.Check(v) {
|
||||
logger.Failuref("Kubernetes version must be %s", constraint)
|
||||
return false
|
||||
}
|
||||
|
||||
logger.Successf("Kubernetes %s %s", v.String(), version)
|
||||
logger.Successf("Kubernetes %s %s", v.String(), constraint)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -176,23 +205,29 @@ func componentsCheck() bool {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
statusChecker, err := NewStatusChecker(time.Second, 30*time.Second)
|
||||
kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
statusChecker, err := NewStatusChecker(time.Second, rootArgs.timeout)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ok := true
|
||||
deployments := append(checkArgs.components, checkArgs.extraComponents...)
|
||||
for _, deployment := range deployments {
|
||||
if err := statusChecker.Assess(deployment); err != nil {
|
||||
ok = false
|
||||
} else {
|
||||
logger.Successf("%s: healthy", deployment)
|
||||
}
|
||||
|
||||
kubectlArgs := []string{"-n", rootArgs.namespace, "get", "deployment", deployment, "-o", "jsonpath=\"{..image}\""}
|
||||
if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, rootArgs.kubeconfig, rootArgs.kubecontext, kubectlArgs...); err == nil {
|
||||
logger.Actionf(strings.TrimPrefix(strings.TrimSuffix(output, "\""), "\""))
|
||||
selector := client.MatchingLabels{"app.kubernetes.io/instance": rootArgs.namespace}
|
||||
var list v1.DeploymentList
|
||||
if err := kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace), selector); err == nil {
|
||||
for _, d := range list.Items {
|
||||
if err := statusChecker.Assess(d.Name); err != nil {
|
||||
ok = false
|
||||
} else {
|
||||
logger.Successf("%s: healthy", d.Name)
|
||||
}
|
||||
for _, c := range d.Spec.Template.Spec.Containers {
|
||||
logger.Actionf(c.Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok
|
||||
|
|
|
|||
|
|
@ -106,19 +106,9 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
|
||||
defer cancel()
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if !installExport {
|
||||
logger.Generatef("generating manifests")
|
||||
}
|
||||
|
||||
components := append(installDefaultComponents, installExtraComponents...)
|
||||
|
||||
if err := utils.ValidateComponents(components); err != nil {
|
||||
err := utils.ValidateComponents(components)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +117,27 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if ok, err := install.ExistingVersion(installVersion); err != nil || !ok {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("targeted version '%s' does not exist", installVersion)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !utils.CompatibleVersion(VERSION, installVersion) {
|
||||
return fmt.Errorf("targeted version '%s' is not compatible with your current version of flux (%s)", installVersion, VERSION)
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", rootArgs.namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if !installExport {
|
||||
logger.Generatef("generating manifests")
|
||||
}
|
||||
|
||||
opts := install.Options{
|
||||
|
|
|
|||
3
go.mod
3
go.mod
|
|
@ -3,7 +3,7 @@ module github.com/fluxcd/flux2
|
|||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/blang/semver/v4 v4.0.0
|
||||
github.com/Masterminds/semver/v3 v3.1.0
|
||||
github.com/cyphar/filepath-securejoin v0.2.2
|
||||
github.com/fluxcd/helm-controller/api v0.7.0
|
||||
github.com/fluxcd/image-automation-controller/api v0.5.0
|
||||
|
|
@ -15,6 +15,7 @@ require (
|
|||
github.com/fluxcd/pkg/runtime v0.8.0
|
||||
github.com/fluxcd/pkg/ssh v0.0.5
|
||||
github.com/fluxcd/pkg/untar v0.0.5
|
||||
github.com/fluxcd/pkg/version v0.0.1
|
||||
github.com/fluxcd/source-controller/api v0.8.0
|
||||
github.com/google/go-containerregistry v0.2.0
|
||||
github.com/manifoldco/promptui v0.7.0
|
||||
|
|
|
|||
7
go.sum
7
go.sum
|
|
@ -69,6 +69,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14=
|
||||
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
|
||||
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
|
|
@ -112,10 +114,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ
|
|||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/bombsimon/wsl v1.2.5/go.mod h1:43lEF/i0kpXbLCeDXL9LMT8c92HyBywXb0AsgMHYngM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
|
|
@ -220,6 +219,8 @@ github.com/fluxcd/pkg/ssh v0.0.5 h1:rnbFZ7voy2JBlUfMbfyqArX2FYaLNpDhccGFC3qW83A=
|
|||
github.com/fluxcd/pkg/ssh v0.0.5/go.mod h1:7jXPdXZpc0ttMNz2kD9QuMi3RNn/e0DOFbj0Tij/+Hs=
|
||||
github.com/fluxcd/pkg/untar v0.0.5 h1:UGI3Ch1UIEIaqQvMicmImL1s9npQa64DJ/ozqHKB7gk=
|
||||
github.com/fluxcd/pkg/untar v0.0.5/go.mod h1:O6V9+rtl8c1mHBafgqFlJN6zkF1HS5SSYn7RpQJ/nfw=
|
||||
github.com/fluxcd/pkg/version v0.0.1 h1:/8asQoDXSThz3csiwi4Qo8Zb6blAxLXbtxNgeMJ9bCg=
|
||||
github.com/fluxcd/pkg/version v0.0.1/go.mod h1:WAF4FEEA9xyhngF8TDxg3UPu5fA1qhEYV8Pmi2Il01Q=
|
||||
github.com/fluxcd/source-controller/api v0.8.0 h1:jOgeOwCLXzmjinRiDT7e/IuSB7WNZMgrUwMLJm09K/o=
|
||||
github.com/fluxcd/source-controller/api v0.8.0/go.mod h1:u2sdc/QDm0tzXHL7mZVj928hc3MMU+4mKCuAQg+94Bk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
|
|
|
|||
|
|
@ -46,14 +46,16 @@ import (
|
|||
kustypes "sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
|
||||
imageautov1 "github.com/fluxcd/image-automation-controller/api/v1alpha1"
|
||||
imagereflectv1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
|
||||
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
||||
notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
|
||||
"github.com/fluxcd/pkg/runtime/dependency"
|
||||
"github.com/fluxcd/pkg/version"
|
||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
||||
|
||||
"github.com/fluxcd/flux2/pkg/manifestgen/install"
|
||||
)
|
||||
|
||||
type Utils struct {
|
||||
|
|
@ -203,41 +205,6 @@ func SplitKubeConfigPath(path string) []string {
|
|||
return strings.Split(path, sep)
|
||||
}
|
||||
|
||||
func WriteFile(content, filename string) error {
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.WriteString(file, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return file.Sync()
|
||||
}
|
||||
|
||||
func CopyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func ContainsItemString(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
|
|
@ -420,3 +387,23 @@ func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
|
|||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// CompatibleVersion returns if the provided binary version is compatible
|
||||
// with the given target version. At present, this is true if the target
|
||||
// version is equal to the MINOR range of the binary, or if the binary
|
||||
// version is a prerelease.
|
||||
func CompatibleVersion(binary, target string) bool {
|
||||
binSv, err := version.ParseVersion(binary)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Assume prerelease builds are compatible.
|
||||
if binSv.Prerelease() != "" {
|
||||
return true
|
||||
}
|
||||
targetSv, err := version.ParseVersion(target)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return binSv.Major() == targetSv.Major() && binSv.Minor() == targetSv.Minor()
|
||||
}
|
||||
|
|
|
|||
42
internal/utils/utils_test.go
Normal file
42
internal/utils/utils_test.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2021 The Flux 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 utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCompatibleVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
binary string
|
||||
target string
|
||||
want bool
|
||||
}{
|
||||
{"different major version", "1.1.0", "0.1.0", false},
|
||||
{"different minor version", "0.1.0", "0.2.0", false},
|
||||
{"same version", "0.1.0", "0.1.0", true},
|
||||
{"binary patch version ahead", "0.1.1", "0.1.0", true},
|
||||
{"target patch version ahead", "0.1.1", "0.1.2", true},
|
||||
{"prerelease binary", "0.0.0-dev.0", "0.1.0", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := CompatibleVersion(tt.binary, tt.target); got != tt.want {
|
||||
t.Errorf("CompatibleVersion() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ func GetLatestVersion() (string, error) {
|
|||
|
||||
res, err := c.Get(ghURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("calling GitHub API failed: %w", err)
|
||||
return "", fmt.Errorf("GitHub API call failed: %w", err)
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
|
|
@ -104,3 +104,32 @@ func GetLatestVersion() (string, error) {
|
|||
|
||||
return m.Tag, err
|
||||
}
|
||||
|
||||
// ExistingVersion calls the GitHub API to confirm the given version does exist.
|
||||
func ExistingVersion(version string) (bool, error) {
|
||||
if !strings.HasPrefix(version, "v") {
|
||||
version = "v" + version
|
||||
}
|
||||
|
||||
ghURL := fmt.Sprintf("https://api.github.com/repos/fluxcd/flux2/releases/tags/%s", version)
|
||||
c := http.DefaultClient
|
||||
c.Timeout = 15 * time.Second
|
||||
|
||||
res, err := c.Get(ghURL)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("GitHub API call failed: %w", err)
|
||||
}
|
||||
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
return true, nil
|
||||
case http.StatusNotFound:
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf("GitHub API returned an unexpected status code (%d)", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue