mirror of
https://github.com/aws-actions/configure-aws-credentials.git
synced 2026-09-03 06:05:04 +09:00
Support usage of AWS Profiles
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
import * as core from '@actions/core';
|
||||
import { fs, vol } from 'memfs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import * as ini from 'ini';
|
||||
import {
|
||||
ensureAwsDirectoryExists,
|
||||
getProfileFilePaths,
|
||||
mergeProfileSection,
|
||||
validateProfileName,
|
||||
writeProfileFiles,
|
||||
} from '../src/profileManager';
|
||||
|
||||
describe('Profile Manager', {}, () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.mock('node:fs');
|
||||
vol.reset();
|
||||
vi.spyOn(core, 'debug').mockImplementation(() => {});
|
||||
vi.spyOn(core, 'info').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
describe('validateProfileName', {}, () => {
|
||||
it('accepts valid profile names', {}, () => {
|
||||
expect(() => validateProfileName('dev')).not.toThrow();
|
||||
expect(() => validateProfileName('production')).not.toThrow();
|
||||
expect(() => validateProfileName('my-profile-123')).not.toThrow();
|
||||
expect(() => validateProfileName('default')).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects empty profile names', {}, () => {
|
||||
expect(() => validateProfileName('')).toThrow('aws-profile must not be empty');
|
||||
expect(() => validateProfileName(' ')).toThrow('aws-profile must not be empty');
|
||||
});
|
||||
|
||||
it('rejects profile names with whitespace', {}, () => {
|
||||
expect(() => validateProfileName('my profile')).toThrow('aws-profile must not contain whitespace');
|
||||
expect(() => validateProfileName('dev\ntest')).toThrow('aws-profile must not contain whitespace');
|
||||
expect(() => validateProfileName('prod\tenv')).toThrow('aws-profile must not contain whitespace');
|
||||
});
|
||||
|
||||
it('rejects profile names with brackets', {}, () => {
|
||||
expect(() => validateProfileName('dev[test]')).toThrow('aws-profile must not contain brackets');
|
||||
expect(() => validateProfileName('[profile]')).toThrow('aws-profile must not contain brackets');
|
||||
});
|
||||
|
||||
it('rejects profile names with path separators', {}, () => {
|
||||
expect(() => validateProfileName('dev/test')).toThrow('aws-profile must not contain path separators');
|
||||
expect(() => validateProfileName('dev\\test')).toThrow('aws-profile must not contain path separators');
|
||||
expect(() => validateProfileName('../etc/passwd')).toThrow('aws-profile must not contain path separators');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProfileFilePaths', {}, () => {
|
||||
it('returns default paths when env vars not set', {}, () => {
|
||||
delete process.env.AWS_SHARED_CREDENTIALS_FILE;
|
||||
delete process.env.AWS_CONFIG_FILE;
|
||||
|
||||
const paths = getProfileFilePaths();
|
||||
|
||||
expect(paths.credentials).toMatch(/\.aws\/credentials$/);
|
||||
expect(paths.config).toMatch(/\.aws\/config$/);
|
||||
});
|
||||
|
||||
it('respects AWS_SHARED_CREDENTIALS_FILE env var', {}, () => {
|
||||
process.env.AWS_SHARED_CREDENTIALS_FILE = '/custom/path/credentials';
|
||||
process.env.AWS_CONFIG_FILE = '/custom/path/config';
|
||||
|
||||
const paths = getProfileFilePaths();
|
||||
|
||||
expect(paths.credentials).toBe('/custom/path/credentials');
|
||||
expect(paths.config).toBe('/custom/path/config');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ensureAwsDirectoryExists', {}, () => {
|
||||
it('creates directory if it does not exist', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
|
||||
ensureAwsDirectoryExists(filePath);
|
||||
|
||||
expect(fs.existsSync('/home/runner/.aws')).toBe(true);
|
||||
});
|
||||
|
||||
it('does not error if directory already exists', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
expect(() => ensureAwsDirectoryExists(filePath)).not.toThrow();
|
||||
});
|
||||
|
||||
it('creates nested directories', {}, () => {
|
||||
const filePath = '/home/runner/custom/path/.aws/credentials';
|
||||
|
||||
ensureAwsDirectoryExists(filePath);
|
||||
|
||||
expect(fs.existsSync('/home/runner/custom/path/.aws')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeProfileSection', {}, () => {
|
||||
it('creates new file with profile section', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
mergeProfileSection(filePath, 'dev', {
|
||||
aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE',
|
||||
aws_secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
});
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const parsed = ini.parse(content);
|
||||
|
||||
expect(parsed.dev).toBeDefined();
|
||||
expect(parsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE');
|
||||
expect(parsed.dev.aws_secret_access_key).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
|
||||
});
|
||||
|
||||
it('merges with existing profiles', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
// Create initial profile
|
||||
mergeProfileSection(filePath, 'dev', {
|
||||
aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE',
|
||||
aws_secret_access_key: 'devSecretKey',
|
||||
});
|
||||
|
||||
// Add second profile
|
||||
mergeProfileSection(filePath, 'prod', {
|
||||
aws_access_key_id: 'AKIAPRODEXAMPLE',
|
||||
aws_secret_access_key: 'prodSecretKey',
|
||||
});
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const parsed = ini.parse(content);
|
||||
|
||||
expect(parsed.dev).toBeDefined();
|
||||
expect(parsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE');
|
||||
expect(parsed.prod).toBeDefined();
|
||||
expect(parsed.prod.aws_access_key_id).toBe('AKIAPRODEXAMPLE');
|
||||
});
|
||||
|
||||
it('overwrites existing profile with same name', {}, () => {
|
||||
const filePath = '/home/runner/.aws/credentials';
|
||||
fs.mkdirSync('/home/runner/.aws', { recursive: true });
|
||||
|
||||
// Create initial profile
|
||||
mergeProfileSection(filePath, 'dev', {
|
||||
aws_access_key_id: 'OLD_KEY',
|
||||
aws_secret_access_key: 'oldSecretKey',
|
||||
});
|
||||
|
||||
// Overwrite with new credentials
|
||||
mergeProfileSection(filePath, 'dev', {
|
||||
aws_access_key_id: 'NEW_KEY',
|
||||
aws_secret_access_key: 'newSecretKey',
|
||||
aws_session_token: 'sessionToken',
|
||||
});
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const parsed = ini.parse(content);
|
||||
|
||||
expect(parsed.dev.aws_access_key_id).toBe('NEW_KEY');
|
||||
expect(parsed.dev.aws_secret_access_key).toBe('newSecretKey');
|
||||
expect(parsed.dev.aws_session_token).toBe('sessionToken');
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeProfileFiles', {}, () => {
|
||||
beforeEach(() => {
|
||||
delete process.env.AWS_SHARED_CREDENTIALS_FILE;
|
||||
delete process.env.AWS_CONFIG_FILE;
|
||||
});
|
||||
|
||||
it('writes credentials and config for new profile', {}, () => {
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
SessionToken: 'FwoGZXIvYXdzEBYaDEXAMPLE',
|
||||
},
|
||||
'us-east-1',
|
||||
);
|
||||
|
||||
// Check credentials file
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
const credParsed = ini.parse(credContent);
|
||||
|
||||
expect(credParsed.dev).toBeDefined();
|
||||
expect(credParsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE');
|
||||
expect(credParsed.dev.aws_secret_access_key).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
|
||||
expect(credParsed.dev.aws_session_token).toBe('FwoGZXIvYXdzEBYaDEXAMPLE');
|
||||
|
||||
// Check config file
|
||||
const configPath = getProfileFilePaths().config;
|
||||
const configContent = fs.readFileSync(configPath, 'utf-8');
|
||||
const configParsed = ini.parse(configContent);
|
||||
|
||||
expect(configParsed['profile dev']).toBeDefined();
|
||||
expect(configParsed['profile dev'].region).toBe('us-east-1');
|
||||
});
|
||||
|
||||
it('uses correct section naming for default profile', {}, () => {
|
||||
writeProfileFiles(
|
||||
'default',
|
||||
{
|
||||
AccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
},
|
||||
'us-west-2',
|
||||
);
|
||||
|
||||
// Check credentials file uses [default]
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
const credParsed = ini.parse(credContent);
|
||||
|
||||
expect(credParsed.default).toBeDefined();
|
||||
expect(credParsed['profile default']).toBeUndefined();
|
||||
|
||||
// Check config file uses [default] (not [profile default])
|
||||
const configPath = getProfileFilePaths().config;
|
||||
const configContent = fs.readFileSync(configPath, 'utf-8');
|
||||
const configParsed = ini.parse(configContent);
|
||||
|
||||
expect(configParsed.default).toBeDefined();
|
||||
expect(configParsed['profile default']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('supports multiple profiles', {}, () => {
|
||||
// Write first profile
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIADEV',
|
||||
SecretAccessKey: 'devSecret',
|
||||
},
|
||||
'us-east-1',
|
||||
);
|
||||
|
||||
// Write second profile
|
||||
writeProfileFiles(
|
||||
'prod',
|
||||
{
|
||||
AccessKeyId: 'AKIAPROD',
|
||||
SecretAccessKey: 'prodSecret',
|
||||
SessionToken: 'prodToken',
|
||||
},
|
||||
'us-west-2',
|
||||
);
|
||||
|
||||
// Verify both profiles exist
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
const credParsed = ini.parse(credContent);
|
||||
|
||||
expect(credParsed.dev).toBeDefined();
|
||||
expect(credParsed.prod).toBeDefined();
|
||||
expect(credParsed.dev.aws_access_key_id).toBe('AKIADEV');
|
||||
expect(credParsed.prod.aws_access_key_id).toBe('AKIAPROD');
|
||||
});
|
||||
|
||||
it('handles credentials without session token', {}, () => {
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
||||
SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
||||
},
|
||||
'us-east-1',
|
||||
);
|
||||
|
||||
const credsPath = getProfileFilePaths().credentials;
|
||||
const credContent = fs.readFileSync(credsPath, 'utf-8');
|
||||
const credParsed = ini.parse(credContent);
|
||||
|
||||
expect(credParsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE');
|
||||
expect(credParsed.dev.aws_secret_access_key).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');
|
||||
expect(credParsed.dev.aws_session_token).toBeUndefined();
|
||||
});
|
||||
|
||||
it('throws error for invalid profile name', {}, () => {
|
||||
expect(() =>
|
||||
writeProfileFiles(
|
||||
'invalid profile',
|
||||
{
|
||||
AccessKeyId: 'AKIA',
|
||||
SecretAccessKey: 'secret',
|
||||
},
|
||||
'us-east-1',
|
||||
),
|
||||
).toThrow('Failed to write AWS profile');
|
||||
expect(() =>
|
||||
writeProfileFiles(
|
||||
'invalid profile',
|
||||
{
|
||||
AccessKeyId: 'AKIA',
|
||||
SecretAccessKey: 'secret',
|
||||
},
|
||||
'us-east-1',
|
||||
),
|
||||
).toThrow('whitespace');
|
||||
});
|
||||
|
||||
it('respects custom file paths from env vars', {}, () => {
|
||||
process.env.AWS_SHARED_CREDENTIALS_FILE = '/custom/credentials';
|
||||
process.env.AWS_CONFIG_FILE = '/custom/config';
|
||||
|
||||
fs.mkdirSync('/custom', { recursive: true });
|
||||
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIA',
|
||||
SecretAccessKey: 'secret',
|
||||
},
|
||||
'us-east-1',
|
||||
);
|
||||
|
||||
expect(fs.existsSync('/custom/credentials')).toBe(true);
|
||||
expect(fs.existsSync('/custom/config')).toBe(true);
|
||||
});
|
||||
|
||||
it('logs info messages', {}, () => {
|
||||
writeProfileFiles(
|
||||
'dev',
|
||||
{
|
||||
AccessKeyId: 'AKIA',
|
||||
SecretAccessKey: 'secret',
|
||||
},
|
||||
'us-east-1',
|
||||
);
|
||||
|
||||
expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: dev');
|
||||
expect(core.info).toHaveBeenCalledWith('Writing config to profile: dev');
|
||||
expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: dev');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user