fn execute_inner()

in edgelet/iotedge/src/config/import/mod.rs [105:602]


fn execute_inner(
    old_config_file: &Path,
    old_master_encryption_key_path: Option<PathBuf>,
) -> Result<String, std::borrow::Cow<'static, str>> {
    let old_config_file_display = old_config_file.display();

    let old_config_contents = match std::fs::read_to_string(old_config_file) {
        Ok(old_config) => old_config,
        Err(err) => match err.kind() {
            std::io::ErrorKind::NotFound => {
                return Err(format!(
                    "there is no old config at {old_config_file_display} available to migrate"
                )
                .into())
            }
            _ => return Err(format!("could not open {old_config_file_display}: {err}").into()),
        },
    };

    let old_config: old_config::Config = {
        // We use YamlFileSource::String to load the file rather than YamlFileSource::File
        // because config::ConfigError makes it harder to recognize an error from a missing file.
        let old_config = config::ConfigBuilder::<config::builder::DefaultState>::default()
            .add_source(YamlFileSource::String(old_config::DEFAULTS.into()))
            .add_source(YamlFileSource::String(old_config_contents.into()))
            .build();

        match old_config.and_then(config::Config::try_deserialize) {
            Ok(old_config) => old_config,
            Err(err) => {
                return Err(format!("could not parse {old_config_file_display}: {err}").into())
            }
        }
    };

    let old_config::Config {
        provisioning,
        agent,
        hostname,
        parent_hostname,
        connect,
        listen,
        // Ignore the old config's `homedir` value. We want to use a fresh directory and have the right ACLs.
        homedir: _,
        certificates,
        watchdog,
        moby_runtime,
    } = old_config;

    let (provisioning, auto_reprovisioning_mode) = {
        let old_config::Provisioning {
            provisioning,
            dynamic_reprovisioning,
        } = provisioning;

        let (provisioning, always_reprovision_on_startup) = match provisioning {
            old_config::ProvisioningType::Manual(old_config::Manual {
                authentication:
                    old_config::ManualAuthMethod::DeviceConnectionString(
                        old_config::ManualDeviceConnectionString {
                            device_id,
                            hostname,
                            shared_access_key,
                        },
                    ),
            }) => (
                common_config::super_config::Provisioning {
                    provisioning: common_config::super_config::ProvisioningType::Manual {
                        inner: common_config::super_config::ManualProvisioning::Explicit {
                            iothub_hostname: hostname,
                            device_id,
                            authentication:
                                common_config::super_config::ManualAuthMethod::SharedPrivateKey {
                                    device_id_pk:
                                        common_config::super_config::SymmetricKey::Inline {
                                            value: shared_access_key,
                                        },
                                },
                        },
                    },
                },
                false,
            ),

            old_config::ProvisioningType::Manual(old_config::Manual {
                authentication:
                    old_config::ManualAuthMethod::X509(old_config::ManualX509Auth {
                        iothub_hostname,
                        device_id,
                        identity_cert,
                        identity_pk,
                    }),
            }) => (
                common_config::super_config::Provisioning {
                    provisioning: common_config::super_config::ProvisioningType::Manual {
                        inner: common_config::super_config::ManualProvisioning::Explicit {
                            iothub_hostname,
                            device_id,
                            authentication: common_config::super_config::ManualAuthMethod::X509 {
                                identity: common_config::super_config::X509Identity::Preloaded {
                                    identity_cert,
                                    identity_pk: {
                                        let identity_pk: aziot_keys_common::PreloadedKeyLocation =
                                        identity_pk.to_string()
                                        .parse()
                                        .map_err(|err| format!("could not parse provisioning.authentication.identity_pk: {err}"))?;
                                        identity_pk
                                    },
                                },
                            },
                        },
                    },
                },
                false,
            ),

            old_config::ProvisioningType::Dps(old_config::Dps {
                global_endpoint,
                scope_id,
                attestation:
                    old_config::AttestationMethod::SymmetricKey(
                        old_config::SymmetricKeyAttestationInfo {
                            registration_id,
                            symmetric_key,
                        },
                    ),
                always_reprovision_on_startup,
            }) => (
                common_config::super_config::Provisioning {
                    provisioning: common_config::super_config::ProvisioningType::Dps {
                        global_endpoint,
                        id_scope: scope_id,
                        attestation:
                            common_config::super_config::DpsAttestationMethod::SymmetricKey {
                                registration_id,
                                symmetric_key: common_config::super_config::SymmetricKey::Inline {
                                    value: symmetric_key,
                                },
                            },
                        payload: None,
                    },
                },
                always_reprovision_on_startup,
            ),

            old_config::ProvisioningType::Dps(old_config::Dps {
                global_endpoint,
                scope_id,
                attestation:
                    old_config::AttestationMethod::X509(old_config::X509AttestationInfo {
                        registration_id,
                        identity_cert,
                        identity_pk,
                    }),
                always_reprovision_on_startup,
            }) => (
                common_config::super_config::Provisioning {
                    provisioning: common_config::super_config::ProvisioningType::Dps {
                        global_endpoint,
                        id_scope: scope_id,
                        attestation: common_config::super_config::DpsAttestationMethod::X509 {
                            registration_id,
                            identity: common_config::super_config::X509Identity::Preloaded {
                                identity_cert,
                                identity_pk: {
                                    let identity_pk: aziot_keys_common::PreloadedKeyLocation =
                                        identity_pk.to_string()
                                        .parse()
                                        .map_err(|err| format!("could not parse provisioning.attestation.identity_pk: {err}"))?;
                                    identity_pk
                                },
                            },
                        },
                        payload: None,
                    },
                },
                always_reprovision_on_startup,
            ),

            old_config::ProvisioningType::Dps(old_config::Dps {
                global_endpoint,
                scope_id,
                attestation:
                    old_config::AttestationMethod::Tpm(old_config::TpmAttestationInfo {
                        registration_id,
                    }),
                always_reprovision_on_startup,
            }) => (
                common_config::super_config::Provisioning {
                    provisioning: common_config::super_config::ProvisioningType::Dps {
                        global_endpoint,
                        id_scope: scope_id,
                        attestation: common_config::super_config::DpsAttestationMethod::Tpm {
                            registration_id,
                        },
                        payload: None,
                    },
                },
                always_reprovision_on_startup,
            ),

            old_config::ProvisioningType::External(_) => {
                return Err("external provisioning is not supported.".into())
            }
        };

        // When both 'dynamic reprovisioning' and 'always reprovision on startup' settings
        // are set in the old configuration, 'dynamic reprovisioning' is prioritized,
        // since it also triggers a reprovisioning before restarting the daemon.

        let auto_reprovisioning_mode = if dynamic_reprovisioning {
            edgelet_settings::aziot::AutoReprovisioningMode::Dynamic
        } else if always_reprovision_on_startup {
            edgelet_settings::aziot::AutoReprovisioningMode::AlwaysOnStartup
        } else {
            edgelet_settings::aziot::AutoReprovisioningMode::OnErrorOnly
        };

        (provisioning, auto_reprovisioning_mode)
    };

    let (edge_ca, trust_bundle_cert) = {
        if let Some(old_config::Certificates {
            device_cert,
            auto_generated_ca_lifetime_days,
        }) = certificates
        {
            if let Some(old_config::DeviceCertificate {
                device_ca_cert,
                device_ca_pk,
                trusted_ca_certs,
            }) = device_cert
            {
                (
                    Some(super_config::EdgeCa::Preloaded {
                        cert: device_ca_cert,
                        pk: device_ca_pk,
                    }),
                    Some(trusted_ca_certs),
                )
            } else {
                (
                    Some(super_config::EdgeCa::Quickstart {
                        auto_generated_edge_ca_expiry_days: auto_generated_ca_lifetime_days.into(),
                        auto_renew: cert_renewal::AutoRenewConfig::default(),
                        subject: None,
                    }),
                    None,
                )
            }
        } else {
            (None, None)
        }
    };

    let config = super_config::Config {
        allow_elevated_docker_permissions: None,

        trust_bundle_cert,

        auto_reprovisioning_mode,

        imported_master_encryption_key: old_master_encryption_key_path,

        #[cfg(contenttrust)]
        manifest_trust_bundle_cert: None,

        additional_info: None,

        iotedge_max_requests: Default::default(),

        aziot: common_config::super_config::Config {
            hostname: Some(hostname),
            parent_hostname,

            provisioning,

            localid: None,

            cloud_timeout_sec: aziot_identityd_config::Settings::default_cloud_timeout(),

            cloud_retries: aziot_identityd_config::Settings::default_cloud_retries(),

            aziot_max_requests: Default::default(),

            prefer_module_identity_cache: Default::default(),

            aziot_keys: Default::default(),

            preloaded_keys: Default::default(),

            cert_issuance: Default::default(),

            preloaded_certs: Default::default(),

            tpm: Default::default(),

            endpoints: Default::default(),
        },

        agent: {
            let old_config::ModuleSpec {
                name,
                type_,
                config,
                env,
                image_pull_policy,
            } = agent;
            edgelet_settings::ModuleSpec::new(
                name,
                type_,
                {
                    let old_config::DockerConfig {
                        image,
                        image_id,
                        create_options,
                        digest,
                        auth,
                    } = config;
                    let new_config = edgelet_settings::DockerConfig::new(
                        image,
                        create_options,
                        digest,
                        auth,
                        true,
                    )?;

                    // Clippy wants map_or here, but that's not possible without cloning.
                    #[allow(clippy::option_if_let_else)]
                    if let Some(image_id) = image_id {
                        new_config.with_image_hash(image_id)
                    } else {
                        new_config
                    }
                },
                env,
                match image_pull_policy {
                    old_config::ImagePullPolicy::OnCreate => {
                        edgelet_settings::module::ImagePullPolicy::OnCreate
                    }
                    old_config::ImagePullPolicy::Never => {
                        edgelet_settings::module::ImagePullPolicy::Never
                    }
                },
            )?
        },

        connect: {
            let old_config::Connect {
                management_uri,
                workload_uri,
            } = connect;
            edgelet_settings::uri::Connect {
                workload_uri,
                management_uri,
            }
        },
        listen: {
            fn map_listen_uri(uri: url::Url) -> Result<url::Url, url::Url> {
                if uri.scheme() == "fd" {
                    match uri.host_str() {
                        Some(old_config::DEFAULT_MGMT_SOCKET_UNIT) => {
                            Ok(AZIOT_EDGED_LISTEN_MGMT_SOCKET_ACTIVATED_URI
                                .parse()
                                .expect("hard-coded URI must parse successfully"))
                        }
                        Some(old_config::DEFAULT_WORKLOAD_SOCKET_UNIT) => {
                            Ok(AZIOT_EDGED_LISTEN_WORKLOAD_SOCKET_ACTIVATED_URI
                                .parse()
                                .expect("hard-coded URI must parse successfully"))
                        }
                        _ => Err(uri),
                    }
                } else {
                    Ok(uri)
                }
            }

            let old_config::Listen {
                management_uri,
                workload_uri,
            } = listen;

            let management_uri = map_listen_uri(management_uri).map_err(|management_uri| {
                format!("unexpected value of listen.management_uri {management_uri}",)
            })?;
            let workload_uri = map_listen_uri(workload_uri).map_err(|workload_uri| {
                format!("unexpected value of listen.workload_uri {workload_uri}")
            })?;

            edgelet_settings::uri::Listen {
                workload_uri,
                management_uri,
            }
        },

        watchdog: {
            let old_config::WatchdogSettings { max_retries } = watchdog;
            edgelet_settings::watchdog::Settings {
                max_retries: match max_retries {
                    old_config::RetryLimit::Infinite => {
                        edgelet_settings::watchdog::MaxRetries::Infinite
                    }
                    old_config::RetryLimit::Num(num) => {
                        edgelet_settings::watchdog::MaxRetries::Num(num)
                    }
                },
            }
        },

        edge_ca,

        moby_runtime: {
            let old_config::MobyRuntime {
                uri,
                network,
                content_trust,
            } = moby_runtime;
            super_config::MobyRuntime {
                uri,

                network: match network {
                    old_config::MobyNetwork::Network(network) => {
                        edgelet_settings::MobyNetwork::Network({
                            let old_config::Network { name, ipv6, ipam } = network;
                            edgelet_settings::docker::network::Network {
                                name,
                                ipv6,
                                ipam: ipam.map(|ipam| {
                                    let old_config::Ipam { config } = ipam;
                                    edgelet_settings::Ipam {
                                        config: config.map(|config| {
                                            config
                                                .into_iter()
                                                .map(|config| {
                                                    let old_config::IpamConfig {
                                                        gateway,
                                                        subnet,
                                                        ip_range,
                                                    } = config;
                                                    edgelet_settings::docker::network::IpamConfig {
                                                        gateway,
                                                        subnet,
                                                        ip_range,
                                                    }
                                                })
                                                .collect()
                                        }),
                                    }
                                }),
                            }
                        })
                    }

                    old_config::MobyNetwork::Name(name) => {
                        edgelet_settings::MobyNetwork::Name(name)
                    }
                },

                content_trust: content_trust
                    .map(
                        |content_trust| -> Result<_, std::borrow::Cow<'static, str>> {
                            let old_config::ContentTrust { ca_certs } = content_trust;

                            Ok(super_config::ContentTrust {
                                ca_certs: ca_certs
                                    .map(|ca_certs| -> Result<_, std::borrow::Cow<'static, str>> {
                                        let mut new_ca_certs: std::collections::BTreeMap<_, _> =
                                            Default::default();

                                        for (hostname, cert_path) in ca_certs {
                                            let cert_uri = url::Url::from_file_path(&cert_path)
                                                .map_err(|()| {
                                                    format!(
                                                        "could not convert path {} to file URI",
                                                        cert_path.display()
                                                    )
                                                })?;
                                            new_ca_certs.insert(hostname, cert_uri);
                                        }

                                        Ok(new_ca_certs)
                                    })
                                    .transpose()?,
                            })
                        },
                    )
                    .transpose()?,
            }
        },
        image_garbage_collection: ImagePruneSettings::default(),
    };

    let config =
        toml::to_string(&config).map_err(|err| format!("could not serialize config: {err}"))?;

    Ok(config)
}