Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/hasura #4

Merged
merged 6 commits into from
Jun 19, 2024
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
177 changes: 85 additions & 92 deletions src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,22 @@ export class Manifest {
});
}

// if (this.value?.deploy?.addons?.redis) {
// this.value = defaultsDeep(this.value, {
// scale: {
// addons: {
// redis: {
// profile: 'small',
// },
// },
// },
// });
// }
if (this.deploy?.addons?.hasura) {
defaultsDeep(this, {
deploy: {
addons: {
hasura: {},
},
},
scale: {
addons: {
hasura: {
replicas: 1,
},
},
},
});
}
}

squidName() {
Expand Down Expand Up @@ -142,95 +147,86 @@ export class Manifest {
return yaml.dump(this.values());
}

eval(context: Record<string, any>): ManifestValue {
const { error: parseError, value: parsed } = this.parse();
if (parseError) {
throw parseError;
}

const _eval = (env: Record<string, Expression> | undefined, path: string) => {
return mapValues(env, (value, key) => {
try {
return value.eval(context);
} catch (e) {
throw new ManifestEvaluatingError([
getError(`${path}.${key}`, get(this as ManifestValue, `${path}.${key}`), e),
]);
}
});
};

const raw = this.toObject();

return {
...raw,
deploy: defaultsDeep(
{
env: _eval(parsed.env, 'deploy.env'),
init: { env: _eval(parsed.init.env, 'deploy.init.env') },
api: { env: _eval(parsed.api.env, 'deploy.api.env') },
processor: parsed.processor.map((p, index) =>
defaultsDeep(
{
env: _eval(p.env, `deploy.processor.[${index}].env`),
},
raw.deploy?.processor?.[index],
),
),
},
raw.deploy,
),
};
private getAllEnvPaths() {
return [
'deploy.env',
'deploy.init.env',
'deploy.api.env',
'deploy.addons.hasura.env',
...(this.deploy?.processor.map((p, index) => `deploy.processor.[${index}].env`) || []),
];
}

variables(path?: string[]) {
const res: Set<string> = new Set();
eval(context: Record<string, unknown>): ManifestValue {
const raw = this.toObject();
const paths = this.getAllEnvPaths();
const parser = new Parser();
const errors: string[] = [];

const { error: parseError, value: parsed } = this.parse();
if (parseError) {
throw parseError;
for (const path of paths) {
const envObject = get(raw, path);
if (!envObject) continue;

set(
raw,
path,
mapValues(envObject, (value, key) => {
let expr: Expression;
try {
expr = parser.parse(value);
} catch (e) {
errors.push(getError(`${path}.${key}`, value, e));
return value;
}

try {
return expr.eval(context);
} catch (e) {
errors.push(getError(`${path}.${key}`, value, e));
return value;
}
}),
);
}

const _variables = (env: Record<string, Expression> | undefined) => {
mapValues(env, value => value.variables(path).forEach(v => res.add(v)));
};

_variables(parsed.env);
_variables(parsed.init.env);
_variables(parsed.api.env);
parsed.processor?.forEach(p => _variables(p.env));
if (errors.length) {
throw new ManifestEvaluatingError(errors);
}

return [...res];
return raw;
}

private parse() {
variables(prefix: string[] = []): string[] {
const raw = this.toObject();
const paths = this.getAllEnvPaths();
const parser = new Parser();
const errors: string[] = [];
const result = new Set<string>();

const _parse = (env: Record<string, string> | undefined, path: string) => {
return env
? mapValues(env, (value, key) => {
try {
return parser.parse(value);
} catch (e: unknown) {
errors.push(getError(`${path}.${key}`, value, e));
return new Expression(['']);
}
})
: undefined;
};
for (const path of paths) {
const envObject: Record<string, string> = get(raw, path);
if (!envObject) continue;

const res = {
env: _parse(this.deploy?.env, 'deploy.env'),
init: { env: _parse(this.deploy?.init?.env, 'deploy.init.env') },
api: { env: _parse(this.deploy?.api?.env, 'deploy.api.env') },
processor:
this.deploy?.processor?.map((p, index) => ({
env: _parse(p.env, `deploy.processor.[${index}].env`),
})) || [],
};
for (const [key, value] of Object.entries(envObject)) {
let expr: Expression;
try {
expr = parser.parse(value);
} catch (e) {
errors.push(getError(`${path}.${key}`, value, e));
continue;
}

return errors.length > 0 ? { error: new ManifestEvaluatingError(errors) } : { value: res };
for (const name of expr.variables(prefix)) {
result.add(name);
}
}
}

if (errors.length) {
throw new ManifestParsingError(errors);
}

return Array.from(result);
}

static validate(value: Partial<ManifestValue>, options: ManifestParsingOptions = {}) {
Expand All @@ -255,17 +251,14 @@ export class Manifest {
try {
const raw = yaml.load(str || '{}') as Partial<ManifestValue>;

['build', 'deploy.api', 'deploy.addons.postgres'].map(path => {
['build', 'deploy.api', 'deploy.addons.postgres', 'deploy.addons.hasura'].map(path => {
if (get(raw, path) === null) {
set(raw, path, {});
}
});

const { error, value } = this.validate(raw, options);

if (error) {
return { error };
}
if (error) return { error };

return {
value: new Manifest(value),
Expand Down
9 changes: 9 additions & 0 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ export const manifestSchema = Joi.object<ManifestValue>({
max_pred_locks_per_transaction: Joi.number().integer().positive(),
}),
}).allow(null),
hasura: Joi.object({
version: Joi.string().default('latest'),
env: envSchema,
}).allow(null),
rpc: Joi.array().items(
Joi.string()
.valid(
Expand Down Expand Up @@ -151,6 +155,11 @@ export const manifestSchema = Joi.object<ManifestValue>({
profile: Joi.string().valid('small', 'medium', 'large'),
}),

hasura: Joi.object({
replicas: Joi.number().integer().positive().max(5),
profile: Joi.string().valid('small', 'medium', 'large'),
}),

/** @deprecated **/
rpc: Joi.object({
'monthly-cap': Joi.string().regex(/\d+[km]/),
Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export type ManifestDeploymentConfig = {
max_pred_locks_per_transaction: number;
};
};
hasura?: {
version?: string;
env?: Record<string, string>;
};
rpc?: string[];
};
api?: {
Expand All @@ -47,6 +51,10 @@ export type ManifestScaleConfig = {
profile: 'small' | 'medium' | 'large';
default_storage?: boolean;
};
hasura?: {
profile: 'small' | 'medium' | 'large';
replicas?: number;
};
};
api?: {
replicas?: number;
Expand Down
73 changes: 73 additions & 0 deletions test/addon_hasura.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Manifest } from '../src';

describe('Addon Hasura', () => {
it('should add hasura defaults', () => {
const { error, value } = Manifest.parse(`
manifest_version: subsquid.io/v0.1
name: test
version: 1
build:
deploy:
addons:
hasura:
processor:
cmd: [ "node", "lib/processor" ]
`);

expect(error).toBeUndefined();
expect(value).toMatchObject({
deploy: {
addons: {
hasura: {
version: 'latest',
},
},
},
scale: {
addons: {
hasura: {
replicas: 1,
},
},
},
});
});

it('should add secrtes', () => {
const res = new Manifest({
manifest_version: 'subsquid.io/v0.1',
name: 'test',
version: 1,
deploy: {
addons: {
hasura: {
env: {
HASURA_ADMIN_SECRET: '${{secrets.admin_secret }}',
},
},
},
processor: {
name: 'processor',
},
},
}).eval({
secrets: { admin_secret: 'mysecret' },
});

expect(res).toMatchObject({
manifest_version: 'subsquid.io/v0.1',
name: 'test',
version: 1,
deploy: {
addons: {
hasura: {
env: {
HASURA_ADMIN_SECRET: 'mysecret',
},
},
},
processor: [{ name: 'processor' }],
},
});
});
});
Loading
Loading