blob: 54364ea0e3cb790d428d67f11c88b11547f22454 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::{ConfigError, ConfigurationStore, Result, TransactionMode, catalog, vault};
use maka_agent_facts::configuration::*;
impl ConfigurationStore {
pub async fn update_connection(
&self,
input: UpdateCatalogConnectionInput,
) -> Result<UpdateCatalogConnectionResult> {
validation::basis(&input.expected).map_err(ConfigError::Invalid)?;
self.transaction(TransactionMode::Immediate, move |tx| {
Box::pin(async move {
let current = catalog::read(tx).await?;
let Some(mut row) = catalog::find(tx, &input.expected.connection_id).await? else {
return Ok(CatalogMutationResult::ConnectionStale {
expected: input.expected,
actual: None,
});
};
if row.revision != input.expected.revision {
return Ok(CatalogMutationResult::ConnectionStale {
expected: input.expected,
actual: Some(catalog::basis(&row)),
});
}
let mut changes = input.changes;
validation::normalize_update(&mut changes).map_err(ConfigError::Invalid)?;
let configuration_changed = changes
.configuration
.as_ref()
.is_some_and(|value| row.configuration != *value);
let overlay_changed = match &changes.request_body_overlay {
Patch::Keep => false,
Patch::Clear => row.request_body_overlay.is_some(),
Patch::Set(value) => row.request_body_overlay.as_ref() != Some(value),
};
let previous = row.clone();
let validate_limits = matches!(changes.model_overrides, Patch::Set(_));
let mut test_changed = configuration_changed
|| changes.enabled.is_some_and(|value| row.enabled != value)
|| changes
.enabled_model_ids
.as_ref()
.is_some_and(|value| row.enabled_model_ids != *value)
|| overlay_changed;
row.revision = catalog::next_revision(row.revision)?;
if let Some(value) = changes.name {
row.name = value;
}
if let Some(value) = changes.configuration {
row.configuration = value;
}
if let Some(value) = changes.enabled {
row.enabled = value;
}
if let Some(value) = changes.enabled_model_ids {
row.enabled_model_ids = value;
}
row.model_overrides = match changes.model_overrides {
Patch::Set(profiles) => Some(profiles),
Patch::Clear => None,
Patch::Keep if configuration_changed => None,
Patch::Keep => row.model_overrides,
};
row.request_body_overlay = match changes.request_body_overlay {
Patch::Keep => row.request_body_overlay,
Patch::Clear => None,
Patch::Set(value) => Some(value),
};
if configuration_changed {
row.models.clear();
row.model_source = None;
row.models_fetched_at = None;
}
test_changed |= !crate::effect_snapshot::same_test_basis(&previous, &row);
if validate_limits {
crate::model_catalog::validate_overrides(&row)?;
}
if test_changed {
row.last_test = None;
}
catalog::write_entry(tx, &row).await?;
let rows = catalog::read(tx).await?.connections;
let target = current
.default_target
.filter(|target| catalog::valid_target(target, &rows));
Ok(CatalogMutationResult::Committed {
catalog_revision: catalog::advance(tx, current.revision, target.as_ref())
.await?,
connection: Some(catalog::basis(&row)),
})
})
})
.await
}
/// Catalog deletion and credential cleanup share one durable transaction.
/// Repeating a removal of an absent connection is a successful no-op.
pub async fn remove_connection(
&self,
input: RemoveCatalogConnectionInput,
) -> Result<RemoveCatalogConnectionResult> {
validation::basis(&input.expected).map_err(ConfigError::Invalid)?;
self.transaction(TransactionMode::Immediate, move |tx| {
Box::pin(async move {
let current = catalog::read(tx).await?;
let existing = catalog::find(tx, &input.expected.connection_id).await?;
if let Some(row) = &existing
&& row.revision != input.expected.revision
{
return Ok(CatalogMutationResult::ConnectionStale {
expected: input.expected,
actual: Some(catalog::basis(row)),
});
}
let removed = sqlx::query(
"DELETE FROM credentials WHERE json_extract(locator, '$.scope') = 'connection'
AND json_extract(locator, '$.connectionId') = ?",
)
.bind(&input.expected.connection_id)
.execute(&mut *tx)
.await?
.rows_affected();
if removed > 0 {
vault::advance(tx).await?;
}
let revision = if existing.is_some() {
sqlx::query("DELETE FROM connections WHERE connection_id = ?")
.bind(&input.expected.connection_id)
.execute(&mut *tx)
.await?;
let target = current
.default_target
.filter(|target| target.connection_id != input.expected.connection_id);
catalog::advance(tx, current.revision, target.as_ref()).await?
} else {
current.revision
};
Ok(CatalogMutationResult::Committed {
catalog_revision: revision,
connection: None,
})
})
})
.await
}
}