Merge pull request #48950 from alexandercampbell/kubectl-deduplicate-deployment-generators

Automatic merge from submit-queue (batch tested with PRs 49120, 46755, 49157, 49165, 48950)

kubectl: deduplicate deployment generators

**What this PR does / why we need it**: See the description on https://github.com/kubernetes/kubectl/issues/44

**Which issue this PR fixes**: fixes https://github.com/kubernetes/kubectl/issues/44

**Special notes for your reviewer**: Yes, the lines added and removed are about the same. This is because I added 20+ lines of docstrings. Check the diff. You'll see I deleted a lot of duplicated logic :)

**Release note**:

```release-note
NONE
```
This commit is contained in:
Kubernetes Submit Queue 2017-07-19 00:06:29 -07:00 committed by GitHub
commit d74ac3785e
3 changed files with 115 additions and 105 deletions

View File

@ -103,16 +103,22 @@ func generatorFromName(
switch generatorName { switch generatorName {
case cmdutil.DeploymentBasicAppsV1Beta1GeneratorName: case cmdutil.DeploymentBasicAppsV1Beta1GeneratorName:
return &kubectl.DeploymentBasicAppsGeneratorV1{ generator := &kubectl.DeploymentBasicAppsGeneratorV1{
Name: deploymentName, BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
Images: imageNames, Name: deploymentName,
}, true Images: imageNames,
},
}
return generator, true
case cmdutil.DeploymentBasicV1Beta1GeneratorName: case cmdutil.DeploymentBasicV1Beta1GeneratorName:
return &kubectl.DeploymentBasicGeneratorV1{ generator := &kubectl.DeploymentBasicGeneratorV1{
Name: deploymentName, BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
Images: imageNames, Name: deploymentName,
}, true Images: imageNames,
},
}
return generator, true
} }
return nil, false return nil, false

View File

@ -47,17 +47,29 @@ func Test_generatorFromName(t *testing.T) {
generator, ok = generatorFromName(basicName, imageNames, deploymentName) generator, ok = generatorFromName(basicName, imageNames, deploymentName)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, &kubectl.DeploymentBasicGeneratorV1{
Name: deploymentName, {
Images: imageNames, expectedGenerator := &kubectl.DeploymentBasicGeneratorV1{
}, generator) BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
Name: deploymentName,
Images: imageNames,
},
}
assert.Equal(t, expectedGenerator, generator)
}
generator, ok = generatorFromName(basicAppsName, imageNames, deploymentName) generator, ok = generatorFromName(basicAppsName, imageNames, deploymentName)
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, &kubectl.DeploymentBasicAppsGeneratorV1{
Name: deploymentName, {
Images: imageNames, expectedGenerator := &kubectl.DeploymentBasicAppsGeneratorV1{
}, generator) BaseDeploymentGenerator: kubectl.BaseDeploymentGenerator{
Name: deploymentName,
Images: imageNames,
},
}
assert.Equal(t, expectedGenerator, generator)
}
} }
func TestCreateDeployment(t *testing.T) { func TestCreateDeployment(t *testing.T) {

View File

@ -27,24 +27,42 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
// DeploymentBasicGeneratorV1 supports stable generation of a deployment // BaseDeploymentGenerator: implement the common functionality of
type DeploymentBasicGeneratorV1 struct { // DeploymentBasicGeneratorV1 and DeploymentBasicAppsGeneratorV1. To reduce
// confusion, it's best to keep this struct in the same file as those
// generators.
type BaseDeploymentGenerator struct {
Name string Name string
Images []string Images []string
} }
// Ensure it supports the generator pattern that uses parameters specified during construction // ParamNames: return the parameters expected by the BaseDeploymentGenerator.
var _ StructuredGenerator = &DeploymentBasicGeneratorV1{} // This method is here to aid in validation. When given a Generator, you can
// learn what it expects by calling this method.
func (DeploymentBasicGeneratorV1) ParamNames() []GeneratorParam { func (BaseDeploymentGenerator) ParamNames() []GeneratorParam {
return []GeneratorParam{ return []GeneratorParam{
{"name", true}, {"name", true},
{"image", true}, {"image", true},
} }
} }
func (s DeploymentBasicGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) { // validate: check if the caller has forgotten to set one of our fields.
err := ValidateParams(s.ParamNames(), params) func (b BaseDeploymentGenerator) validate() error {
if len(b.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(b.Images) == 0 {
return fmt.Errorf("at least one image must be specified")
}
return nil
}
// baseDeploymentGeneratorFromParams: return a new BaseDeploymentGenerator with
// the fields set from params. The returned BaseDeploymentGenerator should have
// all required fields set and will pass validate() with no errors.
func baseDeploymentGeneratorFromParams(params map[string]interface{}) (*BaseDeploymentGenerator, error) {
paramNames := (BaseDeploymentGenerator{}).ParamNames()
err := ValidateParams(paramNames, params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -56,18 +74,37 @@ func (s DeploymentBasicGeneratorV1) Generate(params map[string]interface{}) (run
if !isArray { if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", imageStrings) return nil, fmt.Errorf("expected []string, found :%v", imageStrings)
} }
delegate := &DeploymentBasicGeneratorV1{Name: name, Images: imageStrings} return &BaseDeploymentGenerator{
return delegate.StructuredGenerate() Name: name,
Images: imageStrings,
}, nil
} }
// StructuredGenerate outputs a deployment object using the configured fields // structuredGenerate: determine the fields of a deployment. The struct that
func (s *DeploymentBasicGeneratorV1) StructuredGenerate() (runtime.Object, error) { // embeds BaseDeploymentGenerator should assemble these pieces into a
if err := s.validate(); err != nil { // runtime.Object.
return nil, err func (b BaseDeploymentGenerator) structuredGenerate() (
podSpec v1.PodSpec,
labels map[string]string,
selector metav1.LabelSelector,
err error,
) {
err = b.validate()
if err != nil {
return
} }
podSpec = buildPodSpec(b.Images)
labels = map[string]string{}
labels["app"] = b.Name
selector = metav1.LabelSelector{MatchLabels: labels}
return
}
// buildPodSpec: parse the image strings and assemble them into the Containers
// of a PodSpec. This is all you need to create the PodSpec for a deployment.
func buildPodSpec(images []string) v1.PodSpec {
podSpec := v1.PodSpec{Containers: []v1.Container{}} podSpec := v1.PodSpec{Containers: []v1.Container{}}
for _, imageString := range s.Images { for _, imageString := range images {
// Retain just the image name // Retain just the image name
imageSplit := strings.Split(imageString, "/") imageSplit := strings.Split(imageString, "/")
name := imageSplit[len(imageSplit)-1] name := imageSplit[len(imageSplit)-1]
@ -79,13 +116,30 @@ func (s *DeploymentBasicGeneratorV1) StructuredGenerate() (runtime.Object, error
} }
podSpec.Containers = append(podSpec.Containers, v1.Container{Name: name, Image: imageString}) podSpec.Containers = append(podSpec.Containers, v1.Container{Name: name, Image: imageString})
} }
return podSpec
}
// setup default label and selector // DeploymentBasicGeneratorV1 supports stable generation of a deployment
labels := map[string]string{} type DeploymentBasicGeneratorV1 struct {
labels["app"] = s.Name BaseDeploymentGenerator
}
// Ensure it supports the generator pattern that uses parameters specified during construction
var _ StructuredGenerator = &DeploymentBasicGeneratorV1{}
func (s DeploymentBasicGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
base, err := baseDeploymentGeneratorFromParams(params)
if err != nil {
return nil, err
}
return (&DeploymentBasicGeneratorV1{*base}).StructuredGenerate()
}
// StructuredGenerate outputs a deployment object using the configured fields
func (s *DeploymentBasicGeneratorV1) StructuredGenerate() (runtime.Object, error) {
podSpec, labels, selector, err := s.structuredGenerate()
one := int32(1) one := int32(1)
selector := metav1.LabelSelector{MatchLabels: labels} return &extensionsv1beta1.Deployment{
deployment := extensionsv1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: s.Name, Name: s.Name,
Labels: labels, Labels: labels,
@ -100,80 +154,30 @@ func (s *DeploymentBasicGeneratorV1) StructuredGenerate() (runtime.Object, error
Spec: podSpec, Spec: podSpec,
}, },
}, },
} }, err
return &deployment, nil
}
// validate validates required fields are set to support structured generation
func (s *DeploymentBasicGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.Images) == 0 {
return fmt.Errorf("at least one image must be specified")
}
return nil
} }
// DeploymentBasicAppsGeneratorV1 supports stable generation of a deployment under apps/v1beta1 endpoint // DeploymentBasicAppsGeneratorV1 supports stable generation of a deployment under apps/v1beta1 endpoint
type DeploymentBasicAppsGeneratorV1 struct { type DeploymentBasicAppsGeneratorV1 struct {
Name string BaseDeploymentGenerator
Images []string
} }
// Ensure it supports the generator pattern that uses parameters specified during construction // Ensure it supports the generator pattern that uses parameters specified during construction
var _ StructuredGenerator = &DeploymentBasicAppsGeneratorV1{} var _ StructuredGenerator = &DeploymentBasicAppsGeneratorV1{}
func (DeploymentBasicAppsGeneratorV1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"image", true},
}
}
func (s DeploymentBasicAppsGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) { func (s DeploymentBasicAppsGeneratorV1) Generate(params map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(s.ParamNames(), params) base, err := baseDeploymentGeneratorFromParams(params)
if err != nil { if err != nil {
return nil, err return nil, err
} }
name, isString := params["name"].(string) return (&DeploymentBasicAppsGeneratorV1{*base}).StructuredGenerate()
if !isString {
return nil, fmt.Errorf("expected string, saw %v for 'name'", name)
}
imageStrings, isArray := params["image"].([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found :%v", imageStrings)
}
delegate := &DeploymentBasicAppsGeneratorV1{Name: name, Images: imageStrings}
return delegate.StructuredGenerate()
} }
// StructuredGenerate outputs a deployment object using the configured fields // StructuredGenerate outputs a deployment object using the configured fields
func (s *DeploymentBasicAppsGeneratorV1) StructuredGenerate() (runtime.Object, error) { func (s *DeploymentBasicAppsGeneratorV1) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil { podSpec, labels, selector, err := s.structuredGenerate()
return nil, err
}
podSpec := v1.PodSpec{Containers: []v1.Container{}}
for _, imageString := range s.Images {
// Retain just the image name
imageSplit := strings.Split(imageString, "/")
name := imageSplit[len(imageSplit)-1]
// Remove any tag or hash
if strings.Contains(name, ":") {
name = strings.Split(name, ":")[0]
} else if strings.Contains(name, "@") {
name = strings.Split(name, "@")[0]
}
podSpec.Containers = append(podSpec.Containers, v1.Container{Name: name, Image: imageString})
}
// setup default label and selector
labels := map[string]string{}
labels["app"] = s.Name
one := int32(1) one := int32(1)
selector := metav1.LabelSelector{MatchLabels: labels} return &appsv1beta1.Deployment{
deployment := appsv1beta1.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: s.Name, Name: s.Name,
Labels: labels, Labels: labels,
@ -188,17 +192,5 @@ func (s *DeploymentBasicAppsGeneratorV1) StructuredGenerate() (runtime.Object, e
Spec: podSpec, Spec: podSpec,
}, },
}, },
} }, err
return &deployment, nil
}
// validate validates required fields are set to support structured generation
func (s *DeploymentBasicAppsGeneratorV1) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.Images) == 0 {
return fmt.Errorf("at least one image must be specified")
}
return nil
} }