github.com/hernad/nomad@v1.6.112/ui/app/adapters/variable.js (about) 1 /** 2 * Copyright (c) HashiCorp, Inc. 3 * SPDX-License-Identifier: MPL-2.0 4 */ 5 6 // @ts-check 7 import ApplicationAdapter from './application'; 8 import AdapterError from '@ember-data/adapter/error'; 9 import { pluralize } from 'ember-inflector'; 10 import classic from 'ember-classic-decorator'; 11 import { ConflictError } from '@ember-data/adapter/error'; 12 import DEFAULT_JOB_TEMPLATES from 'nomad-ui/utils/default-job-templates'; 13 import { inject as service } from '@ember/service'; 14 15 @classic 16 export default class VariableAdapter extends ApplicationAdapter { 17 @service store; 18 19 pathForType = () => 'var'; 20 21 // PUT instead of POST on create; 22 // /v1/var instead of /v1/vars on create (urlForFindRecord) 23 createRecord(_store, type, snapshot) { 24 let data = this.serialize(snapshot); 25 let baseUrl = this.buildURL(type.modelName, data.ID); 26 const checkAndSetValue = snapshot?.attr('modifyIndex') || 0; 27 return this.ajax(`${baseUrl}?cas=${checkAndSetValue}`, 'PUT', { data }); 28 } 29 30 /** 31 * Query for job templates, both defaults and variables at the nomad/job-templates path. 32 * @returns {Promise<{variables: Variable[], default: Variable[]}>} 33 */ 34 async getJobTemplates() { 35 await this.populateDefaultJobTemplates(); 36 const jobTemplateVariables = await this.store.query('variable', { 37 prefix: 'nomad/job-templates', 38 namespace: '*', 39 }); 40 41 // Ensure we run a findRecord on each to get its keyValues 42 await Promise.all( 43 jobTemplateVariables.map((t) => this.store.findRecord('variable', t.id)) 44 ); 45 46 const defaultTemplates = this.store 47 .peekAll('variable') 48 .filter((t) => t.isDefaultJobTemplate); 49 50 return { variables: jobTemplateVariables, default: defaultTemplates }; 51 } 52 53 async populateDefaultJobTemplates() { 54 await Promise.all( 55 DEFAULT_JOB_TEMPLATES.map((template) => { 56 if (!this.store.peekRecord('variable', template.id)) { 57 let variableSerializer = this.store.serializerFor('variable'); 58 let normalized = 59 variableSerializer.normalizeDefaultJobTemplate(template); 60 return this.store.createRecord('variable', normalized); 61 } 62 return null; 63 }) 64 ); 65 } 66 67 /** 68 * @typedef Variable 69 * @type {object} 70 */ 71 72 /** 73 * Lookup a job template variable by ID/path. 74 * @param {string} templateID 75 * @returns {Promise<Variable>} 76 */ 77 async getJobTemplate(templateID) { 78 await this.populateDefaultJobTemplates(); 79 const defaultJobs = this.store 80 .peekAll('variable') 81 .filter((template) => template.isDefaultJobTemplate); 82 if (defaultJobs.find((job) => job.id === templateID)) { 83 return defaultJobs.find((job) => job.id === templateID); 84 } else { 85 return this.store.findRecord('variable', templateID); 86 } 87 } 88 89 urlForFindAll(modelName) { 90 let baseUrl = this.buildURL(modelName); 91 return pluralize(baseUrl); 92 } 93 94 urlForQuery(_query, modelName) { 95 let baseUrl = this.buildURL(modelName); 96 return pluralize(baseUrl); 97 } 98 99 urlForFindRecord(identifier, modelName) { 100 let path, 101 namespace = null; 102 103 // TODO: Variables are namespaced. This Adapter should extend the WatchableNamespaceId Adapter. 104 // When that happens, we will need to refactor this to accept JSON tuple like we do for jobs. 105 const delimiter = identifier.lastIndexOf('@'); 106 if (delimiter !== -1) { 107 path = identifier.slice(0, delimiter); 108 namespace = identifier.slice(delimiter + 1); 109 } else { 110 path = identifier; 111 namespace = 'default'; 112 } 113 114 let baseUrl = this.buildURL(modelName, path); 115 return `${baseUrl}?namespace=${namespace}`; 116 } 117 118 urlForUpdateRecord(identifier, modelName, snapshot) { 119 const { id } = _extractIDAndNamespace(identifier, snapshot); 120 let baseUrl = this.buildURL(modelName, id); 121 if (snapshot?.adapterOptions?.overwrite) { 122 return `${baseUrl}`; 123 } else { 124 const checkAndSetValue = snapshot?.attr('modifyIndex') || 0; 125 return `${baseUrl}?cas=${checkAndSetValue}`; 126 } 127 } 128 129 urlForDeleteRecord(identifier, modelName, snapshot) { 130 const { namespace, id } = _extractIDAndNamespace(identifier, snapshot); 131 const baseUrl = this.buildURL(modelName, id); 132 return `${baseUrl}?namespace=${namespace}`; 133 } 134 135 handleResponse(status, _, payload) { 136 if (status === 404) { 137 return new AdapterError([{ detail: payload, status: 404 }]); 138 } 139 if (status === 409) { 140 return new ConflictError([ 141 { detail: _normalizeConflictErrorObject(payload), status: 409 }, 142 ]); 143 } 144 return super.handleResponse(...arguments); 145 } 146 } 147 148 function _extractIDAndNamespace(identifier, snapshot) { 149 const namespace = snapshot?.attr('namespace') || 'default'; 150 const id = snapshot?.attr('path') || identifier; 151 return { 152 namespace, 153 id, 154 }; 155 } 156 157 function _normalizeConflictErrorObject(conflictingVariable) { 158 return { 159 modifyTime: Math.floor(conflictingVariable.ModifyTime / 1000000), 160 items: conflictingVariable.Items, 161 }; 162 }