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/dns-controller/pkg/... -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
|
||||
|
||||
crossbuild-nodeup:
|
||||
|
|
|
@ -18,16 +18,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"k8s.io/kops/channels/pkg/cmd"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
Execute()
|
||||
}
|
||||
|
||||
// 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)
|
||||
os.Exit(1)
|
||||
f := &cmd.DefaultFactory{}
|
||||
if err := cmd.Execute(f, os.Stdout); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,4 +47,15 @@ type AddonSpec struct {
|
|||
|
||||
// Manifest is the URL to the manifest that should be applied
|
||||
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"
|
||||
)
|
||||
|
||||
// Addon is a wrapper around a single version of an addon
|
||||
type Addon struct {
|
||||
Name string
|
||||
ChannelName string
|
||||
|
@ -32,16 +33,42 @@ type Addon struct {
|
|||
Spec *api.AddonSpec
|
||||
}
|
||||
|
||||
// AddonUpdate holds data about a proposed update to an addon
|
||||
type AddonUpdate struct {
|
||||
Name string
|
||||
ExistingVersion *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 {
|
||||
return &ChannelVersion{
|
||||
Channel: &a.ChannelName,
|
||||
Version: a.Spec.Version,
|
||||
Id: a.Spec.Id,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,7 +94,7 @@ func (a *Addon) GetRequiredUpdates(k8sClient kubernetes.Interface) (*AddonUpdate
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if existingVersion != nil && !newVersion.Replaces(existingVersion) {
|
||||
if existingVersion != nil && !newVersion.replaces(existingVersion) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package channels
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/blang/semver"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kops/channels/pkg/api"
|
||||
"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
|
||||
}
|
||||
|
||||
func (a *Addons) GetCurrent() ([]*Addon, error) {
|
||||
all, err := a.All()
|
||||
func (a *Addons) GetCurrent(kubernetesVersion semver.Version) (*AddonMenu, error) {
|
||||
all, err := a.wrapInAddons()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
specs := make(map[string]*Addon)
|
||||
|
||||
menu := NewAddonMenu()
|
||||
for _, addon := range all {
|
||||
if !addon.matches(kubernetesVersion) {
|
||||
continue
|
||||
}
|
||||
name := addon.Name
|
||||
existing := specs[name]
|
||||
if existing == nil || addon.ChannelVersion().Replaces(existing.ChannelVersion()) {
|
||||
specs[name] = addon
|
||||
|
||||
existing := menu.Addons[name]
|
||||
if existing == nil || addon.ChannelVersion().replaces(existing.ChannelVersion()) {
|
||||
menu.Addons[name] = addon
|
||||
}
|
||||
}
|
||||
|
||||
var addons []*Addon
|
||||
for _, addon := range specs {
|
||||
addons = append(addons, addon)
|
||||
}
|
||||
return addons, nil
|
||||
return menu, nil
|
||||
}
|
||||
|
||||
func (a *Addons) All() ([]*Addon, error) {
|
||||
func (a *Addons) wrapInAddons() ([]*Addon, error) {
|
||||
var addons []*Addon
|
||||
for _, s := range a.APIObject.Spec.Addons {
|
||||
name := a.APIObject.ObjectMeta.Name
|
||||
|
@ -98,3 +100,19 @@ func (a *Addons) All() ([]*Addon, error) {
|
|||
}
|
||||
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 {
|
||||
Version *string `json:"version,omitempty"`
|
||||
Channel *string `json:"channel,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func stringValue(s *string) string {
|
||||
|
@ -48,7 +49,11 @@ func stringValue(s *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) {
|
||||
|
@ -91,7 +96,7 @@ func (c *Channel) AnnotationName() string {
|
|||
return AnnotationPrefix + c.Name
|
||||
}
|
||||
|
||||
func (c *ChannelVersion) Replaces(existing *ChannelVersion) bool {
|
||||
func (c *ChannelVersion) replaces(existing *ChannelVersion) bool {
|
||||
if existing.Version != nil {
|
||||
if c.Version == nil {
|
||||
return false
|
||||
|
@ -106,13 +111,25 @@ func (c *ChannelVersion) Replaces(existing *ChannelVersion) bool {
|
|||
glog.Warningf("error parsing existing version %q", *existing.Version)
|
||||
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")
|
||||
if c.Version == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -14,18 +14,21 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
)
|
||||
|
||||
// applyCmd represents the apply command
|
||||
var applyCmd = &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: "apply resources from a channel",
|
||||
}
|
||||
func NewCmdApply(f Factory, out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "apply",
|
||||
Short: "apply resources from a channel",
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCommand.AddCommand(applyCmd)
|
||||
// create subcommands
|
||||
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.
|
||||
*/
|
||||
|
||||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/blang/semver"
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
"k8s.io/kops/channels/pkg/channels"
|
||||
"k8s.io/kops/util/pkg/tables"
|
||||
"net/url"
|
||||
|
@ -26,38 +28,54 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type ApplyChannelCmd struct {
|
||||
type ApplyChannelOptions struct {
|
||||
Yes bool
|
||||
Files []string
|
||||
}
|
||||
|
||||
var applyChannel ApplyChannelCmd
|
||||
func NewCmdApplyChannel(f Factory, out io.Writer) *cobra.Command {
|
||||
var options ApplyChannelOptions
|
||||
|
||||
func init() {
|
||||
cmd := &cobra.Command{
|
||||
Use: "channel",
|
||||
Short: "Apply channel",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := applyChannel.Run(args)
|
||||
if err != nil {
|
||||
exitWithError(err)
|
||||
}
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return RunApplyChannel(f, out, &options, args)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&applyChannel.Yes, "yes", false, "Apply update")
|
||||
cmd.Flags().StringSliceVar(&applyChannel.Files, "f", []string{}, "Apply from a local file")
|
||||
cmd.Flags().BoolVar(&options.Yes, "yes", false, "Apply update")
|
||||
cmd.Flags().StringSliceVar(&options.Files, "f", []string{}, "Apply from a local file")
|
||||
|
||||
applyCmd.AddCommand(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *ApplyChannelCmd) Run(args []string) error {
|
||||
k8sClient, err := rootCommand.KubernetesClient()
|
||||
func RunApplyChannel(f Factory, out io.Writer, options *ApplyChannelOptions, args []string) error {
|
||||
k8sClient, err := f.KubernetesClient()
|
||||
if err != nil {
|
||||
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 {
|
||||
location, err := url.Parse(name)
|
||||
if err != nil {
|
||||
|
@ -80,14 +98,14 @@ func (c *ApplyChannelCmd) Run(args []string) error {
|
|||
return fmt.Errorf("error loading channel %q: %v", location, err)
|
||||
}
|
||||
|
||||
current, err := o.GetCurrent()
|
||||
current, err := o.GetCurrent(kubernetesVersion)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
current, err := o.GetCurrent()
|
||||
current, err := o.GetCurrent(kubernetesVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing latest versions in %q: %v", f, err)
|
||||
}
|
||||
addons = append(addons, current...)
|
||||
menu.Merge(current)
|
||||
}
|
||||
|
||||
var updates []*channels.AddonUpdate
|
||||
var needUpdates []*channels.Addon
|
||||
for _, addon := range addons {
|
||||
for _, addon := range menu.Addons {
|
||||
// TODO: Cache lookups to prevent repeated lookups?
|
||||
update, err := addon.GetRequiredUpdates(k8sClient)
|
||||
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")
|
||||
return nil
|
||||
}
|
||||
|
@ -178,7 +196,7 @@ func (c *ApplyChannelCmd) Run(args []string) error {
|
|||
// Could have been a concurrent request
|
||||
if update != 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 {
|
||||
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.
|
||||
*/
|
||||
|
||||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
)
|
||||
|
||||
// GetCmd represents the get command
|
||||
type GetCmd struct {
|
||||
output string
|
||||
|
||||
cobraCommand *cobra.Command
|
||||
}
|
||||
|
||||
var getCmd = GetCmd{
|
||||
cobraCommand: &cobra.Command{
|
||||
func NewCmdGet(f Factory, out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "get",
|
||||
SuggestFor: []string{"list"},
|
||||
Short: "list or get objects",
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
OutputYaml = "yaml"
|
||||
OutputTable = "table"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := getCmd.cobraCommand
|
||||
|
||||
rootCommand.AddCommand(cmd)
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&getCmd.output, "output", "o", OutputTable, "output format. One of: table, yaml")
|
||||
}
|
||||
|
||||
// create subcommands
|
||||
cmd.AddCommand(NewCmdGetAddons(f, out))
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -14,11 +14,28 @@ See the License for the specific language governing permissions and
|
|||
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 (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"io"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
"k8s.io/kops/channels/pkg/channels"
|
||||
|
@ -26,26 +43,23 @@ import (
|
|||
"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{
|
||||
Use: "addons",
|
||||
Aliases: []string{"addon"},
|
||||
Short: "get addons",
|
||||
Long: `List or get addons.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := getAddonsCmd.Run(args)
|
||||
if err != nil {
|
||||
exitWithError(err)
|
||||
}
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return RunGetAddons(f, out, &options)
|
||||
},
|
||||
}
|
||||
|
||||
getCmd.cobraCommand.AddCommand(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type addonInfo struct {
|
||||
|
@ -54,8 +68,8 @@ type addonInfo struct {
|
|||
Namespace *v1.Namespace
|
||||
}
|
||||
|
||||
func (c *GetAddonsCmd) Run(args []string) error {
|
||||
k8sClient, err := rootCommand.KubernetesClient()
|
||||
func RunGetAddons(f Factory, out io.Writer, options *GetAddonsOptions) error {
|
||||
k8sClient, err := f.KubernetesClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
package cmd
|
||||
|
||||
import (
|
||||
goflag "flag"
|
||||
|
@ -22,48 +22,44 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"io"
|
||||
)
|
||||
|
||||
type RootCmd struct {
|
||||
type CmdRootOptions struct {
|
||||
configFile string
|
||||
|
||||
cobraCommand *cobra.Command
|
||||
}
|
||||
|
||||
var rootCommand = RootCmd{
|
||||
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() {
|
||||
func Execute(f Factory, out io.Writer) error {
|
||||
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().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.
|
||||
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.AddConfigPath("$HOME") // adding home directory as first search path
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
@ -73,28 +69,3 @@ func initConfig() {
|
|||
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/pkg/api
|
||||
k8s.io/kops/channels/pkg/channels
|
||||
k8s.io/kops/channels/pkg/cmd
|
||||
k8s.io/kops/cloudmock/aws/mockautoscaling
|
||||
k8s.io/kops/cloudmock/aws/mockec2
|
||||
k8s.io/kops/cloudmock/aws/mockroute53
|
||||
|
|
Loading…
Reference in New Issue