Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

terraform for OCI #234

Merged
merged 3 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export abstract class BaseTerraformCommandHandler {
cwd: tasks.getInput("workingDirectory")
});

let countProviders = ["aws", "azurerm", "google"].filter(provider => commandOutput.stdout.includes(provider)).length;
let countProviders = ["aws", "azurerm", "google", "oracle"].filter(provider => commandOutput.stdout.includes(provider)).length;

tasks.debug(countProviders.toString());
if (countProviders > 1) {
Expand All @@ -69,6 +69,7 @@ export abstract class BaseTerraformCommandHandler {
case "azurerm": return "AzureRM";
case "aws" : return "AWS";
case "gcp" : return "GCP";
case "oci" : return "OCI";
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import tasks = require('azure-pipelines-task-lib/task');
import {ToolRunner} from 'azure-pipelines-task-lib/toolrunner';
import {TerraformAuthorizationCommandInitializer} from './terraform-commands';
import {BaseTerraformCommandHandler} from './base-terraform-command-handler';
import path = require('path');
import * as uuidV4 from 'uuid/v4';

export class TerraformCommandHandlerOCI extends BaseTerraformCommandHandler {
constructor() {
super();
this.providerName = "oci";
}

private getPrivateKeyFilePath(privateKey: string) {
// This is a bit of a hack but spaces need to be converted to line breaks to make it work
privateKey = privateKey.replace('-----BEGIN PRIVATE KEY-----', '_begin_');
privateKey = privateKey.replace('-----END PRIVATE KEY-----', '_end_');
while(privateKey.indexOf(' ') > -1)
{
privateKey = privateKey.replace(' ', '\n');
}
privateKey = privateKey.replace('_begin_', '-----BEGIN PRIVATE KEY-----');
privateKey = privateKey.replace('_end_', '-----END PRIVATE KEY-----');
const privateKeyFilePath = path.resolve(`keyfile-${uuidV4()}.pem`);
tasks.writeFile(privateKeyFilePath, privateKey);
return privateKeyFilePath;
}

private setupBackend(backendServiceName: string) {
// Unfortunately this seems not to work with OCI provider for the tf statefile
// https://developer.hashicorp.com/terraform/language/settings/backends/configuration#command-line-key-value-pairs
//this.backendConfig.set('address', tasks.getInput("PAR url", true));
//this.backendConfig.set('path', tasks.getInput("PAR path", true));
//this.backendConfig.set('scheme', 'https');
//PAR = OCI Object Storage preauthenticated request (for the statefile bucket)

// Instead, will create a backend.tf config file for it in-flight when generate option was selected 'yes' (the default setting)
if(tasks.getInput("backendOCIBucketConfigGenerate", true) == 'yes')
{
tasks.debug('Generating backend tf statefile config.');
var config = "";
config = config + "terraform {\n backend \"http\" {\n";
config = config + " address = \"" + tasks.getInput("backendOCIBucketPar", true) + "\"\n";
config = config + " update_method = \"PUT\"\n }\n }\n";

const workingDirectory = tasks.getInput("workingDirectory");
const tfConfigyFilePath = path.resolve(`${workingDirectory}/config-${uuidV4()}.tf`);
tasks.writeFile(tfConfigyFilePath, config);
tasks.debug('Generating backend tf statefile config done.');
}
}

public async handleBackend(terraformToolRunner: ToolRunner) : Promise<void> {
let backendServiceName = tasks.getInput("backendServiceOCI", true);
this.setupBackend(backendServiceName);

for (let [key, value] of this.backendConfig.entries()) {
terraformToolRunner.arg(`-backend-config=${key}=${value}`);
}
}

public async handleProvider(command: TerraformAuthorizationCommandInitializer) : Promise<void> {
if (command.serviceProvidername) {
let privateKeyFilePath = this.getPrivateKeyFilePath(tasks.getEndpointDataParameter(command.serviceProvidername, "privateKey", false));
process.env['TF_VAR_tenancy_ocid'] = tasks.getEndpointDataParameter(command.serviceProvidername, "tenancy", false);
process.env['TF_VAR_user_ocid'] = tasks.getEndpointDataParameter(command.serviceProvidername, "user", false);
process.env['TF_VAR_region'] = tasks.getEndpointDataParameter(command.serviceProvidername, "region", false);
process.env['TF_VAR_fingerprint'] = tasks.getEndpointDataParameter(command.serviceProvidername, "fingerprint", false);
process.env['TF_VAR_private_key_path'] = `${privateKeyFilePath}`;
}
}
}
46 changes: 45 additions & 1 deletion Tasks/TerraformTask/TerraformTaskV4/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@
"displayName": "Google Cloud Platform(GCP) backend configuration",
"isExpanded": true,
"visibleRule": "provider = gcp && command = init"
},
{
"name": "backendOCI",
"displayName": "Oracle Cloud Infrastructure(OCI) backend configuration",
"isExpanded": true,
"visibleRule": "provider = oci && command = init"
}
],
"inputs": [
Expand All @@ -66,7 +72,8 @@
"options": {
"azurerm": "azurerm",
"aws": "aws",
"gcp": "gcp"
"gcp": "gcp",
"oci": "oci"
},
"properties": {
"EditableOptions": "False"
Expand Down Expand Up @@ -175,6 +182,14 @@
"visibleRule": "provider = gcp && command != init && command != validate",
"helpMarkDown": "Select a Google Cloud Platform connection for the deployment.<br><br>Note: If your connection is not listed or if you want to use an existing connection, you can setup a Google Cloud Platform service connection using the 'Add' or 'Manage' button."
},
{
"name": "environmentServiceNameOCI",
"type": "connectedService:OracleCloudServiceEndpoint",
"label": "Oracle Cloud Platform connection",
"required": true,
"visibleRule": "provider = oci && command != init && command != validate",
"helpMarkDown": "Select a Oracle Cloud Platform connection for the deployment.<br><br>Note: If your connection is not listed or if you want to use an existing connection, you can setup a Oracle Cloud Platform service connection using the 'Add' or 'Manage' button."
},
{
"name": "backendAzureRmUseEnvironmentVariablesForAuthentication",
"type": "boolean",
Expand Down Expand Up @@ -297,6 +312,35 @@
"required": false,
"helpMarkDown": "The relative path to the state file inside the GCP bucket. For example, if you give the input as 'terraform', then the state file, named default.tfstate, will be stored inside an object called terraform.",
"groupName": "backendGCP"
},
{
"name": "backendServiceOCI",
"type": "connectedService:OracleCloudServiceEndpoint",
"label": "Oracle Cloud Platform connection",
"required": true,
"helpMarkDown": "Oracle Cloud Platform connection for the terraform backend configuration.<br><br>Note: If your connection is not listed or if you want to use an existing connection, you can setup a Oracle Cloud Platform service connection using the 'Add' or 'Manage' button.",
"groupName": "backendOCI"
},
{
"name": "backendOCIBucketPar",
"type": "string",
"label": "Bucket PAR for Terraform remote state file",
"required": false,
"helpMarkDown": "The OCI storage bucket PAR configuration for the Terraform remote state file (optional)",
"groupName": "backendOCI"
},
{
"name": "backendOCIBucketConfigGenerate",
"type": "pickList",
"label": "Generate the Terraform remote state file config (Use Yes when not included in TF files)",
"required": true,
"defaultValue": "yes",
"helpMarkDown": "Generates the Terraform remote state file config, select Yes when not included in TF files, othwerwise No.",
"groupName": "backendOCI",
"options": {
"yes": "yes",
"no": "no"
}
}
],
"dataSourceBindings": [
Expand Down
91 changes: 91 additions & 0 deletions azure-devops-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"Azure",
"AWS",
"GCP",
"OCI",
"Release",
"DevOps"
],
Expand Down Expand Up @@ -268,6 +269,96 @@
}
]
}
},
{
"id": "oracle-cloud-service-endpoint",
"description": "Credentials for connecting to Oracle Cloud Platform",
"type": "ms.vss-endpoint.service-endpoint-type",
"targets": [
"ms.vss-endpoint.endpoint-types"
],
"properties": {
"name": "OracleCloudServiceEndpoint",
"displayName": "OCI for Terraform",
"helpMarkDown": "",
"url": {
"displayName": "Server Url",
"helpText": "OCI homepage",
"value": "https://www.oracle.com/cloud/sign-in.html",
"isVisible": "false"
},
"authenticationSchemes": [
{
"id": "endpoint-auth-scheme-none",
"description": "OCI endpoint authentication scheme with no authentication.",
"type": "ms.vss-endpoint.endpoint-auth-scheme-none",
"targets": [
"ms.vss-endpoint.endpoint-auth-schemes"
],
"properties": {
"name": "None",
"displayName": "OCI No authentication for PAR url"
}
}
],
"inputDescriptors": [
{
"id": "user",
"name": "User OCID",
"description": "OCI user OCID",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
},
{
"id": "tenancy",
"name": "Tenancy OCID",
"description": "OCI tenancy OCID",
"inputMode": "textbox",
"isConfidential": true,
"validation": {
"isRequired": true,
"dataType": "string"
}
},
{
"id": "region",
"name": "Region",
"description": "OCI region",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
},
{
"id": "fingerprint",
"name": "Key fingerprint",
"description": "The OCI private key fingerprint",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
},
{
"id": "privatekey",
"name": "Private key",
"description": "The OCI secret private key",
"inputMode": "passwordbox",
"isConfidential": true,
"validation": {
"isRequired": true,
"dataType": "string"
}
}
]
}
}
]
}
Loading