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

feat: add useSSHSourceFormat option to configure source URL format in Wiki documentation #146

Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ jobs:
delete-legacy-tags: false # Note: We don't want to delete tags in this repository
module-change-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/**
module-asset-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/**
use-ssh-source-format: true
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_REPO_CI_TESTING }}

- name: SonarCloud Scan
uses: sonarsource/sonarcloud-github-action@v4
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v4
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ configuring the following optional input parameters as needed.
| `disable-branding` | Controls whether a small branding link to the action's repository is added to PR comments. Recommended to leave enabled to support OSS. | `false` |
| `module-change-exclude-patterns` | A comma-separated list of file patterns to exclude from triggering version changes in Terraform modules. Patterns follow glob syntax (e.g., `.gitignore,_.md`) and are relative to each Terraform module directory. Files matching these patterns will not affect version changes. **WARNING**: Avoid excluding '`_.tf`' files, as they are essential for module detection and versioning processes. | `.gitignore, *.md, *.tftest.hcl, tests/**` |
| `module-asset-exclude-patterns` | A comma-separated list of file patterns to exclude when bundling a Terraform module for tag/release. Patterns follow glob syntax (e.g., `tests/\*\*`) and are relative to each Terraform module directory. Files matching these patterns will be excluded from the bundled output. | `.gitignore, *.md, *.tftest.hcl, tests/**` |
| `use-ssh-source-format` | If enabled, all links to source code in generated Wiki documentation will use SSH standard format (e.g., `git::ssh://git@github.com/owner/repo.git`) instead of HTTPS format (`git::https://github.com/owner/repo.git`) | `false` |

### Example Usage with Inputs

Expand Down Expand Up @@ -217,6 +218,7 @@ jobs:
wiki-sidebar-changelog-max: 10
module-change-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/**
module-asset-exclude-patterns: .gitignore,*.md,*.tftest.hcl,tests/**
use-ssh-source-format: false
```

## Inspiration
Expand Down
2 changes: 2 additions & 0 deletions __mocks__/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const defaultConfig: Config = {
moduleChangeExcludePatterns: ['.gitignore', '*.md'],
moduleAssetExcludePatterns: ['tests/**', 'examples/**'],
githubToken: 'ghp_test_token_2c6912E7710c838347Ae178B4',
useSSHSourceFormat: false,
};

/**
Expand All @@ -42,6 +43,7 @@ const validConfigKeys = [
'moduleChangeExcludePatterns',
'moduleAssetExcludePatterns',
'githubToken',
'useSSHSourceFormat',
] as const;

type ValidConfigKey = (typeof validConfigKeys)[number];
Expand Down
5 changes: 3 additions & 2 deletions __tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ describe('config', () => {
expect(config.githubToken).toBe('ghp_test_token_2c6912E7710c838347Ae178B4');
expect(config.moduleChangeExcludePatterns).toEqual(['.gitignore', '*.md']);
expect(config.moduleAssetExcludePatterns).toEqual(['tests/**', 'examples/**']);
expect(config.useSSHSourceFormat).toBe(false);
expect(startGroup).toHaveBeenCalledWith('Initializing Config');
expect(startGroup).toHaveBeenCalledTimes(1);
expect(endGroup).toHaveBeenCalledTimes(1);
expect(info).toHaveBeenCalledTimes(10);
expect(info).toHaveBeenCalledTimes(11);
expect(vi.mocked(info).mock.calls).toEqual([
['Major Keywords: MAJOR CHANGE, BREAKING CHANGE, !'],
['Minor Keywords: feat, feature'],
Expand All @@ -132,8 +133,8 @@ describe('config', () => {
['Wiki Sidebar Changelog Max: 10'],
['Module Change Exclude Patterns: .gitignore, *.md'],
['Module Asset Exclude Patterns: tests/**, examples/**'],
['Use SSH Source Format: false'],
]);
expect(info).toHaveBeenCalledTimes(10);
});
});

Expand Down
3 changes: 2 additions & 1 deletion __tests__/helpers/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ export const defaultInputs = {
'module-change-exclude-patterns': '.gitignore,*.md',
'module-asset-exclude-patterns': 'tests/**,examples/**',
github_token: 'ghp_test_token_2c6912E7710c838347Ae178B4',
'use-ssh-source-format': 'false',
};
export const requiredInputs = Object.keys(defaultInputs);
export const booleanInputs = ['delete-legacy-tags', 'disable-wiki', 'disable-branding'];
export const booleanInputs = ['delete-legacy-tags', 'disable-wiki', 'disable-branding', 'use-ssh-source-format'];
export const booleanConfigKeys: BooleanConfigKeys[] = ['deleteLegacyTags', 'disableWiki', 'disableBranding'];

/**
Expand Down
19 changes: 14 additions & 5 deletions __tests__/terraform-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { context } from '@/mocks/context';
import { ensureTerraformDocsConfigDoesNotExist, generateTerraformDocs, installTerraformDocs } from '@/terraform-docs';
import type { TerraformModule } from '@/types';
import { info } from '@actions/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import which from 'which';

const execFilePromisified = promisify(execFile);
Expand Down Expand Up @@ -50,7 +50,6 @@ describe('terraform-docs', async () => {
afterEach(() => {
Object.defineProperty(process, 'platform', { value: realPlatform });
Object.defineProperty(process, 'arch', { value: realArch });
vi.resetAllMocks();
});

describe('install terraform-docs (linux, darwin, freebsd)', () => {
Expand Down Expand Up @@ -184,6 +183,10 @@ describe('terraform-docs', async () => {

expect(() => installTerraformDocs(terraformDocsVersion)).toThrow('not found: invalid-non-existent-binary');
});

afterAll(() => {
mockWhichSync.mockRestore();
});
});

describe('terraform-docs version validation', () => {
Expand Down Expand Up @@ -219,7 +222,7 @@ describe('terraform-docs', async () => {
join('C:\\Windows\\System32', 'terraform-docs.exe'),
];

beforeEach(async () => {
beforeAll(async () => {
// Get real implementations
const realChildProcess = (await vi.importActual('node:child_process')) as typeof import('node:child_process');
const realFs = (await vi.importActual('node:fs')) as typeof import('node:fs');
Expand All @@ -232,6 +235,14 @@ describe('terraform-docs', async () => {
mockWhichSync.mockImplementation(realWhich.sync);
});

afterAll(() => {
// Restore original mock implementations
mockExecFileSync.mockRestore();
fsExistsSyncMock.mockRestore();
mockFsUnlinkSync.mockRestore();
mockWhichSync.mockRestore();
});

afterEach(() => {
// Cleanup downloaded/installed files
for (const file of cleanupFiles) {
Expand All @@ -241,8 +252,6 @@ describe('terraform-docs', async () => {
// Ignore cleanup errors
}
}

// Restore original mock implementations (handled via global resetAllMocks())
});

it(`should install terraform-docs on the real system ${process.arch}/${process.platform}`, () => {
Expand Down
47 changes: 47 additions & 0 deletions __tests__/wiki.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,51 @@ describe('wiki', async () => {
expect(commitCall?.[1]).toEqual(['commit', '-m', 'PR #456 - Complex PR title\n\nLine 1\nLine 2\nLine 3']);
});
});

describe('formatModuleSource()', () => {
beforeEach(() => {
context.set({
repo: { owner: 'techpivot', repo: 'terraform-module-releaser' },
repoUrl: 'https://github.com/techpivot/terraform-module-releaser',
});
});

it('should format source URL as HTTPS when useSSHSourceFormat is false', async () => {
config.set({ useSSHSourceFormat: false });
const files = await generateWikiFiles(terraformModules);

// Read each generated .md file and verify it contains HTTPS format
for (const file of files) {
if (file.endsWith('.md')) {
const content = readFileSync(file, 'utf8');
if (content.includes('source =')) {
expect(content).toContain('source = "git::https://github.com/techpivot/terraform-module-releaser.git?ref=');
expect(content).not.toContain(
'source = "git::ssh://git@github.com/techpivot/terraform-module-releaser.git?ref=',
);
}
}
}
});

it('should format source URL as SSH when useSSHSourceFormat is true', async () => {
config.set({ useSSHSourceFormat: true });
const files = await generateWikiFiles(terraformModules);

// Read each generated .md file and verify it contains SSH format
for (const file of files) {
if (file.endsWith('.md')) {
const content = readFileSync(file, 'utf8');
if (content.includes('source =')) {
expect(content).toContain(
'source = "git::ssh://git@github.com/techpivot/terraform-module-releaser.git?ref=',
);
expect(content).not.toContain(
'source = "git::https://github.com/techpivot/terraform-module-releaser.git?ref=',
);
}
}
}
});
});
});
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ inputs:
The minimatch syntax is used for pattern matching. Files matching these patterns will be excluded from the bundled output.
required: true
default: ".gitignore,*.md,*.tftest.hcl,tests/**"
use-ssh-source-format:
description: If enabled, all links to source code in generated Wiki documentation will use SSH format instead of HTTPS format
required: true
default: "false"
github_token:
description: |
Required for retrieving pull request metadata, tags, releases, updating PR comments, wiki, and creating tags/releases.
Expand Down
8 changes: 1 addition & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,7 @@
"bugs": {
"url": "https://github.com/techpivot/terraform-module-releaser/issues"
},
"keywords": [
"terraform",
"module",
"releaser",
"github-action",
"monorepo"
],
"keywords": ["terraform", "module", "releaser", "github-action", "monorepo"],
"license": "MIT",
"exports": {
".": "./dist/index.js"
Expand Down
1 change: 1 addition & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
sonar.organization=techpivot
sonar.projectKey=terraform-module-releaser
sonar.projectName=Terraform Module Releaser
sonar.projectDescription=A GitHub Action for managing Terraform modules in GitHub monorepos, automating versioning, releases, and documentation.

sonar.sourceEncoding=UTF-8

Expand Down
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function initializeConfig(): Config {
githubToken: getInput('github_token', { required: true }),
moduleChangeExcludePatterns: getArrayInput('module-change-exclude-patterns'),
moduleAssetExcludePatterns: getArrayInput('module-asset-exclude-patterns'),
useSSHSourceFormat: getBooleanInput('use-ssh-source-format', { required: true }),
};

// Validate that *.tf is not in excludePatterns
Expand All @@ -96,6 +97,7 @@ function initializeConfig(): Config {
info(`Wiki Sidebar Changelog Max: ${configInstance.wikiSidebarChangelogMax}`);
info(`Module Change Exclude Patterns: ${configInstance.moduleChangeExcludePatterns.join(', ')}`);
info(`Module Asset Exclude Patterns: ${configInstance.moduleAssetExcludePatterns.join(', ')}`);
info(`Use SSH Source Format: ${configInstance.useSSHSourceFormat}`);

return configInstance;
} finally {
Expand Down
12 changes: 12 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,18 @@ export interface Config {
* tests and other non-functional files as needed.
*/
moduleAssetExcludePatterns: string[];

/**
* If true, the wiki will use the SSH format for the source URL of the repository.
* This changes the format of the source URL in the generated wiki documentation to use the SSH format.
*
* Example:
* - SSH format: git::ssh://git@github.com/techpivot/terraform-module-releaser.git
* - HTTPS format: git::https://github.com/techpivot/terraform-module-releaser.git
*
* When set to true, the SSH standard format (non scp variation) will be used. Otherwise, the HTTPS format will be used.
*/
useSSHSourceFormat: boolean;
}

/**
Expand Down
20 changes: 19 additions & 1 deletion src/wiki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,23 @@ export function getWikiLink(moduleName: string, relative = true): string {
return `${baseUrl}/wiki/${getWikiSlug(moduleName)}`;
}

/**
* Formats the module source URL based on configuration settings.
*
* @param repoUrl - The repository URL
* @param useSSH - Whether to use SSH format
* @returns The formatted source URL for the module
*/
function formatModuleSource(repoUrl: string, useSSH: boolean): string {
if (useSSH) {
// Convert HTTPS URL to SSH format
// From: https://github.com/owner/repo
// To: ssh://git@github.com/owner/repo
return `ssh://${repoUrl.replace(/^https:\/\/github\.com/, 'git@github.com')}.git`;
}
return `${repoUrl}.git`;
}

/**
* Generates the wiki file associated with the specified Terraform module.
* Ensures that the directory structure is created if it doesn't exist and handles overwriting
Expand All @@ -209,12 +226,13 @@ async function generateWikiModule(terraformModule: TerraformModule): Promise<str
// Generate a module changelog
const changelog = getModuleReleaseChangelog(terraformModule);
const tfDocs = await generateTerraformDocs(terraformModule);
const moduleSource = formatModuleSource(context.repoUrl, config.useSSHSourceFormat);
const wikiContent = [
'# Usage\n',
'To use this module in your Terraform, refer to the below module example:\n',
'```hcl',
`module "${moduleName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}" {`,
` source = "git::${context.repoUrl}.git?ref=${latestTag}"`,
` source = "git::${moduleSource}?ref=${latestTag}"`,
'\n # See inputs below for additional required parameters',
'}',
'```',
Expand Down