UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

1,662 lines • 66.8 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GetCron = exports.StartCron = exports.Cron = exports.Run = exports.StartStop = exports.ForceRedeployment = exports.GetWorkload = exports.Create = exports.Open = exports.ConnectExec = exports.withReplicaNameOption = exports.WorkloadCmd = void 0;
const WebSocket = require("ws");
const _ = require("lodash");
const open = require("open");
const options_1 = require("./options");
const resolver_1 = require("./resolver");
const command_1 = require("../cli/command");
const deployment_1 = require("./deployment");
const resultFetcher_1 = require("../rest/resultFetcher");
const functions_1 = require("../util/functions");
const objects_1 = require("../util/objects");
const client_1 = require("../session/client");
const names_1 = require("../util/names");
const time_1 = require("../util/time");
const logger_1 = require("../util/logger");
const workload_1 = require("../util/workload");
const tags_1 = require("./tags");
const links_1 = require("../util/links");
const io_1 = require("../util/io");
const terminal_server_1 = require("../terminal-server/terminal-server");
const helpers_1 = require("../terminal-server/helpers");
const types_1 = require("../terminal-server/types");
const generic_1 = require("./generic");
class WorkloadCmd extends command_1.Command {
    constructor() {
        super(...arguments);
        this.command = 'workload';
        this.aliases = ['w'];
        this.describe = 'Manage workloads within a global virtual cloud';
    }
    builder(yargs) {
        const schema = {
            rels: ['gvc'],
        };
        const resolver = (0, resolver_1.kindResolver)('workload');
        const opts = [options_1.withGvcOptions, options_1.withStandardOptions];
        const commandName = 'workload';
        const commandNamePlural = 'workloads';
        const commandNameA = 'a workload';
        return (yargs
            .demandCommand()
            .version(false)
            .help()
            // generic
            .command(new generic_1.Edit(commandName, resolver, ...opts).toYargs())
            .command(new generic_1.Patch(commandName, resolver, ...opts).toYargs())
            .command(new generic_1.Query(commandNamePlural, resolver, schema, ...opts).toYargs())
            .command(new generic_1.Delete(commandNamePlural, resolver, ...opts).toYargs())
            .command(new generic_1.Eventlog(commandName, resolver, ...opts).toYargs())
            .command(new generic_1.Tag(commandNamePlural, resolver, ...opts).toYargs())
            .command(new generic_1.ListPermissions(commandNameA, resolver, ...opts).toYargs())
            .command(new generic_1.ViewAccessReport(commandName, resolver, ...opts).toYargs())
            .command(new generic_1.Clone(commandName, resolver, ...opts).toYargs())
            .command(new generic_1.Update(commandName, resolver, [
            {
                path: 'description',
            },
            {
                path: 'tags.<key>',
            },
            {
                path: 'spec.identityLink',
                itemLinkResolver: (0, resolver_1.kindResolver)('identity'),
            },
            // Container
            {
                path: 'spec.containers.<name>.image',
            },
            {
                path: 'spec.containers.<name>.workingDir',
            },
            {
                path: 'spec.containers.<name>.metrics.port',
            },
            {
                path: 'spec.containers.<name>.metrics.path',
            },
            {
                path: 'spec.containers.<name>.cpu',
            },
            {
                path: 'spec.containers.<name>.memory',
            },
            {
                path: 'spec.containers.<name>.command',
            },
            {
                path: 'spec.containers.<name>.args',
                array: true,
            },
            // TODO volume
            {
                path: 'spec.containers.<name>.env.<name>.value',
            },
            {
                path: 'spec.containers.<name>.inheritEnv',
                type: 'boolean',
            },
            // Firewall
            {
                path: 'spec.firewallConfig.external.inboundAllowCIDR',
                array: true,
            },
            {
                path: 'spec.firewallConfig.external.outboundAllowHostname',
                array: true,
            },
            {
                path: 'spec.firewallConfig.external.outboundAllowCIDR',
                array: true,
            },
            {
                path: 'spec.firewallConfig.internal.inboundAllowType',
                choices: ['none', 'same-gvc', 'same-org', 'workload-list'],
            },
            {
                path: 'spec.firewallConfig.internal.inboundAllowWorkload',
                itemLinkResolver: (0, resolver_1.kindResolver)('workload'),
                array: true,
            },
            // Default Options
            {
                path: 'spec.defaultOptions.autoscaling.metric',
                choices: ['concurrency', 'cpu', 'rps'],
            },
            {
                path: 'spec.defaultOptions.autoscaling.target',
                type: 'number',
            },
            {
                path: 'spec.defaultOptions.autoscaling.minScale',
                type: 'number',
            },
            {
                path: 'spec.defaultOptions.autoscaling.maxScale',
                type: 'number',
            },
            {
                path: 'spec.defaultOptions.autoscaling.scaleToZeroDelay',
                type: 'number',
            },
            {
                path: 'spec.defaultOptions.autoscaling.maxConcurrency',
                type: 'number',
            },
            {
                path: 'spec.defaultOptions.timeoutSeconds',
                type: 'number',
            },
            {
                path: 'spec.defaultOptions.capacityAI',
                type: 'boolean',
            },
            {
                path: 'spec.defaultOptions.debug',
                type: 'boolean',
            },
            {
                path: 'spec.defaultOptions.suspend',
                type: 'boolean',
            },
            // TODO Local Options
            // Job Spec
            {
                path: 'spec.job.schedule',
            },
            {
                path: 'spec.job.concurrencyPolicy',
                choices: ['Forbid', 'Replace'],
            },
            {
                path: 'spec.job.historyLimit',
                type: 'number',
            },
            {
                path: 'spec.job.restartPolicy',
                choices: ['OnFailure', 'Never'],
            },
            {
                path: 'spec.job.activeDeadlineSeconds',
                type: 'number',
            },
            // Load Balancer
            {
                path: 'spec.loadBalancer.direct.enabled',
                type: 'boolean',
            },
            {
                path: 'spec.loadBalancer.direct.ipSet',
            },
            {
                path: 'spec.loadBalancer.geoLocation.enabled',
                type: 'boolean',
            },
            {
                path: 'spec.loadBalancer.geoLocation.headers.asn',
            },
            {
                path: 'spec.loadBalancer.geoLocation.headers.city',
            },
            {
                path: 'spec.loadBalancer.geoLocation.headers.country',
            },
            {
                path: 'spec.loadBalancer.geoLocation.headers.region',
            },
        ], ...opts).toYargs())
            // specific
            .command(new GetWorkload(resolver, ...opts).toYargs())
            .command(new Open(resolver).toYargs())
            .command(new Create(resolver).toYargs())
            .command(new GetReplicas(resolver).toYargs())
            .command(new ConnectExec(resolver, 'connect').toYargs())
            .command(new ConnectExec(resolver, 'exec').toYargs())
            .command(new ForceRedeployment(resolver).toYargs())
            .command(new StartStop(resolver, 'start').toYargs())
            .command(new StartStop(resolver, 'stop').toYargs())
            .command(new Run(resolver).toYargs())
            .command(new Cron(resolver).toYargs())
            .command(new Replica(resolver).toYargs())
            // deployment related
            .command(new deployment_1.ListDeployments(resolver).toYargs()));
    }
    handle() { }
}
exports.WorkloadCmd = WorkloadCmd;
function withReplicaNameOption(isRequired) {
    return function (yargs) {
        return yargs.options({
            'replica-name': {
                type: 'string',
                requiresArg: true,
                demandOption: isRequired,
                description: 'Name of the replica',
            },
        });
    };
}
exports.withReplicaNameOption = withReplicaNameOption;
class ReplicaCommandBase extends command_1.Command {
    async getReplicas(resolve, args) {
        var _a;
        let result = {
            kind: 'list',
            itemKind: 'replicas',
            items: [],
            links: [],
        };
        if (args.location) {
            args.location = (0, objects_1.toArray)(args.location);
        }
        // Fetch deployments
        let workloadLink = resolve.resourceLink(args.ref, this.session.context);
        const deployments = await this.client.get(`${workloadLink}/deployment`);
        for (const _deployment of deployments.items) {
            const deployment = _deployment;
            // Filter locations
            if (args.location && !args.location.includes(deployment.name)) {
                continue;
            }
            // Extract the deployment endpoint
            let remoteEndpoint = (_a = deployment.status) === null || _a === void 0 ? void 0 : _a.remote;
            // Check if the deployment remote endpoint is defined, and show an error accordingly
            if (!remoteEndpoint) {
                client_1.wire.debug(`WARNING: Location '${deployment.name}' endpoint is unavailable yet. Skipping...`);
                continue;
            }
            // Override remote endpoint with what is specified in the env variable
            if (process.env.TERMINAL_SERVER_URL) {
                remoteEndpoint = process.env.TERMINAL_SERVER_URL;
            }
            const replicaListLink = `${remoteEndpoint}/replicas/org/${this.session.context.org}/gvc/${this.session.context.gvc}/workload/${args.ref}`;
            const replicaList = await this.client.get(replicaListLink);
            result.items.push(...replicaList.items);
            result.links.push(...replicaList.links);
        }
        if (args.replicaName) {
            result.items = result.items.filter((item) => item.name === args.replicaName);
        }
        return result;
    }
}
class GetReplicas extends ReplicaCommandBase {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'get-replicas <ref>';
        this.describe = 'Get the replicas of the referenced workload in a given location';
        this.deprecated = `This subcommand is deprecated, use 'replica get' instead.`;
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        generic_1.withSingleRef, generic_1.withMultipleLocationsOption, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        const replicaList = await this.getReplicas(this.resolve, args);
        this.session.outFormat(replicaList);
    }
}
class Replica extends command_1.Command {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'replica';
        this.describe = 'Manage workload replicas';
    }
    builder(yargs) {
        return (yargs
            .demandCommand()
            .version(false)
            .help()
            // specific
            .command(new GetReplica(this.resolve).toYargs())
            .command(new StopReplica(this.resolve).toYargs()));
    }
    handle() { }
}
class GetReplica extends ReplicaCommandBase {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'get <ref>';
        this.describe = 'Get the replica of the referenced workload in a given location';
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        generic_1.withSingleRef, withReplicaNameOption(false), generic_1.withMultipleLocationsOption, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        const replicaList = await this.getReplicas(this.resolve, args);
        if (this.session.format.max) {
            replicaList.items = replicaList.items.slice(0, this.session.format.max);
        }
        this.session.outFormat(replicaList);
    }
}
class StopReplica extends ReplicaCommandBase {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'stop <ref>';
        this.describe = 'Stop the replica of the referenced workload in a given location';
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        generic_1.withSingleRef, withReplicaNameOption(true), (0, generic_1.withSingleLocationOption)(true), options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        const workloadSelfLink = this.resolve.resourceLink(args.ref, this.session.context);
        const failures = [];
        const accumulator = {
            kind: 'list',
            itemKind: 'stopReplica',
            items: [],
            links: [],
        };
        try {
            const command = {
                type: 'stopReplica',
                spec: {
                    replica: args.replicaName,
                    location: args.location,
                },
            };
            const response = await this.client.axios.post(`${workloadSelfLink}/-command`, command);
            if (response.status === 201) {
                const resource = {
                    workloadName: args.ref,
                    location: args.location,
                    replica: args.replicaName,
                    status: 'Stopping',
                    startTime: new Date(response.headers.date),
                };
                accumulator.items.push(resource);
            }
            else {
                throw new Error(`Unexpected status code ${response.status} while stop the replica ${args.replicaName} in location ${location}`);
            }
        }
        catch (e) {
            failures.push(e);
        }
        await this.session.outFormat(accumulator);
        if (failures.length > 0) {
            this.session.abort({ error: failures });
        }
    }
}
function withTerminalOptions(yargs) {
    return yargs.options({
        replica: {
            requiresArg: true,
            description: 'Replica of the deployment',
        },
        container: {
            requiresArg: true,
            description: 'Container name of the workload',
        },
    });
}
function withConnectOptions(yargs) {
    return yargs.options({
        shell: {
            requiresArg: false,
            description: 'Shell to open on replica',
            default: 'bash',
            alias: 's',
        },
    });
}
function withExecOptions(yargs) {
    return yargs.options({
        '-': {
            requiresArg: true,
            description: 'Command to execute on replica',
        },
        stdin: {
            boolean: true,
            default: false,
            description: 'Pass stdin to the container',
            alias: 'i',
        },
        tty: {
            boolean: true,
            default: false,
            description: 'Stdin is a TTY',
            alias: 't',
        },
        quiet: {
            boolean: true,
            default: false,
            description: 'Only print output from the remote session',
            alias: 'q',
        },
    });
}
class ConnectExec extends command_1.Command {
    constructor(resolve, type) {
        super();
        this.resolve = resolve;
        this.type = type;
        this.command = `${this.type} <ref>`;
        this.describe = this.type === 'connect' ? 'Connect to a replica of the workload' : 'Exec a command on a replica of the workload';
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        generic_1.withSingleRef, (0, generic_1.withSingleLocationOption)(false), withTerminalOptions, this.type === 'connect' ? withConnectOptions : withExecOptions, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        let isInteractiveSession = false;
        let lastMessage = '';
        let pingInterval;
        let terminateTimeout;
        // Read command from args
        const cmd = args['--'];
        // Require command for type exec
        if (this.type === 'exec' && (!cmd || !Array.isArray(cmd) || cmd.length < 1)) {
            this.session.abort({ message: 'ERROR: A command and args are required' });
        }
        // Get terminal configuration
        const terminalServer = new terminal_server_1.TerminalServer(this.session, this.client);
        const terminalConfig = await terminalServer.createConfig(args.ref, args.location, args.replica, args.container);
        // Configure settings depending on command type
        if (this.type === 'connect') {
            terminalConfig.request.shell = args.shell || 'bash';
            terminalConfig.request.stdin = true;
            terminalConfig.request.tty = true;
            terminalConfig.request.quiet = true;
        }
        else if (this.type === 'exec') {
            terminalConfig.request.command = cmd;
            terminalConfig.request.stdin = args.stdin;
            terminalConfig.request.tty = args.tty;
            terminalConfig.request.quiet = args.quiet;
        }
        // If tty is true, then this is definitely an interactive terminal session
        if (terminalConfig.request.tty) {
            isInteractiveSession = true;
            terminalConfig.request.width = process.stdout.columns;
            terminalConfig.request.height = process.stdout.rows;
            // Enable raw mode, which will process each keystroke as it happens
            if (process.stdin.isTTY) {
                process.stdin.setRawMode(true);
            }
        }
        // Initialize a new WebSocket connection
        var client = new WebSocket(terminalConfig.remoteSocket, {
            origin: 'http://localhost',
        });
        // Ensure terminal resize events are handled efficiently and do not trigger
        // too frequently, which could cause performance issues
        const onProcessResize = (0, helpers_1.terminalResizeDebounce)(client);
        // Play ping pong only in an interactive terminal session
        if (isInteractiveSession) {
            pingInterval = setInterval(() => {
                // Ping the terminal server
                client.ping();
                client_1.wire.debug('>>>>>>> WS Ping Sent');
                // Start a timeout, which will terminate the socket connection after PONG_DEADLINE_MS amount of time
                terminateTimeout = setTimeout(() => {
                    client.terminate();
                    client_1.wire.debug('>>>>>>> WS Session Terminated');
                }, types_1.PONG_DEADLINE_MS);
            }, types_1.PING_INTERVAL_MS);
            client.on('pong', () => {
                client_1.wire.debug('<<<<<<< WS Pong Received');
                // Once pong is received, cancel the terminate timeout
                if (terminateTimeout) {
                    clearTimeout(terminateTimeout);
                    terminateTimeout = null;
                }
            });
        }
        client.on('open', () => {
            client_1.wire.debug('<<<<<<< WS Open Event with Token: ' + (0, client_1.convertAuthTokenForDebug)(terminalConfig.request.token));
            // Initialize Request
            client.send(JSON.stringify(terminalConfig.request));
            if (isInteractiveSession) {
                // Subscribe to the resize event
                process.stdout.on('resize', onProcessResize);
                // Subscribe to the data event to send every user input to the remote terminal
                process.stdin.on('data', (chunk) => {
                    const message = {
                        type: 'data',
                        buffer: Array.from(chunk),
                        width: process.stdout.columns,
                        height: process.stdout.rows,
                    };
                    client.send(JSON.stringify(message));
                });
            }
            else {
                // Subscribe to the data event to send standard input to the remote terminal
                process.stdin.on('data', (chunk) => {
                    client.send(chunk, { fin: false, binary: true });
                });
                // Subscribe to the end event to signal end of file
                process.stdin.on('end', () => {
                    client.send(Buffer.from([]), { fin: true });
                });
            }
        });
        client.on('message', (event) => {
            client_1.wire.debug('<<<<<<< WS Message Received');
            const msg = event.toString();
            // Store the last message received
            if (msg.trim()) {
                lastMessage = msg;
            }
            // Echo the remote terminal message to the user
            process.stdout.write(msg);
        });
        client.on('error', (event) => {
            // Stop playing ping pong
            if (pingInterval) {
                clearInterval(pingInterval);
            }
            process.stderr.write('error: ' + event.message);
        });
        client.on('close', () => {
            client_1.wire.debug('<<<<<<< WS Close Event info');
            // Unsubscribe to resize event for when session is interactive
            if (isInteractiveSession) {
                process.stdout.off('resize', onProcessResize);
            }
            // Stop playing ping pong
            if (pingInterval) {
                clearInterval(pingInterval);
            }
            if (lastMessage.includes('Failed to create terminal session: command terminated with exit code')) {
                try {
                    const code = Number(lastMessage.split('exit code ')[1]);
                    process.exit(code);
                }
                catch (e) {
                    process.exit(1);
                }
            }
            if (lastMessage.includes('Failed to create terminal session: Internal error occurred:')) {
                process.exit(1);
            }
            process.exit(0);
        });
    }
}
exports.ConnectExec = ConnectExec;
class Open extends command_1.Command {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'open <ref>';
        this.describe = "Open the referenced workload's endpoint in your browser";
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        generic_1.withSingleRef, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        let link = this.resolve.resourceLink(args.ref, this.session.context);
        const body = await this.client.get(link);
        const endpoint = body.status.endpoint;
        if (endpoint) {
            open(endpoint, {
                wait: false,
            });
        }
    }
}
exports.Open = Open;
class Create extends command_1.Command {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'create';
        this.describe = 'Create a new workload';
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        (yargs) => {
            return yargs.options({
                name: {
                    describe: 'Name of the new workload',
                    requiresArg: true,
                    demandOption: true,
                },
                description: {
                    alias: 'desc',
                    describe: 'Optional description, defaults to the name if not set',
                },
                type: {
                    describe: 'Workload type',
                    default: 'serverless',
                    requiresArg: true,
                    choices: ['serverless', 'standard'],
                },
                image: {
                    describe: 'Name of the container image',
                    requiresArg: true,
                    demandOption: true,
                },
                port: {
                    describe: 'Port to expose',
                    default: 8080,
                    requiresArg: true,
                    number: true,
                },
                env: {
                    describe: 'Environment variables in KEY=VALUE format',
                    requiresArg: true,
                    multiple: true,
                },
                public: {
                    describe: 'Unconstrained ingress & egress for the workload',
                    boolean: true,
                },
                identity: {
                    describe: 'Attach the named identity to the workload spec',
                    requiresArg: true,
                },
                'enable-debug': {
                    describe: 'Enables debug response headers when the headers "x-cpln-debug: true" is in the request.',
                    default: false,
                    boolean: true,
                },
                'inherit-env': {
                    describe: 'Inherits the environment variables set at GVC level.',
                    default: false,
                    boolean: true,
                },
                'container-name': {
                    describe: 'Name of the container item',
                    requiresArg: true,
                },
                cpu: {
                    describe: 'Allocate CPU resources',
                    requiresArg: true,
                    default: '50m',
                },
                memory: {
                    alias: 'mem',
                    describe: 'Allocate Memory',
                    requiresArg: true,
                    default: '128Mi',
                },
                volume: {
                    describe: 'Mount Object Store (S3, GCS, AzureBlob) buckets as file system. E.g. s3://backups@/mnt/storage',
                    requiresArg: true,
                    multiple: true,
                },
            });
        }, generic_1.withTagOptions, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        var _a, _b, _c;
        let link = this.resolve.parentLink(this.session.context);
        let fwc = undefined;
        if (args.public) {
            fwc = {
                external: {
                    inboundAllowCIDR: ['0.0.0.0/0'],
                    outboundAllowCIDR: ['0.0.0.0/0'],
                },
                internal: {
                    inboundAllowType: 'same-org',
                },
            };
        }
        let dfo = undefined;
        if (args.enableDebug) {
            dfo = {
                debug: true,
            };
        }
        const inferredName = args.image.split('/').pop().split('@').shift().split(':').shift().toLowerCase();
        const container = {
            name: args.containerName || inferredName,
            image: args.image,
            env: (0, objects_1.toEnv)((_a = (0, objects_1.toArray)(args.env)) !== null && _a !== void 0 ? _a : []),
            inheritEnv: args.inheritEnv,
            cpu: args.cpu,
            memory: args.memory,
            volumes: (0, objects_1.toVolumes)((_b = (0, objects_1.toArray)(args.volume)) !== null && _b !== void 0 ? _b : []),
            ports: [{ protocol: 'http', number: args.port }],
        };
        const req = {
            name: args.name,
            description: (_c = args.description) !== null && _c !== void 0 ? _c : args.name,
            tags: (0, generic_1.fromTagOptions)(args),
            spec: {
                type: args.type,
                firewallConfig: fwc,
                defaultOptions: dfo,
                containers: [container],
            },
        };
        if (args.identity) {
            req.spec.identityLink = '//identity/' + args.identity;
        }
        const body = await this.client.create(link, req);
        this.session.outFormat(body);
    }
}
exports.Create = Create;
class GetWorkload extends generic_1.Get {
    constructor(resolve, ...funcs) {
        super('workloads', resolve, ...funcs);
        this.describe = 'Retrieve one or more referenced workloads';
    }
    async list(args) {
        let link;
        if (args.allGvcs) {
            link = this.resolve.homeLink(this.session.context);
        }
        else {
            link = this.resolve.parentLink(this.session.context);
        }
        const body = await this.client.get(link);
        await (0, resultFetcher_1.fetchPages)(this.client, this.session.format.max, body);
        for (let item of body.items) {
            item = await this.insertReadyState(item);
        }
        this.session.outFormat(body);
    }
    async get(args) {
        var _a;
        args.ref = _.uniq((0, objects_1.toArray)(args.ref));
        if (((_a = args.ref) === null || _a === void 0 ? void 0 : _a.length) == 1) {
            const link = this.resolve.resourceLink(args.ref[0], this.session.context);
            let body = await this.client.get(link);
            body = await this.insertReadyState(body);
            this.session.outFormat(body);
            return;
        }
        const accumulator = {
            kind: 'list',
            itemKind: this.resolve.kind,
            items: [],
            links: [],
        };
        for (let ref of args.ref) {
            const link = this.resolve.resourceLink(ref, this.session.context);
            let body = await this.client.get(link);
            body = await this.insertReadyState(body);
            accumulator.items.push(body);
        }
        if (accumulator.items.length > 0) {
            this.session.outFormat(accumulator);
        }
    }
    async insertReadyState(body) {
        const deploymentLink = body.links.find((l) => l.rel === 'deployment').href;
        const deploymentRes = await this.client.get(deploymentLink);
        const healthObject = (0, workload_1.getWorkloadHealth)(deploymentRes.items, body);
        body.status = Object.assign({}, body.status, healthObject);
        return body;
    }
    builder(yargs) {
        return (0, functions_1.pipe)(super.builder.bind(this), (yargs) => {
            return yargs.options({
                'all-gvcs': {
                    boolean: true,
                    describe: 'Show workloads from all the global virtual clouds within the current or overridden organization',
                },
            });
        })(yargs);
    }
}
exports.GetWorkload = GetWorkload;
class ForceRedeployment extends command_1.Command {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'force-redeployment <ref...>';
        this.describe = 'Force redeployment of the workload(s)';
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        generic_1.withMultipleRefs, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        var _a, _b, _c;
        args.ref = _.uniq((0, objects_1.toArray)(args.ref));
        let failedResponse;
        const accumulator = {
            kind: 'list',
            itemKind: this.resolve.kind,
            items: [],
            links: [],
        };
        for (let ref of args.ref) {
            try {
                const workloadLink = this.resolve.resourceLink(ref, this.session.context);
                const body = { tags: { 'cpln/deployTimestamp': new Date().toISOString() } };
                const workload = await this.client.patch(workloadLink, body);
                accumulator.items.push(workload);
            }
            catch (e) {
                failedResponse = ((_a = e.response) === null || _a === void 0 ? void 0 : _a.data) || e;
                this.session.err(((_c = (_b = e.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.message) || e.message);
            }
        }
        if (accumulator.items.length > 0) {
            this.session.outFormat(accumulator);
        }
        if (failedResponse) {
            this.session.outFormat(failedResponse, { output: 'json' });
            process.exit(1);
        }
    }
}
exports.ForceRedeployment = ForceRedeployment;
class StartStop extends command_1.Command {
    constructor(resolve, type) {
        super();
        this.resolve = resolve;
        this.type = type;
        this.command = `${this.type} <ref...>`;
        this.describe = `${this.type[0].toUpperCase()}${this.type.substring(1)} the workload(s)`;
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        generic_1.withMultipleRefs, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        var _a, _b, _c;
        args.ref = _.uniq((0, objects_1.toArray)(args.ref));
        let failedResponse;
        const accumulator = {
            kind: 'list',
            itemKind: this.resolve.kind,
            items: [],
            links: [],
        };
        for (let ref of args.ref) {
            try {
                const workloadLink = this.resolve.resourceLink(ref, this.session.context);
                let workload = await this.client.get(workloadLink);
                const body = { spec: { defaultOptions: { suspend: this.type === 'stop' } } };
                if (workload.spec.localOptions && Array.isArray(workload.spec.localOptions) && workload.spec.localOptions.length > 0) {
                    body.spec.localOptions = [];
                    for (const localOption of workload.spec.localOptions) {
                        body.spec.localOptions.push({
                            location: localOption.location,
                            suspend: this.type === 'stop',
                        });
                    }
                }
                workload = await this.client.patch(workloadLink, body);
                accumulator.items.push(workload);
            }
            catch (e) {
                failedResponse = ((_a = e.response) === null || _a === void 0 ? void 0 : _a.data) || e;
                this.session.err(((_c = (_b = e.response) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.message) || e.message);
            }
        }
        if (accumulator.items.length > 0) {
            this.session.outFormat(accumulator);
        }
        if (failedResponse) {
            this.session.outFormat(failedResponse, { output: 'json' });
            process.exit(1);
        }
    }
}
exports.StartStop = StartStop;
function withRunOptions(yargs) {
    return yargs.options({
        clone: {
            describe: 'Clone a workload',
            requiresArg: true,
        },
        tag: {
            description: 'Attach tags (e.g., --tag drink=water)',
            multiple: true,
        },
        image: {
            describe: 'Override image',
            requiresArg: true,
        },
        interactive: {
            describe: 'Make the session interactive',
            boolean: true,
            default: false,
            alias: 'i',
        },
        remove: {
            describe: 'Deletes the workload after the command is run',
            boolean: true,
            default: false,
            alias: 'rm',
        },
        cpu: {
            describe: 'Set allocated CPU for the main container',
            requiresArg: true,
        },
        memory: {
            alias: 'mem',
            describe: 'Set allocated memory for the main container',
            requiresArg: true,
        },
        env: {
            describe: 'Environment variables in KEY=VALUE format',
            requiresArg: true,
            multiple: true,
        },
        command: {
            alias: 'c',
            describe: 'Container command',
            requiresArg: true,
        },
        arg: {
            alias: 'a',
            describe: 'Container args',
            requiresArg: true,
            multiple: true,
        },
        shell: {
            requiresArg: false,
            description: 'Shell to use, only valid when interactive flag is true',
            default: 'bash',
            alias: 's',
        },
        location: {
            describe: 'Location to run the command',
            requiresArg: true,
        },
        container: {
            describe: 'Which container to run the command in, only used when "clone" option is used',
            requiresArg: true,
        },
    });
}
class Run extends command_1.Command {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'run';
        this.describe = 'Run a command with a workload instance';
        this.isWorkloadDeleteRequested = false;
        this.isWorkloadCreated = false;
        this.workloadSelfLink = '';
        this.workloadName = '';
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        withRunOptions, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        var _a, _b;
        let isInteractiveSession = false;
        let sentCommand = false;
        let lastMessage = '';
        let terminateTimeout;
        let pingInterval;
        // Determine where user wants to delet workload after use / error
        this.isWorkloadDeleteRequested = args.remove || false;
        // Resolve GVC link
        const gvcSelfLink = (0, resolver_1.resolveToLink)('gvc', this.session.context.gvc, this.session.context);
        // Validate org and gvc
        if (!this.session.context.org) {
            this.session.abort({ message: 'ERROR: An organization is required. Please, specify one using the --org option.' });
        }
        if (!this.session.context.gvc) {
            this.session.abort({ message: 'ERROR: A GVC is required. Please, specify one using the --gvc option.' });
        }
        // Read command from args
        const cmd = args['--'];
        // Require command for type exec
        if (!cmd || !Array.isArray(cmd) || cmd.length < 1) {
            logger_1.logger.error('Run - Command was not provided');
            this.session.abort({ message: 'ERROR: A command and args are required' });
        }
        try {
            logger_1.logger.info(`Run - GVC Link: ${gvcSelfLink}`);
            // Fetch GVC
            const gvc = await this.client.get(gvcSelfLink);
            // Determine location self link
            let locationSelfLink = '';
            if (args.location) {
                locationSelfLink = (0, resolver_1.resolveToLink)('location', args.location, this.session.context);
            }
            else if (((_b = (_a = gvc.spec) === null || _a === void 0 ? void 0 : _a.staticPlacement) === null || _b === void 0 ? void 0 : _b.locationLinks) && gvc.spec.staticPlacement.locationLinks.length > 0) {
                locationSelfLink = gvc.spec.staticPlacement.locationLinks[0];
            }
            else {
                this.session.abort({ message: 'ERROR: Gvc has no locations' });
            }
            logger_1.logger.info('Run - Location Link: ' + locationSelfLink);
            // Create workload
            const workload = await this.createWorkload(gvcSelfLink, locationSelfLink, args);
            // Delete workload on terminal termination
            process.on('SIGINT', async () => {
                logger_1.logger.info('\nCaught SIGINT');
                await this.tryDeleteWorkload();
                process.exit();
            });
            // Get terminal config
            const terminalConfig = await this.createTerminalConfig(workload, locationSelfLink, args.container);
            // Assumption - I guess we wait a minute here before establishing a WebSocket connection because of some backend issue
            // TODO remove this after backend fix
            await (0, time_1.sleep)(1 * 60 * 1000);
            logger_1.logger.info('Run - Starting Websocket Connection');
            if (args.interactive) {
                terminalConfig.request.shell = args.shell || "sh -c 'bash -i || zsh -i || sh -i'";
                terminalConfig.request.stdin = true;
                terminalConfig.request.tty = true;
                terminalConfig.request.quiet = true;
            }
            else {
                terminalConfig.request.command = cmd;
                terminalConfig.request.stdin = true;
                terminalConfig.request.tty = false;
                terminalConfig.request.quiet = false;
            }
            // If tty is true, then this is definitely an interactive terminal session
            if (terminalConfig.request.tty) {
                isInteractiveSession = true;
                terminalConfig.request.width = process.stdout.columns;
                terminalConfig.request.height = process.stdout.rows;
                // Enable raw mode, which will process each keystroke as it happens
                if (process.stdin.isTTY) {
                    process.stdin.setRawMode(true);
                }
            }
            // Initialize a new WebSocket connection
            client_1.wire.debug('>>>>>>> Creating WS');
            const clientStartTick = new Date().getTime();
            const client = new WebSocket(terminalConfig.remoteSocket, { origin: 'http://localhost' });
            client_1.wire.debug('>>>>>>> WS Created');
            // Ensure terminal resize events are handled efficiently and do not trigger
            // too frequently, which could cause performance issues
            const onProcessResize = (0, helpers_1.terminalResizeDebounce)(client);
            if (isInteractiveSession) {
                pingInterval = setInterval(() => {
                    // Ping the terminal server
                    client_1.wire.debug('>>>>>>> WS Ping Sending');
                    client.ping();
                    client_1.wire.debug('>>>>>>> WS Ping Sent');
                    // Start a timeout, which will terminate the socket connection after PONG_DEADLINE_MS amount of time
                    terminateTimeout = setTimeout(() => {
                        client.terminate();
                    }, types_1.PONG_DEADLINE_MS);
                }, types_1.PING_INTERVAL_MS);
                client.on('pong', () => {
                    client_1.wire.debug('<<<<<<< WS Pong Received');
                    // Once pong is received, cancel the terminate timeout
                    if (terminateTimeout) {
                        clearTimeout(terminateTimeout);
                        terminateTimeout = null;
                    }
                });
            }
            async function initializeClient() {
                client_1.wire.debug(`>>>>>>> Initializing client. Ready state is: ${client.readyState}`);
                // Recursively check for WebSocket connection read state
                if (client.readyState !== 1) {
                    logger_1.logger.info(`RUN - Ready State is: ${client.readyState}`);
                    await (0, time_1.sleep)(5 * 1000);
                    logger_1.logger.info(`RUN - Retrying initialization of client`);
                    initializeClient();
                    return;
                }
                // Initialize Request
                client.send(JSON.stringify(terminalConfig.request));
                if (isInteractiveSession) {
                    // Subscribe to resize event
                    process.stdout.on('resize', onProcessResize);
                    process.stdin.on('data', (chunk) => {
                        const message = {
                            type: 'data',
                            buffer: Array.from(chunk),
                            width: process.stdout.columns,
                            height: process.stdout.rows,
                        };
                        client.send(JSON.stringify(message));
                    });
                }
                else {
                    process.stdin.on('data', (chunk) => {
                        client.send(chunk, { fin: false, binary: true });
                    });
                    process.stdin.on('end', () => {
                        client.send(Buffer.from([]), { fin: true });
                    });
                }
            }
            client.on('open', () => {
                const duration = new Date().getTime() - clientStartTick;
                client_1.wire.debug('<<<<<<< WS Open Event with Token: ' + (0, client_1.convertAuthTokenForDebug)(terminalConfig.request.token));
                client_1.wire.debug('<<<<<<< WS Open Event Duration: ' + duration);
                initializeClient();
            });
            client.on('message', (event) => {
                client_1.wire.debug('<<<<<<< WS Message Received');
                const msg = event.toString();
                // Store the last message received
                if (msg.trim()) {
                    lastMessage = msg;
                }
                // Echo the remote terminal message to the user
                process.stdout.write(msg);
                if (!sentCommand) {
                    sentCommand = true;
                    const _cmd = cmd.join(' ') + '\n';
                    logger_1.logger.info('RUN - Sending initial command: ' + _cmd);
                    if (isInteractiveSession) {
                        const message = {
                            type: 'data',
                            buffer: Array.from(Buffer.from(_cmd)),
                            width: process.stdout.columns,
                            height: process.stdout.rows,
                        };
                        client.send(JSON.stringify(message));
                    }
                    else {
                        client.send(Buffer.from(_cmd));
                    }
                }
            });
            client.on('error', (event) => {
                client_1.wire.debug(`<<<<<<< WS Error. Message: ${event.message}`);
                // Stop playing ping pong
                if (pingInterval) {
                    clearInterval(pingInterval);
                }
                process.stderr.write('error: ' + event.message);
            });
            client.on('close', async () => {
                client_1.wire.debug('<<<<<<< WS Close Event info');
                // Unsubscribe to resize event for connect
                if (isInteractiveSession) {
                    process.stdout.off('resize', onProcessResize);
                }
                // Stop playing ping pong
                if (pingInterval) {
                    clearInterval(pingInterval);
                }
                logger_1.logger.info('Run - Websocket closed');
                // Delete workload after the socket connection has closed
                await this.tryDeleteWorkload();
                if (lastMessage.includes('Failed to create terminal session: Internal error occurred:')) {
                    process.exit(1);
                }
                process.exit(0);
            });
        }
        catch (e) {
            logger_1.logger.info(`Run - Exception. Message: ${e.message}`);
            await this.tryDeleteWorkload();
            this.session.abort({ error: e });
        }
    }
    // ANCHOR - Private Methods
    async createWorkload(gvcSelfLink, locationSelfLink, args) {
        var _a, _b, _c;
        let workload = {};
        if (args.clone) {
            logger_1.logger.info('Run - With Clone: ' + args.clone);
            const workloadLink = this.resolve.resourceLink(args.clone, this.session.context);
            const _workload = await this.client.get(workloadLink);
            delete _workload.links;
            delete _workload.status;
            this.workloadName = (0, names_1.nextName)(`${_workload.name}-`);
            _workload.name = this.workloadName;
            workload = _workload;
        }
        else {
            logger_1.logger.info('Run - Without Clone');
            this.workloadName = (0, names_1.nextName)(`run-`);
            workload = {
                name: this.workloadName,
                spec: getRunWorkloadSpec(),
            };
        }
        // We know for sure that spec and containers are defined
        workload.spec = workload.spec;
        workload.spec.containers = workload.spec.containers;
        if (args.container) {
            const workloadHasContainer = workload.spec.containers.some((c) => c.name === args.container);
            if (!workloadHasContainer) {
                this.session.abort({ message: `Container "${args.container}" does not exist in the workload.` });
            }
        }
        if (args.tag) {
            args.tag = _.uniq((0, objects_1.toArray)(args.tag));
            workload.tags = { ...workload.tags, ...(0, tags_1.expressionToTags)(args.tag) };
        }
        if (workload.spec.defaultOptions) {
            workload.spec.defaultOptions.capacityAI = false;
            workload.spec.defaultOptions.suspend = true;
        }
        else {
            workload.spec.defaultOptions = {
                capacityAI: false,
                suspend: true,
            };
        }
        workload.spec.localOptions = [
            {
                location: locationSelfLink,
                suspend: false,
                capacityAI: false,
                autoscaling: {
                    minScale: 1,
                    maxScale: 1,
                },
            },
        ];
        if (args.image) {
            workload.spec.containers[0].image = args.image;
        }
        else if (!args.clone) {
            // not cloned and not given an image, so it uses ubuntu we need the sleep cmd
            workload.spec.containers[0].command = 'sleep';
            workload.spec.containers[0].args = ['999d'];
        }
        if (args.cpu) {
            workload.spec.containers[0].cpu = args.cpu;
        }
        if (args.memory) {
            workload.spec.containers[0].memory = args.memory;
        }
        if (!workload.spec.containers[0].env) {
            workload.spec.containers[0].env = [];
        }
        workload.spec.containers[0].env.push({ name: 'XCPLN_RUN_BY', value: (_a = this.session.profile.authInfo) === null || _a === void 0 ? void 0 : _a.email });
        const envArr = (0, objects_1.toEnv)((_b = (0, objects_1.toArray)(args.env)) !== null && _b !== void 0 ? _b : []);
        for (let env of envArr) {
            workload.spec.containers[0].env.push(env);
        }
        if (args.command) {
            workload.spec.containers[0].command = args.command;
            workload.spec.containers[0].args = (_c = (0, objects_1.toArray)(args.arg)) !== null && _c !== void 0 ? _c : [];
        }
        logger_1.logger.info('Run - Workload Body: ' + JSON.stringify(workload, null, 2));
        // Create workload
        await this.client.create(gvcSelfLink + '/workload', workload);
        this.isWorkloadCreated = true;
        this.workloadSelfLink = this.resolve.resourceLink(this.workloadName, this.session.context);
        logger_1.logger.info('Run - Workload Link: ' + this.workloadSelfLink);
        // Fetch the created workload
        return await this.client.get(this.workloadSelfLink);
    }
    async createTerminalConfig(workload, locationSelfLink, container) {
        let pod = '';
        let isReady = false;
        let remoteHost = '';
        // Determine location name
        const locationName = locationSelfLink.split('/location/')[1];
        // Determine container name
        let containerName = container || workload.spec.containers[0].name;
        logger_1.logger.info('Run - Container Name: ' + containerName);
        logger_1.logger.info('Workload instance name is: ' + this.workloadName);
        // Wait for the workload to become ready
        await (0, functions_1.retryFn)(async (repeatIndex) => {
            var _a;
            logger_1.logger.info('RUN - Waiting for workload to be ready ' + repeatIndex);
            // Fetch workload deployment at the specific location
            const deployment = await this.client.get(this.workloadSelfLink + '/deployment/' + locationName);
            if (!deployment.status || !deployment.status.remote) {
                throw new Error('ERROR: No deployment is present.');
            }
            const version = deployment.status.versions.find((v) => v.workload === deployment.status.expectedDeploymentVersion);
            remoteHost = deployment.status.remote || '';
            isReady = (_a = version.containers[containerName].ready) !== null && _a !== void 0 ? _a : false;
            if (!isReady) {
                throw new Error('Workload is not ready yet');
            }
        }, {
            repeatTimes: 200,
            onFailure: async () => {
                await this.tryDeleteWorkload();
                this.session.abort({ message: 'After 200 tries, workload is still not ready, please try again. Aborting...' });
            },
        });
        // Find replica
        await (0, functions_1.retryFn)(async (repeatIndex) => {
            const replicaListLink = `${remoteHost}/replicas${this.workloadSelfLink}`;
            // Fetch all workload replias
            const replicaList = await this.client.get(replicaListLink);
            if (replicaList.items.length < 1) {
                logger_1.logger.info(`Run - Replica List is empty, retrying... - Attempt ${repeatIndex}`);
                throw new Error('Replica list is empty');
            }
            else {
                // TODO REQUEST get info from backend about which one is the latest healthy one, if there are multiple replicas
                pod = replicaList.items[0];
            }
        }, {
            repeatTimes: 20,
            onFailure: async () => {
                await this.tryDeleteWorkload();
                this.session.abort({ message: 'No replica is present.' });
            },
        });
        logger_1.logger.info('RUN - Workload is ready');
        // Initialize a new terminal server instance
        const terminalServer = new terminal_server_1.TerminalServer(this.session, this.client);
        return await terminalServer.createConfig(workload.name, locationName, pod, containerName);
    }
    async tryDeleteWorkload() {
        if (this.isWorkloadDeleteRequested && this.isWorkloadCreated) {
            logger_1.logger.info('\nDeleting workload instance...');
            const manualDeleteCommand = `cpln workload delete ${this.workloadName} --org ${this.session.context.org} --gvc ${this.session.context.gvc}`;
            await (0, functions_1.retryFn)(async () => {
                try {
                    await this.client.delete(this.workloadSelfLink);
                }
                catch (e) {
                    // Print error
                    logger_1.logger.info(e);
                    throw e;
                }
            }, {
                repeatTimes: 10,
                intervalSec: 5,
                onSuccess: () => logger_1.logger.info('Deleted workload'),
                onFailure: () => {
                    this.session.out(`Unable to delete workload. Run the following command to delete workload manually: ${manualDeleteCommand}`);
                },
            });
        }
    }
}
exports.Run = Run;
function getRunWorkloadSpec() {
    const container = {
        name: 'main',
        image: 'ubuntu:22.04',
    };
    return {
        type: 'standard',
        containers: [container],
        firewallConfig: {
            external: {
                inboundAllowCIDR: ['0.0.0.0/0'],
                outboundAllowCIDR: ['0.0.0.0/0'],
            },
            internal: {
                inboundAllowType: 'same-org',
            },
        },
        defaultOptions: {
            autoscaling: {
                metric: 'cpu',
                minScale: 1,
                maxScale: 1,
            },
        },
    };
}
class Cron extends command_1.Command {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'cron';
        this.describe = 'Manage cron workloads';
    }
    builder(yargs) {
        return (yargs
            //
            .demandCommand()
            .version(false)
            .help()
            // specific
            .command(new StartCron(this.resolve).toYargs())
            .command(new GetCron(this.resolve).toYargs()));
    }
    handle() { }
}
exports.Cron = Cron;
function withStartCronOptions(yargs) {
    return yargs.options({
        'container-name': {
            type: 'string',
            describe: 'Name of the container',
        },
        env: {
            type: 'string',
            multiple: true,
            describe: 'One or more environment variables in KEY=VALUE format',
        },
        command: {
            type: 'string',
            describe: 'Override the entrypoint',
        },
        arg: {
            type: 'string',
            multiple: true,
            describe: 'One or more custom command line argument that will be sent to the container',
        },
    });
}
class StartCron extends command_1.Command {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'start <ref>';
        this.describe = 'Start a cron job';
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        generic_1.withSingleRef, generic_1.withMultipleLocationsOption, withStartCronOptions, generic_1.withOptionalFileInputOptions, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        var _a, _b, _c, _d;
        // Initialize
        let containerOverrides = [];
        const workloadSelfLink = this.resolve.resourceLink(args.ref, this.session.context);
        const failures = [];
        const accumulator = {
            kind: 'list',
            itemKind: 'workloadCron',
            items: [],
            links: [],
        };
        if (args.containerName || args.env || args.command || args.arg) {
            if (args.file) {
                this.session.abort({
                    message: 'ERROR: You can either provide files containing container overrides or provide container overrides through arguments.',
                });
            }
            let containerName;
            if (args.containerName) {
                containerName = args.containerName;
            }
            else {
                // Fetch workload and get the first container name
                const workload = await this.client.get(workloadSelfLink);
                containerName = workload.spec.containers[0].name;
            }
            const containerOverride = {
                name: containerName,
            };
            if (args.env) {
                containerOverride.env = (0, objects_1.toEnv)((0, objects_1.toArray)(args.env));
            }
            if (args.command) {
                containerOverride.command = args.command;
            }
            if (args.arg) {
                containerOverride.args = (0, objects_1.toArray)(args.arg);
            }
            containerOverrides.push(containerOverride);
        }
        // Determine locations
        if (args.location) {
            args.location = (0, objects_1.toArray)(args.location);
        }
        else {
            // Fetch GVC locations
            const gvcSelfLink = (0, resolver_1.resolveToLink)('gvc', this.session.context.gvc, this.session.context);
            const gvc = await this.client.get(gvcSelfLink);
            // Get location names from GVC
            args.location = (_d = (_c = (_b = (_a = gvc.spec) === null || _a === void 0 ? void 0 : _a.staticPlacement) === null || _b === void 0 ? void 0 : _b.locationLinks) === null || _c === void 0 ? void 0 : _c.map((l) => (0, links_1.getLastPartOfLink)(l))) !== null && _d !== void 0 ? _d : [];
            if (args.location.length === 0) {
                this.session.abort({ message: 'ERROR: GVC has no locations' });
            }
        }
        // Determine container overrides
        const filePaths = (0, io_1.readFileInputsOption)((0, objects_1.toArray)(args.file));
        for (const filePath of filePaths) {
            containerOverrides = [...containerOverrides, ...(0, io_1.loadAllObject)(await (0, io_1.readTextFile)(filePath))];
        }
        // Start executing runCronWorkload command against each location specified
        for (const location of args.location) {
            try {
                const command = {
                    type: 'runCronWorkload',
                    spec: {
                        location,
                        containerOverrides,
                    },
                };
                const response = await this.client.axios.post(`${workloadSelfLink}/-command`, command);
                if (response.status === 201) {
                    const workloadCronResource = {
                        workloadName: args.ref,
                        location,
                        id: (0, links_1.getLastPartOfLink)(response.headers.location),
                        status: 'active',
                        startTime: new Date(response.headers.date),
                    };
                    accumulator.items.push(workloadCronResource);
                }
                else {
                    throw new Error(`Unexpected status code ${response.status} while starting a cron job in location ${location}`);
                }
            }
            catch (e) {
                failures.push(e);
            }
        }
        await this.session.outFormat(accumulator);
        if (failures.length > 0) {
            this.session.abort({ error: failures });
        }
    }
}
exports.StartCron = StartCron;
class GetCron extends command_1.Command {
    constructor(resolve) {
        super();
        this.resolve = resolve;
        this.command = 'get <ref>';
        this.describe = 'Get a list of job executions for the referenced workload';
    }
    builder(yargs) {
        return (0, functions_1.pipe)(
        //
        generic_1.withSingleRef, generic_1.withMultipleLocationsOption, options_1.withAllOptions)(yargs);
    }
    async handle(args) {
        var _a, _b, _c, _d;
        // Initialize
        const ignoreKeywords = ['command-', 'run-for-command-'];
        const workloadSelfLink = this.resolve.resourceLink(args.ref, this.session.context);
        const accumulator = {
            kind: 'list',
            itemKind: 'workloadCron',
            items: [],
            links: [],
        };
        // Fetch data
        const commands = await this.client.get(`${workloadSelfLink}/-command`);
        const deployments = await this.client.get(`${workloadSelfLink}/deployment`);
        // Fetch all items
        await (0, resultFetcher_1.fetchPages)(this.client, this.session.format.max, commands);
        // Fetch all items
        await (0, resultFetcher_1.fetchPages)(this.client, this.session.format.max, deployments);
        // Determine specified locations
        if (args.location) {
            args.location = (0, objects_1.toArray)(args.location);
        }
        for (const _deployment of deployments.items) {
            const deployment = _deployment;
            // Ignore deployments that are not in the specified locations
            if (args.location && !args.location.includes(deployment.name)) {
                continue;
            }
            for (const jobExecution of (_b = (_a = deployment.status) === null || _a === void 0 ? void 0 : _a.jobExecutions) !== null && _b !== void 0 ? _b : []) {
                let skip = false;
                for (const ignoreKeyword of ignoreKeywords) {
                    if (jobExecution.name.startsWith(ignoreKeyword)) {
                        skip = true;
                        break;
                    }
                }
                // Skip job executions that has a name that starts with the ignore keywords
                if (skip) {
                    continue;
                }
                const workloadCronResource = {
                    location: deployment.name,
                    workloadName: args.ref,
                    id: jobExecution.name,
                    status: jobExecution.status,
                    startTime: new Date(jobExecution.startTime),
                    completionTime: jobExecution.completionTime ? new Date(jobExecution.completionTime) : undefined,
                };
                accumulator.items.push(workloadCronResource);
            }
        }
        for (const _command of commands.items) {
            const command = _command;
            if (command.spec && args.location && !args.location.includes(command.spec.location)) {
                continue;
            }
            let status = command.lifecycleStage;
            switch (status) {
                case 'running':
                    status = 'active';
                    break;
                case 'completed':
                    status = 'successful';
                    break;
            }
            const workloadCronResource = {
                location: (_d = (_c = command.spec) === null || _c === void 0 ? void 0 : _c.location) !== null && _d !== void 0 ? _d : '',
                workloadName: args.ref,
                id: command.id,
                status: status,
                startTime: new Date(command.created),
                completionTime: command.lifecycleStage === 'completed' ? new Date(command.lastModified) : undefined,
            };
            accumulator.items.push(workloadCronResource);
        }
        // Sort by location and start time descending
        if (accumulator.items.length > 0) {
            accumulator.items = accumulator.items.sort((a, b) => {
                if (a.location !== b.location) {
                    return a.location.localeCompare(b.location);
                }
                if (a.startTime < b.startTime) {
                    return 1;
                }
                if (a.startTime > b.startTime) {
                    return -1;
                }
                return 0;
            });
        }
        if (this.session.format.max) {
            accumulator.items = accumulator.items.slice(0, this.session.format.max);
        }
        await this.session.outFormat(accumulator);
    }
}
exports.GetCron = GetCron;
//# sourceMappingURL=workload.js.map