/** * Initialize and set up the transport layer * * @param Instance $instance * * @return $this */ public function connect(Instance $instance) { $this->instance = $instance; $this->token = $this->generateToken([$instance->cluster->cluster_id_text, $instance->instance_id_text]); $this->resourceUri = Uri::segment([$instance->getProvisionedEndpoint(), $instance->getResourceUri()], false); return $this; }
/** * Registers an instance * * @param \DreamFactory\Enterprise\Database\Models\Instance $instance * * @return bool|mixed|\stdClass */ public function registerInstance(Instance $instance) { // Get url and send data if (null === ($_url = config('license.endpoints.instance'))) { \Log::warning('[dfe.license] No "instance" license endpoint configured.'); return false; } return $this->postData($_url, $instance->toArray()); }
/** * Initialize and set up the transport layer * * @param Instance $instance * @param string|null $token The token to use instead of automatic one * @param string|null $header The HTTP header to use instead of DFE one * * @return $this */ public function connect(Instance $instance, $token = null, $header = null) { $this->instance = $instance; // Note trailing slash added... $this->resourceUri = rtrim(Uri::segment([$instance->getProvisionedEndpoint(), $instance->getResourceUri()], false), '/') . '/'; // Set up the channel $this->token = $token ?: $this->generateToken([$instance->cluster->cluster_id_text, $instance->instance_id_text]); $this->headers = [$header ?: EnterpriseDefaults::CONSOLE_X_HEADER . ': ' . $this->token]; return $this; }
/** * Display a listing of the resource. * * @return Response */ protected function _loadData() { $_columns = ['instance_t.id', 'instance_t.instance_id_text', 'cluster_t.cluster_id_text', 'instance_t.create_date', 'user_t.email_addr_text', 'user_t.lmod_date']; /** @type Builder $_query */ $_query = Instance::join('user_t', 'instance_t.user_id', '=', 'user_t.id')->join('cluster_t', 'instance_t.cluster_id', '=', 'cluster_t.id')->select($_columns); return $this->processDataRequest('instance_t', Instance::count(), $_columns, $_query); }
/** * Get the current status of an instance * * @param Instance $instance * * @return array */ public function status($instance) { /** @var Instance $_instance */ if (null === ($_instance = Instance::find($instance->id))) { return ['success' => false, 'error' => ['code' => 404, 'message' => 'Instance not found.']]; } return ['success' => true, 'status' => $_instance->state_nbr, 'status_text' => ProvisionStates::prettyNameOf($_instance->state_nbr)]; }
/** * Tests provision request */ public function testProvision() { $_instanceId = 'bender'; /** @var Instance $_instance */ if (null !== ($_instance = Instance::byNameOrId($_instanceId)->first())) { $_instance->delete(); } $_payload = ['instance-id' => $_instanceId, 'owner-id' => '*****@*****.**', 'guest-location' => GuestLocations::DFE_CLUSTER]; \Artisan::call('dfe:provision', $_payload); }
/** * Tests provision request */ public function testProvision() { $_instanceId = 'dfe-test-case'; $_payload = ['instance-id' => $_instanceId, 'owner-id' => 1, 'guest-location' => GuestLocations::DFE_CLUSTER]; /** @var Instance $_instance */ if (null !== ($_instance = Instance::byNameOrId($_instanceId)->first())) { $_instance->delete(); } $_job = new ProvisionJob($_instanceId, $_payload); $_result = \Queue::push($_job); }
/** * Tests provision request */ public function testImport() { $_instanceId = 'jablan'; $_snapshotId = '20150824141112.jablan'; try { Instance::byNameOrId($_instanceId)->firstOrFail(); throw new \RuntimeException('The instance "' . $_instanceId . '" already exists.'); } catch (ModelNotFoundException $_ex) { // Good } $_payload = ['instance-id' => $_instanceId, 'snapshot' => $_snapshotId, 'owner-id' => 1, '--owner-type' => OwnerTypes::USER, '--snapshot-id' => true]; $_result = \Artisan::call('dfe:import', $_payload); }
/** * @param Instance $instance * @param string $subject * @param array $data * * @return int The number of recipients mailed */ protected function notifyInstanceOwner($instance, $subject, array $data) { try { if (!empty($this->subjectPrefix)) { $subject = $this->subjectPrefix . ' ' . trim(str_replace($this->subjectPrefix, null, $subject)); } $data['dashboard_url'] = config('dfe.dashboard-url'); $data['support_email_address'] = config('dfe.support-email-address'); $_result = \Mail::send('emails.generic', $data, function ($message) use($instance, $subject) { $message->to($instance->user->email_addr_text, $instance->user->first_name_text . ' ' . $instance->user->last_name_text)->subject($subject); }); $this instanceof Lumberjack && $this->debug('notification sent to "' . $instance->user->email_addr_text . '"'); return $_result; } catch (\Exception $_ex) { \Log::error('Error sending notification: ' . $_ex->getMessage()); $_mailPath = storage_path('logs/unsent-mail'); if (!is_dir($_mailPath)) { mkdir($_mailPath, 0777, true); } @file_put_contents(date('YmdHis') . '-' . $instance->user->email_addr_text . '.json', Json::encode(array_merge($data, ['subject' => $subject, 'template' => 'emails.generic', 'instance' => $instance->toArray()]))); return false; } }
/** * Returns an array of the instances assigned to a cluster * * @param int|string $clusterId The cluster ID * * @return array */ public function getInstances($clusterId) { $_cluster = $this->_findCluster($clusterId); $_rows = Instance::byClusterId($_cluster->id)->get(['id', 'instance_name_text']); $_response = []; /** @type Instance $_instance */ foreach ($_rows as $_instance) { $_response[] = ['id' => $_instance->id, 'name' => $_instance->instance_name_text]; } //$this->debug('found ' . count($_response) . ' instance(s)'); usort($_response, function ($a, $b) { return strcasecmp($a['name'], $b['name']); }); return $_response; }
/** * Handle the command * * @return mixed */ public function fire() { parent::fire(); $_instanceId = $this->argument('instance-id'); // Check the name here for quicker response... if (false === ($_instanceName = Instance::isNameAvailable($_instanceId)) || is_numeric($_instanceName[0])) { $this->error('The name of your instance cannot be "' . $_instanceId . '". It is either currently in-use, or otherwise invalid.'); exit(1); } $_ownerType = OwnerTypes::USER; $_ownerId = $this->argument('owner-id'); $_guestLocation = $this->argument('guest-location'); $_owner = $this->_locateOwner($_ownerId, $_ownerType); $this->writeln('Provisioning instance <comment>"' . $_instanceId . '"</comment>.'); return \Queue::push(new ProvisionJob($_instanceId, ['guest-location' => $_guestLocation, 'owner-id' => $_owner->id, 'owner-type' => $_ownerType ?: OwnerTypes::USER, 'cluster-id' => $this->option('cluster-id')])); }
/** * @param string|Instance $instanceId * * @return \DreamFactory\Enterprise\Database\Models\Instance * @throws \DreamFactory\Enterprise\Database\Exceptions\InstanceNotFoundException */ protected function _validateInstance($instanceId) { if ($instanceId instanceof Instance) { return $instanceId; } if ($instanceId instanceof InstanceAware) { return $instanceId->getInstance(); } if (!is_string($instanceId)) { throw new InstanceNotFoundException($instanceId); } try { $instanceId = Instance::sanitizeName($instanceId); return Instance::byNameOrId($instanceId)->firstOrFail(); } catch (\Exception $_ex) { throw new InstanceNotFoundException($instanceId); } }
/** * Migrates all instances known, or in a cluster */ protected function migrateAllInstances() { if (null !== ($_clusterId = $this->option('cluster-id'))) { $_instances = $this->findClusterInstances($_clusterId, ['instance_id_text']); } else { $_instances = Instance::orderBy('instance_id_text')->get(['instance_id_text']); } $_results = []; if (!empty($_instances)) { foreach ($_instances as $_instance) { $_id = $_instance->instance_id_text; try { $_results[$_id] = $this->migrateSingleInstance($_id); $this->info('* <comment>' . $_id . ':</comment> <info>success</info>'); } catch (\Exception $_ex) { $_results[$_id] = ['success' => false, 'output' => $_ex->getMessage(), 'exit_code' => $_ex->getCode()]; $this->info('* <comment>' . $_id . ':</comment> <error>failure</error>'); } } } return $_results; }
/** * Returns a path where you can write instance-specific temporary data * * @param \DreamFactory\Enterprise\Database\Models\Instance $instance * @param string|null $append Optional appendage to path * * @return string * @throws DiskException */ public function getWorkPath(Instance $instance, $append = null) { $this->buildStorageMap($instance->user->storage_id_text); // Try private temp path or default to system temp if (false === ($_workPath = Disk::path([$instance->getPrivatePath(), 'tmp', $append], true))) { $_workPath = Disk::path([sys_get_temp_dir(), 'dfe', $instance->instance_id_text, $append], true); if (!$_workPath) { throw new DiskException('Unable to locate a suitable temporary directory.'); } } return $_workPath; }
/** * Creates an export of a instance * * @param Instance $instance The instance of the exports * @param array $exports Array of files to include in the snapshot * @param Filesystem $destination The destination upon which to place the export. Currently unused * If null, the instance's snapshot storage area is used. * @param int $keepDays The number of days to keep the snapshot * * @return array|boolean The snapshot metadata array or false on failure. */ public function createFromExports(Instance $instance, array $exports, Filesystem $destination = null, $keepDays = EnterpriseDefaults::SNAPSHOT_DAYS_TO_KEEP) { // Build our "mise en place", as it were... $_success = false; $_stamp = date('YmdHis'); // Create the snapshot ID $_snapshotId = $_stamp . '.' . Inflector::neutralize($instance->instance_name_text); $_snapshotName = str_replace('{id}', $_snapshotId, config('snapshot.templates.snapshot-file-name')); // Set up the temp dir $this->setWorkPath($instance->getSnapshotPath()); // Create the snapshot archive and stuff it full of goodies /** @var SnapshotManifest $_manifest */ list($_fsSnapshot, $_manifest, $_routeHash, $_routeLink) = $this->createExportArchive($_snapshotId, $_snapshotName, ['timestamp' => $_stamp, 'guest-location' => $instance->guest_location_nbr, 'instance-id' => $instance->instance_id_text, 'cluster-id' => (int) $instance->cluster_id, 'db-server-id' => (int) $instance->db_server_id, 'web-server-id' => (int) $instance->web_server_id, 'app-server-id' => (int) $instance->app_server_id, 'owner-id' => (int) $instance->user->id, 'owner-email-address' => $instance->user->email_addr_text, 'owner-storage-key' => $instance->user->storage_id_text, 'storage-key' => $instance->storage_id_text], $keepDays); try { $this->addFilesToArchive($exports, $_fsSnapshot); try { // Write our snapshot manifesto $_manifest->write(); // Close up the files /** @noinspection PhpUndefinedMethodInspection */ $this->flushZipArchive($_fsSnapshot); // Look up the hash entry if (null === ($_routeHash = RouteHash::byHash($_routeHash)->first())) { throw new \LogicException('Previously created route hash not found.'); } // Create our snapshot record Snapshot::create(['user_id' => $instance->user_id, 'instance_id' => $instance->id, 'route_hash_id' => $_routeHash->id, 'snapshot_id_text' => $_snapshotId, 'public_ind' => true, 'public_url_text' => $_routeLink, 'expire_date' => $_routeHash->expire_date]); // Copy to $destination if requested if ($destination) { if (false === ($_fd = fopen($this->workPath . $_snapshotName, 'r'))) { throw new FileSystemException('Unable to open export file "' . $this->workPath . $_snapshotName . '".'); } $destination->putStream($_snapshotName, $_fd); fclose($_fd); } // Let the user know... $this->notifyInstanceOwner($instance, 'Export successful', ['firstName' => $instance->user->first_name_text, 'headTitle' => 'Export Complete', 'contentHeader' => 'Your export has completed', 'emailBody' => <<<HTML <p>Your export is complete. It may be downloaded it for up to {$keepDays} days, from the following link:<br/> <br/> <strong><a href="{$_routeLink}" target="_blank">{$_routeLink}</a></strong> </p> HTML ]); $_success = true; } catch (\Exception $_ex) { $this->error('exception building snapshot archive: ' . $_ex->getMessage()); throw $_ex; } } catch (\Exception $_ex) { $this->error('exception during sub-provisioner export call: ' . $_ex->getMessage()); $this->notifyInstanceOwner($instance, 'Export failure', ['firstName' => $instance->user->first_name_text, 'headTitle' => 'Export Failure', 'contentHeader' => 'Your export was not created', 'emailBody' => <<<HTML <p>The export requested did not complete properly. Please make sure your instance is up and running, then try again. If the issue persists, please contact support.</p> HTML ]); $_success = false; } finally { // Cleanup $_fsSnapshot = null; } return $_success ? $_manifest->toArray() : false; }
/** * @return array */ protected function gatherInstanceStatistics() { $_stats = []; $_lastGuestLocation = null; /** @type Instance $_instance */ foreach (Instance::all() as $_instance) { $_stats[$_instance->instance_id_text] = ['uri' => $_instance->getProvisionedEndpoint()]; $_api = InstanceApiClient::connect($_instance); try { if (!empty($_resources = $_api->resources())) { $_list = []; foreach ($_resources as $_resource) { if (property_exists($_resource, 'name')) { try { if (false !== ($_result = $_api->resource($_resource->name))) { $_list[$_resource->name] = count($_result); } } catch (\Exception $_ex) { $_list[$_resource->name] = 'unavailable'; } } } $_stats[$_instance->instance_id_text]['resources'] = $_list; $_stats[$_instance->instance_id_text]['_status'] = ['operational']; } } catch (\Exception $_ex) { // Instance unavailable or not initialized $_stats[$_instance->instance_id_text]['resources'] = []; $_stats[$_instance->instance_id_text]['_status'] = ['unreachable']; } } return $_stats; // The new way //return $this->telemetry->make('instance')->getTelemetry(); }
/** * Generates a unique db-name/user/pass for MySQL for an instance * * @param Instance $instance * * @return array * @throws SchemaExistsException */ protected function generateSchemaCredentials(Instance $instance) { $_tries = 0; $_dbUser = null; $_dbName = $this->generateDatabaseName($instance); $_seed = $_dbName . env('APP_KEY') . $instance->instance_name_text; // Make sure our user name is unique... while (true) { $_baseHash = sha1(microtime(true) . $_seed); $_dbUser = substr('u' . $_baseHash, 0, 16); if (0 == Instance::where('db_user_text', '=', $_dbUser)->count()) { $_sql = 'SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = :schema_name'; // Make sure the database name is unique as well. /** @noinspection PhpUndefinedMethodInspection */ $_names = DB::select($_sql, [':schema_name' => $_dbName]); if (!empty($_names)) { throw new SchemaExistsException('The schema "' . $_dbName . '" already exists.'); } break; } if (++$_tries > 10) { throw new \LogicException('Unable to locate a non-unique database user name after ' . $_tries . ' attempts.'); } // Quick snoozy and we try again usleep(500000); } $_creds = ['database' => $_dbName, 'username' => $_dbUser, 'password' => sha1(microtime(true) . $_seed . $_dbUser . microtime(true))]; return $_creds; }
/** * @param int|string $instanceId * * @return Instance */ protected static function _lookupInstance($instanceId) { return Instance::byNameOrId($instanceId)->firstOrFail(); }
/** * Build the 'paths' section of the metadata * * @param \DreamFactory\Enterprise\Database\Models\Instance $instance * * @return array */ protected static function buildPathMetadata(Instance $instance) { return ['storage-root' => InstanceStorage::getStorageRootPath(), 'storage-path' => $instance->getStoragePath(), 'private-path' => $instance->getPrivatePath(), 'owner-private-path' => $instance->getOwnerPrivatePath(), 'snapshot-path' => $instance->getSnapshotPath(), 'trash-path' => $instance->getTrashPath()]; }
/** * Create a new instance record * * @param string $instanceName * @param array $options Array of options for creation. Options are: * * owner-id The id of the instance owner * cluster-id The cluster that owns this instance * trial If true, the "trial" flagged is set for the instance * * @return Instance * @throws DuplicateInstanceException * @throws ProvisioningException */ public function make($instanceName, $options = []) { try { // Basic checks... if (null === ($_ownerId = array_get($options, 'owner-id'))) { throw new \InvalidArgumentException('No "owner-id" given. Cannot create instance.'); } if (null == ($_ownerType = array_get($options, 'owner-type'))) { $_ownerType = OwnerTypes::USER; } try { $_owner = OwnerTypes::getOwner($_ownerId, $_ownerType); } catch (ModelNotFoundException $_ex) { throw new \InvalidArgumentException('The "owner-id" and/or "owner-type" specified is/are invalid.'); } //$this->debug('owner validated: ' . $_owner->id . ($_owner->admin_ind ? ' (admin)' : ' (non-admin)')); if (false === ($_sanitized = Instance::isNameAvailable($instanceName, $_owner->admin_ind))) { throw new DuplicateInstanceException('The instance name "' . $instanceName . '" is not available.'); } // Get the proper location $_guestLocation = array_get($options, 'guest-location', config('provisioning.default-guest-location')); // Validate the cluster and pull component ids $_clusterId = array_get($options, 'cluster-id', config('provisioning.default-cluster-id')); $_clusterConfig = $this->getServersForCluster($_clusterId); $_ownerId = $_owner->id; $_attributes = ['user_id' => (int) $_ownerId, 'instance_id_text' => $_sanitized, 'instance_name_text' => $_sanitized, 'guest_location_nbr' => $_guestLocation, 'cluster_id' => (int) $_clusterConfig['cluster-id'], 'db_server_id' => (int) $_clusterConfig['db-server-id'], 'app_server_id' => (int) $_clusterConfig['app-server-id'], 'web_server_id' => (int) $_clusterConfig['web-server-id'], 'state_nbr' => ProvisionStates::CREATED]; $_guestAttributes = ['instance_id' => null, 'vendor_id' => $_guestLocation, 'vendor_image_id' => array_get($options, 'vendor-image-id', config('provisioning.default-vendor-image-id')), 'vendor_credentials_id' => array_get($options, 'vendor-credentials-id', config('provisioning.default-vendor-credentials-id'))]; // Write it out return \DB::transaction(function () use($_ownerId, $_attributes, $_guestAttributes) { $_instance = Instance::create($_attributes); //$this->debug('created instance row id#' . $_instance->id); $_guestAttributes['instance_id'] = $_instance->id; $_guest = InstanceGuest::create($_guestAttributes); //$this->debug('created guest row id#' . $_guest->id); if (!$_instance || !$_guest) { throw new \RuntimeException('Instance creation failed'); } return $_instance; }); } catch (\Exception $_ex) { throw new ProvisioningException('Error creating new instance: ' . $_ex->getMessage()); } }
/** * Returns all instances managed by $clusterId * * @param \DreamFactory\Enterprise\Database\Models\Cluster|int $clusterId * @param array $columns The columns to retrieve * * @return \Illuminate\Support\Collection */ protected static function findClusterInstances($clusterId, $columns = ['*']) { return Instance::where('cluster_id', static::findCluster($clusterId)->id)->orderBy('instance_id_text')->get($columns); }
public function index() { return $this->renderView('app.instances', ['instances' => Instance::with(['user', 'cluster'])->get()]); }
/** * @param string $instanceId * @param bool $trial * @param bool $remote If true, create instance on user's account * * @return bool|mixed|\stdClass */ public function provisionInstance($instanceId, $trial = false, $remote = false) { Flasher::forget(); $_provisioner = $this->request->input('_provisioner', GuestLocations::DFE_CLUSTER); // Check the name here for quicker UI response... if (false === ($_instanceName = Instance::isNameAvailable($instanceId)) || is_numeric($_instanceName[0])) { Flasher::setIf('The name of your instance cannot be "' . $instanceId . '". It is either currently in-use, or otherwise invalid.', false); return ErrorPacket::create(null, Response::HTTP_BAD_REQUEST, 'Invalid instance name.'); } if (false === ($_clusterConfig = $this->getClusterConfig())) { Flasher::setIf('The DFE Console is not currently available. Please try your request later.', false); return ErrorPacket::create(null, Response::HTTP_INTERNAL_SERVER_ERROR, 'Cluster server configuration error.'); } $_payload = array_merge(['instance-id' => $_instanceName, 'trial' => $trial, 'remote' => $remote, 'ram-size' => $this->request->input('ram-size'), 'disk-size' => $this->request->input('disk-size'), 'vendor-id' => $this->request->input('vendor-id'), 'vendor-secret' => $this->request->input('vendor-secret'), 'owner-id' => \Auth::id(), 'owner-type' => OwnerTypes::USER, 'guest-location' => $_provisioner], $_clusterConfig); $_result = $this->callConsole('provision', $_payload); if ($_result && is_object($_result) && isset($_result->success)) { if ($_result->success) { Flasher::setIf('Instance provisioning requested successfully.'); } else { if (isset($_result->error)) { $_message = isset($_result->error->message) ? $_result->error->message : 'Unknown error'; } else { $_message = 'Unknown server error'; } Flasher::setIf($_message, false); return ErrorPacket::create(null, Response::HTTP_INTERNAL_SERVER_ERROR, 'Provisioning error.'); } } else { Flasher::setIf('The DFE Console is not currently available. Please try your request later.', false); $this->error('Error calling ops console api: ' . print_r($_result, true)); return ErrorPacket::create(null, Response::HTTP_INTERNAL_SERVER_ERROR, 'Cannot connect to ops console.'); } return SuccessPacket::create($_result); }
/** * @param Instance $instance * * @return array */ protected function getStoredData(Instance $instance) { // Get our database connection... $_db = $instance->instanceConnection($instance); // Get a table list... $_tables = $_db->getDoctrineConnection()->getSchemaManager()->listTables(); $_data = []; foreach ($_tables as $_table) { $_tableName = $_table->getName(); // Get the columns $_columns = []; foreach ($_table->getColumns() as $_column) { $_array = $_column->toArray(); $_array['type'] = (string) $_array['type']; $_columns[] = $_array; } $_data[$_tableName] = ['schema' => $_columns, 'data' => $_db->select('SELECT * FROM ' . $_tableName)]; } return $_data; }