Example #1
0
// GetEngine gets an engine from nanobox.io
func GetEngine(user, archive, version string) (*http.Response, error) {

	//
	engine, err := api.GetEngine(user, archive)
	if err != nil {
		os.Stderr.WriteString(stylish.ErrBullet("No official engine, or engine for that user found."))
		return nil, err
	}

	// if no version is provided, fetch the latest release
	if version == "" {
		version = engine.ActiveReleaseID
	}

	//
	path := fmt.Sprintf("http://api.nanobox.io/v1/engines/%v/releases/%v/download", archive, version)

	// if a user is found, pull the engine from their engines
	if user != "" {
		path = fmt.Sprintf("http://api.nanobox.io/v1/engines/%v/%v/releases/%v/download", user, archive, version)
	}

	os.Stderr.WriteString(stylish.Bullet("Fetching engine at '%s'", path))

	//
	return http.Get(path)
}
Example #2
0
// publish
func publish(ccmd *cobra.Command, args []string) {
	stylish.Header("publishing engine")

	//
	api.UserSlug, api.AuthToken = Auth.Authenticate()

	// create a new release
	fmt.Printf(stylish.Bullet("Creating release..."))
	release := &api.EngineRelease{}

	// create an annonymous struct to hold data that doesn't relate to a release but
	// is needed as part of the publish process
	opts := &struct {
		Generic  bool     `json:"generic"`
		Language string   `json:"language"`
		Overlays []string `json:"overlays"`
	}{}

	// ensure there is an Enginefile
	if _, err := os.Stat("./Enginefile"); err != nil {
		fmt.Println("Enginefile not found. Be sure to publish from a project directory. Exiting... ")
		os.Exit(1)
	}

	// parse the ./Enginefile into the new release
	if err := Config.ParseConfig("./Enginefile", release); err != nil {
		fmt.Printf("Nanobox failed to parse your Enginefile. Please ensure it is valid YAML and try again.\n")
		os.Exit(1)
	}

	// parse the ./Enginefile again to get the remaining fields
	if err := Config.ParseConfig("./Enginefile", opts); err != nil {
		fmt.Printf("Nanobox failed to parse your Enginefile. Please ensure it is valid YAML and try again.\n")
		os.Exit(1)
	}

	fmt.Printf(stylish.Bullet("Verifying engine is publishable..."))

	// determine if any required fields (name, version, language, summary) are missing,
	// if any are found to be missing exit 1
	// NOTE: I do this using fallthrough for asthetics onlye. The message is generic
	// enough that all cases will return the same message, and this looks better than
	// a single giant case (var == "" || var == "" || ...)
	switch {
	case opts.Language == "":
		fallthrough
	case release.Name == "":
		fallthrough
	case release.Summary == "":
		fallthrough
	case release.Version == "":
		fmt.Printf(stylish.Error("required fields missing", `Your Enginefile is missing one or more of the following required fields for publishing:

  name:      # the name of your project
  version:   # the current version of the project
  language:  # the lanauge (ruby, golang, etc.) of the engine
  summary:   # a 140 character summary of the project

Please ensure all required fields are provided and try again.`))

		os.Exit(1)
	}

	// attempt to read a README.md file and add it to the release...
	b, err := ioutil.ReadFile("./README.md")
	if err != nil {

		// this only fails if the file is not found, EOF is not an error. If no Readme
		// is found exit 1
		fmt.Printf(stylish.Error("missing readme", "Your engine is missing a README.md file. This file is required for publishing, as it is the only way for you to communicate how to use your engine. Please add a README.md and try again."))
		os.Exit(1)
	}

	//
	release.Readme = string(b)

	// check to see if the engine already exists on nanobox.io
	fmt.Printf(stylish.Bullet("Checking for existing engine on nanobox.io"))
	engine, err := api.GetEngine(api.UserSlug, release.Name)

	// if no engine exists, create a new one
	if err != nil {

		// if no engine is found create one
		if apiErr, _ := err.(api.APIError); apiErr.Code == 404 {

			fmt.Printf(stylish.SubTaskStart("Creating new engine on nanobox.io"))

			//
			engine = &api.Engine{
				Generic:      opts.Generic,
				LanguageName: opts.Language,
				Name:         release.Name,
			}

			//
			if _, err := api.CreateEngine(engine); err != nil {
				fmt.Printf(stylish.ErrBullet("Unable to create engine (%v).", err))
				os.Exit(1)
			}

			// wait until engine has been successfuly created before uploading to s3
			for {
				fmt.Print(".")

				p, err := api.GetEngine(api.UserSlug, release.Name)
				if err != nil {
					Config.Fatal("[commands/publish] api.GetEngine failed", err.Error())
				}

				// once the engine is "active", break
				if p.State == "active" {
					break
				}

				//
				time.Sleep(1000 * time.Millisecond)
			}

			// generically handle any other errors
		} else {
			Config.Fatal("[commands/publish] api.GetEngine failed", err.Error())
		}

		stylish.Success()
	}

	// create a meta.json file where we can add any extra data we might need; since
	// this is only used for internal purposes the file is removed once we're done
	// with it
	meta, err := os.Create("./meta.json")
	if err != nil {
		Config.Fatal("[commands/publish] os.Create() failed", err.Error())
	}
	defer meta.Close()
	defer os.Remove(meta.Name())

	// add any custom info to the metafile
	meta.WriteString(fmt.Sprintf(`{"engine_id": "%s"}`, engine.ID))

	// this is our predefined list of everything that gets archived as part of the
	// engine being published
	files := map[string][]string{
		"required": []string{"./bin", "./Enginefile", "./meta.json"},
		"optional": []string{"./lib", "./templates", "./files"},
	}

	// check to ensure no required files are missing
	for k, v := range files {
		if k == "required" {
			for _, f := range v {
				if _, err := os.Stat(f); err != nil {
					fmt.Printf(stylish.Error("required files missing", "Your Engine is missing one or more required files for publishing. Please read the following documentation to ensure all required files are included and try again.:\n\ndocs.nanobox.io/engines/project-creation/#example-engine-file-structure\n"))
					os.Exit(1)
				}
			}
		}
	}

	// create the temp engines folder for building the tarball
	tarPath := filepath.Join(config.EnginesDir, release.Name)
	if err := os.Mkdir(tarPath, 0755); err != nil {
		Config.Fatal("[commands/engine/publish] os.Create() failed", err.Error())
	}

	// remove tarDir once published
	defer func() {
		if err := os.RemoveAll(tarPath); err != nil {
			os.Stderr.WriteString(stylish.ErrBullet("Faild to remove '%v'...", tarPath))
		}
	}()

	// parse the ./Enginefile again to get the overlays
	if err := Config.ParseConfig("./Enginefile", opts); err != nil {
		fmt.Printf("Nanobox failed to parse your Enginefile. Please ensure it is valid YAML and try again.\n")
		os.Exit(1)
	}

	// iterate through each overlay fetching it and untaring to the tar path
	for _, overlay := range opts.Overlays {
		engineutil.GetOverlay(overlay, tarPath)
	}

	// range over each file from each file type, building the final list of files
	// to be tarballed
	for _, v := range files {
		for _, f := range v {

			// not handling error here because an error simply means the file doesn't
			// exist and therefor wont be copied to the final tarball
			fileutil.Copy(f, tarPath)
		}
	}

	// create an empty buffer for writing the file contents to for the subsequent
	// upload
	archive := bytes.NewBuffer(nil)

	//
	h := md5.New()

	//
	if err := fileutil.Tar(tarPath, archive, h); err != nil {
		Config.Fatal("[commands/engine/publish] file.Tar() failed", err.Error())
	}

	// add the checksum for the new release once its finished being archived
	release.Checksum = fmt.Sprintf("%x", h.Sum(nil))

	//
	// attempt to upload the release to S3
	fmt.Printf(stylish.Bullet("Uploading release to s3..."))

	v := url.Values{}
	v.Add("user_slug", api.UserSlug)
	v.Add("auth_token", api.AuthToken)
	v.Add("version", release.Version)

	//
	s3url, err := s3util.RequestURL(fmt.Sprintf("http://api.nanobox.io/v1/engines/%v/request_upload?%v", release.Name, v.Encode()))
	if err != nil {
		Config.Fatal("[commands/publish] s3.RequestURL() failed", err.Error())
	}

	//
	if err := s3util.Upload(s3url, archive); err != nil {
		Config.Fatal("[commands/publish] s3.Upload() failed", err.Error())
	}

	//
	// if the release uploaded successfully to s3, created one on odin
	fmt.Printf(stylish.Bullet("Uploading release to nanobox.io"))
	if _, err := api.CreateEngineRelease(release.Name, release); err != nil {
		fmt.Printf(stylish.ErrBullet("Unable to publish release (%v).", err))
		os.Exit(1)
	}
}