pulumi/sdk/go/common/workspace/plugins_test.go

1588 lines
47 KiB
Go
Raw Normal View History

// Copyright 2016-2019, Pulumi Corporation.
//
// 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 workspace
import (
2023-02-11 11:05:06 +00:00
"bytes"
"encoding/hex"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
URL-based plugin source overrides via env var (#16648) ### Motivation Pulumi plugin binaries can be downloaded by the CLI from multiple sources. By default, it's downloaded from Pulumi's GitHub releases or get.pulumi.com, but plugins can also specify their binary sources via the `PluginDownloadURL` schema option. They can point to custom GitHub, Gitlab, or HTTP locations. Enterprise customers ask for a way to isolate the CLI from downloads from random locations and to configure the CLI to go to their internal pre-approved artefact location instead. This way, Pulumi can run in "air-gapped" environments (which still have access to Cloud APIs, of course). Related issues: - https://github.com/pulumi/pulumi/issues/14459 - https://github.com/pulumi/pulumi/issues/16240 Currently, there is a basic mechanism to do so via the variable `pluginDownloadURLOverrides`, but it has two major limitations: - The variable value is set via a compile-time flag, so it requires a custom build of the CLI - The overrides are based on the plugin name, so the rules must be defined without access to the original URL, which makes it hard to provide universal rules and still distinguish between first-party, public third-party, or private in-house plugins - We ignore overrides for all plugins that have `PluginDownloadURL` set - Overrides can set a plugin replacement redirect only to HTTP(s) addresses ### Proposal This PR makes two sets of changes: 1. It allows passing overrides via the `PULUMI_PLUGIN_DOWNLOAD_URL_OVERRIDES` environment variable. The compile-time flag is still supported, but the env var takes priority. More configuration levers could be supported, but it not clear if we have good ones until [Support .pulumirc file for global config](https://github.com/pulumi/pulumi/issues/13484) is implemented. I don't expect users to want to set this via their stack configs, but I'm curious what others think. In any case, more sources can be added later. 2. The overrides now apply based on the original download URL, not just on plugin names. Actually, it's the base URL of a download source that is passed to the regexp matcher. Examples of possible options are: - `github://api.github.com/pulumi/pulumi-xyz` for a first-party plugin (note that we don't pass `get.pulumi.com` - `github://api.github.com/pulumiverse/pulumi-grafana` for a community plugin that sets `PluginDownloadURL` - `gitlab://gitlab-host/proj-name` for a community plugin hosted on Gitlab - `https://example.com/downloads/` for HTTP sources So, the override `^github://api.github.com/pulumi/pulumi-xyz=https://example.com/downloads/pulumi-xyz/` will override the single provider URL from our GitHub releases to the given HTTP location. On top of that, regular expressions may contain name groups to capture and use templated values. For example, `^github://api.github.com/(?P<org>[^/]+)/(?P<repo>[^/]+)=https://example.com/downloads/${org}/${repo}` captures any GitHub plugin and redirects it to its corresponding HTTP location. Group indices are also supported: the above override can also be written as `^github://api.github.com/(?P<org>[^/]+)/(?P<repo>[^/]+)=https://example.com/downloads/$1/$2`, with `$0` meaning the full match. The override URLs have the same semantics as `PluginDownloadURL`, so they can point to GitHub, Gitlab, HTTP, or anything we introduce in the future. ### Impact Technically, this is a breaking change, because name-based overrides will stop working. However, we are fairly certain that we have a single customer using the existing compile-time approach, and they indicated that they don't need the name-based overrides if they have URL-based overrides. I reviewed this PR with them and made sure they can migrate immediately after the change is released. Backwards compatibility is slightly tricky, because we'd need to keep name-based override _and_ not applying them to third-party plugins. But we can do it if necessary. Resolve #16240
2024-07-26 10:37:09 +00:00
"reflect"
"regexp"
"testing"
"time"
"github.com/blang/semver"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
"github.com/pulumi/pulumi/sdk/v3/go/common/testing/diagtest"
"github.com/pulumi/pulumi/sdk/v3/go/common/testing/iotest"
"github.com/stretchr/testify/assert"
2023-02-11 11:05:06 +00:00
"github.com/stretchr/testify/require"
)
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
func TestLegacyPluginSelection_Prerelease(t *testing.T) {
t.Parallel()
v1 := semver.MustParse("0.1.0")
v2 := semver.MustParse("0.2.0")
v3 := semver.MustParse("0.3.0-alpha")
candidatePlugins := []PluginInfo{
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v1,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v2,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v3,
},
{
Name: "notmyplugin",
Kind: apitype.ResourcePlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v3,
},
{
Name: "myplugin",
Kind: apitype.AnalyzerPlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v3,
},
}
result := LegacySelectCompatiblePlugin(candidatePlugins, apitype.ResourcePlugin, "myplugin", nil)
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
assert.NotNil(t, result)
assert.Equal(t, "myplugin", result.Name)
assert.Equal(t, "0.2.0", result.Version.String())
}
func TestLegacyPluginSelection_PrereleaseRequested(t *testing.T) {
t.Parallel()
v1 := semver.MustParse("0.1.0")
v2 := semver.MustParse("0.2.0-alpha")
v3 := semver.MustParse("0.3.0-alpha")
candidatePlugins := []PluginInfo{
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v1,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v2,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v3,
},
{
Name: "notmyplugin",
Kind: apitype.ResourcePlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v3,
},
{
Name: "myplugin",
Kind: apitype.AnalyzerPlugin,
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
Version: &v3,
},
}
v := semver.MustParse("0.2.0")
result := LegacySelectCompatiblePlugin(candidatePlugins, apitype.ResourcePlugin, "myplugin", &v)
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
assert.NotNil(t, result)
assert.Equal(t, "myplugin", result.Name)
assert.Equal(t, "0.3.0-alpha", result.Version.String())
}
func TestPluginSelection_ExactMatch(t *testing.T) {
t.Parallel()
v1 := semver.MustParse("0.1.0")
v2 := semver.MustParse("0.2.0")
v3 := semver.MustParse("0.3.0")
candidatePlugins := []PluginInfo{
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v1,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v2,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "notmyplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "myplugin",
Kind: apitype.AnalyzerPlugin,
Version: &v3,
},
}
requested := semver.MustParseRange("0.2.0")
result := SelectCompatiblePlugin(candidatePlugins, apitype.ResourcePlugin, "myplugin", requested)
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
assert.NotNil(t, result)
assert.Equal(t, "myplugin", result.Name)
assert.Equal(t, "0.2.0", result.Version.String())
}
func TestPluginSelection_ExactMatchNotFound(t *testing.T) {
t.Parallel()
v1 := semver.MustParse("0.1.0")
v2 := semver.MustParse("0.2.1")
v3 := semver.MustParse("0.3.0")
candidatePlugins := []PluginInfo{
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v1,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v2,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "notmyplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "myplugin",
Kind: apitype.AnalyzerPlugin,
Version: &v3,
},
}
requested := semver.MustParseRange("0.2.0")
result := SelectCompatiblePlugin(candidatePlugins, apitype.ResourcePlugin, "myplugin", requested)
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
assert.Nil(t, result)
}
func TestPluginSelection_PatchVersionSlide(t *testing.T) {
t.Parallel()
v1 := semver.MustParse("0.1.0")
v2 := semver.MustParse("0.2.0")
v21 := semver.MustParse("0.2.1")
v3 := semver.MustParse("0.3.0")
candidatePlugins := []PluginInfo{
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v1,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v2,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v21,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "notmyplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "myplugin",
Kind: apitype.AnalyzerPlugin,
Version: &v3,
},
}
requested := semver.MustParseRange(">=0.2.0 <0.3.0")
result := SelectCompatiblePlugin(candidatePlugins, apitype.ResourcePlugin, "myplugin", requested)
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
assert.NotNil(t, result)
assert.Equal(t, "myplugin", result.Name)
assert.Equal(t, "0.2.1", result.Version.String())
}
func TestPluginSelection_EmptyVersionNoAlternatives(t *testing.T) {
t.Parallel()
v1 := semver.MustParse("0.1.0")
v2 := semver.MustParse("0.2.1")
v3 := semver.MustParse("0.3.0")
candidatePlugins := []PluginInfo{
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v1,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v2,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: nil,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "notmyplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "myplugin",
Kind: apitype.AnalyzerPlugin,
Version: &v3,
},
}
requested := semver.MustParseRange("0.2.0")
result := SelectCompatiblePlugin(candidatePlugins, apitype.ResourcePlugin, "myplugin", requested)
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
assert.NotNil(t, result)
assert.Equal(t, "myplugin", result.Name)
assert.Nil(t, result.Version)
}
func TestPluginSelection_EmptyVersionWithAlternatives(t *testing.T) {
t.Parallel()
v1 := semver.MustParse("0.1.0")
v2 := semver.MustParse("0.2.0")
v3 := semver.MustParse("0.3.0")
candidatePlugins := []PluginInfo{
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v1,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v2,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: nil,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: nil,
},
{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "notmyplugin",
Kind: apitype.ResourcePlugin,
Version: &v3,
},
{
Name: "myplugin",
Kind: apitype.AnalyzerPlugin,
Version: &v3,
},
}
requested := semver.MustParseRange("0.2.0")
result := SelectCompatiblePlugin(candidatePlugins, apitype.ResourcePlugin, "myplugin", requested)
Prefer stable plugin release to pre-releases (#14700) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Fixes https://github.com/pulumi/pulumi/issues/14680. Updated the plugin logic (which we still use when no explict version is given) to prefer selecting a stable version over a pre-release version when no explict requested version is given. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [ ] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-03 09:15:07 +00:00
assert.NotNil(t, result)
assert.Equal(t, "myplugin", result.Name)
assert.Equal(t, "0.2.0", result.Version.String())
}
func newMockReadCloser(data []byte) (io.ReadCloser, int64, error) {
2023-02-11 11:05:06 +00:00
return io.NopCloser(bytes.NewReader(data)), int64(len(data)), nil
}
func newMockReadCloserString(data string) (io.ReadCloser, int64, error) {
2023-02-11 11:05:06 +00:00
return newMockReadCloser([]byte(data))
}
//nolint:paralleltest // mutates environment variables
func TestPluginDownload(t *testing.T) {
expectedBytes := []byte{1, 2, 3}
token := "RaNd0m70K3n_"
2023-02-11 11:05:06 +00:00
t.Run("Pulumi GitHub Releases", func(t *testing.T) {
t.Setenv("GITHUB_TOKEN", "")
version := semver.MustParse("4.30.0")
spec := PluginSpec{
PluginDownloadURL: "",
Name: "mockdl",
Version: &version,
Kind: apitype.PluginKind("resource"),
}
source, err := spec.GetSource()
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
if req.URL.String() == "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/tags/v4.30.0" {
2023-03-09 09:36:55 +00:00
assert.Equal(t, "", req.Header.Get("Authorization"))
assert.Equal(t, "application/json", req.Header.Get("Accept"))
// Minimal JSON from the releases API to get the test to pass
return newMockReadCloserString(`{
"assets": [
{
"url": "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/assets/654321",
"name": "pulumi-mockdl_4.30.0_checksums.txt"
},
{
"url": "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/assets/123456",
"name": "pulumi-resource-mockdl-v4.30.0-darwin-amd64.tar.gz"
}
]
}
`)
}
assert.Equal(t, "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/assets/123456", req.URL.String())
assert.Equal(t, "application/octet-stream", req.Header.Get("Accept"))
return newMockReadCloser(expectedBytes)
}
r, l, err := source.Download(*spec.Version, "darwin", "amd64", getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
2023-02-11 11:05:06 +00:00
t.Run("get.pulumi.com", func(t *testing.T) {
version := semver.MustParse("4.32.0")
spec := PluginSpec{
PluginDownloadURL: "",
Name: "otherdl",
Version: &version,
Kind: apitype.PluginKind("resource"),
}
source, err := spec.GetSource()
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
// Test that the asset isn't on github
if req.URL.String() == "https://api.github.com/repos/pulumi/pulumi-otherdl/releases/tags/v4.32.0" {
return nil, -1, errors.New("404 not found")
}
assert.Equal(t,
"https://get.pulumi.com/releases/plugins/pulumi-resource-otherdl-v4.32.0-darwin-amd64.tar.gz",
req.URL.String())
return newMockReadCloser(expectedBytes)
}
r, l, err := source.Download(*spec.Version, "darwin", "amd64", getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
2023-02-15 19:48:02 +00:00
t.Run("Custom http URL", func(t *testing.T) {
version := semver.MustParse("4.32.0")
spec := PluginSpec{
PluginDownloadURL: "http://customurl.jfrog.io/artifactory/pulumi-packages/package-name/v${VERSION}/${OS}/${ARCH}",
2023-02-15 19:48:02 +00:00
Name: "mockdl",
Version: &version,
Kind: apitype.PluginKind("resource"),
2023-02-15 19:48:02 +00:00
}
source, err := spec.GetSource()
require.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
assert.Equal(t,
"http://customurl.jfrog.io/artifactory/pulumi-packages/"+
"package-name/v4.32.0/darwin/amd64/pulumi-resource-mockdl-v4.32.0-darwin-amd64.tar.gz",
2023-02-15 19:48:02 +00:00
req.URL.String())
return newMockReadCloser(expectedBytes)
}
r, l, err := source.Download(*spec.Version, "darwin", "amd64", getHTTPResponse)
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
require.NoError(t, err)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
t.Run("Custom https URL", func(t *testing.T) {
version := semver.MustParse("4.32.0")
spec := PluginSpec{
PluginDownloadURL: "https://customurl.jfrog.io/artifactory/pulumi-packages/" +
"package-name/${NAME}/v${VERSION}/${OS}/${ARCH}/",
Name: "mockdl",
Version: &version,
Kind: apitype.PluginKind("resource"),
}
source, err := spec.GetSource()
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
assert.Equal(t,
"https://customurl.jfrog.io/artifactory/pulumi-packages/"+
"package-name/mockdl/v4.32.0/darwin/amd64/pulumi-resource-mockdl-v4.32.0-darwin-amd64.tar.gz",
req.URL.String())
return newMockReadCloser(expectedBytes)
}
r, l, err := source.Download(*spec.Version, "darwin", "amd64", getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
2023-02-11 11:05:06 +00:00
t.Run("Private Pulumi GitHub Releases", func(t *testing.T) {
t.Setenv("GITHUB_TOKEN", token)
version := semver.MustParse("4.32.0")
spec := PluginSpec{
PluginDownloadURL: "",
Name: "mockdl",
Version: &version,
Kind: apitype.PluginKind("resource"),
}
source, err := spec.GetSource()
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
if req.URL.String() == "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/tags/v4.32.0" {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
assert.Equal(t, "token "+token, req.Header.Get("Authorization"))
assert.Equal(t, "application/json", req.Header.Get("Accept"))
// Minimal JSON from the releases API to get the test to pass
return newMockReadCloserString(`{
"assets": [
{
"url": "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/assets/654321",
"name": "pulumi-mockdl_4.32.0_checksums.txt"
},
{
"url": "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/assets/123456",
"name": "pulumi-resource-mockdl-v4.32.0-darwin-amd64.tar.gz"
}
]
}
`)
}
assert.Equal(t, "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/assets/123456", req.URL.String())
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
assert.Equal(t, "token "+token, req.Header.Get("Authorization"))
assert.Equal(t, "application/octet-stream", req.Header.Get("Accept"))
return newMockReadCloser(expectedBytes)
}
r, l, err := source.Download(*spec.Version, "darwin", "amd64", getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
2023-02-11 11:05:06 +00:00
t.Run("Internal GitHub Releases", func(t *testing.T) {
t.Setenv("GITHUB_TOKEN", token)
version := semver.MustParse("4.32.0")
spec := PluginSpec{
PluginDownloadURL: "github://api.git.org/ourorg/mock",
Name: "mockdl",
Version: &version,
Kind: apitype.PluginKind("resource"),
}
source, err := spec.GetSource()
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
// Test that the asset isn't on github
if req.URL.String() == "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/tags/v4.32.0" {
return nil, -1, errors.New("404 not found")
}
if req.URL.String() == "https://api.git.org/repos/ourorg/mock/releases/tags/v4.32.0" {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
assert.Equal(t, "token "+token, req.Header.Get("Authorization"))
assert.Equal(t, "application/json", req.Header.Get("Accept"))
// Minimal JSON from the releases API to get the test to pass
return newMockReadCloserString(`{
"assets": [
{
"url": "https://api.git.org/repos/ourorg/mock/releases/assets/654321",
"name": "pulumi-mockdl_4.32.0_checksums.txt"
},
{
"url": "https://api.git.org/repos/ourorg/mock/releases/assets/123456",
"name": "pulumi-resource-mockdl-v4.32.0-darwin-amd64.tar.gz"
}
]
}
`)
}
assert.Equal(t, "https://api.git.org/repos/ourorg/mock/releases/assets/123456", req.URL.String())
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
assert.Equal(t, "token "+token, req.Header.Get("Authorization"))
assert.Equal(t, "application/octet-stream", req.Header.Get("Accept"))
return newMockReadCloser(expectedBytes)
}
r, l, err := source.Download(*spec.Version, "darwin", "amd64", getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
2023-02-11 11:05:06 +00:00
t.Run("Pulumi GitHub Releases With Checksum", func(t *testing.T) {
t.Setenv("GITHUB_TOKEN", "")
version := semver.MustParse("4.30.0")
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
if req.URL.String() == "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/tags/v4.30.0" {
2023-03-09 09:36:55 +00:00
assert.Equal(t, "", req.Header.Get("Authorization"))
assert.Equal(t, "application/json", req.Header.Get("Accept"))
// Minimal JSON from the releases API to get the test to pass
return newMockReadCloserString(`{
"assets": [
{
"url": "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/assets/654321",
"name": "pulumi-mockdl_4.30.0_checksums.txt"
},
{
"url": "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/assets/123456",
"name": "pulumi-resource-mockdl-v4.30.0-darwin-amd64.tar.gz"
}
]
}
`)
}
assert.Equal(t, "https://api.github.com/repos/pulumi/pulumi-mockdl/releases/assets/123456", req.URL.String())
assert.Equal(t, "application/octet-stream", req.Header.Get("Accept"))
return newMockReadCloser(expectedBytes)
}
chksum := "039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81" //nolint:gosec
t.Run("Invalid Checksum", func(t *testing.T) {
spec := PluginSpec{
PluginDownloadURL: "",
Name: "mockdl",
Version: &version,
Kind: apitype.PluginKind("resource"),
Checksums: map[string][]byte{
"darwin-amd64": {0},
},
}
source, err := spec.GetSource()
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
r, l, err := source.Download(*spec.Version, "darwin", "amd64", getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
assert.Error(t, err, "invalid checksum, expected 00, actual "+chksum)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
t.Run("Valid Checksum", func(t *testing.T) {
checksum, err := hex.DecodeString(chksum)
assert.NoError(t, err)
spec := PluginSpec{
PluginDownloadURL: "",
Name: "mockdl",
Version: &version,
Kind: apitype.PluginKind("resource"),
Checksums: map[string][]byte{
"darwin-amd64": checksum,
},
}
source, err := spec.GetSource()
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
r, l, err := source.Download(*spec.Version, "darwin", "amd64", getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
Allow language plugins to return plugin checksums (#13776) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Also fix an issue where if a platform was missing a checksum it would error with "invalid checksum, expected , actual 01234". None of the language runtimes yet return anything for this, but it's a simple plumbing to expose it for the future. We'll _probably_ start adding checksums to the pulumi-plugin.json files, and then GetRequiredPlugins can simply return that data. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-08-25 15:26:25 +00:00
require.NoError(t, err)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
t.Run("Missing Checksum", func(t *testing.T) {
// In this test the specification has checksums, but is missing the checksum for the current platform.
// There are two sensible ways to handle this:
// 1. Behave as if no checksums were specified at all, and simply fall back to not checking anything.
// 2. Error that the checksum for the current platform is missing.
// We choose to do the former, for now as that's more lenient.
spec := PluginSpec{
PluginDownloadURL: "",
Name: "mockdl",
Version: &version,
Kind: apitype.PluginKind("resource"),
Allow language plugins to return plugin checksums (#13776) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Also fix an issue where if a platform was missing a checksum it would error with "invalid checksum, expected , actual 01234". None of the language runtimes yet return anything for this, but it's a simple plumbing to expose it for the future. We'll _probably_ start adding checksums to the pulumi-plugin.json files, and then GetRequiredPlugins can simply return that data. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [x] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-08-25 15:26:25 +00:00
Checksums: map[string][]byte{
"windows-amd64": {0},
},
}
source, err := spec.GetSource()
require.NoError(t, err)
r, l, err := source.Download(*spec.Version, "darwin", "amd64", getHTTPResponse)
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
})
2023-02-11 11:05:06 +00:00
t.Run("GitLab Releases", func(t *testing.T) {
t.Setenv("GITLAB_TOKEN", token)
version := semver.MustParse("1.23.4")
spec := PluginSpec{
PluginDownloadURL: "gitlab://gitlab.com/278964",
Name: "mock-gitlab",
Version: &version,
Kind: apitype.PluginKind("resource"),
2023-02-11 11:05:06 +00:00
}
source, err := spec.GetSource()
require.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
assert.Equal(t,
"https://gitlab.com/api/v4/projects/278964/releases/v1.23.4/downloads/"+
"pulumi-resource-mock-gitlab-v1.23.4-windows-arm64.tar.gz", req.URL.String())
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
assert.Equal(t, "Bearer "+token, req.Header.Get("Authorization"))
2023-02-11 11:05:06 +00:00
assert.Equal(t, "application/octet-stream", req.Header.Get("Accept"))
return newMockReadCloser(expectedBytes)
}
r, l, err := source.Download(*spec.Version, "windows", "arm64", getHTTPResponse)
require.NoError(t, err)
readBytes, err := io.ReadAll(r)
require.NoError(t, err)
assert.Equal(t, int(l), len(readBytes))
assert.Equal(t, expectedBytes, readBytes)
})
}
//nolint:paralleltest // mutates environment variables
func TestPluginGetLatestVersion(t *testing.T) {
token := "RaNd0m70K3n_"
2023-02-11 11:05:06 +00:00
t.Run("Pulumi GitHub Releases", func(t *testing.T) {
t.Setenv("GITHUB_TOKEN", "")
spec := PluginSpec{
PluginDownloadURL: "",
Name: "mock-latest",
Kind: apitype.PluginKind("resource"),
}
expectedVersion := semver.MustParse("4.37.5")
source, err := spec.GetSource()
assert.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
assert.Equal(t,
"https://api.github.com/repos/pulumi/pulumi-mock-latest/releases/latest",
req.URL.String())
// Minimal JSON from the releases API to get the test to pass
return newMockReadCloserString(`{
"tag_name": "v4.37.5"
}`)
}
version, err := source.GetLatestVersion(getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
assert.Equal(t, expectedVersion, *version)
})
2023-02-15 19:48:02 +00:00
t.Run("Custom http URL", func(t *testing.T) {
spec := PluginSpec{
PluginDownloadURL: "http://customurl.jfrog.io/artifactory/pulumi-packages/package-name",
Name: "mock-latest",
Kind: apitype.PluginKind("resource"),
2023-02-15 19:48:02 +00:00
}
source, err := spec.GetSource()
require.NoError(t, err)
version, err := source.GetLatestVersion(getHTTPResponse)
assert.Nil(t, version)
assert.EqualError(t, err, "GetLatestVersion is not supported for plugins from http sources")
2023-02-15 19:48:02 +00:00
})
t.Run("Custom https URL", func(t *testing.T) {
spec := PluginSpec{
PluginDownloadURL: "https://customurl.jfrog.io/artifactory/pulumi-packages/package-name",
Name: "mock-latest",
Kind: apitype.PluginKind("resource"),
}
source, err := spec.GetSource()
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
version, err := source.GetLatestVersion(getHTTPResponse)
assert.Nil(t, version)
assert.EqualError(t, err, "GetLatestVersion is not supported for plugins from http sources")
})
2023-02-11 11:05:06 +00:00
t.Run("Private Pulumi GitHub Releases", func(t *testing.T) {
t.Setenv("GITHUB_TOKEN", token)
spec := PluginSpec{
PluginDownloadURL: "",
Name: "mock-private",
Kind: apitype.PluginKind("resource"),
}
expectedVersion := semver.MustParse("4.37.5")
source, err := spec.GetSource()
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
if req.URL.String() == "https://api.github.com/repos/pulumi/pulumi-mock-private/releases/latest" {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
assert.Equal(t, "token "+token, req.Header.Get("Authorization"))
assert.Equal(t, "application/json", req.Header.Get("Accept"))
// Minimal JSON from the releases API to get the test to pass
return newMockReadCloserString(`{
"tag_name": "v4.37.5"
}`)
}
panic("Unexpected call to getHTTPResponse")
}
version, err := source.GetLatestVersion(getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
assert.Equal(t, expectedVersion, *version)
})
2023-02-11 11:05:06 +00:00
t.Run("Internal GitHub Releases", func(t *testing.T) {
t.Setenv("GITHUB_TOKEN", token)
spec := PluginSpec{
PluginDownloadURL: "github://api.git.org/ourorg/mock",
Name: "mock-private",
Kind: apitype.PluginKind("resource"),
}
expectedVersion := semver.MustParse("4.37.5")
source, err := spec.GetSource()
assert.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
if req.URL.String() == "https://api.git.org/repos/ourorg/mock/releases/latest" {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
assert.Equal(t, "token "+token, req.Header.Get("Authorization"))
assert.Equal(t, "application/json", req.Header.Get("Accept"))
// Minimal JSON from the releases API to get the test to pass
return newMockReadCloserString(`{
"tag_name": "v4.37.5"
}`)
}
panic("Unexpected call to getHTTPResponse")
}
version, err := source.GetLatestVersion(getHTTPResponse)
2023-02-11 11:05:06 +00:00
require.NoError(t, err)
assert.Equal(t, expectedVersion, *version)
})
t.Run("GitLab Releases", func(t *testing.T) {
t.Setenv("GITLAB_TOKEN", token)
spec := PluginSpec{
PluginDownloadURL: "gitlab://gitlab.com/278964",
Name: "mock-gitlab",
Kind: apitype.PluginKind("resource"),
2023-02-11 11:05:06 +00:00
}
expectedVersion := semver.MustParse("1.23.0")
source, err := spec.GetSource()
require.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
if req.URL.String() == "https://gitlab.com/api/v4/projects/278964/releases/permalink/latest" {
Enable perfsprint linter (#14813) <!--- Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation. --> # Description <!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. --> Prompted by a comment in another review: https://github.com/pulumi/pulumi/pull/14654#discussion_r1419995945 This lints that we don't use `fmt.Errorf` when `errors.New` will suffice, it also covers a load of other cases where `Sprintf` is sub-optimal. Most of these edits were made by running `perfsprint --fix`. ## Checklist - [x] I have run `make tidy` to update any new dependencies - [x] I have run `make lint` to verify my code passes the lint check - [x] I have formatted my code using `gofumpt` <!--- Please provide details if the checkbox below is to be left unchecked. --> - [ ] I have added tests that prove my fix is effective or that my feature works <!--- User-facing changes require a CHANGELOG entry. --> - [ ] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change <!-- If the change(s) in this PR is a modification of an existing call to the Pulumi Cloud, then the service should honor older versions of the CLI where this change would not exist. You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add it to the service. --> - [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Cloud API version <!-- @Pulumi employees: If yes, you must submit corresponding changes in the service repo. -->
2023-12-12 12:19:42 +00:00
assert.Equal(t, "Bearer "+token, req.Header.Get("Authorization"))
2023-02-11 11:05:06 +00:00
assert.Equal(t, "application/json", req.Header.Get("Accept"))
// Minimal JSON from the releases API to get the test to pass
return newMockReadCloserString(`{
"tag_name": "v1.23"
}`)
}
panic("Unexpected call to getHTTPResponse")
}
version, err := source.GetLatestVersion(getHTTPResponse)
require.NoError(t, err)
assert.Equal(t, expectedVersion, *version)
})
2023-03-02 13:34:36 +00:00
t.Run("Hit GitHub ratelimit", func(t *testing.T) {
t.Setenv("GITHUB_TOKEN", "")
spec := PluginSpec{
PluginDownloadURL: "",
Name: "mock-latest",
Kind: apitype.PluginKind("resource"),
2023-03-02 13:34:36 +00:00
}
source, err := spec.GetSource()
assert.NoError(t, err)
getHTTPResponse := func(req *http.Request) (io.ReadCloser, int64, error) {
return nil, 0, newDownloadError(403, req.URL, http.Header{"X-Ratelimit-Remaining": []string{"0"}})
}
_, err = source.GetLatestVersion(getHTTPResponse)
assert.ErrorContains(t, err, "rate limit exceeded")
assert.ErrorContains(t, err, "https://api.github.com/repos/pulumi/pulumi-mock-latest/releases/latest")
2023-03-02 13:34:36 +00:00
})
}
func TestParsePluginDownloadURLOverride(t *testing.T) {
t.Parallel()
type match struct {
name string
url string
ok bool
}
tests := []struct {
input string
expected pluginDownloadOverrideArray
matches []match
expectedError string
}{
{
input: "",
expected: pluginDownloadOverrideArray{},
},
{
input: "^foo.*=https://foo",
expected: pluginDownloadOverrideArray{
{
reg: regexp.MustCompile("^foo.*"),
url: "https://foo",
},
},
matches: []match{
{
name: "foo",
url: "https://foo",
ok: true,
},
{
name: "foo-bar",
url: "https://foo",
ok: true,
},
{
name: "fo",
url: "",
ok: false,
},
{
name: "",
url: "",
ok: false,
},
{
name: "nope",
url: "",
ok: false,
},
},
},
{
input: "^foo.*=https://foo,^bar.*=https://bar",
expected: pluginDownloadOverrideArray{
{
reg: regexp.MustCompile("^foo.*"),
url: "https://foo",
},
{
reg: regexp.MustCompile("^bar.*"),
url: "https://bar",
},
},
matches: []match{
{
name: "foo",
url: "https://foo",
ok: true,
},
{
name: "foo-bar",
url: "https://foo",
ok: true,
},
{
name: "fo",
url: "",
ok: false,
},
{
name: "",
url: "",
ok: false,
},
{
name: "bar",
url: "https://bar",
ok: true,
},
{
name: "barbaz",
url: "https://bar",
ok: true,
},
{
name: "ba",
url: "",
ok: false,
},
{
name: "nope",
url: "",
ok: false,
},
},
},
{
input: "=", // missing regex and url
expectedError: "expected format to be \"regexp1=URL1,regexp2=URL2\"; got \"=\"",
},
{
input: "^foo.*=", // missing url
expectedError: "expected format to be \"regexp1=URL1,regexp2=URL2\"; got \"^foo.*=\"",
},
{
input: "=https://foo", // missing regex
expectedError: "expected format to be \"regexp1=URL1,regexp2=URL2\"; got \"=https://foo\"",
},
{
input: "^foo.*=https://foo,", // trailing comma
expectedError: "expected format to be \"regexp1=URL1,regexp2=URL2\"; got \"^foo.*=https://foo,\"",
},
{
input: "[=https://foo", // invalid regex
expectedError: "error parsing regexp: missing closing ]: `[`",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.input, func(t *testing.T) {
t.Parallel()
actual, err := parsePluginDownloadURLOverrides(tt.input)
if tt.expectedError != "" {
assert.EqualError(t, err, tt.expectedError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expected, actual)
if len(tt.matches) > 0 {
for _, match := range tt.matches {
actualURL, actualOK := actual.get(match.name)
assert.Equal(t, match.url, actualURL)
assert.Equal(t, match.ok, actualOK)
}
}
})
}
}
URL-based plugin source overrides via env var (#16648) ### Motivation Pulumi plugin binaries can be downloaded by the CLI from multiple sources. By default, it's downloaded from Pulumi's GitHub releases or get.pulumi.com, but plugins can also specify their binary sources via the `PluginDownloadURL` schema option. They can point to custom GitHub, Gitlab, or HTTP locations. Enterprise customers ask for a way to isolate the CLI from downloads from random locations and to configure the CLI to go to their internal pre-approved artefact location instead. This way, Pulumi can run in "air-gapped" environments (which still have access to Cloud APIs, of course). Related issues: - https://github.com/pulumi/pulumi/issues/14459 - https://github.com/pulumi/pulumi/issues/16240 Currently, there is a basic mechanism to do so via the variable `pluginDownloadURLOverrides`, but it has two major limitations: - The variable value is set via a compile-time flag, so it requires a custom build of the CLI - The overrides are based on the plugin name, so the rules must be defined without access to the original URL, which makes it hard to provide universal rules and still distinguish between first-party, public third-party, or private in-house plugins - We ignore overrides for all plugins that have `PluginDownloadURL` set - Overrides can set a plugin replacement redirect only to HTTP(s) addresses ### Proposal This PR makes two sets of changes: 1. It allows passing overrides via the `PULUMI_PLUGIN_DOWNLOAD_URL_OVERRIDES` environment variable. The compile-time flag is still supported, but the env var takes priority. More configuration levers could be supported, but it not clear if we have good ones until [Support .pulumirc file for global config](https://github.com/pulumi/pulumi/issues/13484) is implemented. I don't expect users to want to set this via their stack configs, but I'm curious what others think. In any case, more sources can be added later. 2. The overrides now apply based on the original download URL, not just on plugin names. Actually, it's the base URL of a download source that is passed to the regexp matcher. Examples of possible options are: - `github://api.github.com/pulumi/pulumi-xyz` for a first-party plugin (note that we don't pass `get.pulumi.com` - `github://api.github.com/pulumiverse/pulumi-grafana` for a community plugin that sets `PluginDownloadURL` - `gitlab://gitlab-host/proj-name` for a community plugin hosted on Gitlab - `https://example.com/downloads/` for HTTP sources So, the override `^github://api.github.com/pulumi/pulumi-xyz=https://example.com/downloads/pulumi-xyz/` will override the single provider URL from our GitHub releases to the given HTTP location. On top of that, regular expressions may contain name groups to capture and use templated values. For example, `^github://api.github.com/(?P<org>[^/]+)/(?P<repo>[^/]+)=https://example.com/downloads/${org}/${repo}` captures any GitHub plugin and redirects it to its corresponding HTTP location. Group indices are also supported: the above override can also be written as `^github://api.github.com/(?P<org>[^/]+)/(?P<repo>[^/]+)=https://example.com/downloads/$1/$2`, with `$0` meaning the full match. The override URLs have the same semantics as `PluginDownloadURL`, so they can point to GitHub, Gitlab, HTTP, or anything we introduce in the future. ### Impact Technically, this is a breaking change, because name-based overrides will stop working. However, we are fairly certain that we have a single customer using the existing compile-time approach, and they indicated that they don't need the name-based overrides if they have URL-based overrides. I reviewed this PR with them and made sure they can migrate immediately after the change is released. Backwards compatibility is slightly tricky, because we'd need to keep name-based override _and_ not applying them to third-party plugins. But we can do it if necessary. Resolve #16240
2024-07-26 10:37:09 +00:00
func TestPluginDownloadOverrideArray_Get(t *testing.T) {
t.Parallel()
tests := []struct {
name string
overrides pluginDownloadOverrideArray
input string
expectedURL string
expectedMatch bool
}{
{
name: "No match",
overrides: pluginDownloadOverrideArray{
{reg: regexp.MustCompile(`^test-plugin$`), url: "https://example.com/test-plugin"},
},
input: "another-plugin",
expectedURL: "",
expectedMatch: false,
},
{
name: "Simple match",
overrides: pluginDownloadOverrideArray{
{reg: regexp.MustCompile(`^test-plugin$`), url: "https://example.com/test-plugin"},
},
input: "test-plugin",
expectedURL: "https://example.com/test-plugin",
expectedMatch: true,
},
{
name: "Match with name placeholders",
overrides: pluginDownloadOverrideArray{
{
reg: regexp.MustCompile(`^(?P<org>[\w-]+)-v(?P<repo>\d+\.\d+\.\d+)$`),
url: "https://example.com/${org}/${repo}/plugin.zip",
},
},
input: "my-plugin-v1.2.3",
expectedURL: "https://example.com/my-plugin/1.2.3/plugin.zip",
expectedMatch: true,
},
{
name: "Match with index placeholders",
overrides: pluginDownloadOverrideArray{
{
reg: regexp.MustCompile(`^(?P<org>[\w-]+)-v(?P<repo>\d+\.\d+\.\d+)$`),
url: "https://example.com/$1/$2/plugin.zip",
},
},
input: "my-plugin-v1.2.3",
expectedURL: "https://example.com/my-plugin/1.2.3/plugin.zip",
expectedMatch: true,
},
{
name: "Match with $0 placeholder",
overrides: pluginDownloadOverrideArray{
{reg: regexp.MustCompile(`^.+$`), url: "https://example.com/downloads?source=$0"},
},
input: "test-plugin",
expectedURL: "https://example.com/downloads?source=test-plugin",
expectedMatch: true,
},
{
name: "Multiple overrides, second matches",
overrides: pluginDownloadOverrideArray{
{reg: regexp.MustCompile(`^test-plugin$`), url: "https://example.com/test-plugin"},
{reg: regexp.MustCompile(`^another-plugin$`), url: "https://example.com/another-plugin"},
},
input: "another-plugin",
expectedURL: "https://example.com/another-plugin",
expectedMatch: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
actualURL, actualMatch := tt.overrides.get(tt.input)
if actualURL != tt.expectedURL {
assert.Equal(t, tt.expectedURL, actualURL)
}
if actualMatch != tt.expectedMatch {
assert.Equal(t, tt.expectedMatch, actualMatch)
}
})
}
}
func TestDownloadToFile_retries(t *testing.T) {
t.Parallel()
// Verifies that DownloadToFile retries on transient errors
// when trying to download plugins,
// and that it calls the wrapper and retry functions as expected.
//
// Regression test for https://github.com/pulumi/pulumi/issues/12456.
var numRequests int
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method, "expected GET request")
assert.Regexp(t, `/pulumi-language-myplugin-v1.0.0-\S+\.tar\.gz`, r.URL.Path,
"unexpected URL path")
// Fails all requests with a 500 error.
// This will cause every download attempt to fail
// and be retried.
w.WriteHeader(http.StatusInternalServerError)
numRequests++
}))
t.Cleanup(server.Close)
defer func() {
assert.Equal(t, 5, numRequests,
"server received more requests than expected")
}()
// Create a fake plugin.
version := semver.MustParse("1.0.0")
spec := PluginSpec{
Name: "myplugin",
Kind: apitype.LanguagePlugin,
Version: &version,
PluginDownloadURL: server.URL,
PluginDir: t.TempDir(),
}
// numRetries is tracked separately from numRequests.
// numRequests is the number of requests received by the server,
// while numRetries is the number of times the retry function is called.
// These should match--the function is called on all failures.
var numRetries int
currentTime := time.Now()
_, err := (&pluginDownloader{
OnRetry: func(err error, attempt, limit int, delay time.Duration) {
assert.Equal(t, 5, limit, "unexpected retry limit")
numRetries++
assert.Equal(t, numRetries, attempt, "unexpected attempt number")
},
After: func(d time.Duration) <-chan time.Time {
currentTime = currentTime.Add(d)
ch := make(chan time.Time, 1)
ch <- currentTime
return ch
},
}).DownloadToFile(spec)
assert.ErrorContains(t, err, "failed to download plugin: myplugin-1.0.0")
assert.Equal(t, numRequests, numRetries)
}
//nolint:paralleltest // changes directory for process
func TestUnmarshalProjectWithProviderList(t *testing.T) {
t.Parallel()
tempdir := t.TempDir()
pyaml := filepath.Join(tempdir, "Pulumi.yaml")
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
// write to pyaml
err := os.WriteFile(pyaml, []byte(`name: test-yaml
runtime: yaml
description: "Test Pulumi YAML"
plugins:
providers:
- name: aws
version: 1.0.0
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
path: ../bin/aws`), 0o600)
assert.NoError(t, err)
proj, err := LoadProject(pyaml)
assert.NoError(t, err)
assert.NotNil(t, proj.Plugins)
assert.Equal(t, 1, len(proj.Plugins.Providers))
assert.Equal(t, "aws", proj.Plugins.Providers[0].Name)
assert.Equal(t, "1.0.0", proj.Plugins.Providers[0].Version)
assert.Equal(t, "../bin/aws", proj.Plugins.Providers[0].Path)
}
2023-02-15 19:48:02 +00:00
URL-based plugin source overrides via env var (#16648) ### Motivation Pulumi plugin binaries can be downloaded by the CLI from multiple sources. By default, it's downloaded from Pulumi's GitHub releases or get.pulumi.com, but plugins can also specify their binary sources via the `PluginDownloadURL` schema option. They can point to custom GitHub, Gitlab, or HTTP locations. Enterprise customers ask for a way to isolate the CLI from downloads from random locations and to configure the CLI to go to their internal pre-approved artefact location instead. This way, Pulumi can run in "air-gapped" environments (which still have access to Cloud APIs, of course). Related issues: - https://github.com/pulumi/pulumi/issues/14459 - https://github.com/pulumi/pulumi/issues/16240 Currently, there is a basic mechanism to do so via the variable `pluginDownloadURLOverrides`, but it has two major limitations: - The variable value is set via a compile-time flag, so it requires a custom build of the CLI - The overrides are based on the plugin name, so the rules must be defined without access to the original URL, which makes it hard to provide universal rules and still distinguish between first-party, public third-party, or private in-house plugins - We ignore overrides for all plugins that have `PluginDownloadURL` set - Overrides can set a plugin replacement redirect only to HTTP(s) addresses ### Proposal This PR makes two sets of changes: 1. It allows passing overrides via the `PULUMI_PLUGIN_DOWNLOAD_URL_OVERRIDES` environment variable. The compile-time flag is still supported, but the env var takes priority. More configuration levers could be supported, but it not clear if we have good ones until [Support .pulumirc file for global config](https://github.com/pulumi/pulumi/issues/13484) is implemented. I don't expect users to want to set this via their stack configs, but I'm curious what others think. In any case, more sources can be added later. 2. The overrides now apply based on the original download URL, not just on plugin names. Actually, it's the base URL of a download source that is passed to the regexp matcher. Examples of possible options are: - `github://api.github.com/pulumi/pulumi-xyz` for a first-party plugin (note that we don't pass `get.pulumi.com` - `github://api.github.com/pulumiverse/pulumi-grafana` for a community plugin that sets `PluginDownloadURL` - `gitlab://gitlab-host/proj-name` for a community plugin hosted on Gitlab - `https://example.com/downloads/` for HTTP sources So, the override `^github://api.github.com/pulumi/pulumi-xyz=https://example.com/downloads/pulumi-xyz/` will override the single provider URL from our GitHub releases to the given HTTP location. On top of that, regular expressions may contain name groups to capture and use templated values. For example, `^github://api.github.com/(?P<org>[^/]+)/(?P<repo>[^/]+)=https://example.com/downloads/${org}/${repo}` captures any GitHub plugin and redirects it to its corresponding HTTP location. Group indices are also supported: the above override can also be written as `^github://api.github.com/(?P<org>[^/]+)/(?P<repo>[^/]+)=https://example.com/downloads/$1/$2`, with `$0` meaning the full match. The override URLs have the same semantics as `PluginDownloadURL`, so they can point to GitHub, Gitlab, HTTP, or anything we introduce in the future. ### Impact Technically, this is a breaking change, because name-based overrides will stop working. However, we are fairly certain that we have a single customer using the existing compile-time approach, and they indicated that they don't need the name-based overrides if they have URL-based overrides. I reviewed this PR with them and made sure they can migrate immediately after the change is released. Backwards compatibility is slightly tricky, because we'd need to keep name-based override _and_ not applying them to third-party plugins. But we can do it if necessary. Resolve #16240
2024-07-26 10:37:09 +00:00
//nolint:paralleltest // mutates pluginDownloadURLOverridesParsed
func TestPluginSpec_GetSource(t *testing.T) {
tests := []struct {
name string
spec PluginSpec
overrides pluginDownloadOverrideArray
expectedSourceType string
expectedURL string
expectedErrMsg string
}{
{
name: "Use PluginDownloadURL (HTTP)",
spec: PluginSpec{
Name: "test-plugin",
Kind: apitype.PluginKind("resource"),
PluginDownloadURL: "https://example.com/test-plugin",
},
expectedSourceType: "*workspace.httpSource",
expectedURL: "https://example.com/test-plugin",
},
{
name: "Use PluginDownloadURL (GitHub)",
spec: PluginSpec{
Name: "test-plugin",
Kind: apitype.PluginKind("resource"),
PluginDownloadURL: "github://api.github.com/owner/repo",
},
expectedSourceType: "*workspace.githubSource",
expectedURL: "github://api.github.com/owner/repo",
},
{
name: "Use PluginDownloadURL (GitLab)",
spec: PluginSpec{
Name: "test-plugin",
Kind: apitype.PluginKind("resource"),
PluginDownloadURL: "gitlab://mygitlab.example.com/proj1",
},
expectedSourceType: "*workspace.gitlabSource",
expectedURL: "gitlab://mygitlab.example.com/proj1",
},
{
name: "Use fallback source",
spec: PluginSpec{
Name: "test-plugin",
Kind: apitype.PluginKind("resource"),
},
expectedSourceType: "*workspace.fallbackSource",
expectedURL: "github://api.github.com/pulumi/pulumi-test-plugin",
},
{
name: "Apply override (HTTP)",
spec: PluginSpec{
Name: "test-plugin",
Kind: apitype.PluginKind("resource"),
},
overrides: pluginDownloadOverrideArray{
{reg: regexp.MustCompile(`test-plugin`), url: "https://example.com/test-plugin"},
},
expectedSourceType: "*workspace.httpSource",
expectedURL: "https://example.com/test-plugin",
},
{
name: "Apply override (GitHub)",
spec: PluginSpec{
Name: "test-plugin",
Kind: apitype.PluginKind("resource"),
},
overrides: pluginDownloadOverrideArray{
{reg: regexp.MustCompile(`test-plugin`), url: "github://api.github.com/test-org/test-plugin"},
},
expectedSourceType: "*workspace.githubSource",
expectedURL: "github://api.github.com/test-org/test-plugin",
},
{
name: "Apply checksums",
spec: PluginSpec{
Name: "test-plugin",
Kind: apitype.PluginKind("resource"),
Checksums: map[string][]byte{"checksum1": []byte("checksum2")},
},
expectedSourceType: "*workspace.checksumSource",
expectedURL: "github://api.github.com/pulumi/pulumi-test-plugin",
},
{
name: "Invalid URL",
spec: PluginSpec{
Name: "test-plugin",
Kind: apitype.PluginKind("resource"),
PluginDownloadURL: "://invalid-url",
},
expectedErrMsg: "parse \"://invalid-url\": missing protocol scheme",
},
{
name: "Unknown scheme",
spec: PluginSpec{
Name: "test-plugin",
Kind: apitype.PluginKind("resource"),
PluginDownloadURL: "unknown://example.com/plugin",
},
expectedErrMsg: "unknown plugin source scheme: unknown",
},
}
2023-02-15 19:48:02 +00:00
URL-based plugin source overrides via env var (#16648) ### Motivation Pulumi plugin binaries can be downloaded by the CLI from multiple sources. By default, it's downloaded from Pulumi's GitHub releases or get.pulumi.com, but plugins can also specify their binary sources via the `PluginDownloadURL` schema option. They can point to custom GitHub, Gitlab, or HTTP locations. Enterprise customers ask for a way to isolate the CLI from downloads from random locations and to configure the CLI to go to their internal pre-approved artefact location instead. This way, Pulumi can run in "air-gapped" environments (which still have access to Cloud APIs, of course). Related issues: - https://github.com/pulumi/pulumi/issues/14459 - https://github.com/pulumi/pulumi/issues/16240 Currently, there is a basic mechanism to do so via the variable `pluginDownloadURLOverrides`, but it has two major limitations: - The variable value is set via a compile-time flag, so it requires a custom build of the CLI - The overrides are based on the plugin name, so the rules must be defined without access to the original URL, which makes it hard to provide universal rules and still distinguish between first-party, public third-party, or private in-house plugins - We ignore overrides for all plugins that have `PluginDownloadURL` set - Overrides can set a plugin replacement redirect only to HTTP(s) addresses ### Proposal This PR makes two sets of changes: 1. It allows passing overrides via the `PULUMI_PLUGIN_DOWNLOAD_URL_OVERRIDES` environment variable. The compile-time flag is still supported, but the env var takes priority. More configuration levers could be supported, but it not clear if we have good ones until [Support .pulumirc file for global config](https://github.com/pulumi/pulumi/issues/13484) is implemented. I don't expect users to want to set this via their stack configs, but I'm curious what others think. In any case, more sources can be added later. 2. The overrides now apply based on the original download URL, not just on plugin names. Actually, it's the base URL of a download source that is passed to the regexp matcher. Examples of possible options are: - `github://api.github.com/pulumi/pulumi-xyz` for a first-party plugin (note that we don't pass `get.pulumi.com` - `github://api.github.com/pulumiverse/pulumi-grafana` for a community plugin that sets `PluginDownloadURL` - `gitlab://gitlab-host/proj-name` for a community plugin hosted on Gitlab - `https://example.com/downloads/` for HTTP sources So, the override `^github://api.github.com/pulumi/pulumi-xyz=https://example.com/downloads/pulumi-xyz/` will override the single provider URL from our GitHub releases to the given HTTP location. On top of that, regular expressions may contain name groups to capture and use templated values. For example, `^github://api.github.com/(?P<org>[^/]+)/(?P<repo>[^/]+)=https://example.com/downloads/${org}/${repo}` captures any GitHub plugin and redirects it to its corresponding HTTP location. Group indices are also supported: the above override can also be written as `^github://api.github.com/(?P<org>[^/]+)/(?P<repo>[^/]+)=https://example.com/downloads/$1/$2`, with `$0` meaning the full match. The override URLs have the same semantics as `PluginDownloadURL`, so they can point to GitHub, Gitlab, HTTP, or anything we introduce in the future. ### Impact Technically, this is a breaking change, because name-based overrides will stop working. However, we are fairly certain that we have a single customer using the existing compile-time approach, and they indicated that they don't need the name-based overrides if they have URL-based overrides. I reviewed this PR with them and made sure they can migrate immediately after the change is released. Backwards compatibility is slightly tricky, because we'd need to keep name-based override _and_ not applying them to third-party plugins. But we can do it if necessary. Resolve #16240
2024-07-26 10:37:09 +00:00
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pluginDownloadURLOverridesParsed = tt.overrides
source, err := tt.spec.GetSource()
assert.Equal(t, tt.expectedErrMsg != "", err != nil)
if err != nil {
assert.Equal(t, tt.expectedErrMsg, err.Error())
return
}
actualSourceType := reflect.TypeOf(source).String()
assert.Equal(t, tt.expectedSourceType, actualSourceType)
assert.Equal(t, tt.expectedURL, source.URL())
})
2023-02-15 19:48:02 +00:00
}
}
func TestMissingErrorText(t *testing.T) {
t.Parallel()
v1 := semver.MustParse("0.1.0")
tests := []struct {
Name string
Plugin PluginInfo
IncludeAmbient bool
ExpectedError string
}{
{
Name: "ResourceWithVersion",
Plugin: PluginInfo{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v1,
},
IncludeAmbient: true,
ExpectedError: "no resource plugin 'pulumi-resource-myplugin' found in the workspace at version v0.1.0 " +
"or on your $PATH",
},
{
Name: "ResourceWithVersion_ExcludeAmbient",
Plugin: PluginInfo{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: &v1,
},
IncludeAmbient: false,
ExpectedError: "no resource plugin 'pulumi-resource-myplugin' found in the workspace at version v0.1.0",
},
{
Name: "ResourceWithoutVersion",
Plugin: PluginInfo{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: nil,
},
IncludeAmbient: true,
ExpectedError: "no resource plugin 'pulumi-resource-myplugin' found in the workspace or on your $PATH",
},
{
Name: "ResourceWithoutVersion_ExcludeAmbient",
Plugin: PluginInfo{
Name: "myplugin",
Kind: apitype.ResourcePlugin,
Version: nil,
},
IncludeAmbient: false,
ExpectedError: "no resource plugin 'pulumi-resource-myplugin' found in the workspace",
},
{
Name: "LanguageWithoutVersion",
Plugin: PluginInfo{
Name: "dotnet",
Kind: apitype.LanguagePlugin,
Version: nil,
},
IncludeAmbient: true,
ExpectedError: "no language plugin 'pulumi-language-dotnet' found in the workspace or on your $PATH",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.Name, func(t *testing.T) {
t.Parallel()
err := NewMissingError(tt.Plugin.Kind, tt.Plugin.Name, tt.Plugin.Version, tt.IncludeAmbient)
assert.EqualError(t, err, tt.ExpectedError)
})
}
}
//nolint:paralleltest // modifies environment variables
func TestBundledPluginSearch(t *testing.T) {
// Get the path of this executable
exe, err := os.Executable()
require.NoError(t, err)
// Create a fake side-by-side plugin next to this executable, it must match one of our bundled names
bundledPath := filepath.Join(filepath.Dir(exe), "pulumi-language-nodejs")
err = os.WriteFile(bundledPath, []byte{}, 0o700) //nolint: gosec // we intended to write an executable file here
require.NoError(t, err)
bundledPath, _ = filepath.EvalSymlinks(bundledPath)
t.Cleanup(func() {
err := os.Remove(bundledPath)
require.NoError(t, err)
})
// Create another copy of the fake plugin in $PATH
pathDir := t.TempDir()
t.Setenv("PATH", pathDir)
ambientPath := filepath.Join(pathDir, "pulumi-language-nodejs")
err = os.WriteFile(ambientPath, []byte{}, 0o700) //nolint: gosec
require.NoError(t, err)
d := diagtest.LogSink(t)
// Lookup the plugin with ambient search turned on
t.Setenv("PULUMI_IGNORE_AMBIENT_PLUGINS", "false")
path, err := GetPluginPath(d, apitype.LanguagePlugin, "nodejs", nil, nil)
require.NoError(t, err)
assert.Equal(t, ambientPath, path)
// Lookup the plugin with ambient search turned off
t.Setenv("PULUMI_IGNORE_AMBIENT_PLUGINS", "true")
path, err = GetPluginPath(d, apitype.LanguagePlugin, "nodejs", nil, nil)
require.NoError(t, err)
assert.Equal(t, bundledPath, path)
}
//nolint:paralleltest // modifies environment variables
func TestAmbientPluginsWarn(t *testing.T) {
// Create a fake plugin in the path
pathDir := t.TempDir()
t.Setenv("PATH", pathDir)
ambientPath := filepath.Join(pathDir, "pulumi-resource-mock")
err := os.WriteFile(ambientPath, []byte{}, 0o700) //nolint: gosec
require.NoError(t, err)
var stderr bytes.Buffer
d := diag.DefaultSink(
iotest.LogWriter(t), // stdout
&stderr,
diag.FormatOptions{Color: "never"},
)
// Lookup the plugin with ambient search turned on
t.Setenv("PULUMI_IGNORE_AMBIENT_PLUGINS", "false")
path, err := GetPluginPath(d, apitype.ResourcePlugin, "mock", nil, nil)
require.NoError(t, err)
assert.Equal(t, ambientPath, path)
// Check we get a warning about loading this plugin
expectedMessage := fmt.Sprintf("warning: using pulumi-resource-mock from $PATH at %s\n", ambientPath)
assert.Equal(t, expectedMessage, stderr.String())
}
//nolint:paralleltest // modifies environment variables
func TestBundledPluginsDoNotWarn(t *testing.T) {
// Get the path of this executable
exe, err := os.Executable()
require.NoError(t, err)
// Create a fake side-by-side plugin next to this executable, it must match one of our bundled names
bundledPath := filepath.Join(filepath.Dir(exe), "pulumi-language-nodejs")
err = os.WriteFile(bundledPath, []byte{}, 0o700) //nolint: gosec // we intended to write an executable file here
require.NoError(t, err)
t.Cleanup(func() {
err := os.Remove(bundledPath)
require.NoError(t, err)
})
// Add the executable directory to PATH
t.Setenv("PATH", filepath.Dir(exe))
var stderr bytes.Buffer
d := diag.DefaultSink(
iotest.LogWriter(t), // stdout
&stderr,
diag.FormatOptions{Color: "never"},
)
// Lookup the plugin with ambient search turned on
t.Setenv("PULUMI_IGNORE_AMBIENT_PLUGINS", "false")
path, err := GetPluginPath(d, apitype.LanguagePlugin, "nodejs", nil, nil)
require.NoError(t, err)
assert.Equal(t, bundledPath, path)
// Check we don't get a warning about loading this plugin, because it's the bundled one _even_ though it's also on PATH
assert.Empty(t, stderr.String())
}
// Regression test for https://github.com/pulumi/pulumi/issues/13656
//
//nolint:paralleltest // modifies environment variables
func TestSymlinkPathPluginsDoNotWarn(t *testing.T) {
// Get the path of this executable
exe, err := os.Executable()
require.NoError(t, err)
// Create a fake side-by-side plugin next to this executable, it must match one of our bundled names
bundledPath := filepath.Join(filepath.Dir(exe), "pulumi-language-nodejs")
err = os.WriteFile(bundledPath, []byte{}, 0o700) //nolint: gosec
require.NoError(t, err)
t.Cleanup(func() {
err := os.Remove(bundledPath)
require.NoError(t, err)
})
// Create a fake plugin in the path that is a symlink to the bundled plugin
pathDir := t.TempDir()
t.Setenv("PATH", pathDir)
ambientPath := filepath.Join(pathDir, "pulumi-language-nodejs")
err = os.Symlink(bundledPath, ambientPath)
require.NoError(t, err)
var stderr bytes.Buffer
d := diag.DefaultSink(
iotest.LogWriter(t), // stdout
&stderr,
diag.FormatOptions{Color: "never"},
)
// Lookup the plugin with ambient search turned on
t.Setenv("PULUMI_IGNORE_AMBIENT_PLUGINS", "false")
path, err := GetPluginPath(d, apitype.LanguagePlugin, "nodejs", nil, nil)
require.NoError(t, err)
// We expect the ambient path to be returned, but not to warn because it resolves to the same file as the
// bundled path.
assert.Equal(t, ambientPath, path)
assert.Empty(t, stderr.String())
}
// Test that GetPluginInfo works against shimless plugins (i.e. those without a direct executable file).
//
//nolint:paralleltest // modifies environment variables
func TestPluginInfoShimless(t *testing.T) {
// Create a fake plugin in temp
pathDir := t.TempDir()
pluginPath := filepath.Join(pathDir, "pulumi-resource-mock")
err := os.MkdirAll(pluginPath, 0o700) //nolint: gosec
require.NoError(t, err)
err = os.WriteFile(filepath.Join(pluginPath, "PulumiPlugin.yaml"), []byte(`runtime: nodejs`), 0o600)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(pluginPath, "test.ts"), []byte(`testcode`), 0o600)
require.NoError(t, err)
stat, err := os.Stat(pluginPath)
require.NoError(t, err)
var stderr bytes.Buffer
d := diag.DefaultSink(
iotest.LogWriter(t), // stdout
&stderr,
diag.FormatOptions{Color: "never"},
)
info, err := GetPluginInfo(d, apitype.ResourcePlugin, "mock", nil, []ProjectPlugin{
{
Name: "mock",
Kind: apitype.ResourcePlugin,
Path: pluginPath,
},
})
require.NoError(t, err)
assert.Equal(t, pluginPath, info.Path)
assert.Equal(t, int64(23), info.Size)
assert.Equal(t, stat.ModTime(), info.InstallTime)
assert.Equal(t, stat.ModTime(), info.SchemaTime)
// schemaPaths are odd, they're one directory up from the plugin directory
assert.Equal(t, filepath.Join(filepath.Dir(pluginPath), "schema-mock.json"), info.SchemaPath)
}
//nolint:paralleltest // modifies environment variables
func TestProjectPluginsWithUncleanPath(t *testing.T) {
tempdir := t.TempDir()
err := os.WriteFile(filepath.Join(tempdir, "pulumi-resource-aws"), []byte{}, 0o600)
require.NoError(t, err)
t.Setenv("PULUMI_IGNORE_AMBIENT_PLUGINS", "false")
path, err := GetPluginPath(diagtest.LogSink(t), apitype.ResourcePlugin, "aws", nil, []ProjectPlugin{
{
Name: "aws",
Kind: apitype.ResourcePlugin,
Path: tempdir + "/", // path with a trailing slash
},
})
require.NoError(t, err)
assert.Equal(t, filepath.Join(tempdir, "pulumi-resource-aws"), path)
}
//nolint:paralleltest // modifies environment variables
func TestProjectPluginsWithSymlink(t *testing.T) {
tempdir := t.TempDir()
err := os.Mkdir(filepath.Join(tempdir, "subdir"), 0o700)
require.NoError(t, err)
err = os.Symlink(filepath.Join(tempdir, "subdir"), filepath.Join(tempdir, "symlink"))
require.NoError(t, err)
err = os.WriteFile(filepath.Join(tempdir, "subdir", "pulumi-resource-aws"), []byte{}, 0o600)
require.NoError(t, err)
t.Setenv("PULUMI_IGNORE_AMBIENT_PLUGINS", "false")
path, err := GetPluginPath(diagtest.LogSink(t), apitype.ResourcePlugin, "aws", nil, []ProjectPlugin{
{
Name: "aws",
Kind: apitype.ResourcePlugin,
Path: filepath.Join(tempdir, "symlink"),
},
})
require.NoError(t, err)
assert.Equal(t, filepath.Join(tempdir, "symlink", "pulumi-resource-aws"), path)
}