Adding a New Provider
Relevant source files
The following files were used as context for generating this wiki page:- iac-recert-engine/internal/provider/provider.go
- iac-recert-engine/internal/provider/github.go
- iac-recert-engine/internal/provider/azure.go
- iac-recert-engine/internal/provider/gitlab.go
- iac-recert-engine/internal/engine/engine.go
- iac-recert-engine/internal/config/types.go
- iac-recert-engine/example.config.yaml
- iac-recert-engine/internal/types/types.go
Introduction
The iac-recert-engine interacts with different Git hosting services (like GitHub, GitLab, and Azure DevOps) through a provider-based architecture. A "Provider" is a component that abstracts the specific API calls for a given Git service, allowing the core engine to perform actions like creating branches, committing files, and opening pull requests in a vendor-neutral way. This design makes the system extensible.
Adding a new provider allows the engine to support a new Git hosting service. The process involves implementing a common interface, registering the new implementation in a factory function, and updating the system's configuration to recognize the new provider type. The core of this architecture is the GitProvider interface, which defines the contract that all provider implementations must fulfill.
Architecture Overview
The engine initializes a single provider at startup based on the user's configuration. The engine.NewEngine function calls the provider.NewProvider factory function, which is responsible for instantiating the correct provider implementation.
Sources: internal/engine/engine.go:34-37, internal/provider/provider.go:44-54
This diagram illustrates the provider instantiation flow.
Step 1: Implement the GitProvider Interface
The first and most significant step is to create a new Go struct that implements all methods defined in the GitProvider interface. This interface is the central contract for all interactions between the engine and a Git service.
Sources: internal/provider/provider.go:12-29
The following class diagram shows the relationship between the interface and its implementations.
Interface Methods
Each method in the GitProvider interface must be implemented. The GitHubProvider in internal/provider/github.go is the most complete reference implementation. Some providers, like AzureDevOpsProvider, are incomplete and return not implemented errors for several methods.
| Method | Description |
|---|---|
GetRepository | Fetches repository metadata like name, owner, and default branch. |
GetLastModificationDate | Retrieves the timestamp and details of the last commit for a specific file path. |
BranchExists | Checks if a branch with the given name already exists. |
CreateBranch | Creates a new branch from a specified base reference (e.g., "main"). |
CreateCommit | Creates a new commit on a specified branch with a set of file changes. |
PullRequestExists | Checks if an open pull request already exists for a given head and base branch. |
CreatePullRequest | Creates a new pull request. |
UpdatePullRequest | Modifies an existing pull request (e.g., title, description, state). |
ClosePullRequest | Closes an existing pull request. |
AssignPullRequest | Assigns users to a pull request. |
RequestReviewers | Requests reviews from users for a pull request. |
AddLabels | Adds labels to a pull request. |
AddComment | Adds a comment to a pull request. |
Sources: internal/provider/provider.go:12-29, internal/provider/github.go, internal/provider/azure.go:125-189
Step 2: Create the Provider Constructor
A constructor function is required to initialize your new provider. This function is responsible for parsing configuration, setting up the provider-specific API client, and returning an initialized instance of your provider struct.
The constructor must match the following signature:
func NewMyNewProvider(ctx context.Context, repoCfg config.RepositoryConfig, authCfg config.AuthConfig, logger *zap.Logger) (GitProvider, error)
Key tasks for the constructor:
- Read the authentication token from the environment variable specified in
authCfg.TokenEnv. - Parse the repository URL from
repoCfg.URLto extract necessary identifiers (e.g., organization, project, repository name). - Initialize the API client for the target Git service using the token.
- Return a new instance of your provider struct, cast to the
GitProviderinterface.
Sources: internal/provider/github.go:22-50, internal/provider/azure.go:29-71, internal/provider/gitlab.go:25-54
The following diagram shows the sequence of creating a new GitHub provider.
Step 3: Register the New Provider
After implementing the provider and its constructor, you must register it in the NewProvider factory function located in internal/provider/provider.go. Add a new case to the switch statement that matches the provider name you will use in the configuration.
// iac-recert-engine/internal/provider/provider.go
func NewProvider(ctx context.Context, repoCfg config.RepositoryConfig, authCfg config.AuthConfig, logger *zap.Logger) (GitProvider, error) {
switch repoCfg.Provider {
case "github":
return NewGitHubProvider(ctx, repoCfg, authCfg, logger)
case "azure":
return NewAzureDevOpsProvider(ctx, repoCfg, authCfg, logger)
case "gitlab":
return NewGitLabProvider(ctx, repoCfg, authCfg, logger)
// Add your new provider here
case "my_new_provider":
return NewMyNewProvider(ctx, repoCfg, authCfg, logger)
default:
return nil, fmt.Errorf("unsupported provider: %s", repoCfg.Provider)
}
}
Sources: internal/provider/provider.go:45-53
Step 4: Update Configuration Schema
To make the system aware of your new provider, you must update the configuration validation rules. The provider field in both RepositoryConfig and AuthConfig uses a oneof validator to restrict allowed values. Add your new provider's string identifier to this list.
// iac-recert-engine/internal/config/types.go
type RepositoryConfig struct {
URL string `yaml:"url" mapstructure:"url" validate:"required,url"`
Provider string `yaml:"provider" mapstructure:"provider" validate:"required,oneof=github azure gitlab my_new_provider"`
}
type AuthConfig struct {
Provider string `yaml:"provider" mapstructure:"provider" validate:"required,oneof=github azure gitlab my_new_provider"`
TokenEnv string `yaml:"token_env" mapstructure:"token_env" validate:"required"`
}
Sources: internal/config/types.go:22-27, internal/config/types.go:29-32
Example config.yaml
Once the code changes are complete, users can configure the engine to use the new provider by setting the provider fields in their config.yaml file.
# example.config.yaml
repository:
# URL for the new provider's repository format
url: "https://my-git-service.com/my-org/my-repo"
# The string identifier for the new provider
provider: "my_new_provider"
auth:
provider: "my_new_provider"
# Environment variable containing the access token
token_env: "MY_NEW_PROVIDER_TOKEN"
# ... other configurations
Sources: example.config.yaml:7-14
Summary
Adding a new provider is a structured process that extends the engine's capabilities to new Git platforms. The key steps are to implement the GitProvider interface, create a corresponding constructor, register the new implementation in the central factory, and update the configuration schema to validate and accept the new provider type. The GitHubProvider serves as a complete and robust example to follow for a new implementation.