Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenHodgson committed Oct 3, 2024
1 parent e0e3ae3 commit fefdbe0
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 57 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ jobs:
- uses: ./ # buildalon/setup-plastic-scm@v1
with:
unity-username: ${{ secrets.UNITY_USERNAME }}
unity-password: ${{ secrets.UNITY_PASSWORD }}
uvcs-organization: ${{ secrets.UVCS_ORGANIZATION }}
unity-cloud-project-id: ${{ secrets.UNITY_CLOUD_PROJECT_ID }}
unity-service-account-credentials: ${{ secrets.UNITY_SERVICE_ACCOUNT_CREDENTIALS }}
- run: |
cm version
cm repository list
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ A GitHub action to setup and install [Plastic SCM](https://www.plasticscm.com) (
steps:
- uses: buildalon/setup-plastic-scm@v1
with:
unity-username: ${{ secrets.UNITY_USERNAME }}
unity-password: ${{ secrets.UNITY_PASSWORD }}
unity-organization: ${{ secrets.UNITY_ORGANIZATION }}
uvcs-organization: ${{ secrets.UVCS_ORGANIZATION }}
unity-cloud-project-id: ${{ secrets.UNITY_CLOUD_PROJECT_ID }}
unity-service-account-credentials: ${{ secrets.UNITY_SERVICE_ACCOUNT_CREDENTIALS }}
- run: |
cm version
```
Expand All @@ -24,6 +24,6 @@ steps:
| name | description | required |
| ---- | ----------- | -------- |
| `version` | The specific version to install. | defaults to the latest |
| `unity-username` | The email address associated with your Unity account. | true |
| `unity-password` | The password associated with your Unity account. | true |
| `unity-organization` | The organization associated with your Unity account. | true |
| `uvcs-organization` | The Unity Version Control Services Organization. | true |
| `unity-cloud-project-id` | The uuid of your unity cloud project | true |
| `unity-service-account-credentials` | The service account key id and secret key formatted: `<KEY_ID>:<SECRET_KEY>` | true |
12 changes: 6 additions & 6 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ inputs:
version:
description: 'The version of Plastic SCM to install.'
required: false
unity-username:
description: 'The email address associated with your Unity account.'
uvcs-organization:
description: 'The Unity Version Control Services Organization.'
required: true
unity-password:
description: 'The password associated with your Unity account.'
unity-cloud-project-id:
description: 'The Unity Cloud Project ID.'
required: true
unity-organization:
description: 'The organization associated with your Unity account.'
unity-service-account-credentials:
description: 'The service account key id and secret key.'
required: true
runs:
using: 'node20'
Expand Down
117 changes: 98 additions & 19 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33214,7 +33214,12 @@ async function run() {
core.error(`Failed to call cm command!\n${error}`);
}
}
await authenticate();
try {
await testConnection();
}
catch (error) {
await configure();
}
}
function getTempDirectory() {
return process.env['RUNNER_TEMP'] || '';
Expand All @@ -33239,13 +33244,20 @@ async function getDownloadUrl(version) {
}
async function getToolLatestVersion() {
core.info('Getting latest version...');
const response = await fetch('https://www.plasticscm.com/download');
const body = await response.text();
const versionMatch = body.match(/<strong>Version:\s*<\/strong>(\d+\.\d+\.\d+\.\d+)/);
if (!versionMatch) {
throw new Error('Failed to parse version');
let output = '';
await exec.exec('curl', ['-s', 'https://www.plasticscm.com/api/lastversion/after/9.0.0.0/for/cloud/windows'], {
listeners: {
stdout: (data) => {
output += data.toString();
}
},
silent: !core.isDebug()
});
const json = JSON.parse(output);
const version = json.version;
if (!version) {
throw new Error('Failed to get the latest version');
}
const version = versionMatch[1];
core.info(`Latest version: ${version}`);
return version;
}
Expand Down Expand Up @@ -33291,19 +33303,86 @@ async function installLinux(version) {
await exec.exec('sudo', ['apt-get', 'update']);
await exec.exec('sudo', ['apt-get', 'install', installArg]);
}
async function authenticate() {
const clientConfPath = getPlasticClientConfPath();
core.info(`client.conf path: ${clientConfPath}`);
const clientConf = await fs.promises.readFile(clientConfPath, 'utf8');
core.info(clientConf);
}
function getPlasticClientConfPath() {
switch (process.platform) {
case 'win32': return path.join(process.env['APPDATA'] || '', 'plastic4', 'client.conf');
case 'darwin': return path.join(process.env['HOME'] || '', '.plastic4', 'client.conf');
case 'linux': return path.join(process.env['HOME'] || '', '.plastic4', 'client.conf');
default: throw new Error(`Unsupported platform: ${process.platform}`);
async function testConnection() {
const expect = `Test connection executed successfully`;
let output = '';
await exec.exec('cm', ['checkconnection'], {
listeners: {
stdout: (data) => {
output += data.toString();
}
}
});
if (!output.includes(expect)) {
throw new Error(`Test connection failed!\n${output}`);
}
}
async function configure() {
const projectId = core.getInput('unity-cloud-project-id', { required: true });
const credentials = core.getInput('unity-service-account-credentials', { required: true });
const accessToken = await getUnityAccessToken(projectId, credentials);
const [username, token] = await exchangeToken(accessToken);
let organization = core.getInput('uvcs-organization', { required: false });
if (!organization) {
organization = await getOrganization(username, token);
}
await exec.exec('cm', [`configure`, `--workingmode=SSOWorkingMode`, `--server=${organization}@cloud`, `--user=${username}`, `--token=${token}`]);
await exec.exec('cm', ['checkconnection']);
}
async function exchangeToken(accessToken) {
let output = '';
await exec.exec('curl', ['-X', 'GET', '-H', `https://www.plasticscm.com/api/oauth/unityid/exchange/${accessToken}`], {
listeners: {
stdout: (data) => {
output += data.toString();
}
},
silent: true
});
const json = JSON.parse(output);
const token = json.accessToken;
core.setSecret(token);
const username = json.user;
core.setSecret(username);
return [username, token];
}
async function getOrganization(username, token) {
const credentialsBase64 = Buffer.from(`${username}:${token}`).toString('base64');
core.setSecret(credentialsBase64);
let output = '';
await exec.exec('curl', ['-X', 'GET', '-H', `Authorization: Basic ${credentialsBase64}`, 'https://www.plasticscm.com/api/cloud/organizations'], {
listeners: {
stdout: (data) => {
output += data.toString();
}
}
});
const json = JSON.parse(output);
const organizations = json.organizations;
if (!organizations || organizations.length === 0) {
throw new Error(`Failed to get the organizations\n${output}`);
}
return organizations[0];
}
async function getUnityAccessToken(projectId, credentials) {
const credentialsBase64 = Buffer.from(credentials).toString('base64');
core.setSecret(credentialsBase64);
let output = '';
await exec.exec('curl', ['-X', 'POST', '-H', `Authorization: Basic ${credentialsBase64}`, `https://services.api.unity.com/auth/v1/token-exchange?projectId=${projectId}`], {
listeners: {
stdout: (data) => {
output += data.toString();
}
},
silent: true
});
const json = JSON.parse(output);
const accessToken = json.accessToken;
core.setSecret(accessToken);
if (!accessToken) {
throw new Error(`Failed to get the access token!\n${output}`);
}
return accessToken;
}

})();
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

124 changes: 100 additions & 24 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ async function run(): Promise<void> {
core.error(`Failed to call cm command!\n${error}`);
}
}
await configure();
await exec.exec('cm', ['checkconnection']);
try {
await testConnection();
} catch (error) {
await configure();
}
}

function getTempDirectory(): string {
Expand Down Expand Up @@ -56,13 +59,20 @@ async function getDownloadUrl(version: string): Promise<[string, string]> {

async function getToolLatestVersion(): Promise<string> {
core.info('Getting latest version...');
const response = await fetch('https://www.plasticscm.com/download');
const body = await response.text();
const versionMatch = body.match(/<strong>Version:\s*<\/strong>(\d+\.\d+\.\d+\.\d+)/);
if (!versionMatch) {
throw new Error('Failed to parse version');
let output: string = '';
await exec.exec('curl', ['-s', 'https://www.plasticscm.com/api/lastversion/after/9.0.0.0/for/cloud/windows'], {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
},
silent: !core.isDebug()
});
const json = JSON.parse(output);
const version = json.version;
if (!version) {
throw new Error('Failed to get the latest version');
}
const version = versionMatch[1];
core.info(`Latest version: ${version}`);
return version;
}
Expand Down Expand Up @@ -112,23 +122,89 @@ async function installLinux(version: string) {
await exec.exec('sudo', ['apt-get', 'install', installArg]);
}

async function testConnection() {
const expect = `Test connection executed successfully`;
let output: string = '';
await exec.exec('cm', ['checkconnection'], {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
}
});
if (!output.includes(expect)) {
throw new Error(`Test connection failed!\n${output}`);
}
}

async function configure() {
// const username = core.getInput('unity-username', { required: true });
// const password = core.getInput('unity-password', { required: true });
const clientConfPath = getPlasticClientConfPath();
core.info(`client.conf path: ${clientConfPath}`);
const clientConf = await fs.promises.readFile(clientConfPath, 'utf8');
core.info(clientConf);

const projectId = core.getInput('unity-cloud-project-id', { required: true });
const credentials = core.getInput('unity-service-account-credentials', { required: true });
const accessToken = await getUnityAccessToken(projectId, credentials);
const [username, token] = await exchangeToken(accessToken);
let organization = core.getInput('uvcs-organization', { required: false });
if (!organization) {
organization = await getOrganization(username, token);
}
await exec.exec('cm', [`configure`, `--workingmode=SSOWorkingMode`, `--server=${organization}@cloud`, `--user=${username}`, `--token=${token}`]);
await exec.exec('cm', ['checkconnection']);
}

function getPlasticClientConfPath(): string {
switch (process.platform) {
// c:\Users\<user>\AppData\Local\plastic4\client.conf
case 'win32': return path.join(process.env['APPDATA'] || '', 'plastic4', 'client.conf');
// /Users/<user>/.plastic4/client.conf
case 'darwin': return path.join(process.env['HOME'] || '', '.plastic4', 'client.conf');
// /home/<user>/.plastic4/client.conf
case 'linux': return path.join(process.env['HOME'] || '', '.plastic4', 'client.conf');
default: throw new Error(`Unsupported platform: ${process.platform}`);
async function exchangeToken(accessToken: string): Promise<[string, string]> {
let output: string = '';
await exec.exec('curl', ['-X', 'GET', '-H', `https://www.plasticscm.com/api/oauth/unityid/exchange/${accessToken}`], {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
},
silent: true
});
const json = JSON.parse(output);
const token = json.accessToken;
core.setSecret(token);
const username = json.user;
core.setSecret(username);
return [username, token];
}

async function getOrganization(username: string, token: string): Promise<string> {
const credentialsBase64 = Buffer.from(`${username}:${token}`).toString('base64');
core.setSecret(credentialsBase64);
let output: string = '';
await exec.exec('curl', ['-X', 'GET', '-H', `Authorization: Basic ${credentialsBase64}`, 'https://www.plasticscm.com/api/cloud/organizations'], {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
}
});
const json = JSON.parse(output);
const organizations = json.organizations;
if (!organizations || organizations.length === 0) {
throw new Error(`Failed to get the organizations\n${output}`);
}
}
return organizations[0];
}

async function getUnityAccessToken(projectId: string, credentials: string): Promise<string> {
const credentialsBase64 = Buffer.from(credentials).toString('base64');
core.setSecret(credentialsBase64);
let output: string = '';
await exec.exec('curl', ['-X', 'POST', '-H', `Authorization: Basic ${credentialsBase64}`, `https://services.api.unity.com/auth/v1/token-exchange?projectId=${projectId}`], {
listeners: {
stdout: (data: Buffer) => {
output += data.toString();
}
},
silent: true
});
const json = JSON.parse(output);
const accessToken = json.accessToken;
core.setSecret(accessToken);
if (!accessToken) {
throw new Error(`Failed to get the access token!\n${output}`);
}
return accessToken;
}

0 comments on commit fefdbe0

Please sign in to comment.