Skip to content

Commit

Permalink
Fix: no longer require SRC_GITHUB_TOKEN to be set (#958)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdpleiness committed Mar 16, 2023
1 parent 097cf0e commit 454c398
Show file tree
Hide file tree
Showing 5 changed files with 340 additions and 309 deletions.
10 changes: 1 addition & 9 deletions cmd/src/validate_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/mattn/go-isatty"

"github.com/sourcegraph/src-cli/internal/api"
"github.com/sourcegraph/src-cli/internal/validate"
"github.com/sourcegraph/src-cli/internal/validate/install"

"github.com/sourcegraph/sourcegraph/lib/errors"
Expand Down Expand Up @@ -97,14 +96,7 @@ Environmental variables
validationSpec = install.DefaultConfig()
}

envGithubToken := os.Getenv("SRC_GITHUB_TOKEN")

// will work for now with only GitHub supported but will need to be revisited when other code hosts are supported
if envGithubToken == "" {
return errors.Newf("%s failed to read `SRC_GITHUB_TOKEN` environment variable", validate.WarningSign)
}

validationSpec.ExternalService.Config.GitHub.Token = envGithubToken
validationSpec.ExternalService.Config.GitHub.Token = os.Getenv("SRC_GITHUB_TOKEN")

return install.Validate(context.Background(), client, validationSpec)
}
Expand Down
132 changes: 132 additions & 0 deletions internal/validate/install/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package install

import (
"encoding/json"

"gopkg.in/yaml.v3"
)

type ExternalService struct {
// Type of code host, e.g. GITHUB.
Kind string `yaml:"kind"`

// Display name of external service, e.g. sourcegraph-test.
DisplayName string `yaml:"displayName"`

// Configuration for code host.
Config Config `yaml:"config"`

// Maximum retry attempts when cloning test repositories. Defaults to 5 retries.
MaxRetries int `yaml:"maxRetries"`

// Retry timeout in seconds. Defaults to 5 seconds
RetryTimeoutSeconds int `yaml:"retryTimeoutSeconds"`

// Delete code host when test is done. Defaults to true.
DeleteWhenDone bool `yaml:"deleteWhenDone"`
}

// Config for different types of code hosts.
type Config struct {
GitHub GitHub `yaml:"gitHub"`
}

// GitHub configuration parameters.
type GitHub struct {
// URL used to access your GitHub instance, e.g. https://github.com.
URL string `yaml:"url" json:"url"`

// Auth token used to authenticate to GitHub instance. This should be provided via env var SRC_GITHUB_TOKEN.
Token string `yaml:"token" json:"token"`

// List of organizations.
Orgs []string `yaml:"orgs" json:"orgs"`

// List of repositories to pull.
Repos []string `yaml:"repos" json:"repos"`
}

type Insight struct {
Title string `yaml:"title"`
DataSeries []map[string]any `yaml:"dataSeries"`
DeleteWhenDone bool `yaml:"deleteWhenDone"`
}

type ValidationSpec struct {
// Search queries used for validation testing, e.g. "repo:^github\\.com/gorilla/mux$ Router".
SearchQuery []string `yaml:"searchQuery"`

// External Service configuration.
ExternalService ExternalService `yaml:"externalService"`

// Insight used for validation testing.
Insight Insight `yaml:"insight"`
}

// DefaultConfig returns a default configuration to be used for testing.
func DefaultConfig() *ValidationSpec {
return &ValidationSpec{
SearchQuery: []string{
"repo:^github.com/sourcegraph/src-cli$ config",
"repo:^github.com/sourcegraph/src-cli$@4.0.0 config",
"repo:^github.com/sourcegraph/src-cli$ type:symbol config",
},
ExternalService: ExternalService{
Kind: "GITHUB",
DisplayName: "sourcegraph-test",
Config: Config{
GitHub: GitHub{
URL: "https://github.com",
Token: "",
Orgs: []string{},
Repos: []string{"sourcegraph/src-cli"},
},
},
MaxRetries: 5,
RetryTimeoutSeconds: 5,
DeleteWhenDone: true,
},
Insight: Insight{
Title: "test insight",
DataSeries: []map[string]any{
{
"query": "lang:javascript",
"label": "javascript",
"repositoryScope": "",
"lineColor": "#6495ED",
"timeScopeUnit": "MONTH",
"timeScopeValue": 1,
},
{
"query": "lang:typescript",
"label": "typescript",
"lineColor": "#DE3163",
"repositoryScope": "",
"timeScopeUnit": "MONTH",
"timeScopeValue": 1,
},
},
DeleteWhenDone: true,
},
}
}

// LoadYamlConfig will unmarshal a YAML configuration file into a ValidationSpec.
func LoadYamlConfig(userConfig []byte) (*ValidationSpec, error) {
var config ValidationSpec
if err := yaml.Unmarshal(userConfig, &config); err != nil {
return nil, err
}

return &config, nil
}

// LoadJsonConfig will unmarshal a JSON configuration file into a ValidationSpec.
func LoadJsonConfig(userConfig []byte) (*ValidationSpec, error) {
var config ValidationSpec
if err := json.Unmarshal(userConfig, &config); err != nil {
return nil, err
}

return &config, nil
}
95 changes: 95 additions & 0 deletions internal/validate/install/github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package install

import (
"context"
"encoding/json"
"fmt"
"log"

"github.com/sourcegraph/src-cli/internal/api"
"github.com/sourcegraph/src-cli/internal/validate"

"github.com/sourcegraph/sourcegraph/lib/errors"
)

const GITHUB = "GITHUB"

func validateGithub(ctx context.Context, client api.Client, config *ValidationSpec) (func(), error) {
// validate external service
log.Printf("%s validating external service", validate.EmojiFingerPointRight)

srvId, err := addGithubExternalService(ctx, client, config.ExternalService)
if err != nil {
return nil, err
}

log.Printf("%s external service %s is being added", validate.HourglassEmoji, config.ExternalService.DisplayName)

cleanupFunc := func() {
if srvId != "" && config.ExternalService.DeleteWhenDone {
_ = removeExternalService(ctx, client, srvId)
log.Printf("%s external service %s has been removed", validate.SuccessEmoji, config.ExternalService.DisplayName)
}
}

log.Printf("%s cloning repository", validate.HourglassEmoji)

repo := fmt.Sprintf("github.com/%s", config.ExternalService.Config.GitHub.Repos[0])
cloned, err := repoCloneTimeout(ctx, client, repo, config.ExternalService)
if err != nil {
return nil, err
}
if !cloned {
return nil, errors.Newf("%s validate failed, repo did not clone\n", validate.FailureEmoji)
}

log.Printf("%s repositry successfully cloned", validate.SuccessEmoji)

return cleanupFunc, nil
}

func addGithubExternalService(ctx context.Context, client api.Client, srv ExternalService) (string, error) {
if srv.Config.GitHub.Token == "" {
return "", errors.Newf("%s failed to read `SRC_GITHUB_TOKEN` environment variable", validate.WarningSign)
}

cfg, err := json.Marshal(srv.Config.GitHub)
if err != nil {
return "", errors.Wrap(err, "addExternalService failed")
}

q := clientQuery{
opName: "AddExternalService",
query: `mutation AddExternalService($kind: ExternalServiceKind!, $displayName: String!, $config: String!) {
addExternalService(input:{
kind:$kind,
displayName:$displayName,
config: $config
})
{
id
}
}`,
variables: jsonVars{
"kind": GITHUB,
"displayName": srv.DisplayName,
"config": string(cfg),
},
}

var result struct {
AddExternalService struct {
ID string `json:"id"`
} `json:"addExternalService"`
}

ok, err := client.NewRequest(q.query, q.variables).Do(ctx, &result)
if err != nil {
return "", errors.Wrap(err, "addExternalService failed")
}
if !ok {
return "", errors.New("addExternalService failed, no data to unmarshal")
}

return result.AddExternalService.ID, nil
}
101 changes: 101 additions & 0 deletions internal/validate/install/insight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package install

import (
"context"

"github.com/sourcegraph/src-cli/internal/api"

"github.com/sourcegraph/sourcegraph/lib/errors"
)

func createInsight(ctx context.Context, client api.Client, insight Insight) (string, error) {
var dataSeries []map[string]interface{}

for _, ds := range insight.DataSeries {
var series = map[string]interface{}{
"query": ds["query"],
"options": map[string]interface{}{
"label": ds["label"],
"lineColor": ds["lineColor"],
},
"repositoryScope": map[string]interface{}{
"repositories": ds["repositoryScope"],
},
"timeScope": map[string]interface{}{
"stepInterval": map[string]interface{}{
"unit": ds["timeScopeUnit"],
"value": ds["timeScopeValue"],
},
},
}

dataSeries = append(dataSeries, series)
}

q := clientQuery{
opName: "CreateLineChartSearchInsight",
query: `mutation CreateLineChartSearchInsight($input: LineChartSearchInsightInput!) {
createLineChartSearchInsight(input: $input) {
view {
id
}
}
}`,
variables: jsonVars{
"input": map[string]interface{}{
"options": map[string]interface{}{"title": insight.Title},
"dataSeries": dataSeries,
},
},
}

var result struct {
CreateLineChartSearchInsight struct {
View struct {
ID string `json:"id"`
} `json:"view"`
} `json:"createLineChartSearchInsight"`
}

ok, err := client.NewRequest(q.query, q.variables).Do(ctx, &result)
if err != nil {
return "", errors.Wrap(err, "createInsight failed")
}
if !ok {
return "", errors.New("createInsight failed, no data to unmarshal")
}

return result.CreateLineChartSearchInsight.View.ID, nil
}

func removeInsight(ctx context.Context, client api.Client, insightId string) error {
q := clientQuery{
opName: "DeleteInsightView",
query: `mutation DeleteInsightView ($id: ID!) {
deleteInsightView(id: $id){
alwaysNil
}
}`,
variables: jsonVars{
"id": insightId,
},
}

var result struct {
Data struct {
DeleteInsightView struct {
AlwaysNil string `json:"alwaysNil"`
} `json:"deleteInsightView"`
} `json:"data"`
}

ok, err := client.NewRequest(q.query, q.variables).Do(ctx, &result)
if err != nil {
return errors.Wrap(err, "removeInsight failed")
}
if !ok {
return errors.New("removeInsight failed, no data to unmarshal")
}

return nil
}
Loading

0 comments on commit 454c398

Please sign in to comment.