feat(inventory): allow choose from task

This commit is contained in:
Denis Gukov 2024-12-13 23:44:09 +05:00
parent 1705d580f2
commit a94a98fcd3
9 changed files with 110 additions and 389 deletions

View File

@ -199,8 +199,8 @@ type TaskStageType string
const (
TaskStageRepositoryClone TaskStageType = "repository_clone"
TaskStageScriptRun TaskStageType = "script_run"
TaskStageTerraformPlan TaskStageType = "terraform_plan"
TaskStageTerraformApply TaskStageType = "terraform_apply"
)
type TaskStage struct {

View File

@ -603,18 +603,17 @@ func (t *LocalJob) checkoutRepository() error {
// store commit to TaskRunner table
//commitHash, err := repo.GetLastCommitHash()
//
//if err != nil {
// return err
//}
//
//commitMessage, _ := repo.GetLastCommitMessage()
//
//t.task.CommitHash = &commitHash
//t.task.CommitMessage = commitMessage
//
//return t.pool.store.UpdateTask(t.task)
commitHash, err := repo.GetLastCommitHash()
if err != nil {
return err
}
commitMessage, _ := repo.GetLastCommitMessage()
t.Log("Commit hash: " + commitHash)
t.Log("Commit message: " + commitMessage)
return nil
}

View File

@ -21,7 +21,6 @@
:need-reset="needReset"
:source-item-id="sourceItemId"
:app="itemApp"
:fields="fields"
/>
</template>
</EditDialog>
@ -32,52 +31,6 @@ import TemplateForm from './TemplateForm.vue';
import EditDialog from './EditDialog.vue';
import AppsMixin from './AppsMixin';
const ANSIBLE_FIELDS = {
playbook: {
label: 'playbookFilename',
},
inventory: {
label: 'inventory2',
},
repository: {
label: 'repository',
},
environment: {
label: 'environment3',
},
vault: {
label: 'vaultPassword2',
},
};
const TERRAFORM_FIELDS = {
...ANSIBLE_FIELDS,
playbook: {
label: 'Subdirectory path (Optional)',
optional: true,
},
inventory: {
label: 'Workspace (Optional)',
},
vault: undefined,
};
const UNKNOWN_APP_FIELDS = {
...ANSIBLE_FIELDS,
playbook: {
label: 'Script Filename *',
},
inventory: undefined,
vault: undefined,
};
const APP_FIELDS = {
'': ANSIBLE_FIELDS,
ansible: ANSIBLE_FIELDS,
terraform: TERRAFORM_FIELDS,
tofu: TERRAFORM_FIELDS,
};
export default {
components: {
TemplateForm,
@ -100,12 +53,6 @@ export default {
};
},
computed: {
fields() {
return APP_FIELDS[this.itemApp] || UNKNOWN_APP_FIELDS;
},
},
watch: {
async dialog(val) {
this.$emit('input', val);

View File

@ -15,17 +15,6 @@
<template v-slot:form="{ onSave, onError, needSave, needReset }">
<TaskForm
v-if="['terraform', 'tofu'].includes(templateApp)"
:project-id="projectId"
item-id="new"
:template-id="templateId"
@save="onSave"
@error="onError"
:need-save="needSave"
:need-reset="needReset"
/>
<TaskForm
v-else
:project-id="projectId"
item-id="new"
:template-id="templateId"
@ -33,15 +22,16 @@
@error="onError"
:need-save="needSave"
:need-reset="needReset"
:source-task="sourceTask"
/>
</template>
</EditDialog>
</template>
<script>
import { TEMPLATE_TYPE_ACTION_TITLES, TEMPLATE_TYPE_ICONS } from '@/lib/constants';
import TaskForm from './TaskForm.vue';
import EditDialog from './EditDialog.vue';
import { TEMPLATE_TYPE_ACTION_TITLES, TEMPLATE_TYPE_ICONS } from '../lib/constants';
import EventBus from '../event-bus';
export default {
@ -56,6 +46,7 @@ export default {
templateType: String,
templateAlias: String,
templateApp: String,
sourceTask: Object,
},
data() {
return {

View File

@ -17,7 +17,7 @@
dark
icon="mdi-source-fork"
dismissible
v-model="commitAvailable"
v-model="hasCommit"
prominent
>
<div
@ -88,6 +88,19 @@
/>
</div>
<!-- <v-select-->
<!-- v-model="item.inventory_id"-->
<!-- :label="fieldLabel('inventory')"-->
<!-- :items="appInventory"-->
<!-- item-value="id"-->
<!-- item-text="name"-->
<!-- outlined-->
<!-- dense-->
<!-- required-->
<!-- :disabled="formSaving"-->
<!-- v-if="needField('inventory')"-->
<!-- ></v-select>-->
<TaskParamsForm v-if="template.app === 'ansible'" v-model="item.params" :app="template.app" />
<TaskParamsForm v-else v-model="item.params" :app="template.app" />
@ -105,13 +118,9 @@
import ItemFormBase from '@/components/ItemFormBase';
import axios from 'axios';
// import { codemirror } from 'vue-codemirror';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/vue/vue.js';
import 'codemirror/addon/lint/json-lint.js';
import 'codemirror/addon/display/placeholder.js';
import TaskParamsForm from '@/components/TaskParamsForm.vue';
import ArgsPicker from '@/components/ArgsPicker.vue';
import { APP_INVENTORY_TYPES } from '@/lib/constants';
export default {
mixins: [ItemFormBase],
@ -122,13 +131,12 @@ export default {
components: {
ArgsPicker,
TaskParamsForm,
// codemirror,
},
data() {
return {
template: null,
buildTasks: null,
commitAvailable: null,
hasCommit: null,
editedEnvironment: null,
editedSecretEnvironment: null,
cmOptions: {
@ -139,13 +147,17 @@ export default {
lint: true,
indentWithTabs: false,
},
// advancedOptions: false,
inventory: null,
};
},
computed: {
args() {
return JSON.parse(this.item.arguments || '[]');
},
appInventory() {
return this.inventory.filter((i) => (APP_INVENTORY_TYPES[this.app] || []).includes(i.type));
},
},
watch: {
@ -163,7 +175,7 @@ export default {
this.assignItem(val);
},
commitAvailable(val) {
hasCommit(val) {
if (val == null) {
this.commit_hash = null;
}
@ -202,7 +214,7 @@ export default {
this.editedEnvironment = JSON.parse(v.environment || '{}');
this.editedSecretEnvironment = JSON.parse(v.secret || '{}');
this.commitAvailable = v.commit_hash != null;
this.hasCommit = v.commit_hash != null;
},
isLoaded() {
@ -225,8 +237,6 @@ export default {
this.item.params = {};
}
// this.advancedOptions = this.item.arguments != null;
this.template = (await axios({
keys: 'get',
url: `/api/project/${this.projectId}/templates/${this.templateId}`,
@ -239,6 +249,12 @@ export default {
responseType: 'json',
})).data.filter((task) => task.status === 'success') : [];
this.inventory = (await axios({
keys: 'get',
url: `/api/project/${this.projectId}/inventory`,
responseType: 'json',
})).data;
if (this.item.build_task_id == null
&& this.buildTasks.length > 0
&& this.buildTasks.length > 0) {

View File

@ -1,42 +1,15 @@
<template xmlns:v-slot="http://www.w3.org/1999/XSL/Transform">
<div v-if="tasks != null">
<EditDialog
v-model="newTaskDialog"
:save-button-text="$t('Re' + getActionButtonTitle())"
@save="onTaskCreated"
>
<template v-slot:title={}>
<v-icon class="mr-4">{{ TEMPLATE_TYPE_ICONS[template.type] }}</v-icon>
<span class="breadcrumbs__item">{{ template.name }}</span>
<v-icon>mdi-chevron-right</v-icon>
<span class="breadcrumbs__item">{{ $t('newTask') }}</span>
</template>
<template v-slot:form="{ onSave, onError, needSave, needReset }">
<TerraformTaskForm
v-if="['terraform', 'tofu'].includes(template.app)"
:project-id="template.project_id"
item-id="new"
:template-id="template.id"
@save="onSave"
@error="onError"
:need-save="needSave"
:need-reset="needReset"
:source-task="sourceTask"
/>
<TaskForm
v-else
:project-id="template.project_id"
item-id="new"
:template-id="template.id"
@save="onSave"
@error="onError"
:need-save="needSave"
:need-reset="needReset"
:source-task="sourceTask"
/>
</template>
</EditDialog>
<NewTaskDialog
v-model="newTaskDialog"
:project-id="template.project_id"
:template-id="template.id"
:template-alias="template.name"
:template-type="template.type"
:template-app="template.app"
:source-task="sourceTask"
/>
<v-data-table
:headers="headers"
@ -89,20 +62,15 @@
</template>
<script>
import axios from 'axios';
import EventBus from '@/event-bus';
import TaskForm from '@/components/TaskForm.vue';
import TaskStatus from '@/components/TaskStatus.vue';
import TaskLink from '@/components/TaskLink.vue';
import EditDialog from '@/components/EditDialog.vue';
import { TEMPLATE_TYPE_ACTION_TITLES, TEMPLATE_TYPE_ICONS } from '@/lib/constants';
import TerraformTaskForm from '@/components/TerraformTaskForm.vue';
import NewTaskDialog from '@/components/NewTaskDialog.vue';
export default {
components: {
TerraformTaskForm,
EditDialog,
NewTaskDialog,
TaskStatus,
TaskForm,
TaskLink,
},
props: {
@ -174,16 +142,11 @@ export default {
responseType: 'json',
})).data;
},
getActionButtonTitle() {
return this.$i18n.t(`Re${TEMPLATE_TYPE_ACTION_TITLES[this.template.type]}`);
},
onTaskCreated(e) {
EventBus.$emit('i-show-task', {
taskId: e.item.id,
});
},
createTask(task) {
this.sourceTask = task;
this.newTaskDialog = true;

View File

@ -312,7 +312,9 @@ import {
TEMPLATE_TYPE_ICONS,
TEMPLATE_TYPE_TITLES,
APP_INVENTORY_TYPES,
} from '../lib/constants';
APP_FIELDS,
UNKNOWN_APP_FIELDS,
} from '@/lib/constants';
import SurveyVars from './SurveyVars';
export default {
@ -326,7 +328,7 @@ export default {
props: {
sourceItemId: Number,
fields: Object,
// fields: Object,
app: String,
},
@ -397,6 +399,10 @@ export default {
},
computed: {
fields() {
return APP_FIELDS[this.app] || UNKNOWN_APP_FIELDS;
},
isLoaded() {
if (this.isNew && this.sourceItemId == null) {
return true;

View File

@ -1,247 +0,0 @@
<template>
<v-form
ref="form"
lazy-validation
v-model="formValid"
v-if="isLoaded()"
>
<v-alert
:value="formError"
color="error"
class="pb-2"
>{{ formError }}
</v-alert>
<v-alert
color="blue"
dark
icon="mdi-source-fork"
dismissible
v-model="commitAvailable"
prominent
>
<div
style="font-weight: bold;"
>{{ (item.commit_hash || '').substr(0, 10) }}
</div>
<div v-if="sourceTask && sourceTask.commit_message">{{ sourceTask.commit_message }}</div>
</v-alert>
<v-select
v-if="template.type === 'deploy'"
v-model="item.build_task_id"
:label="$t('buildVersion')"
:items="buildTasks"
item-value="id"
:item-text="(itm) => getTaskMessage(itm)"
:rules="[v => !!v || $t('build_version_required')]"
required
:disabled="formSaving"
/>
<v-text-field
v-model="item.message"
:label="$t('messageOptional')"
:disabled="formSaving"
/>
<v-text-field
v-for="(v) in template.survey_vars || []"
:key="v.name"
:label="v.title"
:hint="v.description"
v-model="editedEnvironment[v.name]"
:required="v.required"
:rules="[
val => !v.required || !!val || v.title + $t('isRequired'),
val => !val || v.type !== 'int' || /^\d+$/.test(val) ||
v.title + ' ' + $t('mustBeInteger'),
]"
/>
<v-row no-gutters class="mt-6">
<v-col cols="12" sm="6">
<v-checkbox class="mt-0" v-model="item.dry_run">
<template v-slot:label>
<div class="text-no-wrap">Plan</div>
</template>
</v-checkbox>
</v-col>
</v-row>
<div class="mt-4" v-if="!advancedOptions">
<a @click="advancedOptions = true">
{{ $t('advanced') }}
<v-icon style="transform: translateY(-1px)">mdi-chevron-right</v-icon>
</a>
</div>
<div class="mt-4" v-else>
<a @click="advancedOptions = false">
{{ $t('hide') }}
<v-icon style="transform: translateY(-1px)">mdi-chevron-up</v-icon>
</a>
</div>
<v-alert
v-if="advancedOptions && !template.allow_override_args_in_task"
color="info"
dense
text
class="mb-2"
>
{{ $t('pleaseAllowOverridingCliArgumentInTaskTemplateSett') }}<br>
<div style="position: relative; margin-top: 10px;">
<video
autoplay
muted
style="width: 100%; border-radius: 4px;"
>
<source
src="/allow-override-cli-args-in-task.mp4"
type="video/mp4"/>
</video>
</div>
</v-alert>
<codemirror
class="mt-4"
v-if="advancedOptions && template.allow_override_args_in_task"
:style="{ border: '1px solid lightgray' }"
v-model="item.arguments"
:options="cmOptions"
:placeholder="$t('cliArgsJsonArrayExampleIMyinventoryshPrivatekeythe')"
/>
</v-form>
</template>
<script>
/* eslint-disable import/no-extraneous-dependencies,import/extensions */
import ItemFormBase from '@/components/ItemFormBase';
import axios from 'axios';
import { codemirror } from 'vue-codemirror';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/vue/vue.js';
import 'codemirror/addon/lint/json-lint.js';
import 'codemirror/addon/display/placeholder.js';
export default {
mixins: [ItemFormBase],
props: {
templateId: Number,
sourceTask: Object,
},
components: {
codemirror,
},
data() {
return {
template: null,
buildTasks: null,
commitAvailable: null,
editedEnvironment: null,
cmOptions: {
tabSize: 2,
mode: 'application/json',
lineNumbers: true,
line: true,
lint: true,
indentWithTabs: false,
},
advancedOptions: false,
};
},
watch: {
needReset(val) {
if (val) {
this.item.template_id = this.templateId;
}
},
templateId(val) {
this.item.template_id = val;
},
sourceTask(val) {
this.assignItem(val);
},
commitAvailable(val) {
if (val == null) {
this.commit_hash = null;
}
},
},
methods: {
getTaskMessage(task) {
let buildTask = task;
while (buildTask.version == null && buildTask.build_task != null) {
buildTask = buildTask.build_task;
}
if (!buildTask) {
return '';
}
return buildTask.version + (buildTask.message ? `${buildTask.message}` : '');
},
assignItem(val) {
const v = val || {};
if (this.item == null) {
this.item = {};
}
Object.keys(v).forEach((field) => {
this.item[field] = v[field];
});
this.editedEnvironment = JSON.parse(v.environment || '{}');
this.commitAvailable = v.commit_hash != null;
},
isLoaded() {
return this.item != null
&& this.template != null
&& this.buildTasks != null;
},
beforeSave() {
this.item.environment = JSON.stringify(this.editedEnvironment);
},
async afterLoadData() {
this.assignItem(this.sourceTask);
this.item.template_id = this.templateId;
this.advancedOptions = this.item.arguments != null;
this.template = (await axios({
keys: 'get',
url: `/api/project/${this.projectId}/templates/${this.templateId}`,
responseType: 'json',
})).data;
this.buildTasks = this.template.type === 'deploy' ? (await axios({
keys: 'get',
url: `/api/project/${this.projectId}/templates/${this.template.build_template_id}/tasks?status=success`,
responseType: 'json',
})).data.filter((task) => task.status === 'success') : [];
if (this.item.build_task_id == null
&& this.buildTasks.length > 0
&& this.buildTasks.length > 0) {
this.item.build_task_id = this.buildTasks[0].id;
}
},
getItemsUrl() {
return `/api/project/${this.projectId}/tasks`;
},
},
};
</script>

View File

@ -130,3 +130,49 @@ export const APP_INVENTORY_TYPES = {
};
export const DEFAULT_APPS = Object.keys(APP_ICONS);
export const ANSIBLE_FIELDS = {
playbook: {
label: 'playbookFilename',
},
inventory: {
label: 'inventory2',
},
repository: {
label: 'repository',
},
environment: {
label: 'environment3',
},
vault: {
label: 'vaultPassword2',
},
};
export const TERRAFORM_FIELDS = {
...ANSIBLE_FIELDS,
playbook: {
label: 'Subdirectory path (Optional)',
optional: true,
},
inventory: {
label: 'Workspace (Optional)',
},
vault: undefined,
};
export const UNKNOWN_APP_FIELDS = {
...ANSIBLE_FIELDS,
playbook: {
label: 'Script Filename *',
},
inventory: undefined,
vault: undefined,
};
export const APP_FIELDS = {
'': ANSIBLE_FIELDS,
ansible: ANSIBLE_FIELDS,
terraform: TERRAFORM_FIELDS,
tofu: TERRAFORM_FIELDS,
};