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  }