Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/opencensus-core/src/internal/string-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright 2018 Google LLC
*
* 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.
*/

/**
* Internal utility methods for working with tag keys, tag values, and metric
* names.
*/
export class StringUtils {
/**
* Determines whether the String contains only printable characters.
*
* @param {string} str The String to be validated.
* @returns {boolean} Whether the String contains only printable characters.
*/
static isPrintableString(str: string): boolean {
for (let i = 0; i < str.length; i++) {
const ch: string = str.charAt(i);
if (!StringUtils.isPrintableChar(ch)) {
return false;
}
}
return true;
}

Comment thread
isaikevych marked this conversation as resolved.
/**
* Determines whether the Character is printable.
*
* @param {string} str The Character to be validated.
* @returns {boolean} Whether the Character is printable.
*/
static isPrintableChar(ch: string): boolean {
return ch >= ' ' && ch <= '~';
}
}
178 changes: 178 additions & 0 deletions packages/opencensus-core/src/resource/resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* Copyright 2018, OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {StringUtils} from '../internal/string-utils';

import {Labels, Resource} from './types';

/**
* Resource represents a resource, which capture identifying information about
* the entities for which signals (stats or traces) are reported. It further
* provides a framework for detection of resource information from the
* environment and progressive population as signals propagate from the core
* instrumentation library to a backend's exporter.
*/
export class CoreResource {
// Type, label keys, and label values should not exceed 256 characters.
private static readonly MAX_LENGTH = 255;
Comment thread
isaikevych marked this conversation as resolved.

// OC_RESOURCE_LABELS is a comma-separated list of labels.
private static readonly COMMA_SEPARATOR = ',';

// OC_RESOURCE_LABELS contains key value pair separated by '='.
private static readonly LABEL_KEY_VALUE_SPLITTER = '=';

private static readonly ENV_TYPE =
CoreResource.parseResourceType(process.env.OC_RESOURCE_TYPE);
private static readonly ENV_LABEL_MAP =
CoreResource.parseResourceLabels(process.env.OC_RESOURCE_LABELS);
private static readonly ERROR_MESSAGE_INVALID_CHARS =
'should be a ASCII string with a length greater than 0 and not exceed ' +
CoreResource.MAX_LENGTH + ' characters.';
private static readonly ERROR_MESSAGE_INVALID_VALUE =
'should be a ASCII string with a length not exceed ' +
CoreResource.MAX_LENGTH + ' characters.';

/**
* Returns a Resource. This resource information is loaded from the
* OC_RESOURCE_TYPE and OC_RESOURCE_LABELS environment variables.
*
* @returns {Resource} The resource.
*/
static createFromEnvironmentVariables(): Resource {
return {type: CoreResource.ENV_TYPE, labels: CoreResource.ENV_LABEL_MAP};
}

/**
* Returns a Resource that runs all input resources sequentially and merges
* their results. In case a type of label key is already set, the first set
* value takes precedence.
*
* @param {Resource[]} resources The list of the resources.
* @returns {Resource} The resource.
*/
static mergeResources(resources: Resource[]): Resource {
let currentResource: Resource;
for (const resource of resources) {
currentResource = this.merge(currentResource, resource);
}
return currentResource;
}

/**
* Creates a resource type from the OC_RESOURCE_TYPE environment variable.
*
* OC_RESOURCE_TYPE: A string that describes the type of the resource
* prefixed by a domain namespace, e.g. “kubernetes.io/container”.
Comment thread
isaikevych marked this conversation as resolved.
*
* @param {string} rawEnvType The resource type.
* @returns {string} The sanitized resource type.
*/
private static parseResourceType(rawEnvType: string): string {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please move private methods to top for more readability?

if (rawEnvType) {
if (!CoreResource.isValidAndNotEmpty(rawEnvType)) {
throw new Error(`Type ${CoreResource.ERROR_MESSAGE_INVALID_CHARS}`);
}
return rawEnvType.trim();
}
return null;
}

/**
* Creates a label map from the OC_RESOURCE_LABELS environment variable.
*
* OC_RESOURCE_LABELS: A comma-separated list of labels describing the
* source in more detail, e.g. “key1=val1,key2=val2”. Domain names and paths
* are accepted as label keys. Values may be quoted or unquoted in general. If
* a value contains whitespaces, =, or " characters, it must always be quoted.
Comment thread
isaikevych marked this conversation as resolved.
*
* @param {string} rawEnvLabels The resource labels as a comma-seperated list
* of key/value pairs.
* @returns {Labels} The sanitized resource labels.
*/
private static parseResourceLabels(rawEnvLabels: string): Labels {
const labels: Labels = {};
if (rawEnvLabels) {
const rawLabels: string[] = rawEnvLabels.split(this.COMMA_SEPARATOR, -1);
for (const rawLabel of rawLabels) {
const keyValuePair: string[] =
rawLabel.split(this.LABEL_KEY_VALUE_SPLITTER, -1);
if (keyValuePair.length !== 2) {
continue;
}
let [key, value] = keyValuePair;
// Leading and trailing whitespaces are trimmed.
key = key.trim();
value = value.trim().split('^"|"$').join('');
if (!CoreResource.isValidAndNotEmpty(key)) {
throw new Error(
`Label key ${CoreResource.ERROR_MESSAGE_INVALID_CHARS}`);
}
if (!CoreResource.isValid(value)) {
throw new Error(
`Label value ${CoreResource.ERROR_MESSAGE_INVALID_VALUE}`);
}
labels[key] = value;
}
}
return labels;
}

/**
* Returns a new, merged Resource by merging two resources. In case of
* a collision, first resource takes precedence.
Comment thread
isaikevych marked this conversation as resolved.
*
* @param {Resource} resource The resource object.
* @param {Resource} otherResource The resource object.
* @returns {Resource} A new, merged Resource.
*/
private static merge(resource: Resource, otherResource: Resource): Resource {
if (!resource) {
return otherResource;
}
if (!otherResource) {
return resource;
}
return {
type: resource.type || otherResource.type,
labels: Object.assign({}, otherResource.labels, resource.labels)
};
}


/**
* Determines whether the given String is a valid printable ASCII string with
* a length not exceed MAX_LENGTH characters.
Comment thread
isaikevych marked this conversation as resolved.
*
* @param {string} str The String to be validated.
* @returns {boolean} Whether the String is valid.
*/
private static isValid(name: string): boolean {
return name.length <= CoreResource.MAX_LENGTH &&
StringUtils.isPrintableString(name);
}

/**
* Determines whether the given String is a valid printable ASCII string with
* a length greater than 0 and not exceed MAX_LENGTH characters.
Comment thread
isaikevych marked this conversation as resolved.
*
* @param {string} str The String to be validated.
* @returns {boolean} Whether the String is valid and not empty.
*/
private static isValidAndNotEmpty(name: string): boolean {
return name && name.length > 0 && CoreResource.isValid(name);
}
}
32 changes: 32 additions & 0 deletions packages/opencensus-core/src/resource/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright 2018, OpenCensus Authors
*
* 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.
*/

/** A Resource describes the entity for which a signal was collected. */
export interface Resource {
/**
* An optional string which describes a well-known type of resource.
*/
readonly type: string;

/**
* A dictionary of labels with string keys and values that provide information
* about the entity.
*/
readonly labels: Labels;
}

/** Labels are maps of keys -> values */
export interface Labels { [key: string]: string; }
104 changes: 104 additions & 0 deletions packages/opencensus-core/test/test-resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright 2018, OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as assert from 'assert';

process.env.OC_RESOURCE_TYPE = 'k8s.io/container';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess that its necessary just for first describe, if so - please move to place of usage

process.env.OC_RESOURCE_LABELS =
'k8s.io/pod/name="pod-xyz-123",k8s.io/container/name="c1",k8s.io/namespace/name="default"';

import {CoreResource} from '../src/resource/resource';
import {Resource, Labels} from '../src/resource/types';

describe('Resource()', () => {
after(() => {
delete process.env.OC_RESOURCE_TYPE;
delete process.env.OC_RESOURCE_LABELS;
});

it('should return resource information from environment variables', () => {
const resource = CoreResource.createFromEnvironmentVariables();
const actualLabels = resource.labels;
const expectedLabels: Labels = {
'k8s.io/container/name': '"c1"',
'k8s.io/namespace/name': '"default"',
'k8s.io/pod/name': '"pod-xyz-123"'
};

assert.strictEqual(resource.type, 'k8s.io/container');
assert.equal(Object.keys(actualLabels).length, 3);
assert.deepEqual(actualLabels, expectedLabels);
});
});

describe('mergeResources()', () => {
const DEFAULT_RESOURCE: Resource = {type: null, labels: {}};
const DEFAULT_RESOURCE_1: Resource = {type: 'default', labels: {'a': '100'}};
const RESOURCE_1: Resource = {type: 't1', labels: {'a': '1', 'b': '2'}};
const RESOURCE_2:
Resource = {type: 't2', labels: {'a': '1', 'b': '3', 'c': '4'}};

it('merge resources with default, resource1', () => {
const resources: Resource[] = [DEFAULT_RESOURCE, RESOURCE_1];
const resource = CoreResource.mergeResources(resources);
const expectedLabels: Labels = {'a': '1', 'b': '2'};

assert.equal(resource.type, 't1');
assert.equal(Object.keys(resource.labels).length, 2);
assert.deepEqual(resource.labels, expectedLabels);
});

it('merge resources with default, resource1, resource2 = null', () => {
Comment thread
isaikevych marked this conversation as resolved.
const resources: Resource[] = [DEFAULT_RESOURCE, RESOURCE_1, null];
const resource = CoreResource.mergeResources(resources);
const expectedLabels: Labels = {'a': '1', 'b': '2'};

assert.equal(resource.type, 't1');
assert.equal(Object.keys(resource.labels).length, 2);
assert.deepEqual(resource.labels, expectedLabels);
});

it('merge resources with default, resource1 = null, resource2', () => {
const resources: Resource[] = [DEFAULT_RESOURCE, null, RESOURCE_2];
const resource = CoreResource.mergeResources(resources);
const expectedLabels: Labels = {'a': '1', 'b': '3', 'c': '4'};

assert.equal(resource.type, 't2');
assert.equal(Object.keys(resource.labels).length, 3);
assert.deepEqual(resource.labels, expectedLabels);
});

it('merge resources with default1, resource1, resource2', () => {
const resources: Resource[] = [DEFAULT_RESOURCE_1, RESOURCE_1, RESOURCE_2];
const resource = CoreResource.mergeResources(resources);
const expectedLabels: Labels = {'a': '100', 'b': '2', 'c': '4'};

assert.equal(resource.type, 'default');
assert.equal(Object.keys(resource.labels).length, 3);
assert.deepEqual(resource.labels, expectedLabels);
});

it('merge resources with default, resource1 = undefined, resource2 = undefined',
() => {
const resources: Resource[] = [DEFAULT_RESOURCE_1, undefined, undefined];
const resource = CoreResource.mergeResources(resources);
const expectedLabels: Labels = {'a': '100'};

assert.equal(resource.type, 'default');
assert.equal(Object.keys(resource.labels).length, 1);
assert.deepEqual(resource.labels, expectedLabels);
});
});
30 changes: 30 additions & 0 deletions packages/opencensus-core/test/test-string-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright 2018, OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as assert from 'assert';
import {StringUtils} from '../src/internal/string-utils';

describe('StringUtils', () => {
it('should return true when string is printable', () => {
const isValid = StringUtils.isPrintableString('abcd');
assert.deepStrictEqual(isValid, true);
});

it('should return false when string is not printable', () => {
const isValid = StringUtils.isPrintableString('\x00-\xFF');
assert.deepStrictEqual(isValid, false);
});
});