Skip to content

Commit

Permalink
Support plugins from private Pulumi repos (#9185)
Browse files Browse the repository at this point in the history
* Support plugins from private Pulumi repos

This uses the same environment vars that we we're using for experimental private repos. That is GITHUB_TOKEN or GITHUB_ACTOR and GITHUB_PERSONAL_ACCESS_TOKEN.

This allows us to develop plugins in private repos but continue to use our standard plugin flow.

* Add test

* Add to CHANGELOG

* Only use GITHUB_TOKEN

As described in https://docs.github.com/en/rest/overview/resources-in-the-rest-api#authentication we should be using the Authorization header.

* Fix tests

* Improve download tests

* lint

* Unauth tests need to make sure GITHUB_TOKEN is clear

* Update CHANGELOG

* Log a warning about GITHUB_PERSONAL_ACCESS_TOKEN
  • Loading branch information
Frassle committed Mar 14, 2022
1 parent c5a4321 commit 874d127
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 71 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG_PENDING.md
Expand Up @@ -2,11 +2,14 @@

- [area/cli] - Implement `pulumi stack unselect` [#9179](https://github.com/pulumi/pulumi/pull/9179)
- [language/dotnet] - Updated Pulumi dotnet packages to use grpc-dotnet instead of grpc.
[#9149](https://github.com/pulumi/pulumi/pull/9149)
[#9149](https://github.com/pulumi/pulumi/pull/9149)

- [cli/config] Rename the `config` property in `Pulumi.yaml` to `stackConfigDir`. The `config` key will continue to be supported.
[#9145](https://github.com/pulumi/pulumi/pull/9145)

- [cli/plugins] Add support for downloading plugin from private Pulumi GitHub releases. This also drops the use of the `GITHUB_ACTOR` and `GITHUB_PERSONAL_ACCESS_TOKEN` environment variables for authenticating to github. Only `GITHUB_TOKEN` is now used, but this can be set to a personal access token.
[#9185](https://github.com/pulumi/pulumi/pull/9185)

### Bug Fixes

[sdk/nodejs] - Fix uncaught error "ENOENT: no such file or directory" when an error occurs during the stack up
Expand Down
80 changes: 31 additions & 49 deletions sdk/go/common/workspace/plugins.go
Expand Up @@ -183,7 +183,7 @@ func (source *getPulumiSource) Download(
serverURL,
url.QueryEscape(fmt.Sprintf("pulumi-%s-%s-v%s-%s-%s.tar.gz", source.kind, source.name, version.String(), opSy, arch)))

req, err := buildHTTPRequest(endpoint, "", "", "", "")
req, err := buildHTTPRequest(endpoint, "")
if err != nil {
return nil, -1, err
}
Expand All @@ -196,47 +196,31 @@ type githubSource struct {
name string
kind PluginKind

tokenType string
token string
username string
secret string
token string
}

// Creates a new github source adding authentication data in the environment, if it exists
func newGithubSource(organization, name string, kind PluginKind) *githubSource {
return &githubSource{
organization: organization,
name: name,
kind: kind,
}
}

// Creates a new github source from authentication data in the environment, if it exists
func newAuthenticatedGithubSource(organization, name string, kind PluginKind) (*githubSource, error) {
ghToken := os.Getenv("GITHUB_TOKEN")
paToken := ""
actor := ""
tokenType := "token"
if ghToken == "" {
tokenType = ""
paToken = os.Getenv("GITHUB_PERSONAL_ACCESS_TOKEN")
actor = os.Getenv("GITHUB_ACTOR")
}
if ghToken == "" && (paToken == "" || actor == "") {
return nil, errors.New("no GitHub authentication information provided")
// 14-03-2022 we stopped looking at GITHUB_PERSONAL_ACCESS_TOKEN and sending basic auth for github and
// instead just look at GITHUB_TOKEN and send in a header. Given GITHUB_PERSONAL_ACCESS_TOKEN was an
// envvar we made up we check to see if it's set here and log a warning. This can be removed after a few
// releases.
if os.Getenv("GITHUB_PERSONAL_ACCESS_TOKEN") != "" {
logging.Warningf("GITHUB_PERSONAL_ACCESS_TOKEN is no longer used for Github authentication, set GITHUB_TOKEN instead")
}

source := &githubSource{
return &githubSource{
organization: organization,
name: name,
kind: kind,

tokenType: tokenType,
token: ghToken,
username: actor,
secret: paToken,
token: os.Getenv("GITHUB_TOKEN"),
}
}

return source, nil
func (source *githubSource) HasAuthentication() bool {
return source.token != ""
}

func (source *githubSource) GetLatestVersion(
Expand All @@ -245,7 +229,7 @@ func (source *githubSource) GetLatestVersion(
"https://api.github.com/repos/%s/pulumi-%s/releases/latest",
source.organization, source.name)
logging.V(9).Infof("plugin GitHub releases url: %s", releaseURL)
req, err := buildHTTPRequest(releaseURL, source.tokenType, source.token, source.username, source.secret)
req, err := buildHTTPRequest(releaseURL, source.token)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -275,7 +259,7 @@ func (source *githubSource) GetLatestVersion(
func (source *githubSource) Download(
version semver.Version, opSy string, arch string,
getHTTPResponse func(*http.Request) (io.ReadCloser, int64, error)) (io.ReadCloser, int64, error) {
if source.tokenType == "" && source.token == "" && source.username == "" && source.secret == "" {
if !source.HasAuthentication() {
// If we're not using authentication we can just download from the release/download URL

logging.V(1).Infof(
Expand All @@ -286,7 +270,7 @@ func (source *githubSource) Download(
source.organization, source.name, version.String(), url.QueryEscape(fmt.Sprintf("pulumi-%s-%s-v%s-%s-%s.tar.gz",
source.kind, source.name, version.String(), opSy, arch)))

req, err := buildHTTPRequest(pluginURL, "", "", "", "")
req, err := buildHTTPRequest(pluginURL, "")
if err != nil {
return nil, -1, err
}
Expand All @@ -301,7 +285,7 @@ func (source *githubSource) Download(
source.organization, source.name, version.String())
logging.V(9).Infof("plugin GitHub releases url: %s", releaseURL)

req, err := buildHTTPRequest(releaseURL, source.tokenType, source.token, source.username, source.secret)
req, err := buildHTTPRequest(releaseURL, source.token)
if err != nil {
return nil, -1, err
}
Expand Down Expand Up @@ -341,7 +325,7 @@ func (source *githubSource) Download(

logging.V(1).Infof("%s downloading from %s", source.name, assetURL)

req, err = buildHTTPRequest(assetURL, source.tokenType, source.token, source.username, source.secret)
req, err = buildHTTPRequest(assetURL, source.token)
if err != nil {
return nil, -1, err
}
Expand Down Expand Up @@ -383,7 +367,7 @@ func (source *pluginURLSource) Download(
serverURL,
url.QueryEscape(fmt.Sprintf("pulumi-%s-%s-v%s-%s-%s.tar.gz", source.kind, source.name, version.String(), opSy, arch)))

req, err := buildHTTPRequest(endpoint, "", "", "", "")
req, err := buildHTTPRequest(endpoint, "")
if err != nil {
return nil, -1, err
}
Expand Down Expand Up @@ -413,17 +397,18 @@ func (source *fallbackSource) GetLatestVersion(
return version, nil
}

// Are we in experimental mode? Try a private github release
// Are we in experimental mode? Try a users private github release
if _, ok := os.LookupEnv("PULUMI_EXPERIMENTAL"); ok {
// Check if we have a repo owner set
repoOwner := os.Getenv("GITHUB_REPOSITORY_OWNER")
var privateErr error
if repoOwner == "" {
privateErr = errors.New("ENV[GITHUB_REPOSITORY_OWNER] not set")
} else {
var private PluginSource
private, privateErr = newAuthenticatedGithubSource(repoOwner, source.name, source.kind)
if privateErr == nil {
private := newGithubSource(repoOwner, source.name, source.kind)
if !private.HasAuthentication() {
privateErr = errors.New("no GitHub authentication information provided")
} else {
version, privateErr = private.GetLatestVersion(getHTTPResponse)
if privateErr == nil {
return version, nil
Expand Down Expand Up @@ -458,8 +443,10 @@ func (source *fallbackSource) Download(
if repoOwner == "" {
err = errors.New("ENV[GITHUB_REPOSITORY_OWNER] not set")
} else {
private, err := newAuthenticatedGithubSource(repoOwner, source.name, source.kind)
if err == nil {
private := newGithubSource(repoOwner, source.name, source.kind)
if !private.HasAuthentication() {
err = errors.New("no GitHub authentication information provided")
} else {
resp, length, err := private.Download(version, opSy, arch, getHTTPResponse)
if err == nil {
return resp, length, nil
Expand Down Expand Up @@ -660,7 +647,7 @@ func (info PluginInfo) Download() (io.ReadCloser, int64, error) {
return source.Download(*info.Version, opSy, arch, getHTTPResponse)
}

func buildHTTPRequest(pluginEndpoint, tokenType, token, username, secret string) (*http.Request, error) {
func buildHTTPRequest(pluginEndpoint string, token string) (*http.Request, error) {
req, err := http.NewRequest("GET", pluginEndpoint, nil)
if err != nil {
return nil, err
Expand All @@ -670,12 +657,7 @@ func buildHTTPRequest(pluginEndpoint, tokenType, token, username, secret string)
req.Header.Set("User-Agent", userAgent)

if token != "" {
if tokenType == "" {
tokenType = "Bearer"
}
req.Header.Set("Authentication", fmt.Sprintf("%s %s", tokenType, token))
} else if secret != "" && username != "" {
req.SetBasicAuth(username, secret)
req.Header.Set("Authorization", fmt.Sprintf("token %s", token))
}

return req, nil
Expand Down

0 comments on commit 874d127

Please sign in to comment.