Skip to content

Commit

Permalink
Merge pull request #46 from kubeshop/f1ames/fix/fail-no-origin
Browse files Browse the repository at this point in the history
fix: handle origin connection issues without throwing
  • Loading branch information
f1ames authored Dec 20, 2023
2 parents ee6c1eb + d833539 commit baecf35
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 36 deletions.
6 changes: 5 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ import { config } from "./commands/config.js";
import { init } from "./commands/init.js";
import { handleFailure } from "./errors.js";
import { VERSION } from "./version.js";
import { settings } from "./utils/settings.js";
import fetch from "isomorphic-fetch";

(global as any).fetch = fetch;
import "abortcontroller-polyfill/dist/polyfill-patch-fetch.js";

const argv = hideBin(process.argv);
const debug = argv.includes("--debug");

settings.debug = debug;

export const cli = yargs(argv)
.scriptName("monokle")
.version(VERSION)
Expand All @@ -33,7 +38,6 @@ export const cli = yargs(argv)
"Learn more at https://github.com/kubeshop/monokle-cli");
})
.fail((_, err) => {
const debug = argv.includes("--debug");
handleFailure(err, debug, argv);
})
.showHelpOnFail(false)
Expand Down
4 changes: 4 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export function displayError(err: Error, argv: string[]) {
}
}

export function displayWarning(msg: string) {
print(warningInfo(msg));
}

function getFriendlyErrorMessage(err: Error, argv: string[]): string {
if (!isApiTokenUsed(argv) || !isCloudCommand(argv)) {
return 'Something unexpected happened.';
Expand Down
10 changes: 9 additions & 1 deletion src/utils/authenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,21 @@ class AuthenticatorGetter {
try {
this._authenticator = await createMonokleAuthenticatorFromOrigin(AUTHENTICATOR_CLIENT_ID, origin || undefined);
} catch (err) {
// If we can't use given origin, it doesn't make sense to continue.
// If we can't use given origin, it doesn't make sense to continue since it's not possible to authenticate then.
throw err;
}
}

return this._authenticator;
}

async getInstanceSafe(recreate = false): Promise<Authenticator | undefined> {
try {
return await this.getInstance(recreate);
} catch (err) {
return undefined;
}
}
}

export const authenticatorGetter = new AuthenticatorGetter();
2 changes: 1 addition & 1 deletion src/utils/conditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { authenticatorGetter } from "./authenticator.js";
import {AlreadyAuthenticated, Unauthenticated} from "../errors.js";

export async function isAuthenticated() {
return Boolean((await authenticatorGetter.getInstance()).user.isAuthenticated);
return Boolean((await authenticatorGetter.getInstanceSafe())?.user.isAuthenticated);
}

export async function throwIfNotAuthenticated() {
Expand Down
31 changes: 19 additions & 12 deletions src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { Config, readConfig } from "@monokle/validation";
import { TokenInfo } from '@monokle/synchronizer';
import { authenticatorGetter } from "./authenticator.js";
import { synchronizerGetter } from "./synchronizer.js";
import { settings } from "../utils/settings.js";
import { resolve } from "path";
import { Framework, getFrameworkConfig } from "../frameworks/index.js";
import { isStdinLike } from "./stdin.js";
import { displayWarning } from "../errors.js";

export type ConfigData = {
config: Config | undefined,
Expand Down Expand Up @@ -63,19 +65,24 @@ export async function getConfig(
const useRemoteExplicit = !!projectSlug;
const useLocalExplicit = !!configPath && !options.isDefaultConfigPath;

const authenticator = await authenticatorGetter.getInstance();
if (!options.apiToken && authenticator.user.isAuthenticated) {
await authenticator.refreshToken();
let activeToken: undefined | TokenInfo = options.apiToken ? {accessToken: options.apiToken, tokenType: 'ApiKey'} : undefined;
if (!activeToken) {
try {
const authenticator = await authenticatorGetter.getInstance();
if (authenticator.user.isAuthenticated) {
await authenticator.refreshToken();
}
activeToken = authenticator.user.tokenInfo ?? undefined;
} catch (err: any) {
if (settings.debug) {
displayWarning(`Could not authenticate with given origin: ${settings.origin} to fetch remote policy. Error: ${err.message}.`);
}
}
}

const tokenInfo = (options.apiToken ? {
accessToken: options.apiToken,
tokenType: 'ApiKey'
} : authenticator.user.tokenInfo) as TokenInfo;

if (useRemoteExplicit && useLocalExplicit) { // Remote or local or fail
const results = await Promise.allSettled([
getRemotePolicyForProject(projectSlug, tokenInfo!),
getRemotePolicyForProject(projectSlug, activeToken!),
getLocalPolicy(configPath)
]);

Expand All @@ -91,14 +98,14 @@ export async function getConfig(
}

if (useRemoteExplicit) { // Remote or fail
return getRemotePolicyForProject(projectSlug, tokenInfo!);
return getRemotePolicyForProject(projectSlug, activeToken!);
}

if (useLocalExplicit) { // Local or fail
return getLocalPolicy(configPath);
}

return getPolicyImplicit(path, configPath, tokenInfo!);
return getPolicyImplicit(path, configPath, activeToken);
}

export async function getRemotePolicyForProject(slug: string, token: TokenInfo): Promise<ConfigData> {
Expand Down Expand Up @@ -152,7 +159,7 @@ export async function getLocalPolicy(configPath: string): Promise<ConfigData> {
}
}

export async function getPolicyImplicit(path: string, configPath: string, token: TokenInfo): Promise<ConfigData> {
export async function getPolicyImplicit(path: string, configPath: string, token?: TokenInfo): Promise<ConfigData> {
const isStdin = isStdinLike(path);

if (token?.accessToken?.length && !isStdin) {
Expand Down
13 changes: 13 additions & 0 deletions src/utils/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ export type Settings = {
origin?: string;
};

export type EphemeralSettings = {
debug?: boolean;
};

export class StorageSettings extends StorageHandler<Settings> {
private currentData: Settings = {};
private initialData: Settings = {};
private ephemeralData: EphemeralSettings = {};

constructor() {
super(getDefaultStorageConfigPaths().config);
Expand All @@ -27,6 +32,14 @@ export class StorageSettings extends StorageHandler<Settings> {
this.currentData.origin = origin;
}

get debug() {
return this.ephemeralData.debug || false;
}

set debug(debug: boolean) {
this.ephemeralData.debug = debug;
}

async persist() {
await this.setStoreData({ ...this.initialData, ...this.currentData}, SETTINGS_FILE);
}
Expand Down
2 changes: 1 addition & 1 deletion src/utils/synchronizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class SynchronizerGetter {
try {
this._synchronizer = await createMonokleSynchronizerFromOrigin(origin || undefined);
} catch (err) {
// If we can't use given origin, it doesn't make sense to continue.
// If we can't use given origin, it doesn't make sense to continue since it's not possible to synchronize policies then.
throw err;
}
}
Expand Down
45 changes: 25 additions & 20 deletions src/utils/validator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ResourceParser, MonokleValidator, RemotePluginLoader, requireFromStringCustomPluginLoader, SchemaLoader, AnnotationSuppressor, FingerprintSuppressor, DisabledFixer } from "@monokle/validation";
import { fetchOriginConfig } from "@monokle/synchronizer";
import { settings } from "./settings.js";
import { displayWarning } from "../errors.js";

// This class exists for test purposes to easily mock the validator.
// It also ensures singleton instance of the synchronizer is used.
Expand All @@ -12,29 +13,33 @@ class ValidatorGetter {
if (!this._validator) {
const origin = settings.origin;

let schemasOrigin: string | undefined = undefined;
try {
const originConfig = await fetchOriginConfig(origin);
this._validator = new MonokleValidator(
{
loader: new RemotePluginLoader(requireFromStringCustomPluginLoader),
parser: new ResourceParser(),
schemaLoader: new SchemaLoader(originConfig.schemasOrigin || undefined),
suppressors: [new AnnotationSuppressor(), new FingerprintSuppressor()],
fixer: new DisabledFixer(),
},
{
plugins: {
'kubernetes-schema': true,
'yaml-syntax': true,
'pod-security-standards': true,
'resource-links': true,
},
}
);
} catch (err) {
// If we can't use given origin, it doesn't make sense to continue.
throw err;
schemasOrigin = originConfig?.schemasOrigin;
} catch (err: any) {
if (settings.debug) {
displayWarning(`Could not connect to origin: ${origin}. Using default schemas for validation. Error: ${err.message}.`);
}
}

this._validator = new MonokleValidator(
{
loader: new RemotePluginLoader(requireFromStringCustomPluginLoader),
parser: new ResourceParser(),
schemaLoader: new SchemaLoader(schemasOrigin),
suppressors: [new AnnotationSuppressor(), new FingerprintSuppressor()],
fixer: new DisabledFixer(),
},
{
plugins: {
'kubernetes-schema': true,
'yaml-syntax': true,
'pod-security-standards': true,
'resource-links': true,
},
}
);
}

return this._validator;
Expand Down

0 comments on commit baecf35

Please sign in to comment.