77 "maps"
88 "net/http"
99 "os"
10+ "path/filepath"
1011
1112 "github.com/pkg/errors"
1213 "github.com/samber/lo"
@@ -71,7 +72,12 @@ func readFromFile(path string) (*Config, error) {
7172 if err != nil {
7273 return nil , err
7374 }
74- return loadBytes (b )
75+ config , err := loadBytes (b )
76+ if err != nil {
77+ return nil , err
78+ }
79+ config .Root .AbsRootPath = path
80+ return config , nil
7581}
7682
7783func LoadConfigFromURL (ctx context.Context , url string ) (* Config , error ) {
@@ -104,19 +110,40 @@ func loadBytes(b []byte) (*Config, error) {
104110}
105111
106112func (c * Config ) LoadRecursive (lockfile * lock.File ) error {
113+ return c .loadRecursive (lockfile , map [string ]bool {}, "" /*cyclePath*/ )
114+ }
115+
116+ // loadRecursive loads all the included plugins and their included plugins, etc.
117+ // seen should be a cloned map because loading plugins twice is allowed if they
118+ // are in different paths.
119+ func (c * Config ) loadRecursive (
120+ lockfile * lock.File ,
121+ seen map [string ]bool ,
122+ cyclePath string ,
123+ ) error {
107124 included := make ([]* Config , 0 , len (c .Root .Include ))
108125
109126 for _ , includeRef := range c .Root .Include {
110- pluginConfig , err := plugin .LoadConfigFromInclude (includeRef , lockfile )
127+ pluginConfig , err := plugin .LoadConfigFromInclude (
128+ includeRef , lockfile , filepath .Dir (c .Root .AbsRootPath ))
111129 if err != nil {
112130 return errors .WithStack (err )
113131 }
114132
115- includable := & Config {
116- Root : pluginConfig .ConfigFile ,
117- pluginData : & pluginConfig .PluginOnlyData ,
133+ newCyclePath := fmt .Sprintf ("%s -> %s" , cyclePath , includeRef )
134+ if seen [pluginConfig .Source .Hash ()] {
135+ // Note that duplicate includes are allowed if they are in different paths
136+ // e.g. 2 different plugins can include the same plugin.
137+ // We do not allow a single plugin to include duplicates.
138+ return errors .Errorf (
139+ "circular or duplicate include detected:\n %s" , newCyclePath )
118140 }
119- if err := includable .LoadRecursive (lockfile ); err != nil {
141+ seen [pluginConfig .Source .Hash ()] = true
142+
143+ includable := createIncludableFromPluginConfig (pluginConfig )
144+
145+ if err := includable .loadRecursive (
146+ lockfile , maps .Clone (seen ), newCyclePath ); err != nil {
120147 return errors .WithStack (err )
121148 }
122149
@@ -136,7 +163,9 @@ func (c *Config) LoadRecursive(lockfile *lock.File) error {
136163 Root : builtIn .ConfigFile ,
137164 pluginData : & builtIn .PluginOnlyData ,
138165 }
139- if err := includable .LoadRecursive (lockfile ); err != nil {
166+ newCyclePath := fmt .Sprintf ("%s -> %s" , cyclePath , builtIn .Source .LockfileKey ())
167+ if err := includable .loadRecursive (
168+ lockfile , maps .Clone (seen ), newCyclePath ); err != nil {
140169 return errors .WithStack (err )
141170 }
142171 included = append (included , includable )
@@ -259,3 +288,14 @@ func (c *Config) IsEnvsecEnabled() bool {
259288 }
260289 return c .Root .IsEnvsecEnabled ()
261290}
291+
292+ func createIncludableFromPluginConfig (pluginConfig * plugin.Config ) * Config {
293+ includable := & Config {
294+ Root : pluginConfig .ConfigFile ,
295+ pluginData : & pluginConfig .PluginOnlyData ,
296+ }
297+ if localPlugin , ok := pluginConfig .Source .(* plugin.LocalPlugin ); ok {
298+ includable .Root .AbsRootPath = localPlugin .Path ()
299+ }
300+ return includable
301+ }
0 commit comments