Exemple #1
0
func main() {
	log.Printf("gotcha %s!\n", version)

	if len(os.Args) >= 2 {
		cmd := os.Args[1]
		args := os.Args[2:]
		switch cmd {
		case "new":
			if len(args) == 0 {
				log.Fatalf("Missing application name, e.g. 'gotcha new MyApp'")
			}
			new(args[0])
		case "install":
			if len(args) > 0 {
				log.Fatalf("No additional arguments required for install: %s", args)
			}
			if _, err := os.Stat("Makefile"); os.IsNotExist(err) {
				log.Fatalf("Current directory doesn't appear to be a Gotcha application")
			}
			out, err := exec.Command("make").Output()
			if err != nil {
				log.Fatalf("Error installing application: %s", err)
			}
			log.Printf("Install successful: %s", out)
		default:
			log.Fatalf("Unrecognised command: %s\n", cmd)
		}
	}
}
Exemple #2
0
func (s *Source) loadBackPANSource() error {
	log.Info("Loading BackPAN index: backpan-index")

	file, err := os.Open("backpan-index")
	if err != nil {
		log.Warn(err.Error())
		return nil
	}

	index, err := ioutil.ReadAll(file)
	file.Close()
	if err != nil {
		log.Fatal(err)
	}

	for _, p := range strings.Split(string(index), "\n") {
		if !strings.HasPrefix(p, "authors/id/") {
			continue
		}

		//log.Printf("Parsing: %s\n", p)
		m := s.ModuleFromBackPANIndex(p)
		if m != nil {
			s.ModuleList[m.Name+"-"+m.Version] = m
		}
	}

	log.Printf("Found %d packages for source: %s", len(s.ModuleList), s)
	return nil
}
Exemple #3
0
func (apiv1 *APIv1) download(w http.ResponseWriter, req *http.Request) {
	id := req.URL.Query().Get(":id")
	log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)

	apiv1.defaultOptions(w, req)

	w.Header().Set("Content-Type", "message/rfc822")
	w.Header().Set("Content-Disposition", "attachment; filename=\""+id+".eml\"")

	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
		for h, l := range message.Content.Headers {
			for _, v := range l {
				w.Write([]byte(h + ": " + v + "\r\n"))
			}
		}
		w.Write([]byte("\r\n" + message.Content.Body))
	case *storage.InMemory:
		message, _ := apiv1.config.Storage.(*storage.InMemory).Load(id)
		for h, l := range message.Content.Headers {
			for _, v := range l {
				w.Write([]byte(h + ": " + v + "\r\n"))
			}
		}
		w.Write([]byte("\r\n" + message.Content.Body))
	default:
		w.WriteHeader(500)
	}
}
Exemple #4
0
func createDir(dir string) {
	log.Printf("Creating directory %s", dir)

	err := os.MkdirAll(dir, 0777)
	if err != nil {
		log.Fatalf("Error creating directory %s: %s", dir, err)
	}
}
Exemple #5
0
func (h *Router) ServeHTTP(w nethttp.ResponseWriter, r *nethttp.Request) {
	session := http.CreateSession(h.Config, r, w)
	tStart := time.Now().UnixNano()

	h.Serve(session)

	t := float64(time.Now().UnixNano()-tStart) / 100000 // ms
	log.Printf("%s %s (%3.2fms) (%d)", r.Method, r.URL, t, session.Response.Status)
	h.Config.Events.Emit(session, events.AfterResponse, func() {})
}
Exemple #6
0
// AuthFile sets Authorised to a function which validates against file
func AuthFile(file string) {
	users = make(map[string]string)

	b, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatalf("[HTTP] Error reading auth-file: %s", err)
		// FIXME - go-log
		os.Exit(1)
	}

	buf := bytes.NewBuffer(b)

	for {
		l, err := buf.ReadString('\n')
		l = strings.TrimSpace(l)
		if len(l) > 0 {
			p := strings.SplitN(l, ":", 2)
			if len(p) < 2 {
				log.Fatalf("[HTTP] Error reading auth-file, invalid line: %s", l)
				// FIXME - go-log
				os.Exit(1)
			}
			users[p[0]] = p[1]
		}
		switch {
		case err == io.EOF:
			break
		case err != nil:
			log.Fatalf("[HTTP] Error reading auth-file: %s", err)
			// FIXME - go-log
			os.Exit(1)
			break
		}
		if err == io.EOF {
			break
		} else if err != nil {
		}
	}

	log.Printf("[HTTP] Loaded %d users from %s", len(users), file)

	Authorised = func(u, pw string) bool {
		hpw, ok := users[u]
		if !ok {
			return false
		}

		err := bcrypt.CompareHashAndPassword([]byte(hpw), []byte(pw))
		if err != nil {
			return false
		}

		return true
	}
}
Exemple #7
0
func main() {
	// Create our Gotcha application
	var app = gotcha.Create(Asset)

	// Set the logger output pattern
	log.Logger().Appender().SetLayout(layout.Pattern("[%d] [%p] %m"))
	log.Logger().SetLevel(log.Stol("TRACE"))

	// Get the router
	r := app.Router

	// Create some routes
	r.Get("/", example)
	r.Post("/", examplepost)
	r.Get("/foo", example2)
	r.Get("/bar", example3)
	r.Get("/stream", streamed)
	r.Get("/err", err)

	// Serve static content (but really use a CDN)
	r.Get("/images/(?P<file>.*)", r.Static("assets/images/{{file}}"))
	r.Get("/css/(?P<file>.*)", r.Static("assets/css/{{file}}"))

	// Listen to some events
	app.On(events.BeforeHandler, func(session *http.Session, next func()) {
		n := 0
		c, ok := session.Request.Cookies["test"]
		if ok {
			n, _ = strconv.Atoi(c.Value)
		}
		session.Stash["test"] = n
		log.Printf("Got BeforeHandler event! n = %d", n)
		next()
	})
	app.On(events.AfterHandler, func(session *http.Session, next func()) {
		n := session.Stash["test"].(int) + 1
		session.Response.Cookies.Set(&nethttp.Cookie{
			Name:  "test",
			Value: strconv.Itoa(n),
		})
		log.Println("Got AfterHandler event!")
		next()
	})
	app.On(events.AfterResponse, func(session *http.Session, next func()) {
		log.Println("Got AfterResponse event!")
		next()
	})

	// Start our application
	app.Start()

	<-make(chan int)
}
Exemple #8
0
func (apiv1 *APIv1) download_part(w http.ResponseWriter, req *http.Request) {
	id := req.URL.Query().Get(":id")
	part := req.URL.Query().Get(":part")
	log.Printf("[APIv1] GET /api/v1/messages/%s/mime/part/%s/download\n", id, part)

	// TODO extension from content-type?
	apiv1.defaultOptions(w, req)

	w.Header().Set("Content-Disposition", "attachment; filename=\""+id+"-part-"+part+"\"")

	message, _ := apiv1.config.Storage.Load(id)
	contentTransferEncoding := ""
	pid, _ := strconv.Atoi(part)
	for h, l := range message.MIME.Parts[pid].Headers {
		for _, v := range l {
			switch strings.ToLower(h) {
			case "content-disposition":
				// Prevent duplicate "content-disposition"
				w.Header().Set(h, v)
			case "content-transfer-encoding":
				if contentTransferEncoding == "" {
					contentTransferEncoding = v
				}
				fallthrough
			default:
				w.Header().Add(h, v)
			}
		}
	}
	body := []byte(message.MIME.Parts[pid].Body)
	if strings.ToLower(contentTransferEncoding) == "base64" {
		var e error
		body, e = base64.StdEncoding.DecodeString(message.MIME.Parts[pid].Body)
		if e != nil {
			log.Printf("[APIv1] Decoding base64 encoded body failed: %s", e)
		}
	}
	w.Write(body)
}
Exemple #9
0
func (app *App) Start() *App {
	app.Server = &nethttp.Server{
		Addr:    app.Config.Listen,
		Handler: app.Router,
	}
	log.Printf("Starting application on %s", app.Config.Listen)
	go func() {
		err := app.Server.ListenAndServe()
		if err != nil {
			log.Fatalf("Error binding to %s: %s", app.Config.Listen, err)
		}
	}()
	return app
}
Exemple #10
0
func (apiv1 *APIv1) message(w http.ResponseWriter, req *http.Request) {
	id := req.URL.Query().Get(":id")
	log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)

	apiv1.defaultOptions(w, req)

	message, err := apiv1.config.Storage.Load(id)
	if err != nil {
		log.Printf("- Error: %s", err)
		w.WriteHeader(500)
		return
	}

	bytes, err := json.Marshal(message)
	if err != nil {
		log.Printf("- Error: %s", err)
		w.WriteHeader(500)
		return
	}

	w.Header().Set("Content-Type", "text/json")
	w.Write(bytes)
}
Exemple #11
0
func CreateAPIv1(conf *config.Config, r *pat.Router) *APIv1 {
	log.Println("Creating API v1")
	apiv1 := &APIv1{
		config: conf,
	}

	stream = goose.NewEventStream()

	r.Path("/api/v1/messages").Methods("GET").HandlerFunc(apiv1.messages)
	r.Path("/api/v1/messages").Methods("DELETE").HandlerFunc(apiv1.delete_all)
	r.Path("/api/v1/messages").Methods("OPTIONS").HandlerFunc(apiv1.defaultOptions)

	r.Path("/api/v1/messages/{id}").Methods("GET").HandlerFunc(apiv1.message)
	r.Path("/api/v1/messages/{id}").Methods("DELETE").HandlerFunc(apiv1.delete_one)
	r.Path("/api/v1/messages/{id}").Methods("OPTIONS").HandlerFunc(apiv1.defaultOptions)

	r.Path("/api/v1/messages/{id}/download").Methods("GET").HandlerFunc(apiv1.download)
	r.Path("/api/v1/messages/{id}/download").Methods("OPTIONS").HandlerFunc(apiv1.defaultOptions)

	r.Path("/api/v1/messages/{id}/mime/part/{part}/download").Methods("GET").HandlerFunc(apiv1.download_part)
	r.Path("/api/v1/messages/{id}/mime/part/{part}/download").Methods("OPTIONS").HandlerFunc(apiv1.defaultOptions)

	r.Path("/api/v1/messages/{id}/release").Methods("POST").HandlerFunc(apiv1.release_one)
	r.Path("/api/v1/messages/{id}/release").Methods("OPTIONS").HandlerFunc(apiv1.defaultOptions)

	r.Path("/api/v1/events").Methods("GET").HandlerFunc(apiv1.eventstream)
	r.Path("/api/v1/events").Methods("OPTIONS").HandlerFunc(apiv1.defaultOptions)

	go func() {
		keepaliveTicker := time.Tick(time.Minute)
		for {
			select {
			case msg := <-apiv1.config.MessageChan:
				log.Println("Got message in APIv1 event stream")
				bytes, _ := json.MarshalIndent(msg, "", "  ")
				json := string(bytes)
				log.Printf("Sending content: %s\n", json)
				apiv1.broadcast(json)
			case <-keepaliveTicker:
				apiv1.keepalive()
			}
		}
	}()

	return apiv1
}
Exemple #12
0
func (apiv1 *APIv1) delete_one(w http.ResponseWriter, req *http.Request) {
	id := req.URL.Query().Get(":id")

	log.Printf("[APIv1] POST /api/v1/messages/%s/delete\n", id)

	apiv1.defaultOptions(w, req)

	w.Header().Add("Content-Type", "text/json")
	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		apiv1.config.Storage.(*storage.MongoDB).DeleteOne(id)
	case *storage.InMemory:
		apiv1.config.Storage.(*storage.InMemory).DeleteOne(id)
	default:
		w.WriteHeader(500)
	}
}
Exemple #13
0
func writeAsset(input string, output string) {
	log.Printf("Writing asset %s to %s", input, output)

	f, err := os.Create(filepath.FromSlash(output))
	if err != nil {
		log.Fatalf("Error creating %s: %s", output, err)
	}

	bytes, err := Asset(input)
	if err != nil {
		log.Fatalf("Error loading asset %s: %s", input, err)
	}

	_, err = f.Write(bytes)
	if err != nil {
		log.Fatalf("Error writing output %s: %s", output, err)
	}
}
Exemple #14
0
func new(name string) {
	log.Printf("Creating application: '%s'\n", name)

	// TODO clean this up

	createDir(name)
	writeAsset("assets/new_app/main.go", name+"/main.go")
	writeAsset("assets/new_app/Makefile", name+"/Makefile")
	writeAsset("assets/new_app/README.md", name+"/README.md")

	createDir(name + "/assets/templates")
	writeAsset("assets/new_app/assets/templates/index.html", name+"/assets/templates/index.html")
	writeAsset("assets/new_app/assets/templates/error.html", name+"/assets/templates/error.html")
	writeAsset("assets/new_app/assets/templates/notfound.html", name+"/assets/templates/notfound.html")
	createDir(name + "/assets/images")
	writeAsset("assets/new_app/assets/images/logo-ish.png", name+"/assets/images/logo-ish.png")
	createDir(name + "/assets/css")
	writeAsset("assets/new_app/assets/css/default.css", name+"/assets/css/default.css")
}
Exemple #15
0
func (apiv1 *APIv1) message(w http.ResponseWriter, req *http.Request) {
	id := req.URL.Query().Get(":id")
	log.Printf("[APIv1] GET /api/v1/messages/%s\n", id)

	apiv1.defaultOptions(w, req)

	switch apiv1.config.Storage.(type) {
	case *storage.MongoDB:
		message, _ := apiv1.config.Storage.(*storage.MongoDB).Load(id)
		bytes, _ := json.Marshal(message)
		w.Header().Add("Content-Type", "text/json")
		w.Write(bytes)
	case *storage.InMemory:
		message, _ := apiv1.config.Storage.(*storage.InMemory).Load(id)
		bytes, _ := json.Marshal(message)
		w.Header().Add("Content-Type", "text/json")
		w.Write(bytes)
	default:
		w.WriteHeader(500)
	}
}
Exemple #16
0
func main() {
	configure()

	if comconf.AuthFile != "" {
		http.AuthFile(comconf.AuthFile)
	}

	exitCh = make(chan int)
	cb := func(r gohttp.Handler) {
		api.CreateAPIv1(conf, r.(*pat.Router))
		api.CreateAPIv2(conf, r.(*pat.Router))
	}
	go http.Listen(conf.APIBindAddr, assets.Asset, exitCh, cb)
	go smtp.Listen(conf, exitCh)

	for {
		select {
		case <-exitCh:
			log.Printf("Received exit signal")
			os.Exit(0)
		}
	}
}
Exemple #17
0
func main() {
	configure()

	// FIXME need to make API URL configurable

	if comconf.AuthFile != "" {
		http.AuthFile(comconf.AuthFile)
	}

	exitCh = make(chan int)
	cb := func(r gohttp.Handler) {
		web.CreateWeb(conf, r.(*pat.Router), assets.Asset)
	}
	go http.Listen(conf.UIBindAddr, assets.Asset, exitCh, cb)

	for {
		select {
		case <-exitCh:
			log.Printf("Received exit signal")
			os.Exit(0)
		}
	}
}
Exemple #18
0
func (m *Module) Install() (int, error) {
	log.Debug("Installing module: %s", m)

	n := 0

	if m.Deps != nil {
		log.Trace("Installing module dependencies for %s", m)

		<-install_semaphore
		o, err := m.Deps.Install()
		install_semaphore <- 1

		n += o
		if err != nil {
			log.Error("Error installing module dependencies for %s: %s", m, err)
			return n, err
		}
	}

	var c *exec.Cmd
	var stdout *bytes.Buffer
	var stderr *bytes.Buffer

	cpanm_cache_dir, err := filepath.Abs(config.CacheDir)
	if err != nil {
		log.Error("Failed to get absolute path of gopan cache directory: %s", err)
		return n, err
	}

	os.Setenv("PERL_CPANM_HOME", cpanm_cache_dir)

	done := false
	attempts := 0
	for !done {
		time.Sleep(time.Duration(100) * time.Millisecond)

		c = m.getCmd()
		stdout = new(bytes.Buffer)
		stderr = new(bytes.Buffer)
		c.Stderr = stderr
		c.Stdout = stdout

		// brute force cpanm text file busy errors
		attempts++
		if err := c.Start(); err != nil {
			if attempts > 10 {
				log.Error("Error installing module %s: %s", m, err)
				return n, err
			}
		} else {
			done = true
		}
	}

	if err := c.Wait(); err != nil {
		if !strings.HasPrefix(strings.ToLower(stderr.String()), "plenv: cannot rehash:") && !strings.Contains(strings.ToLower(stderr.String()), "text file busy") &&
			!strings.HasPrefix(strings.ToLower(stdout.String()), "plenv: cannot rehash:") && !strings.Contains(strings.ToLower(stdout.String()), "text file busy") {
			log.Error(m.Name + "-" + m.Version + " failed to install")
			log.Error("Error installing %s %s: %s\nSTDERR:\n%sSTDOUT:\n%s", m.Name, m.Version, err, stderr.String(), stdout.String())
			return n, err
		}
	}

	n++

	log.Printf("Installed " + m.Name + " (" + m.Version + ")")
	return n, nil
}
Exemple #19
0
func (apiv1 *APIv1) release_one(w http.ResponseWriter, req *http.Request) {
	id := req.URL.Query().Get(":id")
	log.Printf("[APIv1] POST /api/v1/messages/%s/release\n", id)

	apiv1.defaultOptions(w, req)

	w.Header().Add("Content-Type", "text/json")
	msg, _ := apiv1.config.Storage.Load(id)

	decoder := json.NewDecoder(req.Body)
	var cfg ReleaseConfig
	err := decoder.Decode(&cfg)
	if err != nil {
		log.Printf("Error decoding request body: %s", err)
		w.WriteHeader(500)
		w.Write([]byte("Error decoding request body"))
		return
	}

	log.Printf("%+v", cfg)

	log.Printf("Got message: %s", msg.ID)

	if cfg.Save {
		if _, ok := apiv1.config.OutgoingSMTP[cfg.Name]; ok {
			log.Printf("Server already exists named %s", cfg.Name)
			w.WriteHeader(400)
			return
		}
		cf := config.OutgoingSMTP(cfg)
		apiv1.config.OutgoingSMTP[cfg.Name] = &cf
		log.Printf("Saved server with name %s", cfg.Name)
	}

	if len(cfg.Name) > 0 {
		if c, ok := apiv1.config.OutgoingSMTP[cfg.Name]; ok {
			log.Printf("Using server with name: %s", cfg.Name)
			cfg.Name = c.Name
			if len(cfg.Email) == 0 {
				cfg.Email = c.Email
			}
			cfg.Host = c.Host
			cfg.Port = c.Port
			cfg.Username = c.Username
			cfg.Password = c.Password
			cfg.Mechanism = c.Mechanism
		} else {
			log.Printf("Server not found: %s", cfg.Name)
			w.WriteHeader(400)
			return
		}
	}

	log.Printf("Releasing to %s (via %s:%s)", cfg.Email, cfg.Host, cfg.Port)

	bytes := make([]byte, 0)
	for h, l := range msg.Content.Headers {
		for _, v := range l {
			bytes = append(bytes, []byte(h+": "+v+"\r\n")...)
		}
	}
	bytes = append(bytes, []byte("\r\n"+msg.Content.Body)...)

	var auth smtp.Auth

	if len(cfg.Username) > 0 || len(cfg.Password) > 0 {
		log.Printf("Found username/password, using auth mechanism: [%s]", cfg.Mechanism)
		switch cfg.Mechanism {
		case "CRAMMD5":
			auth = smtp.CRAMMD5Auth(cfg.Username, cfg.Password)
		case "PLAIN":
			auth = smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
		default:
			log.Printf("Error - invalid authentication mechanism")
			w.WriteHeader(400)
			return
		}
	}

	err = smtp.SendMail(cfg.Host+":"+cfg.Port, auth, "nobody@"+apiv1.config.Hostname, []string{cfg.Email}, bytes)
	if err != nil {
		log.Printf("Failed to release message: %s", err)
		w.WriteHeader(500)
		return
	}
	log.Printf("Message released successfully")
}
Exemple #20
0
func main() {
	if len(os.Args) > 1 && os.Args[1] == "sendmail" {
		args := os.Args
		os.Args = []string{args[0]}
		if len(args) > 2 {
			os.Args = append(os.Args, args[2:]...)
		}
		cmd.Go()
		return
	}

	if len(os.Args) > 1 && os.Args[1] == "bcrypt" {
		var pw string
		if len(os.Args) > 2 {
			pw = os.Args[2]
		} else {
			// TODO: read from stdin
		}
		b, err := bcrypt.GenerateFromPassword([]byte(pw), 4)
		if err != nil {
			log.Fatalf("error bcrypting password: %s", err)
			os.Exit(1)
		}
		fmt.Println(string(b))
		os.Exit(0)
	}

	configure()

	if comconf.AuthFile != "" {
		http.AuthFile(comconf.AuthFile)
	}

	exitCh = make(chan int)
	if uiconf.UIBindAddr == apiconf.APIBindAddr {
		cb := func(r gohttp.Handler) {
			web.CreateWeb(uiconf, r.(*pat.Router), assets.Asset)
			api.CreateAPIv1(apiconf, r.(*pat.Router))
			api.CreateAPIv2(apiconf, r.(*pat.Router))
		}
		go http.Listen(uiconf.UIBindAddr, assets.Asset, exitCh, cb)
	} else {
		cb1 := func(r gohttp.Handler) {
			api.CreateAPIv1(apiconf, r.(*pat.Router))
			api.CreateAPIv2(apiconf, r.(*pat.Router))
		}
		cb2 := func(r gohttp.Handler) {
			web.CreateWeb(uiconf, r.(*pat.Router), assets.Asset)
		}
		go http.Listen(apiconf.APIBindAddr, assets.Asset, exitCh, cb1)
		go http.Listen(uiconf.UIBindAddr, assets.Asset, exitCh, cb2)
	}
	go smtp.Listen(apiconf, exitCh)

	for {
		select {
		case <-exitCh:
			log.Printf("Received exit signal")
			os.Exit(0)
		}
	}
}
Exemple #21
0
func (m *Module) loadDependencies() error {
	yml, err := ioutil.ReadFile(m.Extracted + "/META.yml")
	if err != nil {
		// TODO this isnt an error (it shouldnt make build fail)
		log.Error("Error opening META.yml for %s: %s", m.Name, err)
		// return nil to prevent build fail
		return nil
	}

	meta := make(map[interface{}]interface{})
	err = yaml.Unmarshal(yml, &meta)
	if err != nil {
		// TODO this isnt a real error, probably
		log.Error("Error parsing YAML: %s", err)
		// return nil to prevent build fail
		return nil
	}

	if reqs, ok := meta["requires"]; ok {
		log.Debug("Found dependencies for module %s", m.Name)
		switch reqs.(type) {
		case map[interface{}]interface{}:
			for req, ver := range reqs.(map[interface{}]interface{}) {
				v := float64(0)
				switch ver.(type) {
				case string:
					v = gopan.VersionFromString(ver.(string))
				case int:
					v = float64(ver.(int))
				}
				log.Printf("=> %s (%f)", req, v)
				dep, err := DependencyFromString(req.(string), fmt.Sprintf("%f", ver))
				if err != nil {
					log.Error("Error parsing dependency: %s", err)
					continue
				}
				if _, ok := perl_core[dep.Name]; ok {
					log.Trace("Module is from perl core: %s", dep.Name)
					continue
				}
				m.Deps.AddDependency(dep)
			}
		}

		log.Debug("Resolving module dependency list")

		if err := m.Deps.Resolve(); err != nil {
			log.Error("Error resolving dependency list [%s]: %s", m.Name, err)
			return err
		}

		return nil
	}

	// FIXME repeat of block above, just with more nested levels
	if p, ok := meta["prereqs"]; ok {
		if r, ok := p.(map[interface{}]interface{})["runtime"]; ok {
			if reqs, ok := r.(map[interface{}]interface{})["requires"]; ok {
				log.Debug("Found dependencies for module %s", m.Name)
				switch reqs.(type) {
				case map[interface{}]interface{}:
					for req, ver := range reqs.(map[interface{}]interface{}) {
						v := float64(0)
						switch ver.(type) {
						case string:
							v = gopan.VersionFromString(ver.(string))
						case int:
							v = float64(ver.(int))
						}
						log.Printf("=> %s (%f)", req, v)
						dep, err := DependencyFromString(req.(string), fmt.Sprintf("%f", ver))
						if err != nil {
							log.Error("Error parsing dependency: %s", err)
							continue
						}
						if _, ok := perl_core[dep.Name]; ok {
							log.Trace("Module is from perl core: %s", dep.Name)
							continue
						}
						m.Deps.AddDependency(dep)
					}
				}
			}
		}
		if t, ok := p.(map[interface{}]interface{})["test"]; ok {
			if reqs, ok := t.(map[interface{}]interface{})["requires"]; ok {
				log.Debug("Found dependencies for module %s", m.Name)
				switch reqs.(type) {
				case map[interface{}]interface{}:
					for req, ver := range reqs.(map[interface{}]interface{}) {
						v := float64(0)
						switch ver.(type) {
						case string:
							v = gopan.VersionFromString(ver.(string))
						case int:
							v = float64(ver.(int))
						}
						log.Printf("=> %s (%f)", req, v)
						dep, err := DependencyFromString(req.(string), fmt.Sprintf("%f", ver))
						if err != nil {
							log.Error("Error parsing dependency: %s", err)
							continue
						}
						if _, ok := perl_core[dep.Name]; ok {
							log.Trace("Module is from perl core: %s", dep.Name)
							continue
						}
						m.Deps.AddDependency(dep)
					}
				}
			}
		}

		log.Debug("Resolving module dependency list")
		if err := m.Deps.Resolve(); err != nil {
			log.Error("Error resolving dependency list: %s", err)
			return err
		}

		return nil
	}

	log.Debug("No dependencies for module %s", m.Name)
	return nil
}