307 lines
16 KiB
Plaintext
307 lines
16 KiB
Plaintext
<cfcomponent extends="taffy.core.resource" taffy:uri="/instances/{instanceUid}">
|
|
|
|
<cfsilent>
|
|
<cfimport prefix="m" taglib="../lib"/>
|
|
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
|
|
</cfsilent>
|
|
|
|
|
|
<cffunction name="get" hint="Экземпляр сервиса"><!--- *** TODO проверка принадлежности тенанту --->
|
|
<cfargument name="instanceUid" type="string" required=true hint="type:guid"/>
|
|
<cftry>
|
|
<cfset this.helper.validateField(arguments, "instanceUid", "guid")/>
|
|
<cfcatch type="invalidParamValue">
|
|
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
|
|
<cfquery name="local.qInstance" result="local.result">
|
|
select
|
|
<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
|
|
<m:field>e.instance_uid::text as instance_uid</m:field>
|
|
<m:field>e.display_name</m:field>
|
|
<m:field>e.service_id</m:field>
|
|
<m:field>e.descr</m:field>
|
|
<m:field>e.specification_item_id</m:field>
|
|
<m:field formatter=#request.castToBool#>e.is_auxiliary</m:field>
|
|
<m:field>to_char(e.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as instance_config_dt_created</m:field><!--- to_json(e.dt_created)##>>'{}' --->
|
|
<m:field>to_char(e.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as instance_config_dt_updated</m:field>
|
|
<m:field>d.contract_id</m:field>
|
|
<m:field>i.specification_id</m:field>
|
|
<m:field>s.specification</m:field>
|
|
<m:field>i.quantity</m:field>
|
|
<m:field>i.price</m:field><!--- *** не цена, а скидка (Считая ссылку на базовый тариф атрибутом сервиса) --->
|
|
<m:field>v.svc</m:field>
|
|
<m:field>v.code</m:field>
|
|
<m:field>e.updater_id</m:field>
|
|
<m:field>u.login as updater_login</m:field>
|
|
<m:field>u.shortname as updater_shortname</m:field>
|
|
</m:field_set>
|
|
from instance e
|
|
left outer join specification_item i on (e.specification_item_id=i.specification_item_id)
|
|
left outer join specification s on (i.specification_id=s.specification_id)
|
|
left outer join contract d on (s.contract_id=d.contract_id)
|
|
left outer join contragent k on (d.contragent_id=k.contragent_id)
|
|
left outer join svc v on (e.service_id=v.svc_id)
|
|
left outer join usr u on (e.updater_id=u.usr_id)
|
|
where e.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
|
|
AND s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/><!--- access protection --->
|
|
</cfquery> <!--- *** правильнее при попытке доступа к чужому инстансу (НСД) выбрасывать 403, а тут будет 404, но так тоже делают, например, в gitea --->
|
|
|
|
<cfif local.qInstance.recordCount EQ 0>
|
|
<cfreturn representationOf("Instance not found or no permission to view").withStatus(404)/>
|
|
</cfif>
|
|
|
|
<cfquery name="local.qCurrentState" result="local.result">
|
|
select
|
|
<m:field_set titleMapOut="local.currentStateTitleMap" lengthOut="local.fieldCount">
|
|
<m:field title="instance_state_uid">st.instance_state_uid::text as instance_state_uid</m:field>
|
|
<m:field title="v.">st.version</m:field>
|
|
<m:field title="creator_id">st.creator_id</m:field>
|
|
<m:field title="dt_state">to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>
|
|
<m:field formatter=#request.castToBool#>st.is_test</m:field>
|
|
<m:field title="instance_operation_uid">st.instance_operation_uid::text as instance_operation_uid</m:field>
|
|
<m:field title="instance_data" formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field>
|
|
<m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isDeleted')::boolean as is_deleted</m:field>
|
|
</m:field_set>
|
|
from instance_state st
|
|
where st.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstance.instance_uid#" null=#!isValid('guid',local.qInstance.instance_uid)#/>
|
|
order by st.version desc
|
|
limit 1
|
|
</cfquery><!--- берем local.qInstance.instance_uid ради изоляции (access protection) --->
|
|
|
|
|
|
<cfset local.isCreated = (qCurrentState.recordCount GT 0)/>
|
|
<cfset local.isDeleted = (qCurrentState.is_deleted GT 0)/>
|
|
|
|
<!--- <cfdump var=#local.qInstance#/><cfabort/> --->
|
|
|
|
<!--- операция, создавшая текущее состояние --->
|
|
<!--- *** странноватый селект --->
|
|
<cfquery name="local.qOperation" result="local.result">
|
|
select
|
|
<m:field_set titleMapOut="local.operationTitleMap" lengthOut="local.fieldCount">
|
|
<m:field>o.instance_operation_uid::text as instance_operation_uid</m:field>
|
|
<m:field>status.instance_state_uid::text as instance_state_uid</m:field>
|
|
<m:field>status.version as state_version</m:field>
|
|
<m:field>o.operation</m:field>
|
|
<!--- <m:field title="instance_uid">o.instance_uid::text as instance_uid</m:field> --->
|
|
<m:field>to_char(o.dt_submit, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_submit</m:field>
|
|
<m:field>o.submit_result</m:field>
|
|
<m:field>to_char(o.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_start</m:field>
|
|
<m:field>to_char(o.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_finish</m:field>
|
|
<m:field>extract('epoch' from coalesce(o.dt_finish,CURRENT_TIMESTAMP)- o.dt_start) as duration</m:field>
|
|
<m:field>round(extract('epoch' from CURRENT_TIMESTAMP - coalesce(o.dt_finish,CURRENT_TIMESTAMP)),1) as seconds_passed</m:field>
|
|
<m:field formatter=#request.castToBool#>o.is_successful</m:field>
|
|
<m:field title="dt_created">to_char(o.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
|
|
<m:field>o.error_log</m:field>
|
|
<m:field>u.login as updater_login</m:field>
|
|
<m:field>u.shortname as updater_shortname</m:field>
|
|
</m:field_set>
|
|
from instance_operation o
|
|
left outer join usr u on (o.updater_id=u.usr_id)
|
|
left outer join (select instance_operation_uid, max(version) as version from instance_state
|
|
group by instance_operation_uid) sts on (o.instance_operation_uid=sts.instance_operation_uid)
|
|
left outer join instance_state status on (sts.instance_operation_uid=status.instance_operation_uid AND sts.version=status.version)
|
|
where o.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstance.instance_uid#" null=#!isValid('guid',local.qInstance.instance_uid)#/>
|
|
--order by status.version desc
|
|
order by o.dt_created desc
|
|
</cfquery>
|
|
<!--- *** А тут у нас не получится, что только операции с состояниями? Или в неправильном порядке? --->
|
|
<!--- ?Добавить параметры к операции --->
|
|
<!--- Разобраться в отличии текущей от последней. Может, нужна только одна? --->
|
|
|
|
<!--- <d:field title="Операция">(select operation from instance_state st join instance_operation io on (st.instance_operation_uid=io.instance_operation_uid) where st.instance_uid=e.instance_uid order by version desc limit 1) as operation</d:field>
|
|
<!--- текущая операция. Она еще не создала стейта и не завершена --->
|
|
<d:field title="Текущая операция">(select io.operation from instance_operation io where io.instance_uid=e.instance_uid order by dt_submit desc limit 1) as current_operation</d:field> --->
|
|
|
|
<!--- *** Отработать блокировку --->
|
|
<cfquery name="local.qAvailableOperation" result="local.result">
|
|
select
|
|
<m:field_set titleMapOut="local.availableOperationTitleMap" lengthOut="local.fieldCount">
|
|
<m:field>so.svc_operation_id</m:field>
|
|
<m:field>so.operation</m:field>
|
|
<m:field>
|
|
(select instance_operation_uid::text
|
|
from instance_operation io
|
|
where io.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstance.instance_uid#" null=#!isValid('guid',local.qInstance.instance_uid)#/>
|
|
AND io.operation=so.operation AND io.dt_submit IS NULL
|
|
order by io.dt_created desc
|
|
limit 1
|
|
)as instance_operation_uid
|
|
</m:field>
|
|
</m:field_set>
|
|
from svc_operation so
|
|
where so.svc_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#local.qInstance.service_id#" null=#!isValid('integer',local.qInstance.service_id)#/>
|
|
<cfif local.isCreated>
|
|
AND NOT so.operation='create'
|
|
<cfelse>
|
|
AND so.operation='create'
|
|
</cfif>
|
|
<cfif local.isDeleted>
|
|
AND 1=0
|
|
</cfif>
|
|
order by so.svc_operation_id
|
|
</cfquery>
|
|
|
|
<cfset var out=structNew("linked")/>
|
|
<cfset var local.vaultData=structNew("linked")/>
|
|
|
|
<cftry><!--- *** надо бы такие вещи делать асинхронно --->
|
|
<cfset vaultUrl=""/>
|
|
<cfset instanceData="#deserializeJson(qCurrentState.instance_data)#"/>
|
|
<cfif structKeyExists(instanceData,"vaultUrl")>
|
|
<cfset vaultUrl=instanceData.vaultUrl/> <!--- v001 --->
|
|
<cfelseif structKeyExists(instanceData,"vault")>
|
|
<cfset vaultUrl=instanceData.vault.Url/><!--- v002 --->
|
|
</cfif>
|
|
|
|
<cfhttp method="post"
|
|
url="https://vault.adl.nubes.ru:8200/v1/auth/approle/login"
|
|
timeout="3"
|
|
charset="utf-8"
|
|
result="res"
|
|
>
|
|
<cfhttpparam type="BODY" value='{"role_id":"df05e620-f3a3-8c6a-361d-a96e3b0adeac","secret_id":"585fbb48-ef97-b845-d414-b8831a27d5b6"}'/>
|
|
</cfhttp>
|
|
<cfset resData=#deserializeJson(res.filecontent)#/>
|
|
<!--- <cfdump var=#res#/> --->
|
|
<cfset client_token=#resData.auth.client_token#/>
|
|
|
|
<cftry>
|
|
<cfhttp method="get"
|
|
url="#vaultUrl#"
|
|
timeout="3"
|
|
charset="utf-8"
|
|
result="cred"
|
|
>
|
|
<cfhttpparam type="HEADER" name="X-Vault-Token" value="#client_token#">
|
|
</cfhttp>
|
|
|
|
<!--- *** костыль для перехода от формата v001 к v002 --->
|
|
<cfif structKeyExists(instanceData,"vault") AND structKeyExists(instanceData.vault,"fields")>
|
|
<cfset local.tempVaultData = #deserializeJson(cred.filecontent).data.data#/>
|
|
<cfset "local.vaultData" = #structNew("linked")#/>
|
|
<!--- <cfset "out.vaultData" = #local.tempVaultData#/> --->
|
|
<cfloop collection=#instanceData.vault.fields# index="i">
|
|
<cfset var fld=instanceData.vault.fields[i]/>
|
|
<cfset local.vaultData[fld] = local.tempVaultData[fld]/>
|
|
</cfloop>
|
|
<cfelse>
|
|
<cfset "local.vaultData" = #deserializeJson(cred.filecontent).data.data#/>
|
|
</cfif>
|
|
|
|
<cfcatch type="ANY">
|
|
<!--- <cfreturn representationOf(cfcatch).withStatus(500)/> --->
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
<cftry>
|
|
<cfif structKeyExists(instanceData,"subusers")>
|
|
<cfloop collection=#instanceData.subusers# item="i">
|
|
<cfset vaultUrl=#instanceData.subusers[i].vaultUrl#/>
|
|
<cfhttp method="get"
|
|
url="#vaultUrl#"
|
|
timeout="3"
|
|
charset="utf-8"
|
|
result="cred"
|
|
>
|
|
<cfhttpparam type="HEADER" name="X-Vault-Token" value="#client_token#">
|
|
</cfhttp>
|
|
<!--- <cfdump var=#deserializeJson(cred.filecontent)#/> --->
|
|
<cfset "local.vaultData.subusers[i].userName"=#instanceData.subusers[i].userName#/>
|
|
<cfset "local.vaultData.subusers[i].subUserAccess"=#instanceData.subusers[i].subUserAccess#/>
|
|
<cfset "local.vaultData.subusers[i].vaultData"=#deserializeJson(cred.filecontent).data.data#/>
|
|
<!--- #instanceData.subusers[i].userName# (#instanceData.subusers[i].subUserAccess#)
|
|
<cfdump var=#deserializeJson(cred.filecontent).data.data#/> --->
|
|
</cfloop>
|
|
</cfif>
|
|
<cfcatch type="ANY"><!--- <cfreturn representationOf(cfcatch).withStatus(500)/> ---></cfcatch>
|
|
</cftry>
|
|
|
|
<cfcatch type="any">
|
|
<!--- <cfrethrow/> ---><!--- <cfreturn representationOf(cfcatch).withStatus(500)/> --->
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
|
|
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
|
|
|
|
<cfset "out.instance" = this.helper.appendRecord(
|
|
structNew("linked"), "", local.titleMap, local.qInstance, this.helper.snake2camel
|
|
)/>
|
|
<cfset "out.instance.uptime"=(local.qOperation.is_successful GT 0) ? local.qOperation.seconds_passed : 0/>
|
|
<cfset "out.instance.isCreated"=local.isCreated/>
|
|
<cfset "out.instance.isDeleted"=qCurrentState.is_deleted GT 0/>
|
|
<cfset "out.instance.vaultData"=local.vaultData/>
|
|
|
|
<cfset "out.instance.state" = (local.qCurrentState.recordCount GT 0) ? this.helper.appendRecord(structNew("linked"), "", local.currentStateTitleMap, local.qCurrentState, this.helper.snake2camel) : {}/>
|
|
<!--- <cfset "out.instance.operation"=this.helper.appendRecord(structNew("linked"), "", local.operationTitleMap, local.qOperation, this.helper.snake2camel)/> --->
|
|
<cfset "out.instance.operations"=[]/>
|
|
<cfloop query=#local.qOperation#>
|
|
<cfset arrayAppend(out.instance.operations, this.helper.appendRecord(structNew("linked"), "", local.operationTitleMap, local.qOperation, this.helper.snake2camel))/>
|
|
</cfloop>
|
|
|
|
<cfset "out.instance.availableOperations"=[]/>
|
|
<cfloop query=#local.qAvailableOperation#>
|
|
<cfset arrayAppend(out.instance.availableOperations, this.helper.appendRecord(structNew("linked"), "", local.availableOperationTitleMap, local.qAvailableOperation, this.helper.snake2camel))/>
|
|
</cfloop>
|
|
|
|
<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
|
|
<!--- <cfdump var=#local.qCurrentState#/>
|
|
<cfdump var=#out#/>
|
|
<cfabort/> --->
|
|
<cfreturn representationOf(out) />
|
|
</cffunction>
|
|
|
|
|
|
<cffunction name="patch">
|
|
<cfargument name="instanceUid" type="string" required=true hint="type:guid"/>
|
|
<cfargument name="displayName" type="string" required=false hint="type:string"/>
|
|
<cfargument name="descr" type="string" required=false default="" hint="type:varchar (no cleanup here!)"/>
|
|
|
|
<!--- ****** проверка уникальности --->
|
|
<cftry>
|
|
<cfset checkDisplayName(arguments.displayName, arguments.usrId, arguments.instanceUid)/><!--- как идея - офорить как обертку, возвращающую то же значение. Но будет соблазн еще оттримить иначе пофиксить, а это уже избыточная функция и должно усложнить название --->
|
|
<cfset this.helper.keyExistsAndValid(arguments, "instanceUid", "guid")/>
|
|
<cfquery name="local.qSave">
|
|
update instance set
|
|
dt_updated=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
|
|
,updater_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.usrId#" />
|
|
<cfif structKeyExists(arguments,"displayName")>
|
|
,display_name=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.displayName#" />
|
|
</cfif>
|
|
<cfif structKeyExists(arguments,"descr")>
|
|
,descr=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.descr#" />
|
|
</cfif>
|
|
where instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" />;
|
|
--select @@rowcount as cnt;
|
|
</cfquery>
|
|
|
|
<!--- <cfif local.qSave.cnt EQ 0>
|
|
<cfreturn noData().withStatus(404, "Record Not Found") />
|
|
</cfif> --->
|
|
|
|
<cfcatch type="invalidParamValue">
|
|
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
|
|
</cfcatch>
|
|
</cftry>
|
|
|
|
<cfreturn noData().withStatus(204, "No Content") />
|
|
</cffunction>
|
|
|
|
|
|
|
|
<!--- неудобно бегать из одного компонента в соседний при том, что разделение ответственности между ними получается довольно странное
|
|
Решил, что логичнее повесить проверки уникальности и генерацию дефолтов на коллекцию, тем более и связей так меньше --->
|
|
|
|
<cffunction name="checkDisplayName">
|
|
<cfargument name="displayName" required=true/>
|
|
<cfargument name="usrId" type="numeric" required=true/>
|
|
<cfargument name="instanceUid" type="guid" required=true/>
|
|
|
|
<cfset CreateObject("component", "instance_ls").checkDisplayName(arguments.displayName, arguments.usrId, arguments.instanceUid)/>
|
|
</cffunction>
|
|
|
|
</cfcomponent> |