// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

syntax = "proto3";
package terraform1;

import "google/protobuf/any.proto";

service Setup {
    // Clients must call Handshake before any other function of any other
    // service, to complete the capability negotiation step that may
    // then affect the behaviors of subsequent operations.
    //
    // This function can be called only once per RPC server.
    rpc Handshake(Handshake.Request) returns (Handshake.Response);

    // At any time after handshaking, clients may call Stop to initiate a
    // graceful shutdown of the server.
    rpc Stop(Stop.Request) returns (Stop.Response);
}

message Handshake {
    message Request {
        ClientCapabilities capabilities = 1;
        Config config = 2;
    }
    message Response {
        ServerCapabilities capabilities = 2;
    }
}

message Stop {
    message Request {
    }
    message Response {
    }
}

// The capabilities that the client wishes to advertise to the server during
// handshake.
message ClientCapabilities {
    // There are not yet any negotiatable capabilities.
}

// The capabilities that the server wishes to advertise to the client during
// handshake. Fields in this message can also be used to acknowledge and
// confirm support for client capabilities advertised in ClientCapabilities,
// in situations where the client must vary its behavior based on the server's
// level of support.
message ServerCapabilities {
    // There are not yet any negotiatable capabilities.
}

service Dependencies {
    // Opens a source bundle that was already extracted into the filesystem
    // somewhere, returning an opaque source bundle handle that can be used for
    // subsequent operations.
    rpc OpenSourceBundle(OpenSourceBundle.Request) returns (OpenSourceBundle.Response);

    // Closes a previously-opened source bundle, invalidating the given handle
    // and therefore making it safe to delete or modify the bundle directory
    // on disk.
    rpc CloseSourceBundle(CloseSourceBundle.Request) returns (CloseSourceBundle.Response);

    // Reads and parses an existing dependency lock file from the filesystem,
    // returning a dependency locks handle.
    //
    // This function parses a user-provided source file, and so invalid content
    // in that file is treated as diagnostics in a successful response rather
    // than as an RPC error. Callers must check whether the dependency locks
    // handle in the response is set (non-zero) before using it, and treat
    // an unset handle as indicating a user error which is described in the
    // accompanying diagnostics. Diagnostics can also be returned along with
    // a valid handle, e.g. if there are non-blocking warning diagnostics.
    rpc OpenDependencyLockFile(OpenDependencyLockFile.Request) returns (OpenDependencyLockFile.Response);

    // Creates an in-memory-only dependency locks handle with a fixed set of
    // dependency selections provided as arguments.
    rpc CreateDependencyLocks(CreateDependencyLocks.Request) returns (CreateDependencyLocks.Response);

    rpc CloseDependencyLocks(CloseDependencyLocks.Request) returns (CloseDependencyLocks.Response);

    // Returns information about the provider version selections in a
    // dependency locks object.
    rpc GetLockedProviderDependencies(GetLockedProviderDependencies.Request) returns (GetLockedProviderDependencies.Response);

    // Populates a new provider plugin cache directory in the local filesystem
    // based on the provider version selections in a given dependency locks
    // object.
    //
    // This particular RPC can only install already-selected provider packages
    // recorded in a dependency locks object; it does not support "upgrading"
    // provider selections to newer versions as a CLI user would do with
    // "terraform init -upgrade", because there would be no way to then
    // commit the updated locks to disk as a lock file.
    rpc BuildProviderPluginCache(BuildProviderPluginCache.Request) returns (stream BuildProviderPluginCache.Event);

    // Opens an existing local filesystem directory as a provider plugin cache
    // directory, returning a plugin cache handle that can be used with other
    // RPC operations.
    rpc OpenProviderPluginCache(OpenProviderPluginCache.Request) returns (OpenProviderPluginCache.Response);

    rpc CloseProviderPluginCache(CloseProviderPluginCache.Request) returns (CloseProviderPluginCache.Response);

    // Returns information about the specific provider packages that are
    // available in the given provider plugin cache.
    rpc GetCachedProviders(GetCachedProviders.Request) returns (GetCachedProviders.Response);

    // Returns information about the built-in providers that are compiled in
    // to this Terraform Core server.
    rpc GetBuiltInProviders(GetBuiltInProviders.Request) returns (GetBuiltInProviders.Response);

    // Returns a description of the schema for a particular provider in a
    // given provider plugin cache, or of a particular built-in provider
    // known to this version of Terraform Core.
    //
    // WARNING: This operation requires executing the selected provider plugin,
    // which therefore allows it to run arbitrary code as a child process of
    // this Terraform Core server, with access to all of the same resources.
    // This should typically be used only with providers explicitly selected
    // in a dependency lock file, so users can control what external code
    // has the potential to run in a context that probably has access to
    // private source code and other sensitive information.
    rpc GetProviderSchema(GetProviderSchema.Request) returns (GetProviderSchema.Response);
}

message Config {
    map<string, HostCredential> credentials = 1;
}

message HostCredential {
    string token = 1;
}

message OpenSourceBundle {
    message Request {
        string local_path = 1;
    }
    message Response {
        int64 source_bundle_handle = 1;
    }
}

message CloseSourceBundle {
    message Request {
        int64 source_bundle_handle = 1;
    }
    message Response {
    }
}

message OpenDependencyLockFile {
    message Request {
        int64 source_bundle_handle = 1;
        SourceAddress source_address = 2;
    }
    message Response {
        int64 dependency_locks_handle = 1;
        repeated Diagnostic diagnostics = 2;
    }
}

message CreateDependencyLocks {
    message Request {
        // The provider selections to include in the locks object.
        //
        // A typical value would be the result of an earlier call to
        // GetLockedProviderDependencies on some other locks object,
        // e.g. if a caller needs to propagate a set of locks from one
        // Terraform Core RPC server to another.
        repeated ProviderPackage provider_selections = 1;
    }
    message Response {
        int64 dependency_locks_handle = 1;
    }
}

message CloseDependencyLocks {
    message Request {
        int64 dependency_locks_handle = 1;
    }
    message Response {
    }
}

message GetLockedProviderDependencies {
    message Request {
        int64 dependency_locks_handle = 1;
    }
    message Response {
        repeated ProviderPackage selected_providers = 1;
    }
}

message BuildProviderPluginCache {
    message Request {
        string cache_dir = 1;
        int64 dependency_locks_handle = 2;
        repeated InstallMethod installation_methods = 3;

        // If set, this populates the cache with plugins for a different
        // platform than the one the Terraform Core RPC server is running on.
        // If unset (empty) then the cache will be populated with packages
        // for the same platform as Terraform Core was built for, if available.
        //
        // If this is set to a different platform than the Terraform Core RPC
        // server's then the generated cache directory will appear empty to
        // other operations on this server.
        string override_platform = 4;

        message InstallMethod {
            oneof source {
                bool direct = 1;
                string local_mirror_dir = 2;
                string network_mirror_url = 3;
            }
            repeated string include = 4;
            repeated string exclude = 5;
        }
    }
    message Event {
        oneof event {
            Pending pending = 1;
            ProviderVersion already_installed = 2;

            ProviderVersion built_in = 3;
            ProviderConstraints query_begin = 4;
            ProviderVersion query_success = 5;
            ProviderWarnings query_warnings = 6;

            FetchBegin fetch_begin = 7;
            FetchComplete fetch_complete = 8;

            Diagnostic diagnostic = 9;
        }

        message Pending {
            repeated ProviderConstraints expected = 1;
        }
        message ProviderConstraints {
            string source_addr = 1;
            string versions = 2;
        }
        message ProviderVersion {
            string source_addr = 1;
            string version = 2;
        }
        message ProviderWarnings {
            string source_addr = 1;
            repeated string warnings = 2;
        }
        message FetchBegin {
            ProviderVersion provider_version = 1;
            string location = 2;
        }
        message FetchComplete {
            ProviderVersion provider_version = 1;
            AuthResult auth_result = 2;

            // If auth_result is one of the "_SIGNED" variants then this
            // might contain a UI-oriented identifier for the key that
            // signed the package. The exact format of this string is not
            // guaranteed; do not attempt to parse it or make automated
            // decisions based on it.
            string key_id_for_display = 3;

            enum AuthResult {
                UNKNOWN = 0;
                VERIFIED_CHECKSUM = 1;
                OFFICIAL_SIGNED = 2;
                PARTNER_SIGNED = 3;
                SELF_SIGNED = 4;
            }
        }
    }
}

message OpenProviderPluginCache {
    message Request {
        string cache_dir = 1;

        // As with the field of the same name in BuildProviderPluginCache.Request.
        //
        // If this is set to anything other than this RPC server's native
        // platform then any operations that require executing the provider
        // plugin are likely to fail due to executable format errors or
        // similar. However, it's valid to use the returned handle with
        // GetCachedProviders, since it only analyzes the cache metadata
        // and doesn't actually run the plugins inside.
        string override_platform = 2;
    }
    message Response {
        int64 provider_cache_handle = 1;
    }
}

message CloseProviderPluginCache {
    message Request {
        int64 provider_cache_handle = 1;
    }
    message Response {
    }
}

message GetCachedProviders {
    message Request {
        int64 provider_cache_handle = 1;
    }
    message Response {
        repeated ProviderPackage available_providers = 1;
    }
}

message GetBuiltInProviders {
    message Request {
    }
    message Response {
        // The built-in providers that are compiled in to this Terraform Core
        // server.
        //
        // This uses ProviderPackage messages for consistency with the other
        // operations which list providers, but built-in providers do not
        // have version numbers nor hashes so those fields will always be
        // unset in the result.
        repeated ProviderPackage available_providers = 1;
    }
}

message GetProviderSchema {
    message Request {
        // The address of the provider to retrieve schema for, using the
        // typical provider source address syntax.
        //
        // When requesting schema based on a ProviderPackage message, populate
        // this with its "source_addr" field.
        string provider_addr = 1;
        // The version number of the given provider to retrieve the schema
        // of, which must have already been populated into the cache directory.
        //
        // Not supported for built-in providers because we can only access the
        // single "version" of the provider that's compiled into this Terraform
        // Core server, and so must be left unset or empty for those.
        //
        // When requesting schema based on a ProviderPackage message, populate
        // this with its "version" field.
        string provider_version = 2;

        // The handle for the previously-opened provider plugin cache to
        // load the provider plugin from.
        //
        // Optional for built-in providers, but can still be specified in that
        // case if desired so that callers can safely just send the handle they
        // have in all cases and be naive about which providers are and are
        // not built in.
        int64 provider_cache_handle = 3;
    }
    message Response {
        ProviderSchema schema = 1;
    }
}

// Represents a selected or available version of a provider, from either a
// dependency lock object (selected) or a provider cache object (available).
//
// This message type corresponds in meaning with a single "provider" block in a
// dependency lock file, but not all messages of this type directly represent
// such a physical block.
message ProviderPackage {
    // The address of the provider using the canonical form of the provider
    // source address syntax.
    string source_addr = 1;

    // The version number of this provider package. Unset for (and only for)
    // built-in providers; callers may use the set-ness of this field to
    // distinguish installable vs. built-in providers without having to
    // parse the source address syntax.
    string version = 2;

    // The hash strings that Terraform knows about for this provider package,
    // using the same "scheme:hash" syntax used in Terraform's dependency
    // lock file format.
    //
    // For a message representing a "selected" provider package this enumerates
    // all of the checksums that were previously loaded from a dependency
    // lock file or otherwise inserted into a dependency locks object, which
    // are usually (but not necessarily) originally obtained from the
    // provider's origin registry and then cached in the lock file.
    //
    // For a message representing an "available" provider package this
    // describes only the actual package on disk, and so will typically
    // include only the subset of the checksums from the corresponding
    // "selected" package that are relevant to the current platform where
    // Terraform Core is running.
    repeated string hashes = 3;
}

// ProviderSchema describes the full schema for a particular provider.
message ProviderSchema {
    Schema provider_config = 1;
    map<string, Schema> managed_resource_types = 2;
    map<string, Schema> data_resource_types = 3;
}

service Stacks {
    // Load and perform initial static validation of a stack configuration
    // in a previously-opened source bundle. If successful, returns a
    // stack configuration handle that can be used with other operations.
    rpc OpenStackConfiguration(OpenStackConfiguration.Request)
        returns (OpenStackConfiguration.Response);
    // Close a previously-opened stack configuration using its handle.
    rpc CloseStackConfiguration(CloseStackConfiguration.Request)
        returns (CloseStackConfiguration.Response);
    // Validate an open stack configuration.
    rpc ValidateStackConfiguration(ValidateStackConfiguration.Request)
        returns (ValidateStackConfiguration.Response);
    // Analyze a stack configuration to find all of the components it declares.
    // This is static analysis only, so it cannot produce dynamic information
    // such as the number of instances of each component.
    rpc FindStackConfigurationComponents(FindStackConfigurationComponents.Request)
        returns (FindStackConfigurationComponents.Response);
    // Calculate a desired state from the given configuration and compare it
    // with the current state to propose a set of changes to converge the
    // current state with the desired state, at least in part.
    rpc PlanStackChanges(PlanStackChanges.Request)
        returns (stream PlanStackChanges.Event);
    // Execute the changes proposed by an earlier call to PlanStackChanges.
    rpc ApplyStackChanges(ApplyStackChanges.Request)
        returns (stream ApplyStackChanges.Event);
    // OpenStackInspector creates a stack inspector handle that can be used
    // with subsequent calls to the "Inspect"-prefixed functions.
    rpc OpenStackInspector(OpenStackInspector.Request)
        returns (OpenStackInspector.Response);
    // InspectExpressionResult evaluates an arbitrary expression in the context
    // of a stack inspector handle.
    rpc InspectExpressionResult(InspectExpressionResult.Request)
        returns (InspectExpressionResult.Response);
}

message OpenStackConfiguration {
    message Request {
        int64 source_bundle_handle = 1;
        SourceAddress source_address = 2;
    }
    message Response {
        int64 stack_config_handle = 1;
        repeated Diagnostic diagnostics = 2;
    }
}

message CloseStackConfiguration {
    message Request {
        int64 stack_config_handle = 1;
    }
    message Response {
    }
}

message ValidateStackConfiguration {
    message Request {
        int64 stack_config_handle = 1;
        int64 dependency_locks_handle = 2;
        int64 provider_cache_handle = 3;
    }
    message Response {
        repeated Diagnostic diagnostics = 1;
    }
}

message FindStackConfigurationComponents {
    message Request {
        int64 stack_config_handle = 1;
    }
    message Response {
        StackConfig config = 1;
    }

    enum Instances {
        SINGLE = 0;
        COUNT = 1;
        FOR_EACH = 2;
    }
    message StackConfig {
        map<string, Component> components = 1;
        map<string, EmbeddedStack> embedded_stacks = 2;
    }
    message EmbeddedStack {
        string source_addr = 1;
        Instances instances = 2;
        StackConfig config = 3;
    }
    message Component {
        string source_addr = 1;
        Instances instances = 2;
        string component_addr = 3;
    }
}

message PlanStackChanges {
    message Request {
        PlanMode plan_mode = 1;
        int64 stack_config_handle = 2;
        map<string, google.protobuf.Any> previous_state = 3;
        int64 dependency_locks_handle = 4;
        int64 provider_cache_handle = 5;
        map<string, DynamicValueWithSource> input_values = 6;
        // TODO: Various other planning options
    }
    message Event {
        oneof event {
            PlannedChange planned_change = 1;
            Diagnostic diagnostic = 2;
            StackChangeProgress progress = 10;
        }
        reserved 3 to 9; // formerly used for individual progress events
    }
}

message ApplyStackChanges {
    message Request {
        // This must refer to exactly the same configuration that was
        // passed to PlanStackChanges when creating this plan, or the
        // results will be unpredictable.
        int64 stack_config_handle = 1;
        // The caller should send all of the keys present in the previous
        // apply's description map. Terraform Core will use this for
        // situations such as updating existing descriptions to newer
        // formats even if no change is being made to the corresponding
        // real objects.
        repeated string known_description_keys = 3;
        // This must include all of the "raw" values emitted through
        // PlannedChange events during the PlanStackChanges operation
        // that created this plan, concatenated together in the same
        // order they were written to the PlanStackChanges event stream.
        repeated google.protobuf.Any planned_changes = 4;
        // This must be equivalent to the argument of the same name
        // passed to PlanStackChanges when creating this plan.
        int64 dependency_locks_handle = 5;
        // This must be equivalent to the argument of the same name
        // passed to PlanStackChanges when creating this plan.
        int64 provider_cache_handle = 6;

        reserved 2; // (formerly the previous state, but we now propagate that as part of planned_changes as an implementation detail)
    }
    message Event {
        oneof event {
            AppliedChange applied_change = 1;
            Diagnostic diagnostic = 2;
            StackChangeProgress progress = 3;
        }
    }
}

message OpenStackInspector {
    message Request {
        int64 stack_config_handle = 1;
        map<string, google.protobuf.Any> state = 2;
        int64 dependency_locks_handle = 3;
        int64 provider_cache_handle = 4;
        map<string, DynamicValueWithSource> input_values = 5;
    }
    message Response {
        int64 stack_inspector_handle = 1;
        repeated Diagnostic diagnostics = 2;
    }
}

message InspectExpressionResult {
    message Request {
        int64 stack_inspector_handle = 1;
        bytes expression_src = 2;
        string stack_addr = 3;
    }
    message Response {
        // The result of evaluating the expression, if successful enough to
        // produce a result. Unpopulated if the expression was too invalid
        // to produce a result, with the problem then described in the
        // associated diagnostics.
        //
        // Uses a MessagePack encoding with in-band type information.
        DynamicValue result = 1;
        repeated Diagnostic diagnostics = 2;
    }
}

// Represents dynamically-typed data from within the Terraform language.
// Typically only one of the available serialization formats will be populated,
// depending on what serializations are appropriate for a particular context
// and what capabilities the client and the server negotiated during Handshake.
message DynamicValue {
    bytes msgpack = 1; // The default serialization format
    repeated AttributePath sensitive = 2; // Paths to any sensitive-marked values.
}

// Represents a change of some object from one dynamic value to another.
message DynamicValueChange {
    DynamicValue old = 1;
    DynamicValue new = 2;
}

// Represents a DynamicValue accompanied by a source location where it was
// presumably defined, for values that originated in configuration files for
// situations such as returning error messages.
message DynamicValueWithSource {
    DynamicValue value = 1;
    SourceRange source_range = 2;
}

message AttributePath {
    message Step {
        oneof selector {
            // Set "attribute_name" to represent looking up an attribute
            // in the current object value.
            string attribute_name = 1;
            // Set "element_key_*" to represent looking up an element in
            // an indexable collection type.
            string element_key_string = 2;
            int64 element_key_int = 3;
        }
    }
    repeated Step steps = 1;
}

// Represents the address of a specific component instance within a stack.
message ComponentInstanceInStackAddr {
    // The address of the static component that this is an instance of.
    string component_addr = 1;
    // The address of the instance that's being announced. For
    // multi-instance components this could have any combination of
    // instance keys on the component itself or instance keys on any
    // of the containing embedded stacks.
    string component_instance_addr = 2;
}

// Represents the address of a specific resource instance inside a specific
// component instance within the containing stack.
message ResourceInstanceInStackAddr {
    // Unique address of the component instance that this resource instance
    // belongs to. This is comparable with
    string component_instance_addr = 1;
    // Unique address of the resource instance within the given component
    // instance. Each component instance has a separate namespace of
    // resource instance addresses, so callers must take both fields together
    // to produce a key that's unique throughout the entire plan.
    string resource_instance_addr = 2;
}

// Represents the address of a specific resource instance object inside a
// specific component instance within the containing stack.
message ResourceInstanceObjectInStackAddr {
    // Unique address of the component instance that this resource instance
    // belongs to. This is comparable with
    string component_instance_addr = 1;
    // Unique address of the resource instance within the given component
    // instance. Each component instance has a separate namespace of
    // resource instance addresses, so callers must take both fields together
    // to produce a key that's unique throughout the entire plan.
    string resource_instance_addr = 2;
    // Optional "deposed key" populated only for non-current (deposed) objects,
    // which can appear for "create before destroy" replacements where the
    // create succeeds but then the destroy fails, leaving us with two different
    // objects to track for the same resource instance.
    string deposed_key = 3;
}

enum ResourceMode {
    UNKNOWN = 0;
    MANAGED = 1;
    DATA = 2;
}

// A source address in the same form as it would appear in a Terraform
// configuration: a source string combined with an optional version constraint
// string, where the latter is valid only for registry module addresses.
//
// This is not used for "final" source addresses that have already been reduced
// to an exact version selection. For those we just directly encode the string
// representation of the final address, including a version number if necessary.
message SourceAddress {
    string source = 1;
    string versions = 2;
}

enum PlanMode {
    NORMAL = 0;
    REFRESH_ONLY = 1;
    DESTROY = 2;
}

enum ChangeType {
    NOOP = 0;
    READ = 1;
    CREATE = 2;
    UPDATE = 3;
    DELETE = 4;
}

// Describes one item in a stack plan. The overall plan is the concatentation
// of all messages of this type emitted as events during the plan; splitting
// this information over multiple messages just allows the individual events
// to double as progress notifications for an interactive UI.
message PlannedChange {
    // Terraform Core's internal representation(s) of this change. Callers
    // must provide the messages in this field, if any, verbatim to the
    // ApplyStackChanges RPC in order to apply this change, and must not
    // attempt to decode or analyze the contents because they are subject
    // to change in future versions of Terraform Core.
    //
    // This might be unpopulated if this message represents only information
    // for the caller and Terraform Core doesn't actually need to recall this
    // information during the apply step. Callers must append each raw item
    // to the raw plan in the order specified, and provide them all together
    // in the same order to ApplyStackChanges.
    repeated google.protobuf.Any raw = 1;

    // Caller-facing descriptions of this change, to use for presenting
    // information to end-users in the UI and for other subsystems such as
    // imposing policy rules on the resulting plan.
    //
    // There can be zero or more description objects associated with each
    // change. More than one is not common, but should be supported by clients
    // by treating them the same way as if each description had arrived in
    // a separate PlannedChange message. Clients should not treat the grouping
    // or not-grouping of change description objects as meaningful information,
    // since it's subject to change in future Terraform Core versions.
    //
    // DO NOT attempt to use this to surgically filter particular changes
    // from a larger plan. Although external descriptions often match with
    // the raw representations in field "raw", that is not guaranteed and
    // Terraform Core assumes that it will always be provided with the full
    // set of raw messages -- in the same order they were emitted -- during
    // the apply step. For example, some raw messages might omit information
    // that is implied by earlier raw messages and would therefore be
    // incomplete if isolated.
    repeated ChangeDescription descriptions = 2;
    reserved 3 to 6; // formerly used for an inline "oneof description", now factored out into a separate message type

    // Represents a single caller-facing description of a change, to use for
    // presenting information to end users in the UI and for other subsystems
    // such as imposing policy rules on the resulting plan.
    //
    // New description types might be added in future versions of Terraform
    // Core, and so clients should tolerate description messages that appear
    // to have none of the oneof fields set, and should just ignore those
    // messages entirely.
    message ChangeDescription {
        oneof description {
            ComponentInstance component_instance_planned = 1;
            ResourceInstance resource_instance_planned = 2;
            OutputValue output_value_planned = 3;
            bool plan_applyable = 4;
        }
    }

    // Reports the existence of a particular instance of a component,
    // once Terraform has resolved arguments such as "for_each" that
    // might make the set of instances dynamic.
    message ComponentInstance {
        ComponentInstanceInStackAddr addr = 1;
        // The changes to the existence of this instance relative to the
        // prior state. This only considers the component instance directly,
        // and doesn't take into account what actions are planned for any
        // resource instances inside.
        repeated ChangeType actions = 2;
        // A flag for whether applying this plan is expected to cause the
        // desired state and actual state to become converged.
        //
        // If this field is false, that means Terraform expects that at least
        // one more plan/apply round will be needed to reach convergence.
        //
        // If this field is true then Terraform hopes to be able to converge
        // after this plan is applied, but callers should ideally still check
        // anyway by running one more plan to confirm that there aren't any
        // unexpected differences caused by such situations as contradictory
        // configuration or provider bugs.
        bool plan_complete = 3;
    }
    message ResourceInstance {
        ResourceInstanceObjectInStackAddr addr = 1;
        repeated ChangeType actions = 2;
        DynamicValueChange values = 3;
        Moved moved = 4;
        Imported imported = 5;
        ResourceMode resource_mode = 6;
        string resource_type = 7;
        string provider_addr = 8;

        // previous_run_value is included only if it would be
        // different from values.old, which typically means that
        // Terraform detected some changes made outside of Terraform
        // since the previous run. In that case, this field is
        // the un-refreshed (but still upgraded) value from
        // the previous run and values.old is the refreshed version.
        //
        // If this isn't set then values.old should be used as the
        // previous run value, if needed.
        DynamicValue previous_run_value = 9;

        // This flag is set if Terraform Core considers the difference
        // between previous_run_value and values.old to be "notable",
        // which is a heuristic subject to change over time but is
        // broadly intended to mean that it would be worth mentioning
        // the difference between the two in the UI as a
        // "change outside of Terraform". If this isn't set then the
        // difference is probably not worth mentioning to the user
        // by default, although it could still be shown behind an
        // optional disclosure in UI contexts where such things are possible.
        bool notable_change_outside = 10;

        repeated AttributePath replace_paths = 11;

        // TODO: Everything else we need for feature-parity with the
        // existing JSON plan export format.

        message Moved {
            ResourceInstanceInStackAddr prev_addr = 1;
        }
        message Imported {
            string import_id = 1;
        }
    }
    // Note: this is only for output values from the topmost
    // stack configuration, because all other output values are
    // internal to the configuration and not part of its public API.
    message OutputValue {
        string name = 1;
        repeated ChangeType actions = 2;
        DynamicValueChange values = 3;
    }
}

// Describes a change made during a Stacks.ApplyStackChanges call.
//
// All of the events of this type taken together represent a sort of "patch"
// modifying the two data structures that the caller must maintain: the
// raw state map, and the description map. Callers must apply these changes
// in the order of the emission of the messages and then retain the entirety
// of both data structures to populate fields in the next PlanStackChanges call.
message AppliedChange {
    // Terraform Core's internal representation of the change, presented as
    // a sequence of modifications to the raw state data structure.
    //
    // For each element, in order:
    //   - If both key and value are set and the key matches an element
    //     already in the raw state map, the new value replaces the existing one.
    //   - If both key and value are set but the key does not match an
    //     element in the raw state map, this represents inserting a new element
    //     into the map.
    //   - If key is set and value is not, this represents removing any existing
    //     element from the raw state map which has the given key, or a no-op
    //     if no such element exists.
    //   - No other situation is legal.
    //
    // This sequence can potentially be zero-length if a particular event only
    // has a external-facing "description" component and no raw equivalent. In
    // that case the raw state map is unmodified.
    repeated RawChange raw = 1;

    // Caller-facing description of this change, to use for presenting
    // information to end-users in the UI and for other subsystems such as
    // billing.
    //
    // Callers are expected to maintain a map of description objects that
    // gets updated piecemeal by messages in this field. Callers must treat
    // the keys as entirely opaque and thus treat the resulting data structure
    // as if it were an unsorted set of ChangeDescription objects; the keys
    // exist only to allow patching the data structure over time.
    //
    // For each element, in order:
    //   - If both key and description are set and the key matches an element
    //     from the previous apply's description map, the new value replaces
    //     the existing one.
    //   - If both key and value are set but the key does not match an
    //     element in the previous apply's description map, this represents
    //     inserting a new element into the map.
    //   - If key is set and description is "deleted", this represents removing
    //     any existing element from the previous apply's description map which
    //     has the given key, or a no-op if no such element exists.
    //   - If a description field is set that the caller doesn't understand,
    //     the caller should still write it to the updated description map
    //     but ignore it in further processing.
    //   - No other situation is legal.
    //
    // Callers MUST preserve the verbatim description message in the
    // description map, even if it contains fields that are not present in
    // the caller's current protobuf stubs. In other words, callers must use
    // a protocol buffers implementation that is able to preserve unknown
    // fields and store them so that future versions of the caller might
    // use an updated set of stubs to interact with the previously-stored
    // description.
    //
    // DO NOT attempt to use this to surgically filter particular raw state
    // updates from a larger plan. Although external descriptions often match
    // with the raw representations in field "raw", that is not guaranteed and
    // Terraform Core assumes that it will always be provided with the full
    // raw state map during the next plan step.
    repeated ChangeDescription descriptions = 2;

    message RawChange {
        string key = 1;
        google.protobuf.Any value = 2;
    }
    message ChangeDescription {
        string key = 1;
        oneof description {
            Nothing deleted = 4; // explicitly represents the absense of a description
            ResourceInstance resource_instance = 2;
            OutputValue output_value = 3;
            ComponentInstance component_instance = 5;
        }
        // Field number 20000 is reserved as a field number that will
        // always be unknown to any client, to allow clients to test
        // whether they correctly preserve unexpected fields.
        reserved 20000;
    }
    message ResourceInstance {
        ResourceInstanceObjectInStackAddr addr = 1;
        DynamicValue new_value = 2;
        ResourceMode resource_mode = 4;
        string resource_type = 5;
        string provider_addr = 6;

        // Sometimes Terraform needs to make changes to a resource in
        // multiple steps during the apply phase, with each step
        // changing something about the state. This flag will be set
        // for such interim updates, and left unset for whatever
        // description Terraform Core considers to be "final", at
        // which point the new value should be converged with the
        // desired state.
        //
        // The intended use for this is when presenting updated values
        // to users in the UI, where it might be best to ignore or
        // present differently interim updates to avoid creating
        // confusion by showing the not-yet-converged intermediate
        // states.
        //
        // If Terraform encounters a problem during the apply phase
        // and needs to stop partway through then a "final" change
        // description might never arrive. In that case, callers
        // should save the most recent interim object as the final
        // description, since it would represent the most accurate
        // description of the state the remote system has been left
        // in.
        bool interim = 3;
    }
    message ComponentInstance {
        string component_addr = 3;
        string component_instance_addr = 1;
        map<string,DynamicValue> output_values = 2;
    }
    message OutputValue {
        string name = 1;
        DynamicValue new_value = 2;
    }
    message Nothing {}
}

// A container for "progress report" events in both Stacks.PlanStackChanges
// and Stacks.ApplyStackChanges, which share this message type to allow
// clients to share event-handling code between the two phases.
message StackChangeProgress {
    // Some event types are relevant only to one of the two operations, while
    // others are common across both but will include different status codes,
    // etc in different phases.
    oneof event {
        ComponentInstanceStatus component_instance_status = 1;
        ResourceInstanceStatus resource_instance_status = 2;
        ResourceInstancePlannedChange resource_instance_planned_change = 3;
        ProvisionerStatus provisioner_status = 4;
        ProvisionerOutput provisioner_output = 5;
        ComponentInstanceChanges component_instance_changes = 6;
        ComponentInstances component_instances = 7;
    }

    // ComponentInstanceStatus describes the current status of a component instance
    // undergoing a plan or apply operation.
    message ComponentInstanceStatus {
        ComponentInstanceInStackAddr addr = 1;
        Status status = 2;

        enum Status {
            INVALID = 0;
            PENDING = 1;
            PLANNING = 2;
            PLANNED = 3;
            APPLYING = 4;
            APPLIED = 5;
            ERRORED = 6;
        }
    }

    // ComponentInstanceStatus describes the current status of a resource instance
    // undergoing a plan or apply operation.
    message ResourceInstanceStatus {
        ResourceInstanceObjectInStackAddr addr = 1;
        Status status = 2;
        string provider_addr = 3;

        enum Status {
            INVALID = 0;
            PENDING = 1;
            REFRESHING = 2;
            REFRESHED = 3;
            PLANNING = 4;
            PLANNED = 5;
            APPLYING = 6;
            APPLIED = 7;
            ERRORED = 8;
        }
    }

    // ResourceInstancePlannedChange describes summary information about a planned
    // change for a resource instance. This does not include the full object change,
    // which is described in PlannedChange.ResourceChange. The information in this
    // message is intended for the event stream and need not include the instance's
    // full object values.
    message ResourceInstancePlannedChange {
        ResourceInstanceObjectInStackAddr addr = 1;
        repeated ChangeType actions = 2;
        Moved moved = 3;
        Imported imported = 4;
        string provider_addr = 5;

        message Moved {
            ResourceInstanceInStackAddr prev_addr = 1;
        }
        message Imported {
            string import_id = 1;
        }
    }

    // ProvisionerStatus represents the progress of a given provisioner during its
    // resource instance's apply operation.
    message ProvisionerStatus {
        ResourceInstanceObjectInStackAddr addr = 1;
        string name = 2;
        ProvisionerStatus status = 3;

        enum Status {
            INVALID = 0;
            PROVISIONING = 1;
            PROVISIONED = 2;
            ERRORED = 3;
        }
    }

    // ProvisionerOutput represents recorded output data emitted by a provisioner
    // during a resource instance's apply operation.
    message ProvisionerOutput {
        ResourceInstanceObjectInStackAddr addr = 1;
        string name = 2;
        string output = 3;
    }

    // ComponentInstanceChanges represents a roll-up of change counts for a
    // component instance plan or apply operation.
    message ComponentInstanceChanges {
        ComponentInstanceInStackAddr addr = 1;

        // total is the sum of all of the other count fields.
        //
        // Clients should sum all of the other count fields they know about
        // and compare to total. If the sum is less than total then the
        // difference should be treated as an "other change types" category,
        // for forward-compatibility when the Terraform Core RPC server is
        // using a newer version of this protocol than the client.
        int32 total = 2;
        int32 add = 3;
        int32 change = 4;
        int32 import = 5;
        int32 remove = 6;
    }

    // ComponentInstances represents the result of expanding a component into zero
    // or more instances.
    message ComponentInstances {
        string component_addr = 1;
        repeated string instance_addrs = 2;
    }
}

message Diagnostic {
    enum Severity {
        INVALID = 0;
        ERROR = 1;
        WARNING = 2;
    }
    Severity severity = 1;
    string summary = 2;
    string detail = 3;
    SourceRange subject = 4;
    SourceRange context = 5;
}

message SourceRange {
    string source_addr = 1;
    SourcePos start = 2;
    SourcePos end = 3;
}

message SourcePos {
    int64 byte = 1;
    int64 line = 2;
    int64 column = 3;
}

// Schema describes a schema for an instance of a particular object, such as
// a resource type or a provider's overall configuration.
message Schema {
    // Block is the top level configuration block for this schema.
    Block block = 1;

    message Block {
        repeated Attribute attributes = 1;
        repeated NestedBlock block_types = 2;
        DocString description = 3;
        bool deprecated = 4;
    }

    message Attribute {
        string name = 1;
        bytes type = 2;
        Object nested_type = 10;
        DocString description = 3;
        bool required = 4;
        bool optional = 5;
        bool computed = 6;
        bool sensitive = 7;
        bool deprecated = 8;
    }

    message NestedBlock {
        enum NestingMode {
            INVALID = 0;
            SINGLE = 1;
            LIST = 2;
            SET = 3;
            MAP = 4;
            GROUP = 5;
        }

        string type_name = 1;
        Block block = 2;
        NestingMode nesting = 3;
    }

    message Object {
        enum NestingMode {
            INVALID = 0;
            SINGLE = 1;
            LIST = 2;
            SET = 3;
            MAP = 4;
        }

        repeated Attribute attributes = 1;
        NestingMode nesting = 3;
    }

    message DocString {
        string description = 1;
        Format format = 2;

        enum Format {
            PLAIN = 0;
            MARKDOWN = 1;
        }
    }
}

// The Packages service provides helper functions for retrieving Terraform
// modules and providers.
//
// Unlike the Dependencies service, the Packages service does not require any
// existing configuration or sourcebundle to function.
//
// This service is designed for use with a specific command-line tool, and is
//  currently experimental. It can be changed and removed without warning, even
// in patch releases.
service Packages {
    rpc ProviderPackageVersions(ProviderPackageVersions.Request) returns (ProviderPackageVersions.Response);
    rpc FetchProviderPackage(FetchProviderPackage.Request) returns (FetchProviderPackage.Response);

    rpc ModulePackageVersions(ModulePackageVersions.Request) returns (ModulePackageVersions.Response);
    rpc ModulePackageSourceAddr(ModulePackageSourceAddr.Request) returns (ModulePackageSourceAddr.Response);
    rpc FetchModulePackage(FetchModulePackage.Request) returns (FetchModulePackage.Response);
}

message ProviderPackageVersions {
    message Request {
        string source_addr = 1;
    }
    message Response {
        repeated string versions = 1;
        repeated Diagnostic diagnostics = 2;
    }
}

message FetchProviderPackage {
    message Request {
        string cache_dir = 1;

        string source_addr = 2;
        string version = 3;
        repeated string platforms = 4;
        repeated string hashes = 5;
    }
    message Response {
        // Each requested platform will return a result in this list. The order
        // of the returned results will match the order of the requested
        // platforms. If the binary for a given platform could not be downloaded
        // there will still be an entry in the results with diagnostics
        // explaining why.
        repeated FetchProviderPackage.PlatformResult results = 1;
        repeated Diagnostic diagnostics = 2;
    }
    message PlatformResult {
        ProviderPackage provider = 1;
        repeated Diagnostic diagnostics = 2;
    }
}

message ModulePackageVersions {
    message Request {
        string source_addr = 2;
    }
    message Response {
        repeated string versions = 1;
        repeated Diagnostic diagnostics = 2;
    }
}

message ModulePackageSourceAddr {
    message Request {
        string source_addr = 1;
        string version = 2;
    }
    message Response {
        string url = 1;
        repeated Diagnostic diagnostics = 2;
    }
}

message FetchModulePackage {
    message Request {
        string cache_dir = 1;

        string url = 2;
    }
    message Response {
        repeated Diagnostic diagnostics = 1;
    }
}
