provider-configuration

Par hashicorp · agent-skills

Implémentez la configuration d'un provider Terraform et l'authentification avec le Plugin Framework : schéma du provider pour les credentials (attributs `Optional` + `Sensitive`), fallbacks sur les variables d'environnement, chaînes de résolution des credentials (configuration statique, puis variables d'environnement, fichier de credentials partagé, et identité de la plateforme), gardes contre les valeurs inconnues dans `Configure()`, masquage des secrets, validation des credentials au moment de la configuration, et diagnostics nommant chaque source tentée. À utiliser lors de l'implémentation ou de la revue de la méthode `Configure` ou du schéma d'un provider, de l'ajout d'options d'authentification (clés API, tokens, profils, fichiers de credentials, assume-role), de la décision sur la manière dont un provider doit résoudre ses credentials, du débogage des erreurs « no valid credential sources » ou de credentials manquants, ou lors des tests unitaires de la résolution des credentials.

npx skills add https://github.com/hashicorp/agent-skills --skill provider-configuration

Configuration et Authentification du Provider Terraform

Comment un provider accepte les paramètres de connexion et résout les credentials. Une mauvaise expérience d'authentification est la première chose que chaque utilisateur d'un provider rencontre ; une chaîne de credential bien conçue est ce qui distingue un provider de qualité production d'une démo. Les exemples utilisent un provider examplecloud fictif et le Plugin Framework.

Références (à charger au besoin) :

  • references/credential-chain.md — implémentation complète et compilable de la chaîne de credential (providers, chain, profils de fichier, wiring Configure, tests)
  • references/case-studies.md — comment le provider AWS (aws-sdk-go-base) et les petits providers structurent les vraies chaînes de credential

Schéma du Provider pour l'Authentification

Tout attribut d'authentification doit être Optional, jamais Required — un attribut Required force les utilisateurs à mettre les credentials dans la configuration et rend impossible la résolution par variable d'environnement et fichier de credentials. Marquez les secrets Sensitive pour que Terraform les masque dans la sortie du plan, et indiquez le fallback de variable d'environnement dans chaque description pour que tfplugindocs publie les règles de résolution.

func (p *examplecloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
    resp.Schema = schema.Schema{
        Attributes: map[string]schema.Attribute{
            "endpoint": schema.StringAttribute{
                Optional:            true,
                MarkdownDescription: "API endpoint. May also be set via the `EXAMPLECLOUD_ENDPOINT` environment variable.",
            },
            "api_key": schema.StringAttribute{
                Optional:            true,
                MarkdownDescription: "API key. May also be set via the `EXAMPLECLOUD_API_KEY` environment variable, or in a shared credentials file.",
            },
            "api_secret": schema.StringAttribute{
                Optional:            true,
                Sensitive:           true,
                MarkdownDescription: "API secret. May also be set via the `EXAMPLECLOUD_API_SECRET` environment variable, or in a shared credentials file.",
            },
            "profile": schema.StringAttribute{
                Optional:            true,
                MarkdownDescription: "Named profile in the shared credentials file. May also be set via the `EXAMPLECLOUD_PROFILE` environment variable. Defaults to `default`.",
            },
            "skip_credentials_validation": schema.BoolAttribute{
                Optional:            true,
                MarkdownDescription: "Skip the identity check normally performed during provider configuration.",
            },
        },
    }
}

Ne jamais ajouter une Default à un attribut de credential, et ne jamais coder en dur une credential n'importe où dans le provider. Les defaults appartiennent à la logique de résolution (où les variables d'environnement et les fichiers peuvent les surcharger), pas au schéma.

La Chaîne de Provider de Credential

Résolvez les credentials en consultant une liste ordonnée de sources et en prenant la première qui produit un ensemble complet. C'est le pattern que le provider AWS utilise via aws-sdk-go-base, et cela se généralise à n'importe quel provider. La précédence canonique, du plus au moins prioritaire :

  1. Configuration statique — valeurs définies directement dans le bloc provider. L'explicite gagne toujours.
  2. Variables d'environnementEXAMPLECLOUD_API_KEY, etc. Le chemin friendly aux CI.
  3. Fichier de credentials partagé — profils nommés dans ~/.examplecloud/credentials, pour les humains avec plusieurs comptes.
  4. Identité de plateforme — métadonnées d'instance, identité de workload, ou échange de token OIDC, où la plateforme l'offre. Des credentials que personne n'a à stocker.

Deux règles rendent la chaîne prévisible :

  • Résolvez les secrets en tant que set, pas champ par champ. Si l'environnement fournit une clé API mais pas de secret, cette source n'offre rien — tombez sur la prochaine source pour les deux valeurs. Mélanger une clé de variable d'env avec un secret de profil de fichier produit des échecs d'authentification quasi impossibles à déboguer pour les utilisateurs.
  • Résolvez les paramètres de connexion non-secrets champ par champ. endpoint, profile, ou insecure peuvent chacun indépendamment suivre config > env > fichier > default, car un décalage là est visible et inoffensif.

L'abstraction centrale est une interface à une seule méthode avec une erreur sentinel qui distingue « cette source n'a rien à offrir » (tombez sur la suivante) de « cette source est mal configurée » (surfacez-la) :

// ErrNoCredentials signals a source had nothing to offer. The chain falls
// through to the next source. Any other error means the source was
// configured but unusable (e.g. malformed credentials file) and is
// preserved so the final diagnostics can surface it.
var ErrNoCredentials = errors.New("no credentials found")

type Credentials struct {
    APIKey    string
    APISecret string
    Source    string // which provider supplied them, for logging
}

func (c Credentials) Complete() bool {
    return c.APIKey != "" && c.APISecret != ""
}

type Provider interface {
    Retrieve(ctx context.Context) (Credentials, error)
    Name() string
}

Une Chain (elle-même une Provider, pour que les chaînes se composent) parcourt les providers en ordre et retourne le premier ensemble complet de credentials. Chaque source ignorée est enregistrée dans une ChainError agrégée dont Error() liste chaque source avec la raison pour laquelle elle a été ignorée, et dont la méthode Is rend errors.Is(err, ErrNoCredentials) true seulement quand chaque source a tombé proprement — donc Configure peut distinguer « rien fourni » de « quelque chose fourni mais cassé » avec un seul check. L'implémentation complète — la boucle de chaîne, les providers statique, environnement et fichier, et le constructeur NewDefaultChain qui owns l'ordre canonique — vit dans references/credential-chain.md.

Wiring la Chaîne dans Configure

Configure s'exécute une fois par opération Terraform, avant tout CRUD de ressource. La forme :

func (p *examplecloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
    var config examplecloudProviderModel
    resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
    if resp.Diagnostics.HasError() {
        return
    }

    // 1. Guard against unknown values (e.g. api_key = some_resource.output).
    if config.APIKey.IsUnknown() {
        resp.Diagnostics.AddAttributeError(
            path.Root("api_key"),
            "Unknown API Key",
            "The provider cannot connect because api_key depends on a value known only after apply. "+
                "Set a static value, or use the EXAMPLECLOUD_API_KEY environment variable.",
        )
    }
    // ... repeat for each auth attribute, then:
    if resp.Diagnostics.HasError() {
        return
    }

    // 2. Resolve credentials through the chain.
    chain := credentials.NewDefaultChain(
        config.APIKey.ValueString(),
        config.APISecret.ValueString(),
        credentials.Options{Profile: config.Profile.ValueString()},
    )
    creds, err := chain.Retrieve(ctx)
    if err != nil {
        if errors.Is(err, credentials.ErrNoCredentials) {
            resp.Diagnostics.AddError(
                "No Valid Credential Sources Found",
                "No examplecloud credentials were found. Sources tried, in order:\n\n"+err.Error()+
                    "\n\nSet api_key and api_secret in the provider block, export "+
                    "EXAMPLECLOUD_API_KEY and EXAMPLECLOUD_API_SECRET, or add a profile to "+
                    "~/.examplecloud/credentials. See https://example.com/docs/auth.",
            )
        } else {
            resp.Diagnostics.AddError("Failed to Resolve Credentials", err.Error())
        }
        return
    }
    tflog.Debug(ctx, "resolved credentials", map[string]any{"source": creds.Source})

    // 3. Build the client once; share it with every resource and data source.
    client := examplecloud.NewClient(endpoint, creds.APIKey, creds.APISecret)
    resp.DataSourceData = client
    resp.ResourceData = client
}

Pourquoi chaque étape importe :

  • Gardes de valeurs unknown. Lors de la planification, un attribut câblé à la sortie d'une autre ressource est unknown, pas null. Sans la garde, le provider le traite silencieusement comme vide, tombe sur la chaîne, et s'authentifie comme la mauvaise identité — ou échoue avec une erreur trompeuse « credentials manquants ». Nommez le workaround de variable d'environnement dans le message de garde.
  • Le check sentinel choisit le bon message. « Vous ne m'avez rien donné » (liste d'options actionnables) est un échec différent de « vous m'avez donné quelque chose de cassé » (montrez l'erreur d'analyse). Les fusionner en un message est comment les providers finissent avec des utilisateurs collant des secrets dans la config pour déboguer.
  • Loguez la source, jamais le secret. Savoir quelle source a gagné est le fait de débogage le plus utile et coûte rien à logger.

Diagnostiques Qui Débloquent les Utilisateurs

Un message d'erreur d'authentification est la documentation la plus lue du provider. Chaque diagnostic d'échec de credential devrait nommer :

  • Chaque source essayée, en ordre, avec pourquoi elle a été ignorée — le ChainError fournit ceci. aws-sdk-go-base fait la même chose avec son NoValidCredentialSourcesError.
  • Les noms exactes de variable d'environnement et le chemin du fichier de credentials et le profil qui ont été consultés — pas « définissez les variables d'environnement appropriées ».
  • Une URL de documentation pour le guide d'authentification du provider.

Utilisez des avertissements (pas des erreurs) pour les conditions qui sont suspectes mais pas fatales, nommant ce qui a pris précédence : un profile défini tandis que des credentials d'environnement sont aussi présents (lequel gagne ?), ou un fichier de credentials avec des permissions lisibles par groupe/autres (suggérez chmod 0600).

Hygiène des Secrets

  • Donnez au type Credentials des méthodes String() et GoString() qui masquent les champs secrets, pour qu'un %v, %+v, ou erreur wrap égaré ne puisse jamais fuir un secret dans les logs ou diagnostiques.
  • Ne jamais inclure de valeurs de credential dans les diagnostiques, lignes de log, ou erreurs wrappées — loguez seulement le nom de source et les identifiants non-secrets.
  • Avertissez quand un fichier de credentials est lisible par d'autres utilisateurs (info.Mode().Perm()&0o077 != 0); ignorez ce check sur Windows, où les bits de permission POSIX n'ont pas de sens.

Validation au Temps de Configure

Résolvez la chaîne avec eagerness dans Configure — jamais lazily à la première utilisation de ressource — pour qu'un problème de credential échoue une fois, au plan, avec un bon message, au lieu d'échouer au milieu d'un apply. Si l'API a un endpoint d'identité bon marché (l'équivalent du sts:GetCallerIdentity AWS ou un /whoami), appelez-le après la résolution des credentials pour que les credentials invalides (pas seulement manquants) échouent aussi au temps de configure. Gatez-le derrière un attribut skip_credentials_validation pour les environnements air-gapped ou stubbés.

Unit Testing de la Chaîne

La chaîne est de la logique pure — testez-la avec des unit tests (préfixe Test, pas TF_ACC), pas des acceptance tests. Rendez l'environnement injectable (un champ getenv func(string) string defaultant à os.Getenv, ou utilisez t.Setenv) et pointez le file provider vers les fixtures t.TempDir(). Les tests qui importent :

  • Par-source : chaque provider retourne ses credentials quand défini et ErrNoCredentials quand incomplet (une clé sans secret est incomplet).
  • Précédence : static bat env; env bat fichier; chaîne tombe sur le fichier quand rien au-dessus ne fournit un ensemble complet.
  • Agrégation d'échec : avec toutes les sources vides, errors.Is(err, ErrNoCredentials) est true et le message nomme chaque source.
  • Erreurs dures : un fichier de credentials mal formé ou un profil explicitement demandé qui n'existe pas surfacez une erreur descriptive plutôt que de tombez silencieusement (un profil simplement defaulté tombe silencieusement).
  • Redaction : fmt.Sprintf("%v") et %+v d'une valeur Credentials ne contiennent jamais le secret.

Les exemples de test complets sont dans references/credential-chain.md.

Checklist

  • [ ] Tous les attributs auth Optional; secrets marqués Sensitive: true
  • [ ] Les descriptions d'attribut nomment leurs fallbacks de variable d'environnement
  • [ ] Gardes de valeurs unknown sur chaque attribut auth dans Configure
  • [ ] Précédence de chaîne : config statique > vars env > fichier de credentials > identité de plateforme
  • [ ] Secrets résolus comme un ensemble complet; paramètres non-secrets champ par champ
  • [ ] Sentinel ErrNoCredentials distingue la chute de l'échec dur
  • [ ] Diagnostic de credentials manquants liste chaque source essayée + URL docs
  • [ ] Type Credentials masque les secrets dans String()/GoString()
  • [ ] Avertissement de permission du fichier de credentials (non-Windows)
  • [ ] Résolution eager dans Configure; check d'identité optionnel avec skip_credentials_validation
  • [ ] Unit tests couvrent le comportement par-source, précédence, agrégation, redaction
  • [ ] Aucune valeur de credential jamais loguée ou embarquée dans une erreur

Skills Connexes

Utilisez la skill new-terraform-provider (si disponible) pour scaffolder le provider dans lequel cette configuration vit, et la skill provider-resources pour consommer le client configuré depuis les ressources et sources de données.

Skills similaires