@kustodian/sources 1.1.0
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/package.json +45 -0
- package/src/cache/index.ts +339 -0
- package/src/cache/metadata.ts +25 -0
- package/src/cache/ttl.ts +71 -0
- package/src/fetchers/git.ts +213 -0
- package/src/fetchers/http.ts +217 -0
- package/src/fetchers/index.ts +32 -0
- package/src/fetchers/oci.ts +206 -0
- package/src/fetchers/types.ts +33 -0
- package/src/index.ts +46 -0
- package/src/loader.ts +139 -0
- package/src/resolver.ts +175 -0
- package/src/types.ts +194 -0
package/src/resolver.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { type KustodianErrorType, type ResultType, failure, success } from '@kustodian/core';
|
|
4
|
+
import { type TemplateSourceType, is_mutable_source } from '@kustodian/schema';
|
|
5
|
+
import { type CacheManagerType, create_cache_manager } from './cache/index.js';
|
|
6
|
+
import { get_fetcher_for_source } from './fetchers/index.js';
|
|
7
|
+
import type {
|
|
8
|
+
FetchOptionsType,
|
|
9
|
+
ResolvedSourceType,
|
|
10
|
+
ResolverOptionsType,
|
|
11
|
+
SourceResolverType,
|
|
12
|
+
} from './types.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default cache directory relative to project root.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_CACHE_DIR = '.kustodian/templates-cache';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for creating a source resolver.
|
|
21
|
+
*/
|
|
22
|
+
export interface CreateResolverOptionsType {
|
|
23
|
+
/** Base cache directory (defaults to .kustodian/templates-cache) */
|
|
24
|
+
cache_dir?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Creates a source resolver instance.
|
|
29
|
+
*/
|
|
30
|
+
export function create_source_resolver(options?: CreateResolverOptionsType): SourceResolverType {
|
|
31
|
+
const cache_dir = options?.cache_dir ?? path.join(process.cwd(), DEFAULT_CACHE_DIR);
|
|
32
|
+
const cache_manager = create_cache_manager(cache_dir);
|
|
33
|
+
return new SourceResolver(cache_manager);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class SourceResolver implements SourceResolverType {
|
|
37
|
+
private readonly cache: CacheManagerType;
|
|
38
|
+
|
|
39
|
+
constructor(cache_manager: CacheManagerType) {
|
|
40
|
+
this.cache = cache_manager;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async resolve_all(
|
|
44
|
+
sources: TemplateSourceType[],
|
|
45
|
+
options?: ResolverOptionsType,
|
|
46
|
+
): Promise<ResultType<ResolvedSourceType[], KustodianErrorType>> {
|
|
47
|
+
const parallel = options?.parallel ?? true;
|
|
48
|
+
|
|
49
|
+
if (parallel) {
|
|
50
|
+
const results = await Promise.all(sources.map((source) => this.resolve(source, options)));
|
|
51
|
+
|
|
52
|
+
// Check for first failure
|
|
53
|
+
const resolved: ResolvedSourceType[] = [];
|
|
54
|
+
for (const result of results) {
|
|
55
|
+
if (!result.success) {
|
|
56
|
+
return failure(result.error);
|
|
57
|
+
}
|
|
58
|
+
resolved.push(result.value);
|
|
59
|
+
}
|
|
60
|
+
return success(resolved);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Sequential execution
|
|
64
|
+
const resolved: ResolvedSourceType[] = [];
|
|
65
|
+
for (const source of sources) {
|
|
66
|
+
const result = await this.resolve(source, options);
|
|
67
|
+
if (!result.success) {
|
|
68
|
+
return failure(result.error);
|
|
69
|
+
}
|
|
70
|
+
resolved.push(result.value);
|
|
71
|
+
}
|
|
72
|
+
return success(resolved);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async resolve(
|
|
76
|
+
source: TemplateSourceType,
|
|
77
|
+
options?: FetchOptionsType,
|
|
78
|
+
): Promise<ResultType<ResolvedSourceType, KustodianErrorType>> {
|
|
79
|
+
const force_refresh = options?.force_refresh ?? false;
|
|
80
|
+
const fetcher = get_fetcher_for_source(source);
|
|
81
|
+
const mutable = is_mutable_source(source);
|
|
82
|
+
|
|
83
|
+
// Determine the version to fetch
|
|
84
|
+
const version = this.get_source_version(source);
|
|
85
|
+
|
|
86
|
+
// Check cache first (unless force refresh)
|
|
87
|
+
if (!force_refresh) {
|
|
88
|
+
const cached = await this.cache.get(source.name, version);
|
|
89
|
+
if (cached.success && cached.value) {
|
|
90
|
+
return success({
|
|
91
|
+
source,
|
|
92
|
+
fetch_result: {
|
|
93
|
+
path: cached.value.path,
|
|
94
|
+
version: cached.value.version,
|
|
95
|
+
from_cache: true,
|
|
96
|
+
fetched_at: cached.value.fetched_at,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Fetch from remote
|
|
103
|
+
const fetch_result = await fetcher.fetch(source, options);
|
|
104
|
+
if (!fetch_result.success) {
|
|
105
|
+
return fetch_result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Store in cache
|
|
109
|
+
const cache_result = await this.cache.put(
|
|
110
|
+
source.name,
|
|
111
|
+
fetcher.type,
|
|
112
|
+
fetch_result.value.version,
|
|
113
|
+
fetch_result.value.path,
|
|
114
|
+
mutable,
|
|
115
|
+
source.ttl,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (!cache_result.success) {
|
|
119
|
+
// Log warning but don't fail - we have the content
|
|
120
|
+
console.warn(
|
|
121
|
+
`Warning: Failed to cache source '${source.name}': ${cache_result.error.message}`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// If caching succeeded, use the cached path (which is more stable)
|
|
126
|
+
const final_path = cache_result.success ? cache_result.value.path : fetch_result.value.path;
|
|
127
|
+
|
|
128
|
+
// Cleanup temp directory if we successfully cached
|
|
129
|
+
if (cache_result.success && fetch_result.value.path !== cache_result.value.path) {
|
|
130
|
+
await fs.rm(fetch_result.value.path, { recursive: true, force: true }).catch(() => {});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return success({
|
|
134
|
+
source,
|
|
135
|
+
fetch_result: {
|
|
136
|
+
path: final_path,
|
|
137
|
+
version: fetch_result.value.version,
|
|
138
|
+
from_cache: false,
|
|
139
|
+
fetched_at: fetch_result.value.fetched_at,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async update_all(
|
|
145
|
+
sources: TemplateSourceType[],
|
|
146
|
+
): Promise<ResultType<ResolvedSourceType[], KustodianErrorType>> {
|
|
147
|
+
return this.resolve_all(sources, { force_refresh: true });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private get_source_version(source: TemplateSourceType): string {
|
|
151
|
+
if (source.git) {
|
|
152
|
+
const ref = source.git.ref;
|
|
153
|
+
return ref.tag ?? ref.commit ?? ref.branch ?? 'unknown';
|
|
154
|
+
}
|
|
155
|
+
if (source.http) {
|
|
156
|
+
// Use checksum as version if available, otherwise use URL hash
|
|
157
|
+
return source.http.checksum ?? this.hash_url(source.http.url);
|
|
158
|
+
}
|
|
159
|
+
if (source.oci) {
|
|
160
|
+
return source.oci.digest ?? source.oci.tag ?? 'unknown';
|
|
161
|
+
}
|
|
162
|
+
return 'unknown';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private hash_url(url: string): string {
|
|
166
|
+
// Simple hash for URL-based versioning
|
|
167
|
+
let hash = 0;
|
|
168
|
+
for (let i = 0; i < url.length; i++) {
|
|
169
|
+
const char = url.charCodeAt(i);
|
|
170
|
+
hash = (hash << 5) - hash + char;
|
|
171
|
+
hash = hash & hash;
|
|
172
|
+
}
|
|
173
|
+
return `url-${Math.abs(hash).toString(16)}`;
|
|
174
|
+
}
|
|
175
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { KustodianErrorType, ResultType } from '@kustodian/core';
|
|
2
|
+
import type { TemplateSourceType } from '@kustodian/schema';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Result of a successful fetch operation.
|
|
6
|
+
*/
|
|
7
|
+
export interface FetchResultType {
|
|
8
|
+
/** Absolute path to the fetched/extracted templates directory */
|
|
9
|
+
path: string;
|
|
10
|
+
/** Version/ref that was fetched (tag, commit, digest, etc.) */
|
|
11
|
+
version: string;
|
|
12
|
+
/** Whether this result came from cache */
|
|
13
|
+
from_cache: boolean;
|
|
14
|
+
/** Timestamp when this was fetched */
|
|
15
|
+
fetched_at: Date;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Options for fetching sources.
|
|
20
|
+
*/
|
|
21
|
+
export interface FetchOptionsType {
|
|
22
|
+
/** Force refresh, ignoring cache */
|
|
23
|
+
force_refresh?: boolean;
|
|
24
|
+
/** Target cache directory (defaults to .kustodian/templates-cache) */
|
|
25
|
+
cache_dir?: string;
|
|
26
|
+
/** Custom timeout in milliseconds */
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Version information from a remote source.
|
|
32
|
+
*/
|
|
33
|
+
export interface RemoteVersionType {
|
|
34
|
+
/** Version identifier (tag name, commit sha, etc.) */
|
|
35
|
+
version: string;
|
|
36
|
+
/** Content digest if available */
|
|
37
|
+
digest?: string;
|
|
38
|
+
/** Date of the version if available */
|
|
39
|
+
date?: Date;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Cache entry metadata.
|
|
44
|
+
*/
|
|
45
|
+
export interface CacheEntryType {
|
|
46
|
+
/** Source name from configuration */
|
|
47
|
+
source_name: string;
|
|
48
|
+
/** Source type: git, http, or oci */
|
|
49
|
+
source_type: 'git' | 'http' | 'oci';
|
|
50
|
+
/** Version/ref that was cached */
|
|
51
|
+
version: string;
|
|
52
|
+
/** Absolute path to cached content */
|
|
53
|
+
path: string;
|
|
54
|
+
/** When this was fetched */
|
|
55
|
+
fetched_at: Date;
|
|
56
|
+
/** When this expires (only for mutable refs) */
|
|
57
|
+
expires_at: Date | null;
|
|
58
|
+
/** Content checksum if available */
|
|
59
|
+
checksum?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Template source fetcher interface.
|
|
64
|
+
* Each source type (git, http, oci) implements this interface.
|
|
65
|
+
*/
|
|
66
|
+
export interface SourceFetcherType {
|
|
67
|
+
/** Unique identifier for this fetcher type */
|
|
68
|
+
readonly type: 'git' | 'http' | 'oci';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Fetches templates from the source.
|
|
72
|
+
* Returns path to extracted templates directory.
|
|
73
|
+
*/
|
|
74
|
+
fetch(
|
|
75
|
+
source: TemplateSourceType,
|
|
76
|
+
options?: FetchOptionsType,
|
|
77
|
+
): Promise<ResultType<FetchResultType, KustodianErrorType>>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Lists available versions from the remote.
|
|
81
|
+
* For Git: tags and branches
|
|
82
|
+
* For OCI: tags
|
|
83
|
+
* For HTTP: not applicable (returns empty array)
|
|
84
|
+
*/
|
|
85
|
+
list_versions(
|
|
86
|
+
source: TemplateSourceType,
|
|
87
|
+
): Promise<ResultType<RemoteVersionType[], KustodianErrorType>>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Determines if this source reference is mutable.
|
|
91
|
+
* Mutable refs (branches, 'latest' tag) need TTL-based refresh.
|
|
92
|
+
* Immutable refs (tags, commits, digests) can be cached forever.
|
|
93
|
+
*/
|
|
94
|
+
is_mutable(source: TemplateSourceType): boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Cache manager interface.
|
|
99
|
+
*/
|
|
100
|
+
export interface CacheManagerType {
|
|
101
|
+
/** Base cache directory */
|
|
102
|
+
readonly cache_dir: string;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Gets a cached entry if valid (not expired for mutable refs).
|
|
106
|
+
*/
|
|
107
|
+
get(
|
|
108
|
+
source_name: string,
|
|
109
|
+
version: string,
|
|
110
|
+
): Promise<ResultType<CacheEntryType | null, KustodianErrorType>>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Stores content in cache.
|
|
114
|
+
*/
|
|
115
|
+
put(
|
|
116
|
+
source_name: string,
|
|
117
|
+
source_type: 'git' | 'http' | 'oci',
|
|
118
|
+
version: string,
|
|
119
|
+
content_path: string,
|
|
120
|
+
mutable: boolean,
|
|
121
|
+
ttl?: string,
|
|
122
|
+
): Promise<ResultType<CacheEntryType, KustodianErrorType>>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Invalidates a specific cache entry.
|
|
126
|
+
* If version is omitted, invalidates all versions for the source.
|
|
127
|
+
*/
|
|
128
|
+
invalidate(source_name: string, version?: string): Promise<ResultType<void, KustodianErrorType>>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Clears all expired entries (mutable refs past their TTL).
|
|
132
|
+
* Returns the number of entries removed.
|
|
133
|
+
*/
|
|
134
|
+
prune(): Promise<ResultType<number, KustodianErrorType>>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Lists all cache entries.
|
|
138
|
+
*/
|
|
139
|
+
list(): Promise<ResultType<CacheEntryType[], KustodianErrorType>>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Gets total cache size in bytes.
|
|
143
|
+
*/
|
|
144
|
+
size(): Promise<ResultType<number, KustodianErrorType>>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Clears all cached content.
|
|
148
|
+
*/
|
|
149
|
+
clear(): Promise<ResultType<void, KustodianErrorType>>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolved source with fetch result.
|
|
154
|
+
*/
|
|
155
|
+
export interface ResolvedSourceType {
|
|
156
|
+
source: TemplateSourceType;
|
|
157
|
+
fetch_result: FetchResultType;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Source resolver options.
|
|
162
|
+
*/
|
|
163
|
+
export interface ResolverOptionsType extends FetchOptionsType {
|
|
164
|
+
/** Run fetches in parallel (default: true) */
|
|
165
|
+
parallel?: boolean;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Source resolver interface - main entry point for fetching templates.
|
|
170
|
+
*/
|
|
171
|
+
export interface SourceResolverType {
|
|
172
|
+
/**
|
|
173
|
+
* Resolves and fetches all template sources.
|
|
174
|
+
*/
|
|
175
|
+
resolve_all(
|
|
176
|
+
sources: TemplateSourceType[],
|
|
177
|
+
options?: ResolverOptionsType,
|
|
178
|
+
): Promise<ResultType<ResolvedSourceType[], KustodianErrorType>>;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Resolves and fetches a single source.
|
|
182
|
+
*/
|
|
183
|
+
resolve(
|
|
184
|
+
source: TemplateSourceType,
|
|
185
|
+
options?: FetchOptionsType,
|
|
186
|
+
): Promise<ResultType<ResolvedSourceType, KustodianErrorType>>;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Force updates all sources (ignores cache).
|
|
190
|
+
*/
|
|
191
|
+
update_all(
|
|
192
|
+
sources: TemplateSourceType[],
|
|
193
|
+
): Promise<ResultType<ResolvedSourceType[], KustodianErrorType>>;
|
|
194
|
+
}
|