dev

package
v0.0.0-...-b8869b4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2020 License: Apache-2.0 Imports: 41 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var BuildCmd = &cobra.Command{
	Use:   "build",
	Short: "build an integration",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		integrationDir := args[0]
		logger := log.NewCommandLogger(cmd)
		defer logger.Close()
		integrationDir, _ = filepath.Abs(integrationDir)
		integration := strings.Replace(filepath.Base(integrationDir), "agent.next.", "", -1)
		fp := filepath.Join(integrationDir, "integration.go")
		if !fileutil.FileExists(fp) {
			log.Fatal(logger, "couldn't find the integration at "+fp)
		}
		distDir, _ := cmd.Flags().GetString("dir")
		distDir, _ = filepath.Abs(distDir)
		os.MkdirAll(distDir, 0700)
		dist := filepath.Join(distDir)

		modfp := filepath.Join(integrationDir, "go.mod")
		mod, err := ioutil.ReadFile(modfp)
		if err != nil {
			log.Fatal(logger, "error reading plugin go.mod", "err", err)
		}
		ioutil.WriteFile(modfp, []byte(string(mod)+"\nreplace github.com/pinpt/agent.next => ../agent.next"), 0644)

		bundle, _ := cmd.Flags().GetBool("bundle")
		var bundleRewriter rewriteFunc
		if bundle {
			yfn := filepath.Join(integrationDir, "integration.yaml")
			if !fileutil.FileExists(yfn) {
				log.Fatal(logger, "missing required file at "+yfn)
			}
			ygofn := filepath.Join(integrationDir, "integration.go")
			if !fileutil.FileExists(ygofn) {
				log.Fatal(logger, "missing required file at "+ygofn)
			}
			buf, err := ioutil.ReadFile(yfn)
			if err != nil {
				log.Fatal(logger, "error opening required file at "+yfn, "err", err)
			}
			gobuf, err := ioutil.ReadFile(ygofn)
			if err != nil {
				log.Fatal(logger, "error opening required file at "+ygofn, "err", err)
			}
			var descriptor sdk.Descriptor
			if err := yaml.Unmarshal(buf, &descriptor); err != nil {
				log.Fatal(logger, "error parsing config file at "+yfn, "err", err)
			}
			version := getBuildCommitForIntegration(integrationDir)
			bbuf := base64.StdEncoding.EncodeToString(buf)
			tmpl, err := generateMainTemplate(ygofn, string(gobuf), bbuf, datetime.ISODate(), version)
			if err != nil {
				log.Fatal(logger, "error generating build", "err", err)
			}
			ioutil.WriteFile(ygofn, []byte(tmpl), 0644)
			bundleRewriter = func() {
				ioutil.WriteFile(ygofn, gobuf, 0644)
			}
			defer bundleRewriter()
		}
		theenv := os.Environ()
		oses, _ := cmd.Flags().GetStringArray("os")
		for _, theos := range oses {
			arches, _ := cmd.Flags().GetStringArray("arch")
			for _, arch := range arches {
				env := append(theenv, []string{"GOOS=" + theos, "GOARCH=" + arch}...)
				outfn := filepath.Join(dist, theos, arch, integration)
				os.MkdirAll(filepath.Dir(outfn), 0700)
				c := exec.Command("go", "build", "-o", outfn)
				c.Stderr = os.Stderr
				c.Stdout = os.Stdout
				c.Stdin = os.Stdin
				c.Dir = integrationDir
				c.Env = env
				if err := c.Run(); err != nil {
					bundleRewriter()
					ioutil.WriteFile(modfp, mod, 0644)
					os.Exit(1)
				}
				log.Debug(logger, "file built to "+outfn)
			}
		}
		ioutil.WriteFile(modfp, mod, 0644)
		if bundleRewriter != nil {
			bundleRewriter()
		}
	},
}

BuildCmd represents the build command

View Source
var DevCmd = createDevCommand("dev", "dev-export", "run an integration in development mode", false, func(cmd *cobra.Command, devargs []string) []string {
	historical, _ := cmd.Flags().GetBool("historical")
	if historical {
		devargs = append(devargs, "--historical=true")
	}

	webhookEnabled, _ := cmd.Flags().GetBool("webhook")
	if webhookEnabled {
		devargs = append(devargs, "--webhook")
	}

	secret, _ := cmd.Flags().GetString("secret")
	if secret != "" {
		devargs = append(devargs, "--secret", secret)
	}

	record, _ := cmd.Flags().GetString("record")
	replay, _ := cmd.Flags().GetString("replay")

	if record != "" {
		record, _ = filepath.Abs(record)
		devargs = append(devargs, "--record", record)
	}
	if replay != "" {
		replay, _ = filepath.Abs(replay)
		devargs = append(devargs, "--replay", replay)
	}
	return devargs
})

DevCmd represents the dev command

View Source
var EnrollCmd = &cobra.Command{
	Use:   "enroll",
	Short: "enroll to create a developer account",
	Args:  cobra.NoArgs,
	Run: func(cmd *cobra.Command, args []string) {
		ctx := context.Background()
		logger := log.NewCommandLogger(cmd)
		defer logger.Close()
		channel, _ := cmd.Flags().GetString("channel")
		config, err := loadDevConfig()
		if err != nil {
			log.Fatal(logger, "unable to load developer config", "err", err)
		}
		if config.expired() {
			log.Fatal(logger, "your login session has expired. please login again")
		}
		if config.Channel != channel {
			log.Fatal(logger, "your login session was for a different channel. please login again")
		}

		banner()
		fmt.Println()
		fmt.Println("🚀 Time to enroll you in the Pinpoint Developer Program")
		fmt.Println()
		fmt.Println("We need a few bits of information to continue ...")
		fmt.Println()

		var result struct {
			Name        string `json:"name" survey:"name"`
			Identifier  string `json:"identifier" survey:"identifier"`
			Description string `json:"description" survey:"description"`
			Avatar      string `json:"avatar_url" survey:"avatar_url"`
			URL         string `json:"url" survey:"url"`
			Certificate string `json:"csr"`
		}
		if err := survey.Ask([]*survey.Question{
			{
				Name: "name",
				Prompt: &survey.Input{
					Message: "Your Publisher Name:",
					Help:    "Your name such as Pinpoint Software, Inc",
				},
				Validate: survey.Required,
			},
			{
				Name: "identifier",
				Prompt: &survey.Input{
					Message: "Your Publisher Short Identifier:",
					Help:    "Your short identifier must be unique and should contain no spaces or special characters such as pinpt",
				},
				Validate: createValidateIdentifier(channel, config.APIKey, config.CustomerID),
			},
			{
				Name: "avatar_url",
				Prompt: &survey.Input{
					Message: "Your Publisher Avatar:",
					Help:    "A url to your avatar image in PNG, GIF or JPEG format",
				},
				Validate: validateAvatar,
			},
			{
				Name: "url",
				Prompt: &survey.Input{
					Message: "Your Publisher URL:",
					Help:    "The URL to your homepage or URL to your integration",
				},
				Validate: validateURL,
			},
			{
				Name: "description",
				Prompt: &survey.Input{
					Message: "Your Publisher Bio:",
					Help:    "A short description of your bio or company background",
				},
				Validate: survey.ComposeValidators(survey.Required, survey.MaxLength(255), survey.MinLength(20)),
			},
		}, &result); err != nil {
			os.Exit(1)
		}

		fmt.Println()

		log.Info(logger, "generating new private key")
		privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
		if err != nil {
			log.Fatal(logger, "error generating private key", "err", err)
		}
		privateKeyBuf := pem.EncodeToMemory(&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
		})
		config.PrivateKey = string(privateKeyBuf)
		if err := config.save(); err != nil {
			log.Fatal(logger, "error saving config", "err", err)
		}
		buf, err := generateCertificateRequest(logger, privateKey, config.CustomerID)
		if err != nil {
			log.Fatal(logger, "error generating certificate request", "err", err)
		}
		result.Certificate = string(buf)
		resp, err := api.Put(ctx, channel, api.RegistryService, "enroll", config.APIKey, strings.NewReader(pjson.Stringify(result)))
		if err != nil {
			log.Fatal(logger, "error sending cert request", "err", err)
		}
		respBuf, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(logger, "error reading response body", "err", err)
		}
		resp.Body.Close()
		if resp.StatusCode != http.StatusCreated {
			log.Fatal(logger, "error from api", "err", string(respBuf))
		}
		log.Info(logger, "recieved certificate from Pinpoint")
		config.Certificate = string(respBuf)
		config.PublisherRefType = result.Identifier
		if err := config.save(); err != nil {
			log.Fatal(logger, "error saving config", "err", err)
		}
		log.Info(logger, "successfully enrolled. you can now publish integrations! go forth and build 🎉", "customer_id", config.CustomerID)
	},
}

EnrollCmd represents the enroll command

View Source
var GenCmd = &cobra.Command{
	Use:   "generate",
	Short: "generates an integration",
	Args:  cobra.NoArgs,
	Run: func(cmd *cobra.Command, args []string) {
		banner()
		fmt.Println("Welcome to the Pinpoint Integration generator!")
		fmt.Println()

		logger := log.NewCommandLogger(cmd)
		defer logger.Close()
		var err error
		var result generator.Info

		if err = promptSettings(&result); err != nil {
			log.Error(logger, "error with settings", "err", err)
			os.Exit(1)
		}

		gopath := os.Getenv("GOPATH")
		if gopath == "" {
			var buf strings.Builder
			c := exec.Command("go", "env", "GOPATH")
			c.Stdout = &buf
			if err := c.Run(); err != nil {
				log.Fatal(logger, "error finding your GOPATH. is Golang installed and on your PATH?", "err", err)
			}
			gopath = strings.TrimSpace(buf.String())
		}

		result.Dir = filepath.Join(gopath, "src", result.Pkg)
		if !fileutil.FileExists(result.Dir) {
			os.MkdirAll(result.Dir, 0700)
		}

		if err = generator.Generate(result.Dir, result); err != nil {
			log.Fatal(logger, "error with generator", "err", err)
		}

		appDir := filepath.Join(result.Dir, "app")

		sdkInstall := exec.Command("npm", "install", "@pinpt/agent.websdk", "@pinpt/uic.next", "--save", "--loglevel", "error")
		sdkInstall.Dir = appDir
		sdkInstall.Stderr = os.Stderr
		sdkInstall.Stdin = os.Stdin
		sdkInstall.Stdin = os.Stdin
		sdkInstall.Run()

		npmInstall := exec.Command("npm", "install", "--loglevel", "error")
		npmInstall.Dir = appDir
		npmInstall.Stderr = os.Stderr
		npmInstall.Stdin = os.Stdin
		npmInstall.Stdin = os.Stdin
		npmInstall.Run()

		fmt.Println()
		fmt.Println("🎉 project created! open " + result.Dir + " in your editor and start coding!")
		fmt.Println()
	},
}

GenCmd represents the dev command

View Source
var LoginCmd = &cobra.Command{
	Use:   "login",
	Short: "login to your developer account",
	Run: func(cmd *cobra.Command, args []string) {
		logger := log.NewCommandLogger(cmd)
		defer logger.Close()

		var config *devConfig

		channel, _ := cmd.Flags().GetString("channel")
		baseurl := api.BackendURL(api.AuthService, channel)
		url := sdk.JoinURL(baseurl, "/login?apikey=true")

		err := util.WaitForRedirect(url, func(w http.ResponseWriter, r *http.Request) {
			config, _ = loadDevConfig()
			q := r.URL.Query()
			customerID := q.Get("customer_id")
			if config != nil {
				if config.CustomerID == customerID {
					log.Info(logger, "refreshing token", "customer_id", config.CustomerID)
				} else {
					log.Info(logger, "logging into new customer, you will need to generate a new private key before publishing 🔑", "customer_id", config.CustomerID)
				}
			} else {
				config = &devConfig{}
			}
			config.APIKey = q.Get("apikey")
			config.CustomerID = customerID
			config.Expires = time.Now().Add(time.Hour * 23)
			config.Channel = channel
			if err := config.save(); err != nil {
				log.Error(logger, "error saving config", "err", err)
			}
			httpmessage.RenderStatus(w, r, http.StatusOK, "Login Success", "You have logged in successfully and can now close this window")
		})
		if err != nil {
			log.Fatal(logger, "error waiting for browser", "err", err)
		}

		log.Info(logger, "logged in", "customer_id", config.CustomerID)
	},
}

LoginCmd represents the login command

View Source
var LogoutCmd = &cobra.Command{
	Use:   "logout",
	Short: "logout of your developer account",
	Run: func(cmd *cobra.Command, args []string) {
		var c devConfig
		c.remove()
	},
}

LogoutCmd represents the logout command

View Source
var PackageCmd = &cobra.Command{
	Use:   "package",
	Short: "package an integration",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		integrationDir := args[0]
		logger := log.NewCommandLogger(cmd)
		defer logger.Close()
		integrationDir, _ = filepath.Abs(integrationDir)
		distDir, _ := cmd.Flags().GetString("dir")
		distDir, _ = filepath.Abs(distDir)
		bundleDir := filepath.Join(distDir, "bundle")
		dataDir := filepath.Join(bundleDir, "data")
		appDir := filepath.Join(integrationDir, "app")
		os.MkdirAll(bundleDir, 0700)
		os.MkdirAll(dataDir, 0700)

		buf, err := ioutil.ReadFile(filepath.Join(integrationDir, "integration.yaml"))
		if err != nil {
			log.Fatal(logger, "error loading integration.yaml", "err", err)
		}

		descriptor, err := sdk.LoadDescriptor(base64.StdEncoding.EncodeToString(buf), "", "")
		if err != nil {
			log.Fatal(logger, "error loading descriptor", "err", err)
		}
		if err := ioutil.WriteFile(filepath.Join(bundleDir, "integration.json"), []byte(pjson.Stringify(descriptor)), 0644); err != nil {
			log.Fatal(logger, "error writing integration json", "err", err)
		}

		dataFn := filepath.Join(bundleDir, "data.zip")
		uiFn := filepath.Join(bundleDir, "ui.zip")
		bundleFn := filepath.Join(distDir, "bundle.zip")

		oss, _ := cmd.Flags().GetStringArray("os")
		arches, _ := cmd.Flags().GetStringArray("arch")

		cargs := []string{"build", integrationDir, "--dir", dataDir}
		for _, o := range oss {
			cargs = append(cargs, "--os", o)
		}
		for _, a := range arches {
			cargs = append(cargs, "--arch", a)
		}
		c := exec.Command(os.Args[0], cargs...)
		c.Stdout = os.Stdout
		c.Stderr = os.Stderr
		c.Stdin = os.Stdin
		if err := c.Run(); err != nil {
			log.Fatal(logger, "error running command", "command", c.String(), "err", err)
		}

		c = exec.Command("npm", "install", "--loglevel", "error")
		c.Dir = appDir
		c.Stdout = os.Stdout
		c.Stderr = os.Stderr
		c.Stdin = os.Stdin
		if err := c.Run(); err != nil {
			log.Fatal(logger, "error running command", "command", c.String(), "err", err)
		}
		c = exec.Command("npm", "run", "build", "--loglevel", "error")
		c.Dir = appDir
		c.Stdout = os.Stdout
		c.Stderr = os.Stderr
		c.Stdin = os.Stdin
		if err := c.Run(); err != nil {
			log.Fatal(logger, "error running command", "command", c.String(), "err", err)
		}
		if _, err := fileutil.ZipDir(uiFn, filepath.Join(appDir, "build"), regexp.MustCompile(".*")); err != nil {
			log.Fatal(logger, "error building zip file", "err", err)
		}

		sha := getBuildCommitForIntegration(integrationDir)

		if err := ioutil.WriteFile(filepath.Join(bundleDir, "version.txt"), []byte(sha), 0644); err != nil {
			log.Fatal(logger, "error writing version file to bundle dir", "err", err)
		}

		if devCfg, err := loadDevConfig(); err == nil {
			if devCfg.Certificate != "" {
				if err := ioutil.WriteFile(filepath.Join(bundleDir, "cert.pem"), []byte(devCfg.Certificate), 0644); err != nil {
					log.Fatal(logger, "error writing developer certificate to bundle dir", "err", err)
				}
			} else {
				log.Debug(logger, "no developer certificate found, not including in bundle", "err", err)
			}
		} else {
			log.Warn(logger, "unable to load developer config, the bundle will not contain your developer certificate", "err", err)
		}

		if _, err := fileutil.ZipDir(dataFn, dataDir, regexp.MustCompile(".*")); err != nil {
			log.Fatal(logger, "error building zip file", "err", err)
		}
		if _, err := fileutil.ZipDir(bundleFn, bundleDir, regexp.MustCompile(".(zip|asc|txt|pem|json)$")); err != nil {
			log.Fatal(logger, "error building zip file", "err", err)
		}
		os.RemoveAll(bundleDir)
		log.Info(logger, "bundle packaged to "+bundleFn)
	},
}

PackageCmd represents the package command

View Source
var PublishCmd = &cobra.Command{
	Use:   "publish <integration dir>",
	Short: "publish an integration to the registry",
	Args:  cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		integrationDir := args[0]
		logger := log.NewCommandLogger(cmd)
		defer logger.Close()
		tmpdir, err := ioutil.TempDir("", "")
		if err != nil {
			log.Fatal(logger, "error creating temp dir", "err", err)
		}
		defer os.RemoveAll(tmpdir)
		c, err := loadDevConfig()
		if err != nil {
			log.Fatal(logger, "error opening developer config", "err", err)
		}
		if c.PrivateKey == "" {
			log.Fatal(logger, "missing private key in config, please enroll before publishing")
		}
		if c.expired() {
			log.Fatal(logger, "your login session has expired. please login again")
		}
		channel, _ := cmd.Flags().GetString("channel")
		if c.Channel != channel {
			log.Fatal(logger, "your login session was for a different channel. please login again")
		}
		privateKey, err := util.ParsePrivateKey(c.PrivateKey)
		if err != nil {
			log.Fatal(logger, "unable to parse private key in config")
		}
		log.Info(logger, "building package")
		cm := exec.Command(os.Args[0], "package", integrationDir, "--dir", tmpdir)
		cm.Stdout = os.Stdout
		cm.Stderr = os.Stderr
		cm.Stdin = os.Stdin
		if err := cm.Run(); err != nil {
			log.Fatal(logger, "error running package command", "err", err)
		}
		bundle := filepath.Join(tmpdir, "bundle.zip")
		if !fileutil.FileExists(bundle) {
			log.Fatal(logger, "error bundle does not exist", "err", err)
		}
		signature, err := signFile(bundle, privateKey)
		if err != nil {
			log.Fatal(logger, "error getting signature for bundle", "err", err)
		}
		of, err := os.Open(bundle)
		if err != nil {
			log.Fatal(logger, "error opening bundle", "err", err)
		}
		defer of.Close()
		stat, _ := os.Stat(bundle)
		ctx, cancel := context.WithCancel(context.Background())
		defer cancel()
		opts := []api.WithOption{
			api.WithContentType("application/zip"),
			api.WithHeader("x-pinpt-signature", signature),
			func(req *http.Request) error {
				req.ContentLength = stat.Size()
				return nil
			},
		}
		apikey, _ := cmd.Flags().GetString("apikey")
		secret, _ := cmd.Flags().GetString("secret")
		if secret != "" {
			opts = append(opts, api.WithHeader("x-api-key", secret))
		} else if apikey == "" {
			apikey = c.APIKey
			if apikey == "" {
				log.Fatal(logger, "you must login or provide the apikey using --apikey before continuing")
			}
		}
		descriptorFn := filepath.Join(integrationDir, "integration.yaml")
		descriptorBuf, err := ioutil.ReadFile(descriptorFn)
		if err != nil {
			log.Fatal(logger, "error reading descriptor", "err", err, "file", descriptorFn)
		}
		descriptor, err := sdk.LoadDescriptor(base64.StdEncoding.EncodeToString(descriptorBuf), "", "")
		if err != nil {
			log.Fatal(logger, "error loading descriptor", "err", err, "file", descriptorFn)
		}
		version := getBuildCommitForIntegration(integrationDir)
		basepath := fmt.Sprintf("publish/%s/%s/%s", c.PublisherRefType, descriptor.RefType, version)
		log.Info(logger, "uploading", "size", pnum.ToBytesSize(stat.Size()))
		resp, err := api.Put(ctx, channel, api.RegistryService, basepath, apikey, of, opts...)
		if err != nil || resp.StatusCode != http.StatusAccepted {
			var buf []byte
			if resp != nil {
				buf, _ = ioutil.ReadAll(resp.Body)
			}
			log.Fatal(logger, "error publishing your bundle", "err", err, "body", string(buf))
		}
		log.Info(logger, "🚀 published", "integration", descriptor.RefType, "version", version)
	},
}

PublishCmd represents the publish command

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL