Skip to content

Commit 50db96b

Browse files
authored
YQ-3006 added secrets validation (#3635)
1 parent 79f96db commit 50db96b

File tree

9 files changed

+138
-67
lines changed

9 files changed

+138
-67
lines changed

ydb/core/kqp/executer_actor/kqp_executer_impl.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -1628,7 +1628,7 @@ class TKqpExecuterBase : public TActorBootstrapped<TDerived> {
16281628
}
16291629

16301630
void GetSecretsSnapshot() {
1631-
RegisterDescribeSecretsActor(this->SelfId(), UserToken ? UserToken->GetUserSID() : "", SecretNames, this->ActorContext(), MaximalSecretsSnapshotWaitTime);
1631+
RegisterDescribeSecretsActor(this->SelfId(), UserToken ? UserToken->GetUserSID() : "", SecretNames, this->ActorContext().ActorSystem(), MaximalSecretsSnapshotWaitTime);
16321632
}
16331633

16341634
void GetResourcesSnapshot() {

ydb/core/kqp/federated_query/kqp_federated_query_actors.cpp

+71-15
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,34 @@ class TDescribeSecretsActor: public NActors::TActorBootstrapped<TDescribeSecrets
2424
const bool isFound = snapshot->GetSecretValue(NMetadata::NSecret::TSecretIdOrValue::BuildAsId(secretId), secretValue);
2525
if (!isFound) {
2626
LastResponse = TEvDescribeSecretsResponse::TDescription(Ydb::StatusIds::BAD_REQUEST, { NYql::TIssue("secret with name '" + secretId.GetSecretId() + "' not found") });
27+
if (!SubscribedOnSecrets) {
28+
CompleteAndPassAway(LastResponse);
29+
}
2730
return;
2831
}
2932
secretValues.push_back(secretValue);
3033
}
31-
Promise.SetValue(TEvDescribeSecretsResponse::TDescription(secretValues));
3234

33-
UnsubscribeFromSecrets();
34-
PassAway();
35+
CompleteAndPassAway(TEvDescribeSecretsResponse::TDescription(secretValues));
3536
}
3637

3738
void Handle(NActors::TEvents::TEvWakeup::TPtr&) {
38-
Promise.SetValue(LastResponse);
39+
CompleteAndPassAway(LastResponse);
40+
}
41+
42+
void CompleteAndPassAway(const TEvDescribeSecretsResponse::TDescription& response) {
43+
Promise.SetValue(response);
3944

40-
UnsubscribeFromSecrets();
45+
if (SubscribedOnSecrets) {
46+
this->Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), new NMetadata::NProvider::TEvUnsubscribeExternal(GetSecretsSnapshotParser()));
47+
}
4148
PassAway();
4249
}
4350

4451
NMetadata::NFetcher::ISnapshotsFetcher::TPtr GetSecretsSnapshotParser() {
4552
return std::make_shared<NMetadata::NSecret::TSnapshotsFetcher>();
4653
}
4754

48-
void UnsubscribeFromSecrets() {
49-
this->Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), new NMetadata::NProvider::TEvUnsubscribeExternal(GetSecretsSnapshotParser()));
50-
}
51-
5255
public:
5356
TDescribeSecretsActor(const TString& ownerUserId, const std::vector<TString>& secretIds, NThreading::TPromise<TEvDescribeSecretsResponse::TDescription> promise, TDuration maximalSecretsSnapshotWaitTime)
5457
: SecretIds(CreateSecretIds(ownerUserId, secretIds))
@@ -64,8 +67,13 @@ class TDescribeSecretsActor: public NActors::TActorBootstrapped<TDescribeSecrets
6467
return;
6568
}
6669

67-
this->Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), new NMetadata::NProvider::TEvSubscribeExternal(GetSecretsSnapshotParser()));
68-
this->Schedule(MaximalSecretsSnapshotWaitTime, new NActors::TEvents::TEvWakeup());
70+
if (MaximalSecretsSnapshotWaitTime) {
71+
this->Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), new NMetadata::NProvider::TEvSubscribeExternal(GetSecretsSnapshotParser()));
72+
this->Schedule(MaximalSecretsSnapshotWaitTime, new NActors::TEvents::TEvWakeup());
73+
} else {
74+
this->Send(NMetadata::NProvider::MakeServiceId(SelfId().NodeId()), new NMetadata::NProvider::TEvAskSnapshot(GetSecretsSnapshotParser()));
75+
SubscribedOnSecrets = false;
76+
}
6977
Become(&TDescribeSecretsActor::StateFunc);
7078
}
7179

@@ -83,6 +91,7 @@ class TDescribeSecretsActor: public NActors::TActorBootstrapped<TDescribeSecrets
8391
NThreading::TPromise<TEvDescribeSecretsResponse::TDescription> Promise;
8492
TEvDescribeSecretsResponse::TDescription LastResponse;
8593
TDuration MaximalSecretsSnapshotWaitTime;
94+
bool SubscribedOnSecrets = true;
8695
};
8796

8897
} // anonymous namespace
@@ -91,13 +100,60 @@ IActor* CreateDescribeSecretsActor(const TString& ownerUserId, const std::vector
91100
return new TDescribeSecretsActor(ownerUserId, secretIds, promise, maximalSecretsSnapshotWaitTime);
92101
}
93102

94-
void RegisterDescribeSecretsActor(const NActors::TActorId& replyActorId, const TString& ownerUserId, const std::vector<TString>& secretIds, const TActorContext& actorContext, TDuration maximalSecretsSnapshotWaitTime) {
103+
void RegisterDescribeSecretsActor(const NActors::TActorId& replyActorId, const TString& ownerUserId, const std::vector<TString>& secretIds, NActors::TActorSystem* actorSystem, TDuration maximalSecretsSnapshotWaitTime) {
95104
auto promise = NThreading::NewPromise<TEvDescribeSecretsResponse::TDescription>();
96-
actorContext.Register(CreateDescribeSecretsActor(ownerUserId, secretIds, promise, maximalSecretsSnapshotWaitTime));
105+
actorSystem->Register(CreateDescribeSecretsActor(ownerUserId, secretIds, promise, maximalSecretsSnapshotWaitTime));
97106

98-
promise.GetFuture().Subscribe([actorContext, replyActorId](const NThreading::TFuture<TEvDescribeSecretsResponse::TDescription>& result){
99-
actorContext.Send(replyActorId, new TEvDescribeSecretsResponse(result.GetValue()));
107+
promise.GetFuture().Subscribe([actorSystem, replyActorId](const NThreading::TFuture<TEvDescribeSecretsResponse::TDescription>& result){
108+
actorSystem->Send(replyActorId, new TEvDescribeSecretsResponse(result.GetValue()));
100109
});
101110
}
102111

112+
NThreading::TFuture<TEvDescribeSecretsResponse::TDescription> DescribeExternalDataSourceSecrets(const NKikimrSchemeOp::TAuth& authDescription, const TString& ownerUserId, TActorSystem* actorSystem, TDuration maximalSecretsSnapshotWaitTime) {
113+
switch (authDescription.identity_case()) {
114+
case NKikimrSchemeOp::TAuth::kServiceAccount: {
115+
const TString& saSecretId = authDescription.GetServiceAccount().GetSecretName();
116+
auto promise = NThreading::NewPromise<TEvDescribeSecretsResponse::TDescription>();
117+
actorSystem->Register(CreateDescribeSecretsActor(ownerUserId, {saSecretId}, promise, maximalSecretsSnapshotWaitTime));
118+
return promise.GetFuture();
119+
}
120+
121+
case NKikimrSchemeOp::TAuth::kNone:
122+
return NThreading::MakeFuture(TEvDescribeSecretsResponse::TDescription({}));
123+
124+
case NKikimrSchemeOp::TAuth::kBasic: {
125+
const TString& passwordSecretId = authDescription.GetBasic().GetPasswordSecretName();
126+
auto promise = NThreading::NewPromise<TEvDescribeSecretsResponse::TDescription>();
127+
actorSystem->Register(CreateDescribeSecretsActor(ownerUserId, {passwordSecretId}, promise, maximalSecretsSnapshotWaitTime));
128+
return promise.GetFuture();
129+
}
130+
131+
case NKikimrSchemeOp::TAuth::kMdbBasic: {
132+
const TString& saSecretId = authDescription.GetMdbBasic().GetServiceAccountSecretName();
133+
const TString& passwordSecreId = authDescription.GetMdbBasic().GetPasswordSecretName();
134+
auto promise = NThreading::NewPromise<TEvDescribeSecretsResponse::TDescription>();
135+
actorSystem->Register(CreateDescribeSecretsActor(ownerUserId, {saSecretId, passwordSecreId}, promise, maximalSecretsSnapshotWaitTime));
136+
return promise.GetFuture();
137+
}
138+
139+
case NKikimrSchemeOp::TAuth::kAws: {
140+
const TString& awsAccessKeyIdSecretId = authDescription.GetAws().GetAwsAccessKeyIdSecretName();
141+
const TString& awsAccessKeyKeySecretId = authDescription.GetAws().GetAwsSecretAccessKeySecretName();
142+
auto promise = NThreading::NewPromise<TEvDescribeSecretsResponse::TDescription>();
143+
actorSystem->Register(CreateDescribeSecretsActor(ownerUserId, {awsAccessKeyIdSecretId, awsAccessKeyKeySecretId}, promise, maximalSecretsSnapshotWaitTime));
144+
return promise.GetFuture();
145+
}
146+
147+
case NKikimrSchemeOp::TAuth::kToken: {
148+
const TString& tokenSecretId = authDescription.GetToken().GetTokenSecretName();
149+
auto promise = NThreading::NewPromise<TEvDescribeSecretsResponse::TDescription>();
150+
actorSystem->Register(CreateDescribeSecretsActor(ownerUserId, {tokenSecretId}, promise, maximalSecretsSnapshotWaitTime));
151+
return promise.GetFuture();
152+
}
153+
154+
case NKikimrSchemeOp::TAuth::IDENTITY_NOT_SET:
155+
return NThreading::MakeFuture(TEvDescribeSecretsResponse::TDescription(Ydb::StatusIds::BAD_REQUEST, { NYql::TIssue("identity case is not specified") }));
156+
}
157+
}
158+
103159
} // namespace NKikimr::NKqp
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
#pragma once
22

33
#include <ydb/core/kqp/common/events/script_executions.h>
4+
#include <ydb/core/protos/flat_scheme_op.pb.h>
45

56
#include <ydb/library/actors/core/actor.h>
67

78

89
namespace NKikimr::NKqp {
910

10-
NActors::IActor* CreateDescribeSecretsActor(const TString& ownerUserId, const std::vector<TString>& secretIds, NThreading::TPromise<TEvDescribeSecretsResponse::TDescription> promise, TDuration maximalSecretsSnapshotWaitTime);
11-
void RegisterDescribeSecretsActor(const NActors::TActorId& replyActorId, const TString& ownerUserId, const std::vector<TString>& secretIds, const TActorContext& actorContext, TDuration maximalSecretsSnapshotWaitTime);
11+
IActor* CreateDescribeSecretsActor(const TString& ownerUserId, const std::vector<TString>& secretIds, NThreading::TPromise<TEvDescribeSecretsResponse::TDescription> promise, TDuration maximalSecretsSnapshotWaitTime = TDuration::Zero());
12+
13+
void RegisterDescribeSecretsActor(const TActorId& replyActorId, const TString& ownerUserId, const std::vector<TString>& secretIds, TActorSystem* actorSystem, TDuration maximalSecretsSnapshotWaitTime = TDuration::Zero());
14+
15+
NThreading::TFuture<TEvDescribeSecretsResponse::TDescription> DescribeExternalDataSourceSecrets(const NKikimrSchemeOp::TAuth& authDescription, const TString& ownerUserId, TActorSystem* actorSystem, TDuration maximalSecretsSnapshotWaitTime = TDuration::Zero());
1216

1317
} // namespace NKikimr::NKqp

ydb/core/kqp/federated_query/ya.make

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ PEERDIR(
1515
ydb/library/yql/providers/yt/comp_nodes/dq
1616
ydb/library/yql/providers/yt/gateway/native
1717
ydb/library/yql/providers/yt/lib/yt_download
18+
ydb/services/metadata/secret
1819
)
1920

2021
YQL_LAST_ABI_VERSION()

ydb/core/kqp/finalize_script_service/kqp_finalize_script_actor.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class TScriptFinalizerActor : public TActorBootstrapped<TScriptFinalizerActor> {
7575
}
7676

7777
void FetchSecrets() {
78-
RegisterDescribeSecretsActor(SelfId(), UserToken_, SecretNames_, ActorContext(), MaximalSecretsSnapshotWaitTime_);
78+
RegisterDescribeSecretsActor(SelfId(), UserToken_, SecretNames_, ActorContext().ActorSystem(), MaximalSecretsSnapshotWaitTime_);
7979
}
8080

8181
void Handle(TEvDescribeSecretsResponse::TPtr& ev) {

ydb/core/kqp/gateway/behaviour/external_data_source/manager.cpp

+35-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "manager.h"
33

44
#include <ydb/core/tx/tx_proxy/proxy.h>
5+
#include <ydb/core/kqp/federated_query/kqp_federated_query_actors.h>
56
#include <ydb/core/kqp/gateway/actors/scheme.h>
67
#include <ydb/core/kqp/gateway/utils/scheme_helpers.h>
78
#include <ydb/core/kqp/provider/yql_kikimr_gateway.h>
@@ -143,6 +144,20 @@ NThreading::TFuture<TExternalDataSourceManager::TYqlConclusionStatus> SendScheme
143144
});
144145
}
145146

147+
NThreading::TFuture<TExternalDataSourceManager::TYqlConclusionStatus> ValidateCreateExternalDatasource(const NKikimrSchemeOp::TExternalDataSourceDescription& externaDataSourceDesc, const TExternalDataSourceManager::TInternalModificationContext& context) {
148+
const auto& authDescription = externaDataSourceDesc.GetAuth();
149+
const auto& externalData = context.GetExternalData();
150+
const auto& userToken = externalData.GetUserToken();
151+
auto describeFuture = DescribeExternalDataSourceSecrets(authDescription, userToken ? userToken->GetUserSID() : "", externalData.GetActorSystem());
152+
153+
return describeFuture.Apply([](const NThreading::TFuture<TEvDescribeSecretsResponse::TDescription>& f) mutable {
154+
if (const auto& value = f.GetValue(); value.Status != Ydb::StatusIds::SUCCESS) {
155+
return TExternalDataSourceManager::TYqlConclusionStatus::Fail(NYql::YqlStatusFromYdbStatus(value.Status), value.Issues.ToString());
156+
}
157+
return TExternalDataSourceManager::TYqlConclusionStatus::Success();
158+
});
159+
}
160+
146161
}
147162

148163
NThreading::TFuture<TExternalDataSourceManager::TYqlConclusionStatus> TExternalDataSourceManager::DoModify(const NYql::TObjectSettingsImpl& settings,
@@ -179,7 +194,14 @@ NThreading::TFuture<TExternalDataSourceManager::TYqlConclusionStatus> TExternalD
179194
auto& schemeTx = *ev->Record.MutableTransaction()->MutableModifyScheme();
180195
FillCreateExternalDataSourceCommand(schemeTx, settings, context);
181196

182-
return SendSchemeRequest(ev.Release(), context.GetExternalData().GetActorSystem(), schemeTx.GetFailedOnAlreadyExists(), schemeTx.GetSuccessOnNotExist());
197+
auto validationFuture = ValidateCreateExternalDatasource(schemeTx.GetCreateExternalDataSource(), context);
198+
199+
return validationFuture.Apply([ev = ev.Release(), context, schemeTx](const NThreading::TFuture<TExternalDataSourceManager::TYqlConclusionStatus>& f) {
200+
if (const auto& value = f.GetValue(); value.IsFail()) {
201+
return NThreading::MakeFuture(value);
202+
}
203+
return SendSchemeRequest(ev, context.GetExternalData().GetActorSystem(), schemeTx.GetFailedOnAlreadyExists(), schemeTx.GetSuccessOnNotExist());
204+
});
183205
}
184206

185207
NThreading::TFuture<TExternalDataSourceManager::TYqlConclusionStatus> TExternalDataSourceManager::DropExternalDataSource(const NYql::TDropObjectSettings& settings,
@@ -243,11 +265,15 @@ NThreading::TFuture<NMetadata::NModifications::IOperationsManager::TYqlConclusio
243265
ev->Record.SetUserToken(context.GetUserToken()->GetSerializedToken());
244266
}
245267

268+
auto validationFuture = NThreading::MakeFuture(TExternalDataSourceManager::TYqlConclusionStatus::Success());
246269
auto& schemeTx = *ev->Record.MutableTransaction()->MutableModifyScheme();
247270
switch (schemeOperation.GetOperationCase()) {
248-
case NKqpProto::TKqpSchemeOperation::kCreateExternalDataSource:
249-
schemeTx.CopyFrom(schemeOperation.GetCreateExternalDataSource());
271+
case NKqpProto::TKqpSchemeOperation::kCreateExternalDataSource: {
272+
const auto& createExternalDataSource = schemeOperation.GetCreateExternalDataSource();
273+
validationFuture = ValidateCreateExternalDatasource(createExternalDataSource.GetCreateExternalDataSource(), context);
274+
schemeTx.CopyFrom(createExternalDataSource);
250275
break;
276+
}
251277
case NKqpProto::TKqpSchemeOperation::kAlterExternalDataSource:
252278
schemeTx.CopyFrom(schemeOperation.GetAlterExternalDataSource());
253279
break;
@@ -259,7 +285,12 @@ NThreading::TFuture<NMetadata::NModifications::IOperationsManager::TYqlConclusio
259285
TStringBuilder() << "Execution of prepare operation for EXTERNAL_DATA_SOURCE object: unsupported operation: " << int(schemeOperation.GetOperationCase())));
260286
}
261287

262-
return SendSchemeRequest(ev.Release(), context.GetActorSystem(), schemeTx.GetFailedOnAlreadyExists(), schemeTx.GetSuccessOnNotExist());
288+
return validationFuture.Apply([ev = ev.Release(), context, schemeTx](const NThreading::TFuture<TExternalDataSourceManager::TYqlConclusionStatus>& f) {
289+
if (const auto& value = f.GetValue(); value.IsFail()) {
290+
return NThreading::MakeFuture(value);
291+
}
292+
return SendSchemeRequest(ev, context.GetActorSystem(), schemeTx.GetFailedOnAlreadyExists(), schemeTx.GetSuccessOnNotExist());
293+
});
263294
}
264295

265296
}

ydb/core/kqp/gateway/kqp_metadata_loader.cpp

+1-44
Original file line numberDiff line numberDiff line change
@@ -468,50 +468,7 @@ void UpdateExternalDataSourceSecretsValue(TTableMetadataResult& externalDataSour
468468

469469
NThreading::TFuture<TEvDescribeSecretsResponse::TDescription> LoadExternalDataSourceSecretValues(const NSchemeCache::TSchemeCacheNavigate::TEntry& entry, const TIntrusiveConstPtr<NACLib::TUserToken>& userToken, TDuration maximalSecretsSnapshotWaitTime, TActorSystem* actorSystem) {
470470
const auto& authDescription = entry.ExternalDataSourceInfo->Description.GetAuth();
471-
switch (authDescription.identity_case()) {
472-
case NKikimrSchemeOp::TAuth::kServiceAccount: {
473-
const TString& saSecretId = authDescription.GetServiceAccount().GetSecretName();
474-
auto promise = NewPromise<TEvDescribeSecretsResponse::TDescription>();
475-
actorSystem->Register(CreateDescribeSecretsActor(userToken ? userToken->GetUserSID() : "", {saSecretId}, promise, maximalSecretsSnapshotWaitTime));
476-
return promise.GetFuture();
477-
}
478-
479-
case NKikimrSchemeOp::TAuth::kNone:
480-
return MakeFuture(TEvDescribeSecretsResponse::TDescription({}));
481-
482-
case NKikimrSchemeOp::TAuth::kBasic: {
483-
const TString& passwordSecretId = authDescription.GetBasic().GetPasswordSecretName();
484-
auto promise = NewPromise<TEvDescribeSecretsResponse::TDescription>();
485-
actorSystem->Register(CreateDescribeSecretsActor(userToken ? userToken->GetUserSID() : "", {passwordSecretId}, promise, maximalSecretsSnapshotWaitTime));
486-
return promise.GetFuture();
487-
}
488-
489-
case NKikimrSchemeOp::TAuth::kMdbBasic: {
490-
const TString& saSecretId = authDescription.GetMdbBasic().GetServiceAccountSecretName();
491-
const TString& passwordSecreId = authDescription.GetMdbBasic().GetPasswordSecretName();
492-
auto promise = NewPromise<TEvDescribeSecretsResponse::TDescription>();
493-
actorSystem->Register(CreateDescribeSecretsActor(userToken ? userToken->GetUserSID() : "", {saSecretId, passwordSecreId}, promise, maximalSecretsSnapshotWaitTime));
494-
return promise.GetFuture();
495-
}
496-
497-
case NKikimrSchemeOp::TAuth::kAws: {
498-
const TString& awsAccessKeyIdSecretId = authDescription.GetAws().GetAwsAccessKeyIdSecretName();
499-
const TString& awsAccessKeyKeySecretId = authDescription.GetAws().GetAwsSecretAccessKeySecretName();
500-
auto promise = NewPromise<TEvDescribeSecretsResponse::TDescription>();
501-
actorSystem->Register(CreateDescribeSecretsActor(userToken ? userToken->GetUserSID() : "", {awsAccessKeyIdSecretId, awsAccessKeyKeySecretId}, promise, maximalSecretsSnapshotWaitTime));
502-
return promise.GetFuture();
503-
}
504-
505-
case NKikimrSchemeOp::TAuth::kToken: {
506-
const TString& tokenSecretId = authDescription.GetToken().GetTokenSecretName();
507-
auto promise = NewPromise<TEvDescribeSecretsResponse::TDescription>();
508-
actorSystem->Register(CreateDescribeSecretsActor(userToken ? userToken->GetUserSID() : "", {tokenSecretId}, promise, maximalSecretsSnapshotWaitTime));
509-
return promise.GetFuture();
510-
}
511-
512-
case NKikimrSchemeOp::TAuth::IDENTITY_NOT_SET:
513-
return MakeFuture(TEvDescribeSecretsResponse::TDescription(Ydb::StatusIds::BAD_REQUEST, { NYql::TIssue("identity case is not specified") }));
514-
}
471+
return DescribeExternalDataSourceSecrets(authDescription, userToken ? userToken->GetUserSID() : "", actorSystem, maximalSecretsSnapshotWaitTime);
515472
}
516473

517474
} // anonymous namespace

ydb/core/kqp/provider/yql_kikimr_gateway_ut.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,26 @@ Y_UNIT_TEST_SUITE(KikimrIcGateway) {
527527
UNIT_ASSERT_VALUES_EQUAL(response.Metadata->ExternalSource.Token, secretTokenValue);
528528
UNIT_ASSERT_VALUES_EQUAL(response.Metadata->ExternalSource.Properties.GetProperties().size(), 0);
529529
}
530+
531+
Y_UNIT_TEST(TestSecretsExistingValidation) {
532+
TKikimrRunner kikimr;
533+
kikimr.GetTestServer().GetRuntime()->GetAppData(0).FeatureFlags.SetEnableExternalDataSources(true);
534+
auto db = kikimr.GetTableClient();
535+
auto session = db.CreateSession().GetValueSync().GetSession();
536+
537+
TString secretId = "unexisting_secret_name";
538+
auto query = TStringBuilder() << R"(
539+
CREATE EXTERNAL DATA SOURCE `/Root/ExternalDataSource` WITH (
540+
SOURCE_TYPE="YT",
541+
LOCATION="localhost",
542+
AUTH_METHOD="TOKEN",
543+
TOKEN_SECRET_NAME=")" << secretId << R"("
544+
);)";
545+
546+
auto result = session.ExecuteSchemeQuery(query).GetValueSync();
547+
UNIT_ASSERT_C(result.GetStatus() == NYdb::EStatus::BAD_REQUEST, result.GetIssues().ToString());
548+
UNIT_ASSERT_STRING_CONTAINS(result.GetIssues().ToOneLineString(), TStringBuilder() << "secret with name '" << secretId << "' not found");
549+
}
530550
}
531551

532552
} // namespace NYql

0 commit comments

Comments
 (0)