Skip to content

Commit fb72316

Browse files
committed
github token vendoring
1 parent 80058a8 commit fb72316

File tree

4 files changed

+160
-8
lines changed

4 files changed

+160
-8
lines changed

internal/exec/vendor_utils.go

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ func ExecuteAtmosVendorInternal(
265265
return fmt.Errorf("either 'spec.sources' or 'spec.imports' (or both) must be defined in the vendor config file '%s'", vendorConfigFileName)
266266
}
267267

268+
var tempDir string
269+
268270
// Process imports and return all sources from all the imports and from `vendor.yaml`
269271
sources, _, err := processVendorImports(
270272
atmosConfig,
@@ -314,6 +316,16 @@ func ExecuteAtmosVendorInternal(
314316
)
315317
}
316318

319+
tempDir, err = os.MkdirTemp("", "atmos_vendor_")
320+
if err != nil {
321+
return fmt.Errorf("failed to create temp directory: %w", err)
322+
}
323+
defer func() {
324+
if err := os.RemoveAll(tempDir); err != nil {
325+
u.LogWarning(atmosConfig, fmt.Sprintf("failed to clean up temp directory %s: %v", tempDir, err))
326+
}
327+
}()
328+
317329
// Allow having duplicate targets in different sources.
318330
// This can be used to vendor mixins (from local and remote sources) and write them to the same targets.
319331
// TODO: consider adding a flag to `atmos vendor pull` to specify if duplicate targets are allowed or not.
@@ -356,7 +368,24 @@ func ExecuteAtmosVendorInternal(
356368
return err
357369
}
358370

359-
useOciScheme, useLocalFileSystem, sourceIsLocalFile := determineSourceType(&uri, vendorConfigFilePath)
371+
useOciScheme, useLocalFileSystem, sourceIsLocalFile, isGitHubSource := determineSourceType(&uri, vendorConfigFilePath)
372+
373+
// Handle GitHub source
374+
if isGitHubSource {
375+
u.LogInfo(atmosConfig, fmt.Sprintf("Fetching GitHub source: %s", uri))
376+
fileContents, err := u.DownloadFileFromGitHub(uri)
377+
if err != nil {
378+
return fmt.Errorf("failed to download GitHub file: %w", err)
379+
}
380+
// Save the downloaded file to the existing tempDir
381+
tempGitHubFile := filepath.Join(tempDir, filepath.Base(uri))
382+
err = os.WriteFile(tempGitHubFile, fileContents, os.ModePerm)
383+
if err != nil {
384+
return fmt.Errorf("failed to save GitHub file to temp location: %w", err)
385+
}
386+
// Update the URI to point to the saved file in the temp directory
387+
uri = tempGitHubFile
388+
}
360389

361390
// Determine package type
362391
var pType pkgType
@@ -494,23 +523,32 @@ func shouldSkipSource(s *schema.AtmosVendorSource, component string, tags []stri
494523
return (component != "" && s.Component != component) || (len(tags) > 0 && len(lo.Intersect(tags, s.Tags)) == 0)
495524
}
496525

497-
func determineSourceType(uri *string, vendorConfigFilePath string) (bool, bool, bool) {
498-
// Determine if the URI is an OCI scheme, a local file, or remote
526+
func determineSourceType(uri *string, vendorConfigFilePath string) (bool, bool, bool, bool) {
527+
// Determine if the URI is an OCI scheme, a local file, a remote GitHub source, or a generic remote
499528
useOciScheme := strings.HasPrefix(*uri, "oci://")
500529
if useOciScheme {
501530
*uri = strings.TrimPrefix(*uri, "oci://")
502531
}
503532

504533
useLocalFileSystem := false
505534
sourceIsLocalFile := false
535+
isGitHubSource := false
536+
506537
if !useOciScheme {
507-
if absPath, err := u.JoinAbsolutePathWithPath(vendorConfigFilePath, *uri); err == nil {
508-
uri = &absPath
509-
useLocalFileSystem = true
510-
sourceIsLocalFile = u.FileExists(*uri)
538+
if strings.Contains(*uri, "github.com") {
539+
// Check if the URL is a GitHub source
540+
isGitHubSource = true
541+
} else {
542+
// Handle local file system sources
543+
if absPath, err := u.JoinAbsolutePathWithPath(vendorConfigFilePath, *uri); err == nil {
544+
uri = &absPath
545+
useLocalFileSystem = true
546+
sourceIsLocalFile = u.FileExists(*uri)
547+
}
511548
}
512549
}
513-
return useOciScheme, useLocalFileSystem, sourceIsLocalFile
550+
551+
return useOciScheme, useLocalFileSystem, sourceIsLocalFile, isGitHubSource
514552
}
515553

516554
func copyToTarget(atmosConfig schema.AtmosConfiguration, tempDir, targetPath string, s *schema.AtmosVendorSource, sourceIsLocalFile bool, uri string) error {

pkg/utils/file_utils.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,3 +242,27 @@ func GetFileNameFromURL(rawURL string) (string, error) {
242242
}
243243
return fileName, nil
244244
}
245+
246+
// ParseGitHubURL parses a GitHub URL and returns the owner, repo, file path and branch
247+
func ParseGitHubURL(rawURL string) (owner, repo, filePath, branch string, err error) {
248+
parsedURL, err := url.Parse(rawURL)
249+
if err != nil {
250+
return "", "", "", "", fmt.Errorf("invalid URL: %w", err)
251+
}
252+
253+
if !strings.Contains(parsedURL.Host, "github.com") {
254+
return "", "", "", "", fmt.Errorf("URL is not a GitHub URL")
255+
}
256+
257+
pathParts := strings.Split(strings.Trim(parsedURL.Path, "/"), "/")
258+
if len(pathParts) < 4 || pathParts[2] != "blob" {
259+
return "", "", "", "", fmt.Errorf("URL format not supported. Expected: /owner/repo/blob/branch/filepath")
260+
}
261+
262+
owner = pathParts[0]
263+
repo = pathParts[1]
264+
branch = pathParts[3]
265+
filePath = strings.Join(pathParts[4:], "/")
266+
267+
return owner, repo, filePath, branch, nil
268+
}

pkg/utils/github_utils.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package utils
22

33
import (
44
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
59
"time"
610

711
"github.com/google/go-github/v59/github"
@@ -28,3 +32,38 @@ func GetLatestGitHubRepoRelease(owner string, repo string) (string, error) {
2832

2933
return "", nil
3034
}
35+
36+
// ParseGitHubURL parses a GitHub URL and returns the owner, repo, file path and branch
37+
func DownloadFileFromGitHub(rawURL string) ([]byte, error) {
38+
owner, repo, filePath, branch, err := ParseGitHubURL(rawURL)
39+
if err != nil {
40+
return nil, fmt.Errorf("failed to parse GitHub URL: %w", err)
41+
}
42+
43+
githubToken := os.Getenv("GITHUB_TOKEN")
44+
if githubToken == "" {
45+
return nil, fmt.Errorf("GITHUB_TOKEN is not set")
46+
}
47+
48+
apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/contents/%s?ref=%s", owner, repo, filePath, branch)
49+
req, err := http.NewRequest("GET", apiURL, nil)
50+
if err != nil {
51+
return nil, fmt.Errorf("failed to create request: %w", err)
52+
}
53+
54+
req.Header.Set("Authorization", "Bearer "+githubToken)
55+
req.Header.Set("Accept", "application/vnd.github.v3.raw")
56+
57+
client := &http.Client{}
58+
resp, err := client.Do(req)
59+
if err != nil {
60+
return nil, fmt.Errorf("failed to perform request: %w", err)
61+
}
62+
defer resp.Body.Close()
63+
64+
if resp.StatusCode != http.StatusOK {
65+
return nil, fmt.Errorf("failed to download file: %s", resp.Status)
66+
}
67+
68+
return io.ReadAll(resp.Body)
69+
}

pkg/vender/vendor_config_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,54 @@ spec:
142142
assert.Nil(t, err)
143143
})
144144
}
145+
146+
// New separate test function that directly calls ExecuteAtmosVendorInternal
147+
// after setting a GitHub token and reading a vendor.yaml referencing a GitHub source.
148+
func TestExecuteAtmosVendorInternalWithToken(t *testing.T) {
149+
testDir := t.TempDir()
150+
151+
// Set the GitHub token environment variable
152+
os.Setenv("GITHUB_TOKEN", "my-test-token")
153+
defer os.Unsetenv("GITHUB_TOKEN")
154+
155+
// Create a vendor.yaml file referencing a GitHub source
156+
vendorYaml := `
157+
apiVersion: atmos/v1
158+
kind: AtmosVendorConfig
159+
metadata:
160+
name: test-vendor-config-with-token
161+
spec:
162+
sources:
163+
- component: github-component
164+
source: https://github.com/example/repo/blob/main/test-file.yaml
165+
included_paths:
166+
- "**/*.yaml"
167+
targets:
168+
- vendor/github-component
169+
`
170+
vendorYamlPath := filepath.Join(testDir, "vendor.yaml")
171+
err := os.WriteFile(vendorYamlPath, []byte(vendorYaml), 0644)
172+
assert.Nil(t, err)
173+
174+
// Initialize CLI configuration for this test
175+
atmosConfig := schema.AtmosConfiguration{
176+
BasePath: testDir,
177+
Components: schema.Components{
178+
Terraform: schema.Terraform{
179+
BasePath: "components/terraform",
180+
},
181+
},
182+
}
183+
atmosConfig.Logs.Level = "Trace"
184+
185+
// Read the vendor config
186+
vendorConfig, exists, foundVendorConfigFile, err := e.ReadAndProcessVendorConfigFile(atmosConfig, vendorYamlPath, true)
187+
assert.Nil(t, err)
188+
assert.True(t, exists)
189+
assert.NotEmpty(t, foundVendorConfigFile)
190+
191+
// Now call ExecuteAtmosVendorInternal directly
192+
// We use dryRun to avoid downloading and writing files
193+
err = e.ExecuteAtmosVendorInternal(atmosConfig, foundVendorConfigFile, vendorConfig.Spec, "github-component", []string{}, true)
194+
assert.Nil(t, err, "ExecuteAtmosVendorInternal should run without errors using the token in dry-run mode")
195+
}

0 commit comments

Comments
 (0)