2023-02-27 19:05:28 +00:00
import { CoreV1Api , KubeConfig } from '@kubernetes/client-node' ;
Cloud Runner v0 - Reliable and trimmed down cloud runner (#353)
* Update cloud-runner-aws-pipeline.yml
* Update cloud-runner-k8s-pipeline.yml
* yarn build
* yarn build
* correct branch ref
* correct branch ref passed to target repo
* Create k8s-tests.yml
* Delete k8s-tests.yml
* correct branch ref passed to target repo
* correct branch ref passed to target repo
* Always describe AWS tasks for now, because unstable error handling
* Remove unused tree commands
* Use lfs guid sum
* Simple override cache push
* Simple override cache push and pull override to allow pure cloud storage driven caching
* Removal of early branch (breaks lfs caching)
* Remove unused tree commands
* Update action.yml
* Update action.yml
* Support cache and input override commands as input + full support custom hooks
* Increase k8s timeout
* replace filename being appended for unknclear reason
* cache key should not contain whitespaces
* Always try and deploy rook for k8s
* Apply k8s files for rook
* Update action.yml
* Apply k8s files for rook
* Apply k8s files for rook
* cache test and action description for kuber storage class
* Correct test and implement dependency health check and start
* GCP-secret run, cache key
* lfs smudge set explicit and undo explicit
* Run using external secret provider to speed up input
* Update cloud-runner-aws-pipeline.yml
* Add nodejs as build step dependency
* Add nodejs as build step dependency
* Cloud Runner Tests must be specified to capture logs from cloud runner for tests
* Cloud Runner Tests must be specified to capture logs from cloud runner for tests
* Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs
* Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs
* Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs
* Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs
* Refactor and cleanup - no async input, combined setup/build, removed github logs for cli runs
* better defaults for new inputs
* better defaults
* merge latest
* force build update
* use npm n to update node in unity builder
* use npm n to update node in unity builder
* use npm n to update node in unity builder
* correct new line
* quiet zipping
* quiet zipping
* default secrets for unity username and password
* default secrets for unity username and password
* ls active directory before lfs install
* Get cloud runner secrets from
* Get cloud runner secrets from
* Cleanup setup of default secrets
* Various fixes
* Cleanup setup of default secrets
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* Various fixes
* AWS secrets manager support
* less caching logs
* default k8s storage class to pd-standard
* more readable build commands
* Capture aws exit code 1 reliably
* Always replace /head from branch
* k8s default storage class to standard-rwo
* cleanup
* further cleanup input
* further cleanup input
* further cleanup input
* further cleanup input
* further cleanup input
* folder sizes to inspect caching
* dir command for local cloud runner test
* k8s wait for pending because pvc will not create earlier
* prefer k8s standard storage
* handle empty string as cloud runner cluster input
* local-system is now used for cloud runner test implementation AND correctly unset test CLI input
* local-system is now used for cloud runner test implementation AND correctly unset test CLI input
* fix unterminated quote
* fix unterminated quote
* do not share build parameters in tests - in cloud runner this will cause conflicts with resouces of the same name
* remove head and heads from branch prefix
* fix reversed caching direction of cache-push
* fixes
* fixes
* fixes
* cachePull cli
* fixes
* fixes
* fixes
* fixes
* fixes
* order cache test to be first
* order cache test to be first
* fixes
* populate cache key instead of using branch
* cleanup cli
* garbage-collect-aws cli can iterate over aws resources and cli scans all ts files
* import cli methods
* import cli files explicitly
* import cli files explicitly
* import cli files explicitly
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* import cli methods
* log parameters in cloud runner parameter test
* log parameters in cloud runner parameter test
* log parameters in cloud runner parameter test
* Cloud runner param test before caching because we have a fast local cache test now
* Using custom build path relative to repo root rather than project root
* aws-garbage-collect at end of pipeline
* aws-garbage-collect do not actually delete anything for now - just list
* remove some legacy du commands
* Update cloud-runner-aws-pipeline.yml
* log contents after cache pull and fix some scenarios with duplicate secrets
* log contents after cache pull and fix some scenarios with duplicate secrets
* log contents after cache pull and fix some scenarios with duplicate secrets
* PR comments
* Replace guid with uuid package
* use fileExists lambda instead of stat to check file exists in caching
* build failed results in core error message
* Delete sample.txt
2022-04-10 23:00:37 +00:00
import CloudRunnerLogger from '../../services/cloud-runner-logger' ;
2022-02-01 02:31:20 +00:00
import * as core from '@actions/core' ;
import waitUntil from 'async-wait-until' ;
2022-05-04 23:25:17 +00:00
import { FollowLogStreamService } from '../../services/follow-log-stream-service' ;
2023-02-16 17:24:05 +00:00
import { CloudRunnerSystem } from '../../services/cloud-runner-system' ;
2023-03-17 22:09:41 +00:00
import CloudRunner from '../../cloud-runner' ;
2022-02-01 02:31:20 +00:00
class KubernetesTaskRunner {
2023-03-07 15:00:50 +00:00
static lastReceivedTimestamp : number ;
2023-03-17 23:44:25 +00:00
static lastReceivedMessage : string = ` ` ;
2022-02-01 02:31:20 +00:00
static async runTask (
kubeConfig : KubeConfig ,
kubeClient : CoreV1Api ,
jobName : string ,
podName : string ,
containerName : string ,
namespace : string ,
) {
2023-03-24 01:04:31 +00:00
const lastReceivedMessage =
2023-03-24 22:25:55 +00:00
this . lastReceivedMessage !== ` `
? ` \ nLast Log Message " ${ this . lastReceivedMessage } " ${ this . lastReceivedTimestamp } `
: ` ` ;
2023-02-16 00:20:23 +00:00
CloudRunnerLogger . log (
2023-03-24 23:43:39 +00:00
` Streaming logs from pod: ${ podName } container: ${ containerName } namespace: ${ namespace } ${ CloudRunner . buildParameters . kubeVolumeSize } / ${ CloudRunner . buildParameters . containerCpu } / ${ CloudRunner . buildParameters . containerMemory } \ n ${ lastReceivedMessage } ` ,
2023-02-16 00:20:23 +00:00
) ;
2022-02-01 02:31:20 +00:00
let output = '' ;
let didStreamAnyLogs : boolean = false ;
2022-05-04 23:25:17 +00:00
let shouldReadLogs = true ;
let shouldCleanup = true ;
2023-02-01 23:00:41 +00:00
2023-03-25 00:19:11 +00:00
// eslint-disable-next-line no-constant-condition
while ( true ) {
let sinceTime = ` ` ;
if ( ` ${ KubernetesTaskRunner . lastReceivedTimestamp } ` !== ` ` ) {
const currentDate = new Date ( KubernetesTaskRunner . lastReceivedTimestamp ) ;
const dateTimeIsoString = currentDate . toISOString ( ) ;
2023-03-25 00:03:17 +00:00
2023-03-25 00:19:11 +00:00
// k8s compatible iso date format - split by dot - https://www.googlecloudcommunity.com/gc/Apigee/JS-for-current-timestamp-in-W3C-WSDL-date-format-YYYY-MM-DDThh/td-p/68415
const currentDateTime = dateTimeIsoString . split ( '.' ) [ 0 ] ;
const timeZoneOffset = currentDate . getTimezoneOffset ( ) ;
const positiveOffset = Math . abs ( timeZoneOffset ) ;
const timeOffsetInHours = - ( timeZoneOffset / 60 ) ;
const minZone = positiveOffset - Math . floor ( timeOffsetInHours ) * 60 ;
const symbolOffset = timeZoneOffset > 0 ? '-' : '+' ;
const hourOffset = Math . floor ( timeOffsetInHours ) < 10 ? 0 : '' ;
const minOffset = minZone < 10 ? 0 : '' ;
const tzd = ` ${ symbolOffset + hourOffset + Math . floor ( timeOffsetInHours ) } : ${ minOffset } ${ minZone } ` ;
const dateTZDformat = currentDateTime + tzd ;
sinceTime = ` --since-time=" ${ dateTZDformat } " ` ;
}
let lastMessageSeenIncludedInChunk = false ;
let lastMessageSeen = false ;
2023-03-07 16:07:01 +00:00
2023-03-25 00:19:11 +00:00
// using this instead of Kube
const logs = await CloudRunnerSystem . Run (
` kubectl logs ${ podName } -f -c ${ containerName } --timestamps ${ sinceTime } ` ,
false ,
true ,
) ;
const splitLogs = logs . split ( ` \ n ` ) ;
for ( const chunk of splitLogs ) {
if (
chunk . replace ( /\s/g , ` ` ) === KubernetesTaskRunner . lastReceivedMessage . replace ( /\s/g , ` ` ) &&
KubernetesTaskRunner . lastReceivedMessage . replace ( /\s/g , ` ` ) !== ` `
) {
CloudRunnerLogger . log ( ` Previous log message found ${ chunk } ` ) ;
lastMessageSeenIncludedInChunk = true ;
2023-03-17 22:45:05 +00:00
}
2023-03-25 00:19:11 +00:00
}
for ( const chunk of splitLogs ) {
const newDate = Date . parse ( ` ${ chunk . toString ( ) . split ( ` Z ` ) [ 0 ] } Z ` ) ;
if ( chunk . replace ( /\s/g , ` ` ) === KubernetesTaskRunner . lastReceivedMessage . replace ( /\s/g , ` ` ) ) {
lastMessageSeen = true ;
2023-03-24 23:43:39 +00:00
}
2023-03-25 00:19:11 +00:00
if ( lastMessageSeenIncludedInChunk && ! lastMessageSeen ) {
continue ;
2023-03-24 23:43:39 +00:00
}
2023-03-25 00:19:11 +00:00
didStreamAnyLogs = true ;
const message = CloudRunner . buildParameters . cloudRunnerDebug ? chunk : chunk.split ( ` Z ` ) [ 1 ] ;
KubernetesTaskRunner . lastReceivedMessage = chunk ;
KubernetesTaskRunner . lastReceivedTimestamp = newDate ;
( { shouldReadLogs , shouldCleanup , output } = FollowLogStreamService . handleIteration (
message ,
shouldReadLogs ,
shouldCleanup ,
output ,
) ) ;
}
if ( ! didStreamAnyLogs ) {
core . error ( 'Failed to stream any logs, listing namespace events, check for an error with the container' ) ;
core . error (
JSON . stringify (
{
events : ( await kubeClient . listNamespacedEvent ( namespace ) ) . body . items
. filter ( ( x ) = > {
return x . involvedObject . name === podName || x . involvedObject . name === jobName ;
} )
. map ( ( x ) = > {
return {
type : x . involvedObject . kind ,
name : x.involvedObject.name ,
message : x.message ,
} ;
} ) ,
} ,
undefined ,
4 ,
) ,
) ;
throw new Error ( ` No logs streamed from k8s ` ) ;
}
if ( FollowLogStreamService . DidReceiveEndOfTransmission ) {
CloudRunnerLogger . log ( 'end of log stream' ) ;
break ;
2022-02-01 02:31:20 +00:00
}
}
2022-04-11 22:43:41 +00:00
2022-02-01 02:31:20 +00:00
return output ;
}
static async watchUntilPodRunning ( kubeClient : CoreV1Api , podName : string , namespace : string ) {
let success : boolean = false ;
2023-03-06 18:27:58 +00:00
let message = ` ` ;
2022-02-01 02:31:20 +00:00
CloudRunnerLogger . log ( ` Watching ${ podName } ${ namespace } ` ) ;
await waitUntil (
async ( ) = > {
const status = await kubeClient . readNamespacedPodStatus ( podName , namespace ) ;
const phase = status ? . body . status ? . phase ;
success = phase === 'Running' ;
2023-03-06 18:27:58 +00:00
message = ` Phase: ${ status . body . status ? . phase } \ n Reason: ${
status . body . status ? . conditions ? . [ 0 ] . reason || ''
} \ n Message :$ { status . body . status ? . conditions ? . [ 0 ] . message || '' } ` ;
2023-03-17 21:40:36 +00:00
// CloudRunnerLogger.log(
// JSON.stringify(
// (await kubeClient.listNamespacedEvent(namespace)).body.items
// .map((x) => {
// return {
// message: x.message || ``,
// name: x.metadata.name || ``,
// reason: x.reason || ``,
// };
// })
// .filter((x) => x.name.includes(podName)),
// undefined,
// 4,
// ),
// );
2022-02-01 02:31:20 +00:00
if ( success || phase !== 'Pending' ) return true ;
2022-04-11 22:43:41 +00:00
2022-02-01 02:31:20 +00:00
return false ;
} ,
{
timeout : 2000000 ,
intervalBetweenAttempts : 15000 ,
} ,
) ;
2023-03-07 16:07:01 +00:00
if ( ! success ) {
CloudRunnerLogger . log ( message ) ;
}
2022-04-11 22:43:41 +00:00
2022-02-01 02:31:20 +00:00
return success ;
}
}
export default KubernetesTaskRunner ;