mirror of https://github.com/kubernetes/kops.git
channels: support targeting kubernetes versions
This commit is contained in:
parent
343217eb8a
commit
a7c2c554e1
3
Makefile
3
Makefile
|
@ -111,7 +111,8 @@ test:
|
||||||
go test k8s.io/kops/protokube/... -args -v=1 -logtostderr
|
go test k8s.io/kops/protokube/... -args -v=1 -logtostderr
|
||||||
go test k8s.io/kops/dns-controller/pkg/... -args -v=1 -logtostderr
|
go test k8s.io/kops/dns-controller/pkg/... -args -v=1 -logtostderr
|
||||||
go test k8s.io/kops/cmd/... -args -v=1 -logtostderr
|
go test k8s.io/kops/cmd/... -args -v=1 -logtostderr
|
||||||
go test k8s.io/kops/tests/... -args -v=1 -logtostderr
|
go test k8s.io/kops/cmd/... -args -v=1 -logtostderr
|
||||||
|
go test k8s.io/kops/channels/... -args -v=1 -logtostderr
|
||||||
go test k8s.io/kops/util/... -args -v=1 -logtostderr
|
go test k8s.io/kops/util/... -args -v=1 -logtostderr
|
||||||
|
|
||||||
crossbuild-nodeup:
|
crossbuild-nodeup:
|
||||||
|
|
|
@ -18,16 +18,14 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"k8s.io/kops/channels/pkg/cmd"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
Execute()
|
f := &cmd.DefaultFactory{}
|
||||||
}
|
if err := cmd.Execute(f, os.Stdout); err != nil {
|
||||||
|
|
||||||
// exitWithError will terminate execution with an error result
|
|
||||||
// It prints the error to stderr and exits with a non-zero exit code
|
|
||||||
func exitWithError(err error) {
|
|
||||||
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -47,4 +47,15 @@ type AddonSpec struct {
|
||||||
|
|
||||||
// Manifest is the URL to the manifest that should be applied
|
// Manifest is the URL to the manifest that should be applied
|
||||||
Manifest *string `json:"manifest,omitempty"`
|
Manifest *string `json:"manifest,omitempty"`
|
||||||
|
|
||||||
|
// KubernetesVersion is a semver version range on which this version of the addon can be applied
|
||||||
|
KubernetesVersion string `json:"kubernetesVersion,omitempty"`
|
||||||
|
|
||||||
|
// Id is an optional value which can be used to force a refresh even if the Version matches
|
||||||
|
// This is useful for when we have two manifests expressing the same addon version for two
|
||||||
|
// different kubernetes api versions. For example, we might label the 1.5 version "k8s-1.5"
|
||||||
|
// and the 1.6 version "k8s-1.6". Both would have the same Version, determined by the
|
||||||
|
// version of the software we are packaging. But we always want to reinstall when we
|
||||||
|
// switch kubernetes versions.
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Addon is a wrapper around a single version of an addon
|
||||||
type Addon struct {
|
type Addon struct {
|
||||||
Name string
|
Name string
|
||||||
ChannelName string
|
ChannelName string
|
||||||
|
@ -32,16 +33,42 @@ type Addon struct {
|
||||||
Spec *api.AddonSpec
|
Spec *api.AddonSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddonUpdate holds data about a proposed update to an addon
|
||||||
type AddonUpdate struct {
|
type AddonUpdate struct {
|
||||||
Name string
|
Name string
|
||||||
ExistingVersion *ChannelVersion
|
ExistingVersion *ChannelVersion
|
||||||
NewVersion *ChannelVersion
|
NewVersion *ChannelVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddonMenu is a collection of addons, with helpers for computing the latest versions
|
||||||
|
type AddonMenu struct {
|
||||||
|
Addons map[string]*Addon
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddonMenu() *AddonMenu {
|
||||||
|
return &AddonMenu{
|
||||||
|
Addons: make(map[string]*Addon),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *AddonMenu) Merge(o *AddonMenu) {
|
||||||
|
for k, v := range o.Addons {
|
||||||
|
existing := m.Addons[k]
|
||||||
|
if existing == nil {
|
||||||
|
m.Addons[k] = v
|
||||||
|
} else {
|
||||||
|
if existing.ChannelVersion().replaces(v.ChannelVersion()) {
|
||||||
|
m.Addons[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Addon) ChannelVersion() *ChannelVersion {
|
func (a *Addon) ChannelVersion() *ChannelVersion {
|
||||||
return &ChannelVersion{
|
return &ChannelVersion{
|
||||||
Channel: &a.ChannelName,
|
Channel: &a.ChannelName,
|
||||||
Version: a.Spec.Version,
|
Version: a.Spec.Version,
|
||||||
|
Id: a.Spec.Id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +94,7 @@ func (a *Addon) GetRequiredUpdates(k8sClient kubernetes.Interface) (*AddonUpdate
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if existingVersion != nil && !newVersion.Replaces(existingVersion) {
|
if existingVersion != nil && !newVersion.replaces(existingVersion) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/blang/semver"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"k8s.io/kops/channels/pkg/api"
|
"k8s.io/kops/channels/pkg/api"
|
||||||
"k8s.io/kops/upup/pkg/fi/utils"
|
"k8s.io/kops/upup/pkg/fi/utils"
|
||||||
|
@ -58,28 +59,29 @@ func ParseAddons(name string, location *url.URL, data []byte) (*Addons, error) {
|
||||||
return &Addons{ChannelName: name, ChannelLocation: *location, APIObject: apiObject}, nil
|
return &Addons{ChannelName: name, ChannelLocation: *location, APIObject: apiObject}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Addons) GetCurrent() ([]*Addon, error) {
|
func (a *Addons) GetCurrent(kubernetesVersion semver.Version) (*AddonMenu, error) {
|
||||||
all, err := a.All()
|
all, err := a.wrapInAddons()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
specs := make(map[string]*Addon)
|
|
||||||
|
menu := NewAddonMenu()
|
||||||
for _, addon := range all {
|
for _, addon := range all {
|
||||||
|
if !addon.matches(kubernetesVersion) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
name := addon.Name
|
name := addon.Name
|
||||||
existing := specs[name]
|
|
||||||
if existing == nil || addon.ChannelVersion().Replaces(existing.ChannelVersion()) {
|
existing := menu.Addons[name]
|
||||||
specs[name] = addon
|
if existing == nil || addon.ChannelVersion().replaces(existing.ChannelVersion()) {
|
||||||
|
menu.Addons[name] = addon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var addons []*Addon
|
return menu, nil
|
||||||
for _, addon := range specs {
|
|
||||||
addons = append(addons, addon)
|
|
||||||
}
|
|
||||||
return addons, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Addons) All() ([]*Addon, error) {
|
func (a *Addons) wrapInAddons() ([]*Addon, error) {
|
||||||
var addons []*Addon
|
var addons []*Addon
|
||||||
for _, s := range a.APIObject.Spec.Addons {
|
for _, s := range a.APIObject.Spec.Addons {
|
||||||
name := a.APIObject.ObjectMeta.Name
|
name := a.APIObject.ObjectMeta.Name
|
||||||
|
@ -98,3 +100,19 @@ func (a *Addons) All() ([]*Addon, error) {
|
||||||
}
|
}
|
||||||
return addons, nil
|
return addons, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Addon) matches(kubernetesVersion semver.Version) bool {
|
||||||
|
if s.Spec.KubernetesVersion != "" {
|
||||||
|
versionRange, err := semver.ParseRange(s.Spec.KubernetesVersion)
|
||||||
|
if err != nil {
|
||||||
|
glog.Warningf("unable to parse KubernetesVersion %q; skipping", s.Spec.KubernetesVersion)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !versionRange(kubernetesVersion) {
|
||||||
|
glog.V(4).Infof("Skipping version range %q that does not match current version %s", s.Spec.KubernetesVersion, kubernetesVersion)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 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 channels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/blang/semver"
|
||||||
|
"k8s.io/kops/channels/pkg/api"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Filtering(t *testing.T) {
|
||||||
|
grid := []struct {
|
||||||
|
Input api.AddonSpec
|
||||||
|
KubernetesVersion string
|
||||||
|
Expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Input: api.AddonSpec{
|
||||||
|
KubernetesVersion: ">=1.6.0",
|
||||||
|
},
|
||||||
|
KubernetesVersion: "1.6.0",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: api.AddonSpec{
|
||||||
|
KubernetesVersion: "<1.6.0",
|
||||||
|
},
|
||||||
|
KubernetesVersion: "1.6.0",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: api.AddonSpec{
|
||||||
|
KubernetesVersion: ">=1.6.0",
|
||||||
|
},
|
||||||
|
KubernetesVersion: "1.5.9",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: api.AddonSpec{
|
||||||
|
KubernetesVersion: ">=1.4.0 <1.6.0",
|
||||||
|
},
|
||||||
|
KubernetesVersion: "1.5.9",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Input: api.AddonSpec{
|
||||||
|
KubernetesVersion: ">=1.4.0 <1.6.0",
|
||||||
|
},
|
||||||
|
KubernetesVersion: "1.6.0",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, g := range grid {
|
||||||
|
k8sVersion := semver.MustParse(g.KubernetesVersion)
|
||||||
|
addon := &Addon{
|
||||||
|
Spec: &g.Input,
|
||||||
|
}
|
||||||
|
actual := addon.matches(k8sVersion)
|
||||||
|
if actual != g.Expected {
|
||||||
|
t.Errorf("unexpected result from %v, %s. got %v", g.Input.KubernetesVersion, g.KubernetesVersion, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Replacement(t *testing.T) {
|
||||||
|
grid := []struct {
|
||||||
|
Old *ChannelVersion
|
||||||
|
New *ChannelVersion
|
||||||
|
Replaces bool
|
||||||
|
}{
|
||||||
|
// With no id, update iff newer semver
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.0.0"), Id: ""},
|
||||||
|
New: &ChannelVersion{Version: s("1.0.0"), Id: ""},
|
||||||
|
Replaces: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.0.0"), Id: ""},
|
||||||
|
New: &ChannelVersion{Version: s("1.0.1"), Id: ""},
|
||||||
|
Replaces: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.0.1"), Id: ""},
|
||||||
|
New: &ChannelVersion{Version: s("1.0.0"), Id: ""},
|
||||||
|
Replaces: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.1.0"), Id: ""},
|
||||||
|
New: &ChannelVersion{Version: s("1.1.1"), Id: ""},
|
||||||
|
Replaces: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.1.1"), Id: ""},
|
||||||
|
New: &ChannelVersion{Version: s("1.1.0"), Id: ""},
|
||||||
|
Replaces: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// With id, update if different id and same version, otherwise follow semver
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.0.0"), Id: "a"},
|
||||||
|
New: &ChannelVersion{Version: s("1.0.0"), Id: "a"},
|
||||||
|
Replaces: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.0.0"), Id: "a"},
|
||||||
|
New: &ChannelVersion{Version: s("1.0.0"), Id: "b"},
|
||||||
|
Replaces: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.0.0"), Id: "b"},
|
||||||
|
New: &ChannelVersion{Version: s("1.0.0"), Id: "a"},
|
||||||
|
Replaces: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.0.0"), Id: "a"},
|
||||||
|
New: &ChannelVersion{Version: s("1.0.1"), Id: "a"},
|
||||||
|
Replaces: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.0.0"), Id: "a"},
|
||||||
|
New: &ChannelVersion{Version: s("1.0.1"), Id: "a"},
|
||||||
|
Replaces: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Old: &ChannelVersion{Version: s("1.0.0"), Id: "a"},
|
||||||
|
New: &ChannelVersion{Version: s("1.0.1"), Id: "a"},
|
||||||
|
Replaces: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, g := range grid {
|
||||||
|
actual := g.New.replaces(g.Old)
|
||||||
|
if actual != g.Replaces {
|
||||||
|
t.Errorf("unexpected result from %v -> %v, expect %t. actual %v", g.Old, g.New, g.Replaces, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func s(v string) *string {
|
||||||
|
return &v
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ type Channel struct {
|
||||||
type ChannelVersion struct {
|
type ChannelVersion struct {
|
||||||
Version *string `json:"version,omitempty"`
|
Version *string `json:"version,omitempty"`
|
||||||
Channel *string `json:"channel,omitempty"`
|
Channel *string `json:"channel,omitempty"`
|
||||||
|
Id string `json:"id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringValue(s *string) string {
|
func stringValue(s *string) string {
|
||||||
|
@ -48,7 +49,11 @@ func stringValue(s *string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChannelVersion) String() string {
|
func (c *ChannelVersion) String() string {
|
||||||
return "Version=" + stringValue(c.Version) + " Channel=" + stringValue(c.Channel)
|
s := "Version=" + stringValue(c.Version) + " Channel=" + stringValue(c.Channel)
|
||||||
|
if c.Id != "" {
|
||||||
|
s += " Id=" + c.Id
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseChannelVersion(s string) (*ChannelVersion, error) {
|
func ParseChannelVersion(s string) (*ChannelVersion, error) {
|
||||||
|
@ -91,7 +96,7 @@ func (c *Channel) AnnotationName() string {
|
||||||
return AnnotationPrefix + c.Name
|
return AnnotationPrefix + c.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChannelVersion) Replaces(existing *ChannelVersion) bool {
|
func (c *ChannelVersion) replaces(existing *ChannelVersion) bool {
|
||||||
if existing.Version != nil {
|
if existing.Version != nil {
|
||||||
if c.Version == nil {
|
if c.Version == nil {
|
||||||
return false
|
return false
|
||||||
|
@ -106,13 +111,25 @@ func (c *ChannelVersion) Replaces(existing *ChannelVersion) bool {
|
||||||
glog.Warningf("error parsing existing version %q", *existing.Version)
|
glog.Warningf("error parsing existing version %q", *existing.Version)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return cVersion.GT(existingVersion)
|
if cVersion.LT(existingVersion) {
|
||||||
|
return false
|
||||||
|
} else if cVersion.GT(existingVersion) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
// Same version; check ids
|
||||||
|
if c.Id == existing.Id {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
glog.V(4).Infof("Channels had same version %q but different ids (%q vs %q); will replace", c.Version, c.Id, existing.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.Warningf("ChannelVersion did not have a version; can't perform real version check")
|
glog.Warningf("ChannelVersion did not have a version; can't perform real version check")
|
||||||
if c.Version == nil {
|
if c.Version == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,18 +14,21 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package main
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// applyCmd represents the apply command
|
func NewCmdApply(f Factory, out io.Writer) *cobra.Command {
|
||||||
var applyCmd = &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "apply",
|
Use: "apply",
|
||||||
Short: "apply resources from a channel",
|
Short: "apply resources from a channel",
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
// create subcommands
|
||||||
rootCommand.AddCommand(applyCmd)
|
cmd.AddCommand(NewCmdApplyChannel(f, out))
|
||||||
|
|
||||||
|
return cmd
|
||||||
}
|
}
|
|
@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package main
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/blang/semver"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"io"
|
||||||
"k8s.io/kops/channels/pkg/channels"
|
"k8s.io/kops/channels/pkg/channels"
|
||||||
"k8s.io/kops/util/pkg/tables"
|
"k8s.io/kops/util/pkg/tables"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -26,38 +28,54 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApplyChannelCmd struct {
|
type ApplyChannelOptions struct {
|
||||||
Yes bool
|
Yes bool
|
||||||
Files []string
|
Files []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var applyChannel ApplyChannelCmd
|
func NewCmdApplyChannel(f Factory, out io.Writer) *cobra.Command {
|
||||||
|
var options ApplyChannelOptions
|
||||||
|
|
||||||
func init() {
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "channel",
|
Use: "channel",
|
||||||
Short: "Apply channel",
|
Short: "Apply channel",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := applyChannel.Run(args)
|
return RunApplyChannel(f, out, &options, args)
|
||||||
if err != nil {
|
|
||||||
exitWithError(err)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().BoolVar(&applyChannel.Yes, "yes", false, "Apply update")
|
cmd.Flags().BoolVar(&options.Yes, "yes", false, "Apply update")
|
||||||
cmd.Flags().StringSliceVar(&applyChannel.Files, "f", []string{}, "Apply from a local file")
|
cmd.Flags().StringSliceVar(&options.Files, "f", []string{}, "Apply from a local file")
|
||||||
|
|
||||||
applyCmd.AddCommand(cmd)
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApplyChannelCmd) Run(args []string) error {
|
func RunApplyChannel(f Factory, out io.Writer, options *ApplyChannelOptions, args []string) error {
|
||||||
k8sClient, err := rootCommand.KubernetesClient()
|
k8sClient, err := f.KubernetesClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var addons []*channels.Addon
|
kubernetesVersionInfo, err := k8sClient.Discovery().ServerVersion()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error querying kubernetes version: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//kubernetesVersion, err := semver.Parse(kubernetesVersionInfo.Major + "." + kubernetesVersionInfo.Minor + ".0")
|
||||||
|
//if err != nil {
|
||||||
|
// return fmt.Errorf("cannot parse kubernetes version %q", kubernetesVersionInfo.Major+"."+kubernetesVersionInfo.Minor + ".0")
|
||||||
|
//}
|
||||||
|
|
||||||
|
kubernetesVersion, err := semver.ParseTolerant(kubernetesVersionInfo.GitVersion)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot parse kubernetes version %q", kubernetesVersionInfo.GitVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Pre and Patch, as they make semver comparisons impractical
|
||||||
|
kubernetesVersion.Pre = nil
|
||||||
|
|
||||||
|
menu := channels.NewAddonMenu()
|
||||||
|
|
||||||
for _, name := range args {
|
for _, name := range args {
|
||||||
location, err := url.Parse(name)
|
location, err := url.Parse(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -80,14 +98,14 @@ func (c *ApplyChannelCmd) Run(args []string) error {
|
||||||
return fmt.Errorf("error loading channel %q: %v", location, err)
|
return fmt.Errorf("error loading channel %q: %v", location, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
current, err := o.GetCurrent()
|
current, err := o.GetCurrent(kubernetesVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error processing latest versions in %q: %v", location, err)
|
return fmt.Errorf("error processing latest versions in %q: %v", location, err)
|
||||||
}
|
}
|
||||||
addons = append(addons, current...)
|
menu.Merge(current)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range c.Files {
|
for _, f := range options.Files {
|
||||||
location, err := url.Parse(f)
|
location, err := url.Parse(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to parse argument %q as url", f)
|
return fmt.Errorf("unable to parse argument %q as url", f)
|
||||||
|
@ -108,16 +126,16 @@ func (c *ApplyChannelCmd) Run(args []string) error {
|
||||||
return fmt.Errorf("error loading file %q: %v", f, err)
|
return fmt.Errorf("error loading file %q: %v", f, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
current, err := o.GetCurrent()
|
current, err := o.GetCurrent(kubernetesVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error processing latest versions in %q: %v", f, err)
|
return fmt.Errorf("error processing latest versions in %q: %v", f, err)
|
||||||
}
|
}
|
||||||
addons = append(addons, current...)
|
menu.Merge(current)
|
||||||
}
|
}
|
||||||
|
|
||||||
var updates []*channels.AddonUpdate
|
var updates []*channels.AddonUpdate
|
||||||
var needUpdates []*channels.Addon
|
var needUpdates []*channels.Addon
|
||||||
for _, addon := range addons {
|
for _, addon := range menu.Addons {
|
||||||
// TODO: Cache lookups to prevent repeated lookups?
|
// TODO: Cache lookups to prevent repeated lookups?
|
||||||
update, err := addon.GetRequiredUpdates(k8sClient)
|
update, err := addon.GetRequiredUpdates(k8sClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -165,7 +183,7 @@ func (c *ApplyChannelCmd) Run(args []string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.Yes {
|
if !options.Yes {
|
||||||
fmt.Printf("\nMust specify --yes to update\n")
|
fmt.Printf("\nMust specify --yes to update\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -178,7 +196,7 @@ func (c *ApplyChannelCmd) Run(args []string) error {
|
||||||
// Could have been a concurrent request
|
// Could have been a concurrent request
|
||||||
if update != nil {
|
if update != nil {
|
||||||
if update.NewVersion.Version != nil {
|
if update.NewVersion.Version != nil {
|
||||||
fmt.Printf("Updated %q to %v\n", update.Name, *update.NewVersion.Version)
|
fmt.Printf("Updated %q to %s\n", update.Name, *update.NewVersion.Version)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Updated %q\n", update.Name)
|
fmt.Printf("Updated %q\n", update.Name)
|
||||||
}
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Factory interface {
|
||||||
|
KubernetesClient() (kubernetes.Interface, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultFactory struct {
|
||||||
|
kubernetesClient kubernetes.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Factory = &DefaultFactory{}
|
||||||
|
|
||||||
|
func (f *DefaultFactory) KubernetesClient() (kubernetes.Interface, error) {
|
||||||
|
if f.kubernetesClient == nil {
|
||||||
|
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||||
|
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
|
||||||
|
|
||||||
|
configOverrides := &clientcmd.ConfigOverrides{
|
||||||
|
ClusterDefaults: clientcmd.ClusterDefaults,
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||||
|
config, err := kubeConfig.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot load kubecfg settings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
k8sClient, err := kubernetes.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot build kube client: %v", err)
|
||||||
|
}
|
||||||
|
f.kubernetesClient = k8sClient
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.kubernetesClient, nil
|
||||||
|
}
|
|
@ -14,36 +14,22 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package main
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetCmd represents the get command
|
func NewCmdGet(f Factory, out io.Writer) *cobra.Command {
|
||||||
type GetCmd struct {
|
cmd := &cobra.Command{
|
||||||
output string
|
|
||||||
|
|
||||||
cobraCommand *cobra.Command
|
|
||||||
}
|
|
||||||
|
|
||||||
var getCmd = GetCmd{
|
|
||||||
cobraCommand: &cobra.Command{
|
|
||||||
Use: "get",
|
Use: "get",
|
||||||
SuggestFor: []string{"list"},
|
SuggestFor: []string{"list"},
|
||||||
Short: "list or get objects",
|
Short: "list or get objects",
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
// create subcommands
|
||||||
OutputYaml = "yaml"
|
cmd.AddCommand(NewCmdGetAddons(f, out))
|
||||||
OutputTable = "table"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
return cmd
|
||||||
cmd := getCmd.cobraCommand
|
|
||||||
|
|
||||||
rootCommand.AddCommand(cmd)
|
|
||||||
|
|
||||||
cmd.PersistentFlags().StringVarP(&getCmd.output, "output", "o", OutputTable, "output format. One of: table, yaml")
|
|
||||||
}
|
}
|
|
@ -14,11 +14,28 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package main
|
/*
|
||||||
|
Copyright 2017 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 cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"io"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/pkg/api/v1"
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
"k8s.io/kops/channels/pkg/channels"
|
"k8s.io/kops/channels/pkg/channels"
|
||||||
|
@ -26,26 +43,23 @@ import (
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GetAddonsCmd struct {
|
type GetAddonsOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var getAddonsCmd GetAddonsCmd
|
func NewCmdGetAddons(f Factory, out io.Writer) *cobra.Command {
|
||||||
|
var options GetAddonsOptions
|
||||||
|
|
||||||
func init() {
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "addons",
|
Use: "addons",
|
||||||
Aliases: []string{"addon"},
|
Aliases: []string{"addon"},
|
||||||
Short: "get addons",
|
Short: "get addons",
|
||||||
Long: `List or get addons.`,
|
Long: `List or get addons.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := getAddonsCmd.Run(args)
|
return RunGetAddons(f, out, &options)
|
||||||
if err != nil {
|
|
||||||
exitWithError(err)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
getCmd.cobraCommand.AddCommand(cmd)
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
type addonInfo struct {
|
type addonInfo struct {
|
||||||
|
@ -54,8 +68,8 @@ type addonInfo struct {
|
||||||
Namespace *v1.Namespace
|
Namespace *v1.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GetAddonsCmd) Run(args []string) error {
|
func RunGetAddons(f Factory, out io.Writer, options *GetAddonsOptions) error {
|
||||||
k8sClient, err := rootCommand.KubernetesClient()
|
k8sClient, err := f.KubernetesClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package main
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
goflag "flag"
|
goflag "flag"
|
||||||
|
@ -22,48 +22,44 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"k8s.io/client-go/kubernetes"
|
"io"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RootCmd struct {
|
type CmdRootOptions struct {
|
||||||
configFile string
|
configFile string
|
||||||
|
|
||||||
cobraCommand *cobra.Command
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCommand = RootCmd{
|
func Execute(f Factory, out io.Writer) error {
|
||||||
cobraCommand: &cobra.Command{
|
|
||||||
Use: "channels",
|
|
||||||
Short: "channels applies software from a channel",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func Execute() {
|
|
||||||
goflag.Set("logtostderr", "true")
|
|
||||||
goflag.CommandLine.Parse([]string{})
|
|
||||||
if err := rootCommand.cobraCommand.Execute(); err != nil {
|
|
||||||
exitWithError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
|
|
||||||
cmd := rootCommand.cobraCommand
|
cmd := NewCmdRoot(f, out)
|
||||||
|
|
||||||
|
goflag.Set("logtostderr", "true")
|
||||||
|
goflag.CommandLine.Parse([]string{})
|
||||||
|
return cmd.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCmdRoot(f Factory, out io.Writer) *cobra.Command {
|
||||||
|
options := &CmdRootOptions{}
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "channels",
|
||||||
|
Short: "channels applies software from a channel",
|
||||||
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().AddGoFlagSet(goflag.CommandLine)
|
cmd.PersistentFlags().AddGoFlagSet(goflag.CommandLine)
|
||||||
|
|
||||||
cmd.PersistentFlags().StringVar(&rootCommand.configFile, "config", "", "config file (default is $HOME/.channels.yaml)")
|
cmd.PersistentFlags().StringVar(&options.configFile, "config", "", "config file (default is $HOME/.channels.yaml)")
|
||||||
|
|
||||||
|
// create subcommands
|
||||||
|
cmd.AddCommand(NewCmdApply(f, out))
|
||||||
|
cmd.AddCommand(NewCmdGet(f, out))
|
||||||
|
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfig reads in config file and ENV variables if set.
|
// initConfig reads in config file and ENV variables if set.
|
||||||
func initConfig() {
|
func initConfig() {
|
||||||
if rootCommand.configFile != "" {
|
|
||||||
// enable ability to specify config file via flag
|
|
||||||
viper.SetConfigFile(rootCommand.configFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
viper.SetConfigName(".channels") // name of config file (without extension)
|
viper.SetConfigName(".channels") // name of config file (without extension)
|
||||||
viper.AddConfigPath("$HOME") // adding home directory as first search path
|
viper.AddConfigPath("$HOME") // adding home directory as first search path
|
||||||
viper.AutomaticEnv() // read in environment variables that match
|
viper.AutomaticEnv() // read in environment variables that match
|
||||||
|
@ -73,28 +69,3 @@ func initConfig() {
|
||||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RootCmd) AddCommand(cmd *cobra.Command) {
|
|
||||||
c.cobraCommand.AddCommand(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RootCmd) KubernetesClient() (kubernetes.Interface, error) {
|
|
||||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
|
||||||
loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
|
|
||||||
|
|
||||||
configOverrides := &clientcmd.ConfigOverrides{
|
|
||||||
ClusterDefaults: clientcmd.ClusterDefaults,
|
|
||||||
}
|
|
||||||
|
|
||||||
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
|
||||||
config, err := kubeConfig.ClientConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot load kubecfg settings: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
k8sClient, err := kubernetes.NewForConfig(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot build kube client: %v", err)
|
|
||||||
}
|
|
||||||
return k8sClient, err
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ k8s.io/kops
|
||||||
k8s.io/kops/channels/cmd/channels
|
k8s.io/kops/channels/cmd/channels
|
||||||
k8s.io/kops/channels/pkg/api
|
k8s.io/kops/channels/pkg/api
|
||||||
k8s.io/kops/channels/pkg/channels
|
k8s.io/kops/channels/pkg/channels
|
||||||
|
k8s.io/kops/channels/pkg/cmd
|
||||||
k8s.io/kops/cloudmock/aws/mockautoscaling
|
k8s.io/kops/cloudmock/aws/mockautoscaling
|
||||||
k8s.io/kops/cloudmock/aws/mockec2
|
k8s.io/kops/cloudmock/aws/mockec2
|
||||||
k8s.io/kops/cloudmock/aws/mockroute53
|
k8s.io/kops/cloudmock/aws/mockroute53
|
||||||
|
|
Loading…
Reference in New Issue