mirror of https://github.com/pulumi/pulumi.git
276 lines
9.1 KiB
TypeScript
276 lines
9.1 KiB
TypeScript
// Copyright 2016-2018, Pulumi Corporation.
|
|
//
|
|
// 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 { deserializeProperties, serializeProperties } from "./rpc";
|
|
import { getProject, getStack, setMockOptions } from "./settings";
|
|
import { getStore } from "./state";
|
|
|
|
import * as structproto from "google-protobuf/google/protobuf/struct_pb";
|
|
import * as provproto from "../proto/provider_pb";
|
|
import * as resproto from "../proto/resource_pb";
|
|
|
|
/**
|
|
* {@link MockResourceArgs} is used to construct a new resource mock.
|
|
*/
|
|
export interface MockResourceArgs {
|
|
/**
|
|
* The token that indicates which resource type is being constructed. This
|
|
* token is of the form "package:module:type".
|
|
*/
|
|
type: string;
|
|
|
|
/**
|
|
* The logical name of the resource instance.
|
|
*/
|
|
name: string;
|
|
|
|
/**
|
|
* The inputs for the resource.
|
|
*/
|
|
inputs: any;
|
|
|
|
/**
|
|
* If provided, the identifier of the provider instance being used to manage
|
|
* this resource.
|
|
*/
|
|
provider?: string;
|
|
|
|
/**
|
|
* Specifies whether or not the resource is Custom (i.e. managed by a
|
|
* resource provider).
|
|
*/
|
|
custom?: boolean;
|
|
|
|
/**
|
|
* If provided, the physical identifier of an existing resource to read or
|
|
* import.
|
|
*/
|
|
id?: string;
|
|
}
|
|
|
|
/**
|
|
* {@link MockResourceResult} is the result of a new resource mock, returning a
|
|
* physical identifier and the output properties for the resource being
|
|
* constructed.
|
|
*/
|
|
export type MockResourceResult = {
|
|
id: string | undefined;
|
|
state: Record<string, any>;
|
|
};
|
|
|
|
/**
|
|
* {@link MockResourceArgs} is used to construct call mocks.
|
|
*/
|
|
export interface MockCallArgs {
|
|
/**
|
|
* The token that indicates which function is being called. This token is of
|
|
* the form "package:module:function".
|
|
*/
|
|
token: string;
|
|
|
|
/**
|
|
* The arguments provided to the function call.
|
|
*/
|
|
inputs: any;
|
|
|
|
/**
|
|
* If provided, the identifier of the provider instance being used to make
|
|
* the call.
|
|
*/
|
|
provider?: string;
|
|
}
|
|
|
|
/**
|
|
* {@link MockCallResult} is the result of a call mock.
|
|
*/
|
|
export type MockCallResult = Record<string, any>;
|
|
|
|
/**
|
|
* {@link Mocks} allows implementations to replace operations normally
|
|
* implemented by the Pulumi engine with their own implementations. This can be
|
|
* used during testing to ensure that calls to provider functions and resource
|
|
* constructors return predictable values.
|
|
*/
|
|
export interface Mocks {
|
|
/**
|
|
* Mocks provider-implemented function calls (e.g. `aws.get_availability_zones`).
|
|
*
|
|
* @param args MockCallArgs
|
|
*/
|
|
call(args: MockCallArgs): MockCallResult | Promise<MockCallResult>;
|
|
|
|
/**
|
|
* Mocks resource construction calls. This function should return the
|
|
* physical identifier and the output properties for the resource being
|
|
* constructed.
|
|
*
|
|
* @param args MockResourceArgs
|
|
*/
|
|
newResource(args: MockResourceArgs): MockResourceResult | Promise<MockResourceResult>;
|
|
}
|
|
|
|
export class MockMonitor {
|
|
readonly resources = new Map<string, { urn: string; id: string | null; state: any }>();
|
|
|
|
constructor(readonly mocks: Mocks) {}
|
|
|
|
private newUrn(parent: string, type: string, name: string): string {
|
|
if (parent) {
|
|
const qualifiedType = parent.split("::")[2];
|
|
const parentType = qualifiedType.split("$").pop();
|
|
type = parentType + "$" + type;
|
|
}
|
|
return "urn:pulumi:" + [getStack(), getProject(), type, name].join("::");
|
|
}
|
|
|
|
public async invoke(req: any, callback: (err: any, innerResponse: any) => void) {
|
|
try {
|
|
const tok = req.getTok();
|
|
const inputs = deserializeProperties(req.getArgs());
|
|
|
|
if (tok === "pulumi:pulumi:getResource") {
|
|
const registeredResource = this.resources.get(inputs.urn);
|
|
if (!registeredResource) {
|
|
throw new Error(`unknown resource ${inputs.urn}`);
|
|
}
|
|
const resp = new provproto.InvokeResponse();
|
|
resp.setReturn(structproto.Struct.fromJavaScript(registeredResource));
|
|
callback(null, resp);
|
|
return;
|
|
}
|
|
|
|
const result: MockCallResult = await this.mocks.call({
|
|
token: tok,
|
|
inputs: inputs,
|
|
provider: req.getProvider(),
|
|
});
|
|
const response = new provproto.InvokeResponse();
|
|
response.setReturn(structproto.Struct.fromJavaScript(await serializeProperties("", result)));
|
|
callback(null, response);
|
|
} catch (err) {
|
|
callback(err, undefined);
|
|
}
|
|
}
|
|
|
|
public async readResource(req: any, callback: (err: any, innterResponse: any) => void) {
|
|
try {
|
|
const result: MockResourceResult = await this.mocks.newResource({
|
|
type: req.getType(),
|
|
name: req.getName(),
|
|
inputs: deserializeProperties(req.getProperties()),
|
|
provider: req.getProvider(),
|
|
custom: true,
|
|
id: req.getId(),
|
|
});
|
|
|
|
const urn = this.newUrn(req.getParent(), req.getType(), req.getName());
|
|
const serializedState = await serializeProperties("", result.state);
|
|
|
|
this.resources.set(urn, { urn, id: result.id ?? null, state: serializedState });
|
|
|
|
const response = new resproto.ReadResourceResponse();
|
|
response.setUrn(urn);
|
|
response.setProperties(structproto.Struct.fromJavaScript(serializedState));
|
|
callback(null, response);
|
|
} catch (err) {
|
|
callback(err, undefined);
|
|
}
|
|
}
|
|
|
|
public async registerResource(req: any, callback: (err: any, innerResponse: any) => void) {
|
|
try {
|
|
const result: MockResourceResult = await this.mocks.newResource({
|
|
type: req.getType(),
|
|
name: req.getName(),
|
|
inputs: deserializeProperties(req.getObject()),
|
|
provider: req.getProvider(),
|
|
custom: req.getCustom(),
|
|
id: req.getImportid(),
|
|
});
|
|
|
|
const urn = this.newUrn(req.getParent(), req.getType(), req.getName());
|
|
const serializedState = await serializeProperties("", result.state);
|
|
|
|
this.resources.set(urn, { urn, id: result.id ?? null, state: serializedState });
|
|
|
|
const response = new resproto.RegisterResourceResponse();
|
|
response.setUrn(urn);
|
|
response.setId(result.id || "");
|
|
response.setObject(structproto.Struct.fromJavaScript(serializedState));
|
|
callback(null, response);
|
|
} catch (err) {
|
|
callback(err, undefined);
|
|
}
|
|
}
|
|
|
|
public registerResourceOutputs(req: any, callback: (err: any, innerResponse: any) => void) {
|
|
try {
|
|
const registeredResource = this.resources.get(req.getUrn());
|
|
if (!registeredResource) {
|
|
throw new Error(`unknown resource ${req.getUrn()}`);
|
|
}
|
|
registeredResource.state = req.getOutputs();
|
|
|
|
callback(null, {});
|
|
} catch (err) {
|
|
callback(err, undefined);
|
|
}
|
|
}
|
|
|
|
public supportsFeature(req: any, callback: (err: any, innerResponse: any) => void) {
|
|
const id = req.getId();
|
|
|
|
// Support for "outputValues" is deliberately disabled for the mock monitor so
|
|
// instances of `Output` don't show up in `MockResourceArgs` inputs.
|
|
const hasSupport = id !== "outputValues";
|
|
|
|
callback(null, {
|
|
getHassupport: () => hasSupport,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configures the Pulumi runtime to use the given mocks for testing.
|
|
*
|
|
* @param mocks
|
|
* The mocks to use for calls to provider functions and resource construction.
|
|
* @param project
|
|
* If provided, the name of the Pulumi project. Defaults to "project".
|
|
* @param stack
|
|
* If provided, the name of the Pulumi stack. Defaults to "stack".
|
|
* @param preview
|
|
* If provided, indicates whether or not the program is running a preview. Defaults to false.
|
|
* @param organization
|
|
* If provided, the name of the Pulumi organization. Defaults to nothing.
|
|
*/
|
|
export async function setMocks(
|
|
mocks: Mocks,
|
|
project?: string,
|
|
stack?: string,
|
|
preview?: boolean,
|
|
organization?: string,
|
|
) {
|
|
setMockOptions(new MockMonitor(mocks), project, stack, preview, organization);
|
|
|
|
// Mocks enable all features except outputValues.
|
|
const store = getStore();
|
|
store.supportsSecrets = true;
|
|
store.supportsResourceReferences = true;
|
|
store.supportsOutputValues = false;
|
|
store.supportsDeletedWith = true;
|
|
store.supportsAliasSpecs = true;
|
|
store.supportsTransforms = false;
|
|
}
|