<file_sep>// Copyright 2021 Splunk Inc.
//
// Licensed 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 main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/AlecAivazis/survey/v2"
	"github.com/splunk/acs-privateapps-demo/src/appinspect"
	"io/ioutil"
	"path/filepath"
	"time"
)

type vet struct {
	PackageFilePath   string `kong:"arg,help='the path to the app-package (tar.gz) file',type='path'"`
	SplunkComUsername string `kong:"env='SPLUNK_COM_USERNAME',help='the splunkbase username'"`
	SplunkComPassword string `kong:"env='SPLUNK_COM_PASSWORD',help='the splunkbase password'"`
	JSONReportFile    string `kong:"help='the file to write the inspection report in json format',type='path'"`
}

func (v *vet) Run(c *context) error {

	pf, err := ioutil.ReadFile(v.PackageFilePath)
	if err != nil {
		return err
	}
	if v.SplunkComUsername == "" {
		survey.AskOne(&survey.Input{
			Message: "splunkbase username:",
		}, &v.SplunkComUsername)
	}
	if v.SplunkComPassword == "" {
		survey.AskOne(&survey.Password{
			Message: "splunkbase password:",
		}, &v.SplunkComPassword)
		fmt.Println("")
	}
	cli := appinspect.New()
	err = cli.Login(v.SplunkComUsername, v.SplunkComPassword)
	if err != nil {
		return err
	}
	submitRes, err := cli.Submit(filepath.Base(v.PackageFilePath), bytes.NewReader(pf))
	if err != nil {
		return err
	}
	fmt.Printf("submitted app for inspection (requestId='%s')\n", submitRes.RequestID)

	status, err := cli.Status(submitRes.RequestID)
	if err != nil {
		return err
	}
	if status.Status == "PROCESSING" || status.Status == "PREPARING" || status.Status == "PENDING" {
		fmt.Printf("waiting for inspection to finish...\n")
		for {
			time.Sleep(2 * time.Second)
			status, err = cli.Status(submitRes.RequestID)
			if err != nil {
				return err
			}
			if status.Status != "PROCESSING" {
				break
			}
		}
	}
	if status.Status == "SUCCESS" {
		data, _ := json.MarshalIndent(status.Info, "", "    ")
		fmt.Printf("vetting completed, summary: \n%s\n", string(data))
		if status.Info.Failure > 0 || status.Info.Error > 0 {
			err = fmt.Errorf("vetting failed (failures=%d, errors=%d)", status.Info.Failure, status.Info.Error)
		}
	} else {
		err = fmt.Errorf("vetting failed to complete (status='%s')", status.Status)
	}

	if v.JSONReportFile != "" {
		report, e := cli.ReportJSON(submitRes.RequestID)
		if e != nil {
			fmt.Printf("failed to pull report: %s\n", e)
		}
		data, _ := json.MarshalIndent(report, "", "    ")
		e = ioutil.WriteFile(v.JSONReportFile, data, 0644)
		if e != nil {
			fmt.Printf("failed to write report: %s\n", e)
		}
	}
	return err
}
<file_sep>// Copyright 2021 Splunk Inc.
//
// Licensed 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 appinspect

import (
	"fmt"
	"io"
	"net/url"

	"github.com/go-resty/resty/v2"
)

const (
	appInspectBaseURL = "https://appinspect.splunk.com/v1/app"
)

// Client to interface with the appinspect service
type Client struct {
	*resty.Client
	token string
}

// Error ...
type Error struct {
	Code        string `json:"code"`
	Description string `json:"description"`
}

func (e *Error) Error() string {
	return fmt.Sprintf("%s: %s", e.Code, e.Description)
}

// New client to interface with the appinspect service
func New() *Client {
	client := &Client{
		Client: resty.New().SetHostURL(appInspectBaseURL).SetError(&Error{}).SetAuthScheme("Bearer"),
	}
	client.Client = client.Client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error {
		if client.token != "" {
			req.SetAuthToken(client.token)
		}
		return nil
	})
	return client
}

// NewWithToken ...
func NewWithToken(token string) *Client {
	c := New()
	c.token = token
	return c
}

// AuthenticateResult ...
type AuthenticateResult struct {
	StatusCode int    `json:"status_code"`
	Status     string `json:"status"`
	Msg        string `json:"msg"`
	Data       struct {
		Token string `json:"token"`
		User  struct {
			Name     string   `json:"name"`
			Email    string   `json:"email"`
			Username string   `json:"username"`
			Groups   []string `json:"groups"`
		} `json:"user"`
	} `json:"data"`
}

// Authenticate ...
func Authenticate(username, password string) (*AuthenticateResult, error) {
	type erro struct {
		StatusCode int    `json:"status_code"`
		Status     string `json:"status"`
		Msg        string `json:"msg"`
		Errors     string `json:"errors"`
	}
	resp, err := resty.New().R().SetBasicAuth(username, password).SetResult(&AuthenticateResult{}).SetError(&erro{}).
		Get("https://api.splunk.com/2.0/rest/login/splunk")
	if err != nil {
		return nil, fmt.Errorf("error while login: %s", err)
	}
	if resp.IsError() {
		if e, ok := resp.Error().(*erro); ok {
			return nil, fmt.Errorf("error while login: %s-%s", e.Status, e.Msg)
		}
		return nil, fmt.Errorf("error while login: %s", resp.Status())
	}
	var r *AuthenticateResult
	var ok bool
	if r, ok = resp.Result().(*AuthenticateResult); !ok {
		return nil, fmt.Errorf("error while login: failed to parse response")
	}
	return r, nil
}

// Login to appinspect service
func (c *Client) Login(username, password string) error {
	r, err := Authenticate(username, password)
	if err != nil {
		return err
	}
	c.token = r.Data.Token
	return nil
}

// SubmitResult ...
type SubmitResult struct {
	RequestID string `json:"request_id"`
	Message   string `json:"message"`
	Links     []struct {
		Rel  string `json:"rel"`
		Href string `json:"href"`
	} `json:"links"`
}

// Submit an app-package for inspection
func (c *Client) Submit(filename string, file io.Reader) (*SubmitResult, error) {

	formdata := url.Values{
		"included_tags": []string{"private_app"},
	}
	resp, err := c.R().SetAuthToken(c.token).SetFormDataFromValues(formdata).
		SetFileReader("app_package", filename, file).SetResult(&SubmitResult{}).Post("/validate")
	if err != nil {
		return nil, fmt.Errorf("error while submit: %s", err)
	}
	if resp.IsError() {
		if e, ok := resp.Error().(*Error); ok {
			return nil, fmt.Errorf("error while submit: %s", e)
		}
		return nil, fmt.Errorf("error while submit: %s", resp.Status())
	}
	var r *SubmitResult
	var ok bool
	if r, ok = resp.Result().(*SubmitResult); !ok {
		return nil, fmt.Errorf("error while submit: failed to parse response")
	}
	return r, nil
}

// StatusResult ...
type StatusResult struct {
	RequestID string `json:"request_id"`
	Status    string `json:"status"`
	Info      struct {
		Error         int `json:"error"`
		Failure       int `json:"failure"`
		Skipped       int `json:"skipped"`
		ManualCheck   int `json:"manual_check"`
		NotApplicable int `json:"not_applicable"`
		Warning       int `json:"warning"`
		Success       int `json:"success"`
	} `json:"info"`
	Links []struct {
		Rel  string `json:"rel"`
		Href string `json:"href"`
	} `json:"links"`
}

// Status of an app-package inspection
func (c *Client) Status(requestID string) (*StatusResult, error) {
	resp, err := c.R().SetAuthToken(c.token).SetResult(&StatusResult{}).Get("/validate/status/" + requestID)
	if err != nil {
		return nil, fmt.Errorf("error while getting status: %s", err)
	}
	if resp.IsError() {
		if e, ok := resp.Error().(*Error); ok {
			return nil, fmt.Errorf("error while getting status: %s", e)
		}
		return nil, fmt.Errorf("error while getting status: %s", resp.Status())
	}
	var r *StatusResult
	var ok bool
	if r, ok = resp.Result().(*StatusResult); !ok {
		return nil, fmt.Errorf("error while getting status: failed to parse response")
	}
	return r, nil
}

// ReportJSONResult ...
type ReportJSONResult struct {
	RequestID string `json:"request_id"`
	Cloc      string `json:"cloc"`
	Reports   []struct {
		AppAuthor      string `json:"app_author"`
		AppDescription string `json:"app_description"`
		AppHash        string `json:"app_hash"`
		AppName        string `json:"app_name"`
		AppVersion     string `json:"app_version"`
		Metrics        struct {
			StartTime     string  `json:"start_time"`
			EndTime       string  `json:"end_time"`
			ExecutionTime float64 `json:"execution_time"`
		} `json:"metrics"`
		RunParameters struct {
			APIRequestID      string   `json:"api_request_id"`
			Identity          string   `json:"identity"`
			SplunkbaseID      string   `json:"splunkbase_id"`
			Version           string   `json:"version"`
			SplunkVersion     string   `json:"splunk_version"`
			StackID           string   `json:"stack_id"`
			APITimestamp      string   `json:"api_timestamp"`
			PackageLocation   string   `json:"package_location"`
			AppinspectVersion string   `json:"appinspect_version"`
			IncludedTags      []string `json:"included_tags"`
			ExcludedTags      []string `json:"excluded_tags"`
		} `json:"run_parameters"`
		Groups []struct {
			Checks []struct {
				Description string `json:"description"`
				Messages    []struct {
					Code            string      `json:"code"`
					Filename        string      `json:"filename"`
					Line            int         `json:"line"`
					Message         string      `json:"message"`
					Result          string      `json:"result"`
					MessageFilename string      `json:"message_filename"`
					MessageLine     interface{} `json:"message_line"`
				} `json:"messages"`
				Name   string   `json:"name"`
				Tags   []string `json:"tags"`
				Result string   `json:"result"`
			} `json:"checks"`
			Description string `json:"description"`
			Name        string `json:"name"`
		} `json:"groups"`
		Summary struct {
			Error         int `json:"error"`
			Failure       int `json:"failure"`
			Skipped       int `json:"skipped"`
			ManualCheck   int `json:"manual_check"`
			NotApplicable int `json:"not_applicable"`
			Warning       int `json:"warning"`
			Success       int `json:"success"`
		} `json:"summary"`
	} `json:"reports"`
	Summary struct {
		Error         int `json:"error"`
		Failure       int `json:"failure"`
		Skipped       int `json:"skipped"`
		ManualCheck   int `json:"manual_check"`
		NotApplicable int `json:"not_applicable"`
		Warning       int `json:"warning"`
		Success       int `json:"success"`
	} `json:"summary"`
	Metrics struct {
		StartTime     string  `json:"start_time"`
		EndTime       string  `json:"end_time"`
		ExecutionTime float64 `json:"execution_time"`
	} `json:"metrics"`
	RunParameters struct {
		APIRequestID      string   `json:"api_request_id"`
		Identity          string   `json:"identity"`
		SplunkbaseID      string   `json:"splunkbase_id"`
		Version           string   `json:"version"`
		SplunkVersion     string   `json:"splunk_version"`
		StackID           string   `json:"stack_id"`
		APITimestamp      string   `json:"api_timestamp"`
		PackageLocation   string   `json:"package_location"`
		AppinspectVersion string   `json:"appinspect_version"`
		IncludedTags      []string `json:"included_tags"`
		ExcludedTags      []string `json:"excluded_tags"`
	} `json:"run_parameters"`
	Links []struct {
		Rel  string `json:"rel"`
		Href string `json:"href"`
	} `json:"links"`
}

// ReportJSON of an app-package inspection
func (c *Client) ReportJSON(requestID string) (*ReportJSONResult, error) {
	resp, err := c.R().SetAuthToken(c.token).SetResult(&ReportJSONResult{}).Get("/report/" + requestID)
	if err != nil {
		return nil, fmt.Errorf("error while getting json report: %s", err)
	}
	if resp.IsError() {
		if e, ok := resp.Error().(*Error); ok {
			return nil, fmt.Errorf("error while getting json report: %s", e)
		}
		return nil, fmt.Errorf("error while getting json report: %s", resp.Status())
	}
	var r *ReportJSONResult
	var ok bool
	if r, ok = resp.Result().(*ReportJSONResult); !ok {
		return nil, fmt.Errorf("error while getting json report: failed to parse response")
	}
	return r, nil
}
<file_sep>// Copyright 2021 Splunk Inc.
//
// Licensed 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 main

import (
	"fmt"
	"github.com/AlecAivazis/survey/v2"
	"github.com/splunk/acs-privateapps-demo/src/appinspect"
)

type login struct {
	SplunkComUsername string `kong:"env='SPLUNK_COM_USERNAME',help='the splunkbase username'"`
	SplunkComPassword string `kong:"env='SPLUNK_COM_PASSWORD',help='the splunkbase password'"`
}

func (v *login) Run(c *context) error {

	if v.SplunkComUsername == "" {
		survey.AskOne(&survey.Input{
			Message: "splunkbase username:",
		}, &v.SplunkComUsername)
	}
	if v.SplunkComPassword == "" {
		survey.AskOne(&survey.Password{
			Message: "splunkbase password:",
		}, &v.SplunkComPassword)
		fmt.Println("")
	}
	res, err := appinspect.Authenticate(v.SplunkComUsername, v.SplunkComPassword)
	if err != nil {
		return err
	}
	fmt.Printf("Token: %s\n", res.Data.Token)
	return nil
}
<file_sep>module github.com/splunk/acs-privateapps-demo

go 1.14

require (
	github.com/AlecAivazis/survey/v2 v2.2.7
	github.com/alecthomas/kong v0.2.12
	github.com/go-resty/resty/v2 v2.4.0
	github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
	github.com/segmentio/go-prompt v1.2.0
	github.com/stretchr/testify v1.7.0
	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
)
<file_sep>// Copyright 2021 Splunk Inc.
//
// Licensed 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 acs

import (
	"fmt"
	"io"

	"github.com/go-resty/resty/v2"
)

// Client to interface with the appinspect service
type Client struct {
	resty *resty.Client
	token string
}

// Error ...
type Error struct {
	Code        string `json:"code"`
	Description string `json:"description"`
}

func (e *Error) Error() string {
	return fmt.Sprintf("%s: %s", e.Code, e.Description)
}

// NewWithURL client to interface with the appinspect service
func NewWithURL(acsURL, token string) *Client {
	return &Client{
		resty: resty.New().SetHostURL(acsURL).SetError(&Error{}).SetAuthScheme("Bearer").SetAuthToken(token),
	}
}

// InstallApp ...
func (c *Client) InstallApp(stack, token, packageFileName string, packageReader io.Reader) error {
	resp, err := c.resty.R().SetFormData(map[string]string{"token": token}).
		SetFileReader("package", packageFileName, packageReader).
		Post("/" + stack + "/adminconfig/v2beta1/apps")
	if err != nil {
		return fmt.Errorf("error while installing app: %s", err)
	}
	if resp.IsError() {
		return fmt.Errorf("error while submit: %s: %s", resp.Status(), resp.String())
	}
	return nil
}

// App ...
type App struct {
	Label   *string `json:"label,omitempty"`
	Package *string `json:"package,omitempty"`
	Status  string  `json:"status"`
	Version *string `json:"version,omitempty"`
}

// ListApps ...
func (c *Client) ListApps(stack string) (map[string]App, error) {
	resp, err := c.resty.R().SetResult(&map[string]App{}).Get("/" + stack + "/adminconfig/v2beta1/apps")
	if err != nil {
		return nil, fmt.Errorf("error while listing app: %s", err)
	}
	if resp.IsError() {
		return nil, fmt.Errorf("error while listing apps: %s: %s", resp.Status(), resp.String())
	}
	apps, ok := resp.Result().(*map[string]App)
	if !ok {
		return nil, fmt.Errorf("error while parsing response")
	}
	return *apps, nil
}

// DescribeApp ...
func (c *Client) DescribeApp(stack string, appName string) (*App, error) {
	resp, err := c.resty.R().SetResult(&App{}).Get("/" + stack + "/adminconfig/v2beta1/apps/" + appName)
	if err != nil {
		return nil, fmt.Errorf("error while describing app: %s", err)
	}
	if resp.IsError() {
		return nil, fmt.Errorf("error while describing apps: %s: %s", resp.Status(), resp.String())
	}
	app, ok := resp.Result().(*App)
	if !ok {
		return nil, fmt.Errorf("error while parsing response")
	}
	return app, nil
}

// UninstallApp ...
func (c *Client) UninstallApp(stack string, appName string) error {
	resp, err := c.resty.R().Delete("/" + stack + "/adminconfig/v2beta1/apps/" + appName)
	if err != nil {
		return fmt.Errorf("error while uninstalling app: %s", err)
	}
	if resp.IsError() {
		return fmt.Errorf("error while uninstalling apps: %s: %s", resp.Status(), resp.String())
	}
	return nil
}
<file_sep>// Copyright 2021 Splunk Inc.
//
// Licensed 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 main

import (
	"github.com/alecthomas/kong"
)

type context struct {
	Debug bool
}

var cli struct {
	Debug     bool      `kong:"help='enable debug mode'"`
	Login     login     `kong:"cmd,help='login to splunkbase and generate token'"`
	Vet       vet       `kong:"cmd,help='vet the app package against the app-inspect service'"`
	Install   install   `kong:"cmd,help=install the app package on the splunk stack"`
	Uninstall uninstall `kong:"cmd,help=uninstall the app package from the splunk stack"`
	Get       get       `kong:"cmd,help=get an app/apps installed on the splunk stack"`
}

func main() {
	ctx := kong.Parse(&cli)
	// Call the Run() method of the selected parsed command.
	err := ctx.Run(&context{Debug: cli.Debug})
	ctx.FatalIfErrorf(err)
}
<file_sep>// Copyright 2021 Splunk Inc.
//
// Licensed 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 main

import (
	"encoding/json"
	"fmt"

	"github.com/AlecAivazis/survey/v2"
	"github.com/splunk/acs-privateapps-demo/src/acs"
)

type get struct {
	StackName  string `kong:"arg,help='the splunk cloud stack'"`
	AppName    string `kong:"arg,optional,help='the app'"`
	StackToken string `kong:"env='STACK_TOKEN',help='the stack sc_admin jwt token'"`
	AcsURL     string `kong:"env='ACS_URL',help='the acs url',default:'http://localhost:8443/'"`
}

func (g *get) Run(c *context) error {

	if g.StackToken == "" {
		survey.AskOne(&survey.Password{
			Message: "stack token:",
		}, &g.StackToken)
		fmt.Println("")
	}

	cli := acs.NewWithURL(g.AcsURL, g.StackToken)

	var object interface{}
	var err error
	if g.AppName == "" {
		object, err = cli.ListApps(g.StackName)
		if err != nil {
			return err
		}
	} else {
		object, err = cli.DescribeApp(g.StackName, g.AppName)
		if err != nil {
			return err
		}

	}

	if data, e := json.MarshalIndent(object, "", "    "); e == nil {
		fmt.Printf("%s\n", string(data))
	} else {
		fmt.Printf("%v\n", data)
	}
	return nil
}
<file_sep># acs-privateapps-demo

Cloned from:
https://github.com/splunk/acs-privateapps-demo

This repository demonstrates how one can use [splunkcloud's self-service apis](https://www.splunk.com/en_us/blog/platform/splunk-cloud-self-service-announcing-the-new-admin-config-service-api.html) to build a pipeline that can continuously deploy Splunk apps to Splunk Enterprise Cloud stacks.

## Steps
The pipeline primarily consists of 4 steps:
1. Build cloudctl (`make build-cloudctl`), the CLI that will be used for the remaining steps -- this step assumes that [go](https://golang.org) is installed.
1. Package the app artifacts into a tar gz archive (`make generate-app-package`) -- this step assumes there is a top-level directory called `testapp` which contains the app.
1. Upload the app-package to the app inspect service and wait for the inspection report (`make inspect-app`) -- this step assumes the existence of the environment variables defined below.
1. If the inspection is successful, install/update the app on the stack using the self-serive apis (`make install-app`) -- this step also assumes the existence of the environment variables defined below.

The steps of the pipeline can be found [here](https://github.com/splunk/acs-privateapps-demo/blob/main/.github/workflows/main.yml).

## Setting up the environment
The environment needs to be configured with a few variables. If leveraging this from a Github repository using Github Actions workflows, the variables will need to be set up as [secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets). If running this locally, these values simply need to be set as environment variables:
* SPLUNK_COM_USERNAME / SPLUNK_COM_PASSWORD - the [splunk.com](https://login.splunk.com/) credentials to use for authentication to perform app inspection.
* STACK_NAME - the name of the Splunk Cloud stack where you want to install/update the app package on.
* STACK_TOKEN - the [jwt token](https://docs.splunk.com/Documentation/Splunk/latest/Security/Setupauthenticationwithtokens) created on the stack.
* ACS_URL - `https://admin.splunk.com`
<file_sep>build-cloudctl:
	go build -o cloudCtl ./src/cmd

generate-app-package:
	tar zcf app-package.tar.gz testapp

inspect-app:
	./cloudCtl vet app-package.tar.gz --json-report-file=report.json

install-app:
	./cloudCtl install ${STACK_NAME} app-package.tar.gz

uninstall-app:
	./cloudCtl uninstall ${STACK_NAME} testapp

<file_sep>// Copyright 2021 Splunk Inc.
//
// Licensed 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 main

import (
	"bytes"
	"fmt"
	"github.com/AlecAivazis/survey/v2"
	"github.com/splunk/acs-privateapps-demo/src/acs"
	"github.com/splunk/acs-privateapps-demo/src/appinspect"
	"io/ioutil"
	"path/filepath"
)

type install struct {
	StackName         string `kong:"arg,help='the splunk cloud stack'"`
	SplunkComUsername string `kong:"env='SPLUNK_COM_USERNAME',help='the splunkbase username'"`
	SplunkComPassword string `kong:"env='SPLUNK_COM_PASSWORD',help='the splunkbase password'"`
	PackageFilePath   string `kong:"arg,help='the path to the app-package (tar.gz) file',type='path'"`
	StackToken        string `kong:"env='STACK_TOKEN',help='the stack sc_admin jwt token'"`
	AcsURL            string `kong:"env='ACS_URL',help='the acs url',default:'http://localhost:8443/'"`
}

func (i *install) Run(c *context) error {

	pf, err := ioutil.ReadFile(i.PackageFilePath)
	if err != nil {
		return err
	}
	if i.SplunkComUsername == "" {
		survey.AskOne(&survey.Input{
			Message: "splunkbase username:",
		}, &i.SplunkComUsername)
	}
	if i.SplunkComPassword == "" {
		survey.AskOne(&survey.Password{
			Message: "splunkbase password:",
		}, &i.SplunkComPassword)
		fmt.Println("")
	}

	if i.StackToken == "" {
		survey.AskOne(&survey.Password{
			Message: "stack token:",
		}, &i.StackToken)
		fmt.Println("")
	}
	ar, err := appinspect.Authenticate(i.SplunkComUsername, i.SplunkComPassword)
	if err != nil {
		return err
	}

	cli := acs.NewWithURL(i.AcsURL, i.StackToken)
	return cli.InstallApp(i.StackName, ar.Data.Token, filepath.Base(i.PackageFilePath), bytes.NewReader(pf))
}

type uninstall struct {
	StackName  string `kong:"arg,help='the splunk cloud stack'"`
	AppName    string `kong:"arg,optional,help='the app'"`
	StackToken string `kong:"env='STACK_TOKEN',help='the stack sc_admin jwt token'"`
	AcsURL     string `kong:"env='ACS_URL',help='the acs url',default:'http://localhost:8443/'"`
}

func (u *uninstall) Run(c *context) error {

	if u.StackToken == "" {
		survey.AskOne(&survey.Password{
			Message: "stack token:",
		}, &u.StackToken)
		fmt.Println("")
	}

	cli := acs.NewWithURL(u.AcsURL, u.StackToken)
	return cli.UninstallApp(u.StackName, u.AppName)
}
