Skip to content

Commit

Permalink
terraform for OCI (#234)
Browse files Browse the repository at this point in the history
Co-authored-by: mericstam <[email protected]>
  • Loading branch information
mikarinneoracle and mericstam authored Oct 5, 2024
1 parent b9408fd commit 8f3f68a
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 2 deletions.
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"
}
}
]
}
}
]
}

0 comments on commit 8f3f68a

Please sign in to comment.