@leverege/build-tools 2.93.0-beta.16 → 2.93.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,15 +18,7 @@ npm install -g @leverege/build-tools
18
18
  ```bash
19
19
  . `build-tools --bashfun`
20
20
  ```
21
- * **docker-to-registry**: used to build and deploy application docker images to the GCP artifact registry
22
- * **pull-artifact**: used to pull generic artifacts from the GCP artifact registry
23
- ```bash
24
- Usage: pull-artifact -f <artifact schema file path> -n <package name> -v <package version>
25
- ```
26
- * **push-artifact**: used to push generic artifacts to the GCP artifact registry
27
- ```bash
28
- Usage: push-artifact -f <artifact schema file path> -n <package name> -v <package version>
29
- ```
21
+ * **docker-to-registry**: used to build and deploy docker images to the GCP image area
30
22
 
31
23
  ### Helm Helpers:
32
24
  * **overwhelm**: a k8s context helper and yaml replacer *${REPLACE_ME}*
@@ -65,15 +57,6 @@ Usage: k8scale <up|dn|down|roll> [services]
65
57
  * **prune-git**: performs maintenance on a list of git repositories.
66
58
  * **pull-git**: walks a list of git repos, pulling each if they are clean.
67
59
 
68
- ### Troubleshooting:
69
-
70
- For additional debug level logging, set the environment variable `BUILD_TOOLS_DEBUG=1` before running the tool.
71
-
72
- Example:
73
- ```bash
74
- BUILD_TOOLS_DEBUG=1 docker-to-registry
75
- ```
76
-
77
60
  ## Authors
78
61
 
79
62
  * **DevOps and Friends**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leverege/build-tools",
3
- "version": "2.93.0-beta.16",
3
+ "version": "2.93.0-beta.3",
4
4
  "description": "A collection of build / support tools for Leverege developers",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -50,13 +50,12 @@
50
50
  "npm-link": "src/npm-link.sh",
51
51
  "npm-unlink": "src/npm-unlink.sh",
52
52
  "overwhelm": "src/overwhelm.mjs",
53
+ "package-artifact": "src/package-artifact/Command.mjs",
53
54
  "pkgck": "src/pkgck.sh",
54
55
  "pkglint": "src/pkglint.sh",
55
56
  "prepack": "src/prepack.mjs",
56
57
  "prune-git": "src/prune-git.sh",
57
- "pull-artifact": "src/artifacts/pull-artifact.mjs",
58
58
  "pull-git": "src/pull-git.sh",
59
- "push-artifact": "src/artifacts/push-artifact.mjs",
60
59
  "push-my-chart": "src/push-my-chart.mjs",
61
60
  "refresh-npm-token": "src/refresh-npm-token.mjs",
62
61
  "refresh-py-idx": "src/refresh-py-idx.sh",
@@ -0,0 +1,49 @@
1
+ import { warning } from '../Utils.mjs'
2
+
3
+ import { getArtifactEntriesSchema, flattenArtifactEntriesSchema } from './Utils.mjs'
4
+ import { GenericArtifactEntryPackager } from './Packager.mjs'
5
+
6
+ async function main() {
7
+
8
+ // If an argument is provided, use it as the artifacts.yml file path
9
+ const artifactsYmlPath = process.argv[2]
10
+ const artifactPackageName = process.argv[3]
11
+ const artifactPackageVersion = process.argv[4]
12
+
13
+ if ( artifactsYmlPath ) {
14
+ const artifactsSchema = await getArtifactEntriesSchema( artifactsYmlPath )
15
+ const flattenedArtifactsSchema = flattenArtifactEntriesSchema( artifactsSchema )
16
+
17
+ // Open for unexpected matches if we end up re-using the same package name and version for
18
+ // multiple artifacts spread across multiple repositories and/or projects.
19
+ const filteredArtifactSchemas = flattenedArtifactsSchema.filter(
20
+ artifact => artifact.gcp_artifact_registry_package_name === artifactPackageName &&
21
+ artifact.gcp_artifact_registry_package_version === artifactPackageVersion )
22
+
23
+ if ( filteredArtifactSchemas.length === 0 ) {
24
+ console.error( `No artifact entries found for ${artifactPackageName} version ${artifactPackageVersion} in ${artifactsYmlPath}` )
25
+ process.exit( 1 )
26
+ }
27
+ for ( const artifactSchema of filteredArtifactSchemas ) {
28
+ let packager
29
+ switch ( artifactSchema.type ) {
30
+ case 'generic':
31
+ packager = new GenericArtifactEntryPackager( artifactSchema )
32
+ break
33
+ default:
34
+ warning( `Unsupported artifact type "${artifactSchema.type}"` )
35
+ continue
36
+ }
37
+
38
+ if ( !packager ) {
39
+ warning( `Unsupported artifact type "${artifactSchema.type}"` )
40
+ continue
41
+ }
42
+
43
+ // eslint-disable-next-line no-await-in-loop
44
+ await packager.package()
45
+ }
46
+ }
47
+ }
48
+
49
+ main()
@@ -0,0 +1,118 @@
1
+ import crypto from 'crypto'
2
+ import fs from 'fs'
3
+
4
+ class ArtifactPackager {
5
+ constructor( artifact ) {
6
+ this.artifact = artifact
7
+ }
8
+
9
+ calculateHash() {
10
+ try {
11
+ const hash = crypto.createHash( 'sha256' )
12
+ hash.update( fs.readFileSync( this.artifact.source_path ) )
13
+ return hash.digest( 'base64url' )
14
+ } catch ( error ) {
15
+ throw new Error( `Failed to calculate hash for artifact ${this.artifact.name}: ${error.message}` )
16
+ }
17
+ }
18
+
19
+ async exists() {
20
+ throw new Error( 'Not implemented' )
21
+ }
22
+
23
+ async build() {
24
+ throw new Error( 'Not implemented' )
25
+ }
26
+
27
+ async publish() {
28
+ throw new Error( 'Not implemented' )
29
+ }
30
+
31
+ async package() {
32
+ if ( await this.exists() ) {
33
+ console.log( 'Artifact already exists in the registry' )
34
+ return false
35
+ }
36
+ await this.build()
37
+ return this.publish()
38
+ }
39
+ }
40
+
41
+ class GenericArtifactPackager extends ArtifactPackager {
42
+
43
+ async exists() {
44
+ // Check if artifact already exists in the registry
45
+ console.log( 'Checking if artifact already exists in the registry...' )
46
+
47
+ const artifactHash = this.calculateHash()
48
+
49
+ const command = `gcloud artifacts files list \
50
+ --project=${this.artifact.gcp_project_id} \
51
+ --location=${this.artifact.gcp_location} \
52
+ --repository=${this.artifact.gcp_artifact_registry_repository_name} \
53
+ --package=${this.artifact.gcp_artifact_registry_package_name} \
54
+ --version ${this.artifact.gcp_artifact_registry_package_version} \
55
+ --filter="name:${this.artifact.destination_path}" \
56
+ --format="json"`
57
+
58
+ try {
59
+ const { execSync } = await import( 'child_process' )
60
+ const result = execSync( command, { encoding : 'utf8', stdio : 'pipe' } )
61
+
62
+ // Parse the JSON response
63
+ const files = JSON.parse( result )
64
+
65
+ // Check if any files were found and match our hash
66
+ if ( files && files.length > 0 ) {
67
+ for ( const file of files ) {
68
+ for ( const hash of file.hashes ) {
69
+ // Compare without padding since base64url may omit trailing '='
70
+ const normalizedOurHash = artifactHash.replace( /=+$/, '' )
71
+ const normalizedRegistryHash = hash.value.replace( /=+$/, '' )
72
+ if ( hash.type === 'SHA256' && normalizedRegistryHash === normalizedOurHash ) {
73
+ console.log( `Artifact already exists in the registry: ${this.artifact.gcp_artifact_registry_package_name}:${this.artifact.gcp_artifact_registry_package_version}` )
74
+ return true
75
+ }
76
+ }
77
+ }
78
+ }
79
+ console.log( 'Artifact does not exist in the registry' )
80
+ return false
81
+
82
+ } catch ( error ) {
83
+ console.error( 'Error checking artifact existence:', error.message )
84
+ throw error
85
+ }
86
+ }
87
+
88
+ async build() {
89
+ return true
90
+ }
91
+
92
+ async publish() {
93
+ console.log( 'Publishing artifact to registry...' )
94
+
95
+ const command = `gcloud artifacts generic upload \
96
+ --project=${this.artifact.gcp_project_id} \
97
+ --location=${this.artifact.gcp_location} \
98
+ --repository=${this.artifact.gcp_artifact_registry_repository_name} \
99
+ --package=${this.artifact.gcp_artifact_registry_package_name} \
100
+ --version=${this.artifact.gcp_artifact_registry_package_version} \
101
+ --source=${this.artifact.source_path} \
102
+ --destination-path=${this.artifact.destination_path}`
103
+
104
+ // console.log( 'Command:', command )
105
+
106
+ try {
107
+ const { execSync } = await import( 'child_process' )
108
+ execSync( command, { encoding : 'utf8', stdio : 'pipe' } )
109
+ console.log( `Successfully published artifact: ${this.artifact.gcp_artifact_registry_package_name}:${this.artifact.gcp_artifact_registry_package_version}` )
110
+ return true
111
+ } catch ( error ) {
112
+ console.error( 'Error publishing artifact:', error.message )
113
+ return false
114
+ }
115
+ }
116
+ }
117
+
118
+ export { ArtifactPackager, GenericArtifactPackager }
@@ -0,0 +1,60 @@
1
+ import { assign, object, string, array, optional, enums, union } from 'superstruct'
2
+
3
+ const ArtifactTypeEnum = enums( [ 'generic' ] )
4
+
5
+ // Superstruct schema for individual artifact configuration
6
+ const PartialArtifactEntrySchema = object( {
7
+ name : string(),
8
+ type : optional( ArtifactTypeEnum ),
9
+ gcp_project_id : optional( string() ),
10
+ gcp_location : optional( string() ),
11
+ gcp_artifact_registry_repository_name : optional( string() ),
12
+ gcp_artifact_registry_package_name : optional( string() ),
13
+ gcp_artifact_registry_package_version : optional( string() )
14
+ } )
15
+
16
+ const ArtifactEntrySchema = assign( PartialArtifactEntrySchema, object( {
17
+ name : string(),
18
+ type : ArtifactTypeEnum,
19
+ gcp_project_id : string(),
20
+ gcp_location : string(),
21
+ gcp_artifact_registry_repository_name : string(),
22
+ gcp_artifact_registry_package_name : string(),
23
+ gcp_artifact_registry_package_version : string()
24
+ } ) )
25
+
26
+ const PartialGenericArtifactEntrySchema = assign( PartialArtifactEntrySchema, object( {
27
+ type : enums( [ 'generic' ] ),
28
+ source_path : string(),
29
+ destination_path : string()
30
+ } ) )
31
+
32
+ const GenericArtifactEntrySchema = assign( ArtifactEntrySchema, object( {
33
+ source_path : string(),
34
+ destination_path : string()
35
+ } ) )
36
+
37
+ // Superstruct schema for defaults configuration
38
+ const DefaultsSchema = object( {
39
+ gcp_project_id : optional( string() ),
40
+ gcp_location : optional( string() ),
41
+ gcp_artifact_registry_repository_name : optional( string() ),
42
+ gcp_artifact_registry_package_name : optional( string() ),
43
+ gcp_artifact_registry_package_version : optional( string() )
44
+ } )
45
+
46
+ // Superstruct schema for the complete artifacts configuration with defaults
47
+ const ArtifactEntriesSchema = object( {
48
+ defaults : DefaultsSchema,
49
+ artifacts : array( union( [ PartialArtifactEntrySchema, PartialGenericArtifactEntrySchema ] ) )
50
+ } )
51
+
52
+ const FlattenedArtifactEntriesSchema = array( assign( union( [ ArtifactEntrySchema, GenericArtifactEntrySchema ] ) ) )
53
+
54
+ export {
55
+ ArtifactEntriesSchema,
56
+ FlattenedArtifactEntriesSchema,
57
+ ArtifactEntrySchema,
58
+ DefaultsSchema,
59
+ GenericArtifactEntrySchema,
60
+ }
@@ -0,0 +1,49 @@
1
+ import { validate } from 'superstruct'
2
+
3
+ import { parseYamlFile, warning } from '../Utils.mjs'
4
+
5
+ import { ArtifactEntriesSchema, GenericArtifactEntrySchema } from './Structs.mjs'
6
+
7
+ const getArtifactEntriesSchema = async ( artifactsConfigPath = './artifacts.yml' ) => {
8
+ const artifactsConfig = await parseYamlFile( artifactsConfigPath )
9
+ const [ error, result ] = validate( artifactsConfig, ArtifactEntriesSchema )
10
+ if ( error ) {
11
+ throw new Error( `Invalid artifacts.yml file: ${error.message}` )
12
+ }
13
+ return result
14
+ }
15
+
16
+ const flattenArtifactEntriesSchema = ( artifactsSchema ) => {
17
+ return artifactsSchema.artifacts.reduce( ( flattenedArtifacts, artifact ) => {
18
+ const mergedArtifact = {
19
+ ...artifactsSchema.defaults,
20
+ ...artifact
21
+ }
22
+
23
+ let schema
24
+ switch ( mergedArtifact.type ) {
25
+ case 'generic':
26
+ schema = GenericArtifactEntrySchema
27
+ break
28
+ default:
29
+ warning( `Unsupported artifact type "${mergedArtifact.type}"` )
30
+ break
31
+ }
32
+
33
+ if ( !schema ) {
34
+ warning( `Invalid artifact type "${mergedArtifact.type}"` )
35
+ return flattenedArtifacts
36
+ }
37
+
38
+ const [ error, validatedArtifact ] = validate( mergedArtifact, schema )
39
+
40
+ if ( error ) {
41
+ warning( `Invalid artifact "${mergedArtifact.name}": ${error.message}` )
42
+ return flattenedArtifacts
43
+ }
44
+
45
+ return [ ...flattenedArtifacts, validatedArtifact ]
46
+ }, [] )
47
+ }
48
+
49
+ export { getArtifactEntriesSchema, flattenArtifactEntriesSchema }
@@ -1,177 +0,0 @@
1
- /* eslint-disable security/detect-non-literal-fs-filename */
2
- import fs from 'node:fs'
3
-
4
- import { shellCmd, log, debug, err } from '../Utils.mjs'
5
-
6
- import { toExplicitPath, deduplicatePath, convertGenericArtifactNameToPath } from './Utils.mjs'
7
-
8
- class ArtifactPuller {
9
- constructor( artifact ) {
10
- this.artifact = artifact
11
- }
12
-
13
- async pull() {
14
- throw new Error( 'Not implemented' )
15
- }
16
- }
17
-
18
- class GenericArtifactPuller extends ArtifactPuller {
19
-
20
- async prepareFileDownload( tmpFilePath, finalFilePath ) {
21
- // Ensure directories exist
22
- fs.mkdirSync( tmpFilePath.split( '/' ).slice( 0, -1 ).join( '/' ), { recursive : true } )
23
- fs.mkdirSync( finalFilePath.split( '/' ).slice( 0, -1 ).join( '/' ), { recursive : true } )
24
-
25
- // Ensure no conflicting files exist
26
- if ( fs.existsSync( tmpFilePath ) ) {
27
- fs.unlinkSync( tmpFilePath )
28
- }
29
- if ( fs.existsSync( finalFilePath ) ) {
30
- fs.unlinkSync( finalFilePath )
31
- }
32
- }
33
-
34
- async cleanupFileDownload( tmpFilePath, finalFilePath ) {
35
- // Delete the tmp file
36
- if ( fs.existsSync( tmpFilePath ) ) {
37
- fs.unlinkSync( tmpFilePath )
38
- }
39
- // Delete the tmp directory
40
- if ( fs.existsSync( tmpFilePath.split( '/' ).slice( 0, -1 ).join( '/' ) ) ) {
41
- fs.rmdirSync( tmpFilePath.split( '/' ).slice( 0, -1 ).join( '/' ), { recursive : true } )
42
- }
43
- }
44
-
45
- async downloadFile( remoteFilePath, localFilePath ) {
46
- debug( { remoteFilePath, localFilePath }, '<==Download File' )
47
-
48
- // 1 dir per file to avoid dir creation and deletion race conditions for files in the same directory
49
- const tmpDirectoryPath = `/tmp/${remoteFilePath.replaceAll( '/', '_' ).replaceAll( '.', '_' )}`
50
- const tmpFilePath = `${tmpDirectoryPath}/${remoteFilePath.split( '/' ).pop()}`
51
-
52
- await this.prepareFileDownload( tmpFilePath, localFilePath )
53
-
54
- try {
55
- const command = `gcloud artifacts generic download \
56
- --project=${this.artifact.gcp_project_id} \
57
- --location=${this.artifact.gcp_location} \
58
- --repository=${this.artifact.gcp_artifact_registry_repository_name} \
59
- --package=${this.artifact.gcp_artifact_registry_package_name} \
60
- --version=${this.artifact.gcp_artifact_registry_package_version} \
61
- --name="${remoteFilePath}" \
62
- --destination=${tmpDirectoryPath}`
63
-
64
- debug( { command }, '<==Download File Command' )
65
-
66
- await shellCmd( command )
67
-
68
- // Check if file exists in the tmp path
69
- if ( !fs.existsSync( tmpFilePath ) ) {
70
- debug( { tmpFilePath }, '<==File does not exist in the tmp path' )
71
- return false
72
- }
73
-
74
- debug( { tmpFilePath, localFilePath }, '<==Copying file' )
75
-
76
- await fs.copyFileSync( tmpFilePath, localFilePath )
77
-
78
- // Check if file exists in the local path
79
- if ( !fs.existsSync( localFilePath ) ) {
80
- debug( { localFilePath }, '<==File does not exist in the local path' )
81
- return false
82
- }
83
-
84
- return true
85
- } catch ( error ) {
86
- err( `Error downloading file: ${remoteFilePath} ${error.message}` )
87
- return false
88
- } finally {
89
- await this.cleanupFileDownload( tmpFilePath, localFilePath )
90
- }
91
- }
92
-
93
- async downloadDirectory( localPath ) {
94
- const command = `gcloud artifacts generic download \
95
- --project=${this.artifact.gcp_project_id} \
96
- --location=${this.artifact.gcp_location} \
97
- --repository=${this.artifact.gcp_artifact_registry_repository_name} \
98
- --package=${this.artifact.gcp_artifact_registry_package_name} \
99
- --version=${this.artifact.gcp_artifact_registry_package_version} \
100
- --destination=${localPath}`
101
-
102
- debug( { command }, '<==Download Directory Command' )
103
-
104
- await shellCmd( command )
105
- }
106
-
107
- async downloadNestedDirectory( remotePath, localPath ) {
108
- debug( { remotePath, localPath }, '<==Download Nested Directory' )
109
-
110
- const files = await this.listFiles( remotePath )
111
- await Promise.all(
112
- files
113
- .filter( file => convertGenericArtifactNameToPath( file.name ).startsWith( remotePath ) )
114
- .map( async ( file ) => {
115
- const remoteFilePath = convertGenericArtifactNameToPath( file.name )
116
- const localFilePath = deduplicatePath( `${localPath}/${remoteFilePath.replaceAll( remotePath, '' )}` )
117
- await this.downloadFile( remoteFilePath, localFilePath )
118
- } )
119
- )
120
- }
121
-
122
- async listFiles( ) {
123
- const command = `gcloud artifacts files list \
124
- --project=${this.artifact.gcp_project_id} \
125
- --location=${this.artifact.gcp_location} \
126
- --repository=${this.artifact.gcp_artifact_registry_repository_name} \
127
- --package=${this.artifact.gcp_artifact_registry_package_name} \
128
- --version ${this.artifact.gcp_artifact_registry_package_version} \
129
- --format="json"`
130
-
131
- debug( { command }, '<==List Files Command' )
132
-
133
- const result = await shellCmd( command )
134
-
135
- // Parse the JSON response
136
- const files = JSON.parse( result )
137
- return files
138
- }
139
-
140
- async pull() {
141
- log( `Pulling artifact: ${this.artifact.name}` )
142
-
143
- debug( { artifact : this.artifact }, '<==Pulling artifact' )
144
-
145
- try {
146
- if ( this.artifact.remote_file_path && this.artifact.local_file_path ) {
147
- await this.downloadFile( this.artifact.remote_file_path, toExplicitPath( this.artifact.local_file_path ) )
148
- } else if ( this.artifact.remote_file_path && this.artifact.local_path ) {
149
- await this.downloadFile( this.artifact.remote_file_path, toExplicitPath( `${this.artifact.local_path}/${this.artifact.remote_file_path.split( '/' ).pop()}` ) )
150
- } else if ( this.artifact.remote_path && this.artifact.local_path ) {
151
- await this.downloadNestedDirectory( this.artifact.remote_path, toExplicitPath( this.artifact.local_path ) )
152
- } else if ( this.artifact.remote_path && !this.artifact.local_path && !this.artifact.local_file_path ) {
153
- await this.downloadDirectory( this.artifact.remote_path, '.' )
154
- } else if ( !this.artifact.remote_path && !this.artifact.remote_file_path && this.artifact.local_path ) {
155
- await this.downloadDirectory( toExplicitPath( this.artifact.local_path ) )
156
- } else {
157
- throw new Error( 'No valid paths provided' )
158
- }
159
-
160
- log( `Successfully pulled artifact: ${this.artifact.name}` )
161
-
162
- return true
163
-
164
- // if ( !this.artifact.remote_path && !this.artifact.remote_file_path && this.local_file_path ) {
165
- // throw new Error( '' )
166
- // }
167
- // if ( this.artifact.remote_path && this.artifact.local_file_path ) {
168
- // throw new Error( '' )
169
- // }
170
- } catch ( error ) {
171
- err( `Error pulling artifact: ${this.artifact.name} ${error.message}` )
172
- }
173
- return false
174
- }
175
- }
176
-
177
- export { ArtifactPuller, GenericArtifactPuller }
@@ -1,86 +0,0 @@
1
- /* eslint-disable security/detect-non-literal-fs-filename */
2
- import { shellCmd, log, err, debug } from '../Utils.mjs'
3
-
4
- import { convertGenericArtifactNameToPath } from './Utils.mjs'
5
-
6
- class ArtifactPusher {
7
- constructor( artifact ) {
8
- this.artifact = artifact
9
- }
10
-
11
- async push() {
12
- throw new Error( 'Not implemented' )
13
- }
14
- }
15
-
16
- class GenericArtifactPusher extends ArtifactPusher {
17
-
18
- async listFiles( ) {
19
- const command = `gcloud artifacts files list \
20
- --project=${this.artifact.gcp_project_id} \
21
- --location=${this.artifact.gcp_location} \
22
- --repository=${this.artifact.gcp_artifact_registry_repository_name} \
23
- --package=${this.artifact.gcp_artifact_registry_package_name} \
24
- --version ${this.artifact.gcp_artifact_registry_package_version} \
25
- --format="json"`
26
-
27
- debug( { command }, '<==List Files Command' )
28
-
29
- const result = await shellCmd( command )
30
-
31
- // Parse the JSON response
32
- const files = JSON.parse( result )
33
-
34
- return files
35
- }
36
-
37
- async push() {
38
- log( `Pushing artifact: ${this.artifact.name}` )
39
-
40
- if ( this.artifact.remote_file_path ) {
41
- err( 'Remote file path is not supported for pushing artifacts' )
42
- return false
43
- }
44
-
45
- try {
46
-
47
- let command = ''
48
-
49
- command = `gcloud artifacts generic upload \
50
- --project=${this.artifact.gcp_project_id} \
51
- --location=${this.artifact.gcp_location} \
52
- --repository=${this.artifact.gcp_artifact_registry_repository_name} \
53
- --package=${this.artifact.gcp_artifact_registry_package_name} \
54
- --version=${this.artifact.gcp_artifact_registry_package_version}`
55
-
56
- if ( this.artifact.local_path ) {
57
- command += ` --source-directory=${this.artifact.local_path}`
58
- command += ' --skip-existing'
59
- } else if ( this.artifact.local_file_path ) {
60
- const files = await this.listFiles( this.artifact.remote_path )
61
- if ( files.some( file => convertGenericArtifactNameToPath( file.name ) === `${this.artifact.remote_path}/${this.artifact.remote_file_path.split( '/' ).pop()}` ) ) {
62
- err( `File already exists in the artifact registry: ${this.artifact.local_file_path}` )
63
- return false
64
- }
65
- command += ` --source=${this.artifact.local_file_path}`
66
- }
67
-
68
- if ( this.artifact.remote_path ) {
69
- command += ` --destination-path=${this.artifact.remote_path}`
70
- }
71
-
72
- debug( { command }, '<==Upload Command' )
73
-
74
- await shellCmd( command )
75
-
76
- log( `Successfully pushed artifact: ${this.artifact.name}` )
77
-
78
- return true
79
- } catch ( error ) {
80
- err( `Error pushing artifact: ${this.artifact.name} ${error.message}` )
81
- return false
82
- }
83
- }
84
- }
85
-
86
- export { ArtifactPusher, GenericArtifactPusher }
@@ -1,91 +0,0 @@
1
- import { assign, object, string, array, optional, enums, union, refine } from 'superstruct'
2
-
3
- const ArtifactTypeEnum = {
4
- GENERIC : 'generic',
5
- }
6
-
7
- // Superstruct schema for individual artifact configuration
8
- const PartialArtifactSchema = object( {
9
- name : string(),
10
- type : optional( enums( Object.values( ArtifactTypeEnum ) ) ),
11
- gcp_project_id : optional( string() ),
12
- gcp_location : optional( string() ),
13
- gcp_artifact_registry_repository_name : optional( string() ),
14
- gcp_artifact_registry_package_name : optional( string() ),
15
- gcp_artifact_registry_package_version : optional( string() )
16
- } )
17
-
18
- const ArtifactSchema = assign( PartialArtifactSchema, object( {
19
- name : string(),
20
- type : enums( Object.values( ArtifactTypeEnum ) ),
21
- gcp_project_id : string(),
22
- gcp_location : string(),
23
- gcp_artifact_registry_repository_name : string(),
24
- gcp_artifact_registry_package_name : string(),
25
- gcp_artifact_registry_package_version : string()
26
- } ) )
27
-
28
- const PartialGenericArtifactSchema = refine(
29
- assign( PartialArtifactSchema, object( {
30
- type : enums( [ ArtifactTypeEnum.GENERIC ] ),
31
- local_file_path : optional( string() ),
32
- local_path : optional( string() ),
33
- remote_file_path : optional( string() ),
34
- remote_path : optional( string() )
35
- } ) ),
36
- 'ConflictingPaths',
37
- ( value ) => {
38
- if ( value.local_file_path && value.local_path ) {
39
- return 'Only one of local_file_path or local_path must be provided'
40
- }
41
- if ( value.remote_file_path && value.remote_path ) {
42
- return 'Only one of remote_file_path or remote_path must be provided'
43
- }
44
- return true
45
- }
46
- )
47
-
48
- const GenericArtifactSchema = refine(
49
- assign( ArtifactSchema, object( {
50
- local_file_path : optional( string() ),
51
- local_path : optional( string() ),
52
- remote_file_path : optional( string() ),
53
- remote_path : optional( string() )
54
- } ) ),
55
- 'ConflictingPaths',
56
- ( value ) => {
57
- if ( value.local_file_path && value.local_path ) {
58
- return 'Only one of local_file_path or local_path must be provided'
59
- }
60
- if ( value.remote_file_path && value.remote_path ) {
61
- return 'Only one of remote_file_path or remote_path must be provided'
62
- }
63
- return true
64
- }
65
- )
66
-
67
- // Superstruct schema for defaults configuration
68
- const DefaultsSchema = object( {
69
- gcp_project_id : optional( string() ),
70
- gcp_location : optional( string() ),
71
- gcp_artifact_registry_repository_name : optional( string() ),
72
- gcp_artifact_registry_package_name : optional( string() ),
73
- gcp_artifact_registry_package_version : optional( string() )
74
- } )
75
-
76
- // Superstruct schema for the complete artifacts configuration with defaults
77
- const ArtifactsSchema = object( {
78
- defaults : optional( DefaultsSchema ),
79
- artifacts : array( union( [ PartialArtifactSchema, PartialGenericArtifactSchema ] ) )
80
- } )
81
-
82
- const FlattenedArtifactsSchema = array( assign( union( [ ArtifactSchema, GenericArtifactSchema ] ) ) )
83
-
84
- export {
85
- ArtifactTypeEnum,
86
- ArtifactsSchema,
87
- FlattenedArtifactsSchema,
88
- ArtifactSchema,
89
- DefaultsSchema,
90
- GenericArtifactSchema,
91
- }
@@ -1,92 +0,0 @@
1
- import fs from 'node:fs'
2
- import crypto from 'crypto'
3
-
4
- import { validate } from 'superstruct'
5
-
6
- import { parseYamlFile, warning } from '../Utils.mjs'
7
-
8
- import { ArtifactTypeEnum, ArtifactsSchema, GenericArtifactSchema } from './Structs.mjs'
9
-
10
- const getArtifactsSchema = async ( artifactsFilePath = './artifacts.yml' ) => {
11
- const artifactsConfig = await parseYamlFile( artifactsFilePath )
12
- const [ error, result ] = validate( artifactsConfig, ArtifactsSchema )
13
- if ( error ) {
14
- throw new Error( `Invalid artifacts.yml file: ${error.message}` )
15
- }
16
- return result
17
- }
18
-
19
- const flattenArtifactsSchema = ( artifactsSchema ) => {
20
- return artifactsSchema.artifacts.reduce( ( flattenedArtifacts, artifact ) => {
21
- const mergedArtifact = {
22
- ...artifactsSchema.defaults,
23
- ...artifact
24
- }
25
-
26
- let schema
27
- switch ( mergedArtifact.type ) {
28
- case ArtifactTypeEnum.GENERIC:
29
- schema = GenericArtifactSchema
30
- break
31
- default:
32
- warning( `Unsupported artifact type "${mergedArtifact.type}"` )
33
- break
34
- }
35
-
36
- if ( !schema ) {
37
- warning( `Invalid artifact type "${mergedArtifact.type}"` )
38
- return flattenedArtifacts
39
- }
40
-
41
- const [ error, validatedArtifact ] = validate( mergedArtifact, schema )
42
-
43
- if ( error ) {
44
- warning( `Invalid artifact "${mergedArtifact.name}": ${error.message}` )
45
- return flattenedArtifacts
46
- }
47
-
48
- return [ ...flattenedArtifacts, validatedArtifact ]
49
- }, [] )
50
- }
51
-
52
- const toImplicitPath = ( path ) => {
53
- let implicitPath = path
54
- // Remove leading './' if present
55
- if ( implicitPath.startsWith( './' ) ) {
56
- implicitPath = implicitPath.slice( 2 )
57
- }
58
- // Remove trailing slash
59
- implicitPath = implicitPath.replace( /\/+$/, '' )
60
- return implicitPath
61
- }
62
-
63
- const toExplicitPath = ( path ) => {
64
- let explicitPath = path
65
- // Keep absolute paths and paths already starting with '.' as-is
66
- if ( !path.startsWith( '/' ) && !path.startsWith( '.' ) ) {
67
- // Add './' to relative paths
68
- explicitPath = `./${explicitPath}`
69
- }
70
- return explicitPath
71
- }
72
-
73
- const deduplicatePath = ( path ) => {
74
- return path.replaceAll( '//', '/' )
75
- }
76
-
77
- const convertGenericArtifactNameToPath = ( genericArtifactName ) => {
78
- return genericArtifactName.split( ':' ).pop().replaceAll( '%2F', '/' ).replaceAll( '%3A', ':' )
79
- }
80
-
81
- const calculateHash = ( path ) => {
82
- try {
83
- const hash = crypto.createHash( 'sha256' )
84
- hash.update( fs.readFileSync( path ) )
85
- return hash.digest( 'base64url' )
86
- } catch ( error ) {
87
- console.error( error )
88
- throw new Error( `Failed to calculate hash for artifact ${path}: ${error.message}` )
89
- }
90
- }
91
-
92
- export { calculateHash, getArtifactsSchema, flattenArtifactsSchema, toImplicitPath, toExplicitPath, deduplicatePath, convertGenericArtifactNameToPath }
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { program } from 'commander'
4
-
5
- import { warning, log } from '../Utils.mjs'
6
-
7
- import { getArtifactsSchema, flattenArtifactsSchema } from './Utils.mjs'
8
- import { ArtifactTypeEnum } from './Structs.mjs'
9
- import { GenericArtifactPuller } from './ArtifactPuller.mjs'
10
-
11
- program
12
- .name( 'pull-artifact' )
13
- .description( 'Pull artifacts from GCP Artifact Registry' )
14
- .requiredOption( '-f, --file-path <path>', 'Path to artifacts.yaml file' )
15
- .option( '-n, --package-name <name>', 'Filter by artifact package name' )
16
- .option( '-v, --package-version <version>', 'Filter by artifact package version' )
17
- .parse()
18
-
19
- const options = program.opts()
20
-
21
- async function main() {
22
- const artifactsYmlPath = options.filePath
23
- const artifactPackageName = options.packageName || null
24
- const artifactPackageVersion = options.packageVersion || null
25
-
26
- const artifactsSchema = await getArtifactsSchema( artifactsYmlPath )
27
- const flattenedArtifactsSchema = flattenArtifactsSchema( artifactsSchema )
28
-
29
- let filteredArtifactsSchema
30
- if ( artifactPackageName && artifactPackageVersion ) {
31
- filteredArtifactsSchema = flattenedArtifactsSchema.filter(
32
- artifact => artifact.gcp_artifact_registry_package_name === artifactPackageName &&
33
- artifact.gcp_artifact_registry_package_version === artifactPackageVersion )
34
- } else if ( artifactPackageName ) {
35
- filteredArtifactsSchema = flattenedArtifactsSchema.filter(
36
- artifact => artifact.gcp_artifact_registry_package_name === artifactPackageName )
37
- } else {
38
- filteredArtifactsSchema = flattenedArtifactsSchema
39
- }
40
-
41
- let counter = 0
42
-
43
- const pullResults = await Promise.all(
44
- filteredArtifactsSchema.map( async ( artifactSchema ) => {
45
- let puller
46
- switch ( artifactSchema.type ) {
47
- case ArtifactTypeEnum.GENERIC:
48
- puller = new GenericArtifactPuller( artifactSchema )
49
- break
50
- default:
51
- warning( `Unsupported artifact type "${artifactSchema.type}"` )
52
- return false
53
- }
54
-
55
- if ( !puller ) {
56
- warning( `Unsupported artifact type "${artifactSchema.type}"` )
57
- return false
58
- }
59
-
60
- try {
61
- return await puller.pull()
62
- } catch ( error ) {
63
- warning( `Error pulling artifact "${artifactSchema.name}": ${error.message}` )
64
- return false
65
- }
66
- } )
67
- )
68
-
69
- counter = pullResults.filter( Boolean ).length
70
-
71
- log( `Successfully pulled ${counter}/${filteredArtifactsSchema.length} artifacts` )
72
- }
73
-
74
- main()
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { program } from 'commander'
4
-
5
- import { warning, log } from '../Utils.mjs'
6
-
7
- import { getArtifactsSchema, flattenArtifactsSchema } from './Utils.mjs'
8
- import { ArtifactTypeEnum } from './Structs.mjs'
9
- import { GenericArtifactPusher } from './ArtifactPusher.mjs'
10
-
11
- program
12
- .name( 'push-artifact' )
13
- .description( 'Push artifacts to GCP Artifact Registry' )
14
- .requiredOption( '-f, --file-path <path>', 'Path to artifacts.yaml file' )
15
- .option( '-n, --package-name <name>', 'Filter by artifact package name' )
16
- .option( '-v, --package-version <version>', 'Filter by artifact package version' )
17
- .parse()
18
-
19
- const options = program.opts()
20
-
21
- async function main() {
22
- const artifactsYmlPath = options.filePath
23
- const artifactPackageName = options.packageName || null
24
- const artifactPackageVersion = options.packageVersion || null
25
-
26
- const artifactsSchema = await getArtifactsSchema( artifactsYmlPath )
27
- const flattenedArtifactsSchema = flattenArtifactsSchema( artifactsSchema )
28
-
29
- let filteredArtifactsSchema
30
- if ( artifactPackageName && artifactPackageVersion ) {
31
- filteredArtifactsSchema = flattenedArtifactsSchema.filter(
32
- artifact => artifact.gcp_artifact_registry_package_name === artifactPackageName &&
33
- artifact.gcp_artifact_registry_package_version === artifactPackageVersion )
34
- } else if ( artifactPackageName ) {
35
- filteredArtifactsSchema = flattenedArtifactsSchema.filter(
36
- artifact => artifact.gcp_artifact_registry_package_name === artifactPackageName )
37
- } else {
38
- filteredArtifactsSchema = flattenedArtifactsSchema
39
- }
40
-
41
- let counter = 0
42
-
43
- const pushResults = await Promise.all(
44
- filteredArtifactsSchema.map( async ( artifactSchema ) => {
45
- let pusher
46
- switch ( artifactSchema.type ) {
47
- case ArtifactTypeEnum.GENERIC:
48
- pusher = new GenericArtifactPusher( artifactSchema )
49
- break
50
- default:
51
- warning( `Unsupported artifact type "${artifactSchema.type}"` )
52
- return false
53
- }
54
-
55
- if ( !pusher ) {
56
- warning( `Unsupported artifact type "${artifactSchema.type}"` )
57
- return false
58
- }
59
-
60
- try {
61
- return await pusher.push()
62
- } catch ( error ) {
63
- warning( `Error pushing artifact "${artifactSchema.name}": ${error.message}` )
64
- return false
65
- }
66
- } )
67
- )
68
-
69
- counter = pushResults.filter( Boolean ).length
70
-
71
- log( `Successfully pushed ${counter}/${filteredArtifactsSchema.length} artifacts` )
72
- }
73
-
74
- main()