k2hr3-api 1.0.42 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/k2hr3-init.sh.templ +2 -2
- package/dist/.gitkeep +0 -0
- package/dist/src/app.js +262 -0
- package/{bin → dist/src/bin}/run.sh +1 -1
- package/dist/src/bin/watcher.js +113 -0
- package/dist/src/bin/www.js +217 -0
- package/dist/src/lib/basicipcheck.js +392 -0
- package/dist/src/lib/cacerts.js +106 -0
- package/dist/src/lib/dbglogging.js +190 -0
- package/dist/src/lib/dummyuserapi.js +719 -0
- package/dist/src/lib/ipwatch.js +354 -0
- package/dist/src/lib/k2hr3acrutil.js +532 -0
- package/dist/src/lib/k2hr3apiutil.js +1444 -0
- package/dist/src/lib/k2hr3cliutil.js +183 -0
- package/dist/src/lib/k2hr3config.js +832 -0
- package/dist/src/lib/k2hr3cryptutil.js +258 -0
- package/dist/src/lib/k2hr3dkc.js +12121 -0
- package/dist/src/lib/k2hr3extdata.js +198 -0
- package/dist/src/lib/k2hr3keys.js +207 -0
- package/dist/src/lib/k2hr3resutil.js +111 -0
- package/dist/src/lib/k2hr3template.js +6546 -0
- package/dist/src/lib/k2hr3tokens.js +2643 -0
- package/dist/src/lib/k2hr3userdata.js +296 -0
- package/dist/src/lib/k8soidc.js +1000 -0
- package/dist/src/lib/openstackapiv2.js +695 -0
- package/dist/src/lib/openstackapiv3.js +932 -0
- package/dist/src/lib/openstackep.js +667 -0
- package/{tests/auto_common.js → dist/src/lib/types.js} +4 -38
- package/dist/src/routes/acr.js +704 -0
- package/dist/src/routes/debugVerify.js +294 -0
- package/dist/src/routes/extdata.js +219 -0
- package/dist/src/routes/list.js +264 -0
- package/dist/src/routes/policy.js +840 -0
- package/dist/src/routes/resource.js +1489 -0
- package/dist/src/routes/role.js +2627 -0
- package/dist/src/routes/service.js +908 -0
- package/dist/src/routes/tenant.js +1141 -0
- package/dist/src/routes/userTokens.js +482 -0
- package/dist/src/routes/userdata.js +212 -0
- package/dist/src/routes/version.js +103 -0
- package/package.json +152 -121
- package/ChangeLog +0 -378
- package/app.js +0 -292
- package/bin/watcher +0 -122
- package/bin/www +0 -180
- package/eslint.config.mjs +0 -68
- package/lib/basicipcheck.js +0 -376
- package/lib/cacerts.js +0 -71
- package/lib/dbglogging.js +0 -151
- package/lib/dummyuserapi.js +0 -766
- package/lib/ipwatch.js +0 -379
- package/lib/k2hr3acrutil.js +0 -516
- package/lib/k2hr3apiutil.js +0 -1494
- package/lib/k2hr3cliutil.js +0 -191
- package/lib/k2hr3config.js +0 -826
- package/lib/k2hr3cryptutil.js +0 -254
- package/lib/k2hr3dkc.js +0 -12632
- package/lib/k2hr3extdata.js +0 -198
- package/lib/k2hr3keys.js +0 -234
- package/lib/k2hr3resutil.js +0 -100
- package/lib/k2hr3template.js +0 -6925
- package/lib/k2hr3tokens.js +0 -2799
- package/lib/k2hr3userdata.js +0 -312
- package/lib/k8soidc.js +0 -1012
- package/lib/openstackapiv2.js +0 -764
- package/lib/openstackapiv3.js +0 -1032
- package/lib/openstackep.js +0 -553
- package/routes/acr.js +0 -738
- package/routes/debugVerify.js +0 -263
- package/routes/extdata.js +0 -232
- package/routes/list.js +0 -270
- package/routes/policy.js +0 -869
- package/routes/resource.js +0 -1441
- package/routes/role.js +0 -2664
- package/routes/service.js +0 -894
- package/routes/tenant.js +0 -1095
- package/routes/userTokens.js +0 -511
- package/routes/userdata.js +0 -218
- package/routes/version.js +0 -108
- package/templ/Dockerfile.templ +0 -71
- package/tests/auto_acr.js +0 -1101
- package/tests/auto_acr_spec.js +0 -79
- package/tests/auto_all_spec.js +0 -142
- package/tests/auto_control_subprocess.sh +0 -243
- package/tests/auto_extdata.js +0 -220
- package/tests/auto_extdata_spec.js +0 -79
- package/tests/auto_init_config_json.sh +0 -275
- package/tests/auto_k2hdkc_server.ini +0 -109
- package/tests/auto_k2hdkc_slave.ini +0 -83
- package/tests/auto_list.js +0 -439
- package/tests/auto_list_spec.js +0 -79
- package/tests/auto_policy.js +0 -1579
- package/tests/auto_policy_spec.js +0 -79
- package/tests/auto_resource.js +0 -10956
- package/tests/auto_resource_spec.js +0 -79
- package/tests/auto_role.js +0 -6150
- package/tests/auto_role_spec.js +0 -79
- package/tests/auto_service.js +0 -770
- package/tests/auto_service_spec.js +0 -79
- package/tests/auto_subprocesses.js +0 -114
- package/tests/auto_template.sh +0 -126
- package/tests/auto_tenant.js +0 -1100
- package/tests/auto_tenant_spec.js +0 -79
- package/tests/auto_token_util.js +0 -219
- package/tests/auto_userdata.js +0 -292
- package/tests/auto_userdata_spec.js +0 -79
- package/tests/auto_usertokens.js +0 -565
- package/tests/auto_usertokens_spec.js +0 -79
- package/tests/auto_version.js +0 -127
- package/tests/auto_version_spec.js +0 -79
- package/tests/auto_watcher.js +0 -157
- package/tests/auto_watcher_spec.js +0 -79
- package/tests/k2hdkc_test.data +0 -986
- package/tests/k2hdkc_test_load.sh +0 -255
- package/tests/k2hr3template_test.js +0 -187
- package/tests/k2hr3template_test.sh +0 -339
- package/tests/k2hr3template_test_async.js +0 -216
- package/tests/k2hr3template_test_template.result +0 -7117
- package/tests/k2hr3template_test_template.txt +0 -3608
- package/tests/k2hr3template_test_vars.js +0 -194
- package/tests/manual_acr_delete.js +0 -143
- package/tests/manual_acr_get.js +0 -297
- package/tests/manual_acr_postput.js +0 -215
- package/tests/manual_allusertenant_get.js +0 -113
- package/tests/manual_extdata_get.js +0 -191
- package/tests/manual_k2hr3keys_get.js +0 -84
- package/tests/manual_list_gethead.js +0 -230
- package/tests/manual_policy_delete.js +0 -132
- package/tests/manual_policy_gethead.js +0 -275
- package/tests/manual_policy_postput.js +0 -297
- package/tests/manual_resource_delete.js +0 -433
- package/tests/manual_resource_gethead.js +0 -423
- package/tests/manual_resource_postput.js +0 -487
- package/tests/manual_role_delete.js +0 -404
- package/tests/manual_role_gethead.js +0 -547
- package/tests/manual_role_postput.js +0 -544
- package/tests/manual_service_delete.js +0 -153
- package/tests/manual_service_gethead.js +0 -178
- package/tests/manual_service_postput.js +0 -348
- package/tests/manual_tenant_delete.js +0 -186
- package/tests/manual_tenant_gethead.js +0 -268
- package/tests/manual_tenant_postput.js +0 -293
- package/tests/manual_test.sh +0 -352
- package/tests/manual_userdata_get.js +0 -173
- package/tests/manual_usertoken_gethead.js +0 -136
- package/tests/manual_usertoken_postput.js +0 -310
- package/tests/manual_version_get.js +0 -127
- package/tests/run_local_test_k2hdkc.sh +0 -174
- package/tests/test.sh +0 -333
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* K2HR3 REST API
|
|
4
|
+
*
|
|
5
|
+
* Copyright 2017 Yahoo Japan Corporation.
|
|
6
|
+
*
|
|
7
|
+
* K2HR3 is K2hdkc based Resource and Roles and policy Rules, gathers
|
|
8
|
+
* common management information for the cloud.
|
|
9
|
+
* K2HR3 can dynamically manage information as "who", "what", "operate".
|
|
10
|
+
* These are stored as roles, resources, policies in K2hdkc, and the
|
|
11
|
+
* client system can dynamically read and modify these information.
|
|
12
|
+
*
|
|
13
|
+
* For the full copyright and license information, please view
|
|
14
|
+
* the license file that was distributed with this source code.
|
|
15
|
+
*
|
|
16
|
+
* AUTHOR: Takeshi Nakatani
|
|
17
|
+
* CREATE: Wed Jun 8 2017
|
|
18
|
+
* REVISION:
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
24
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
25
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(o, k2, desc);
|
|
28
|
+
}) : (function(o, m, k, k2) {
|
|
29
|
+
if (k2 === undefined) k2 = k;
|
|
30
|
+
o[k2] = m[k];
|
|
31
|
+
}));
|
|
32
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
33
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
34
|
+
}) : function(o, v) {
|
|
35
|
+
o["default"] = v;
|
|
36
|
+
});
|
|
37
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
38
|
+
var ownKeys = function(o) {
|
|
39
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
40
|
+
var ar = [];
|
|
41
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
42
|
+
return ar;
|
|
43
|
+
};
|
|
44
|
+
return ownKeys(o);
|
|
45
|
+
};
|
|
46
|
+
return function (mod) {
|
|
47
|
+
if (mod && mod.__esModule) return mod;
|
|
48
|
+
var result = {};
|
|
49
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
50
|
+
__setModuleDefault(result, mod);
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
})();
|
|
54
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
55
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
56
|
+
};
|
|
57
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
+
exports.openstackep = void 0;
|
|
59
|
+
const https = __importStar(require("https"));
|
|
60
|
+
const http = __importStar(require("http"));
|
|
61
|
+
const k2hr3apiutil_1 = __importDefault(require("./k2hr3apiutil"));
|
|
62
|
+
const dbglogging_1 = __importDefault(require("./dbglogging"));
|
|
63
|
+
const k2hr3keys_1 = require("./k2hr3keys");
|
|
64
|
+
const cacerts_1 = require("./cacerts");
|
|
65
|
+
;
|
|
66
|
+
//
|
|
67
|
+
// Type checking
|
|
68
|
+
//
|
|
69
|
+
const rawIsValTypeKeystoneEndpoint = (val) => {
|
|
70
|
+
if (!k2hr3apiutil_1.default.isPlainObject(val)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const _obj = val;
|
|
74
|
+
const _isUrl = (key) => k2hr3apiutil_1.default.isString(_obj[key]);
|
|
75
|
+
const _isChecked = (key) => k2hr3apiutil_1.default.isBoolean(_obj[key]);
|
|
76
|
+
const _isStatus = (key) => k2hr3apiutil_1.default.isSafeNumber(_obj[key]);
|
|
77
|
+
return (_isUrl('url') &&
|
|
78
|
+
_isChecked('checked') &&
|
|
79
|
+
_isStatus('status'));
|
|
80
|
+
};
|
|
81
|
+
const rawIsValTypeKeystoneEndpointMap = (val) => {
|
|
82
|
+
if (!k2hr3apiutil_1.default.isPlainObject(val)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
for (const [, value] of Object.entries(val)) {
|
|
86
|
+
if (!k2hr3apiutil_1.default.isSafeString(value) && !rawIsValTypeKeystoneEndpoint(value)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
};
|
|
92
|
+
const rawIsValTypeGetKeystoneEndpointArgs = (val) => {
|
|
93
|
+
if (!k2hr3apiutil_1.default.isPlainObject(val)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const _obj = val;
|
|
97
|
+
const _isIsV3 = (key) => k2hr3apiutil_1.default.isBoolean(_obj[key]);
|
|
98
|
+
const _isIsTest = (key) => k2hr3apiutil_1.default.isBoolean(_obj[key]);
|
|
99
|
+
const _isTimeout = (key) => k2hr3apiutil_1.default.isSafeNumber(_obj[key]);
|
|
100
|
+
const _isCallback = (key) => {
|
|
101
|
+
const func = _obj[key];
|
|
102
|
+
if (!k2hr3apiutil_1.default.isPlainObject(func) || !k2hr3apiutil_1.default.isFunction(func)) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(func.length) || 3 !== func.length) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
};
|
|
110
|
+
return (_isIsV3('is_v3') &&
|
|
111
|
+
_isIsTest('is_test') &&
|
|
112
|
+
_isTimeout('timeout') &&
|
|
113
|
+
_isCallback('callback'));
|
|
114
|
+
};
|
|
115
|
+
const rawIsValTypeUrlKeystoneEndpoint = (val) => {
|
|
116
|
+
if (!k2hr3apiutil_1.default.isPlainObject(val)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
// check UrlWithStringQuery
|
|
120
|
+
if ((k2hr3apiutil_1.default.isSafeEntity(val.query) && !k2hr3apiutil_1.default.isString(val.query)) ||
|
|
121
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.href) && !k2hr3apiutil_1.default.isString(val.href)) ||
|
|
122
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.protocol) && !k2hr3apiutil_1.default.isString(val.protocol)) ||
|
|
123
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.auth) && !k2hr3apiutil_1.default.isString(val.auth)) ||
|
|
124
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.host) && !k2hr3apiutil_1.default.isString(val.host)) ||
|
|
125
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.hostname) && !k2hr3apiutil_1.default.isString(val.hostname)) ||
|
|
126
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.port) && !k2hr3apiutil_1.default.isString(val.port)) ||
|
|
127
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.pathname) && !k2hr3apiutil_1.default.isString(val.pathname)) ||
|
|
128
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.search) && !k2hr3apiutil_1.default.isString(val.search)) ||
|
|
129
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.path) && !k2hr3apiutil_1.default.isString(val.path)) ||
|
|
130
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.hash) && !k2hr3apiutil_1.default.isString(val.hash)) ||
|
|
131
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.query) && !k2hr3apiutil_1.default.isSafeEntity(val.query)) ||
|
|
132
|
+
(k2hr3apiutil_1.default.isSafeEntity(val.slashes) && !k2hr3apiutil_1.default.isBoolean(val.slashes))) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
// valTypeUrlKeystoneEndpoint
|
|
136
|
+
if (k2hr3apiutil_1.default.isSafeEntity(val.region) && !k2hr3apiutil_1.default.isString(val.region)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
};
|
|
141
|
+
const rawIsDynamicOpenstackEpModule = (mod) => {
|
|
142
|
+
if (!k2hr3apiutil_1.default.isPlainObject(mod)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (!('getDynamicKeystoneEndpoints' in mod)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (!k2hr3apiutil_1.default.isPlainObject(mod.getDynamicKeystoneEndpoints) || !k2hr3apiutil_1.default.isFunction(mod.getDynamicKeystoneEndpoints)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(mod.getDynamicKeystoneEndpoints.length) || 2 !== mod.getDynamicKeystoneEndpoints.length) { // getDynamicKeystoneEndpoints must have 2 arguments.
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
};
|
|
156
|
+
//---------------------------------------------------------
|
|
157
|
+
// Callback function for testing one keystone endpoint
|
|
158
|
+
//---------------------------------------------------------
|
|
159
|
+
// Input parameter:
|
|
160
|
+
// err : if error is occurred, error object is set(if no error, this is null).
|
|
161
|
+
// callback : specify the callback to be called when all epallmap is checked.
|
|
162
|
+
// epallmap : all of keystone endpoint
|
|
163
|
+
// region : checked region
|
|
164
|
+
// status_code : check result
|
|
165
|
+
//
|
|
166
|
+
// Result: callback(error, epallmap)
|
|
167
|
+
// error : if error is occurred, error object is set(if no error, this is null).
|
|
168
|
+
// epallmap : all endpoint mapping
|
|
169
|
+
//
|
|
170
|
+
// [NOTE]
|
|
171
|
+
// The callback function is only called when all checks of
|
|
172
|
+
// epallmap are complete.
|
|
173
|
+
// This assumes that the caller will call this function on all
|
|
174
|
+
// epallmaps.
|
|
175
|
+
//
|
|
176
|
+
const rawTestKeystoneEpCallback = (err, callback, epallmap, region, status_code) => {
|
|
177
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(callback) || !k2hr3apiutil_1.default.isSafeString(region) || !k2hr3apiutil_1.default.isPlainObject(epallmap)) {
|
|
178
|
+
const error = new Error('some parameters are wrong : epallmap=' + JSON.stringify(epallmap) + ', region=' + JSON.stringify(region));
|
|
179
|
+
dbglogging_1.default.elog(error.message);
|
|
180
|
+
if (k2hr3apiutil_1.default.isSafeEntity(callback)) {
|
|
181
|
+
callback(error);
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const tmpEpRegin = epallmap[region];
|
|
186
|
+
if (!rawIsValTypeKeystoneEndpoint(tmpEpRegin)) {
|
|
187
|
+
const error = new Error('some parameters are wrong : epallmap=' + JSON.stringify(epallmap) + ', region=' + JSON.stringify(region));
|
|
188
|
+
dbglogging_1.default.elog(error.message);
|
|
189
|
+
if (k2hr3apiutil_1.default.isSafeEntity(callback)) {
|
|
190
|
+
callback(error);
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (!k2hr3apiutil_1.default.isSafeNumber(status_code)) {
|
|
195
|
+
dbglogging_1.default.elog('parameter is wrong : status_code=' + JSON.stringify(status_code) + ', but continue with status_code(500).');
|
|
196
|
+
status_code = 500; // status code = internal error
|
|
197
|
+
}
|
|
198
|
+
if (null !== err) {
|
|
199
|
+
dbglogging_1.default.elog('failed test about keystone endpoint(' + tmpEpRegin.url + ') for region(' + region + ') by ' + err.message + ', but continue with status_code(500).');
|
|
200
|
+
status_code = 500; // status code = internal error
|
|
201
|
+
}
|
|
202
|
+
// set checked flag and status result
|
|
203
|
+
//
|
|
204
|
+
// [NOTE][TODO]
|
|
205
|
+
// Under control by this flag, exclusion control is not perfect.
|
|
206
|
+
// In other words, the callback function may be called multiple times.
|
|
207
|
+
// (However, the callback function will be called at least once.)
|
|
208
|
+
// Currently, exclusion control here is not performed, and the caller
|
|
209
|
+
// recognizes multiple calls. Here is the code to fix.
|
|
210
|
+
//
|
|
211
|
+
epallmap[region].checked = true;
|
|
212
|
+
epallmap[region].status = status_code;
|
|
213
|
+
// check for finish
|
|
214
|
+
let is_finish = true;
|
|
215
|
+
for (const test_region in epallmap) {
|
|
216
|
+
const tmpOneRegion = epallmap[test_region];
|
|
217
|
+
if (!rawIsValTypeKeystoneEndpoint(tmpOneRegion)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (!tmpOneRegion.checked) {
|
|
221
|
+
is_finish = false;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (is_finish) {
|
|
226
|
+
// checked all of endpoints, then call original callback.
|
|
227
|
+
//
|
|
228
|
+
// [NOTE]
|
|
229
|
+
// Perhaps this function passes here multiple times.
|
|
230
|
+
//
|
|
231
|
+
callback(null);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
//---------------------------------------------------------
|
|
235
|
+
// Test one keystone endpoint
|
|
236
|
+
//---------------------------------------------------------
|
|
237
|
+
// Input parameter
|
|
238
|
+
// epallmap : all of keystone endpoint
|
|
239
|
+
// region : target one region for checking
|
|
240
|
+
// endpoint : target one endpoint for checking
|
|
241
|
+
// is_v3 : keystone v3 or v2
|
|
242
|
+
// timeout : timeout for checking one endpoint
|
|
243
|
+
// lastest_callback : callback function which is called end of last checking endpoint.
|
|
244
|
+
//
|
|
245
|
+
// Result: lastest_callback(error, orgcb, epallmap, region, status_code, orgcb)
|
|
246
|
+
// error : error object
|
|
247
|
+
// orgcb : original callback
|
|
248
|
+
// epallmap : all endpoint mapping
|
|
249
|
+
// region : region
|
|
250
|
+
// status_code : status code for request(timeout = 504)
|
|
251
|
+
//
|
|
252
|
+
// [NOTE]
|
|
253
|
+
// The lastest_callback callback function is only called when
|
|
254
|
+
// all checks of epallmap are complete.
|
|
255
|
+
// This assumes that the caller will call this function on all
|
|
256
|
+
// epallmaps.
|
|
257
|
+
//
|
|
258
|
+
const rawTestKeystoneEndpoint = (epallmap, region, endpoint, is_v3, timeout, lastest_callback) => {
|
|
259
|
+
if (!k2hr3apiutil_1.default.isSafeEntity(epallmap) || !k2hr3apiutil_1.default.isSafeEntity(lastest_callback) || !k2hr3apiutil_1.default.isSafeString(region) || !k2hr3apiutil_1.default.isSafeString(endpoint)) {
|
|
260
|
+
const error = new Error('some parameters are wrong : epallmap=' + JSON.stringify(epallmap) + ', region=' + JSON.stringify(region) + ', endpoint=' + JSON.stringify(endpoint));
|
|
261
|
+
dbglogging_1.default.elog(error.message);
|
|
262
|
+
rawTestKeystoneEpCallback(error, lastest_callback, epallmap, region, 500); // return result code = 500
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (!k2hr3apiutil_1.default.isSafeNumber(timeout)) {
|
|
266
|
+
const error = new Error('parameter is wrong : timeout=' + JSON.stringify(timeout));
|
|
267
|
+
dbglogging_1.default.elog(error.message);
|
|
268
|
+
rawTestKeystoneEpCallback(error, lastest_callback, epallmap, region, 500); // return result code = 500
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (!k2hr3apiutil_1.default.isBoolean(is_v3)) {
|
|
272
|
+
is_v3 = true;
|
|
273
|
+
}
|
|
274
|
+
const _epallmap = epallmap;
|
|
275
|
+
const _region = region;
|
|
276
|
+
const _endpoint = endpoint;
|
|
277
|
+
const _is_v3 = is_v3;
|
|
278
|
+
const _timeout = timeout;
|
|
279
|
+
const _lastest_cb = lastest_callback;
|
|
280
|
+
// Make request body data
|
|
281
|
+
// This body failed authorization.(wrong user/passwd)
|
|
282
|
+
//
|
|
283
|
+
let strbody;
|
|
284
|
+
if (!_is_v3) {
|
|
285
|
+
const body = {
|
|
286
|
+
'auth': {
|
|
287
|
+
'tenantName': '', // unscoped token for test
|
|
288
|
+
'passwordCredentials': {
|
|
289
|
+
'username': '', // user name is empty for testing
|
|
290
|
+
'password': '' // unauthorized passwd
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
strbody = JSON.stringify(body);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
const body = {
|
|
298
|
+
'auth': {
|
|
299
|
+
'identity': {
|
|
300
|
+
'password': {
|
|
301
|
+
'user': {
|
|
302
|
+
'domain': {
|
|
303
|
+
'id': 'default'
|
|
304
|
+
},
|
|
305
|
+
'name': '', // user name is empty for testing
|
|
306
|
+
'password': '' // unauthorized passwd
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
'methods': ['password']
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
strbody = JSON.stringify(body);
|
|
314
|
+
}
|
|
315
|
+
const ep = k2hr3apiutil_1.default.urlParse(_endpoint);
|
|
316
|
+
const is_sec = k2hr3apiutil_1.default.compareCaseString('https:', ep.protocol);
|
|
317
|
+
const agent = is_sec ? https : http;
|
|
318
|
+
const headers = {
|
|
319
|
+
'Content-Type': 'application/json',
|
|
320
|
+
'Content-Length': strbody.length
|
|
321
|
+
};
|
|
322
|
+
const options = {
|
|
323
|
+
'host': k2hr3apiutil_1.default.getSafeString(ep.hostname),
|
|
324
|
+
'port': k2hr3apiutil_1.default.isSafeNumber(ep.port) ? ep.port : 0,
|
|
325
|
+
'path': _is_v3 ? '/v3/auth/tokens' : '/v2.0/tokens',
|
|
326
|
+
'method': 'POST',
|
|
327
|
+
'headers': headers,
|
|
328
|
+
'ca': (is_sec && null !== cacerts_1.ca) ? ((0, cacerts_1.ca)() ?? undefined) : undefined
|
|
329
|
+
};
|
|
330
|
+
// send request
|
|
331
|
+
const req = agent.request(options, (res) => {
|
|
332
|
+
let _body = '';
|
|
333
|
+
const _status = res.statusCode;
|
|
334
|
+
dbglogging_1.default.dlog('response status: ' + res.statusCode);
|
|
335
|
+
dbglogging_1.default.dlog('response header: ' + JSON.stringify(res.headers));
|
|
336
|
+
res.setEncoding('utf8');
|
|
337
|
+
res.on('data', (chunk) => {
|
|
338
|
+
//r3logger.dlog('response chunk: ' + chunk);
|
|
339
|
+
_body += chunk;
|
|
340
|
+
});
|
|
341
|
+
res.on('end', () => {
|
|
342
|
+
dbglogging_1.default.dlog('response body: ' + _body.slice(0, 36) + '...'); // for eslint to use _body.
|
|
343
|
+
rawTestKeystoneEpCallback(null, _lastest_cb, _epallmap, _region, _status ?? 500); // return result code
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
req.on('error', (err) => {
|
|
347
|
+
rawTestKeystoneEpCallback(err, _lastest_cb, _epallmap, _region, 504); // return result code = 500
|
|
348
|
+
});
|
|
349
|
+
req.on('socket', (socket) => {
|
|
350
|
+
socket.setTimeout(_timeout, () => {
|
|
351
|
+
req.abort();
|
|
352
|
+
rawTestKeystoneEpCallback(null, _lastest_cb, _epallmap, _region, 504); // return result code = 504
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
// write data to request body
|
|
356
|
+
req.write(strbody);
|
|
357
|
+
req.end();
|
|
358
|
+
};
|
|
359
|
+
//---------------------------------------------------------
|
|
360
|
+
// Callback function for dynamic/static endpoint list
|
|
361
|
+
//---------------------------------------------------------
|
|
362
|
+
// Input parameter:
|
|
363
|
+
// cbargs : parent function parameter in this object
|
|
364
|
+
// timeout : rawGetKeystoneEndpoint function argument
|
|
365
|
+
// is_test : rawGetKeystoneEndpoint function argument
|
|
366
|
+
// is_v3 : rawGetKeystoneEndpoint function argument
|
|
367
|
+
// callback : rawGetKeystoneEndpoint function argument
|
|
368
|
+
// err : if error is occurred, error object is set(if no error, this is null).
|
|
369
|
+
// epmap : keystone endpoint map which is result of dynamic/static list.
|
|
370
|
+
//
|
|
371
|
+
// Result: callback(error, keystone_ep)
|
|
372
|
+
// error : error object
|
|
373
|
+
// keystone_ep : url object with region string element
|
|
374
|
+
//
|
|
375
|
+
const rawGetKeystoneEndpointsCallback = (cbargs, err, epmap) => {
|
|
376
|
+
const _cbargs = cbargs;
|
|
377
|
+
if (null !== err) {
|
|
378
|
+
const error = new Error('failed to get dynamic keystone endpoints : ' + err.message);
|
|
379
|
+
dbglogging_1.default.elog(error.message);
|
|
380
|
+
_cbargs.callback(error, null);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (!rawIsValTypeKeystoneEndpointMap(epmap)) {
|
|
384
|
+
const error = new Error('getting dynamic keystone endpoints is something wrong.');
|
|
385
|
+
dbglogging_1.default.elog(error.message);
|
|
386
|
+
_cbargs.callback(error, null);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
//
|
|
390
|
+
// check and register endpoints to k2hdkc
|
|
391
|
+
//
|
|
392
|
+
const keys = (0, k2hr3keys_1.getK2hr3Keys)();
|
|
393
|
+
const epallmap = {};
|
|
394
|
+
for (const region in epmap) {
|
|
395
|
+
const tmpRegionEp = epmap[region];
|
|
396
|
+
if (!k2hr3apiutil_1.default.isSafeString(tmpRegionEp)) {
|
|
397
|
+
// wrong data
|
|
398
|
+
dbglogging_1.default.wlog('dynamic keystone endpoint for ' + region + ' is something wrong(' + JSON.stringify(tmpRegionEp) + '), thus skip it.');
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const region_url = tmpRegionEp;
|
|
402
|
+
// register only https!
|
|
403
|
+
const ep = k2hr3apiutil_1.default.urlParse(region_url);
|
|
404
|
+
if (!k2hr3apiutil_1.default.compareCaseString('https:', ep.protocol)) {
|
|
405
|
+
// not https
|
|
406
|
+
dbglogging_1.default.wlog('dynamic keystone endpoint for ' + region + ' is not https(' + ep.protocol + '), it is not good endpoint.');
|
|
407
|
+
}
|
|
408
|
+
// add to temporary
|
|
409
|
+
const tmpmap = {
|
|
410
|
+
'url': region_url,
|
|
411
|
+
'checked': false,
|
|
412
|
+
'status': 0
|
|
413
|
+
};
|
|
414
|
+
epallmap[region] = tmpmap;
|
|
415
|
+
}
|
|
416
|
+
if (!_cbargs.is_test) {
|
|
417
|
+
// not need to test(only updates), finish here
|
|
418
|
+
//
|
|
419
|
+
// [NOTE]
|
|
420
|
+
// keystone endpoint is null
|
|
421
|
+
//
|
|
422
|
+
_cbargs.callback(null, null);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
// test all endpoints(asynchronous)
|
|
426
|
+
for (const region in epallmap) {
|
|
427
|
+
// check each endpoint
|
|
428
|
+
//
|
|
429
|
+
// [NOTE]
|
|
430
|
+
// The callback function is called only when all the elements of
|
|
431
|
+
// epallmap are checked.
|
|
432
|
+
//
|
|
433
|
+
const tmpRegionEp = epallmap[region];
|
|
434
|
+
if (!rawIsValTypeKeystoneEndpoint(tmpRegionEp)) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
rawTestKeystoneEndpoint(epallmap, region, tmpRegionEp.url, _cbargs.is_v3, _cbargs.timeout, (err) => {
|
|
438
|
+
const _callback = _cbargs.callback;
|
|
439
|
+
const _epallmap = epallmap;
|
|
440
|
+
if (null !== err) {
|
|
441
|
+
const error = new Error('failed to check keystone endpoints : ' + err.message);
|
|
442
|
+
dbglogging_1.default.elog(error.message);
|
|
443
|
+
_callback(error, null);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
// set all endpoint map
|
|
447
|
+
for (const region2 in _epallmap) {
|
|
448
|
+
// register endpoint(type should be VALUE_KEYSTONE_NORMAL)
|
|
449
|
+
//
|
|
450
|
+
// [NOTE][TODO]
|
|
451
|
+
// Now, we do not distinguish v2 and v3 keystone, we are registering it into k2hdkc.
|
|
452
|
+
// This may possibly cause problems.
|
|
453
|
+
//
|
|
454
|
+
const tmpRegionEp2 = _epallmap[region2];
|
|
455
|
+
if (!rawIsValTypeKeystoneEndpoint(tmpRegionEp2)) {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
// [NOTE]
|
|
459
|
+
// k2hr3dkc is loaded here using require to lazily load it.
|
|
460
|
+
// This prevents errors caused by circular loading.
|
|
461
|
+
//
|
|
462
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
463
|
+
const k2hr3 = require('./k2hr3dkc').default;
|
|
464
|
+
const res_ep = k2hr3.setKeystoneEndpointAll(region2, tmpRegionEp2.url, keys.VALUE_KEYSTONE_NORMAL, 0);
|
|
465
|
+
if (!res_ep.result) {
|
|
466
|
+
dbglogging_1.default.elog('could not set keystone endpoint(' + tmpRegionEp2.url + ') for region2(' + region2 + ') into k2hdkc, but continue...');
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
dbglogging_1.default.mlog('add new keystone endpoint(' + tmpRegionEp2.url + ') for region2(' + region2 + ') into k2hdkc.');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// call ownself with not reentrant flag
|
|
473
|
+
rawGetKeystoneEndpoint((err, keystone_ep) => {
|
|
474
|
+
if (null !== err) {
|
|
475
|
+
const error = new Error('failed to get keystone endpoint : ' + err.message);
|
|
476
|
+
dbglogging_1.default.elog(error.message);
|
|
477
|
+
_callback(error, null);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
//
|
|
481
|
+
// returns keystone endpoint after remaking all endpoint
|
|
482
|
+
//
|
|
483
|
+
_callback(null, keystone_ep);
|
|
484
|
+
}, _cbargs.is_v3, _cbargs.is_test, _cbargs.timeout, false);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
//---------------------------------------------------------
|
|
489
|
+
// Get one of endpoint for keystone from static urls
|
|
490
|
+
//---------------------------------------------------------
|
|
491
|
+
// Input parameter
|
|
492
|
+
// callback : specify callback function
|
|
493
|
+
// is_v3 : keystone api v3 or v2(default v3)
|
|
494
|
+
// is_test : specify whether to test each keystone
|
|
495
|
+
// endpoint after unable to find a valid
|
|
496
|
+
// keystone endpoint and re-creating it.
|
|
497
|
+
// (default true)
|
|
498
|
+
// timeout : specify the timeout required to check
|
|
499
|
+
// each keystone endpoint.
|
|
500
|
+
// (default 30s)
|
|
501
|
+
// is_remake_keystone_ep : if keystone endpoint is not registered,
|
|
502
|
+
// it specifies whether to recreate it.
|
|
503
|
+
// (default false)
|
|
504
|
+
//
|
|
505
|
+
// Result: callback(error, keystone_ep)
|
|
506
|
+
// error : error object
|
|
507
|
+
// keystone_ep : url object with region string element
|
|
508
|
+
//
|
|
509
|
+
let last_region = null;
|
|
510
|
+
let last_endpoint = null;
|
|
511
|
+
let ksepinit = false;
|
|
512
|
+
let kseplist = null;
|
|
513
|
+
let ksepobj = null;
|
|
514
|
+
const rawInitializeKeystoneEpList = async () => {
|
|
515
|
+
if (ksepinit) {
|
|
516
|
+
// already initialized
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
520
|
+
const { r3ApiConfig } = require('./k2hr3config');
|
|
521
|
+
const apiConf = new r3ApiConfig();
|
|
522
|
+
if (apiConf.isKeystoneEpList()) {
|
|
523
|
+
kseplist = apiConf.getKeystoneEpList();
|
|
524
|
+
}
|
|
525
|
+
else if (apiConf.isKeystoneEpFile()) {
|
|
526
|
+
const ksepobjPath = './' + apiConf.getKeystoneEpFile();
|
|
527
|
+
const mod = await k2hr3apiutil_1.default.tryLoadModule(ksepobjPath);
|
|
528
|
+
if (rawIsDynamicOpenstackEpModule(mod)) {
|
|
529
|
+
ksepobj = mod;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
ksepinit = true;
|
|
533
|
+
};
|
|
534
|
+
const rawGetKeystoneEndpoint = (callback, is_v3, is_test, timeout, is_remake_keystone_ep) => {
|
|
535
|
+
if (!k2hr3apiutil_1.default.isSafeNumber(timeout)) {
|
|
536
|
+
timeout = 30000; // default 30s
|
|
537
|
+
}
|
|
538
|
+
if (!k2hr3apiutil_1.default.isBoolean(is_test)) {
|
|
539
|
+
is_test = true; // default true
|
|
540
|
+
}
|
|
541
|
+
if (!k2hr3apiutil_1.default.isBoolean(is_v3)) {
|
|
542
|
+
is_v3 = true; // default v3
|
|
543
|
+
}
|
|
544
|
+
if (!k2hr3apiutil_1.default.isBoolean(is_remake_keystone_ep)) {
|
|
545
|
+
is_remake_keystone_ep = false;
|
|
546
|
+
}
|
|
547
|
+
// [NOTE]
|
|
548
|
+
// This object is inherited from getDynamicKeystoneEndpoints
|
|
549
|
+
// to rawGetKeystoneEndpointsCallback.
|
|
550
|
+
// Ultimately, this function is called recursively and receives
|
|
551
|
+
// the data of this object as an argument.
|
|
552
|
+
//
|
|
553
|
+
const cbargs = {
|
|
554
|
+
timeout: timeout,
|
|
555
|
+
is_test: is_test,
|
|
556
|
+
is_v3: is_v3,
|
|
557
|
+
callback: callback
|
|
558
|
+
};
|
|
559
|
+
if (k2hr3apiutil_1.default.isSafeString(last_endpoint)) {
|
|
560
|
+
// there is a cache for endpoint/region
|
|
561
|
+
const keystone_ep = k2hr3apiutil_1.default.urlParse(last_endpoint);
|
|
562
|
+
if (rawIsValTypeUrlKeystoneEndpoint(keystone_ep)) {
|
|
563
|
+
keystone_ep.region = k2hr3apiutil_1.default.isSafeString(last_region) ? last_region : undefined;
|
|
564
|
+
cbargs.callback(null, keystone_ep);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
// there is no cache for endpoint/region
|
|
569
|
+
// [NOTE]
|
|
570
|
+
// k2hr3dkc is loaded here using require to lazily load it.
|
|
571
|
+
// This prevents errors caused by circular loading.
|
|
572
|
+
//
|
|
573
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
574
|
+
const k2hr3 = require('./k2hr3dkc').default;
|
|
575
|
+
const allres = k2hr3.getKeystoneEndpointAll();
|
|
576
|
+
if (!allres.result) {
|
|
577
|
+
dbglogging_1.default.elog('failed to get all keystone endpoint : ' + k2hr3apiutil_1.default.getSafeString(allres.message) + 'but continue for recovering.');
|
|
578
|
+
}
|
|
579
|
+
let keystones = {};
|
|
580
|
+
if (k2hr3apiutil_1.default.isSafeEntity(allres.keystones)) {
|
|
581
|
+
keystones = allres.keystones;
|
|
582
|
+
}
|
|
583
|
+
// search OK status from all
|
|
584
|
+
const keys = (0, k2hr3keys_1.getK2hr3Keys)();
|
|
585
|
+
for (const region in keystones) {
|
|
586
|
+
const tmpKSRegion = keystones[region];
|
|
587
|
+
const tmpStatus = k2hr3apiutil_1.default.isSafeNumeric(tmpKSRegion.status) ? k2hr3apiutil_1.default.cvtToNumber(tmpKSRegion.status) : null;
|
|
588
|
+
if (k2hr3apiutil_1.default.isPlainObject(tmpKSRegion) &&
|
|
589
|
+
k2hr3apiutil_1.default.isSafeString(tmpKSRegion.url) &&
|
|
590
|
+
k2hr3apiutil_1.default.isSafeString(tmpKSRegion.type) &&
|
|
591
|
+
(keys.VALUE_KEYSTONE_NORMAL === tmpKSRegion.type) &&
|
|
592
|
+
k2hr3apiutil_1.default.isSafeNumeric(tmpKSRegion.status) &&
|
|
593
|
+
k2hr3apiutil_1.default.isSafeNumber(tmpStatus) && (tmpStatus < 500)) // allow 0, 2xx, 3xx, 4xx
|
|
594
|
+
{
|
|
595
|
+
// found reachable endpoint, then set cache and result
|
|
596
|
+
last_region = region;
|
|
597
|
+
last_endpoint = tmpKSRegion.url;
|
|
598
|
+
const keystone_ep = k2hr3apiutil_1.default.urlParse(last_endpoint);
|
|
599
|
+
if (rawIsValTypeUrlKeystoneEndpoint(keystone_ep)) {
|
|
600
|
+
keystone_ep.region = last_region;
|
|
601
|
+
cbargs.callback(null, keystone_ep);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
//
|
|
607
|
+
// There are no available endpoints
|
|
608
|
+
//
|
|
609
|
+
if (!is_remake_keystone_ep) {
|
|
610
|
+
const error = new Error('there is no safe keystone endpoints.');
|
|
611
|
+
dbglogging_1.default.elog(error.message);
|
|
612
|
+
cbargs.callback(error, null);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
//
|
|
616
|
+
// Initialize endpoint list(object)
|
|
617
|
+
//
|
|
618
|
+
rawInitializeKeystoneEpList();
|
|
619
|
+
//
|
|
620
|
+
// try to remake keystone endpoint
|
|
621
|
+
//
|
|
622
|
+
if (rawIsValTypeKeystoneEndpointMap(kseplist)) {
|
|
623
|
+
//
|
|
624
|
+
// Get a static keystone endpoint.
|
|
625
|
+
//
|
|
626
|
+
rawGetKeystoneEndpointsCallback(cbargs, null, kseplist);
|
|
627
|
+
}
|
|
628
|
+
else if (k2hr3apiutil_1.default.isSafeEntity(ksepobj)) {
|
|
629
|
+
//
|
|
630
|
+
// Get keystone endpoint list dynamically.
|
|
631
|
+
//
|
|
632
|
+
ksepobj.getDynamicKeystoneEndpoints(cbargs, rawGetKeystoneEndpointsCallback);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
//
|
|
636
|
+
// Wrong configuration
|
|
637
|
+
//
|
|
638
|
+
const error = new Error('keystone endpoint configuration is something wrong.');
|
|
639
|
+
dbglogging_1.default.elog(error.message);
|
|
640
|
+
cbargs.callback(error, null);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
//---------------------------------------------------------
|
|
645
|
+
// Exports
|
|
646
|
+
//---------------------------------------------------------
|
|
647
|
+
//
|
|
648
|
+
// Functions
|
|
649
|
+
//
|
|
650
|
+
exports.openstackep = {
|
|
651
|
+
isValTypeKeystoneEndpoint: rawIsValTypeKeystoneEndpoint,
|
|
652
|
+
isValTypeKeystoneEndpointMap: rawIsValTypeKeystoneEndpointMap,
|
|
653
|
+
isValTypeGetKeystoneEndpointArgs: rawIsValTypeGetKeystoneEndpointArgs,
|
|
654
|
+
getKeystoneEndpoint: rawGetKeystoneEndpoint
|
|
655
|
+
};
|
|
656
|
+
//
|
|
657
|
+
// Default
|
|
658
|
+
//
|
|
659
|
+
exports.default = exports.openstackep;
|
|
660
|
+
/*
|
|
661
|
+
* Local variables:
|
|
662
|
+
* tab-width: 4
|
|
663
|
+
* c-basic-offset: 4
|
|
664
|
+
* End:
|
|
665
|
+
* vim600: noexpandtab sw=4 ts=4 fdm=marker
|
|
666
|
+
* vim<600: noexpandtab sw=4 ts=4
|
|
667
|
+
*/
|