| /* |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package source |
| |
| import ( |
| "context" |
| "fmt" |
| "net/http" |
| "net/url" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "github.com/apache/camel-k/v2/pkg/util" |
| "github.com/spf13/cobra" |
| |
| "golang.org/x/oauth2" |
| |
| "github.com/google/go-github/v72/github" |
| ) |
| |
| // Source represents the source file of an Integration. |
| type Source struct { |
| Origin string |
| Location string |
| Name string |
| Content string |
| Compress bool |
| Local bool |
| } |
| |
| // newSource creates a source using the content provider function. |
| func newSource(location string, compress bool, loadContent func() ([]byte, error)) (Source, error) { |
| // strip query part from location if any |
| locPath := util.SubstringBefore(location, "?") |
| if locPath == "" { |
| locPath = location |
| } |
| src := Source{ |
| Name: filepath.Base(locPath), |
| Origin: location, |
| Location: location, |
| Compress: compress, |
| } |
| |
| content, err := loadContent() |
| if err != nil { |
| return Source{}, err |
| } |
| if err := src.setContent(content); err != nil { |
| return Source{}, err |
| } |
| |
| return src, nil |
| } |
| |
| func (s *Source) setContent(content []byte) error { |
| if s.Compress { |
| result, err := CompressToString(content) |
| if err != nil { |
| return err |
| } |
| |
| s.Content = result |
| } else { |
| s.Content = string(content) |
| } |
| |
| return nil |
| } |
| |
| func (s *Source) IsYaml() bool { |
| return strings.HasSuffix(s.Name, ".yaml") || strings.HasSuffix(s.Name, ".yml") |
| } |
| |
| // globSources identifies glob patterns like sources/*.yaml and expand them into individual file paths. |
| func globSources(locations []string) ([]string, error) { |
| var sources = make([]string, 0, len(locations)) |
| |
| for _, src := range locations { |
| glob, err := isGlobCandidate(src) |
| if err != nil { |
| return nil, err |
| } |
| |
| if glob { |
| matches, err := filepath.Glob(src) |
| if err != nil { |
| return nil, err |
| } |
| |
| if len(matches) > 0 { |
| sources = append(sources, matches...) |
| } else { |
| // leave the original location if there wasn't any matches |
| sources = append(sources, src) |
| } |
| } else { |
| sources = append(sources, src) |
| } |
| } |
| |
| return sources, nil |
| } |
| |
| // Resolve resolves sources from a variety of locations including local and remote. |
| func Resolve(ctx context.Context, locations []string, compress bool, cmd *cobra.Command) ([]Source, error) { |
| locations, err := globSources(locations) |
| if err != nil { |
| return nil, err |
| } |
| |
| sources := make([]Source, 0, len(locations)) |
| |
| for _, location := range locations { |
| ok, err := IsLocalAndFileExists(location) |
| if err != nil { |
| return sources, err |
| } |
| |
| if ok { |
| answer, err := resolveLocal(location, compress) |
| if err != nil { |
| return sources, err |
| } |
| |
| sources = append(sources, answer) |
| } else { |
| u, err := url.Parse(location) |
| if err != nil { |
| return sources, err |
| } |
| |
| switch { |
| case u.Scheme == gistScheme || strings.HasPrefix(location, "https://gist.github.com/"): |
| answer, err := resolveGist(ctx, location, compress, cmd, u) |
| if err != nil { |
| return sources, err |
| } |
| sources = append(sources, answer...) |
| case u.Scheme == githubScheme: |
| answer, err := newSource(location, compress, func() ([]byte, error) { |
| return loadContentGitHub(ctx, u) |
| }) |
| if err != nil { |
| return sources, err |
| } |
| sources = append(sources, answer) |
| case u.Scheme == httpScheme || u.Scheme == httpsScheme: |
| answer, err := newSource(location, compress, func() ([]byte, error) { |
| return loadContentHTTP(ctx, u) |
| }) |
| if err != nil { |
| return sources, err |
| } |
| sources = append(sources, answer) |
| default: |
| return sources, fmt.Errorf("missing file or unsupported scheme in %s", location) |
| } |
| } |
| } |
| |
| return sources, nil |
| } |
| |
| // resolveGist resolves sources from a Gist. |
| func resolveGist(ctx context.Context, location string, compress bool, cmd *cobra.Command, u *url.URL) ([]Source, error) { |
| var hc *http.Client |
| |
| if token, ok := os.LookupEnv("GITHUB_TOKEN"); ok { |
| ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) |
| hc = oauth2.NewClient(ctx, ts) |
| |
| fmt.Fprintln(cmd.OutOrStdout(), "GITHUB_TOKEN env var detected, using it for GitHub APIs authentication") |
| } |
| |
| gc := github.NewClient(hc) |
| gistID := "" |
| |
| if strings.HasPrefix(location, "https://gist.github.com/") { |
| names := util.FindNamedMatches(`^https://gist.github.com/(([a-zA-Z0-9]*)/)?(?P<gistid>[a-zA-Z0-9]*)$`, location) |
| if value, ok := names["gistid"]; ok { |
| gistID = value |
| } |
| } else { |
| gistID = u.Opaque |
| } |
| |
| if gistID == "" { |
| return []Source{}, fmt.Errorf("unable to determining gist id from %s", location) |
| } |
| |
| gists, _, err := gc.Gists.Get(ctx, gistID) |
| if err != nil { |
| return []Source{}, err |
| } |
| |
| sources := make([]Source, 0, len(gists.Files)) |
| for _, v := range gists.Files { |
| if v.Filename == nil || v.Content == nil { |
| continue |
| } |
| |
| answer := Source{ |
| Name: *v.Filename, |
| Compress: compress, |
| Origin: location, |
| } |
| if v.RawURL != nil { |
| answer.Location = *v.RawURL |
| } |
| if err := answer.setContent([]byte(*v.Content)); err != nil { |
| return sources, err |
| } |
| sources = append(sources, answer) |
| } |
| |
| return sources, nil |
| } |
| |
| // resolveLocal resolves a source from the local file system. |
| func resolveLocal(location string, compress bool) (Source, error) { |
| if _, err := os.Stat(location); err != nil && os.IsNotExist(err) { |
| return Source{}, fmt.Errorf("file %s does not exist: %w", location, err) |
| } else if err != nil { |
| return Source{}, fmt.Errorf("error while accessing file %s: %w", location, err) |
| } |
| |
| answer, err := newSource(location, compress, func() ([]byte, error) { |
| return util.ReadFile(location) |
| }) |
| if err != nil { |
| return Source{}, err |
| } |
| answer.Local = true |
| |
| return answer, nil |
| } |