Repository: bottlerocket-os/bottlerocket
File: sources/api/apiserver/src/server/mod.rs (Rust)

Code before:
        let keys = comma_separated("keys", keys_str)?;
        controller::get_settings_keys(&*datastore, &keys, &Committed::Live)
    } else if let Some(mut prefix) = query.get("prefix") {
        if prefix.is_empty() {
            return error::EmptyInputSnafu { input: "prefix" }.fail();
        }
        // When retrieving from /settings, the settings prefix is implied, so we add it if it
        // wasn't given.
        let with_prefix = format!("settings.{}", prefix);
        if !prefix.starts_with("settings") {
            prefix = &with_prefix;
        }
        controller::get_settings_prefix(&*datastore, prefix, &Committed::Live)
            .map(|opt| opt.unwrap_or_default())
    } else {
        controller::get_settings(&*datastore, &Committed::Live)
    }?;

    Ok(SettingsResponse(settings))
}

/// Prints a more specific message before exiting through usage().
fn usage_msg<S: AsRef<str>>(msg: S) -> ! {
    eprintln!("{}\n", msg.as_ref());
    process::exit(2);
}

/// Constructs a Key Pair Map from the
fn construct_key_pair_map(settings_key_pair_vec: &Vec<String>) -> HashMap<Key, String> {
    let mut settings_key_pair_map = HashMap::new();
    for settings_key_pair in settings_key_pair_vec {
        let (raw_key, value) = settings_key_pair.split_once('=').unwrap();

        let mut key = Key::new(KeyType::Data, raw_key).unwrap_or_else(|_| {
            usage_msg(format!("Given key '{}' is not a valid format", raw_key))
        });

        // Add "settings" prefix if the user didn't give a known prefix, to ease usage
        let key_prefix = &key.segments()[0];
        if key_prefix != "settings" {
            let mut segments = key.segments().clone();
            segments.insert(0, "settings".to_string());
            key = Key::from_segments(KeyType::Data, &segments)
                .expect("Adding prefix to key resulted in invalid key?!");
        }

        settings_key_pair_map.insert(key, value.to_string());
    }
    trace!("Key=Value map: {:#?}", settings_key_pair_map);
    settings_key_pair_map
}

Review comment (suggestion):
We don't want to tear down the server in response to a bad client request. This needs to go back to the client as a bad request error.

Code after:
    let settings_key_pair_map = construct_key_pair_map(&settings.request_payload)?;
    let transaction = transaction_name(&query);
    let mut datastore = data.ds.write().ok().context(error::DataStoreLockSnafu)?;
    // We massage the values in the input key pair map.
    // The data store deserialization code understands how to turn the key names
    // (a.b.c) and serialized values into the nested Settings structure.
    let settings_model = datastore::deserialization::from_map(&settings_key_pair_map)
        .context(error::DeserializeMapSnafu)?;
    controller::set_settings(&mut *datastore, &settings_model, transaction)?;
    Ok(HttpResponse::NoContent().finish()) // 204
}

async fn get_transaction_list(data: web::Data<SharedData>) -> Result<TransactionListResponse> {
    let datastore = data.ds.read().ok().context(error::DataStoreLockSnafu)?;
    let data = controller::list_transactions(&*datastore)?;
    Ok(TransactionListResponse(data))
}

/// Get any pending settings in the given transaction, or the "default" transaction if unspecified.
async fn get_transaction(
    query: web::Query<HashMap<String, String>>,
    data: web::Data<SharedData>,
) -> Result<SettingsResponse> {
    let transaction = transaction_name(&query);
    let datastore = data.ds.read().ok().context(error::DataStoreLockSnafu)?;
    let data = controller::get_transaction(&*datastore, transaction)?;
    Ok(SettingsResponse(data))
}

/// Delete the given transaction, or the "default" transaction if unspecified.
async fn delete_transaction(
    query: web::Query<HashMap<String, String>>,
    data: web::Data<SharedData>,
) -> Result<ChangedKeysResponse> {
    let transaction = transaction_name(&query);
    let mut datastore = data.ds.write().ok().context(error::DataStoreLockSnafu)?;
    let deleted = controller::delete_transaction(&mut *datastore, transaction)?;
    Ok(ChangedKeysResponse(deleted))
}

/// Save settings changes from the given transaction, or the "default" transaction if unspecified,
/// to the live data store.  Returns the list of changed keys.
async fn commit_transaction(
    query: web::Query<HashMap<String, String>>,
    data: web::Data<SharedData>,
) -> Result<ChangedKeysResponse> {
    let transaction = transaction_name(&query);
    let mut datastore = data.ds.write().ok().context(error::DataStoreLockSnafu)?;

    let changes = controller::commit_transaction(&mut *datastore, transaction)?;