Commit 82ec00ff by Jeffrey Phillips Committed by GitHub

Initial addition of common services/providers/constants (#1)

* Initial addition of common services/providers/constants

* Add travis build-ability, update .gitignore, fix incorrect case of Openshift

* remove unnecessary use of openshift-jvm in install-deps script

* Update dependencies, use range for angular components
parent 516b176c
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
node_modules
.tmp
.bundle
.idea
bower_components
phantomjsdriver.log
.DS_Store
test/test-results.xml
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": false,
"curly": true,
"eqeqeq": true,
"expr" : true,
"immed": true,
"indent": 2,
"latedef": "nofunc",
"newcap": false,
"noarg": true,
"quotmark": false,
"regexp": true,
"smarttabs": true,
"strict": true,
"sub" : true,
"undef": true,
"unused": true,
"globals": {
"jsyaml": false,
"angular": false,
"ansi_up": false,
"c3": false,
"d3": false,
"hawtioPluginLoader": false,
"HawtioCore": false,
"Logger" : false,
"LabelSelector": false,
"ResourceGroupVersion": false,
"moment": false,
"Messenger" : false,
"URI": false,
"Clipboard": false,
"$": false,
"_" : false,
"URITemplate": false,
"saveAs": false
}
}
language: node_js
node_js:
- "4"
cache:
directories:
- node_modules
- bower_components
before_script:
- make build
script:
- hack/test-headless.sh test
- hack/verify-dist.sh
module.exports = function (grunt) {
'use strict';
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
function init () {
grunt.initConfig({
availabletasks: {
tasks: {
options: {
descriptions: {
'help': 'Task list helper for your Grunt enabled projects.',
'clean': 'Deletes the content of the dist directory.',
'build': 'Builds the project (including documentation) into the dist directory. You can specify modules to be built as arguments (' +
'grunt build:buttons:notification) otherwise all available modules are built.',
'test': 'Executes the karma testsuite.'
},
groups: {
'Basic project tasks': ['help', 'clean', 'build', 'test']
}
}
}
},
clean: {
all: ['dist/*']
},
concat: {
options: {
separator: ';'
},
dist: {
src: ['src/**/*.module.js', 'src/**/*.js'],
dest: 'dist/origin-web-common.js'
}
},
karma: {
unit: {
configFile: 'test/karma.conf.js',
singleRun: true,
browsers: ['PhantomJS']
}
},
// ng-annotate tries to make the code safe for minification automatically
// by using the Angular long form for dependency injection.
ngAnnotate: {
dist: {
files: [{
src: 'dist/origin-web-common.js',
dest: 'dist/origin-web-common.js'
}]
}
},
uglify: {
options: {
mangle: false
},
build: {
files: {},
src: 'dist/origin-web-common.js',
dest: 'dist/origin-web-common.min.js'
}
}
});
// You can specify which modules to build as arguments of the build task.
grunt.registerTask('build', 'Create bootstrap build files', function () {
var concatSrc = [];
if (this.args.length) {
this.args.forEach(function (file) {
if (grunt.file.exists('./src/' + file)) {
grunt.log.ok('Adding ' + file + ' to the build queue.');
concatSrc.push('src/' + file + '/*.js');
} else {
grunt.fail.warn('Unable to build module \'' + file + '\'. The module doesn\'t exist.');
}
});
} else {
concatSrc = 'src/**/*.js';
}
grunt.task.run(['clean', 'concat', 'ngAnnotate', 'uglify:build', 'test']);
});
// Runs all the tasks of build with the exception of tests
grunt.registerTask('deploy', 'Prepares the project for deployment. Does not run unit tests', function () {
var concatSrc = 'src/**/*.js';
grunt.task.run(['clean', 'concat', 'ngAnnotate', 'uglify:build']);
});
grunt.registerTask('default', ['build']);
grunt.registerTask('test', ['karma']);
grunt.registerTask('check', ['test']);
grunt.registerTask('help', ['availabletasks']);
}
init({});
};
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2014 Red Hat, Inc.
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.
\ No newline at end of file
.DEFAULT_GOAL := build
# Pull in dependencies
#
# Examples:
# make install
install:
hack/install-deps.sh
.PHONY: install
# Clean up all dependencies
#
# Args:
# GRUNT_FLAGS: Extra flags to pass to 'grunt test'
#
# Example:
# make clean
# make test GRUNT_FLAGS='--stack'
clean:
grunt clean $(GRUNT_FLAGS)
hack/clean-deps.sh
.PHONY: clean
# Run `grunt build`
#
# Args:
# GRUNT_FLAGS: Extra flags to pass to 'grunt build'
#
# Examples:
# make build
# make build GRUNT_FLAGS='--verbose'
build: install
grunt build $(GRUNT_FLAGS)
.PHONY: build
# Run all tests
#
# Args:
# GRUNT_FLAGS: Extra flags to pass to 'grunt test'
#
# Examples:
# make test
# make test GRUNT_FLAGS='--gruntfile=~/special/Gruntfile.js'
test: build
hack/verify-dist.sh
hack/test-headless.sh test $(GRUNT_FLAGS)
hack/test-headless.sh test-integration $(GRUNT_FLAGS)
.PHONY: test
# origin-web-common OpenShift Common Web Services
\ No newline at end of file ==============================
The common services used for the [OpenShift Management Console](https://github.com/openshift/origin-web-console).
[![Build Status](https://travis-ci.org/openshift/origin-web-common.svg?branch=master)](https://travis-ci.org/openshift/origin-web-common)
Contributing
------------
#### Getting started
1. Install [Nodejs](http://nodejs.org/) and [npm](https://www.npmjs.org/)
2. Install [grunt-cli](http://gruntjs.com/installing-grunt) and [bower](http://bower.io/) by running `npm install -g grunt-cli bower` (may need to be run with sudo)
3. Install dependencies by running `npm install` and `bower install`
4. Build and run tests by running `npm build`
#### Before opening a pull request
Please configure your editor to use the
following settings to avoid common code inconsistencies and dirty
diffs:
* Use soft-tabs set to two spaces.
* Trim trailing white space on save.
* Set encoding to UTF-8.
* Add new line at end of files.
Or [configure your editor](http://editorconfig.org/#download) to
utilize [`.editorconfig`](https://github.com/openshift/origin-web-common/blob/master/.editorconfig),
which will apply these settings automatically.
Then:
1. If needed, run `grunt build` to update the files under the dist directory
2. Run the spec tests with `grunt test`
4. Rebase and squash changes to a single commit
{
"name": "origin-web-comon",
"version": "0.0.1",
"main": [
"dist/origin-web-common.js"
],
"authors": [
"Red Hat"
],
"description": "Library of common services and components for OpenShift Web Console.",
"license": "Apache-2.0",
"ignore": [
"Gruntfile.js",
"package.json",
".bowerrc",
".editorconfig",
".gitignore",
".jshintrc",
"src",
"README.md",
"bower.json",
".bower.json",
"grunt-ngdocs-index.tmpl"
],
"repository": {
"type": "git",
"url": "git://github.com/openshift/origin-web-common.git"
},
"dependencies": {
"angular": "1.5.11 < 2.0.0",
"angular-sanitize": "1.5.11 < 2.0.0",
"angular-utf8-base64": "0.0.5",
"hawtio-core": "2.0.11",
"hawtio-extension-service": "2.0.2",
"jquery": "2.1.4",
"kubernetes-container-terminal": "1.0.2",
"lodash": "3.10.1",
"messenger": "1.4.1",
"microplugin": "0.0.3",
"openshift-object-describer": "1.1.2",
"sifter": "0.4.1",
"uri.js": "1.18.0"
},
"devDependencies": {
"angular-mocks": "1.5.11 < 2.0.0",
"angular-scenario": "1.5.11 < 2.0.0"
},
"resolutions": {
"angular-sanitize": "~1.5.11",
"angular": "~1.5.11"
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
function ResourceGroupVersion(resource,group,version){return this.resource=resource,this.group=group,this.version=version,this}angular.module("openshiftCommon",["kubernetesUI","ab-base64"]).config(["AuthServiceProvider",function(AuthServiceProvider){AuthServiceProvider.UserStore("MemoryUserStore")}]).constant("API_CFG",_.get(window.OPENSHIFT_CONFIG,"api",{})).constant("APIS_CFG",_.get(window.OPENSHIFT_CONFIG,"apis",{})).constant("AUTH_CFG",_.get(window.OPENSHIFT_CONFIG,"auth",{})).constant("LOGGING_URL",_.get(window.OPENSHIFT_CONFIG,"loggingURL")).constant("METRICS_URL",_.get(window.OPENSHIFT_CONFIG,"metricsURL")).constant("LIMIT_REQUEST_OVERRIDES",_.get(window.OPENSHIFT_CONFIG,"limitRequestOverrides")).config(["$httpProvider","AuthServiceProvider","RedirectLoginServiceProvider","AUTH_CFG","API_CFG","kubernetesContainerSocketProvider",function($httpProvider,AuthServiceProvider,RedirectLoginServiceProvider,AUTH_CFG,API_CFG,kubernetesContainerSocketProvider){$httpProvider.interceptors.push("AuthInterceptor"),AuthServiceProvider.LoginService("RedirectLoginService"),AuthServiceProvider.LogoutService("DeleteTokenLogoutService"),AuthServiceProvider.UserStore("LocalStorageUserStore"),RedirectLoginServiceProvider.OAuthClientID(AUTH_CFG.oauth_client_id),RedirectLoginServiceProvider.OAuthAuthorizeURI(AUTH_CFG.oauth_authorize_uri),RedirectLoginServiceProvider.OAuthRedirectURI(URI(AUTH_CFG.oauth_redirect_base).segment("oauth").toString()),kubernetesContainerSocketProvider.WebSocketFactory="ContainerWebSocket"}]),hawtioPluginLoader.addModule("openshiftCommon"),hawtioPluginLoader.registerPreBootstrapTask(function(next){if(_.get(window,"OPENSHIFT_CONFIG.api.k8s.resources"))return void next();var api={k8s:{},openshift:{}},apis={},API_DISCOVERY_ERRORS=[],protocol=window.location.protocol+"//",k8sBaseURL=protocol+window.OPENSHIFT_CONFIG.api.k8s.hostPort+window.OPENSHIFT_CONFIG.api.k8s.prefix,k8sDeferred=$.get(k8sBaseURL+"/v1").done(function(data){api.k8s.v1=_.indexBy(data.resources,"name")}).fail(function(data,textStatus,jqXHR){API_DISCOVERY_ERRORS.push({data:data,textStatus:textStatus,xhr:jqXHR})}),osBaseURL=protocol+window.OPENSHIFT_CONFIG.api.openshift.hostPort+window.OPENSHIFT_CONFIG.api.openshift.prefix,osDeferred=$.get(osBaseURL+"/v1").done(function(data){api.openshift.v1=_.indexBy(data.resources,"name")}).fail(function(data,textStatus,jqXHR){API_DISCOVERY_ERRORS.push({data:data,textStatus:textStatus,xhr:jqXHR})}),apisBaseURL=protocol+window.OPENSHIFT_CONFIG.apis.hostPort+window.OPENSHIFT_CONFIG.apis.prefix,apisDeferred=$.get(apisBaseURL).then(function(data){var apisDeferredVersions=[];return _.each(data.groups,function(apiGroup){var group={name:apiGroup.name,preferredVersion:apiGroup.preferredVersion.version,versions:{}};apis[group.name]=group,_.each(apiGroup.versions,function(apiVersion){var versionStr=apiVersion.version;group.versions[versionStr]={version:versionStr,groupVersion:apiVersion.groupVersion},apisDeferredVersions.push($.get(apisBaseURL+"/"+apiVersion.groupVersion).done(function(data){group.versions[versionStr].resources=_.indexBy(data.resources,"name")}).fail(function(data,textStatus,jqXHR){API_DISCOVERY_ERRORS.push({data:data,textStatus:textStatus,xhr:jqXHR})}))})}),$.when.apply(this,apisDeferredVersions)},function(data,textStatus,jqXHR){API_DISCOVERY_ERRORS.push({data:data,textStatus:textStatus,xhr:jqXHR})}),discoveryFinished=function(){window.OPENSHIFT_CONFIG.api.k8s.resources=api.k8s,window.OPENSHIFT_CONFIG.api.openshift.resources=api.openshift,window.OPENSHIFT_CONFIG.apis.groups=apis,API_DISCOVERY_ERRORS.length&&(window.OPENSHIFT_CONFIG.apis.API_DISCOVERY_ERRORS=API_DISCOVERY_ERRORS),next()};$.when(k8sDeferred,osDeferred,apisDeferred).always(discoveryFinished)}),window.OPENSHIFT_CONFIG||(window.OPENSHIFT_CONFIG={apis:{hostPort:"localhost:8443",prefix:"/apis"},api:{openshift:{hostPort:"localhost:8443",prefix:"/oapi"},k8s:{hostPort:"localhost:8443",prefix:"/api"}},auth:{oauth_authorize_uri:"https://localhost:8443/oauth/authorize",oauth_redirect_base:"https://localhost:9000/dev-console",oauth_client_id:"openshift-web-console",logout_uri:""},loggingURL:"",metricsURL:""},window.OPENSHIFT_VERSION={openshift:"dev-mode",kubernetes:"dev-mode"}),ResourceGroupVersion.prototype.toString=function(){var s=this.resource;return this.group&&(s+="/"+this.group),this.version&&(s+="/"+this.version),s},ResourceGroupVersion.prototype.primaryResource=function(){if(!this.resource)return"";var i=this.resource.indexOf("/");return-1===i?this.resource:this.resource.substring(0,i)},ResourceGroupVersion.prototype.subresources=function(){var segments=(this.resource||"").split("/");return segments.shift(),segments},ResourceGroupVersion.prototype.equals=function(resource,group,version){return this.resource!==resource?!1:1===arguments.length?!0:this.group!==group?!1:2===arguments.length?!0:this.version!==version?!1:!0},angular.module("openshiftCommon").factory("APIService",["API_CFG","APIS_CFG","AuthService","Constants","Logger","$q","$http","$filter","$window",function(API_CFG,APIS_CFG,AuthService,Constants,Logger,$q,$http,$filter,$window){function normalizeResource(resource){if(!resource)return resource;var i=resource.indexOf("/");return-1===i?resource.toLowerCase():resource.substring(0,i).toLowerCase()+resource.substring(i)}function kindToResource(kind,humanize){if(!kind)return"";var resource=kind;if(humanize){var humanizeKind=$filter("humanizeKind");resource=humanizeKind(resource)}return resource=String(resource).toLowerCase(),"endpoints"===resource||"securitycontextconstraints"===resource||("s"===resource[resource.length-1]?resource+="es":"y"===resource[resource.length-1]?resource=resource.substring(0,resource.length-1)+"ies":resource+="s"),resource}var defaultVersion={"":"v1",extensions:"v1beta1"},toResourceGroupVersion=function(r){if(r instanceof ResourceGroupVersion)return r;var resource,group,version;return angular.isString(r)?(resource=normalizeResource(r),group="",version=defaultVersion[group]):r&&r.resource&&(resource=normalizeResource(r.resource),group=r.group||"",version=r.version||defaultVersion[group]||_.get(APIS_CFG,["groups",group,"preferredVersion"])),new ResourceGroupVersion(resource,group,version)},parseGroupVersion=function(apiVersion){if(!apiVersion)return void 0;var parts=apiVersion.split("/");return 1===parts.length?"v1"===parts[0]?{group:"",version:parts[0]}:{group:parts[0],version:""}:2===parts.length?{group:parts[0],version:parts[1]}:void Logger.warn('Invalid apiVersion "'+apiVersion+'"')},objectToResourceGroupVersion=function(apiObject){if(!apiObject||!apiObject.kind||!apiObject.apiVersion)return void 0;var resource=kindToResource(apiObject.kind);if(!resource)return void 0;var groupVersion=parseGroupVersion(apiObject.apiVersion);return groupVersion?new ResourceGroupVersion(resource,groupVersion.group,groupVersion.version):void 0},deriveTargetResource=function(resource,object){if(!resource||!object)return void 0;var objectResource=kindToResource(object.kind),objectGroupVersion=parseGroupVersion(object.apiVersion),resourceGroupVersion=toResourceGroupVersion(resource);return objectResource&&objectGroupVersion&&resourceGroupVersion?angular.isString(resource)?(resourceGroupVersion.equals(objectResource)&&(resourceGroupVersion.group=objectGroupVersion.group,resourceGroupVersion.version=objectGroupVersion.version),resourceGroupVersion):(resourceGroupVersion.equals(objectResource,objectGroupVersion.group)&&(resourceGroupVersion.version=objectGroupVersion.version),resourceGroupVersion):void 0},apiInfo=function(resource){if(APIS_CFG.API_DISCOVERY_ERRORS){var possibleCertFailure=_.every(APIS_CFG.API_DISCOVERY_ERRORS,function(error){return 0===_.get(error,"data.status")});return possibleCertFailure&&!AuthService.isLoggedIn()?void AuthService.withUser():void($window.location.href=URI("error").query({error_description:"Unable to load details about the server. If the problem continues, please contact your system administrator.",error:"API_DISCOVERY"}).toString())}resource=toResourceGroupVersion(resource);var primaryResource=resource.primaryResource();if(resource.group)return _.get(APIS_CFG,["groups",resource.group,"versions",resource.version,"resources",primaryResource])?{hostPort:APIS_CFG.hostPort,prefix:APIS_CFG.prefix,group:resource.group,version:resource.version}:void 0;var api;for(var apiName in API_CFG)if(api=API_CFG[apiName],_.get(api,["resources",resource.version,primaryResource]))return{hostPort:api.hostPort,prefix:api.prefix,version:resource.version};return void 0},invalidObjectKindOrVersion=function(apiObject){var kind="<none>",version="<none>";return apiObject&&apiObject.kind&&(kind=apiObject.kind),apiObject&&apiObject.apiVersion&&(version=apiObject.apiVersion),"Invalid kind ("+kind+") or API version ("+version+")"},unsupportedObjectKindOrVersion=function(apiObject){var kind="<none>",version="<none>";return apiObject&&apiObject.kind&&(kind=apiObject.kind),apiObject&&apiObject.apiVersion&&(version=apiObject.apiVersion),"The API version "+version+" for kind "+kind+" is not supported by this server"},calculateAvailableKinds=function(includeClusterScoped){var kinds=[],rejectedKinds=Constants.AVAILABLE_KINDS_BLACKLIST;return _.each(API_CFG,function(api){_.each(api.resources.v1,function(resource){if(resource.namespaced||includeClusterScoped){if(resource.name.indexOf("/")>=0||_.contains(rejectedKinds,resource.kind))return;kinds.push({kind:resource.kind})}})}),_.each(APIS_CFG.groups,function(group){var preferredVersion=defaultVersion[group.name]||group.preferredVersion;_.each(group.versions[preferredVersion].resources,function(resource){resource.name.indexOf("/")>=0||_.contains(rejectedKinds,resource.kind)||"autoscaling"===group.name&&"HorizontalPodAutoscaler"===resource.kind||"batch"===group.name&&"Job"===resource.kind||(resource.namespaced||includeClusterScoped)&&kinds.push({kind:resource.kind,group:group.name})})}),_.uniq(kinds,!1,function(value){return value.group+"/"+value.kind})},namespacedKinds=calculateAvailableKinds(!1),allKinds=calculateAvailableKinds(!0),availableKinds=function(includeClusterScoped){return includeClusterScoped?allKinds:namespacedKinds};return{toResourceGroupVersion:toResourceGroupVersion,parseGroupVersion:parseGroupVersion,objectToResourceGroupVersion:objectToResourceGroupVersion,deriveTargetResource:deriveTargetResource,kindToResource:kindToResource,apiInfo:apiInfo,invalidObjectKindOrVersion:invalidObjectKindOrVersion,unsupportedObjectKindOrVersion:unsupportedObjectKindOrVersion,availableKinds:availableKinds}}]),angular.module("openshiftCommon").provider("AuthService",function(){var _userStore="";this.UserStore=function(userStoreName){return userStoreName&&(_userStore=userStoreName),_userStore};var _loginService="";this.LoginService=function(loginServiceName){return loginServiceName&&(_loginService=loginServiceName),_loginService};var _logoutService="";this.LogoutService=function(logoutServiceName){return logoutServiceName&&(_logoutService=logoutServiceName),_logoutService};var loadService=function(injector,name,setter){if(name)return angular.isString(name)?injector.get(name):injector.invoke(name);throw setter+" not set"};this.$get=["$q","$injector","$log","$rootScope","Logger",function($q,$injector,$log,$rootScope,Logger){var authLogger=Logger.get("auth");authLogger.log("AuthServiceProvider.$get",arguments);var _loginCallbacks=$.Callbacks(),_logoutCallbacks=$.Callbacks(),_userChangedCallbacks=$.Callbacks(),_loginPromise=null,_logoutPromise=null,userStore=loadService($injector,_userStore,"AuthServiceProvider.UserStore()");userStore.available()||Logger.error("AuthServiceProvider.$get user store "+_userStore+" not available");var loginService=loadService($injector,_loginService,"AuthServiceProvider.LoginService()"),logoutService=loadService($injector,_logoutService,"AuthServiceProvider.LogoutService()");return{UserStore:function(){return userStore},isLoggedIn:function(){return!!userStore.getUser()},withUser:function(){var user=userStore.getUser();return user?($rootScope.user=user,authLogger.log("AuthService.withUser()",user),$q.when(user)):(authLogger.log("AuthService.withUser(), calling startLogin()"),this.startLogin())},setUser:function(user,token,ttl){authLogger.log("AuthService.setUser()",user,token,ttl);var oldUser=userStore.getUser();userStore.setUser(user,ttl),userStore.setToken(token,ttl),$rootScope.user=user;var oldName=oldUser&&oldUser.metadata&&oldUser.metadata.name,newName=user&&user.metadata&&user.metadata.name;oldName!==newName&&(authLogger.log("AuthService.setUser(), user changed",oldUser,user),_userChangedCallbacks.fire(user))},requestRequiresAuth:function(config){var requiresAuth=!!config.auth;return authLogger.log("AuthService.requestRequiresAuth()",config.url.toString(),requiresAuth),requiresAuth},addAuthToRequest:function(config){var token="";return config&&config.auth&&config.auth.token?(token=config.auth.token,authLogger.log("AuthService.addAuthToRequest(), using token from request config",token)):(token=userStore.getToken(),authLogger.log("AuthService.addAuthToRequest(), using token from user store",token)),token?("WATCH"===config.method?(config.url=URI(config.url).addQuery({access_token:token}).toString(),authLogger.log("AuthService.addAuthToRequest(), added token param",config.url)):(config.headers.Authorization="Bearer "+token,authLogger.log("AuthService.addAuthToRequest(), added token header",config.headers.Authorization)),!0):(authLogger.log("AuthService.addAuthToRequest(), no token available"),!1)},startLogin:function(){if(_loginPromise)return authLogger.log("Login already in progress"),_loginPromise;var self=this;return _loginPromise=loginService.login().then(function(result){self.setUser(result.user,result.token,result.ttl),_loginCallbacks.fire(result.user)})["catch"](function(err){Logger.error(err)})["finally"](function(){_loginPromise=null})},startLogout:function(){if(_logoutPromise)return authLogger.log("Logout already in progress"),_logoutPromise;var self=this,user=userStore.getUser(),token=userStore.getToken(),wasLoggedIn=this.isLoggedIn();return _logoutPromise=logoutService.logout(user,token).then(function(){authLogger.log("Logout service success")})["catch"](function(err){authLogger.error("Logout service error",err)})["finally"](function(){self.setUser(null,null);var isLoggedIn=self.isLoggedIn();wasLoggedIn&&!isLoggedIn&&_logoutCallbacks.fire(),_logoutPromise=null})},onLogin:function(callback){_loginCallbacks.add(callback)},onLogout:function(callback){_logoutCallbacks.add(callback)},onUserChanged:function(callback){_userChangedCallbacks.add(callback)}}}]}).factory("AuthInterceptor",["$q","AuthService",function($q,AuthService){var pendingRequestConfigs=[];return{request:function(config){if(!AuthService.requestRequiresAuth(config))return config;if(AuthService.addAuthToRequest(config))return config;if(config.auth&&config.auth.triggerLogin===!1)return config;var deferred=$q.defer();return pendingRequestConfigs.push([deferred,config,"request"]),AuthService.startLogin(),deferred.promise},responseError:function(rejection){var authConfig=rejection.config.auth||{};if(!AuthService.requestRequiresAuth(rejection.config))return $q.reject(rejection);if(authConfig.triggerLogin===!1)return $q.reject(rejection);var status=rejection.status;switch(status){case 401:var deferred=$q.defer();return pendingRequestConfigs.push([deferred,rejection.config,"responseError"]),AuthService.startLogin(),deferred.promise;default:return $q.reject(rejection)}}}}]),angular.module("openshiftCommon").factory("AuthorizationService",["$q","$cacheFactory","Logger","$interval","APIService","DataService",function($q,$cacheFactory,Logger,$interval,APIService,DataService){var currentProject=null,cachedRulesByProject=$cacheFactory("rulesCache",{number:10}),permissiveMode=!1,REVIEW_RESOURCES=["localresourceaccessreviews","localsubjectaccessreviews","resourceaccessreviews","selfsubjectrulesreviews","subjectaccessreviews"],normalizeRules=function(rules){var normalizedRules={};return _.each(rules,function(rule){_.each(rule.apiGroups,function(apiGroup){normalizedRules[apiGroup]||(normalizedRules[apiGroup]={}),_.each(rule.resources,function(resource){normalizedRules[apiGroup][resource]=rule.verbs})})}),normalizedRules},checkResource=function(resource){return"projectrequests"===resource||_.contains(resource,"/")||_.contains(REVIEW_RESOURCES,resource)?!1:!0},canAddToProjectCheck=function(rules){return _.some(rules,function(rule){return _.some(rule.resources,function(resource){return checkResource(resource)&&!_.isEmpty(_.intersection(rule.verbs,["*","create","update"]))})})},getProjectRules=function(projectName,forceRefresh){var deferred=$q.defer();currentProject=projectName;var projectRules=cachedRulesByProject.get(projectName),rulesResource="selfsubjectrulesreviews";if(!projectRules||projectRules.forceRefresh||forceRefresh)if(APIService.apiInfo(rulesResource)){Logger.log("AuthorizationService, loading user rules for "+projectName+" project");var object={kind:"SelfSubjectRulesReview",apiVersion:"v1"};DataService.create(rulesResource,null,object,{namespace:projectName}).then(function(data){var normalizedData=normalizeRules(data.status.rules),canUserAddToProject=canAddToProjectCheck(data.status.rules);cachedRulesByProject.put(projectName,{rules:normalizedData,canAddToProject:canUserAddToProject,forceRefresh:!1,cacheTimestamp:_.now()}),deferred.resolve()},function(){permissiveMode=!0,deferred.resolve()})}else Logger.log("AuthorizationService, resource 'selfsubjectrulesreviews' is not part of APIserver. Switching into permissive mode."),permissiveMode=!0,deferred.resolve();else Logger.log("AuthorizationService, using cached rules for "+projectName+" project"),_.now()-projectRules.cacheTimestamp>=6e5&&(projectRules.forceRefresh=!0),deferred.resolve();return deferred.promise},getRulesForProject=function(projectName){return _.get(cachedRulesByProject.get(projectName||currentProject),["rules"])},_canI=function(rules,verb,group,resource){var resources=rules[group];if(!resources)return!1;var verbs=resources[resource];return verbs?_.contains(verbs,verb)||_.contains(verbs,"*"):!1},canI=function(resource,verb,projectName){if(permissiveMode)return!0;var r=APIService.toResourceGroupVersion(resource),rules=getRulesForProject(projectName||currentProject);return rules?_canI(rules,verb,r.group,r.resource)||_canI(rules,verb,"*","*")||_canI(rules,verb,r.group,"*")||_canI(rules,verb,"*",r.resource):!1},canIAddToProject=function(projectName){return permissiveMode?!0:!!_.get(cachedRulesByProject.get(projectName||currentProject),["canAddToProject"])};return{checkResource:checkResource,getProjectRules:getProjectRules,canI:canI,canIAddToProject:canIAddToProject,getRulesForProject:getRulesForProject}}]),angular.module("openshiftCommon").factory("base64util",function(){return{pad:function(data){if(!data)return"";switch(data.length%4){case 1:return data+"===";case 2:return data+"==";case 3:return data+"=";default:return data}}}}),angular.module("openshiftCommon").factory("Constants",function(){var constants=_.clone(window.OPENSHIFT_CONSTANTS||{}),version=_.clone(window.OPENSHIFT_VERSION||{});return constants.VERSION=version,constants}),angular.module("openshiftCommon").factory("DataService",["$cacheFactory","$http","$ws","$rootScope","$q","API_CFG","APIService","Notification","Logger","$timeout","base64","base64util",function($cacheFactory,$http,$ws,$rootScope,$q,API_CFG,APIService,Notification,Logger,$timeout,base64,base64util){function Data(array){this._data={},this._objectsByAttribute(array,"metadata.name",this._data)}function _objectByAttribute(obj,attr,map,action){for(var subAttrs=attr.split("."),attrValue=obj,i=0;i<subAttrs.length;i++)if(attrValue=attrValue[subAttrs[i]],void 0===attrValue)return;if($.isArray(attrValue));else if($.isPlainObject(attrValue))for(var key in attrValue){var val=attrValue[key];map[key]||(map[key]={}),"DELETED"===action?delete map[key][val]:map[key][val]=obj}else"DELETED"===action?delete map[attrValue]:map[attrValue]=obj}function DataService(){this._listDeferredMap={},this._watchCallbacksMap={},this._watchObjectCallbacksMap={},this._watchOperationMap={},this._listOperationMap={},this._resourceVersionMap={},this._dataCache=$cacheFactory("dataCache",{number:25}),this._immutableDataCache=$cacheFactory("immutableDataCache",{number:50}),this._watchOptionsMap={},this._watchWebsocketsMap={},this._watchPollTimeoutsMap={},this._websocketEventsMap={};var self=this;$rootScope.$on("$routeChangeStart",function(event,next,current){self._websocketEventsMap={}})}function isTooManyRecentEvents(events){var recentDuration=3e4;return events.length>=maxWebsocketEvents&&Date.now()-events[0].time<recentDuration}function isTooManyConsecutiveCloses(events){var maxConsecutiveCloseEvents=5;if(events.length<maxConsecutiveCloseEvents)return!1;for(var i=events.length-maxConsecutiveCloseEvents;i<events.length;i++)if("close"!==events[i].type)return!1;return!0}Data.prototype.by=function(attr){if("metadata.name"===attr)return this._data;var map={};for(var key in this._data)_objectByAttribute(this._data[key],attr,map,null);return map},Data.prototype.update=function(object,action){_objectByAttribute(object,"metadata.name",this._data,action)},Data.prototype._objectsByAttribute=function(objects,attr,map,actions){angular.forEach(objects,function(obj,key){_objectByAttribute(obj,attr,map,actions?actions[key]:null)})},DataService.prototype.list=function(resource,context,callback,opts){resource=APIService.toResourceGroupVersion(resource);var key=this._uniqueKey(resource,null,context,_.get(opts,"http.params")),deferred=this._listDeferred(key);return callback&&deferred.promise.then(callback),this._isCached(key)?deferred.resolve(this._data(key)):this._listInFlight(key)||this._startListOp(resource,context,opts),deferred.promise},DataService.prototype["delete"]=function(resource,name,context,opts){resource=APIService.toResourceGroupVersion(resource),opts=opts||{};var data,deferred=$q.defer(),self=this,headers={};return _.has(opts,"gracePeriodSeconds")&&(data={kind:"DeleteOptions",apiVersion:"v1",gracePeriodSeconds:opts.gracePeriodSeconds},headers["Content-Type"]="application/json"),this._getNamespace(resource,context,opts).then(function(ns){$http(angular.extend({method:"DELETE",auth:{},data:data,headers:headers,url:self._urlForResource(resource,name,context,!1,ns)},opts.http||{})).success(function(data,status,headerFunc,config,statusText){deferred.resolve(data)}).error(function(data,status,headers,config){deferred.reject({data:data,status:status,headers:headers,config:config})})}),deferred.promise},DataService.prototype.update=function(resource,name,object,context,opts){resource=APIService.deriveTargetResource(resource,object),opts=opts||{};var deferred=$q.defer(),self=this;return this._getNamespace(resource,context,opts).then(function(ns){$http(angular.extend({method:"PUT",auth:{},data:object,url:self._urlForResource(resource,name,context,!1,ns)},opts.http||{})).success(function(data,status,headerFunc,config,statusText){deferred.resolve(data)}).error(function(data,status,headers,config){deferred.reject({data:data,status:status,headers:headers,config:config})})}),deferred.promise},DataService.prototype.create=function(resource,name,object,context,opts){resource=APIService.deriveTargetResource(resource,object),opts=opts||{};var deferred=$q.defer(),self=this;return this._getNamespace(resource,context,opts).then(function(ns){$http(angular.extend({method:"POST",auth:{},data:object,url:self._urlForResource(resource,name,context,!1,ns)},opts.http||{})).success(function(data,status,headerFunc,config,statusText){deferred.resolve(data)}).error(function(data,status,headers,config){deferred.reject({data:data,status:status,headers:headers,config:config})})}),deferred.promise},DataService.prototype.batch=function(objects,context,action,opts){function _checkDone(){0===remaining&&deferred.resolve({success:successResults,failure:failureResults})}var deferred=$q.defer(),successResults=[],failureResults=[],self=this,remaining=objects.length;return action=action||"create",_.each(objects,function(object){var resource=APIService.objectToResourceGroupVersion(object);if(!resource)return failureResults.push({object:object,data:{message:APIService.invalidObjectKindOrVersion(object)}}),remaining--,void _checkDone();if(!APIService.apiInfo(resource))return failureResults.push({object:object,data:{message:APIService.unsupportedObjectKindOrVersion(object)}}),remaining--,void _checkDone();var success=function(data){data.object=object,successResults.push(data),remaining--,_checkDone()},failure=function(data){data.object=object,failureResults.push(data),remaining--,_checkDone()};switch(action){case"create":self.create(resource,null,object,context,opts).then(success,failure);break;case"update":self.update(resource,object.metadata.name,object,context,opts).then(success,failure);break;default:return deferred.reject({data:"Invalid '"+action+"' action.",status:400,headers:function(){return null},config:{},object:object})}}),deferred.promise},DataService.prototype.get=function(resource,name,context,opts){resource=APIService.toResourceGroupVersion(resource),opts=opts||{};var key=this._uniqueKey(resource,name,context,_.get(opts,"http.params"));!!opts.force;delete opts.force;var deferred=$q.defer(),existingImmutableData=this._immutableData(key);if(this._hasImmutable(resource,existingImmutableData,name))$timeout(function(){deferred.resolve(existingImmutableData.by("metadata.name")[name])},0);else{var self=this;this._getNamespace(resource,context,opts).then(function(ns){$http(angular.extend({method:"GET",auth:{},url:self._urlForResource(resource,name,context,!1,ns)},opts.http||{})).success(function(data,status,headerFunc,config,statusText){self._isImmutable(resource)&&(existingImmutableData?existingImmutableData.update(data,"ADDED"):self._immutableData(key,[data])),deferred.resolve(data)}).error(function(data,status,headers,config){if(opts.errorNotification!==!1){var msg="Failed to get "+resource+"/"+name;0!==status&&(msg+=" ("+status+")"),Notification.error(msg)}deferred.reject({data:data,status:status,headers:headers,config:config})})})}return deferred.promise},DataService.prototype.createStream=function(resource,name,context,opts,isRaw){var self=this;resource=APIService.toResourceGroupVersion(resource);var stream,protocols=isRaw?"binary.k8s.io":"base64.binary.k8s.io",identifier="stream_",openQueue={},messageQueue={},closeQueue={},errorQueue={},makeStream=function(){return self._getNamespace(resource,context,{}).then(function(params){var cumulativeBytes=0;return $ws({url:self._urlForResource(resource,name,context,!0,_.extend(params,opts)),auth:{},onopen:function(evt){_.each(openQueue,function(fn){fn(evt)})},onmessage:function(evt){if(!_.isString(evt.data))return void Logger.log("log stream response is not a string",evt.data);var message;isRaw||(message=base64.decode(base64util.pad(evt.data)),cumulativeBytes+=message.length),_.each(messageQueue,function(fn){isRaw?fn(evt.data):fn(message,evt.data,cumulativeBytes)})},onclose:function(evt){_.each(closeQueue,function(fn){fn(evt)})},onerror:function(evt){_.each(errorQueue,function(fn){fn(evt)})},protocols:protocols}).then(function(ws){return Logger.log("Streaming pod log",ws),ws})})};return{onOpen:function(fn){if(_.isFunction(fn)){var id=_.uniqueId(identifier);return openQueue[id]=fn,id}},onMessage:function(fn){if(_.isFunction(fn)){var id=_.uniqueId(identifier);return messageQueue[id]=fn,id}},onClose:function(fn){if(_.isFunction(fn)){var id=_.uniqueId(identifier);return closeQueue[id]=fn,id}},onError:function(fn){if(_.isFunction(fn)){var id=_.uniqueId(identifier);return errorQueue[id]=fn,id}},remove:function(id){openQueue[id]&&delete openQueue[id],messageQueue[id]&&delete messageQueue[id],closeQueue[id]&&delete closeQueue[id],errorQueue[id]&&delete errorQueue[id]},start:function(){return stream=makeStream()},stop:function(){stream.then(function(ws){ws.close()})}}},DataService.prototype.watch=function(resource,context,callback,opts){resource=APIService.toResourceGroupVersion(resource),opts=opts||{};var key=this._uniqueKey(resource,null,context,_.get(opts,"http.params"));if(callback)this._watchCallbacks(key).add(callback);else if(!this._watchCallbacks(key).has())return{};var existingWatchOpts=this._watchOptions(key);if(existingWatchOpts){if(!!existingWatchOpts.poll!=!!opts.poll)throw"A watch already exists for "+resource+" with a different polling option."}else this._watchOptions(key,opts);var self=this;if(this._isCached(key))callback&&$timeout(function(){callback(self._data(key))},0);else{if(callback){var resourceVersion=this._resourceVersion(key);this._data(key)&&$timeout(function(){resourceVersion===self._resourceVersion(key)&&callback(self._data(key))},0)}this._listInFlight(key)||this._startListOp(resource,context,opts)}return{resource:resource,context:context,callback:callback,opts:opts}},DataService.prototype.watchObject=function(resource,name,context,callback,opts){resource=APIService.toResourceGroupVersion(resource),opts=opts||{};var wrapperCallback,key=this._uniqueKey(resource,name,context,_.get(opts,"http.params"));if(callback){this._watchObjectCallbacks(key).add(callback);var self=this;wrapperCallback=function(items,event,item){if(item&&item.metadata.name===name)self._watchObjectCallbacks(key).fire(item,event);else if(!item){var itemsByName=items.by("metadata.name");itemsByName[name]&&self._watchObjectCallbacks(key).fire(itemsByName[name])}}}else if(!this._watchObjectCallbacks(key).has())return{};var handle=this.watch(resource,context,wrapperCallback,opts);return handle.objectCallback=callback,handle.objectName=name,handle},DataService.prototype.unwatch=function(handle){var resource=handle.resource,objectName=handle.objectName,context=handle.context,callback=handle.callback,objectCallback=handle.objectCallback,opts=handle.opts,key=this._uniqueKey(resource,null,context,_.get(opts,"http.params"));if(objectCallback&&objectName){var objectKey=this._uniqueKey(resource,objectName,context,_.get(opts,"http.params")),objCallbacks=this._watchObjectCallbacks(objectKey);objCallbacks.remove(objectCallback)}var callbacks=this._watchCallbacks(key);if(callback&&callbacks.remove(callback),!callbacks.has()){if(opts&&opts.poll)clearTimeout(this._watchPollTimeouts(key)),this._watchPollTimeouts(key,null);else if(this._watchWebsockets(key)){var ws=this._watchWebsockets(key);ws.shouldClose=!0,ws.close(),this._watchWebsockets(key,null)}this._watchInFlight(key,!1),this._watchOptions(key,null)}},DataService.prototype.unwatchAll=function(handles){for(var i=0;i<handles.length;i++)this.unwatch(handles[i])},DataService.prototype._watchCallbacks=function(key){return this._watchCallbacksMap[key]||(this._watchCallbacksMap[key]=$.Callbacks()),this._watchCallbacksMap[key]},DataService.prototype._watchObjectCallbacks=function(key){return this._watchObjectCallbacksMap[key]||(this._watchObjectCallbacksMap[key]=$.Callbacks()),this._watchObjectCallbacksMap[key]},DataService.prototype._listDeferred=function(key){return this._listDeferredMap[key]||(this._listDeferredMap[key]=$q.defer()),this._listDeferredMap[key]},DataService.prototype._watchInFlight=function(key,op){return op||op===!1?void(this._watchOperationMap[key]=op):this._watchOperationMap[key]},DataService.prototype._listInFlight=function(key,op){return op||op===!1?void(this._listOperationMap[key]=op):this._listOperationMap[key]},DataService.prototype._resourceVersion=function(key,rv){return rv?void(this._resourceVersionMap[key]=rv):this._resourceVersionMap[key]},DataService.prototype._data=function(key,data){return data?this._dataCache.put(key,new Data(data)):this._dataCache.get(key)},DataService.prototype._immutableData=function(key,data){return data?this._immutableDataCache.put(key,new Data(data)):this._immutableDataCache.get(key)},DataService.prototype._isCached=function(key){return this._watchInFlight(key)&&this._resourceVersion(key)&&!!this._data(key)},DataService.prototype._watchOptions=function(key,opts){
return void 0===opts?this._watchOptionsMap[key]:void(this._watchOptionsMap[key]=opts)},DataService.prototype._watchPollTimeouts=function(key,timeout){return timeout?void(this._watchPollTimeoutsMap[key]=timeout):this._watchPollTimeoutsMap[key]},DataService.prototype._watchWebsockets=function(key,timeout){return timeout?void(this._watchWebsocketsMap[key]=timeout):this._watchWebsocketsMap[key]};var maxWebsocketEvents=10;DataService.prototype._addWebsocketEvent=function(key,eventType){var events=this._websocketEventsMap[key];for(events||(events=this._websocketEventsMap[key]=[]),events.push({type:eventType,time:Date.now()});events.length>maxWebsocketEvents;)events.shift()},DataService.prototype._isTooManyWebsocketRetries=function(key){var events=this._websocketEventsMap[key];return events?isTooManyRecentEvents(events)?(Logger.log("Too many websocket open or close events for resource/context in a short period",key,events),!0):isTooManyConsecutiveCloses(events)?(Logger.log("Too many consecutive websocket close events for resource/context",key,events),!0):!1:!1};var paramsForKey=function(params){var keys=_.keysIn(_.pick(params,["fieldSelector","labelSelector"])).sort();return _.reduce(keys,function(result,key,i){return result+key+"="+encodeURIComponent(params[key])+(i<keys.length-1?"&":"")},"?")};DataService.prototype._uniqueKey=function(resource,name,context,params){var ns=context&&context.namespace||_.get(context,"project.metadata.name")||context.projectName;return this._urlForResource(resource,name,context,null,angular.extend({},{},{namespace:ns})).toString()+paramsForKey(params||{})},DataService.prototype._startListOp=function(resource,context,opts){opts=opts||{};var key=this._uniqueKey(resource,null,context,_.get(opts,"http.params"));this._listInFlight(key,!0);var self=this;context.projectPromise&&!resource.equals("projects")?context.projectPromise.done(function(project){$http(angular.extend({method:"GET",auth:{},url:self._urlForResource(resource,null,context,!1,{namespace:project.metadata.name})},opts.http||{})).success(function(data,status,headerFunc,config,statusText){self._listOpComplete(key,resource,context,opts,data)}).error(function(data,status,headers,config){self._listInFlight(key,!1);var deferred=self._listDeferred(key);if(delete self._listDeferredMap[key],deferred.reject(data,status,headers,config),_.get(opts,"errorNotification",!0)){var msg="Failed to list "+resource;0!==status&&(msg+=" ("+status+")"),Notification.error(msg)}})}):$http({method:"GET",auth:{},url:this._urlForResource(resource,null,context)}).success(function(data,status,headerFunc,config,statusText){self._listOpComplete(key,resource,context,opts,data)}).error(function(data,status,headers,config){self._listInFlight(key,!1);var deferred=self._listDeferred(key);if(delete self._listDeferredMap[key],deferred.reject(data,status,headers,config),_.get(opts,"errorNotification",!0)){var msg="Failed to list "+resource;0!==status&&(msg+=" ("+status+")"),Notification.error(msg)}})},DataService.prototype._listOpComplete=function(key,resource,context,opts,data){data.items||console.warn("List request for "+resource+" returned a null items array. This is an invalid API response.");var items=data.items||[];data.kind&&data.kind.indexOf("List")===data.kind.length-4&&angular.forEach(items,function(item){item.kind||(item.kind=data.kind.slice(0,-4)),item.apiVersion||(item.apiVersion=data.apiVersion)}),this._listInFlight(key,!1);var deferred=this._listDeferred(key);if(delete this._listDeferredMap[key],this._resourceVersion(key,data.resourceVersion||data.metadata.resourceVersion),this._data(key,items),deferred.resolve(this._data(key)),this._watchCallbacks(key).fire(this._data(key)),this._watchCallbacks(key).has()){var watchOpts=this._watchOptions(key)||{};watchOpts.poll?(this._watchInFlight(key,!0),this._watchPollTimeouts(key,setTimeout($.proxy(this,"_startListOp",resource,context),watchOpts.pollInterval||5e3))):this._watchInFlight(key)||this._startWatchOp(key,resource,context,opts,this._resourceVersion(key))}},DataService.prototype._startWatchOp=function(key,resource,context,opts,resourceVersion){if(this._watchInFlight(key,!0),$ws.available()){var self=this,params=_.get(opts,"http.params")||{};params.watch=!0,resourceVersion&&(params.resourceVersion=resourceVersion),context.projectPromise&&!resource.equals("projects")?context.projectPromise.done(function(project){params.namespace=project.metadata.name,$ws({method:"WATCH",url:self._urlForResource(resource,null,context,!0,params),auth:{},onclose:$.proxy(self,"_watchOpOnClose",resource,context,opts),onmessage:$.proxy(self,"_watchOpOnMessage",resource,context,opts),onopen:$.proxy(self,"_watchOpOnOpen",resource,context,opts)}).then(function(ws){Logger.log("Watching",ws),self._watchWebsockets(key,ws)})}):$ws({method:"WATCH",url:self._urlForResource(resource,null,context,!0,params),auth:{},onclose:$.proxy(self,"_watchOpOnClose",resource,context,opts),onmessage:$.proxy(self,"_watchOpOnMessage",resource,context,opts),onopen:$.proxy(self,"_watchOpOnOpen",resource,context,opts)}).then(function(ws){Logger.log("Watching",ws),self._watchWebsockets(key,ws)})}},DataService.prototype._watchOpOnOpen=function(resource,context,opts,event){Logger.log("Websocket opened for resource/context",resource,context);var key=this._uniqueKey(resource,null,context,_.get(opts,"http.params"));this._addWebsocketEvent(key,"open")},DataService.prototype._watchOpOnMessage=function(resource,context,opts,event){var key=this._uniqueKey(resource,null,context,_.get(opts,"http.params"));try{var eventData=$.parseJSON(event.data);if("ERROR"==eventData.type)return Logger.log("Watch window expired for resource/context",resource,context),void(event.target&&(event.target.shouldRelist=!0));"DELETED"===eventData.type&&eventData.object&&eventData.object.metadata&&!eventData.object.metadata.deletionTimestamp&&(eventData.object.metadata.deletionTimestamp=(new Date).toISOString()),eventData.object&&this._resourceVersion(key,eventData.object.resourceVersion||eventData.object.metadata.resourceVersion),this._data(key).update(eventData.object,eventData.type);var self=this;$timeout(function(){self._watchCallbacks(key).fire(self._data(key),eventData.type,eventData.object)},0)}catch(e){Logger.error("Error processing message",resource,event.data)}},DataService.prototype._watchOpOnClose=function(resource,context,opts,event){var eventWS=event.target,key=this._uniqueKey(resource,null,context,_.get(opts,"http.params"));if(!eventWS)return void Logger.log("Skipping reopen, no eventWS in event",event);var registeredWS=this._watchWebsockets(key);if(!registeredWS)return void Logger.log("Skipping reopen, no registeredWS for resource/context",resource,context);if(eventWS!==registeredWS)return void Logger.log("Skipping reopen, eventWS does not match registeredWS",eventWS,registeredWS);if(this._watchInFlight(key,!1),eventWS.shouldClose)return void Logger.log("Skipping reopen, eventWS was explicitly closed",eventWS);if(event.wasClean)return void Logger.log("Skipping reopen, clean close",event);if(!this._watchCallbacks(key).has())return void Logger.log("Skipping reopen, no listeners registered for resource/context",resource,context);if(this._isTooManyWebsocketRetries(key))return void(_.get(opts,"errorNotification",!0)&&Notification.error("Server connection interrupted.",{id:"websocket_retry_halted",mustDismiss:!0,actions:{refresh:{label:"Refresh",action:function(){window.location.reload()}}}}));if(this._addWebsocketEvent(key,"close"),eventWS.shouldRelist){Logger.log("Relisting for resource/context",resource,context);var self=this;return void setTimeout(function(){self.watch(resource,context)},2e3)}Logger.log("Rewatching for resource/context",resource,context),this._watchInFlight(key,!0),setTimeout($.proxy(this,"_startWatchOp",key,resource,context,opts,this._resourceVersion(key)),2e3)};var URL_ROOT_TEMPLATE="{protocol}://{+hostPort}{+prefix}{/group}/{version}/",URL_GET_LIST=URL_ROOT_TEMPLATE+"{resource}{?q*}",URL_OBJECT=URL_ROOT_TEMPLATE+"{resource}/{name}{/subresource*}{?q*}",URL_NAMESPACED_GET_LIST=URL_ROOT_TEMPLATE+"namespaces/{namespace}/{resource}{?q*}",URL_NAMESPACED_OBJECT=URL_ROOT_TEMPLATE+"namespaces/{namespace}/{resource}/{name}{/subresource*}{?q*}";DataService.prototype._urlForResource=function(resource,name,context,isWebsocket,params){var apiInfo=APIService.apiInfo(resource);if(!apiInfo)return Logger.error("_urlForResource called with unknown resource",resource,arguments),null;var protocol;params=params||{},protocol=isWebsocket?"http:"===window.location.protocol?"ws":"wss":"http:"===window.location.protocol?"http":"https",context&&context.namespace&&!params.namespace&&(params.namespace=context.namespace);var namespaceInPath=params.namespace,namespace=null;namespaceInPath&&(namespace=params.namespace,params=angular.copy(params),delete params.namespace);var template,templateOptions={protocol:protocol,hostPort:apiInfo.hostPort,prefix:apiInfo.prefix,group:apiInfo.group,version:apiInfo.version,resource:resource.primaryResource(),subresource:resource.subresources(),name:name,namespace:namespace,q:params};return template=name?namespaceInPath?URL_NAMESPACED_OBJECT:URL_OBJECT:namespaceInPath?URL_NAMESPACED_GET_LIST:URL_GET_LIST,URI.expand(template,templateOptions).toString()},DataService.prototype.url=function(options){if(options&&options.resource){var opts=angular.copy(options);delete opts.resource,delete opts.group,delete opts.version,delete opts.name,delete opts.isWebsocket;var resource=APIService.toResourceGroupVersion({resource:options.resource,group:options.group,version:options.version});return this._urlForResource(resource,options.name,null,!!options.isWebsocket,opts)}return null},DataService.prototype.openshiftAPIBaseUrl=function(){var protocol="http:"===window.location.protocol?"http":"https",hostPort=API_CFG.openshift.hostPort;return new URI({protocol:protocol,hostname:hostPort}).toString()};var IMMUTABLE_RESOURCE={imagestreamimages:!0};return DataService.prototype._isImmutable=function(resource){return!!IMMUTABLE_RESOURCE[resource.resource]},DataService.prototype._hasImmutable=function(resource,existingData,name){return this._isImmutable(resource)&&existingData&&existingData.by("metadata.name")[name]},DataService.prototype._getNamespace=function(resource,context,opts){var deferred=$q.defer();return opts.namespace?deferred.resolve({namespace:opts.namespace}):context.projectPromise&&!resource.equals("projects")?context.projectPromise.done(function(project){deferred.resolve({namespace:project.metadata.name})}):deferred.resolve(null),deferred.promise},new DataService}]),angular.module("openshiftCommon").provider("DeleteTokenLogoutService",function(){this.$get=["$q","$injector","Logger",function($q,$injector,Logger){var authLogger=Logger.get("auth");return{logout:function(user,token){if(authLogger.log("DeleteTokenLogoutService.logout()",user,token),!token)return authLogger.log("DeleteTokenLogoutService, no token, returning immediately"),$q.when({});var DataService=$injector.get("DataService"),opts={http:{auth:{token:token,triggerLogin:!1}}};return DataService["delete"]("oauthaccesstokens",token,{},opts)}}}]}),angular.module("openshiftCommon").provider("Logger",function(){this.$get=function(){var OSLogger=Logger.get("OpenShift"),logger={get:function(name){var logger=Logger.get("OpenShift/"+name),logLevel="OFF";return localStorage&&(logLevel=localStorage["OpenShiftLogLevel."+name]||logLevel),logger.setLevel(Logger[logLevel]),logger},log:function(){OSLogger.log.apply(OSLogger,arguments)},info:function(){OSLogger.info.apply(OSLogger,arguments)},debug:function(){OSLogger.debug.apply(OSLogger,arguments)},warn:function(){OSLogger.warn.apply(OSLogger,arguments)},error:function(){OSLogger.error.apply(OSLogger,arguments)}},logLevel="ERROR";return localStorage&&(logLevel=localStorage["OpenShiftLogLevel.main"]||logLevel),OSLogger.setLevel(Logger[logLevel]),logger}}),angular.module("openshiftCommon").provider("MemoryUserStore",function(){this.$get=["Logger",function(Logger){var authLogger=Logger.get("auth"),_user=null,_token=null;return{available:function(){return!0},getUser:function(){return authLogger.log("MemoryUserStore.getUser",_user),_user},setUser:function(user,ttl){authLogger.log("MemoryUserStore.setUser",user),_user=user},getToken:function(){return authLogger.log("MemoryUserStore.getToken",_token),_token},setToken:function(token,ttl){authLogger.log("MemoryUserStore.setToken",token),_token=token}}}]}).provider("SessionStorageUserStore",function(){this.$get=["Logger",function(Logger){var authLogger=Logger.get("auth"),userkey="SessionStorageUserStore.user",tokenkey="SessionStorageUserStore.token";return{available:function(){try{var x=String((new Date).getTime());sessionStorage["SessionStorageUserStore.test"]=x;var y=sessionStorage["SessionStorageUserStore.test"];return sessionStorage.removeItem("SessionStorageUserStore.test"),x===y}catch(e){return!1}},getUser:function(){try{var user=JSON.parse(sessionStorage[userkey]);return authLogger.log("SessionStorageUserStore.getUser",user),user}catch(e){return authLogger.error("SessionStorageUserStore.getUser",e),null}},setUser:function(user,ttl){user?(authLogger.log("SessionStorageUserStore.setUser",user),sessionStorage[userkey]=JSON.stringify(user)):(authLogger.log("SessionStorageUserStore.setUser",user,"deleting"),sessionStorage.removeItem(userkey))},getToken:function(){try{var token=sessionStorage[tokenkey];return authLogger.log("SessionStorageUserStore.getToken",token),token}catch(e){return authLogger.error("SessionStorageUserStore.getToken",e),null}},setToken:function(token,ttl){token?(authLogger.log("SessionStorageUserStore.setToken",token),sessionStorage[tokenkey]=token):(authLogger.log("SessionStorageUserStore.setToken",token,"deleting"),sessionStorage.removeItem(tokenkey))}}}]}).provider("LocalStorageUserStore",function(){this.$get=["Logger",function(Logger){var authLogger=Logger.get("auth"),userkey="LocalStorageUserStore.user",tokenkey="LocalStorageUserStore.token",ttlKey=function(key){return key+".ttl"},setTTL=function(key,ttl){if(ttl){var expires=(new Date).getTime()+1e3*ttl;localStorage[ttlKey(key)]=expires,authLogger.log("LocalStorageUserStore.setTTL",key,ttl,new Date(expires).toString())}else localStorage.removeItem(ttlKey(key)),authLogger.log("LocalStorageUserStore.setTTL deleting",key)},isTTLExpired=function(key){var ttl=localStorage[ttlKey(key)];if(!ttl)return!1;var expired=parseInt(ttl)<(new Date).getTime();return authLogger.log("LocalStorageUserStore.isTTLExpired",key,expired),expired};return{available:function(){try{var x=String((new Date).getTime());localStorage["LocalStorageUserStore.test"]=x;var y=localStorage["LocalStorageUserStore.test"];return localStorage.removeItem("LocalStorageUserStore.test"),x===y}catch(e){return!1}},getUser:function(){try{if(isTTLExpired(userkey))return authLogger.log("LocalStorageUserStore.getUser expired"),localStorage.removeItem(userkey),setTTL(userkey,null),null;var user=JSON.parse(localStorage[userkey]);return authLogger.log("LocalStorageUserStore.getUser",user),user}catch(e){return authLogger.error("LocalStorageUserStore.getUser",e),null}},setUser:function(user,ttl){user?(authLogger.log("LocalStorageUserStore.setUser",user,ttl),localStorage[userkey]=JSON.stringify(user),setTTL(userkey,ttl)):(authLogger.log("LocalStorageUserStore.setUser",user,"deleting"),localStorage.removeItem(userkey),setTTL(userkey,null))},getToken:function(){try{if(isTTLExpired(tokenkey))return authLogger.log("LocalStorageUserStore.getToken expired"),localStorage.removeItem(tokenkey),setTTL(tokenkey,null),null;var token=localStorage[tokenkey];return authLogger.log("LocalStorageUserStore.getToken",token),token}catch(e){return authLogger.error("LocalStorageUserStore.getToken",e),null}},setToken:function(token,ttl){token?(authLogger.log("LocalStorageUserStore.setToken",token,ttl),localStorage[tokenkey]=token,setTTL(tokenkey,ttl)):(authLogger.log("LocalStorageUserStore.setToken",token,ttl,"deleting"),localStorage.removeItem(tokenkey),setTTL(tokenkey,null))}}}]}),angular.module("openshiftCommon").factory("Notification",["$rootScope",function($rootScope){function Notification(){this.messenger=Messenger({extraClasses:"messenger-fixed messenger-on-bottom messenger-on-right",theme:"flat",messageDefaults:{showCloseButton:!0,hideAfter:10}});var self=this;$rootScope.$on("$routeChangeStart",function(event,next,current){self.clear()})}return Notification.prototype.notify=function(type,message,opts){opts=opts||{};var notifyOpts={type:type,message:$("<div/>").text(message).html(),id:opts.id,actions:opts.actions};opts.mustDismiss&&(notifyOpts.hideAfter=!1),this.messenger.post(notifyOpts)},Notification.prototype.success=function(message,opts){this.notify("success",message,opts)},Notification.prototype.info=function(message,opts){this.notify("info",message,opts)},Notification.prototype.error=function(message,opts){this.notify("error",message,opts)},Notification.prototype.warning=function(message,opts){this.notify("warning",message,opts)},Notification.prototype.clear=function(){this.messenger.hideAll()},new Notification}]),angular.module("openshiftCommon").provider("RedirectLoginService",function(){var _oauth_client_id="",_oauth_authorize_uri="",_oauth_redirect_uri="";this.OAuthClientID=function(id){return id&&(_oauth_client_id=id),_oauth_client_id},this.OAuthAuthorizeURI=function(uri){return uri&&(_oauth_authorize_uri=uri),_oauth_authorize_uri},this.OAuthRedirectURI=function(uri){return uri&&(_oauth_redirect_uri=uri),_oauth_redirect_uri},this.$get=["$location","$q","Logger","base64",function($location,$q,Logger,base64){var authLogger=Logger.get("auth"),getRandomInts=function(length){var randomValues;if(window.crypto&&window.Uint32Array)try{var r=new Uint32Array(length);window.crypto.getRandomValues(r),randomValues=[];for(var j=0;length>j;j++)randomValues.push(r[j])}catch(e){authLogger.debug("RedirectLoginService.getRandomInts: ",e),randomValues=null}if(!randomValues){randomValues=[];for(var i=0;length>i;i++)randomValues.push(Math.floor(4294967296*Math.random()))}return randomValues},nonceKey="RedirectLoginService.nonce",makeState=function(then){var nonce=String((new Date).getTime())+"-"+getRandomInts(8).join("");try{window.localStorage[nonceKey]=nonce}catch(e){authLogger.log("RedirectLoginService.makeState, localStorage error: ",e)}return base64.urlencode(JSON.stringify({then:then,nonce:nonce}))},parseState=function(state){var retval={then:null,verified:!1},nonce="";try{nonce=window.localStorage[nonceKey],window.localStorage.removeItem(nonceKey)}catch(e){authLogger.log("RedirectLoginService.parseState, localStorage error: ",e)}try{var data=state?JSON.parse(base64.urldecode(state)):{};data&&data.nonce&&nonce&&data.nonce===nonce&&(retval.verified=!0,retval.then=data.then)}catch(e){authLogger.error("RedirectLoginService.parseState, state error: ",e)}return authLogger.error("RedirectLoginService.parseState",retval),retval};return{login:function(){if(""===_oauth_client_id)return $q.reject({error:"invalid_request",error_description:"RedirectLoginServiceProvider.OAuthClientID() not set"});if(""===_oauth_authorize_uri)return $q.reject({error:"invalid_request",error_description:"RedirectLoginServiceProvider.OAuthAuthorizeURI() not set"});if(""===_oauth_redirect_uri)return $q.reject({error:"invalid_request",error_description:"RedirectLoginServiceProvider.OAuthRedirectURI not set"});var deferred=$q.defer(),uri=new URI(_oauth_authorize_uri),returnUri=new URI($location.url()).fragment("");return uri.query({client_id:_oauth_client_id,response_type:"token",state:makeState(returnUri.toString()),redirect_uri:_oauth_redirect_uri}),authLogger.log("RedirectLoginService.login(), redirecting",uri.toString()),window.location.href=uri.toString(),deferred.promise},finish:function(){var u=new URI($location.url()),queryParams=u.query(!0),fragmentParams=new URI("?"+u.fragment()).query(!0);authLogger.log("RedirectLoginService.finish()",queryParams,fragmentParams);var error=queryParams.error||fragmentParams.error;if(error){var error_description=queryParams.error_description||fragmentParams.error_description,error_uri=queryParams.error_uri||fragmentParams.error_uri;return authLogger.log("RedirectLoginService.finish(), error",error,error_description,error_uri),$q.reject({error:error,error_description:error_description,error_uri:error_uri})}var stateData=parseState(fragmentParams.state);if(fragmentParams.access_token&&"bearer"===(fragmentParams.token_type||"").toLowerCase()){var deferred=$q.defer();return deferred.resolve({token:fragmentParams.access_token,ttl:fragmentParams.expires_in,then:stateData.then,verified:stateData.verified}),deferred.promise}return $q.reject({error:"invalid_request",error_description:"No API token returned"})}}}]}),angular.module("openshiftCommon").provider("$ws",["$httpProvider",function($httpProvider){this.$get=["$q","$injector","Logger",function($q,$injector,Logger){var authLogger=Logger.get("auth");authLogger.log("$wsProvider.$get",arguments);var _interceptors=[];angular.forEach($httpProvider.interceptors,function(interceptorFactory){angular.isString(interceptorFactory)?_interceptors.unshift($injector.get(interceptorFactory)):_interceptors.unshift($injector.invoke(interceptorFactory))});var $ws=function(config){config.method=angular.uppercase(config.method||"WATCH"),authLogger.log("$ws (pre-intercept)",config.url.toString());var serverRequest=function(config){authLogger.log("$ws (post-intercept)",config.url.toString());var ws=new WebSocket(config.url,config.protocols);return config.onclose&&(ws.onclose=config.onclose),config.onmessage&&(ws.onmessage=config.onmessage),config.onopen&&(ws.onopen=config.onopen),config.onerror&&(ws.onerror=config.onerror),ws},chain=[serverRequest,void 0],promise=$q.when(config);for(angular.forEach(_interceptors,function(interceptor){(interceptor.request||interceptor.requestError)&&chain.unshift(interceptor.request,interceptor.requestError)});chain.length;){var thenFn=chain.shift(),rejectFn=chain.shift();promise=promise.then(thenFn,rejectFn)}return promise};return $ws.available=function(){try{return!!WebSocket}catch(e){return!1}},$ws}]}]).factory("ContainerWebSocket",["API_CFG","$ws",function(API_CFG,$ws){return function(url,protocols){var scheme;return 0===url.indexOf("/")&&(scheme="http:"===window.location.protocol?"ws://":"wss://",url=scheme+API_CFG.openshift.hostPort+url),$ws({url:url,method:"WATCH",protocols:protocols,auth:{}})}}]);
\ No newline at end of file
#!/bin/bash
set -e
# We don't need bower to be installed globally for the system, so
# we can amend our path to look into the local node_modules for the
# correct binaries.
repo_root="$( dirname "${BASH_SOURCE}" )/.."
export PATH="${PATH}:${repo_root}/node_modules/bower/bin"
if which bower > /dev/null 2>&1 ; then
# In case upstream components change things without incrementing versions
echo "Clearing bower cache..."
bower cache clean --allow-root
else
echo "Skipping bower cache clean, bower not installed."
fi
echo "Cleaning up bower_components and node_modules..."
rm -rf bower_components/* node_modules/*
#!/bin/bash
set -e
STARTTIME=$(date +%s)
TMPDIR="${TMPDIR:-"/tmp"}"
LOG_DIR="${LOG_DIR:-$(mktemp -d ${TMPDIR}/openshift.assets.logs.XXXX)}"
function cmd() {
local cmd="$1"
local tries="${2:-1}"
local log_file=$(mktemp ${LOG_DIR}/install-assets.XXXX)
echo "[install-assets] ${cmd}"
rc="0"
while [[ "$tries" -gt 0 ]]; do
rc="0"
$cmd &> ${log_file} || rc=$?
[[ "$rc" == "0" ]] && return 0
((tries--))
done
echo "[ERROR] Command '${cmd}' failed with rc ${rc}, logs:" && cat ${log_file}
exit $rc
}
# We don't need grunt and bower to be installed globally for the system,
# so we can amend our path to look into the local node_modules for the
# correct binaries.
repo_root="$( dirname "${BASH_SOURCE}" )/.."
export PATH="${PATH}:${repo_root}/node_modules/bower/bin:${repo_root}/node_modules/grunt-cli/bin"
# Install bower if needed
if ! which bower > /dev/null 2>&1 ; then
cmd "npm install bower"
fi
# Install grunt if needed
if ! which grunt > /dev/null 2>&1 ; then
cmd "npm install grunt-cli"
fi
cmd "npm install --unsafe-perm"
# In case upstream components change things without incrementing versions
cmd "bower cache clean --allow-root"
cmd "bower update --allow-root" 3
ret=$?; ENDTIME=$(date +%s); echo "$0 took $(($ENDTIME - $STARTTIME)) seconds"; exit "$ret"
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
function cleanup_xvfb() {
if [[ -n "${xvfb_process:-}" ]]; then
kill -SIGTERM "${xvfb_process}"
fi
}
trap cleanup_xvfb EXIT
if ! which xdpyinfo >/dev/null 2>&1; then
echo "[ERROR] The \`xdpyinfo\` utility is required to run this script!."
exit 1
fi
if ! which Xvfb >/dev/null 2>&1; then
echo "[ERROR] The \`Xvfb\` utility is required to run this script!."
exit 1
fi
# We don't need grunt to be installed globally for the system, so
# we can amend our path to look into the local node_modules for the
# correct binaries.
repo_root="$( dirname "${BASH_SOURCE}" )/.."
export PATH="${PATH}:${repo_root}/node_modules/grunt-cli/bin"
echo "[INFO] Starting virtual framebuffer for headless tests..."
export DISPLAY=':10'
export SCREEN='0'
Xvfb "${DISPLAY}" -screen "${SCREEN}" 1024x768x24 -ac &
xvfb_process="$!"
while ! xdpyinfo -d "${DISPLAY}" >/dev/null 2>&1; do
sleep 0.2
done
grunt "$@"
#!/bin/bash
set -e
# We don't need grunt to be installed globally for the system, so
# we can amend our path to look into the local node_modules for the
# correct binaries.
repo_root="$( dirname "${BASH_SOURCE}" )/.."
export PATH="${PATH}:${repo_root}/node_modules/grunt-cli/bin"
grunt build
echo "Verifying that checked in built files under dist match the source..."
if [[ $(git status -s -u dist*) ]]; then
git status -vv -u dist*
echo "Built dist does not match what is committed, run 'grunt build' and include the results in your commit."
exit 1
else
echo "Verified."
fi
{
"author": "Red Hat",
"name": "origin-web-common",
"version": "0.0.1",
"license": "Apache-2.0",
"description": "Library of common services and components for OpenShift Web Console.",
"homepage": "https://github.com/openshift/origin-web-common.git",
"dependencies": {
"angular": "1.5.11"
},
"devDependencies": {
"express": "3.4.4",
"grunt": "0.4.5",
"grunt-angular-templates": "0.5.7",
"grunt-available-tasks": "0.4.3",
"grunt-contrib-clean": "0.4.1",
"grunt-contrib-concat": "0.3.0",
"grunt-contrib-connect": "0.5.0",
"grunt-contrib-copy": "0.5.0",
"grunt-contrib-cssmin": "0.9.0",
"grunt-contrib-jshint": "0.7.0",
"grunt-contrib-uglify": "0.2.5",
"grunt-contrib-watch": "0.5.3",
"grunt-eslint": "~17.1.0",
"grunt-karma": "0.8.3",
"grunt-ng-annotate": "^1.0.1",
"grunt-remove": "^0.1.0",
"karma": "0.12.23",
"karma-chrome-launcher": "0.1.4",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "0.1.3",
"karma-jasmine": "0.2.2",
"karma-junit-reporter": "0.2.2",
"karma-ng-html2js-preprocessor": "~0.1",
"karma-phantomjs-launcher": "^1.0.0",
"matchdep": "0.3.0",
"nsp": "^2.6.1"
},
"scripts": {
"test": "grunt test"
},
"repository": {
"type": "git",
"url": "https://github.com/openshift/origin-web-common.git"
},
"engines": {
"node": ">=0.10.10"
}
}
// This is the default configuration for the dev mode of the web console.
// A generated version of this config is created at run-time when running
// the web console from the openshift binary.
//
// To change configuration for local development, copy this file to
// assets/app/config.local.js and edit the copy.
if (!window.OPENSHIFT_CONFIG) {
window.OPENSHIFT_CONFIG = {
apis: {
hostPort: "localhost:8443",
prefix: "/apis"
},
api: {
openshift: {
hostPort: "localhost:8443",
prefix: "/oapi"
},
k8s: {
hostPort: "localhost:8443",
prefix: "/api"
}
},
auth: {
oauth_authorize_uri: "https://localhost:8443/oauth/authorize",
oauth_redirect_base: "https://localhost:9000/dev-console",
oauth_client_id: "openshift-web-console",
logout_uri: ""
},
loggingURL: "",
metricsURL: ""
};
window.OPENSHIFT_VERSION = {
openshift: "dev-mode",
kubernetes: "dev-mode"
};
}
/**
* @name openshiftCommon
*
* @description
* Base module for openshiftCommon.
*/
angular.module('openshiftCommon', ['kubernetesUI', 'ab-base64'])
.config(function(AuthServiceProvider) {
AuthServiceProvider.UserStore('MemoryUserStore');
})
.constant("API_CFG", _.get(window.OPENSHIFT_CONFIG, "api", {}))
.constant("APIS_CFG", _.get(window.OPENSHIFT_CONFIG, "apis", {}))
.constant("AUTH_CFG", _.get(window.OPENSHIFT_CONFIG, "auth", {}))
.constant("LOGGING_URL", _.get(window.OPENSHIFT_CONFIG, "loggingURL"))
.constant("METRICS_URL", _.get(window.OPENSHIFT_CONFIG, "metricsURL"))
.constant("LIMIT_REQUEST_OVERRIDES", _.get(window.OPENSHIFT_CONFIG, "limitRequestOverrides"))
.config(function($httpProvider, AuthServiceProvider, RedirectLoginServiceProvider, AUTH_CFG, API_CFG, kubernetesContainerSocketProvider) {
$httpProvider.interceptors.push('AuthInterceptor');
AuthServiceProvider.LoginService('RedirectLoginService');
AuthServiceProvider.LogoutService('DeleteTokenLogoutService');
// TODO: fall back to cookie store when localStorage is unavailable (see known issues at http://caniuse.com/#feat=namevalue-storage)
AuthServiceProvider.UserStore('LocalStorageUserStore');
RedirectLoginServiceProvider.OAuthClientID(AUTH_CFG.oauth_client_id);
RedirectLoginServiceProvider.OAuthAuthorizeURI(AUTH_CFG.oauth_authorize_uri);
RedirectLoginServiceProvider.OAuthRedirectURI(URI(AUTH_CFG.oauth_redirect_base).segment("oauth").toString());
// Configure the container terminal
kubernetesContainerSocketProvider.WebSocketFactory = "ContainerWebSocket";
});
hawtioPluginLoader.addModule('openshiftCommon');
// API Discovery, this runs before the angular app is bootstrapped
// TODO we want this to be possible with a single request against the API instead of being dependent on the numbers of groups and versions
hawtioPluginLoader.registerPreBootstrapTask(function(next) {
// Skips api discovery, needed to run spec tests
if ( _.get(window, "OPENSHIFT_CONFIG.api.k8s.resources") ) {
next();
return;
}
var api = {
k8s: {},
openshift: {}
};
var apis = {};
var API_DISCOVERY_ERRORS = [];
var protocol = window.location.protocol + "//";
// Fetch /api/v1 for legacy k8s resources, we will never bump the version of these legacy apis so fetch version immediately
var k8sBaseURL = protocol + window.OPENSHIFT_CONFIG.api.k8s.hostPort + window.OPENSHIFT_CONFIG.api.k8s.prefix;
var k8sDeferred = $.get(k8sBaseURL + "/v1")
.done(function(data) {
api.k8s.v1 = _.indexBy(data.resources, 'name');
})
.fail(function(data, textStatus, jqXHR) {
API_DISCOVERY_ERRORS.push({
data: data,
textStatus: textStatus,
xhr: jqXHR
});
});
// Fetch /oapi/v1 for legacy openshift resources, we will never bump the version of these legacy apis so fetch version immediately
var osBaseURL = protocol + window.OPENSHIFT_CONFIG.api.openshift.hostPort + window.OPENSHIFT_CONFIG.api.openshift.prefix;
var osDeferred = $.get(osBaseURL + "/v1")
.done(function(data) {
api.openshift.v1 = _.indexBy(data.resources, 'name');
})
.fail(function(data, textStatus, jqXHR) {
API_DISCOVERY_ERRORS.push({
data: data,
textStatus: textStatus,
xhr: jqXHR
});
});
// Fetch /apis to get the list of groups and versions, then fetch each group/
// Because the api discovery doc returns arrays and we want maps, this creates a structure like:
// {
// extensions: {
// name: "extensions",
// preferredVersion: "v1beta1",
// versions: {
// v1beta1: {
// version: "v1beta1",
// groupVersion: "extensions/v1beta1"
// resources: {
// daemonsets: {
// /* resource returned from discovery API */
// }
// }
// }
// }
// }
// }
var apisBaseURL = protocol + window.OPENSHIFT_CONFIG.apis.hostPort + window.OPENSHIFT_CONFIG.apis.prefix;
var apisDeferred = $.get(apisBaseURL)
.then(function(data) {
var apisDeferredVersions = [];
_.each(data.groups, function(apiGroup) {
var group = {
name: apiGroup.name,
preferredVersion: apiGroup.preferredVersion.version,
versions: {}
};
apis[group.name] = group;
_.each(apiGroup.versions, function(apiVersion) {
var versionStr = apiVersion.version;
group.versions[versionStr] = {
version: versionStr,
groupVersion: apiVersion.groupVersion
};
apisDeferredVersions.push($.get(apisBaseURL + "/" + apiVersion.groupVersion)
.done(function(data) {
group.versions[versionStr].resources = _.indexBy(data.resources, 'name');
})
.fail(function(data, textStatus, jqXHR) {
API_DISCOVERY_ERRORS.push({
data: data,
textStatus: textStatus,
xhr: jqXHR
});
}));
});
});
return $.when.apply(this, apisDeferredVersions);
}, function(data, textStatus, jqXHR) {
API_DISCOVERY_ERRORS.push({
data: data,
textStatus: textStatus,
xhr: jqXHR
});
});
// Will be called on success or failure
var discoveryFinished = function() {
window.OPENSHIFT_CONFIG.api.k8s.resources = api.k8s;
window.OPENSHIFT_CONFIG.api.openshift.resources = api.openshift;
window.OPENSHIFT_CONFIG.apis.groups = apis;
if (API_DISCOVERY_ERRORS.length) {
window.OPENSHIFT_CONFIG.apis.API_DISCOVERY_ERRORS = API_DISCOVERY_ERRORS;
}
next();
};
$.when(k8sDeferred,osDeferred,apisDeferred).always(discoveryFinished);
});
'use strict';
// ResourceGroupVersion represents a fully qualified resource
function ResourceGroupVersion(resource, group, version) {
this.resource = resource;
this.group = group;
this.version = version;
return this;
}
// toString() includes the group and version information if present
ResourceGroupVersion.prototype.toString = function() {
var s = this.resource;
if (this.group) { s += "/" + this.group; }
if (this.version) { s += "/" + this.version; }
return s;
};
// primaryResource() returns the resource with any subresources removed
ResourceGroupVersion.prototype.primaryResource = function() {
if (!this.resource) { return ""; }
var i = this.resource.indexOf('/');
if (i === -1) { return this.resource; }
return this.resource.substring(0,i);
};
// subresources() returns a (possibly empty) list of subresource segments
ResourceGroupVersion.prototype.subresources = function() {
var segments = (this.resource || '').split("/");
segments.shift();
return segments;
};
// equals() returns true if the given resource, group, and version match.
// If omitted, group and version are not compared.
ResourceGroupVersion.prototype.equals = function(resource, group, version) {
if (this.resource !== resource) { return false; }
if (arguments.length === 1) { return true; }
if (this.group !== group) { return false; }
if (arguments.length === 2) { return true; }
if (this.version !== version) { return false; }
return true;
};
angular.module('openshiftCommon')
.factory('APIService', function(API_CFG,
APIS_CFG,
AuthService,
Constants,
Logger,
$q,
$http,
$filter,
$window) {
// Set the default api versions the console will use if otherwise unspecified
var defaultVersion = {
"": "v1",
"extensions": "v1beta1"
};
// toResourceGroupVersion() returns a ResourceGroupVersion.
// If resource is already a ResourceGroupVersion, returns itself.
//
// if r is a string, the empty group and default version for the empty group are assumed.
//
// if r is an object, the resource, group, and version attributes are read.
// a missing group attribute defaults to the legacy group.
// a missing version attribute defaults to the default version for the group, or undefined if the group is unknown.
//
// if r is already a ResourceGroupVersion, it is returned as-is
var toResourceGroupVersion = function(r) {
if (r instanceof ResourceGroupVersion) {
return r;
}
var resource, group, version;
if (angular.isString(r)) {
resource = normalizeResource(r);
group = '';
version = defaultVersion[group];
} else if (r && r.resource) {
resource = normalizeResource(r.resource);
group = r.group || '';
version = r.version || defaultVersion[group] || _.get(APIS_CFG, ["groups", group, "preferredVersion"]);
}
return new ResourceGroupVersion(resource, group, version);
};
// normalizeResource lowercases the first segment of the given resource. subresources can be case-sensitive.
function normalizeResource(resource) {
if (!resource) {
return resource;
}
var i = resource.indexOf('/');
if (i === -1) {
return resource.toLowerCase();
}
return resource.substring(0, i).toLowerCase() + resource.substring(i);
}
// port of group_version.go#ParseGroupVersion
var parseGroupVersion = function(apiVersion) {
if (!apiVersion) {
return undefined;
}
var parts = apiVersion.split("/");
if (parts.length === 1) {
if (parts[0] === "v1") {
return {group: '', version: parts[0]};
}
return {group: parts[0], version: ''};
}
if (parts.length === 2) {
return {group:parts[0], version: parts[1]};
}
Logger.warn('Invalid apiVersion "' + apiVersion + '"');
return undefined;
};
var objectToResourceGroupVersion = function(apiObject) {
if (!apiObject || !apiObject.kind || !apiObject.apiVersion) {
return undefined;
}
var resource = kindToResource(apiObject.kind);
if (!resource) {
return undefined;
}
var groupVersion = parseGroupVersion(apiObject.apiVersion);
if (!groupVersion) {
return undefined;
}
return new ResourceGroupVersion(resource, groupVersion.group, groupVersion.version);
};
// deriveTargetResource figures out the fully qualified destination to submit the object to.
// if resource is a string, and the object's kind matches the resource, the object's group/version are used.
// if resource is a ResourceGroupVersion, and the object's kind and group match, the object's version is used.
// otherwise, resource is used as-is.
var deriveTargetResource = function(resource, object) {
if (!resource || !object) {
return undefined;
}
var objectResource = kindToResource(object.kind);
var objectGroupVersion = parseGroupVersion(object.apiVersion);
var resourceGroupVersion = toResourceGroupVersion(resource);
if (!objectResource || !objectGroupVersion || !resourceGroupVersion) {
return undefined;
}
// We specified something like "pods"
if (angular.isString(resource)) {
// If the object had a matching kind {"kind":"Pod","apiVersion":"v1"}, use the group/version from the object
if (resourceGroupVersion.equals(objectResource)) {
resourceGroupVersion.group = objectGroupVersion.group;
resourceGroupVersion.version = objectGroupVersion.version;
}
return resourceGroupVersion;
}
// If the resource was already a fully specified object,
// require the group to match as well before taking the version from the object
if (resourceGroupVersion.equals(objectResource, objectGroupVersion.group)) {
resourceGroupVersion.version = objectGroupVersion.version;
}
return resourceGroupVersion;
};
// port of restmapper.go#kindToResource
// humanize will add spaces between words in the resource
function kindToResource(kind, humanize) {
if (!kind) {
return "";
}
var resource = kind;
if (humanize) {
var humanizeKind = $filter("humanizeKind");
resource = humanizeKind(resource);
}
resource = String(resource).toLowerCase();
if (resource === 'endpoints' || resource === 'securitycontextconstraints') {
// no-op, plural is the singular
}
else if (resource[resource.length-1] === 's') {
resource = resource + 'es';
}
else if (resource[resource.length-1] === 'y') {
resource = resource.substring(0, resource.length-1) + 'ies';
}
else {
resource = resource + 's';
}
return resource;
}
// apiInfo returns the host/port, prefix, group, and version for the given resource,
// or undefined if the specified resource/group/version is known not to exist.
var apiInfo = function(resource) {
// If API discovery had any failures, calls to api info should redirect to the error page
if (APIS_CFG.API_DISCOVERY_ERRORS) {
var possibleCertFailure = _.every(APIS_CFG.API_DISCOVERY_ERRORS, function(error){
return _.get(error, "data.status") === 0;
});
if (possibleCertFailure && !AuthService.isLoggedIn()) {
// will trigger a login flow which will redirect to the api server
AuthService.withUser();
return;
}
// Otherwise go to the error page, the server might be down. Can't use Navigate.toErrorPage or it will create a circular dependency
$window.location.href = URI('error').query({
error_description: "Unable to load details about the server. If the problem continues, please contact your system administrator.",
error: "API_DISCOVERY"
}).toString();
return;
}
resource = toResourceGroupVersion(resource);
var primaryResource = resource.primaryResource();
// API info for resources in an API group, if the resource was not found during discovery return undefined
if (resource.group) {
if (!_.get(APIS_CFG, ["groups", resource.group, "versions", resource.version, "resources", primaryResource])) {
return undefined;
}
return {
hostPort: APIS_CFG.hostPort,
prefix: APIS_CFG.prefix,
group: resource.group,
version: resource.version
};
}
// Resources without an API group could be legacy k8s or origin resources.
// Scan through resources to determine which this is.
var api;
for (var apiName in API_CFG) {
api = API_CFG[apiName];
if (!_.get(api, ["resources", resource.version, primaryResource])) {
continue;
}
return {
hostPort: api.hostPort,
prefix: api.prefix,
version: resource.version
};
}
return undefined;
};
var invalidObjectKindOrVersion = function(apiObject) {
var kind = "<none>";
var version = "<none>";
if (apiObject && apiObject.kind) { kind = apiObject.kind; }
if (apiObject && apiObject.apiVersion) { version = apiObject.apiVersion; }
return "Invalid kind ("+kind+") or API version ("+version+")";
};
var unsupportedObjectKindOrVersion = function(apiObject) {
var kind = "<none>";
var version = "<none>";
if (apiObject && apiObject.kind) { kind = apiObject.kind; }
if (apiObject && apiObject.apiVersion) { version = apiObject.apiVersion; }
return "The API version "+version+" for kind " + kind + " is not supported by this server";
};
// Returns an array of available kinds, including their group
var calculateAvailableKinds = function(includeClusterScoped) {
var kinds = [];
var rejectedKinds = Constants.AVAILABLE_KINDS_BLACKLIST;
// Legacy openshift and k8s kinds
_.each(API_CFG, function(api) {
_.each(api.resources.v1, function(resource) {
if (resource.namespaced || includeClusterScoped) {
// Exclude subresources and any rejected kinds
if (resource.name.indexOf("/") >= 0 || _.contains(rejectedKinds, resource.kind)) {
return;
}
kinds.push({
kind: resource.kind
});
}
});
});
// Kinds under api groups
_.each(APIS_CFG.groups, function(group) {
// Use the console's default version first, and the server's preferred version second
var preferredVersion = defaultVersion[group.name] || group.preferredVersion;
_.each(group.versions[preferredVersion].resources, function(resource) {
// Exclude subresources and any rejected kinds
if (resource.name.indexOf("/") >= 0 || _.contains(rejectedKinds, resource.kind)) {
return;
}
// Exclude duplicate kinds we know about that map to the same storage as another group/kind
// This is unusual, so we are special casing these
if (group.name === "autoscaling" && resource.kind === "HorizontalPodAutoscaler" ||
group.name === "batch" && resource.kind === "Job"
) {
return;
}
if (resource.namespaced || includeClusterScoped) {
kinds.push({
kind: resource.kind,
group: group.name
});
}
});
});
return _.uniq(kinds, false, function(value) {
return value.group + "/" + value.kind;
});
};
var namespacedKinds = calculateAvailableKinds(false);
var allKinds = calculateAvailableKinds(true);
var availableKinds = function(includeClusterScoped) {
return includeClusterScoped ? allKinds : namespacedKinds;
};
return {
toResourceGroupVersion: toResourceGroupVersion,
parseGroupVersion: parseGroupVersion,
objectToResourceGroupVersion: objectToResourceGroupVersion,
deriveTargetResource: deriveTargetResource,
kindToResource: kindToResource,
apiInfo: apiInfo,
invalidObjectKindOrVersion: invalidObjectKindOrVersion,
unsupportedObjectKindOrVersion: unsupportedObjectKindOrVersion,
availableKinds: availableKinds
};
});
'use strict';
angular.module('openshiftCommon')
// In a config step, set the desired user store and login service. For example:
// AuthServiceProvider.setUserStore('LocalStorageUserStore')
// AuthServiceProvider.setLoginService('RedirectLoginService')
//
// AuthService provides the following functions:
// withUser()
// returns a promise that resolves when there is a current user
// starts a login if there is no current user
// setUser(user, token[, ttl])
// sets the current user and token to use for authenticated requests
// if ttl is specified, it indicates how many seconds the user and token are valid
// triggers onUserChanged callbacks if the new user is different than the current user
// requestRequiresAuth(config)
// returns true if the request is to a protected URL
// addAuthToRequest(config)
// adds auth info to the request, if available
// if specified, uses config.auth.token as the token, otherwise uses the token store
// startLogin()
// returns a promise that is resolved when the login is complete
// onLogin(callback)
// the given callback is called whenever a login is completed
// onUserChanged(callback)
// the given callback is called whenever the current user changes
.provider('AuthService', function() {
var _userStore = "";
this.UserStore = function(userStoreName) {
if (userStoreName) {
_userStore = userStoreName;
}
return _userStore;
};
var _loginService = "";
this.LoginService = function(loginServiceName) {
if (loginServiceName) {
_loginService = loginServiceName;
}
return _loginService;
};
var _logoutService = "";
this.LogoutService = function(logoutServiceName) {
if (logoutServiceName) {
_logoutService = logoutServiceName;
}
return _logoutService;
};
var loadService = function(injector, name, setter) {
if (!name) {
throw setter + " not set";
} else if (angular.isString(name)) {
return injector.get(name);
} else {
return injector.invoke(name);
}
};
this.$get = function($q, $injector, $log, $rootScope, Logger) {
var authLogger = Logger.get("auth");
authLogger.log('AuthServiceProvider.$get', arguments);
var _loginCallbacks = $.Callbacks();
var _logoutCallbacks = $.Callbacks();
var _userChangedCallbacks = $.Callbacks();
var _loginPromise = null;
var _logoutPromise = null;
var userStore = loadService($injector, _userStore, "AuthServiceProvider.UserStore()");
if (!userStore.available()) {
Logger.error("AuthServiceProvider.$get user store " + _userStore + " not available");
}
var loginService = loadService($injector, _loginService, "AuthServiceProvider.LoginService()");
var logoutService = loadService($injector, _logoutService, "AuthServiceProvider.LogoutService()");
return {
// Returns the configured user store
UserStore: function() {
return userStore;
},
// Returns true if currently logged in.
isLoggedIn: function() {
return !!userStore.getUser();
},
// Returns a promise of a user, which is resolved with a logged in user. Triggers a login if needed.
withUser: function() {
var user = userStore.getUser();
if (user) {
$rootScope.user = user;
authLogger.log('AuthService.withUser()', user);
return $q.when(user);
} else {
authLogger.log('AuthService.withUser(), calling startLogin()');
return this.startLogin();
}
},
setUser: function(user, token, ttl) {
authLogger.log('AuthService.setUser()', user, token, ttl);
var oldUser = userStore.getUser();
userStore.setUser(user, ttl);
userStore.setToken(token, ttl);
$rootScope.user = user;
var oldName = oldUser && oldUser.metadata && oldUser.metadata.name;
var newName = user && user.metadata && user.metadata.name;
if (oldName !== newName) {
authLogger.log('AuthService.setUser(), user changed', oldUser, user);
_userChangedCallbacks.fire(user);
}
},
requestRequiresAuth: function(config) {
var requiresAuth = !!config.auth;
authLogger.log('AuthService.requestRequiresAuth()', config.url.toString(), requiresAuth);
return requiresAuth;
},
addAuthToRequest: function(config) {
// Use the given token, if provided
var token = "";
if (config && config.auth && config.auth.token) {
token = config.auth.token;
authLogger.log('AuthService.addAuthToRequest(), using token from request config', token);
} else {
token = userStore.getToken();
authLogger.log('AuthService.addAuthToRequest(), using token from user store', token);
}
if (!token) {
authLogger.log('AuthService.addAuthToRequest(), no token available');
return false;
}
// Handle web socket requests with a parameter
if (config.method === 'WATCH') {
config.url = URI(config.url).addQuery({access_token: token}).toString();
authLogger.log('AuthService.addAuthToRequest(), added token param', config.url);
} else {
config.headers["Authorization"] = "Bearer " + token;
authLogger.log('AuthService.addAuthToRequest(), added token header', config.headers["Authorization"]);
}
return true;
},
startLogin: function() {
if (_loginPromise) {
authLogger.log("Login already in progress");
return _loginPromise;
}
var self = this;
_loginPromise = loginService.login().then(function(result) {
self.setUser(result.user, result.token, result.ttl);
_loginCallbacks.fire(result.user);
}).catch(function(err) {
Logger.error(err);
}).finally(function() {
_loginPromise = null;
});
return _loginPromise;
},
startLogout: function() {
if (_logoutPromise) {
authLogger.log("Logout already in progress");
return _logoutPromise;
}
var self = this;
var user = userStore.getUser();
var token = userStore.getToken();
var wasLoggedIn = this.isLoggedIn();
_logoutPromise = logoutService.logout(user, token).then(function() {
authLogger.log("Logout service success");
}).catch(function(err) {
authLogger.error("Logout service error", err);
}).finally(function() {
// Clear the user and token
self.setUser(null, null);
// Make sure isLoggedIn() returns false before we fire logout callbacks
var isLoggedIn = self.isLoggedIn();
// Only fire logout callbacks if we transitioned from a logged in state to a logged out state
if (wasLoggedIn && !isLoggedIn) {
_logoutCallbacks.fire();
}
_logoutPromise = null;
});
return _logoutPromise;
},
// TODO: add a way to unregister once we start doing in-page logins
onLogin: function(callback) {
_loginCallbacks.add(callback);
},
// TODO: add a way to unregister once we start doing in-page logouts
onLogout: function(callback) {
_logoutCallbacks.add(callback);
},
// TODO: add a way to unregister once we start doing in-page user changes
onUserChanged: function(callback) {
_userChangedCallbacks.add(callback);
}
};
};
})
// register the interceptor as a service
.factory('AuthInterceptor', ['$q', 'AuthService', function($q, AuthService) {
var pendingRequestConfigs = [];
// TODO: subscribe to user change events to empty the saved configs
// TODO: subscribe to login events to retry the saved configs
return {
// If auth is not needed, or is already present, returns a config
// If auth is needed and not present, starts a login flow and returns a promise of a config
request: function(config) {
// Requests that don't require auth can continue
if (!AuthService.requestRequiresAuth(config)) {
// console.log("No auth required", config.url);
return config;
}
// If we could add auth info, we can continue
if (AuthService.addAuthToRequest(config)) {
// console.log("Auth added", config.url);
return config;
}
// We should have added auth info, but couldn't
// If we were specifically told not to trigger a login, return
if (config.auth && config.auth.triggerLogin === false) {
return config;
}
// 1. Set up a deferred and remember this config, so we can add auth info and resume once login is complete
var deferred = $q.defer();
pendingRequestConfigs.push([deferred, config, 'request']);
// 2. Start the login flow
AuthService.startLogin();
// 3. Return the deferred's promise
return deferred.promise;
},
responseError: function(rejection) {
var authConfig = rejection.config.auth || {};
// Requests that didn't require auth can continue
if (!AuthService.requestRequiresAuth(rejection.config)) {
// console.log("No auth required", rejection.config.url);
return $q.reject(rejection);
}
// If we were specifically told not to trigger a login, return
if (authConfig.triggerLogin === false) {
return $q.reject(rejection);
}
// detect if this is an auth error (401) or other error we should trigger a login flow for
var status = rejection.status;
switch (status) {
case 401:
// console.log('responseError', status);
// 1. Set up a deferred and remember this config, so we can add auth info and retry once login is complete
var deferred = $q.defer();
pendingRequestConfigs.push([deferred, rejection.config, 'responseError']);
// 2. Start the login flow
AuthService.startLogin();
// 3. Return the deferred's promise
return deferred.promise;
default:
return $q.reject(rejection);
}
}
};
}]);
'use strict';
angular.module("openshiftCommon")
.factory("AuthorizationService", function($q, $cacheFactory, Logger, $interval, APIService, DataService){
var currentProject = null;
var cachedRulesByProject = $cacheFactory('rulesCache', {
number: 10
});
// Permisive mode will cause no checks to be done for the user actions.
var permissiveMode = false;
var REVIEW_RESOURCES = ["localresourceaccessreviews", "localsubjectaccessreviews", "resourceaccessreviews", "selfsubjectrulesreviews", "subjectaccessreviews"];
// Transform data from:
// rules = {resources: ["jobs"], apiGroups: ["extensions"], verbs:["create","delete","get","list","update"]}
// into:
// normalizedRules = {"extensions": {"jobs": ["create","delete","get","list","update"]}}
var normalizeRules = function(rules) {
var normalizedRules = {};
_.each(rules, function(rule) {
_.each(rule.apiGroups, function(apiGroup) {
if (!normalizedRules[apiGroup]) {
normalizedRules[apiGroup] = {};
}
_.each(rule.resources, function(resource) {
normalizedRules[apiGroup][resource] = rule.verbs;
});
});
});
return normalizedRules;
};
// Check if resource name meets one of following conditions, since those resources can't be create/update via `Add to project` page:
// - 'projectrequests'
// - subresource that contains '/', eg: 'builds/source', 'builds/logs', ...
// - resource is in REVIEW_RESOURCES list
var checkResource = function(resource) {
if (resource === "projectrequests" || _.contains(resource, "/") || _.contains(REVIEW_RESOURCES, resource)) {
return false;
} else {
return true;
}
};
// Check if user can create/update any resource on the 'Add to project' so the button will be displayed.
var canAddToProjectCheck = function(rules) {
return _.some(rules, function(rule) {
return _.some(rule.resources, function(resource) {
return checkResource(resource) && !_.isEmpty(_.intersection(rule.verbs ,(["*", "create", "update"])));
});
});
};
// forceRefresh is a boolean to bust the cache & request new perms
var getProjectRules = function(projectName, forceRefresh) {
var deferred = $q.defer();
currentProject = projectName;
var projectRules = cachedRulesByProject.get(projectName);
var rulesResource = "selfsubjectrulesreviews";
if (!projectRules || projectRules.forceRefresh || forceRefresh) {
// Check if APIserver contains 'selfsubjectrulesreviews' resource. If not switch to permissive mode.
if (APIService.apiInfo(rulesResource)) {
Logger.log("AuthorizationService, loading user rules for " + projectName + " project");
var object = {kind: "SelfSubjectRulesReview",
apiVersion: "v1"
};
DataService.create(rulesResource, null, object, {namespace: projectName}).then(
function(data) {
var normalizedData = normalizeRules(data.status.rules);
var canUserAddToProject = canAddToProjectCheck(data.status.rules);
cachedRulesByProject.put(projectName, {rules: normalizedData,
canAddToProject: canUserAddToProject,
forceRefresh: false,
cacheTimestamp: _.now()
});
deferred.resolve();
}, function() {
permissiveMode = true;
deferred.resolve();
});
} else {
Logger.log("AuthorizationService, resource 'selfsubjectrulesreviews' is not part of APIserver. Switching into permissive mode.");
permissiveMode = true;
deferred.resolve();
}
} else {
// Using cached data.
Logger.log("AuthorizationService, using cached rules for " + projectName + " project");
if ((_.now() - projectRules.cacheTimestamp) >= 600000) {
projectRules.forceRefresh = true;
}
deferred.resolve();
}
return deferred.promise;
};
var getRulesForProject = function(projectName) {
return _.get(cachedRulesByProject.get(projectName || currentProject), ['rules']);
};
// _canI checks whether any rule allows the specified verb (directly or via a wildcard verb) on the literal group and resource.
var _canI = function(rules, verb, group, resource) {
var resources = rules[group];
if (!resources) {
return false;
}
var verbs = resources[resource];
if (!verbs) {
return false;
}
return _.contains(verbs, verb) || _.contains(verbs, '*');
};
// canI checks whether any rule allows the specified verb on the specified group-resource (directly or via a wildcard rule).
var canI = function(resource, verb, projectName) {
if (permissiveMode) {
return true;
}
// normalize to structured form
var r = APIService.toResourceGroupVersion(resource);
var rules = getRulesForProject(projectName || currentProject);
if (!rules) {
return false;
}
return _canI(rules, verb, r.group, r.resource) ||
_canI(rules, verb, '*', '*' ) ||
_canI(rules, verb, r.group, '*' ) ||
_canI(rules, verb, '*', r.resource);
};
var canIAddToProject = function(projectName) {
if (permissiveMode) {
return true;
} else {
return !!_.get(cachedRulesByProject.get(projectName || currentProject), ['canAddToProject']);
}
};
return {
checkResource: checkResource,
getProjectRules: getProjectRules,
canI: canI,
canIAddToProject: canIAddToProject,
getRulesForProject: getRulesForProject
};
});
'use strict';
angular.module('openshiftCommon')
.factory('base64util', function() {
return {
pad: function(data){
if (!data) { return ""; }
switch (data.length % 4) {
case 1: return data + "===";
case 2: return data + "==";
case 3: return data + "=";
default: return data;
}
}
};
});
'use strict';
angular.module('openshiftCommon')
.factory('Constants', function() {
var constants = _.clone(window.OPENSHIFT_CONSTANTS || {});
var version = _.clone(window.OPENSHIFT_VERSION || {});
constants.VERSION = version;
return constants;
});
'use strict';
/* jshint eqeqeq: false, unused: false, expr: true */
angular.module('openshiftCommon')
.factory('DataService', function($cacheFactory, $http, $ws, $rootScope, $q, API_CFG, APIService, Notification, Logger, $timeout, base64, base64util) {
function Data(array) {
this._data = {};
this._objectsByAttribute(array, "metadata.name", this._data);
}
Data.prototype.by = function(attr) {
// TODO store already generated indices
if (attr === "metadata.name") {
return this._data;
}
var map = {};
for (var key in this._data) {
_objectByAttribute(this._data[key], attr, map, null);
}
return map;
};
Data.prototype.update = function(object, action) {
_objectByAttribute(object, "metadata.name", this._data, action);
};
// actions is whether the object was (ADDED|DELETED|MODIFIED). ADDED is assumed if actions is not
// passed. If objects is a hash then actions must be a hash with the same keys. If objects is an array
// then actions must be an array of the same order and length.
Data.prototype._objectsByAttribute = function(objects, attr, map, actions) {
angular.forEach(objects, function(obj, key) {
_objectByAttribute(obj, attr, map, actions ? actions[key] : null);
});
};
// Handles attr with dot notation
// TODO write lots of tests for this helper
// Note: this lives outside the Data prototype for now so it can be used by the helper in DataService as well
function _objectByAttribute(obj, attr, map, action) {
var subAttrs = attr.split(".");
var attrValue = obj;
for (var i = 0; i < subAttrs.length; i++) {
attrValue = attrValue[subAttrs[i]];
if (attrValue === undefined) {
return;
}
}
if ($.isArray(attrValue)) {
// TODO implement this when we actually need it
}
else if ($.isPlainObject(attrValue)) {
for (var key in attrValue) {
var val = attrValue[key];
if (!map[key]) {
map[key] = {};
}
if (action === "DELETED") {
delete map[key][val];
}
else {
map[key][val] = obj;
}
}
}
else {
if (action === "DELETED") {
delete map[attrValue];
}
else {
map[attrValue] = obj;
}
}
}
function DataService() {
this._listDeferredMap = {};
this._watchCallbacksMap = {};
this._watchObjectCallbacksMap = {};
this._watchOperationMap = {};
this._listOperationMap = {};
this._resourceVersionMap = {};
this._dataCache = $cacheFactory('dataCache', {
// 25 is a reasonable number to keep at least one or two projects worth of data in cache
number: 25
});
this._immutableDataCache = $cacheFactory('immutableDataCache', {
// 50 is a reasonable number for the immutable resources that are stored per resource instead of grouped by type
number: 50
});
this._watchOptionsMap = {};
this._watchWebsocketsMap = {};
this._watchPollTimeoutsMap = {};
this._websocketEventsMap = {};
var self = this;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
self._websocketEventsMap = {};
});
}
// resource: API resource (e.g. "pods")
// context: API context (e.g. {project: "..."})
// callback: (optional) function to be called with the list of the requested resource and context,
// parameters passed to the callback:
// Data: a Data object containing the (context-qualified) results
// which includes a helper method for returning a map indexed
// by attribute (e.g. data.by('metadata.name'))
// opts: http - options to pass to the inner $http call
//
// returns a promise
DataService.prototype.list = function(resource, context, callback, opts) {
resource = APIService.toResourceGroupVersion(resource);
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
var deferred = this._listDeferred(key);
if (callback) {
deferred.promise.then(callback);
}
if (this._isCached(key)) {
// A watch operation is running, and we've already received the
// initial set of data for this resource
deferred.resolve(this._data(key));
}
else if (this._listInFlight(key)) {
// no-op, our callback will get called when listOperation completes
}
else {
this._startListOp(resource, context, opts);
}
return deferred.promise;
};
// resource: API resource (e.g. "pods")
// name: API name, the unique name for the object
// context: API context (e.g. {project: "..."})
// opts:
// http - options to pass to the inner $http call
// gracePeriodSeconds - duration in seconds to wait before deleting the resource
// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes.
DataService.prototype.delete = function(resource, name, context, opts) {
resource = APIService.toResourceGroupVersion(resource);
opts = opts || {};
var deferred = $q.defer();
var self = this;
var data, headers = {};
// Differentiate between 0 and undefined
if (_.has(opts, 'gracePeriodSeconds')) {
data = {
kind: "DeleteOptions",
apiVersion: "v1",
gracePeriodSeconds: opts.gracePeriodSeconds
};
headers['Content-Type'] = 'application/json';
}
this._getNamespace(resource, context, opts).then(function(ns){
$http(angular.extend({
method: 'DELETE',
auth: {},
data: data,
headers: headers,
url: self._urlForResource(resource, name, context, false, ns)
}, opts.http || {}))
.success(function(data, status, headerFunc, config, statusText) {
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
deferred.reject({
data: data,
status: status,
headers: headers,
config: config
});
});
});
return deferred.promise;
};
// resource: API resource (e.g. "pods")
// name: API name, the unique name for the object
// object: API object data(eg. { kind: "Build", parameters: { ... } } )
// context: API context (e.g. {project: "..."})
// opts: http - options to pass to the inner $http call
// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes.
DataService.prototype.update = function(resource, name, object, context, opts) {
resource = APIService.deriveTargetResource(resource, object);
opts = opts || {};
var deferred = $q.defer();
var self = this;
this._getNamespace(resource, context, opts).then(function(ns){
$http(angular.extend({
method: 'PUT',
auth: {},
data: object,
url: self._urlForResource(resource, name, context, false, ns)
}, opts.http || {}))
.success(function(data, status, headerFunc, config, statusText) {
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
deferred.reject({
data: data,
status: status,
headers: headers,
config: config
});
});
});
return deferred.promise;
};
// TODO: Enable PATCH when it's added to CORS Access-Control-Allow-Methods
// resource: API resource group version object (e.g. { group: "", resource: "pods", version: "v1" }).
// Must be the full resource group version because it can't be derived from the patch object.
// name: API name, the unique name for the object
// object: API object data(eg. { kind: "Build", parameters: { ... } } )
// context: API context (e.g. {project: "..."})
// opts: http - options to pass to the inner $http call
// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes.
// DataService.prototype.patch = function(resourceGroupVersion, name, object, context, opts) {
// opts = opts || {};
// var deferred = $q.defer();
// var self = this;
// this._getNamespace(resourceGroupVersion, context, opts).then(function(ns){
// $http(angular.extend({
// method: 'PATCH',
// auth: {},
// data: object,
// url: self._urlForResource(resourceGroupVersion, name, context, false, ns)
// }, opts.http || {}))
// .success(function(data, status, headerFunc, config, statusText) {
// deferred.resolve(data);
// })
// .error(function(data, status, headers, config) {
// deferred.reject({
// data: data,
// status: status,
// headers: headers,
// config: config
// });
// });
// });
// return deferred.promise;
// };
// resource: API resource (e.g. "pods")
// name: API name, the unique name for the object.
// In case the name of the Object is provided, expected format of 'resource' parameter is 'resource/subresource', eg: 'buildconfigs/instantiate'.
// object: API object data(eg. { kind: "Build", parameters: { ... } } )
// context: API context (e.g. {project: "..."})
// opts: http - options to pass to the inner $http call
// Returns a promise resolved with response data or rejected with {data:..., status:..., headers:..., config:...} when the delete call completes.
DataService.prototype.create = function(resource, name, object, context, opts) {
resource = APIService.deriveTargetResource(resource, object);
opts = opts || {};
var deferred = $q.defer();
var self = this;
this._getNamespace(resource, context, opts).then(function(ns){
$http(angular.extend({
method: 'POST',
auth: {},
data: object,
url: self._urlForResource(resource, name, context, false, ns)
}, opts.http || {}))
.success(function(data, status, headerFunc, config, statusText) {
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
deferred.reject({
data: data,
status: status,
headers: headers,
config: config
});
});
});
return deferred.promise;
};
// objects: Array of API object data(eg. [{ kind: "Build", parameters: { ... } }] )
// context: API context (e.g. {project: "..."})
// opts: action - defines the REST action that will be called
// - available actions: create, update
// http - options to pass to the inner $http call
// Returns a promise resolved with an an object like: { success: [], failure: [] }
// where success and failure contain an array of results from the individual
// create calls.
DataService.prototype.batch = function(objects, context, action, opts) {
var deferred = $q.defer();
var successResults = [];
var failureResults = [];
var self = this;
var remaining = objects.length;
action = action || 'create';
function _checkDone() {
if (remaining === 0) {
deferred.resolve({ success: successResults, failure: failureResults });
}
}
_.each(objects, function(object) {
var resource = APIService.objectToResourceGroupVersion(object);
if (!resource) {
// include the original object, so the error handler can display the kind/name
failureResults.push({object: object, data: {message: APIService.invalidObjectKindOrVersion(object)}});
remaining--;
_checkDone();
return;
}
if (!APIService.apiInfo(resource)) {
// include the original object, so the error handler can display the kind/name
failureResults.push({object: object, data: {message: APIService.unsupportedObjectKindOrVersion(object)}});
remaining--;
_checkDone();
return;
}
var success = function(data) {
// include the original object, so the error handler can display the kind/name
data.object = object;
successResults.push(data);
remaining--;
_checkDone();
};
var failure = function(data) {
// include the original object, so the handler can display the kind/name
data.object = object;
failureResults.push(data);
remaining--;
_checkDone();
};
switch(action) {
case "create":
self.create(resource, null, object, context, opts).then(success, failure);
break;
case "update":
self.update(resource, object.metadata.name, object, context, opts).then(success, failure);
break;
default:
// default case to prevent unspecified actions and typos
return deferred.reject({
data: "Invalid '" + action + "' action.",
status: 400,
headers: function() { return null; },
config: {},
object: object
});
}
});
return deferred.promise;
};
// resource: API resource (e.g. "pods")
// name: API name, the unique name for the object
// context: API context (e.g. {project: "..."})
// opts: force - always request (default is false)
// http - options to pass to the inner $http call
// errorNotification - will popup an error notification if the API request fails (default true)
DataService.prototype.get = function(resource, name, context, opts) {
resource = APIService.toResourceGroupVersion(resource);
opts = opts || {};
var key = this._uniqueKey(resource, name, context, _.get(opts, 'http.params'));
var force = !!opts.force;
delete opts.force;
var deferred = $q.defer();
var existingImmutableData = this._immutableData(key);
// special case, if we have an immutable item, we can return it immediately
if (this._hasImmutable(resource, existingImmutableData, name)) {
$timeout(function() {
// we can be guaranteed this wont change on us, just send what we have in existingData
deferred.resolve(existingImmutableData.by('metadata.name')[name]);
}, 0);
}
else {
var self = this;
this._getNamespace(resource, context, opts).then(function(ns){
$http(angular.extend({
method: 'GET',
auth: {},
url: self._urlForResource(resource, name, context, false, ns)
}, opts.http || {}))
.success(function(data, status, headerFunc, config, statusText) {
if (self._isImmutable(resource)) {
if (!existingImmutableData) {
self._immutableData(key, [data]);
}
else {
existingImmutableData.update(data, "ADDED");
}
}
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
if (opts.errorNotification !== false) {
var msg = "Failed to get " + resource + "/" + name;
if (status !== 0) {
msg += " (" + status + ")";
}
Notification.error(msg);
}
deferred.reject({
data: data,
status: status,
headers: headers,
config: config
});
});
});
}
return deferred.promise;
};
// TODO (bpeterse): Create a new Streamer service & get this out of DataService.
DataService.prototype.createStream = function(resource, name, context, opts, isRaw) {
var self = this;
resource = APIService.toResourceGroupVersion(resource);
var protocols = isRaw ? 'binary.k8s.io' : 'base64.binary.k8s.io';
var identifier = 'stream_';
var openQueue = {};
var messageQueue = {};
var closeQueue = {};
var errorQueue = {};
var stream;
var makeStream = function() {
return self._getNamespace(resource, context, {})
.then(function(params) {
var cumulativeBytes = 0;
return $ws({
url: self._urlForResource(resource, name, context, true, _.extend(params, opts)),
auth: {},
onopen: function(evt) {
_.each(openQueue, function(fn) {
fn(evt);
});
},
onmessage: function(evt) {
if(!_.isString(evt.data)) {
Logger.log('log stream response is not a string', evt.data);
return;
}
var message;
if(!isRaw) {
message = base64.decode(base64util.pad(evt.data));
// Count bytes for log streams, which will stop when limitBytes is reached.
// There's no other way to detect we've reach the limit currently.
cumulativeBytes += message.length;
}
_.each(messageQueue, function(fn) {
if(isRaw) {
fn(evt.data);
} else {
fn(message, evt.data, cumulativeBytes);
}
});
},
onclose: function(evt) {
_.each(closeQueue, function(fn) {
fn(evt);
});
},
onerror: function(evt) {
_.each(errorQueue, function(fn) {
fn(evt);
});
},
protocols: protocols
}).then(function(ws) {
Logger.log("Streaming pod log", ws);
return ws;
});
});
};
return {
onOpen: function(fn) {
if(!_.isFunction(fn)) {
return;
}
var id = _.uniqueId(identifier);
openQueue[id] = fn;
return id;
},
onMessage: function(fn) {
if(!_.isFunction(fn)) {
return;
}
var id = _.uniqueId(identifier);
messageQueue[id] = fn;
return id;
},
onClose: function(fn) {
if(!_.isFunction(fn)) {
return;
}
var id = _.uniqueId(identifier);
closeQueue[id] = fn;
return id;
},
onError: function(fn) {
if(!_.isFunction(fn)) {
return;
}
var id = _.uniqueId(identifier);
errorQueue[id] = fn;
return id;
},
// can remove any callback from open, message, close or error
remove: function(id) {
if (openQueue[id]) { delete openQueue[id]; }
if (messageQueue[id]) { delete messageQueue[id]; }
if (closeQueue[id]) { delete closeQueue[id]; }
if (errorQueue[id]) { delete errorQueue[id]; }
},
start: function() {
stream = makeStream();
return stream;
},
stop: function() {
stream.then(function(ws) {
ws.close();
});
}
};
};
// resource: API resource (e.g. "pods")
// context: API context (e.g. {project: "..."})
// callback: optional function to be called with the initial list of the requested resource,
// and when updates are received, parameters passed to the callback:
// Data: a Data object containing the (context-qualified) results
// which includes a helper method for returning a map indexed
// by attribute (e.g. data.by('metadata.name'))
// event: specific event that caused this call ("ADDED", "MODIFIED",
// "DELETED", or null) callbacks can optionally use this to
// more efficiently process updates
// obj: specific object that caused this call (may be null if the
// entire list was updated) callbacks can optionally use this
// to more efficiently process updates
// opts: options
// poll: true | false - whether to poll the server instead of opening
// a websocket. Default is false.
// pollInterval: in milliseconds, how long to wait between polling the server
// only applies if poll=true. Default is 5000.
// http: similar to .get, etc. at this point, only used to pass http.params for filtering
// errorNotification: will popup an error notification if the API request fails (default true)
// returns handle to the watch, needed to unwatch e.g.
// var handle = DataService.watch(resource,context,callback[,opts])
// DataService.unwatch(handle)
DataService.prototype.watch = function(resource, context, callback, opts) {
resource = APIService.toResourceGroupVersion(resource);
opts = opts || {};
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
if (callback) {
// If we were given a callback, add it
this._watchCallbacks(key).add(callback);
}
else if (!this._watchCallbacks(key).has()) {
// We can be called with no callback in order to re-run a list/watch sequence for existing callbacks
// If there are no existing callbacks, return
return {};
}
var existingWatchOpts = this._watchOptions(key);
if (existingWatchOpts) {
// Check any options for compatibility with existing watch
if (!!existingWatchOpts.poll !== !!opts.poll) { // jshint ignore:line
throw "A watch already exists for " + resource + " with a different polling option.";
}
}
else {
this._watchOptions(key, opts);
}
var self = this;
if (this._isCached(key)) {
if (callback) {
$timeout(function() {
callback(self._data(key));
}, 0);
}
}
else {
if (callback) {
var resourceVersion = this._resourceVersion(key);
if (this._data(key)) {
$timeout(function() {
// If the cached data is still the latest that we have, send it to the callback
if (resourceVersion === self._resourceVersion(key)) {
callback(self._data(key)); // but just in case, still pull from the current data map
}
}, 0);
}
}
if (!this._listInFlight(key)) {
this._startListOp(resource, context, opts);
}
}
// returned handle needs resource, context, and callback in order to unwatch
return {
resource: resource,
context: context,
callback: callback,
opts: opts
};
};
// resource: API resource (e.g. "pods")
// name: API name, the unique name for the object
// context: API context (e.g. {project: "..."})
// callback: optional function to be called with the initial list of the requested resource,
// and when updates are received, parameters passed to the callback:
// obj: the requested object
// event: specific event that caused this call ("ADDED", "MODIFIED",
// "DELETED", or null) callbacks can optionally use this to
// more efficiently process updates
// opts: options
// poll: true | false - whether to poll the server instead of opening
// a websocket. Default is false.
// pollInterval: in milliseconds, how long to wait between polling the server
// only applies if poll=true. Default is 5000.
//
// returns handle to the watch, needed to unwatch e.g.
// var handle = DataService.watch(resource,context,callback[,opts])
// DataService.unwatch(handle)
DataService.prototype.watchObject = function(resource, name, context, callback, opts) {
resource = APIService.toResourceGroupVersion(resource);
opts = opts || {};
var key = this._uniqueKey(resource, name, context, _.get(opts, 'http.params'));
var wrapperCallback;
if (callback) {
// If we were given a callback, add it
this._watchObjectCallbacks(key).add(callback);
var self = this;
wrapperCallback = function(items, event, item) {
// If we got an event for a single item, only fire the callback if its the item we care about
if (item && item.metadata.name === name) {
self._watchObjectCallbacks(key).fire(item, event);
}
else if (!item) {
// Otherwise its an initial listing, see if we can find the item we care about in the list
var itemsByName = items.by("metadata.name");
if (itemsByName[name]) {
self._watchObjectCallbacks(key).fire(itemsByName[name]);
}
}
};
}
else if (!this._watchObjectCallbacks(key).has()) {
// This block may not be needed yet, don't expect this would get called without a callback currently...
return {};
}
// For now just watch the type, eventually we may want to do something more complicated
// and watch just the object if the type is not already being watched
var handle = this.watch(resource, context, wrapperCallback, opts);
handle.objectCallback = callback;
handle.objectName = name;
return handle;
};
DataService.prototype.unwatch = function(handle) {
var resource = handle.resource;
var objectName = handle.objectName;
var context = handle.context;
var callback = handle.callback;
var objectCallback = handle.objectCallback;
var opts = handle.opts;
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
if (objectCallback && objectName) {
var objectKey = this._uniqueKey(resource, objectName, context, _.get(opts, 'http.params'));
var objCallbacks = this._watchObjectCallbacks(objectKey);
objCallbacks.remove(objectCallback);
}
var callbacks = this._watchCallbacks(key);
if (callback) {
callbacks.remove(callback);
}
if (!callbacks.has()) {
if (opts && opts.poll) {
clearTimeout(this._watchPollTimeouts(key));
this._watchPollTimeouts(key, null);
}
else if (this._watchWebsockets(key)){
// watchWebsockets may not have been set up yet if the projectPromise never resolves
var ws = this._watchWebsockets(key);
// Make sure the onclose listener doesn't reopen this websocket.
ws.shouldClose = true;
ws.close();
this._watchWebsockets(key, null);
}
this._watchInFlight(key, false);
this._watchOptions(key, null);
}
};
// Takes an array of watch handles and unwatches them
DataService.prototype.unwatchAll = function(handles) {
for (var i = 0; i < handles.length; i++) {
this.unwatch(handles[i]);
}
};
DataService.prototype._watchCallbacks = function(key) {
if (!this._watchCallbacksMap[key]) {
this._watchCallbacksMap[key] = $.Callbacks();
}
return this._watchCallbacksMap[key];
};
DataService.prototype._watchObjectCallbacks = function(key) {
if (!this._watchObjectCallbacksMap[key]) {
this._watchObjectCallbacksMap[key] = $.Callbacks();
}
return this._watchObjectCallbacksMap[key];
};
DataService.prototype._listDeferred = function(key) {
if (!this._listDeferredMap[key]) {
this._listDeferredMap[key] = $q.defer();
}
return this._listDeferredMap[key];
};
DataService.prototype._watchInFlight = function(key, op) {
if (!op && op !== false) {
return this._watchOperationMap[key];
}
else {
this._watchOperationMap[key] = op;
}
};
DataService.prototype._listInFlight = function(key, op) {
if (!op && op !== false) {
return this._listOperationMap[key];
}
else {
this._listOperationMap[key] = op;
}
};
DataService.prototype._resourceVersion = function(key, rv) {
if (!rv) {
return this._resourceVersionMap[key];
}
else {
this._resourceVersionMap[key] = rv;
}
};
// uses $cacheFactory to impl LRU cache
DataService.prototype._data = function(key, data) {
return data ?
this._dataCache.put(key, new Data(data)) :
this._dataCache.get(key);
};
// uses $cacheFactory to impl LRU cache
DataService.prototype._immutableData = function(key, data) {
return data ?
this._immutableDataCache.put(key, new Data(data)) :
this._immutableDataCache.get(key);
};
DataService.prototype._isCached = function(key) {
return this._watchInFlight(key) &&
this._resourceVersion(key) &&
(!!this._data(key));
};
DataService.prototype._watchOptions = function(key, opts) {
if (opts === undefined) {
return this._watchOptionsMap[key];
}
else {
this._watchOptionsMap[key] = opts;
}
};
DataService.prototype._watchPollTimeouts = function(key, timeout) {
if (!timeout) {
return this._watchPollTimeoutsMap[key];
}
else {
this._watchPollTimeoutsMap[key] = timeout;
}
};
DataService.prototype._watchWebsockets = function(key, timeout) {
if (!timeout) {
return this._watchWebsocketsMap[key];
}
else {
this._watchWebsocketsMap[key] = timeout;
}
};
// Maximum number of websocket events to track per resource/context in _websocketEventsMap.
var maxWebsocketEvents = 10;
DataService.prototype._addWebsocketEvent = function(key, eventType) {
var events = this._websocketEventsMap[key];
if (!events) {
events = this._websocketEventsMap[key] = [];
}
// Add the event to the end of the array with the time in millis.
events.push({
type: eventType,
time: Date.now()
});
// Only keep 10 events. Shift the array to make room for the new event.
while (events.length > maxWebsocketEvents) { events.shift(); }
};
function isTooManyRecentEvents(events) {
// If we've had more than 10 events in 30 seconds, stop.
// The oldest event is at index 0.
var recentDuration = 1000 * 30;
return events.length >= maxWebsocketEvents && (Date.now() - events[0].time) < recentDuration;
}
function isTooManyConsecutiveCloses(events) {
var maxConsecutiveCloseEvents = 5;
if (events.length < maxConsecutiveCloseEvents) {
return false;
}
// Make sure the last 5 events were not close events, which means the
// connection is not succeeding. This check is necessary if connection
// timeouts take longer than 6 seconds.
for (var i = events.length - maxConsecutiveCloseEvents; i < events.length; i++) {
if (events[i].type !== 'close') {
return false;
}
}
return true;
}
DataService.prototype._isTooManyWebsocketRetries = function(key) {
var events = this._websocketEventsMap[key];
if (!events) {
return false;
}
if (isTooManyRecentEvents(events)) {
Logger.log("Too many websocket open or close events for resource/context in a short period", key, events);
return true;
}
if (isTooManyConsecutiveCloses(events)) {
Logger.log("Too many consecutive websocket close events for resource/context", key, events);
return true;
}
return false;
};
// will take an object, filter & sort it for consistent unique key generation
// uses encodeURIComponent internally because keys can have special characters, such as '='
var paramsForKey = function(params) {
var keys = _.keysIn(
_.pick(
params,
['fieldSelector', 'labelSelector'])
).sort();
return _.reduce(
keys,
function(result, key, i) {
return result + key + '=' + encodeURIComponent(params[key]) +
((i < (keys.length-1)) ? '&' : '');
}, '?');
};
// - creates a unique key representing a resource in its context (project)
// - primary use case for caching
// - proxies to _urlForResource to generate unique keys
// - ensure namespace if available
// - ensure only witelisted url params used for keys (fieldSelector, labelSelector) via paramsForKey
// and that these are consistently ordered
// - NOTE: Do not use the key as your url for API requests. This function does not use the 'isWebsocket'
// bool. Both websocket & http operations should respond with the same data from cache if key matches
// so the unique key will always include http://
DataService.prototype._uniqueKey = function(resource, name, context, params) {
var ns = context && context.namespace ||
_.get(context, 'project.metadata.name') ||
context.projectName;
return this._urlForResource(resource, name, context, null, angular.extend({}, {}, {namespace: ns})).toString() + paramsForKey(params || {});
};
DataService.prototype._startListOp = function(resource, context, opts) {
opts = opts || {};
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
// mark the operation as in progress
this._listInFlight(key, true);
var self = this;
if (context.projectPromise && !resource.equals("projects")) {
context.projectPromise.done(function(project) {
$http(angular.extend({
method: 'GET',
auth: {},
url: self._urlForResource(resource, null, context, false, {namespace: project.metadata.name})
}, opts.http || {}))
.success(function(data, status, headerFunc, config, statusText) {
self._listOpComplete(key, resource, context, opts, data);
}).error(function(data, status, headers, config) {
// mark list op as complete
self._listInFlight(key, false);
var deferred = self._listDeferred(key);
delete self._listDeferredMap[key];
deferred.reject(data, status, headers, config);
if (!_.get(opts, 'errorNotification', true)) {
return;
}
var msg = "Failed to list " + resource;
if (status !== 0) {
msg += " (" + status + ")";
}
Notification.error(msg);
});
});
}
else {
$http({
method: 'GET',
auth: {},
url: this._urlForResource(resource, null, context),
}).success(function(data, status, headerFunc, config, statusText) {
self._listOpComplete(key, resource, context, opts, data);
}).error(function(data, status, headers, config) {
// mark list op as complete
self._listInFlight(key, false);
var deferred = self._listDeferred(key);
delete self._listDeferredMap[key];
deferred.reject(data, status, headers, config);
if (!_.get(opts, 'errorNotification', true)) {
return;
}
var msg = "Failed to list " + resource;
if (status !== 0) {
msg += " (" + status + ")";
}
Notification.error(msg);
});
}
};
DataService.prototype._listOpComplete = function(key, resource, context, opts, data) {
if (!data.items) {
console.warn("List request for " + resource + " returned a null items array. This is an invalid API response.");
}
var items = data.items || [];
// Here we normalize all items to have a kind property.
// One of the warts in the kubernetes REST API is that items retrieved
// via GET on a list resource won't have a kind property set.
// See: https://github.com/kubernetes/kubernetes/issues/3030
if (data.kind && data.kind.indexOf("List") === data.kind.length - 4) {
angular.forEach(items, function(item) {
if (!item.kind) {
item.kind = data.kind.slice(0, -4);
}
if (!item.apiVersion) {
item.apiVersion = data.apiVersion;
}
});
}
// mark list op as complete
this._listInFlight(key, false);
var deferred = this._listDeferred(key);
delete this._listDeferredMap[key];
this._resourceVersion(key, data.resourceVersion || data.metadata.resourceVersion);
this._data(key, items);
deferred.resolve(this._data(key));
this._watchCallbacks(key).fire(this._data(key));
if (this._watchCallbacks(key).has()) {
var watchOpts = this._watchOptions(key) || {};
if (watchOpts.poll) {
this._watchInFlight(key, true);
this._watchPollTimeouts(key, setTimeout($.proxy(this, "_startListOp", resource, context), watchOpts.pollInterval || 5000));
}
else if (!this._watchInFlight(key)) {
this._startWatchOp(key, resource, context, opts, this._resourceVersion(key));
}
}
};
DataService.prototype._startWatchOp = function(key, resource, context, opts, resourceVersion) {
this._watchInFlight(key, true);
// Note: current impl uses one websocket per resource
// eventually want a single websocket connection that we
// send a subscription request to for each resource
// Only listen for updates if websockets are available
if ($ws.available()) {
var self = this;
var params = _.get(opts, 'http.params') || {};
params.watch = true;
if (resourceVersion) {
params.resourceVersion = resourceVersion;
}
if (context.projectPromise && !resource.equals("projects")) {
context.projectPromise.done(function(project) {
params.namespace = project.metadata.name;
$ws({
method: "WATCH",
url: self._urlForResource(resource, null, context, true, params),
auth: {},
onclose: $.proxy(self, "_watchOpOnClose", resource, context, opts),
onmessage: $.proxy(self, "_watchOpOnMessage", resource, context, opts),
onopen: $.proxy(self, "_watchOpOnOpen", resource, context, opts)
}).then(function(ws) {
Logger.log("Watching", ws);
self._watchWebsockets(key, ws);
});
});
}
else {
$ws({
method: "WATCH",
url: self._urlForResource(resource, null, context, true, params),
auth: {},
onclose: $.proxy(self, "_watchOpOnClose", resource, context, opts),
onmessage: $.proxy(self, "_watchOpOnMessage", resource, context, opts),
onopen: $.proxy(self, "_watchOpOnOpen", resource, context, opts)
}).then(function(ws){
Logger.log("Watching", ws);
self._watchWebsockets(key, ws);
});
}
}
};
DataService.prototype._watchOpOnOpen = function(resource, context, opts, event) {
Logger.log('Websocket opened for resource/context', resource, context);
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
this._addWebsocketEvent(key, 'open');
};
DataService.prototype._watchOpOnMessage = function(resource, context, opts, event) {
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
try {
var eventData = $.parseJSON(event.data);
if (eventData.type == "ERROR") {
Logger.log("Watch window expired for resource/context", resource, context);
if (event.target) {
event.target.shouldRelist = true;
}
return;
}
else if (eventData.type === "DELETED") {
// Add this ourselves since the API doesn't add anything
// this way the views can use it to trigger special behaviors
if (eventData.object && eventData.object.metadata && !eventData.object.metadata.deletionTimestamp) {
eventData.object.metadata.deletionTimestamp = (new Date()).toISOString();
}
}
if (eventData.object) {
this._resourceVersion(key, eventData.object.resourceVersion || eventData.object.metadata.resourceVersion);
}
// TODO do we reset all the by() indices, or simply update them, since we should know what keys are there?
// TODO let the data object handle its own update
this._data(key).update(eventData.object, eventData.type);
var self = this;
// Wrap in a $timeout which will trigger an $apply to mirror $http callback behavior
// without timeout this is triggering a repeated digest loop
$timeout(function() {
self._watchCallbacks(key).fire(self._data(key), eventData.type, eventData.object);
}, 0);
}
catch (e) {
// TODO: surface in the UI?
Logger.error("Error processing message", resource, event.data);
}
};
DataService.prototype._watchOpOnClose = function(resource, context, opts, event) {
var eventWS = event.target;
var key = this._uniqueKey(resource, null, context, _.get(opts, 'http.params'));
if (!eventWS) {
Logger.log("Skipping reopen, no eventWS in event", event);
return;
}
var registeredWS = this._watchWebsockets(key);
if (!registeredWS) {
Logger.log("Skipping reopen, no registeredWS for resource/context", resource, context);
return;
}
// Don't reopen a web socket that is no longer registered for this resource/context
if (eventWS !== registeredWS) {
Logger.log("Skipping reopen, eventWS does not match registeredWS", eventWS, registeredWS);
return;
}
// We are the registered web socket for this resource/context, and we are no longer in flight
// Unlock this resource/context in case we decide not to reopen
this._watchInFlight(key, false);
// Don't reopen web sockets we closed ourselves
if (eventWS.shouldClose) {
Logger.log("Skipping reopen, eventWS was explicitly closed", eventWS);
return;
}
// Don't reopen clean closes (for example, navigating away from the page to example.com)
if (event.wasClean) {
Logger.log("Skipping reopen, clean close", event);
return;
}
// Don't reopen if no one is listening for this data any more
if (!this._watchCallbacks(key).has()) {
Logger.log("Skipping reopen, no listeners registered for resource/context", resource, context);
return;
}
// Don't reopen if we've failed this resource/context too many times
if (this._isTooManyWebsocketRetries(key)) {
// Show an error notication unless disabled in opts.
if (_.get(opts, 'errorNotification', true)) {
Notification.error("Server connection interrupted.", {
id: "websocket_retry_halted",
mustDismiss: true,
actions: {
refresh: {label: "Refresh", action: function() { window.location.reload(); }}
}
});
}
return;
}
// Keep track of this event.
this._addWebsocketEvent(key, 'close');
// If our watch window expired, we have to relist to get a new resource version to watch from
if (eventWS.shouldRelist) {
Logger.log("Relisting for resource/context", resource, context);
// Restart a watch() from the beginning, which triggers a list/watch sequence
// The watch() call is responsible for setting _watchInFlight back to true
// Add a short delay to avoid a scenario where we make non-stop requests
// When the timeout fires, if no callbacks are registered for this
// resource/context, or if a watch is already in flight, `watch()` is a no-op
var self = this;
setTimeout(function() {
self.watch(resource, context);
}, 2000);
return;
}
// Attempt to re-establish the connection after a two-second back-off
// Re-mark ourselves as in-flight to prevent other callers from jumping in in the meantime
Logger.log("Rewatching for resource/context", resource, context);
this._watchInFlight(key, true);
setTimeout(
$.proxy(this, "_startWatchOp", key, resource, context, opts, this._resourceVersion(key)),
2000
);
};
var URL_ROOT_TEMPLATE = "{protocol}://{+hostPort}{+prefix}{/group}/{version}/";
var URL_GET_LIST = URL_ROOT_TEMPLATE + "{resource}{?q*}";
var URL_OBJECT = URL_ROOT_TEMPLATE + "{resource}/{name}{/subresource*}{?q*}";
var URL_NAMESPACED_GET_LIST = URL_ROOT_TEMPLATE + "namespaces/{namespace}/{resource}{?q*}";
var URL_NAMESPACED_OBJECT = URL_ROOT_TEMPLATE + "namespaces/{namespace}/{resource}/{name}{/subresource*}{?q*}";
DataService.prototype._urlForResource = function(resource, name, context, isWebsocket, params) {
var apiInfo = APIService.apiInfo(resource);
if (!apiInfo) {
Logger.error("_urlForResource called with unknown resource", resource, arguments);
return null;
}
var protocol;
params = params || {};
if (isWebsocket) {
protocol = window.location.protocol === "http:" ? "ws" : "wss";
}
else {
protocol = window.location.protocol === "http:" ? "http" : "https";
}
if (context && context.namespace && !params.namespace) {
params.namespace = context.namespace;
}
var namespaceInPath = params.namespace;
var namespace = null;
if (namespaceInPath) {
namespace = params.namespace;
params = angular.copy(params);
delete params.namespace;
}
var template;
var templateOptions = {
protocol: protocol,
hostPort: apiInfo.hostPort,
prefix: apiInfo.prefix,
group: apiInfo.group,
version: apiInfo.version,
resource: resource.primaryResource(),
subresource: resource.subresources(),
name: name,
namespace: namespace,
q: params
};
if (name) {
template = namespaceInPath ? URL_NAMESPACED_OBJECT : URL_OBJECT;
}
else {
template = namespaceInPath ? URL_NAMESPACED_GET_LIST : URL_GET_LIST;
}
return URI.expand(template, templateOptions).toString();
};
DataService.prototype.url = function(options) {
if (options && options.resource) {
var opts = angular.copy(options);
delete opts.resource;
delete opts.group;
delete opts.version;
delete opts.name;
delete opts.isWebsocket;
var resource = APIService.toResourceGroupVersion({
resource: options.resource,
group: options.group,
version: options.version
});
return this._urlForResource(resource, options.name, null, !!options.isWebsocket, opts);
}
return null;
};
DataService.prototype.openshiftAPIBaseUrl = function() {
var protocol = window.location.protocol === "http:" ? "http" : "https";
var hostPort = API_CFG.openshift.hostPort;
return new URI({protocol: protocol, hostname: hostPort}).toString();
};
// Immutables are flagged here as we should not need to fetch them more than once.
var IMMUTABLE_RESOURCE = {
imagestreamimages: true
};
// - request once and never need to request again, these do not change!
DataService.prototype._isImmutable = function(resource) {
return !!IMMUTABLE_RESOURCE[resource.resource];
};
// do we already have the data for this?
DataService.prototype._hasImmutable = function(resource, existingData, name) {
return this._isImmutable(resource) && existingData && existingData.by('metadata.name')[name];
};
DataService.prototype._getNamespace = function(resource, context, opts) {
var deferred = $q.defer();
if (opts.namespace) {
deferred.resolve({namespace: opts.namespace});
}
else if (context.projectPromise && !resource.equals("projects")) {
context.projectPromise.done(function(project) {
deferred.resolve({namespace: project.metadata.name});
});
}
else {
deferred.resolve(null);
}
return deferred.promise;
};
return new DataService();
});
'use strict';
// Logout strategies
angular.module('openshiftCommon')
.provider('DeleteTokenLogoutService', function() {
this.$get = function($q, $injector, Logger) {
var authLogger = Logger.get("auth");
return {
logout: function(user, token) {
authLogger.log("DeleteTokenLogoutService.logout()", user, token);
// If we don't have a token, we're done
if (!token) {
authLogger.log("DeleteTokenLogoutService, no token, returning immediately");
return $q.when({});
}
// Lazily get the data service. Can't explicitly depend on it or we get circular dependencies.
var DataService = $injector.get('DataService');
// Use the token to delete the token
// Never trigger a login when deleting our token
var opts = {http: {auth: {token: token, triggerLogin: false}}};
// TODO: Change this to return a promise that "succeeds" even if the token delete fails?
return DataService.delete("oauthaccesstokens", token, {}, opts);
},
};
};
});
'use strict';
angular.module('openshiftCommon')
.provider('Logger', function() {
this.$get = function() {
// Wraps the global Logger from https://github.com/jonnyreeves/js-logger
var OSLogger = Logger.get("OpenShift");
var logger = {
get: function(name) {
var logger = Logger.get("OpenShift/" + name);
var logLevel = "OFF";
if (localStorage) {
logLevel = localStorage['OpenShiftLogLevel.' + name] || logLevel;
}
logger.setLevel(Logger[logLevel]);
return logger;
},
log: function() {
OSLogger.log.apply(OSLogger, arguments);
},
info: function() {
OSLogger.info.apply(OSLogger, arguments);
},
debug: function() {
OSLogger.debug.apply(OSLogger, arguments);
},
warn: function() {
OSLogger.warn.apply(OSLogger, arguments);
},
error: function() {
OSLogger.error.apply(OSLogger, arguments);
}
};
// Set default log level
var logLevel = "ERROR";
if (localStorage) {
logLevel = localStorage['OpenShiftLogLevel.main'] || logLevel;
}
OSLogger.setLevel(Logger[logLevel]);
return logger;
};
});
'use strict';
/* jshint unused: false */
// UserStore objects able to remember user and tokens for the current user
angular.module('openshiftCommon')
.provider('MemoryUserStore', function() {
this.$get = function(Logger){
var authLogger = Logger.get("auth");
var _user = null;
var _token = null;
return {
available: function() {
return true;
},
getUser: function(){
authLogger.log("MemoryUserStore.getUser", _user);
return _user;
},
setUser: function(user, ttl) {
// TODO: honor ttl
authLogger.log("MemoryUserStore.setUser", user);
_user = user;
},
getToken: function() {
authLogger.log("MemoryUserStore.getToken", _token);
return _token;
},
setToken: function(token, ttl) {
// TODO: honor ttl
authLogger.log("MemoryUserStore.setToken", token);
_token = token;
}
};
};
})
.provider('SessionStorageUserStore', function() {
this.$get = function(Logger){
var authLogger = Logger.get("auth");
var userkey = "SessionStorageUserStore.user";
var tokenkey = "SessionStorageUserStore.token";
return {
available: function() {
try {
var x = String(new Date().getTime());
sessionStorage['SessionStorageUserStore.test'] = x;
var y = sessionStorage['SessionStorageUserStore.test'];
sessionStorage.removeItem('SessionStorageUserStore.test');
return x === y;
} catch(e) {
return false;
}
},
getUser: function(){
try {
var user = JSON.parse(sessionStorage[userkey]);
authLogger.log("SessionStorageUserStore.getUser", user);
return user;
} catch(e) {
authLogger.error("SessionStorageUserStore.getUser", e);
return null;
}
},
setUser: function(user, ttl) {
// TODO: honor ttl
if (user) {
authLogger.log("SessionStorageUserStore.setUser", user);
sessionStorage[userkey] = JSON.stringify(user);
} else {
authLogger.log("SessionStorageUserStore.setUser", user, "deleting");
sessionStorage.removeItem(userkey);
}
},
getToken: function() {
try {
var token = sessionStorage[tokenkey];
authLogger.log("SessionStorageUserStore.getToken", token);
return token;
} catch(e) {
authLogger.error("SessionStorageUserStore.getToken", e);
return null;
}
},
setToken: function(token, ttl) {
// TODO: honor ttl
if (token) {
authLogger.log("SessionStorageUserStore.setToken", token);
sessionStorage[tokenkey] = token;
} else {
authLogger.log("SessionStorageUserStore.setToken", token, "deleting");
sessionStorage.removeItem(tokenkey);
}
}
};
};
})
.provider('LocalStorageUserStore', function() {
this.$get = function(Logger){
var authLogger = Logger.get("auth");
var userkey = "LocalStorageUserStore.user";
var tokenkey = "LocalStorageUserStore.token";
var ttlKey = function(key) {
return key + ".ttl";
};
var setTTL = function(key, ttl) {
if (ttl) {
var expires = new Date().getTime() + ttl*1000;
localStorage[ttlKey(key)] = expires;
authLogger.log("LocalStorageUserStore.setTTL", key, ttl, new Date(expires).toString());
} else {
localStorage.removeItem(ttlKey(key));
authLogger.log("LocalStorageUserStore.setTTL deleting", key);
}
};
var isTTLExpired = function(key) {
var ttl = localStorage[ttlKey(key)];
if (!ttl) {
return false;
}
var expired = parseInt(ttl) < new Date().getTime();
authLogger.log("LocalStorageUserStore.isTTLExpired", key, expired);
return expired;
};
return {
available: function() {
try {
var x = String(new Date().getTime());
localStorage['LocalStorageUserStore.test'] = x;
var y = localStorage['LocalStorageUserStore.test'];
localStorage.removeItem('LocalStorageUserStore.test');
return x === y;
} catch(e) {
return false;
}
},
getUser: function(){
try {
if (isTTLExpired(userkey)) {
authLogger.log("LocalStorageUserStore.getUser expired");
localStorage.removeItem(userkey);
setTTL(userkey, null);
return null;
}
var user = JSON.parse(localStorage[userkey]);
authLogger.log("LocalStorageUserStore.getUser", user);
return user;
} catch(e) {
authLogger.error("LocalStorageUserStore.getUser", e);
return null;
}
},
setUser: function(user, ttl) {
if (user) {
authLogger.log("LocalStorageUserStore.setUser", user, ttl);
localStorage[userkey] = JSON.stringify(user);
setTTL(userkey, ttl);
} else {
authLogger.log("LocalStorageUserStore.setUser", user, "deleting");
localStorage.removeItem(userkey);
setTTL(userkey, null);
}
},
getToken: function() {
try {
if (isTTLExpired(tokenkey)) {
authLogger.log("LocalStorageUserStore.getToken expired");
localStorage.removeItem(tokenkey);
setTTL(tokenkey, null);
return null;
}
var token = localStorage[tokenkey];
authLogger.log("LocalStorageUserStore.getToken", token);
return token;
} catch(e) {
authLogger.error("LocalStorageUserStore.getToken", e);
return null;
}
},
setToken: function(token, ttl) {
if (token) {
authLogger.log("LocalStorageUserStore.setToken", token, ttl);
localStorage[tokenkey] = token;
setTTL(tokenkey, ttl);
} else {
authLogger.log("LocalStorageUserStore.setToken", token, ttl, "deleting");
localStorage.removeItem(tokenkey);
setTTL(tokenkey, null);
}
}
};
};
});
'use strict';
/* jshint unused: false */
angular.module('openshiftCommon')
.factory('Notification', function($rootScope) {
function Notification() {
this.messenger = Messenger({
extraClasses: 'messenger-fixed messenger-on-bottom messenger-on-right',
theme: 'flat',
messageDefaults: {
showCloseButton: true,
hideAfter: 10
}
});
var self = this;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
self.clear();
});
}
// Opts:
// id - if an id is passed only one message with this id will ever be shown
// mustDismiss - the user must explicitly dismiss the message, it will not auto-hide
Notification.prototype.notify = function(type, message, opts) {
opts = opts || {};
var notifyOpts = {
type: type,
// TODO report this issue upstream to messenger, they don't handle messages with invalid html
// they should be escaping it
message: $('<div/>').text(message).html(),
id: opts.id,
actions: opts.actions
};
if (opts.mustDismiss) {
notifyOpts.hideAfter = false;
}
this.messenger.post(notifyOpts);
};
Notification.prototype.success = function(message, opts) {
this.notify("success", message, opts);
};
Notification.prototype.info = function(message, opts) {
this.notify("info", message, opts);
};
Notification.prototype.error = function(message, opts) {
this.notify("error", message, opts);
};
Notification.prototype.warning = function(message, opts) {
this.notify("warning", message, opts);
};
Notification.prototype.clear = function() {
this.messenger.hideAll();
};
return new Notification();
});
'use strict';
// Login strategies
angular.module('openshiftCommon')
.provider('RedirectLoginService', function() {
var _oauth_client_id = "";
var _oauth_authorize_uri = "";
var _oauth_redirect_uri = "";
this.OAuthClientID = function(id) {
if (id) {
_oauth_client_id = id;
}
return _oauth_client_id;
};
this.OAuthAuthorizeURI = function(uri) {
if (uri) {
_oauth_authorize_uri = uri;
}
return _oauth_authorize_uri;
};
this.OAuthRedirectURI = function(uri) {
if (uri) {
_oauth_redirect_uri = uri;
}
return _oauth_redirect_uri;
};
this.$get = function($location, $q, Logger, base64) {
var authLogger = Logger.get("auth");
var getRandomInts = function(length) {
var randomValues;
if (window.crypto && window.Uint32Array) {
try {
var r = new Uint32Array(length);
window.crypto.getRandomValues(r);
randomValues = [];
for (var j=0; j < length; j++) {
randomValues.push(r[j]);
}
} catch(e) {
authLogger.debug("RedirectLoginService.getRandomInts: ", e);
randomValues = null;
}
}
if (!randomValues) {
randomValues = [];
for (var i=0; i < length; i++) {
randomValues.push(Math.floor(Math.random() * 4294967296));
}
}
return randomValues;
};
var nonceKey = "RedirectLoginService.nonce";
var makeState = function(then) {
var nonce = String(new Date().getTime()) + "-" + getRandomInts(8).join("");
try {
window.localStorage[nonceKey] = nonce;
} catch(e) {
authLogger.log("RedirectLoginService.makeState, localStorage error: ", e);
}
return base64.urlencode(JSON.stringify({then: then, nonce:nonce}));
};
var parseState = function(state) {
var retval = {
then: null,
verified: false
};
var nonce = "";
try {
nonce = window.localStorage[nonceKey];
window.localStorage.removeItem(nonceKey);
} catch(e) {
authLogger.log("RedirectLoginService.parseState, localStorage error: ", e);
}
try {
var data = state ? JSON.parse(base64.urldecode(state)) : {};
if (data && data.nonce && nonce && data.nonce === nonce) {
retval.verified = true;
retval.then = data.then;
}
} catch(e) {
authLogger.error("RedirectLoginService.parseState, state error: ", e);
}
authLogger.error("RedirectLoginService.parseState", retval);
return retval;
};
return {
// Returns a promise that resolves with {user:{...}, token:'...', ttl:X}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']}
login: function() {
if (_oauth_client_id === "") {
return $q.reject({error:'invalid_request', error_description:'RedirectLoginServiceProvider.OAuthClientID() not set'});
}
if (_oauth_authorize_uri === "") {
return $q.reject({error:'invalid_request', error_description:'RedirectLoginServiceProvider.OAuthAuthorizeURI() not set'});
}
if (_oauth_redirect_uri === "") {
return $q.reject({error:'invalid_request', error_description:'RedirectLoginServiceProvider.OAuthRedirectURI not set'});
}
var deferred = $q.defer();
var uri = new URI(_oauth_authorize_uri);
// Never send a local fragment to remote servers
var returnUri = new URI($location.url()).fragment("");
uri.query({
client_id: _oauth_client_id,
response_type: 'token',
state: makeState(returnUri.toString()),
redirect_uri: _oauth_redirect_uri
});
authLogger.log("RedirectLoginService.login(), redirecting", uri.toString());
window.location.href = uri.toString();
// Return a promise we never intend to keep, because we're redirecting to another page
return deferred.promise;
},
// Parses oauth callback parameters from window.location
// Returns a promise that resolves with {token:'...',then:'...',verified:true|false}, or rejects with {error:'...'[,error_description:'...',error_uri:'...']}
// If no token and no error is present, resolves with {}
// Example error codes: https://tools.ietf.org/html/rfc6749#section-5.2
finish: function() {
// Get url
var u = new URI($location.url());
// Read params
var queryParams = u.query(true);
var fragmentParams = new URI("?" + u.fragment()).query(true);
authLogger.log("RedirectLoginService.finish()", queryParams, fragmentParams);
// Error codes can come in query params or fragment params
// Handle an error response from the OAuth server
var error = queryParams.error || fragmentParams.error;
if (error) {
var error_description = queryParams.error_description || fragmentParams.error_description;
var error_uri = queryParams.error_uri || fragmentParams.error_uri;
authLogger.log("RedirectLoginService.finish(), error", error, error_description, error_uri);
return $q.reject({
error: error,
error_description: error_description,
error_uri: error_uri
});
}
var stateData = parseState(fragmentParams.state);
// Handle an access_token response
if (fragmentParams.access_token && (fragmentParams.token_type || "").toLowerCase() === "bearer") {
var deferred = $q.defer();
deferred.resolve({
token: fragmentParams.access_token,
ttl: fragmentParams.expires_in,
then: stateData.then,
verified: stateData.verified
});
return deferred.promise;
}
// No token and no error is invalid
return $q.reject({
error: "invalid_request",
error_description: "No API token returned"
});
}
};
};
});
'use strict';
// Provide a websocket implementation that behaves like $http
// Methods:
// $ws({
// url: "...", // required
// method: "...", // defaults to WATCH
// })
// returns a promise to the opened WebSocket
//
// $ws.available()
// returns true if WebSockets are available to use
angular.module('openshiftCommon')
.provider('$ws', function($httpProvider) {
// $get method is called to build the $ws service
this.$get = function($q, $injector, Logger) {
var authLogger = Logger.get("auth");
authLogger.log("$wsProvider.$get", arguments);
// Build list of interceptors from $httpProvider when constructing the $ws service
// Build in reverse-order, so the last interceptor added gets to handle the request first
var _interceptors = [];
angular.forEach($httpProvider.interceptors, function(interceptorFactory) {
if (angular.isString(interceptorFactory)) {
_interceptors.unshift($injector.get(interceptorFactory));
} else {
_interceptors.unshift($injector.invoke(interceptorFactory));
}
});
// Implement $ws()
var $ws = function(config) {
config.method = angular.uppercase(config.method || "WATCH");
authLogger.log("$ws (pre-intercept)", config.url.toString());
var serverRequest = function(config) {
authLogger.log("$ws (post-intercept)", config.url.toString());
var ws = new WebSocket(config.url, config.protocols);
if (config.onclose) { ws.onclose = config.onclose; }
if (config.onmessage) { ws.onmessage = config.onmessage; }
if (config.onopen) { ws.onopen = config.onopen; }
if (config.onerror) { ws.onerror = config.onerror; }
return ws;
};
// Apply interceptors to request config
var chain = [serverRequest, undefined];
var promise = $q.when(config);
angular.forEach(_interceptors, function(interceptor) {
if (interceptor.request || interceptor.requestError) {
chain.unshift(interceptor.request, interceptor.requestError);
}
// TODO: figure out how to get interceptors to handle response errors from web sockets
// if (interceptor.response || interceptor.responseError) {
// chain.push(interceptor.response, interceptor.responseError);
// }
});
while (chain.length) {
var thenFn = chain.shift();
var rejectFn = chain.shift();
promise = promise.then(thenFn, rejectFn);
}
return promise;
};
// Implement $ws.available()
$ws.available = function() {
try {
return !!WebSocket;
}
catch(e) {
return false;
}
};
return $ws;
};
})
/* A WebSocket factory for kubernetesContainerTerminal */
.factory("ContainerWebSocket", function(API_CFG, $ws) {
return function AuthWebSocket(url, protocols) {
var scheme;
if (url.indexOf("/") === 0) {
scheme = window.location.protocol === "http:" ? "ws://" : "wss://";
url = scheme + API_CFG.openshift.hostPort + url;
}
return $ws({ url: url, method: "WATCH", protocols: protocols, auth: {} });
};
});
// Karma configuration
// http://karma-runner.github.io/0.12/config/configuration-file.html
// Generated on 2014-09-12 using
// generator-karma 0.8.3
module.exports = function(config) {
'use strict';
config.set({
// base path, that will be used to resolve files and exclude
basePath: '../',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
"bower_components/jquery/dist/jquery.js",
"bower_components/lodash/lodash.js",
"bower_components/angular/angular.js",
'bower_components/angular-mocks/angular-mocks.js',
"bower_components/angular-sanitize/angular-sanitize.js",
"bower_components/js-logger/src/logger.js",
"bower_components/messenger/build/js/messenger.js",
"bower_components/angular-utf8-base64/angular-utf8-base64.js",
"bower_components/uri.js/src/URI.js",
"bower_components/uri.js/src/URITemplate.js",
"bower_components/uri.js/src/jquery.URI.js",
"bower_components/uri.js/src/URI.fragmentURI.js",
"bower_components/hawtio-core/hawtio-core.js",
"bower_components/kubernetes-container-terminal/dist/container-terminal.js",
"bower_components/hawtio-extension-service/dist/hawtio-extension-service.js",
'src/**/*.js',
'test/spec/spec-helper.js',
'test/spec/fixtures/api-discovery.js',
'test/spec/**/*.js'
],
// list of files / patterns to exclude
exclude: [],
// use dots reporter, as travis terminal does not support escaping sequences
// possible values: 'dots', 'progress'
// CLI --reporters progress
reporters: ['progress', 'junit'],
junitReporter: {
// will be resolved to basePath (in the same way as files/exclude patterns)
outputFile: 'test/test-results.xml'
},
// web server port
port: 8443,
colors: true,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_DEBUG,
// enable / disable watching file and executing tests whenever any file changes
// CLI --auto-watch --no-auto-watch
autoWatch: true,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
// CLI --browsers Chrome,Firefox,Safari
browsers: [process.env.TRAVIS ? 'Firefox' : 'Chrome'],
// If browser does not capture in given timeout [ms], kill it
// CLI --capture-timeout 5000
captureTimeout: 20000,
// Auto run tests on start (when browsers are captured) and exit
// CLI --single-run --no-single-run
singleRun: false,
// report which specs are slower than 500ms
// CLI --report-slower-than 500
reportSlowerThan: 500
});
};
// Mocked API discovery
window.OPENSHIFT_CONFIG.api.k8s.resources = {
"v1": {
"bindings": {
"name": "bindings",
"namespaced": true,
"kind": "Binding"
},
"componentstatuses": {
"name": "componentstatuses",
"namespaced": false,
"kind": "ComponentStatus"
},
"configmaps": {
"name": "configmaps",
"namespaced": true,
"kind": "ConfigMap"
},
"endpoints": {
"name": "endpoints",
"namespaced": true,
"kind": "Endpoints"
},
"events": {
"name": "events",
"namespaced": true,
"kind": "Event"
},
"limitranges": {
"name": "limitranges",
"namespaced": true,
"kind": "LimitRange"
},
"namespaces": {
"name": "namespaces",
"namespaced": false,
"kind": "Namespace"
},
"namespaces/finalize": {
"name": "namespaces/finalize",
"namespaced": false,
"kind": "Namespace"
},
"namespaces/status": {
"name": "namespaces/status",
"namespaced": false,
"kind": "Namespace"
},
"nodes": {
"name": "nodes",
"namespaced": false,
"kind": "Node"
},
"nodes/proxy": {
"name": "nodes/proxy",
"namespaced": false,
"kind": "Node"
},
"nodes/status": {
"name": "nodes/status",
"namespaced": false,
"kind": "Node"
},
"persistentvolumeclaims": {
"name": "persistentvolumeclaims",
"namespaced": true,
"kind": "PersistentVolumeClaim"
},
"persistentvolumeclaims/status": {
"name": "persistentvolumeclaims/status",
"namespaced": true,
"kind": "PersistentVolumeClaim"
},
"persistentvolumes": {
"name": "persistentvolumes",
"namespaced": false,
"kind": "PersistentVolume"
},
"persistentvolumes/status": {
"name": "persistentvolumes/status",
"namespaced": false,
"kind": "PersistentVolume"
},
"pods": {
"name": "pods",
"namespaced": true,
"kind": "Pod"
},
"pods/attach": {
"name": "pods/attach",
"namespaced": true,
"kind": "Pod"
},
"pods/binding": {
"name": "pods/binding",
"namespaced": true,
"kind": "Binding"
},
"pods/exec": {
"name": "pods/exec",
"namespaced": true,
"kind": "Pod"
},
"pods/log": {
"name": "pods/log",
"namespaced": true,
"kind": "Pod"
},
"pods/portforward": {
"name": "pods/portforward",
"namespaced": true,
"kind": "Pod"
},
"pods/proxy": {
"name": "pods/proxy",
"namespaced": true,
"kind": "Pod"
},
"pods/status": {
"name": "pods/status",
"namespaced": true,
"kind": "Pod"
},
"podtemplates": {
"name": "podtemplates",
"namespaced": true,
"kind": "PodTemplate"
},
"replicationcontrollers": {
"name": "replicationcontrollers",
"namespaced": true,
"kind": "ReplicationController"
},
"replicationcontrollers/scale": {
"name": "replicationcontrollers/scale",
"namespaced": true,
"kind": "Scale"
},
"replicationcontrollers/status": {
"name": "replicationcontrollers/status",
"namespaced": true,
"kind": "ReplicationController"
},
"resourcequotas": {
"name": "resourcequotas",
"namespaced": true,
"kind": "ResourceQuota"
},
"resourcequotas/status": {
"name": "resourcequotas/status",
"namespaced": true,
"kind": "ResourceQuota"
},
"secrets": {
"name": "secrets",
"namespaced": true,
"kind": "Secret"
},
"securitycontextconstraints": {
"name": "securitycontextconstraints",
"namespaced": false,
"kind": "SecurityContextConstraints"
},
"serviceaccounts": {
"name": "serviceaccounts",
"namespaced": true,
"kind": "ServiceAccount"
},
"services": {
"name": "services",
"namespaced": true,
"kind": "Service"
},
"services/proxy": {
"name": "services/proxy",
"namespaced": true,
"kind": "Service"
},
"services/status": {
"name": "services/status",
"namespaced": true,
"kind": "Service"
}
}
};
window.OPENSHIFT_CONFIG.api.openshift.resources = {
"v1": {
"buildconfigs": {
"name": "buildconfigs",
"namespaced": true,
"kind": "BuildConfig"
},
"buildconfigs/instantiate": {
"name": "buildconfigs/instantiate",
"namespaced": true,
"kind": "BuildRequest"
},
"buildconfigs/instantiatebinary": {
"name": "buildconfigs/instantiatebinary",
"namespaced": true,
"kind": "BinaryBuildRequestOptions"
},
"buildconfigs/webhooks": {
"name": "buildconfigs/webhooks",
"namespaced": true,
"kind": "Status"
},
"builds": {
"name": "builds",
"namespaced": true,
"kind": "Build"
},
"builds/clone": {
"name": "builds/clone",
"namespaced": true,
"kind": "BuildRequest"
},
"builds/details": {
"name": "builds/details",
"namespaced": true,
"kind": "Build"
},
"builds/log": {
"name": "builds/log",
"namespaced": true,
"kind": "BuildLog"
},
"clusternetworks": {
"name": "clusternetworks",
"namespaced": false,
"kind": "ClusterNetwork"
},
"clusterpolicies": {
"name": "clusterpolicies",
"namespaced": false,
"kind": "ClusterPolicy"
},
"clusterpolicybindings": {
"name": "clusterpolicybindings",
"namespaced": false,
"kind": "ClusterPolicyBinding"
},
"clusterrolebindings": {
"name": "clusterrolebindings",
"namespaced": false,
"kind": "ClusterRoleBinding"
},
"clusterroles": {
"name": "clusterroles",
"namespaced": false,
"kind": "ClusterRole"
},
"deploymentconfigrollbacks": {
"name": "deploymentconfigrollbacks",
"namespaced": true,
"kind": "DeploymentConfigRollback"
},
"deploymentconfigs": {
"name": "deploymentconfigs",
"namespaced": true,
"kind": "DeploymentConfig"
},
"deploymentconfigs/log": {
"name": "deploymentconfigs/log",
"namespaced": true,
"kind": "DeploymentLog"
},
"deploymentconfigs/scale": {
"name": "deploymentconfigs/scale",
"namespaced": true,
"kind": "Scale"
},
"generatedeploymentconfigs": {
"name": "generatedeploymentconfigs",
"namespaced": true,
"kind": "DeploymentConfig"
},
"groups": {
"name": "groups",
"namespaced": false,
"kind": "Group"
},
"hostsubnets": {
"name": "hostsubnets",
"namespaced": false,
"kind": "HostSubnet"
},
"identities": {
"name": "identities",
"namespaced": false,
"kind": "Identity"
},
"images": {
"name": "images",
"namespaced": false,
"kind": "Image"
},
"imagestreamimages": {
"name": "imagestreamimages",
"namespaced": true,
"kind": "ImageStreamImage"
},
"imagestreamimports": {
"name": "imagestreamimports",
"namespaced": true,
"kind": "ImageStreamImport"
},
"imagestreammappings": {
"name": "imagestreammappings",
"namespaced": true,
"kind": "ImageStreamMapping"
},
"imagestreams": {
"name": "imagestreams",
"namespaced": true,
"kind": "ImageStream"
},
"imagestreams/secrets": {
"name": "imagestreams/secrets",
"namespaced": true,
"kind": "SecretList"
},
"imagestreams/status": {
"name": "imagestreams/status",
"namespaced": true,
"kind": "ImageStream"
},
"imagestreamtags": {
"name": "imagestreamtags",
"namespaced": true,
"kind": "ImageStreamTag"
},
"localresourceaccessreviews": {
"name": "localresourceaccessreviews",
"namespaced": true,
"kind": "LocalResourceAccessReview"
},
"localsubjectaccessreviews": {
"name": "localsubjectaccessreviews",
"namespaced": true,
"kind": "LocalSubjectAccessReview"
},
"netnamespaces": {
"name": "netnamespaces",
"namespaced": false,
"kind": "NetNamespace"
},
"oauthaccesstokens": {
"name": "oauthaccesstokens",
"namespaced": false,
"kind": "OAuthAccessToken"
},
"oauthauthorizetokens": {
"name": "oauthauthorizetokens",
"namespaced": false,
"kind": "OAuthAuthorizeToken"
},
"oauthclientauthorizations": {
"name": "oauthclientauthorizations",
"namespaced": false,
"kind": "OAuthClientAuthorization"
},
"oauthclients": {
"name": "oauthclients",
"namespaced": false,
"kind": "OAuthClient"
},
"policies": {
"name": "policies",
"namespaced": true,
"kind": "Policy"
},
"policybindings": {
"name": "policybindings",
"namespaced": true,
"kind": "PolicyBinding"
},
"processedtemplates": {
"name": "processedtemplates",
"namespaced": true,
"kind": "Template"
},
"projectrequests": {
"name": "projectrequests",
"namespaced": false,
"kind": "ProjectRequest"
},
"projects": {
"name": "projects",
"namespaced": false,
"kind": "Project"
},
"resourceaccessreviews": {
"name": "resourceaccessreviews",
"namespaced": true,
"kind": "ResourceAccessReview"
},
"rolebindings": {
"name": "rolebindings",
"namespaced": true,
"kind": "RoleBinding"
},
"roles": {
"name": "roles",
"namespaced": true,
"kind": "Role"
},
"routes": {
"name": "routes",
"namespaced": true,
"kind": "Route"
},
"routes/status": {
"name": "routes/status",
"namespaced": true,
"kind": "Route"
},
"subjectaccessreviews": {
"name": "subjectaccessreviews",
"namespaced": true,
"kind": "SubjectAccessReview"
},
"templates": {
"name": "templates",
"namespaced": true,
"kind": "Template"
},
"useridentitymappings": {
"name": "useridentitymappings",
"namespaced": false,
"kind": "UserIdentityMapping"
},
"users": {
"name": "users",
"namespaced": false,
"kind": "User"
}
}
};
window.OPENSHIFT_CONFIG.apis.groups = {
"autoscaling": {
"name": "autoscaling",
"preferredVersion": "v1",
"versions": {
"v1": {
"version": "v1",
"groupVersion": "autoscaling/v1",
"resources": {
"horizontalpodautoscalers": {
"name": "horizontalpodautoscalers",
"namespaced": true,
"kind": "HorizontalPodAutoscaler"
},
"horizontalpodautoscalers/status": {
"name": "horizontalpodautoscalers/status",
"namespaced": true,
"kind": "HorizontalPodAutoscaler"
}
}
}
}
},
"batch": {
"name": "batch",
"preferredVersion": "v1",
"versions": {
"v1": {
"version": "v1",
"groupVersion": "batch/v1",
"resources": {
"jobs": {
"name": "jobs",
"namespaced": true,
"kind": "Job"
},
"jobs/status": {
"name": "jobs/status",
"namespaced": true,
"kind": "Job"
}
}
}
}
},
"extensions": {
"name": "extensions",
"preferredVersion": "v1beta1",
"versions": {
"v1beta1": {
"version": "v1beta1",
"groupVersion": "extensions/v1beta1",
"resources": {
"daemonsets": {
"name": "daemonsets",
"namespaced": true,
"kind": "DaemonSet"
},
"daemonsets/status": {
"name": "daemonsets/status",
"namespaced": true,
"kind": "DaemonSet"
},
"deployments": {
"name": "deployments",
"namespaced": true,
"kind": "Deployment"
},
"deployments/rollback": {
"name": "deployments/rollback",
"namespaced": true,
"kind": "DeploymentRollback"
},
"deployments/scale": {
"name": "deployments/scale",
"namespaced": true,
"kind": "Scale"
},
"deployments/status": {
"name": "deployments/status",
"namespaced": true,
"kind": "Deployment"
},
"horizontalpodautoscalers": {
"name": "horizontalpodautoscalers",
"namespaced": true,
"kind": "HorizontalPodAutoscaler"
},
"horizontalpodautoscalers/status": {
"name": "horizontalpodautoscalers/status",
"namespaced": true,
"kind": "HorizontalPodAutoscaler"
},
"ingresses": {
"name": "ingresses",
"namespaced": true,
"kind": "Ingress"
},
"ingresses/status": {
"name": "ingresses/status",
"namespaced": true,
"kind": "Ingress"
},
"jobs": {
"name": "jobs",
"namespaced": true,
"kind": "Job"
},
"jobs/status": {
"name": "jobs/status",
"namespaced": true,
"kind": "Job"
},
"replicasets": {
"name": "replicasets",
"namespaced": true,
"kind": "ReplicaSet"
},
"replicasets/scale": {
"name": "replicasets/scale",
"namespaced": true,
"kind": "Scale"
},
"replicasets/status": {
"name": "replicasets/status",
"namespaced": true,
"kind": "ReplicaSet"
},
"replicationcontrollers": {
"name": "replicationcontrollers",
"namespaced": true,
"kind": "ReplicationControllerDummy"
},
"replicationcontrollers/scale": {
"name": "replicationcontrollers/scale",
"namespaced": true,
"kind": "Scale"
}
}
}
}
}
};
\ No newline at end of file
describe("APIService", function(){
var APIService;
beforeEach(function(){
inject(function(_APIService_){
APIService = _APIService_;
});
});
describe("#toResourceGroupVersion", function(){
var tc = [
// string args
// simple
['pods', {r:'pods',g:'',v:'v1'}],
// normalization
['Pods', {r:'pods',g:'',v:'v1'}],
// normalization preserves subresources
['PODS/FOO', {r:'pods/FOO',g:'',v:'v1'}],
// structured, resource only
// simple
[{resource:'pods'}, {r:'pods',g:'',v:'v1'}],
// normalization
[{resource:'Pods'}, {r:'pods',g:'',v:'v1'}],
// normalization preserves subresources
[{resource:'PODS/FOO'}, {r:'pods/FOO',g:'',v:'v1'}],
// structured, with group
// groups default version if known
[{resource:'pods',group:''}, {r:'pods',g:'', v:'v1' }],
[{resource:'jobs',group:'extensions'}, {r:'jobs',g:'extensions',v:'v1beta1'}],
// unknown groups do not default version
[{resource:'foos',group:'unknown'}, {r:'foos',g:'unknown', v:undefined}],
// structured, with version
// groups default
[{resource:'pods',version:'v1'}, {r:'pods',g:'',v:'v1' }],
[{resource:'pods',version:'v1beta3'}, {r:'pods',g:'',v:'v1beta3'}],
// structured, fully specified
[{resource:'pods',group:'', version:'v1'}, {r:'pods',g:'', v:'v1' }],
[{resource:'pods',group:'', version:'v1beta3'}, {r:'pods',g:'', v:'v1beta3'}],
[{resource:'jobs',group:'extensions',version:'v1'}, {r:'jobs',g:'extensions',v:'v1' }],
[{resource:'jobs',group:'extensions',version:'v1beta1'}, {r:'jobs',g:'extensions',v:'v1beta1'}],
[{resource:'foos',group:'unknown', version:'v1'}, {r:'foos',g:'unknown', v:'v1' }],
[{resource:'foos',group:'unknown', version:'v1beta1'}, {r:'foos',g:'unknown', v:'v1beta1'}]
];
angular.forEach(tc, _.spread(function(input, expectedRGV) {
it('should result in ' + JSON.stringify(expectedRGV) + ' when called with ' + JSON.stringify(input), function() {
// Call once and compare the components
var actualRGV = APIService.toResourceGroupVersion(input);
expect(actualRGV.resource).toEqual(expectedRGV.r);
expect(actualRGV.group ).toEqual(expectedRGV.g);
expect(actualRGV.version ).toEqual(expectedRGV.v);
// Call again with the result and make sure it is returns the same thing
var actualRGV2 = APIService.toResourceGroupVersion(actualRGV);
expect(actualRGV).toEqual(actualRGV2);
});
}));
});
/*
describe("#parseGroupVersion", function(){
var tc = [
// invalid cases
[null, undefined],
["", undefined],
['foo/bar/baz', undefined],
// legacy
['v1', {group:'',version:'v1' }],
// groups
['foo/bar', {group:'foo',version:'bar'}],
['FOO/BAR', {group:'FOO',version:'BAR'}],
// group missing version, we see this on events
['apps', {group:'apps',version:''}],
];
angular.forEach(tc, _.spread(function(input, expectedGroupVersion) {
it('should result in ' + JSON.stringify(expectedGroupVersion) + ' when called with ' + JSON.stringify(input), function() {
expect(APIService.parseGroupVersion(input)).toEqual(expectedGroupVersion);
});
}));
});
describe("#objectToResourceGroupVersion", function(){
var tc = [
// invalid cases
[null, undefined],
["", undefined],
[{}, undefined],
[{kind:"Pod"}, undefined],
[{apiVersion:"v1"}, undefined],
// legacy
[{kind:"Pod", apiVersion:"v1"}, {g:'',v:'v1',r:'pods'}],
// extensions
[{kind:"Job",apiVersion:"extensions/v1beta1"}, {g:'extensions',v:'v1beta1',r:'jobs'}],
[{kind:"Foo",apiVersion:"unknown/v1beta6"}, {g:'unknown', v:'v1beta6',r:'foos'}],
];
angular.forEach(tc, _.spread(function(input, expectedRGV) {
it('should result in ' + JSON.stringify(expectedRGV) + ' when called with ' + JSON.stringify(input), function() {
// Call once and compare the components
var actualRGV = APIService.objectToResourceGroupVersion(input);
if (expectedRGV) {
expect(actualRGV.resource).toEqual(expectedRGV.r);
expect(actualRGV.group ).toEqual(expectedRGV.g);
expect(actualRGV.version ).toEqual(expectedRGV.v);
} else {
expect(actualRGV).toEqual(expectedRGV);
}
});
}));
});
describe("#kindToResource", function(){
var tc = [
// invalid cases
[null, ""],
["", ""],
// pluralization
["foo", "foos"],
// pluralization with s
["foos", "fooses"],
// pluralization with y
["Policy", "policies"],
// special cases
["Endpoints", "endpoints"],
["SecurityContextConstraints", "securitycontextconstraints"],
];
angular.forEach(tc, _.spread(function(kind, resource) {
it('should result in ' + JSON.stringify(resource) + ' when called with ' + JSON.stringify(kind), function() {
expect(APIService.kindToResource(kind)).toEqual(resource);
});
}));
});
describe("#deriveTargetResource", function(){
var tc = [
// invalid cases
[null,null, undefined],
["","", undefined],
[{},{}, undefined],
// simple resource, matching object overrides group/version
['pods', {kind:"Pod",apiVersion:"v1"}, {r:'pods',g:'', v:'v1' }],
['pods', {kind:"Pod",apiVersion:"extensions"}, {r:'pods',g:'extensions',v:'' }],
['jobs', {kind:"Job",apiVersion:"extensions/v1beta1"}, {r:'jobs',g:'extensions',v:'v1beta1'}],
['jobs', {kind:"Job",apiVersion:"extensions/v1beta2"}, {r:'jobs',g:'extensions',v:'v1beta2'}],
// simple resource, non-matching object leaves group/version alone
['pods', {kind:"Foo",apiVersion:"v1"}, {r:'pods',g:'',v:'v1'}],
['pods', {kind:"Foo",apiVersion:"v2"}, {r:'pods',g:'',v:'v1'}],
['jobs', {kind:"Foo",apiVersion:"extensions/v1beta1"}, {r:'jobs',g:'',v:'v1'}],
['jobs', {kind:"Foo",apiVersion:"extensions/v1beta2"}, {r:'jobs',g:'',v:'v1'}],
// actual use:
['deploymentconfigs/scale', {kind:"Scale",apiVersion:"extensions/v1beta1"}, {r:'deploymentconfigs/scale',g:'',v:'v1'}],
// complex resource, matching object kind and group overrides version
[{resource:'pods',group:'' }, {kind:"Pod",apiVersion:"v1"}, {r:'pods',g:'', v:'v1' }],
[{resource:'pods',group:'', version:'v2'}, {kind:"Pod",apiVersion:"v1"}, {r:'pods',g:'', v:'v1' }],
[{resource:'jobs',group:'extensions' }, {kind:"Job",apiVersion:"extensions/v1beta3"}, {r:'jobs',g:'extensions',v:'v1beta3'}],
[{resource:'jobs',group:'extensions', version:'v2'}, {kind:"Job",apiVersion:"extensions/v1beta3"}, {r:'jobs',g:'extensions',v:'v1beta3'}],
// complex resource, non-matching object group leaves group/version alone
[{resource:'pods', }, {kind:"Pod",apiVersion:"othergroup/v3"}, {r:'pods',g:'', v:'v1' }],
[{resource:'pods',group:'' }, {kind:"Pod",apiVersion:"othergroup/v3"}, {r:'pods',g:'', v:'v1' }],
[{resource:'pods',group:'', version:'v2'}, {kind:"Pod",apiVersion:"othergroup/v3"}, {r:'pods',g:'', v:'v2' }],
[{resource:'jobs',group:'extensions' }, {kind:"Job",apiVersion:"othergroup/v1beta3"}, {r:'jobs',g:'extensions',v:'v1beta1'}],
[{resource:'jobs',group:'extensions', version:'v2'}, {kind:"Job",apiVersion:"othergroup/v1beta3"}, {r:'jobs',g:'extensions',v:'v2' }],
// complex resource, non-matching object kind leaves group/version alone
[{resource:'pods',group:'' }, {kind:"Foo",apiVersion:"v3"}, {r:'pods',g:'', v:'v1' }],
[{resource:'pods',group:'', version:'v2'}, {kind:"Foo",apiVersion:"v3"}, {r:'pods',g:'', v:'v2' }],
// actual use:
[{resource:'deploymentconfigs/scale'}, {kind:"Scale",apiVersion:"extensions/v1beta1"}, {r:'deploymentconfigs/scale',g:'',v:'v1'}],
];
angular.forEach(tc, _.spread(function(resource, apiObject, expectedRGV) {
it('should result in ' + JSON.stringify(expectedRGV) + ' when called with ' + JSON.stringify(resource)+","+JSON.stringify(apiObject), function() {
// Call once and compare the components
var actualRGV = APIService.deriveTargetResource(resource, apiObject);
if (expectedRGV) {
expect(actualRGV.resource).toEqual(expectedRGV.r);
expect(actualRGV.group ).toEqual(expectedRGV.g);
expect(actualRGV.version ).toEqual(expectedRGV.v);
} else {
expect(actualRGV).toEqual(expectedRGV);
}
});
}));
});
describe("#primaryResource", function(){
var tc = [
// invalid cases
[null, ""],
["", ""],
// no subresources
["foo", "foo"],
["FOO", "foo"],
// subresource cases
["foo/bar", "foo"],
["FOO/bar/baz", "foo"]
];
angular.forEach(tc, _.spread(function(resource, primaryResource) {
it('should result in ' + JSON.stringify(primaryResource) + ' when called with ' + JSON.stringify(resource), function() {
expect(APIService.toResourceGroupVersion(resource).primaryResource()).toEqual(primaryResource);
});
}));
});
describe("#subresources", function(){
var tc = [
// invalid cases
[null, []],
["", []],
// no subresources
["foo", []],
["FOO", []],
// subresource cases
["foo/bar", ["bar"]],
["FOO/bar/baz", ["bar","baz"]],
["FOO/Bar/Baz", ["Bar","Baz"]]
];
angular.forEach(tc, _.spread(function(resource, subresources) {
it('should result in ' + JSON.stringify(subresources) + ' when called with ' + JSON.stringify(resource), function() {
expect(APIService.toResourceGroupVersion(resource).subresources()).toEqual(subresources);
});
}));
});
*/
});
"use strict";
describe("DataService", function(){
var DataService;
beforeEach(function(){
inject(function(_DataService_){
DataService = _DataService_;
});
});
describe("#url", function(){
var tc = [
// Empty tests
[null, null],
[{}, null],
// Unknown resources
[{resource:''}, null],
[{resource:'bogus'}, null],
// Kind is not allowed
[{resource:'Pod'}, null],
[{resource:'User'}, null],
// resource normalization
[{resource:'users'}, "http://localhost:8443/oapi/v1/users"],
[{resource:'Users'}, "http://localhost:8443/oapi/v1/users"],
[{resource:'oauthaccesstokens'}, "http://localhost:8443/oapi/v1/oauthaccesstokens"],
[{resource:'OAuthAccessTokens'}, "http://localhost:8443/oapi/v1/oauthaccesstokens"],
[{resource:'pods'}, "http://localhost:8443/api/v1/pods"],
[{resource:'Pods'}, "http://localhost:8443/api/v1/pods"],
// OpenShift resource
[{resource:'builds' }, "http://localhost:8443/oapi/v1/builds"],
[{resource:'builds', namespace:"foo" }, "http://localhost:8443/oapi/v1/namespaces/foo/builds"],
[{resource:'builds', name:"bar"}, "http://localhost:8443/oapi/v1/builds/bar"],
[{resource:'builds', namespace:"foo", name:"bar"}, "http://localhost:8443/oapi/v1/namespaces/foo/builds/bar"],
// k8s resource
[{resource:'replicationcontrollers' }, "http://localhost:8443/api/v1/replicationcontrollers"],
[{resource:'replicationcontrollers', namespace:"foo" }, "http://localhost:8443/api/v1/namespaces/foo/replicationcontrollers"],
[{resource:'replicationcontrollers', name:"bar"}, "http://localhost:8443/api/v1/replicationcontrollers/bar"],
[{resource:'replicationcontrollers', namespace:"foo", name:"bar"}, "http://localhost:8443/api/v1/namespaces/foo/replicationcontrollers/bar"],
// Subresources and webhooks
[{resource:'pods/proxy', name:"mypod:1123", namespace:"foo"}, "http://localhost:8443/api/v1/namespaces/foo/pods/mypod%3A1123/proxy"],
[{resource:'builds/clone', name:"mybuild", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/builds/mybuild/clone"],
[{resource:'buildconfigs/instantiate', name:"mycfg", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/buildconfigs/mycfg/instantiate"],
[{resource:'buildconfigs/webhooks/123/github', name:"mycfg", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/buildconfigs/mycfg/webhooks/123/github"],
[{resource:'buildconfigs/webhooks/123?234/github', name:"mycfg", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/buildconfigs/mycfg/webhooks/123%3F234/github"],
// Subresources aren't lowercased
[{resource:'buildconfigs/webhooks/Aa1/github', name:"mycfg", namespace:"foo"}, "http://localhost:8443/oapi/v1/namespaces/foo/buildconfigs/mycfg/webhooks/Aa1/github"],
// Plain websocket
[{resource:'pods', namespace:"foo", isWebsocket:true }, "ws://localhost:8443/api/v1/namespaces/foo/pods"],
// Watch resource
[{resource:'pods', namespace:"foo", isWebsocket:true, watch: true }, "ws://localhost:8443/api/v1/namespaces/foo/pods?watch=true"],
[{resource:'pods', namespace:"foo", isWebsocket:true, watch: true, resourceVersion:"5" }, "ws://localhost:8443/api/v1/namespaces/foo/pods?watch=true&resourceVersion=5"],
// Follow log
// subresource is ignored without a resource name
[{resource:'pods/log', namespace:"foo", isWebsocket:true, follow: true }, "ws://localhost:8443/api/v1/namespaces/foo/pods?follow=true"],
[{resource:'builds/log', namespace:"foo", isWebsocket:true, follow: true }, "ws://localhost:8443/oapi/v1/namespaces/foo/builds?follow=true"],
// subresource is honored with a resource name
[{resource:'pods/log', name:"p", namespace:"foo", isWebsocket:true, follow: true }, "ws://localhost:8443/api/v1/namespaces/foo/pods/p/log?follow=true"],
[{resource:'builds/log', name:"b", namespace:"foo", isWebsocket:true, follow: true }, "ws://localhost:8443/oapi/v1/namespaces/foo/builds/b/log?follow=true"],
// Namespaced subresource with params
[{resource:'pods/proxy', name:"mypod", namespace:"myns", myparam1:"myvalue"}, "http://localhost:8443/api/v1/namespaces/myns/pods/mypod/proxy?myparam1=myvalue"],
// Different API versions
[{resource:'builds',version:'v1beta3'}, null],
[{resource:'builds',version:'v1' }, "http://localhost:8443/oapi/v1/builds"],
[{resource:'builds',version:'unknown'}, null],
[{resource:'pods', version:'v1beta3'}, null],
[{resource:'pods', version:'v1' }, "http://localhost:8443/api/v1/pods"],
[{resource:'pods', version:'unknown'}, null],
// Different API groups
[{resource:'jobs', group: 'extensions', version:'v1beta1'}, "http://localhost:8443/apis/extensions/v1beta1/jobs"]
];
angular.forEach(tc, function(item) {
it('should generate a correct URL for ' + JSON.stringify(item[0]), function() {
expect(DataService.url(item[0])).toEqual(item[1]);
});
});
});
});
"use strict";
//load the module
beforeEach(module('openshiftCommon'));
beforeEach(module(function ($provide) {
$provide.provider("HawtioNavBuilder", function() {
function Mocked() {}
this.create = function() {return this;};
this.id = function() {return this;};
this.title = function() {return this;};
this.template = function() {return this;};
this.isSelected = function() {return this;};
this.href = function() {return this;};
this.page = function() {return this;};
this.subPath = function() {return this;};
this.build = function() {return this;};
this.join = function() {return "";};
this.$get = function() {return new Mocked();};
});
$provide.factory("HawtioNav", function(){
return {add: function() {}};
});
$provide.factory("HawtioExtension", function() {
return {
add: function() {}
};
});
// Make sure a base location exists in the generated test html
if (!$('head base').length) {
$('head').append($('<base href="/">'));
}
angular.module('openshiftCommon').config(function(AuthServiceProvider) {
AuthServiceProvider.UserStore('MemoryUserStore');
})
.constant("API_CFG", _.get(window.OPENSHIFT_CONFIG, "api", {}))
.constant("APIS_CFG", _.get(window.OPENSHIFT_CONFIG, "apis", {}))
.constant("AUTH_CFG", _.get(window.OPENSHIFT_CONFIG, "auth", {}))
.constant("LOGGING_URL", _.get(window.OPENSHIFT_CONFIG, "loggingURL"))
.constant("METRICS_URL", _.get(window.OPENSHIFT_CONFIG, "metricsURL"))
.constant("LIMIT_REQUEST_OVERRIDES", _.get(window.OPENSHIFT_CONFIG, "limitRequestOverrides"))
.config(function($httpProvider, AuthServiceProvider, AUTH_CFG, API_CFG) {
AuthServiceProvider.LoginService('RedirectLoginService');
AuthServiceProvider.LogoutService('DeleteTokenLogoutService');
AuthServiceProvider.UserStore('LocalStorageUserStore');
});
hawtioPluginLoader.addModule('openshiftCommon');
}));
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment