提交 3354c7d6 编写于 作者: B bryk

Add command and command args to the deploy form

The UI is as specified in the mocks, except for the names of the fields.
I decided to go with what is in the API.

Everything is extensively tested. I also added some tests for previously
untested cases.
上级 3aeb1a34
......@@ -32,6 +32,13 @@ type AppDeploymentSpec struct {
// Docker image path for the application.
ContainerImage string `json:"containerImage"`
// Command that is executed instead of container entrypoint, if specified.
ContainerCommand *string `json:"containerCommand"`
// Arguments for the specified container command or container entrypoint (if command is not
// specified here).
ContainerCommandArgs *string `json:"containerCommandArgs"`
// Number of replicas of the image to maintain.
Replicas int `json:"replicas"`
......@@ -43,7 +50,7 @@ type AppDeploymentSpec struct {
IsExternal bool `json:"isExternal"`
// Description of the deployment.
Description string `json:"description"`
Description *string `json:"description"`
// Target namespace of the application.
Namespace string `json:"namespace"`
......@@ -77,8 +84,11 @@ type Label struct {
// App deployment consists of a replication controller and an optional service. Both of them share
// common labels.
// TODO(bryk): Write tests for this function.
func DeployApp(spec *AppDeploymentSpec, client *client.Client) error {
annotations := map[string]string{DescriptionAnnotationKey: spec.Description}
func DeployApp(spec *AppDeploymentSpec, client client.Interface) error {
annotations := map[string]string{}
if spec.Description != nil {
annotations[DescriptionAnnotationKey] = *spec.Description
}
labels := getLabelsMap(spec.Labels)
objectMeta := api.ObjectMeta{
Annotations: annotations,
......@@ -86,13 +96,23 @@ func DeployApp(spec *AppDeploymentSpec, client *client.Client) error {
Labels: labels,
}
containerSpec := api.Container{
Name: spec.Name,
Image: spec.ContainerImage,
}
if spec.ContainerCommand != nil {
containerSpec.Command = []string{*spec.ContainerCommand}
}
if spec.ContainerCommandArgs != nil {
containerSpec.Args = []string{*spec.ContainerCommandArgs}
}
podTemplate := &api.PodTemplateSpec{
ObjectMeta: objectMeta,
Spec: api.PodSpec{
Containers: []api.Container{{
Name: spec.Name,
Image: spec.ContainerImage,
}},
Containers: []api.Container{containerSpec},
},
}
......
......@@ -36,9 +36,11 @@ backendApi.PortMapping;
/**
* @typedef {{
* containerImage: string,
* containerCommand: ?string,
* containerCommandArgs: ?string,
* isExternal: boolean,
* name: string,
* description: string,
* description: ?string,
* portMappings: !Array<!backendApi.PortMapping>,
* replicas: number,
* namespace: string,
......
......@@ -114,3 +114,22 @@ limitations under the License.
<a href="">Learn more</a>
</kd-user-help>
</kd-help-section>
<kd-help-section>
<div>
<md-input-container class="md-block">
<label>Run command (optional)</label>
<input ng-model="ctrl.containerCommand">
</md-input-container>
<md-input-container class="md-block">
<label>Run command arguments (optional)</label>
<input ng-model="ctrl.containerCommandArgs">
</md-input-container>
</div>
<kd-user-help>
By default, your containers run the selected image's default entrypoint command. You can use the
command options to override the default.
<a href="">Learn more</a>
</kd-user-help>
</kd-help-section>
......@@ -45,6 +45,12 @@ export default class DeployFromSettingsController {
/** @export {string} */
this.containerImage = '';
/** @export {string} */
this.containerCommand = '';
/** @export {string} */
this.containerCommandArgs = '';
/** @export {number} */
this.replicas = 1;
......@@ -112,9 +118,11 @@ export default class DeployFromSettingsController {
/** @type {!backendApi.AppDeploymentSpec} */
let appDeploymentSpec = {
containerImage: this.containerImage,
containerCommand: this.containerCommand ? this.containerCommand : null,
containerCommandArgs: this.containerCommandArgs ? this.containerCommandArgs : null,
isExternal: this.isExternal,
name: this.name,
description: this.description,
description: this.description ? this.description : null,
portMappings: this.portMappings.filter(this.isPortMappingEmpty_),
replicas: this.replicas,
namespace: this.namespace,
......
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 main
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
"reflect"
"testing"
)
func TestDeployApp(t *testing.T) {
namespace := "foo-namespace"
spec := &AppDeploymentSpec{
Namespace: namespace,
Name: "foo-name",
}
expectedRc := &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: "foo-name",
Labels: map[string]string{},
Annotations: map[string]string{},
},
Spec: api.ReplicationControllerSpec{
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Name: "foo-name",
Labels: map[string]string{},
Annotations: map[string]string{},
},
Spec: api.PodSpec{
Containers: []api.Container{{
Name: "foo-name",
}},
},
},
Selector: map[string]string{},
},
}
testClient := testclient.NewSimpleFake()
DeployApp(spec, testClient)
createAction := testClient.Actions()[0].(testclient.CreateActionImpl)
if len(testClient.Actions()) != 1 {
t.Errorf("Expected one create action but got %#v", len(testClient.Actions()))
}
if createAction.Namespace != namespace {
t.Errorf("Expected namespace to be %#v but go %#v", namespace, createAction.Namespace)
}
rc := createAction.GetObject().(*api.ReplicationController)
if !reflect.DeepEqual(rc, expectedRc) {
t.Errorf("Expected replication controller \n%#v\n to be created but got \n%#v\n",
expectedRc, rc)
}
}
func TestDeployAppContainerCommands(t *testing.T) {
command := "foo-command"
commandArgs := "foo-command-args"
spec := &AppDeploymentSpec{
Namespace: "foo-namespace",
Name: "foo-name",
ContainerCommand: &command,
ContainerCommandArgs: &commandArgs,
}
testClient := testclient.NewSimpleFake()
DeployApp(spec, testClient)
createAction := testClient.Actions()[0].(testclient.CreateActionImpl)
rc := createAction.GetObject().(*api.ReplicationController)
container := rc.Spec.Template.Spec.Containers[0]
if container.Command[0] != command {
t.Errorf("Expected command to be %#v but got %#v",
command, container.Command)
}
if container.Args[0] != commandArgs {
t.Errorf("Expected command args to be %#v but got %#v",
commandArgs, container.Args)
}
}
......@@ -17,13 +17,17 @@ import deployModule from 'deploy/deploy_module';
import DeployLabel from 'deploy/deploylabel';
describe('DeployFromSettings controller', () => {
/** @type {!DeployFromSettingController} */
let ctrl;
/** @type {!angular.$resource} */
let mockResource;
beforeEach(() => {
angular.mock.module(deployModule.name);
angular.mock.inject(($controller) => {
ctrl = $controller(DeployFromSettingController, {}, {namespaces: []});
mockResource = jasmine.createSpy('$resource');
ctrl = $controller(DeployFromSettingController, {$resource: mockResource}, {namespaces: []});
});
});
......@@ -104,4 +108,53 @@ describe('DeployFromSettings controller', () => {
expect(result).toEqual(expected);
});
it('should deploy empty description and container commands as nulls', () => {
// given
let resourceObject = {
save: jasmine.createSpy('save'),
};
mockResource.and.returnValue(resourceObject);
resourceObject.save.and.callFake(function(spec) {
// then
expect(spec.containerCommand).toBeNull();
expect(spec.containerCommandArgs).toBeNull();
expect(spec.description).toBeNull();
});
ctrl.labels = [
new DeployLabel('key1', 'val1'),
];
// when
ctrl.deploy();
// then
expect(resourceObject.save).toHaveBeenCalled();
});
it('should deploy non empty description and container commands', () => {
// given
let resourceObject = {
save: jasmine.createSpy('save'),
};
mockResource.and.returnValue(resourceObject);
resourceObject.save.and.callFake(function(spec) {
// then
expect(spec.containerCommand).toBe('command');
expect(spec.containerCommandArgs).toBe('commandArgs');
expect(spec.description).toBe('desc');
});
ctrl.labels = [
new DeployLabel('key1', 'val1'),
];
ctrl.containerCommand = 'command';
ctrl.containerCommandArgs = 'commandArgs';
ctrl.description = 'desc';
// when
ctrl.deploy();
// then
expect(resourceObject.save).toHaveBeenCalled();
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册