提交 eccb746a 编写于 作者: P Piotr Bryk

Merge pull request #100 from maciaszczykm/deploy-namespaces

Implemented create new namespace dialog
......@@ -33,8 +33,8 @@ func CreateHttpApiHandler(client *client.Client) http.Handler {
deployWs.Route(
deployWs.POST("").
To(apiHandler.handleDeploy).
Reads(AppDeployment{}).
Writes(AppDeployment{}))
Reads(AppDeploymentSpec{}).
Writes(AppDeploymentSpec{}))
wsContainer.Add(deployWs)
replicaSetWs := new(restful.WebService)
......@@ -50,14 +50,20 @@ func CreateHttpApiHandler(client *client.Client) http.Handler {
Writes(ReplicaSetDetail{}))
wsContainer.Add(replicaSetWs)
namespaceListWs := new(restful.WebService)
namespaceListWs.Path("/api/namespaces").
namespacesWs := new(restful.WebService)
namespacesWs.Path("/api/namespaces").
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON)
namespaceListWs.Route(
namespaceListWs.GET("").
To(apiHandler.handleGetNamespaceList).
namespacesWs.Route(
namespacesWs.POST("").
To(apiHandler.handleCreateNamespace).
Reads(NamespaceSpec{}).
Writes(NamespaceSpec{}))
namespacesWs.Route(
namespacesWs.GET("").
To(apiHandler.handleGetNamespaces).
Writes(NamespacesList{}))
wsContainer.Add(namespaceListWs)
wsContainer.Add(namespacesWs)
logsWs := new(restful.WebService)
logsWs.Path("/api/logs").
......@@ -77,17 +83,17 @@ type ApiHandler struct {
// Handles deploy API call.
func (apiHandler *ApiHandler) handleDeploy(request *restful.Request, response *restful.Response) {
cfg := new(AppDeployment)
if err := request.ReadEntity(cfg); err != nil {
appDeploymentSpec := new(AppDeploymentSpec)
if err := request.ReadEntity(appDeploymentSpec); err != nil {
handleInternalError(response, err)
return
}
if err := DeployApp(cfg, apiHandler.client); err != nil {
if err := DeployApp(appDeploymentSpec, apiHandler.client); err != nil {
handleInternalError(response, err)
return
}
response.WriteHeaderAndEntity(http.StatusCreated, cfg)
response.WriteHeaderAndEntity(http.StatusCreated, appDeploymentSpec)
}
// Handles get Replica Set list API call.
......@@ -118,8 +124,24 @@ func (apiHandler *ApiHandler) handleGetReplicaSetDetail(
response.WriteHeaderAndEntity(http.StatusCreated, result)
}
// Handles namespace creation API call.
func (apiHandler *ApiHandler) handleCreateNamespace(request *restful.Request,
response *restful.Response) {
namespaceSpec := new(NamespaceSpec)
if err := request.ReadEntity(namespaceSpec); err != nil {
handleInternalError(response, err)
return
}
if err := CreateNamespace(namespaceSpec, apiHandler.client); err != nil {
handleInternalError(response, err)
return
}
response.WriteHeaderAndEntity(http.StatusCreated, namespaceSpec)
}
// Handles get namespace list API call.
func (apiHandler *ApiHandler) handleGetNamespaceList(
func (apiHandler *ApiHandler) handleGetNamespaces(
request *restful.Request, response *restful.Response) {
result, err := GetNamespaceList(apiHandler.client)
......
......@@ -20,8 +20,8 @@ import (
"k8s.io/kubernetes/pkg/util"
)
// Configuration for an app deployment.
type AppDeployment struct {
// Specification for an app deployment.
type AppDeploymentSpec struct {
// Name of the application.
Name string `json:"name"`
......@@ -58,55 +58,55 @@ type PortMapping 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(deployment *AppDeployment, client *client.Client) error {
func DeployApp(spec *AppDeploymentSpec, client *client.Client) error {
podTemplate := &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"name": deployment.Name},
Labels: map[string]string{"name": spec.Name},
},
Spec: api.PodSpec{
Containers: []api.Container{{
Name: deployment.Name,
Image: deployment.ContainerImage,
Name: spec.Name,
Image: spec.ContainerImage,
}},
},
}
replicaSet := &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: deployment.Name,
Name: spec.Name,
},
Spec: api.ReplicationControllerSpec{
Replicas: deployment.Replicas,
Selector: map[string]string{"name": deployment.Name},
Replicas: spec.Replicas,
Selector: map[string]string{"name": spec.Name},
Template: podTemplate,
},
}
_, err := client.ReplicationControllers(deployment.Namespace).Create(replicaSet)
_, err := client.ReplicationControllers(spec.Namespace).Create(replicaSet)
if err != nil {
// TODO(bryk): Roll back created resources in case of error.
return err
}
if len(deployment.PortMappings) > 0 {
if len(spec.PortMappings) > 0 {
service := &api.Service{
ObjectMeta: api.ObjectMeta{
Name: deployment.Name,
Labels: map[string]string{"name": deployment.Name},
Name: spec.Name,
Labels: map[string]string{"name": spec.Name},
},
Spec: api.ServiceSpec{
Selector: map[string]string{"name": deployment.Name},
Selector: map[string]string{"name": spec.Name},
},
}
if deployment.IsExternal {
if spec.IsExternal {
service.Spec.Type = api.ServiceTypeLoadBalancer
} else {
service.Spec.Type = api.ServiceTypeNodePort
}
for _, portMapping := range deployment.PortMappings {
for _, portMapping := range spec.PortMappings {
servicePort :=
api.ServicePort{
Protocol: portMapping.Protocol,
......@@ -119,7 +119,7 @@ func DeployApp(deployment *AppDeployment, client *client.Client) error {
service.Spec.Ports = append(service.Spec.Ports, servicePort)
}
_, err = client.Services(deployment.Namespace).Create(service)
_, err = client.Services(spec.Namespace).Create(service)
// TODO(bryk): Roll back created resources in case of error.
......
......@@ -15,17 +15,37 @@
package main
import (
api "k8s.io/kubernetes/pkg/api"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
)
// Specification of namespace to be created.
type NamespaceSpec struct {
// Name of the namespace.
Name string `json:"name"`
}
// List of Namespaces in the cluster.
type NamespacesList struct {
// Unordered list of Namespaces.
Namespaces []string `json:"namespaces"`
}
// Creates namespace based on given specification.
func CreateNamespace(spec *NamespaceSpec, client *client.Client) error {
namespace := &api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: spec.Name,
},
}
_, err := client.Namespaces().Create(namespace)
return err
}
// Returns a list of all namespaces in the cluster.
func GetNamespaceList(client *client.Client) (*NamespacesList, error) {
list, err := client.Namespaces().List(labels.Everything(), fields.Everything())
......
......@@ -43,7 +43,7 @@ backendApi.PortMapping;
* namespace: string
* }}
*/
backendApi.AppDeployment;
backendApi.AppDeploymentSpec;
/**
* @typedef {{
......@@ -92,6 +92,13 @@ backendApi.ReplicaSetDetail;
*/
backendApi.ReplicaSetPod;
/**
* @typedef {{
* name: string
* }}
*/
backendApi.NamespaceSpec;
/**
* @typedef {{
* namespaces: !Array<string>
......
<!--
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.
-->
<md-dialog aria-label="Create a new namespace" layout="column" layout-padding>
<md-content layout-padding>
<h4 class="md-title" >Create a new namespace</h4>
<p>The new namespace will be added to the cluster.</p>
<!-- TODO(maciaszczykm): Missing detailed cluster info and learn more link. -->
<form id="namespaceForm" ng-submit="ctrl.createNamespace()">
<md-input-container class="md-block">
<label>Namespace name</label>
<input ng-model="ctrl.namespace" required>
</md-input-container>
<div class="md-actions" layout="row">
<md-button ng-disabled="ctrl.isDisabled()" class="md-primary" type="submit">OK</md-button>
<md-button ng-click="ctrl.cancel()">Cancel</md-button>
</div>
</form>
</md-content>
</md-dialog>
// 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.
/**
* Namespace creation dialog controller.
*
* @final
*/
export default class NamespaceDialogController {
/**
* @param {!md.$dialog} $mdDialog
* @param {!angular.$log} $log
* @param {!angular.$resource} $resource
* @param {!Array<string>} namespaces
* @ngInject
*/
constructor($mdDialog, $log, $resource, namespaces) {
/** @private {!md.$dialog} */
this.mdDialog_ = $mdDialog;
/** @private {!angular.$log} */
this.log_ = $log;
/** @private {!angular.$resource} */
this.resource_ = $resource;
/**
* List of available namespaces.
* @export {!Array<string>}
*/
this.namespaces = namespaces;
/** @export {string} */
this.namespace = '';
}
/**
* Returns true if new namespace name hasn't been filled by the user, i.e, is empty.
* @return {boolean}
*/
isDisabled() {
return !this.namespace || /^\s*$/.test(!this.namespace) ||
this.namespaces.indexOf(this.namespace) >= 0;
}
/** Cancels the new namespace form. */
cancel() { this.mdDialog_.cancel(); }
/**
* Creates new namespace based on the state of the controller.
*/
createNamespace() {
/** @type {!backendApi.NamespaceSpec} */
let namespaceSpec = {name: this.namespace};
/** @type {!angular.Resource<!backendApi.NamespaceSpec>} */
let resource = this.resource_('/api/namespaces');
resource.save(
namespaceSpec,
(savedConfig) => {
this.log_.info('Successfully created namespace:', savedConfig);
this.mdDialog_.hide(this.namespace);
},
(err) => {
this.log_.info('Error creating namespace:', err);
this.mdDialog_.hide();
});
}
}
// 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.
import NamespaceDialogController from 'deploy/createnamespace_controller';
/**
* Displays new namespace creation dialog.
*
* @param {!md.$dialog} mdDialog
* @param {!angular.Scope.Event} event
* @param {!Array<string>} namespaces
* @return {!angular.$q.Promise}
*/
export default function showNamespaceDialog(mdDialog, event, namespaces) {
return mdDialog.show({
controller: NamespaceDialogController,
controllerAs: 'ctrl',
clickOutsideToClose: true,
targetEvent: event,
templateUrl: '/deploy/createnamespace.html',
locals: {
namespaces: namespaces,
},
});
}
......@@ -62,6 +62,9 @@ limitations under the License.
<md-option ng-repeat="namespace in ctrl.namespaces" ng-value="namespace">
{{namespace}}
</md-option>
<md-option ng-click="ctrl.handleNamespaceDialog($event)">
Create a new namespace...
</md-option>
</md-select>
</md-input-container>
<md-switch ng-model="ctrl.isExternal" class="md-primary">
......
......@@ -14,6 +14,7 @@
import {stateName as zerostate} from 'zerostate/zerostate_state';
import {stateName as replicasetliststate} from 'replicasetlist/replicasetlist_state';
import showNamespaceDialog from 'deploy/createnamespace_dialog';
/**
* Controller for the deploy view.
......@@ -25,10 +26,11 @@ export default class DeployController {
* @param {!angular.$resource} $resource
* @param {!angular.$log} $log
* @param {!ui.router.$state} $state
* @param {!md.$dialog} $mdDialog
* @param {!backendApi.NamespaceList} namespaces
* @ngInject
*/
constructor($resource, $log, $state, namespaces) {
constructor($resource, $log, $state, $mdDialog, namespaces) {
/** @export {string} */
this.name = '';
......@@ -72,6 +74,9 @@ export default class DeployController {
/** @private {!ui.router.$state} */
this.state_ = $state;
/** @private {!md.$dialog} */
this.mdDialog_ = $mdDialog;
/** @private {boolean} */
this.isDeployInProgress_ = false;
}
......@@ -84,8 +89,8 @@ export default class DeployController {
deploy() {
// TODO(bryk): Validate input data before sending to the server.
/** @type {!backendApi.AppDeployment} */
let deployAppConfig = {
/** @type {!backendApi.AppDeploymentSpec} */
let appDeploymentSpec = {
containerImage: this.containerImage,
isExternal: this.isExternal,
name: this.name,
......@@ -94,12 +99,12 @@ export default class DeployController {
namespace: this.namespace,
};
/** @type {!angular.Resource<!backendApi.AppDeployment>} */
/** @type {!angular.Resource<!backendApi.AppDeploymentSpec>} */
let resource = this.resource_('/api/appdeployments');
this.isDeployInProgress_ = true;
resource.save(
deployAppConfig,
appDeploymentSpec,
(savedConfig) => {
this.isDeployInProgress_ = false;
this.log_.info('Successfully deployed application: ', savedConfig);
......@@ -111,6 +116,31 @@ export default class DeployController {
});
}
/**
* Displays new namespace creation dialog.
*
* @param {!angular.Scope.Event} event
*/
handleNamespaceDialog(event) {
showNamespaceDialog(this.mdDialog_, event, this.namespaces)
.then(
/**
* Handles namespace dialog result. If namespace was created successfully then it
* will be selected, otherwise first namespace will be selected.
*
* @param {string|undefined} answer
*/
(answer) => {
if (answer) {
this.namespace = answer;
this.namespaces = this.namespaces.concat(answer);
} else {
this.namespace = this.namespaces[0];
}
},
() => { this.namespace = this.namespaces[0]; });
}
/**
* Returns true when the deploy action should be enabled.
* @return {boolean}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册